From 18a140092822df55ab5e2aefa5bb5e3ec20c5293 Mon Sep 17 00:00:00 2001 From: feihujiang Date: Mon, 10 Aug 2015 19:02:14 +0800 Subject: [PATCH] Kubectl get command accepts a filename param --- contrib/completions/bash/kubectl | 6 +++ docs/man/man1/kubectl-get.1 | 7 +++ docs/user-guide/kubectl/kubectl_get.md | 6 ++- pkg/kubectl/cmd/get.go | 27 +++++++---- pkg/kubectl/cmd/get_test.go | 66 ++++++++++++++++++++++++++ pkg/kubectl/resource/builder.go | 4 ++ 6 files changed, 107 insertions(+), 9 deletions(-) diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 33c0c4d911e..e1961a6dbbe 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -230,6 +230,12 @@ _kubectl_get() flags_completion=() flags+=("--all-namespaces") + flags+=("--filename=") + flags_with_completion+=("--filename") + flags_completion+=("__handle_filename_extension_flag json|yaml|yml") + two_word_flags+=("-f") + flags_with_completion+=("-f") + flags_completion+=("__handle_filename_extension_flag json|yaml|yml") flags+=("--help") flags+=("-h") flags+=("--label-columns=") diff --git a/docs/man/man1/kubectl-get.1 b/docs/man/man1/kubectl-get.1 index 66006511d9b..4141453b47f 100644 --- a/docs/man/man1/kubectl-get.1 +++ b/docs/man/man1/kubectl-get.1 @@ -31,6 +31,10 @@ of the \-\-template flag, you can filter the attributes of the fetched resource( \fB\-\-all\-namespaces\fP=false If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with \-\-namespace. +.PP +\fB\-f\fP, \fB\-\-filename\fP=[] + Filename, directory, or URL to a file identifying the resource to get from a server. + .PP \fB\-h\fP, \fB\-\-help\fP=false help for get @@ -188,6 +192,9 @@ $ kubectl get replicationcontroller web # List a single pod in JSON output format. $ kubectl get \-o json pod web\-pod\-13je7 +# List a pod identified by type and name specified in "pod.yaml" in JSON output format. +$ kubectl get \-f pod.yaml \-o json + # Return only the phase value of the specified pod. $ kubectl get \-o template web\-pod\-13je7 \-\-template={{.status.phase}} \-\-api\-version=v1 diff --git a/docs/user-guide/kubectl/kubectl_get.md b/docs/user-guide/kubectl/kubectl_get.md index 383f0e6cf0e..9aa21c5794f 100644 --- a/docs/user-guide/kubectl/kubectl_get.md +++ b/docs/user-guide/kubectl/kubectl_get.md @@ -67,6 +67,9 @@ $ kubectl get replicationcontroller web # List a single pod in JSON output format. $ kubectl get -o json pod web-pod-13je7 +# List a pod identified by type and name specified in "pod.yaml" in JSON output format. +$ kubectl get -f pod.yaml -o json + # Return only the phase value of the specified pod. $ kubectl get -o template web-pod-13je7 --template={{.status.phase}} --api-version=v1 @@ -81,6 +84,7 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7 ``` --all-namespaces[=false]: If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. + -f, --filename=[]: Filename, directory, or URL to a file identifying the resource to get from a server. -h, --help[=false]: help for get -L, --label-columns=[]: Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag statements like -L label1 -L label2... --no-headers[=false]: When using the default output, don't print headers. @@ -126,7 +130,7 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7 * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-08-12 23:41:01.301023165 +0000 UTC +###### Auto generated by spf13/cobra at 2015-08-13 06:12:05.386038784 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_get.md?pixel)]() diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 3afa4e05457..24c20af5d84 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -20,12 +20,11 @@ import ( "fmt" "io" + "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/watch" - - "github.com/spf13/cobra" ) const ( @@ -50,6 +49,9 @@ $ kubectl get replicationcontroller web # List a single pod in JSON output format. $ kubectl get -o json pod web-pod-13je7 +# List a pod identified by type and name specified in "pod.yaml" in JSON output format. +$ kubectl get -f pod.yaml -o json + # Return only the phase value of the specified pod. $ kubectl get -o template web-pod-13je7 --template={{.status.phase}} --api-version=v1 @@ -83,6 +85,8 @@ func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("watch-only", false, "Watch for changes to the requested object(s), without listing/getting first.") cmd.Flags().Bool("all-namespaces", false, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") cmd.Flags().StringSliceP("label-columns", "L", []string{}, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag statements like -L label1 -L label2...") + usage := "Filename, directory, or URL to a file identifying the resource to get from a server." + kubectl.AddJsonFilenameFlag(cmd, usage) return cmd } @@ -93,12 +97,14 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") mapper, typer := f.Object() - cmdNamespace, _, err := f.DefaultNamespace() + cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } - if len(args) == 0 { + filenames := cmdutil.GetFlagStringSlice(cmd, "filename") + + if len(args) == 0 && len(filenames) == 0 { fmt.Fprint(out, "You must specify the type of resource to get. ", valid_resources, ` * componentstatuses (aka 'cs') * endpoints (aka 'ep') `) @@ -110,19 +116,24 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string if isWatch || isWatchOnly { r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). + FilenameParam(enforceNamespace, filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(true, args...). SingleResourceType(). + Latest(). Do() if err != nil { return err } - - mapping, err := r.ResourceMapping() + infos, err := r.Infos() if err != nil { return err } - + if len(infos) != 1 { + return fmt.Errorf("watch is only supported on a single resource - %d resources were found", len(infos)) + } + info := infos[0] + mapping := info.ResourceMapping() printer, err := f.PrinterForMapping(cmd, mapping, allNamespaces) if err != nil { return err @@ -132,7 +143,6 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string if err != nil { return err } - rv, err := mapping.MetadataAccessor.ResourceVersion(obj) if err != nil { return err @@ -159,6 +169,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). + FilenameParam(enforceNamespace, filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(true, args...). ContinueOnError(). diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index c5b068a7239..bb49d4c4762 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -275,6 +275,33 @@ func TestGetObjects(t *testing.T) { } } +func TestGetObjectsIdentifiedByFile(t *testing.T) { + pods, _, _ := testData() + + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &client.FakeRESTClient{ + Codec: codec, + Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + + cmd := NewCmdGet(f, buf) + cmd.SetOutput(buf) + cmd.Flags().Set("filename", "../../../examples/cassandra/cassandra.yaml") + cmd.Run(cmd, []string{}) + + expected := []runtime.Object{&pods.Items[0]} + actual := tf.Printer.(*testPrinter).Objects + if !reflect.DeepEqual(expected, actual) { + t.Errorf("unexpected object: %#v", actual) + } + if len(buf.String()) == 0 { + t.Errorf("unexpected empty output") + } +} + func TestGetListObjects(t *testing.T) { pods, _, _ := testData() @@ -635,6 +662,45 @@ func TestWatchResource(t *testing.T) { } } +func TestWatchResourceIdentifiedByFile(t *testing.T) { + pods, events := watchTestData() + + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &client.FakeRESTClient{ + Codec: codec, + Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { + switch req.URL.Path { + case "/namespaces/test/pods/cassandra": + return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil + case "/watch/namespaces/test/pods/cassandra": + return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdGet(f, buf) + cmd.SetOutput(buf) + + cmd.Flags().Set("watch", "true") + cmd.Flags().Set("filename", "../../../examples/cassandra/cassandra.yaml") + cmd.Run(cmd, []string{}) + + expected := []runtime.Object{&pods[0], events[0].Object, events[1].Object} + actual := tf.Printer.(*testPrinter).Objects + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected object: %#v unexpected object: %#v", expected, actual) + } + + if len(buf.String()) == 0 { + t.Errorf("unexpected empty output") + } +} + func TestWatchOnlyResource(t *testing.T) { pods, events := watchTestData() diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 35c8cbad4ed..c28146cd21d 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -584,6 +584,10 @@ func (b *Builder) visitorResult() *Result { if b.flatten { visitors = NewFlattenListVisitor(visitors, b.mapper) } + // must set namespace prior to fetching + if b.defaultNamespace { + visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace)) + } visitors = NewDecoratedVisitor(visitors, RetrieveLatest) } return &Result{singular: singular, visitor: visitors, sources: b.paths}