From 83fec5bafbda5066d741fa08ce03544bca5e8eca Mon Sep 17 00:00:00 2001 From: Arthur Miranda Date: Fri, 19 May 2017 13:07:54 -0300 Subject: [PATCH] Add `kubeclt config rename-context` Enables renaming of a context via `kubectl` Fix https://github.com/kubernetes/kubernetes/issues/45131 --- docs/.generated_docs | 2 + docs/man/man1/kubectl-config-rename-context.1 | 3 + .../kubectl/kubectl_config_rename-context.md | 3 + pkg/kubectl/cmd/config/BUILD | 2 + pkg/kubectl/cmd/config/config.go | 1 + pkg/kubectl/cmd/config/rename_context.go | 136 +++++++++++++++ pkg/kubectl/cmd/config/rename_context_test.go | 156 ++++++++++++++++++ 7 files changed, 303 insertions(+) create mode 100644 docs/man/man1/kubectl-config-rename-context.1 create mode 100644 docs/user-guide/kubectl/kubectl_config_rename-context.md create mode 100644 pkg/kubectl/cmd/config/rename_context.go create mode 100644 pkg/kubectl/cmd/config/rename_context_test.go diff --git a/docs/.generated_docs b/docs/.generated_docs index a288f3c408297..5dcce34547666 100644 --- a/docs/.generated_docs +++ b/docs/.generated_docs @@ -33,6 +33,7 @@ docs/man/man1/kubectl-config-delete-cluster.1 docs/man/man1/kubectl-config-delete-context.1 docs/man/man1/kubectl-config-get-clusters.1 docs/man/man1/kubectl-config-get-contexts.1 +docs/man/man1/kubectl-config-rename-context.1 docs/man/man1/kubectl-config-set-cluster.1 docs/man/man1/kubectl-config-set-context.1 docs/man/man1/kubectl-config-set-credentials.1 @@ -129,6 +130,7 @@ docs/user-guide/kubectl/kubectl_config_delete-cluster.md docs/user-guide/kubectl/kubectl_config_delete-context.md docs/user-guide/kubectl/kubectl_config_get-clusters.md docs/user-guide/kubectl/kubectl_config_get-contexts.md +docs/user-guide/kubectl/kubectl_config_rename-context.md docs/user-guide/kubectl/kubectl_config_set-cluster.md docs/user-guide/kubectl/kubectl_config_set-context.md docs/user-guide/kubectl/kubectl_config_set-credentials.md diff --git a/docs/man/man1/kubectl-config-rename-context.1 b/docs/man/man1/kubectl-config-rename-context.1 new file mode 100644 index 0000000000000..b6fd7a0f9896b --- /dev/null +++ b/docs/man/man1/kubectl-config-rename-context.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_config_rename-context.md b/docs/user-guide/kubectl/kubectl_config_rename-context.md new file mode 100644 index 0000000000000..b6fd7a0f9896b --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_config_rename-context.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/pkg/kubectl/cmd/config/BUILD b/pkg/kubectl/cmd/config/BUILD index a0eb1492a757e..e3a802f85b4f7 100644 --- a/pkg/kubectl/cmd/config/BUILD +++ b/pkg/kubectl/cmd/config/BUILD @@ -19,6 +19,7 @@ go_library( "get_clusters.go", "get_contexts.go", "navigation_step_parser.go", + "rename_context.go", "set.go", "unset.go", "use_context.go", @@ -57,6 +58,7 @@ go_test( "get_clusters_test.go", "get_contexts_test.go", "navigation_step_parser_test.go", + "rename_context_test.go", "set_test.go", "unset_test.go", "use_context_test.go", diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index 93ab585e6046e..c90586cc440cb 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -64,6 +64,7 @@ func NewCmdConfig(pathOptions *clientcmd.PathOptions, out, errOut io.Writer) *co cmd.AddCommand(NewCmdConfigGetClusters(out, pathOptions)) cmd.AddCommand(NewCmdConfigDeleteCluster(out, pathOptions)) cmd.AddCommand(NewCmdConfigDeleteContext(out, errOut, pathOptions)) + cmd.AddCommand(NewCmdConfigRenameContext(out, pathOptions)) return cmd } diff --git a/pkg/kubectl/cmd/config/rename_context.go b/pkg/kubectl/cmd/config/rename_context.go new file mode 100644 index 0000000000000..7d00eb83ac8c6 --- /dev/null +++ b/pkg/kubectl/cmd/config/rename_context.go @@ -0,0 +1,136 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "errors" + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/client-go/tools/clientcmd" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +// RenameContextOptions contains the options for running the rename-context cli command. +type RenameContextOptions struct { + configAccess clientcmd.ConfigAccess + contextName string + newName string +} + +const ( + renameContextUse = "rename-context CONTEXT_NAME NEW_NAME" + + renameContextShort = "Renames a context from the kubeconfig file." +) + +var ( + renameContextLong = templates.LongDesc(` + Renames a context from the kubeconfig file . + + CONTEXT_NAME is the context name that you wish change. + + NEW_NAME is the new name you wish to set. + + Note: In case the context being renamed is the 'current-context', this field will also be updated.`) + + renameContextExample = templates.Examples(` + # Rename the context 'old-name' to 'new-name' in your kubeconfig file + kubectl config rename-context old-name new-name`) +) + +// NewCmdConfigRenameContext creates a command object for the "rename-context" action +func NewCmdConfigRenameContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { + options := &RenameContextOptions{configAccess: configAccess} + + cmd := &cobra.Command{ + Use: renameContextUse, + Short: renameContextShort, + Long: renameContextLong, + Example: renameContextExample, + Run: func(cmd *cobra.Command, args []string) { + if err := options.Complete(cmd, args, out); err != nil { + cmdutil.CheckErr(err) + } + if err := options.Validate(); err != nil { + cmdutil.UsageError(cmd, err.Error()) + } + if err := options.RunRenameContext(out); err != nil { + cmdutil.CheckErr(err) + } + }, + } + return cmd +} + +// Complete assigns RenameContextOptions from the args. +func (o *RenameContextOptions) Complete(cmd *cobra.Command, args []string, out io.Writer) error { + if len(args) != 2 { + cmd.Help() + return fmt.Errorf("Unexpected args: %v", args) + } + + o.contextName = args[0] + o.newName = args[1] + return nil +} + +func (o RenameContextOptions) Validate() error { + if len(o.newName) == 0 { + return errors.New("You must specify a new non-empty context name") + } + return nil +} + +func (o RenameContextOptions) RunRenameContext(out io.Writer) error { + config, err := o.configAccess.GetStartingConfig() + if err != nil { + return err + } + + configFile := o.configAccess.GetDefaultFilename() + if o.configAccess.IsExplicitFile() { + configFile = o.configAccess.GetExplicitFile() + } + + context, exists := config.Contexts[o.contextName] + if !exists { + return fmt.Errorf("cannot rename the context %q, it's not in %s", o.contextName, configFile) + } + + _, newExists := config.Contexts[o.newName] + if newExists { + return fmt.Errorf("cannot rename the context %q, the context %q already exists in %s", o.contextName, o.newName, configFile) + } + + config.Contexts[o.newName] = context + delete(config.Contexts, o.contextName) + + if config.CurrentContext == o.contextName { + config.CurrentContext = o.newName + } + + if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil { + return err + } + + fmt.Fprintf(out, "Context %q was renamed to %q.\n", o.contextName, o.newName) + return nil +} diff --git a/pkg/kubectl/cmd/config/rename_context_test.go b/pkg/kubectl/cmd/config/rename_context_test.go new file mode 100644 index 0000000000000..4a074a49e8141 --- /dev/null +++ b/pkg/kubectl/cmd/config/rename_context_test.go @@ -0,0 +1,156 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" + "testing" + + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +const ( + currentContext = "current-context" + newContext = "new-context" + nonexistentCurrentContext = "nonexistent-current-context" + existentNewContext = "existent-new-context" +) + +var ( + contextData *clientcmdapi.Context = clientcmdapi.NewContext() +) + +type renameContextTest struct { + description string + initialConfig clientcmdapi.Config // initial config + expectedConfig clientcmdapi.Config // expected config + args []string // kubectl rename-context args + expectedOut string // expected out message + expectedErr string // expected error message +} + +func TestRenameContext(t *testing.T) { + initialConfig := clientcmdapi.Config{ + CurrentContext: currentContext, + Contexts: map[string]*clientcmdapi.Context{currentContext: contextData}} + + expectedConfig := clientcmdapi.Config{ + CurrentContext: newContext, + Contexts: map[string]*clientcmdapi.Context{newContext: contextData}} + + test := renameContextTest{ + description: "Testing for kubectl config rename-context whose context to be renamed is the CurrentContext", + initialConfig: initialConfig, + expectedConfig: expectedConfig, + args: []string{currentContext, newContext}, + expectedOut: fmt.Sprintf("Context %q was renamed to %q.\n", currentContext, newContext), + expectedErr: "", + } + test.run(t) +} + +func TestRenameNonexistentContext(t *testing.T) { + initialConfig := clientcmdapi.Config{ + CurrentContext: currentContext, + Contexts: map[string]*clientcmdapi.Context{currentContext: contextData}} + + test := renameContextTest{ + description: "Testing for kubectl config rename-context whose context to be renamed no exists", + initialConfig: initialConfig, + expectedConfig: initialConfig, + args: []string{nonexistentCurrentContext, newContext}, + expectedOut: "", + expectedErr: fmt.Sprintf("cannot rename the context %q, it's not in", nonexistentCurrentContext), + } + test.run(t) +} + +func TestRenameToAlreadyExistingContext(t *testing.T) { + initialConfig := clientcmdapi.Config{ + CurrentContext: currentContext, + Contexts: map[string]*clientcmdapi.Context{ + currentContext: contextData, + existentNewContext: contextData}} + + test := renameContextTest{ + description: "Testing for kubectl config rename-context whose the new name is already in another context.", + initialConfig: initialConfig, + expectedConfig: initialConfig, + args: []string{currentContext, existentNewContext}, + expectedOut: "", + expectedErr: fmt.Sprintf("cannot rename the context %q, the context %q already exists", currentContext, existentNewContext), + } + test.run(t) +} + +func (test renameContextTest) run(t *testing.T) { + fakeKubeFile, _ := ioutil.TempFile("", "") + defer os.Remove(fakeKubeFile.Name()) + err := clientcmd.WriteToFile(test.initialConfig, fakeKubeFile.Name()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + pathOptions := clientcmd.NewDefaultPathOptions() + pathOptions.GlobalFile = fakeKubeFile.Name() + pathOptions.EnvVar = "" + options := RenameContextOptions{ + configAccess: pathOptions, + contextName: test.args[0], + newName: test.args[1], + } + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdConfigRenameContext(buf, options.configAccess) + + options.Complete(cmd, test.args, buf) + options.Validate() + err = options.RunRenameContext(buf) + + if len(test.expectedErr) != 0 { + if err == nil { + t.Errorf("Did not get %v", test.expectedErr) + } else { + if !strings.Contains(err.Error(), test.expectedErr) { + t.Errorf("Expected error %v, but got %v", test.expectedErr, err) + } + } + return + } + + config, err := clientcmd.LoadFromFile(fakeKubeFile.Name()) + if err != nil { + t.Fatalf("unexpected error loading kubeconfig file: %v", err) + } + + _, oldExists := config.Contexts[currentContext] + _, newExists := config.Contexts[newContext] + + if (!newExists) || (oldExists) || (config.CurrentContext != newContext) { + t.Errorf("Failed in: %q\n expected %v\n but got %v", test.description, test.expectedConfig, *config) + } + + if len(test.expectedOut) != 0 { + if buf.String() != test.expectedOut { + t.Errorf("Failed in:%q\n expected out %v\n but got %v", test.description, test.expectedOut, buf.String()) + } + } +}