diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 06d163c4e52..ab8feed120d 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -73,6 +73,7 @@ go_library( "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/util/cert:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ], @@ -102,6 +103,7 @@ go_test( "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", "//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec/testing:go_default_library", ], diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index e42ccd9dbac..ed95b39399f 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -35,6 +35,7 @@ import ( "k8s.io/apimachinery/pkg/util/duration" clientset "k8s.io/client-go/kubernetes" bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" + "k8s.io/client-go/tools/clientcmd" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" @@ -50,6 +51,8 @@ import ( api "k8s.io/kubernetes/pkg/apis/core" ) +const defaultKubeConfig = "/etc/kubernetes/admin.conf" + // NewCmdToken returns cobra.Command for token management func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { var kubeConfigFile string @@ -85,7 +88,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { } tokenCmd.PersistentFlags().StringVar(&kubeConfigFile, - "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster") + "kubeconfig", defaultKubeConfig, "The KubeConfig file to use when talking to the cluster. If the flag is not set a set of standard locations are searched for an existing KubeConfig file") tokenCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", dryRun, "Whether to enable dry-run mode or not") @@ -121,6 +124,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { kubeadmutil.CheckErr(err) glog.V(1).Infoln("[token] getting Clientsets from KubeConfig file") + kubeConfigFile = findExistingKubeConfig(kubeConfigFile) client, err := getClientset(kubeConfigFile, dryRun) kubeadmutil.CheckErr(err) @@ -152,6 +156,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { This command will list all bootstrap tokens for you. `), Run: func(tokenCmd *cobra.Command, args []string) { + kubeConfigFile = findExistingKubeConfig(kubeConfigFile) client, err := getClientset(kubeConfigFile, dryRun) kubeadmutil.CheckErr(err) @@ -175,6 +180,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { if len(args) < 1 { kubeadmutil.CheckErr(fmt.Errorf("missing subcommand; 'token delete' is missing token of form [%q]", tokenutil.TokenIDRegexpString)) } + kubeConfigFile = findExistingKubeConfig(kubeConfigFile) client, err := getClientset(kubeConfigFile, dryRun) kubeadmutil.CheckErr(err) @@ -381,3 +387,16 @@ func getClientset(file string, dryRun bool) (clientset.Interface, error) { } return kubeconfigutil.ClientSetFromFile(file) } + +func findExistingKubeConfig(file string) string { + // The user did provide a --kubeconfig flag. Respect that and threat it as an + // explicit path without building a DefaultClientConfigLoadingRules object. + if file != defaultKubeConfig { + return file + } + // The user did not provide a --kubeconfig flag. Find a config in the standard + // locations using DefaultClientConfigLoadingRules, but also consider `defaultKubeConfig`. + rules := clientcmd.NewDefaultClientConfigLoadingRules() + rules.Precedence = append(rules.Precedence, defaultKubeConfig) + return rules.GetDefaultFilename() +} diff --git a/cmd/kubeadm/app/cmd/token_test.go b/cmd/kubeadm/app/cmd/token_test.go index e9d25088366..a5dcf59ead7 100644 --- a/cmd/kubeadm/app/cmd/token_test.go +++ b/cmd/kubeadm/app/cmd/token_test.go @@ -37,12 +37,13 @@ import ( "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" + "k8s.io/client-go/tools/clientcmd" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" ) const ( - TokenExpectedRegex = "^\\S{6}\\.\\S{16}\n$" - TestConfig = `apiVersion: v1 + tokenExpectedRegex = "^\\S{6}\\.\\S{16}\n$" + testConfigToken = `apiVersion: v1 clusters: - cluster: certificate-authority-data: @@ -63,8 +64,8 @@ users: client-certificate-data: client-key-data: ` - TestConfigCertAuthorityData = "certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFM01USXhOREUxTlRFek1Gb1hEVEkzTVRJeE1qRTFOVEV6TUZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTlZrCnNkT0NjRDBIOG9ycXZ5djBEZ09jZEpjRGc4aTJPNGt3QVpPOWZUanJGRHJqbDZlVXRtdlMyZ1lZd0c4TGhPV2gKb0lkZ3AvbVkrbVlDakliUUJtTmE2Ums1V2JremhJRzM1c1lseE9NVUJJR0xXMzN0RTh4SlR1RVd3V0NmZnpLcQpyaU1UT1A3REF3MUxuM2xUNlpJNGRNM09NOE1IUk9Wd3lRMDVpbWo5eUx5R1lYdTlvSncwdTVXWVpFYmpUL3VpCjJBZ2QwVDMrZGFFb044aVBJOTlVQkQxMzRkc2VGSEJEY3hHcmsvVGlQdHBpSC9IOGoxRWZaYzRzTGlONzJmL2YKYUpacTROSHFiT2F5UkpITCtJejFNTW1DRkN3cjdHOHVENWVvWWp2dEdZN2xLc1pBTlUwK3VlUnJsTitxTzhQWQpxaTZNMDFBcmV1UzFVVHFuTkM4Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFNbXo4Nm9LMmFLa0owMnlLSC9ZcTlzaDZZcDEKYmhLS25mMFJCaTA1clRacWdhTi9oTnROdmQxSzJxZGRLNzhIT2pVdkpNRGp3NERieXF0Wll2V01XVFRCQnQrSgpPMGNyWkg5NXlqUW42YzRlcU1FTjFhOUFKNXRlclNnTDVhREJsK0FMTWxaNVpxTzBUOUJDdTJtNXV3dGNWaFZuCnh6cGpTT3V5WVdOQ3A5bW9mV2VPUTljNXhEcElWeUlMUkFvNmZ5Z2c3N25TSDN4ckVmd0VKUHFMd1RPYVk1bTcKeEZWWWJoR3dxUGU5V0I5aTR5cnNrZUFBWlpUSzdVbklKMXFkRmlHQk9aZlRtaDhYQ3BOTHZZcFBLQW9hWWlsRwpjOW1acVhpWVlESTV6R1IxMElpc2FWNXJUY2hDenNQVWRhQzRVbnpTZG01cTdKYTAyb0poQlU1TE1FMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" - TestConfigNoCluster = `apiVersion: v1 + testConfigTokenCertAuthorityData = "certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFM01USXhOREUxTlRFek1Gb1hEVEkzTVRJeE1qRTFOVEV6TUZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTlZrCnNkT0NjRDBIOG9ycXZ5djBEZ09jZEpjRGc4aTJPNGt3QVpPOWZUanJGRHJqbDZlVXRtdlMyZ1lZd0c4TGhPV2gKb0lkZ3AvbVkrbVlDakliUUJtTmE2Ums1V2JremhJRzM1c1lseE9NVUJJR0xXMzN0RTh4SlR1RVd3V0NmZnpLcQpyaU1UT1A3REF3MUxuM2xUNlpJNGRNM09NOE1IUk9Wd3lRMDVpbWo5eUx5R1lYdTlvSncwdTVXWVpFYmpUL3VpCjJBZ2QwVDMrZGFFb044aVBJOTlVQkQxMzRkc2VGSEJEY3hHcmsvVGlQdHBpSC9IOGoxRWZaYzRzTGlONzJmL2YKYUpacTROSHFiT2F5UkpITCtJejFNTW1DRkN3cjdHOHVENWVvWWp2dEdZN2xLc1pBTlUwK3VlUnJsTitxTzhQWQpxaTZNMDFBcmV1UzFVVHFuTkM4Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFNbXo4Nm9LMmFLa0owMnlLSC9ZcTlzaDZZcDEKYmhLS25mMFJCaTA1clRacWdhTi9oTnROdmQxSzJxZGRLNzhIT2pVdkpNRGp3NERieXF0Wll2V01XVFRCQnQrSgpPMGNyWkg5NXlqUW42YzRlcU1FTjFhOUFKNXRlclNnTDVhREJsK0FMTWxaNVpxTzBUOUJDdTJtNXV3dGNWaFZuCnh6cGpTT3V5WVdOQ3A5bW9mV2VPUTljNXhEcElWeUlMUkFvNmZ5Z2c3N25TSDN4ckVmd0VKUHFMd1RPYVk1bTcKeEZWWWJoR3dxUGU5V0I5aTR5cnNrZUFBWlpUSzdVbklKMXFkRmlHQk9aZlRtaDhYQ3BOTHZZcFBLQW9hWWlsRwpjOW1acVhpWVlESTV6R1IxMElpc2FWNXJUY2hDenNQVWRhQzRVbnpTZG01cTdKYTAyb0poQlU1TE1FMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" + testConfigTokenNoCluster = `apiVersion: v1 clusters: - cluster: server: @@ -89,12 +90,12 @@ func TestRunGenerateToken(t *testing.T) { output := buf.String() - matched, err := regexp.MatchString(TokenExpectedRegex, output) + matched, err := regexp.MatchString(tokenExpectedRegex, output) if err != nil { t.Fatalf("Encountered an error while trying to match RunGenerateToken's output: %v", err) } if !matched { - t.Errorf("RunGenerateToken's output did not match expected regex; wanted: [%s], got: [%s]", TokenExpectedRegex, output) + t.Errorf("RunGenerateToken's output did not match expected regex; wanted: [%s], got: [%s]", tokenExpectedRegex, output) } } @@ -211,14 +212,14 @@ func TestNewCmdTokenGenerate(t *testing.T) { func TestNewCmdToken(t *testing.T) { var buf, bufErr bytes.Buffer - testConfigFile := "test-config-file" + testConfigTokenFile := "test-config-file" tmpDir, err := ioutil.TempDir("", "kubeadm-token-test") if err != nil { t.Errorf("Unable to create temporary directory: %v", err) } defer os.RemoveAll(tmpDir) - fullPath := filepath.Join(tmpDir, testConfigFile) + fullPath := filepath.Join(tmpDir, testConfigTokenFile) f, err := os.Create(fullPath) if err != nil { @@ -230,6 +231,7 @@ func TestNewCmdToken(t *testing.T) { name string args []string configToWrite string + kubeConfigEnv string expectedError bool }{ { @@ -239,23 +241,39 @@ func TestNewCmdToken(t *testing.T) { expectedError: false, }, { - name: "valid: delete", + name: "valid: delete from --kubeconfig", args: []string{"delete", "abcdef.1234567890123456", "--dry-run", "--kubeconfig=" + fullPath}, - configToWrite: TestConfig, + configToWrite: testConfigToken, + expectedError: false, + }, + { + name: "valid: delete from " + clientcmd.RecommendedConfigPathEnvVar, + args: []string{"delete", "abcdef.1234567890123456", "--dry-run"}, + configToWrite: testConfigToken, + kubeConfigEnv: fullPath, expectedError: false, }, } - cmd := NewCmdToken(&buf, &bufErr) for _, tc := range testCases { + // the command is created for each test so that the kubeConfigFile + // variable in NewCmdToken() is reset. + cmd := NewCmdToken(&buf, &bufErr) if _, err = f.WriteString(tc.configToWrite); err != nil { t.Errorf("Unable to write test file %q: %v", fullPath, err) } + // store the current value of the environment variable. + storedEnv := os.Getenv(clientcmd.RecommendedConfigPathEnvVar) + if tc.kubeConfigEnv != "" { + os.Setenv(clientcmd.RecommendedConfigPathEnvVar, tc.kubeConfigEnv) + } cmd.SetArgs(tc.args) err := cmd.Execute() if (err != nil) != tc.expectedError { t.Errorf("Test case %q: NewCmdToken expected error: %v, saw: %v", tc.name, tc.expectedError, (err != nil)) } + // restore the environment variable. + os.Setenv(clientcmd.RecommendedConfigPathEnvVar, storedEnv) } } @@ -276,14 +294,14 @@ func TestGetSecretString(t *testing.T) { } func TestGetClientset(t *testing.T) { - testConfigFile := "test-config-file" + testConfigTokenFile := "test-config-file" tmpDir, err := ioutil.TempDir("", "kubeadm-token-test") if err != nil { t.Errorf("Unable to create temporary directory: %v", err) } defer os.RemoveAll(tmpDir) - fullPath := filepath.Join(tmpDir, testConfigFile) + fullPath := filepath.Join(tmpDir, testConfigTokenFile) // test dryRun = false on a non-exisiting file if _, err = getClientset(fullPath, false); err == nil { @@ -301,7 +319,7 @@ func TestGetClientset(t *testing.T) { } defer f.Close() - if _, err = f.WriteString(TestConfig); err != nil { + if _, err = f.WriteString(testConfigToken); err != nil { t.Errorf("Unable to write test file %q: %v", fullPath, err) } @@ -327,7 +345,7 @@ func TestRunDeleteToken(t *testing.T) { } defer f.Close() - if _, err = f.WriteString(TestConfig); err != nil { + if _, err = f.WriteString(testConfigToken); err != nil { t.Errorf("Unable to write test file %q: %v", fullPath, err) } @@ -369,7 +387,7 @@ func TestRunListTokens(t *testing.T) { defer f.Close() // test config without secrets; should fail - if _, err = f.WriteString(TestConfig); err != nil { + if _, err = f.WriteString(testConfigToken); err != nil { t.Errorf("Unable to write test file %q: %v", fullPath, err) } @@ -394,9 +412,9 @@ func TestRunListTokens(t *testing.T) { }() fmt.Printf("dummy API server listening on localhost:%s\n", portString) - testConfigOpenPort := strings.Replace(TestConfig, "server: localhost:8000", "server: localhost:"+portString, -1) + testConfigTokenOpenPort := strings.Replace(testConfigToken, "server: localhost:8000", "server: localhost:"+portString, -1) - if _, err = f.WriteString(testConfigOpenPort); err != nil { + if _, err = f.WriteString(testConfigTokenOpenPort); err != nil { t.Errorf("Unable to write test file %q: %v", fullPath, err) }