From 82c700c89673bae8459a14477c3a1527ae295c83 Mon Sep 17 00:00:00 2001 From: abrand Date: Fri, 29 Jul 2016 23:34:13 -0400 Subject: [PATCH] Add get/delete cluster, delete context to kubectl --- .generated_docs | 6 ++ docs/man/man1/kubectl-config-delete-cluster.1 | 3 + docs/man/man1/kubectl-config-delete-context.1 | 3 + docs/man/man1/kubectl-config-get-clusters.1 | 3 + .../kubectl/kubectl_config_delete-cluster.md | 36 +++++++ .../kubectl/kubectl_config_delete-context.md | 36 +++++++ .../kubectl/kubectl_config_get-clusters.md | 36 +++++++ pkg/kubectl/cmd/config/config.go | 4 + pkg/kubectl/cmd/config/delete_cluster.go | 73 ++++++++++++++ pkg/kubectl/cmd/config/delete_cluster_test.go | 94 +++++++++++++++++++ pkg/kubectl/cmd/config/delete_context.go | 73 ++++++++++++++ pkg/kubectl/cmd/config/delete_context_test.go | 94 +++++++++++++++++++ pkg/kubectl/cmd/config/get_clusters.go | 55 +++++++++++ pkg/kubectl/cmd/config/get_clusters_test.go | 81 ++++++++++++++++ 14 files changed, 597 insertions(+) create mode 100644 docs/man/man1/kubectl-config-delete-cluster.1 create mode 100644 docs/man/man1/kubectl-config-delete-context.1 create mode 100644 docs/man/man1/kubectl-config-get-clusters.1 create mode 100644 docs/user-guide/kubectl/kubectl_config_delete-cluster.md create mode 100644 docs/user-guide/kubectl/kubectl_config_delete-context.md create mode 100644 docs/user-guide/kubectl/kubectl_config_get-clusters.md create mode 100644 pkg/kubectl/cmd/config/delete_cluster.go create mode 100644 pkg/kubectl/cmd/config/delete_cluster_test.go create mode 100644 pkg/kubectl/cmd/config/delete_context.go create mode 100644 pkg/kubectl/cmd/config/delete_context_test.go create mode 100644 pkg/kubectl/cmd/config/get_clusters.go create mode 100644 pkg/kubectl/cmd/config/get_clusters_test.go diff --git a/.generated_docs b/.generated_docs index 1bcefdf9d55..8e39d99bdb7 100644 --- a/.generated_docs +++ b/.generated_docs @@ -15,6 +15,9 @@ docs/man/man1/kubectl-cluster-info-dump.1 docs/man/man1/kubectl-cluster-info.1 docs/man/man1/kubectl-completion.1 docs/man/man1/kubectl-config-current-context.1 +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-set-cluster.1 docs/man/man1/kubectl-config-set-context.1 @@ -77,6 +80,9 @@ docs/user-guide/kubectl/kubectl_cluster-info_dump.md docs/user-guide/kubectl/kubectl_completion.md docs/user-guide/kubectl/kubectl_config.md docs/user-guide/kubectl/kubectl_config_current-context.md +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_set-cluster.md docs/user-guide/kubectl/kubectl_config_set-context.md diff --git a/docs/man/man1/kubectl-config-delete-cluster.1 b/docs/man/man1/kubectl-config-delete-cluster.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-config-delete-cluster.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/man/man1/kubectl-config-delete-context.1 b/docs/man/man1/kubectl-config-delete-context.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-config-delete-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/man/man1/kubectl-config-get-clusters.1 b/docs/man/man1/kubectl-config-get-clusters.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-config-get-clusters.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_delete-cluster.md b/docs/user-guide/kubectl/kubectl_config_delete-cluster.md new file mode 100644 index 00000000000..c917a833ff9 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_config_delete-cluster.md @@ -0,0 +1,36 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +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. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_config_delete-cluster.md?pixel)]() + diff --git a/docs/user-guide/kubectl/kubectl_config_delete-context.md b/docs/user-guide/kubectl/kubectl_config_delete-context.md new file mode 100644 index 00000000000..488025bd1cf --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_config_delete-context.md @@ -0,0 +1,36 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +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. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_config_delete-context.md?pixel)]() + diff --git a/docs/user-guide/kubectl/kubectl_config_get-clusters.md b/docs/user-guide/kubectl/kubectl_config_get-clusters.md new file mode 100644 index 00000000000..9cc1dba517b --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_config_get-clusters.md @@ -0,0 +1,36 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +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. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_config_get-clusters.md?pixel)]() + diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index 36a14350314..6e9ff162f70 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -26,6 +26,7 @@ import ( "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" ) +// NewCmdConfig creates a command object for the "config" action, and adds all child commands to it. func NewCmdConfig(pathOptions *clientcmd.PathOptions, out io.Writer) *cobra.Command { if len(pathOptions.ExplicitFileFlag) == 0 { pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag @@ -58,6 +59,9 @@ The loading order follows these rules: cmd.AddCommand(NewCmdConfigCurrentContext(out, pathOptions)) cmd.AddCommand(NewCmdConfigUseContext(out, pathOptions)) cmd.AddCommand(NewCmdConfigGetContexts(out, pathOptions)) + cmd.AddCommand(NewCmdConfigGetClusters(out, pathOptions)) + cmd.AddCommand(NewCmdConfigDeleteCluster(out, pathOptions)) + cmd.AddCommand(NewCmdConfigDeleteContext(out, pathOptions)) return cmd } diff --git a/pkg/kubectl/cmd/config/delete_cluster.go b/pkg/kubectl/cmd/config/delete_cluster.go new file mode 100644 index 00000000000..24b30497894 --- /dev/null +++ b/pkg/kubectl/cmd/config/delete_cluster.go @@ -0,0 +1,73 @@ +/* +Copyright 2016 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 ( + "fmt" + "io" + + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +func NewCmdConfigDeleteCluster(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete-cluster NAME", + Short: "Delete the specified cluster from the kubeconfig", + Run: func(cmd *cobra.Command, args []string) { + err := runDeleteCluster(out, configAccess, cmd) + cmdutil.CheckErr(err) + }, + } + + return cmd +} + +func runDeleteCluster(out io.Writer, configAccess clientcmd.ConfigAccess, cmd *cobra.Command) error { + config, err := configAccess.GetStartingConfig() + if err != nil { + return err + } + + args := cmd.Flags().Args() + if len(args) != 1 { + cmd.Help() + return nil + } + + configFile := configAccess.GetDefaultFilename() + if configAccess.IsExplicitFile() { + configFile = configAccess.GetExplicitFile() + } + + name := args[0] + _, ok := config.Clusters[name] + if !ok { + return fmt.Errorf("cannot delete cluster %s, not in %s", name, configFile) + } + + delete(config.Clusters, name) + + if err := clientcmd.ModifyConfig(configAccess, *config, true); err != nil { + return err + } + + fmt.Fprintf(out, "deleted cluster %s from %s", name, configFile) + + return nil +} diff --git a/pkg/kubectl/cmd/config/delete_cluster_test.go b/pkg/kubectl/cmd/config/delete_cluster_test.go new file mode 100644 index 00000000000..55d223cba86 --- /dev/null +++ b/pkg/kubectl/cmd/config/delete_cluster_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2016 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" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" +) + +type deleteClusterTest struct { + config clientcmdapi.Config + clusterToDelete string + expectedClusters []string + expectedOut string +} + +func TestDeleteCluster(t *testing.T) { + conf := clientcmdapi.Config{ + Clusters: map[string]*clientcmdapi.Cluster{ + "minikube": {Server: "https://192.168.0.99"}, + "otherkube": {Server: "https://192.168.0.100"}, + }, + } + test := deleteClusterTest{ + config: conf, + clusterToDelete: "minikube", + expectedClusters: []string{"otherkube"}, + expectedOut: "deleted cluster minikube from %s", + } + + test.run(t) +} + +func (test deleteClusterTest) run(t *testing.T) { + fakeKubeFile, _ := ioutil.TempFile("", "") + defer os.Remove(fakeKubeFile.Name()) + err := clientcmd.WriteToFile(test.config, fakeKubeFile.Name()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + pathOptions := clientcmd.NewDefaultPathOptions() + pathOptions.GlobalFile = fakeKubeFile.Name() + pathOptions.EnvVar = "" + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdConfigDeleteCluster(buf, pathOptions) + cmd.SetArgs([]string{test.clusterToDelete}) + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error executing command: %v", err) + } + + expectedOutWithFile := fmt.Sprintf(test.expectedOut, fakeKubeFile.Name()) + if expectedOutWithFile != buf.String() { + t.Errorf("expected output %s, but got %s", expectedOutWithFile, buf.String()) + return + } + + // Verify cluster was removed from kubeconfig file + config, err := clientcmd.LoadFromFile(fakeKubeFile.Name()) + if err != nil { + t.Fatalf("unexpected error loading kubeconfig file: %v", err) + } + + clusters := make([]string, 0, len(config.Clusters)) + for k := range config.Clusters { + clusters = append(clusters, k) + } + + if !reflect.DeepEqual(test.expectedClusters, clusters) { + t.Errorf("expected clusters %v, but found %v in kubeconfig", test.expectedClusters, clusters) + } +} diff --git a/pkg/kubectl/cmd/config/delete_context.go b/pkg/kubectl/cmd/config/delete_context.go new file mode 100644 index 00000000000..9a56246deff --- /dev/null +++ b/pkg/kubectl/cmd/config/delete_context.go @@ -0,0 +1,73 @@ +/* +Copyright 2016 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 ( + "fmt" + "io" + + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +func NewCmdConfigDeleteContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete-context NAME", + Short: "Delete the specified context from the kubeconfig", + Run: func(cmd *cobra.Command, args []string) { + err := runDeleteContext(out, configAccess, cmd) + cmdutil.CheckErr(err) + }, + } + + return cmd +} + +func runDeleteContext(out io.Writer, configAccess clientcmd.ConfigAccess, cmd *cobra.Command) error { + config, err := configAccess.GetStartingConfig() + if err != nil { + return err + } + + args := cmd.Flags().Args() + if len(args) != 1 { + cmd.Help() + return nil + } + + configFile := configAccess.GetDefaultFilename() + if configAccess.IsExplicitFile() { + configFile = configAccess.GetExplicitFile() + } + + name := args[0] + _, ok := config.Contexts[name] + if !ok { + return fmt.Errorf("cannot delete context %s, not in %s", name, configFile) + } + + delete(config.Contexts, name) + + if err := clientcmd.ModifyConfig(configAccess, *config, true); err != nil { + return err + } + + fmt.Fprintf(out, "deleted context %s from %s", name, configFile) + + return nil +} diff --git a/pkg/kubectl/cmd/config/delete_context_test.go b/pkg/kubectl/cmd/config/delete_context_test.go new file mode 100644 index 00000000000..c575dd26148 --- /dev/null +++ b/pkg/kubectl/cmd/config/delete_context_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2016 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" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" +) + +type deleteContextTest struct { + config clientcmdapi.Config + contextToDelete string + expectedContexts []string + expectedOut string +} + +func TestDeleteContext(t *testing.T) { + conf := clientcmdapi.Config{ + Contexts: map[string]*clientcmdapi.Context{ + "minikube": {Cluster: "minikube"}, + "otherkube": {Cluster: "otherkube"}, + }, + } + test := deleteContextTest{ + config: conf, + contextToDelete: "minikube", + expectedContexts: []string{"otherkube"}, + expectedOut: "deleted context minikube from %s", + } + + test.run(t) +} + +func (test deleteContextTest) run(t *testing.T) { + fakeKubeFile, _ := ioutil.TempFile("", "") + defer os.Remove(fakeKubeFile.Name()) + err := clientcmd.WriteToFile(test.config, fakeKubeFile.Name()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + pathOptions := clientcmd.NewDefaultPathOptions() + pathOptions.GlobalFile = fakeKubeFile.Name() + pathOptions.EnvVar = "" + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdConfigDeleteContext(buf, pathOptions) + cmd.SetArgs([]string{test.contextToDelete}) + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error executing command: %v", err) + } + + expectedOutWithFile := fmt.Sprintf(test.expectedOut, fakeKubeFile.Name()) + if expectedOutWithFile != buf.String() { + t.Errorf("expected output %s, but got %s", expectedOutWithFile, buf.String()) + return + } + + // Verify context was removed from kubeconfig file + config, err := clientcmd.LoadFromFile(fakeKubeFile.Name()) + if err != nil { + t.Fatalf("unexpected error loading kubeconfig file: %v", err) + } + + contexts := make([]string, 0, len(config.Contexts)) + for k := range config.Contexts { + contexts = append(contexts, k) + } + + if !reflect.DeepEqual(test.expectedContexts, contexts) { + t.Errorf("expected contexts %v, but found %v in kubeconfig", test.expectedContexts, contexts) + } +} diff --git a/pkg/kubectl/cmd/config/get_clusters.go b/pkg/kubectl/cmd/config/get_clusters.go new file mode 100644 index 00000000000..550be37b50d --- /dev/null +++ b/pkg/kubectl/cmd/config/get_clusters.go @@ -0,0 +1,55 @@ +/* +Copyright 2016 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 ( + "fmt" + "io" + + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +// NewCmdConfigGetClusters creates a command object for the "get-clusters" action, which +// lists all clusters defined in the kubeconfig. +func NewCmdConfigGetClusters(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { + cmd := &cobra.Command{ + Use: "get-clusters", + Short: "Display clusters defined in the kubeconfig", + Run: func(cmd *cobra.Command, args []string) { + err := runGetClusters(out, configAccess) + cmdutil.CheckErr(err) + }, + } + + return cmd +} + +func runGetClusters(out io.Writer, configAccess clientcmd.ConfigAccess) error { + config, err := configAccess.GetStartingConfig() + if err != nil { + return err + } + + fmt.Fprintf(out, "NAME\n") + for name := range config.Clusters { + fmt.Fprintf(out, "%s\n", name) + } + + return nil +} diff --git a/pkg/kubectl/cmd/config/get_clusters_test.go b/pkg/kubectl/cmd/config/get_clusters_test.go new file mode 100644 index 00000000000..8493de3fcd0 --- /dev/null +++ b/pkg/kubectl/cmd/config/get_clusters_test.go @@ -0,0 +1,81 @@ +/* +Copyright 2014 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" + "io/ioutil" + "os" + "testing" + + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" +) + +type getClustersTest struct { + config clientcmdapi.Config + expected string +} + +func TestGetClusters(t *testing.T) { + conf := clientcmdapi.Config{ + Clusters: map[string]*clientcmdapi.Cluster{ + "minikube": {Server: "https://192.168.0.99"}, + }, + } + test := getClustersTest{ + config: conf, + expected: `NAME +minikube +`, + } + + test.run(t) +} + +func TestGetClustersEmpty(t *testing.T) { + test := getClustersTest{ + config: clientcmdapi.Config{}, + expected: "NAME\n", + } + + test.run(t) +} + +func (test getClustersTest) run(t *testing.T) { + fakeKubeFile, _ := ioutil.TempFile("", "") + defer os.Remove(fakeKubeFile.Name()) + err := clientcmd.WriteToFile(test.config, fakeKubeFile.Name()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + pathOptions := clientcmd.NewDefaultPathOptions() + pathOptions.GlobalFile = fakeKubeFile.Name() + pathOptions.EnvVar = "" + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdConfigGetClusters(buf, pathOptions) + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error executing command: %v", err) + } + if len(test.expected) != 0 { + if buf.String() != test.expected { + t.Errorf("expected %v, but got %v", test.expected, buf.String()) + } + return + } +}