From e0ce987f4e8d923faf977ddca0bc5c9d2fc9b894 Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Tue, 5 Jul 2016 11:23:32 +0200 Subject: [PATCH] Add command "kubectl config get-contexts" Note: Context is not a runtime object (doesn't have Kind and Version) so we can't use the resource_printer --- .generated_docs | 2 + CHANGELOG.md | 10 +- docs/man/man1/kubectl-config-get-contexts.1 | 3 + .../kubectl/kubectl_config_get-contexts.md | 36 ++++ pkg/kubectl/cmd/config/config.go | 1 + pkg/kubectl/cmd/config/get_contexts.go | 168 ++++++++++++++++++ pkg/kubectl/cmd/config/get_contexts_test.go | 158 ++++++++++++++++ pkg/kubectl/cmd/util/printing.go | 14 +- 8 files changed, 384 insertions(+), 8 deletions(-) create mode 100644 docs/man/man1/kubectl-config-get-contexts.1 create mode 100644 docs/user-guide/kubectl/kubectl_config_get-contexts.md create mode 100644 pkg/kubectl/cmd/config/get_contexts.go create mode 100644 pkg/kubectl/cmd/config/get_contexts_test.go diff --git a/.generated_docs b/.generated_docs index f606691a406..f36b59e130d 100644 --- a/.generated_docs +++ b/.generated_docs @@ -15,6 +15,7 @@ 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-get-contexts.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 @@ -75,6 +76,7 @@ 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_get-contexts.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/CHANGELOG.md b/CHANGELOG.md index 02a08dfa79d..f6bbc1710ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,9 @@ - [v1.3.0](#v130) - [Downloads](#downloads) - - [Major Themes](#major-themes) - - [Other notable improvements](#other-notable-improvements) - - [Known Issues](#known-issues) + - [Highlights](#highlights) + - [Known Issues and Important Steps before Upgrading](#known-issues-and-important-steps-before-upgrading) - [Provider-specific Notes](#provider-specific-notes) - - [Changelog since v1.3.0-beta.3](#changelog-since-v130-beta3) - [Previous Releases Included in v1.3.0](#previous-releases-included-in-v130) - [v1.3.0-beta.3](#v130-beta3) - [Downloads](#downloads) @@ -102,7 +100,7 @@ binary | sha1 hash | md5 hash * Authorization: * **Alpha** RBAC authorization API group -* Federation +* Federation * federation api group is now **beta** * Services from all federated clusters are now registered in Cloud DNS (AWS and GCP). * Stateful Apps: @@ -139,7 +137,7 @@ binary | sha1 hash | md5 hash * ARP caching fix * Use /dev/xvdXX names * ELB: - * ELB proxy protocol support + * ELB proxy protocol support * mixed plaintext/encrypted ports support in ELBs * SSL support for ELB listeners * Allow VPC CIDR to be specified (experimental) diff --git a/docs/man/man1/kubectl-config-get-contexts.1 b/docs/man/man1/kubectl-config-get-contexts.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-config-get-contexts.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_get-contexts.md b/docs/user-guide/kubectl/kubectl_config_get-contexts.md new file mode 100644 index 00000000000..0df905e80a5 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_config_get-contexts.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-contexts.md?pixel)]() + diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index 2a3523a614a..1504fa92a5e 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -57,6 +57,7 @@ The loading order follows these rules: cmd.AddCommand(NewCmdConfigUnset(out, pathOptions)) cmd.AddCommand(NewCmdConfigCurrentContext(out, pathOptions)) cmd.AddCommand(NewCmdConfigUseContext(out, pathOptions)) + cmd.AddCommand(NewCmdConfigGetContexts(out, pathOptions)) return cmd } diff --git a/pkg/kubectl/cmd/config/get_contexts.go b/pkg/kubectl/cmd/config/get_contexts.go new file mode 100644 index 00000000000..cef853e76fe --- /dev/null +++ b/pkg/kubectl/cmd/config/get_contexts.go @@ -0,0 +1,168 @@ +/* +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" + "strings" + "text/tabwriter" + + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" + "k8s.io/kubernetes/pkg/kubectl" + + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/sets" +) + +// GetContextsOptions contains the assignable options from the args. +type GetContextsOptions struct { + configAccess clientcmd.ConfigAccess + nameOnly bool + showHeaders bool + contextNames []string + out io.Writer +} + +const ( + getContextsLong = `Displays one or many contexts from the kubeconfig file.` + + getContextsExample = `# List all the contexts in your kubeconfig file +kubectl config get-contexts + +# Describe one context in your kubeconfig file. +kubectl config get-contexts my-context` +) + +// NewCmdConfigGetContexts creates a command object for the "get-contexts" action, which +// retrieves one or more contexts from a kubeconfig. +func NewCmdConfigGetContexts(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { + options := &GetContextsOptions{configAccess: configAccess} + + cmd := &cobra.Command{ + Use: "get-contexts [(-o|--output=)name)]", + Short: "Describe one or many contexts", + Long: getContextsLong, + Example: getContextsExample, + Run: func(cmd *cobra.Command, args []string) { + validOutputTypes := sets.NewString("", "json", "yaml", "wide", "name", "go-template", "go-template-file", "jsonpath", "jsonpath-file") + supportedOutputTypes := sets.NewString("", "name") + outputFormat := cmdutil.GetFlagString(cmd, "output") + if !validOutputTypes.Has(outputFormat) { + cmdutil.CheckErr(fmt.Errorf("output must be one of '' or 'name': %v", outputFormat)) + } + if !supportedOutputTypes.Has(outputFormat) { + fmt.Fprintf(out, "--output %v is not available in kubectl config get-contexts; reseting to default output format", outputFormat) + cmd.Flags().Set("output", "") + } + cmdutil.CheckErr(options.Complete(cmd, args, out)) + cmdutil.CheckErr(options.RunGetContexts()) + }, + } + cmdutil.AddOutputFlags(cmd) + cmdutil.AddNoHeadersFlags(cmd) + return cmd +} + +// Complete assigns GetContextsOptions from the args. +func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string, out io.Writer) error { + o.contextNames = args + o.out = out + o.nameOnly = false + if cmdutil.GetFlagString(cmd, "output") == "name" { + o.nameOnly = true + } + o.showHeaders = true + if cmdutil.GetFlagBool(cmd, "no-headers") || o.nameOnly { + o.showHeaders = false + } + + return nil +} + +// RunGetContexts implements all the necessary functionality for context retrieval. +func (o GetContextsOptions) RunGetContexts() error { + config, err := o.configAccess.GetStartingConfig() + if err != nil { + return err + } + + out, found := o.out.(*tabwriter.Writer) + if !found { + out = kubectl.GetNewTabWriter(o.out) + defer out.Flush() + } + + // Build a list of context names to print, and warn if any requested contexts are not found. + // Do this before printing the headers so it doesn't look ugly. + allErrs := []error{} + toPrint := []string{} + if len(o.contextNames) == 0 { + for name := range config.Contexts { + toPrint = append(toPrint, name) + } + } else { + for _, name := range o.contextNames { + _, ok := config.Contexts[name] + if ok { + toPrint = append(toPrint, name) + } else { + allErrs = append(allErrs, fmt.Errorf("context %v not found", name)) + } + } + } + if o.showHeaders { + err = printContextHeaders(out, o.nameOnly) + if err != nil { + allErrs = append(allErrs, err) + } + } + + for _, name := range toPrint { + err = printContext(name, config.Contexts[name], out, o.nameOnly, config.CurrentContext == name) + if err != nil { + allErrs = append(allErrs, err) + } + } + + return utilerrors.NewAggregate(allErrs) +} + +func printContextHeaders(out io.Writer, nameOnly bool) error { + columnNames := []string{"CURRENT", "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE"} + if nameOnly { + columnNames = columnNames[:1] + } + _, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t")) + return err +} + +func printContext(name string, context *clientcmdapi.Context, w io.Writer, nameOnly, current bool) error { + if nameOnly { + _, err := fmt.Fprintf(w, "%s\n", name) + return err + } + prefix := " " + if current { + prefix = "*" + } + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", prefix, name, context.Cluster, context.AuthInfo, context.Namespace) + return err +} diff --git a/pkg/kubectl/cmd/config/get_contexts_test.go b/pkg/kubectl/cmd/config/get_contexts_test.go new file mode 100644 index 00000000000..6bb38896d85 --- /dev/null +++ b/pkg/kubectl/cmd/config/get_contexts_test.go @@ -0,0 +1,158 @@ +/* +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 getContextsTest struct { + startingConfig clientcmdapi.Config + names []string + noHeader bool + nameOnly bool + expectedOut string +} + +func TestGetContextsAll(t *testing.T) { + tconf := clientcmdapi.Config{ + CurrentContext: "shaker-context", + Contexts: map[string]*clientcmdapi.Context{ + "shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}} + test := getContextsTest{ + startingConfig: tconf, + names: []string{}, + noHeader: false, + nameOnly: false, + expectedOut: `CURRENT NAME CLUSTER AUTHINFO NAMESPACE +* shaker-context big-cluster blue-user saw-ns +`, + } + test.run(t) +} + +func TestGetContextsAllNoHeader(t *testing.T) { + tconf := clientcmdapi.Config{ + CurrentContext: "shaker-context", + Contexts: map[string]*clientcmdapi.Context{ + "shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}} + test := getContextsTest{ + startingConfig: tconf, + names: []string{}, + noHeader: true, + nameOnly: false, + expectedOut: "* shaker-context big-cluster blue-user saw-ns\n", + } + test.run(t) +} + +func TestGetContextsAllName(t *testing.T) { + tconf := clientcmdapi.Config{ + Contexts: map[string]*clientcmdapi.Context{ + "shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}} + test := getContextsTest{ + startingConfig: tconf, + names: []string{}, + noHeader: false, + nameOnly: true, + expectedOut: "shaker-context\n", + } + test.run(t) +} + +func TestGetContextsAllNameNoHeader(t *testing.T) { + tconf := clientcmdapi.Config{ + CurrentContext: "shaker-context", + Contexts: map[string]*clientcmdapi.Context{ + "shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}} + test := getContextsTest{ + startingConfig: tconf, + names: []string{}, + noHeader: true, + nameOnly: true, + expectedOut: "shaker-context\n", + } + test.run(t) +} + +func TestGetContextsAllNone(t *testing.T) { + test := getContextsTest{ + startingConfig: *clientcmdapi.NewConfig(), + names: []string{}, + noHeader: true, + nameOnly: false, + expectedOut: "", + } + test.run(t) +} + +func TestGetContextsSelectOneOfTwo(t *testing.T) { + tconf := clientcmdapi.Config{ + CurrentContext: "shaker-context", + Contexts: map[string]*clientcmdapi.Context{ + "shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}, + "not-this": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}} + test := getContextsTest{ + startingConfig: tconf, + names: []string{"shaker-context"}, + noHeader: true, + nameOnly: true, + expectedOut: "shaker-context\n", + } + test.run(t) +} + +func (test getContextsTest) run(t *testing.T) { + fakeKubeFile, _ := ioutil.TempFile("", "") + defer os.Remove(fakeKubeFile.Name()) + err := clientcmd.WriteToFile(test.startingConfig, fakeKubeFile.Name()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + pathOptions := clientcmd.NewDefaultPathOptions() + pathOptions.GlobalFile = fakeKubeFile.Name() + pathOptions.EnvVar = "" + buf := bytes.NewBuffer([]byte{}) + options := GetContextsOptions{ + configAccess: pathOptions, + } + cmd := NewCmdConfigGetContexts(buf, options.configAccess) + if test.nameOnly { + cmd.Flags().Set("output", "name") + } + if test.noHeader { + cmd.Flags().Set("no-headers", "true") + } + cmd.Run(cmd, test.names) + if len(test.expectedOut) != 0 { + if buf.String() != test.expectedOut { + t.Errorf("Expected %v, but got %v", test.expectedOut, buf.String()) + } + return + } + + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} diff --git a/pkg/kubectl/cmd/util/printing.go b/pkg/kubectl/cmd/util/printing.go index 205d50b23d1..7d4592d148c 100644 --- a/pkg/kubectl/cmd/util/printing.go +++ b/pkg/kubectl/cmd/util/printing.go @@ -30,9 +30,9 @@ import ( // AddPrinterFlags adds printing related flags to a command (e.g. output format, no headers, template path) func AddPrinterFlags(cmd *cobra.Command) { - cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].") + AddOutputFlags(cmd) cmd.Flags().String("output-version", "", "Output the formatted object with the given group version (for ex: 'extensions/v1beta1').") - cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers.") + AddNoHeadersFlags(cmd) cmd.Flags().Bool("show-labels", false, "When printing, show all labels as the last column (default hide labels column)") cmd.Flags().String("template", "", "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].") cmd.MarkFlagFilename("template") @@ -45,6 +45,16 @@ func AddOutputFlagsForMutation(cmd *cobra.Command) { cmd.Flags().StringP("output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).") } +// AddOutputFlags adds output related flags to a command. +func AddOutputFlags(cmd *cobra.Command) { + cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].") +} + +// AddNoHeadersFlags adds no-headers flags to a command. +func AddNoHeadersFlags(cmd *cobra.Command) { + cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers.") +} + // PrintSuccess prints message after finishing mutating operations func PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource string, name string, operation string) { resource, _ = mapper.ResourceSingularizer(resource)