diff --git a/pkg/kubectl/cmd/annotate.go b/pkg/kubectl/cmd/annotate.go index e850a1fa517..0e8e2ac6d2d 100644 --- a/pkg/kubectl/cmd/annotate.go +++ b/pkg/kubectl/cmd/annotate.go @@ -112,7 +112,7 @@ func NewAnnotateOptions(ioStreams genericclioptions.IOStreams) *AnnotateOptions } } -func NewCmdAnnotate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { +func NewCmdAnnotate(parent string, f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { o := NewAnnotateOptions(ioStreams) validArgs := cmdutil.ValidArgList(f) @@ -120,7 +120,7 @@ func NewCmdAnnotate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *c Use: "annotate [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]", DisableFlagsInUseLine: true, Short: i18n.T("Update the annotations on a resource"), - Long: annotateLong + "\n\n" + cmdutil.ValidResourceTypeList(f), + Long: annotateLong + "\n\n" + cmdutil.SuggestApiResources(parent), Example: annotateExample, Run: func(cmd *cobra.Command, args []string) { if err := o.Complete(f, cmd, args); err != nil { diff --git a/pkg/kubectl/cmd/annotate_test.go b/pkg/kubectl/cmd/annotate_test.go index ec30bbd47a7..e76621419da 100644 --- a/pkg/kubectl/cmd/annotate_test.go +++ b/pkg/kubectl/cmd/annotate_test.go @@ -425,7 +425,7 @@ func TestAnnotateErrors(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() iostreams, _, bufOut, bufErr := genericclioptions.NewTestIOStreams() - cmd := NewCmdAnnotate(tf, iostreams) + cmd := NewCmdAnnotate("kubectl", tf, iostreams) cmd.SetOutput(bufOut) for k, v := range testCase.flags { @@ -489,7 +489,7 @@ func TestAnnotateObject(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdAnnotate(tf, iostreams) + cmd := NewCmdAnnotate("kubectl", tf, iostreams) cmd.SetOutput(bufOut) options := NewAnnotateOptions(iostreams) args := []string{"pods/foo", "a=b", "c-"} @@ -543,7 +543,7 @@ func TestAnnotateObjectFromFile(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdAnnotate(tf, iostreams) + cmd := NewCmdAnnotate("kubectl", tf, iostreams) cmd.SetOutput(bufOut) options := NewAnnotateOptions(iostreams) options.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"} @@ -575,7 +575,7 @@ func TestAnnotateLocal(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() iostreams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdAnnotate(tf, iostreams) + cmd := NewCmdAnnotate("kubectl", tf, iostreams) options := NewAnnotateOptions(iostreams) options.local = true options.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"} @@ -631,7 +631,7 @@ func TestAnnotateMultipleObjects(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() iostreams, _, _, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdAnnotate(tf, iostreams) + cmd := NewCmdAnnotate("kubectl", tf, iostreams) cmd.SetOutput(iostreams.Out) options := NewAnnotateOptions(iostreams) options.all = true diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 9a5834de12b..3f23fbe54dd 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -264,8 +264,8 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob { Message: "Basic Commands (Intermediate):", Commands: []*cobra.Command{ - NewCmdExplain(f, ioStreams), - get.NewCmdGet(f, ioStreams), + NewCmdExplain("kubectl", f, ioStreams), + get.NewCmdGet("kubectl", f, ioStreams), NewCmdEdit(f, ioStreams), NewCmdDelete(f, out, err), }, @@ -294,7 +294,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob { Message: "Troubleshooting and Debugging Commands:", Commands: []*cobra.Command{ - NewCmdDescribe(f, out, err), + NewCmdDescribe("kubectl", f, ioStreams), NewCmdLogs(f, out, err), NewCmdAttach(f, in, out, err), NewCmdExec(f, in, out, err), @@ -317,7 +317,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob Message: "Settings Commands:", Commands: []*cobra.Command{ NewCmdLabel(f, ioStreams), - NewCmdAnnotate(f, ioStreams), + NewCmdAnnotate("kubectl", f, ioStreams), NewCmdCompletion(out, ""), }, }, diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index 8d6139ab856..75e17193e02 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -18,16 +18,17 @@ package cmd import ( "fmt" - "io" "strings" "github.com/spf13/cobra" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/printers" @@ -67,61 +68,103 @@ var ( kubectl describe pods frontend`)) ) -func NewCmdDescribe(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command { - options := &resource.FilenameOptions{} - describerSettings := &printers.DescriberSettings{ - ShowEvents: true, +type DescribeOptions struct { + CmdParent string + Selector string + Namespace string + + Describer func(*meta.RESTMapping) (printers.Describer, error) + Builder *resource.Builder + + BuilderArgs []string + + EnforceNamespace bool + AllNamespaces bool + IncludeUninitialized bool + + DescriberSettings *printers.DescriberSettings + FilenameOptions *resource.FilenameOptions + + genericclioptions.IOStreams +} + +func NewCmdDescribe(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { + o := &DescribeOptions{ + FilenameOptions: &resource.FilenameOptions{}, + DescriberSettings: &printers.DescriberSettings{ + ShowEvents: true, + }, + + CmdParent: parent, + + IOStreams: streams, } cmd := &cobra.Command{ Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)", DisableFlagsInUseLine: true, Short: i18n.T("Show details of a specific resource or group of resources"), - Long: describeLong + "\n\n" + cmdutil.ValidResourceTypeList(f), + Long: describeLong + "\n\n" + cmdutil.SuggestApiResources(parent), Example: describeExample, Run: func(cmd *cobra.Command, args []string) { - err := RunDescribe(f, out, cmdErr, cmd, args, options, describerSettings) - cmdutil.CheckErr(err) + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Run()) }, } usage := "containing the resource to describe" - cmdutil.AddFilenameOptionFlags(cmd, options, usage) + cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage) cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") 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().BoolVar(&describerSettings.ShowEvents, "show-events", describerSettings.ShowEvents, "If true, display events related to the described object.") + cmd.Flags().BoolVar(&o.DescriberSettings.ShowEvents, "show-events", o.DescriberSettings.ShowEvents, "If true, display events related to the described object.") cmdutil.AddIncludeUninitializedFlag(cmd) return cmd } -func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions, describerSettings *printers.DescriberSettings) error { - selector := cmdutil.GetFlagString(cmd, "selector") - allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") - cmdNamespace, enforceNamespace, err := f.DefaultNamespace() +func (o *DescribeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.Selector = cmdutil.GetFlagString(cmd, "selector") + o.AllNamespaces = cmdutil.GetFlagBool(cmd, "all-namespaces") + + var err error + o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace() if err != nil { return err } - if allNamespaces { - enforceNamespace = false + + if o.AllNamespaces { + o.EnforceNamespace = false } - if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(options.Filenames) { - fmt.Fprintf(cmdErr, "You must specify the type of resource to describe. %s\n\n", cmdutil.ValidResourceTypeList(f)) - return cmdutil.UsageErrorf(cmd, "Required resource not specified.") + + if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) { + return fmt.Errorf("You must specify the type of resource to describe. %s\n", cmdutil.SuggestApiResources(o.CmdParent)) } + o.BuilderArgs = args + + o.Describer = f.Describer + o.Builder = f.NewBuilder() + // include the uninitialized objects by default // unless user explicitly set --include-uninitialized=false - includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, true) - r := f.NewBuilder(). + o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, true) + return nil +} + +func (o *DescribeOptions) Validate(args []string) error { + return nil +} + +func (o *DescribeOptions) Run() error { + r := o.Builder. Unstructured(). ContinueOnError(). - NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). - FilenameParam(enforceNamespace, options). - LabelSelectorParam(selector). - IncludeUninitialized(includeUninitialized). - ResourceTypeOrNameArgs(true, args...). + NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces). + FilenameParam(o.EnforceNamespace, o.FilenameOptions). + LabelSelectorParam(o.Selector). + IncludeUninitialized(o.IncludeUninitialized). + ResourceTypeOrNameArgs(true, o.BuilderArgs...). Flatten(). Do() - err = r.Err() + err := r.Err() if err != nil { return err } @@ -129,8 +172,8 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a allErrs := []error{} infos, err := r.Infos() if err != nil { - if apierrors.IsNotFound(err) && len(args) == 2 { - return DescribeMatchingResources(f, cmdNamespace, args[0], args[1], describerSettings, out, err) + if apierrors.IsNotFound(err) && len(o.BuilderArgs) == 2 { + return o.DescribeMatchingResources(err, o.BuilderArgs[0], o.BuilderArgs[1]) } allErrs = append(allErrs, err) } @@ -139,7 +182,7 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a first := true for _, info := range infos { mapping := info.ResourceMapping() - describer, err := f.Describer(mapping) + describer, err := o.Describer(mapping) if err != nil { if errs.Has(err.Error()) { continue @@ -148,7 +191,7 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a errs.Insert(err.Error()) continue } - s, err := describer.Describe(info.Namespace, info.Name, *describerSettings) + s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings) if err != nil { if errs.Has(err.Error()) { continue @@ -159,20 +202,20 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a } if first { first = false - fmt.Fprint(out, s) + fmt.Fprint(o.Out, s) } else { - fmt.Fprintf(out, "\n\n%s", s) + fmt.Fprintf(o.Out, "\n\n%s", s) } } return utilerrors.NewAggregate(allErrs) } -func DescribeMatchingResources(f cmdutil.Factory, namespace, rsrc, prefix string, describerSettings *printers.DescriberSettings, out io.Writer, originalError error) error { - r := f.NewBuilder(). +func (o *DescribeOptions) DescribeMatchingResources(originalError error, resource, prefix string) error { + r := o.Builder. Unstructured(). - NamespaceParam(namespace).DefaultNamespace(). - ResourceTypeOrNameArgs(true, rsrc). + NamespaceParam(o.Namespace).DefaultNamespace(). + ResourceTypeOrNameArgs(true, resource). SingleResourceType(). Flatten(). Do() @@ -180,7 +223,7 @@ func DescribeMatchingResources(f cmdutil.Factory, namespace, rsrc, prefix string if err != nil { return err } - describer, err := f.Describer(mapping) + describer, err := o.Describer(mapping) if err != nil { return err } @@ -193,11 +236,11 @@ func DescribeMatchingResources(f cmdutil.Factory, namespace, rsrc, prefix string info := infos[ix] if strings.HasPrefix(info.Name, prefix) { isFound = true - s, err := describer.Describe(info.Namespace, info.Name, *describerSettings) + s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings) if err != nil { return err } - fmt.Fprintf(out, "%s\n", s) + fmt.Fprintf(o.Out, "%s\n", s) } } if !isFound { diff --git a/pkg/kubectl/cmd/describe_test.go b/pkg/kubectl/cmd/describe_test.go index 85424e97ec1..e89845fa2a3 100644 --- a/pkg/kubectl/cmd/describe_test.go +++ b/pkg/kubectl/cmd/describe_test.go @@ -17,7 +17,6 @@ limitations under the License. package cmd import ( - "bytes" "fmt" "net/http" "strings" @@ -26,6 +25,7 @@ import ( "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api/legacyscheme" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -40,10 +40,11 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, } + + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + tf.Namespace = "non-default" - buf := bytes.NewBuffer([]byte{}) - buferr := bytes.NewBuffer([]byte{}) - cmd := NewCmdDescribe(tf, buf, buferr) + cmd := NewCmdDescribe("kubectl", tf, streams) cmd.Run(cmd, []string{"type", "foo"}) if d.Name != "foo" || d.Namespace != "" { @@ -68,9 +69,10 @@ func TestDescribeUnknownNamespacedSchemaObject(t *testing.T) { Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalNamespacedType("", "", "foo", "non-default"))}, } tf.Namespace = "non-default" - buf := bytes.NewBuffer([]byte{}) - buferr := bytes.NewBuffer([]byte{}) - cmd := NewCmdDescribe(tf, buf, buferr) + + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + + cmd := NewCmdDescribe("kubectl", tf, streams) cmd.Run(cmd, []string{"namespacedtype", "foo"}) if d.Name != "foo" || d.Namespace != "non-default" { @@ -103,9 +105,10 @@ func TestDescribeObject(t *testing.T) { }), } tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - buferr := bytes.NewBuffer([]byte{}) - cmd := NewCmdDescribe(tf, buf, buferr) + + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + + cmd := NewCmdDescribe("kubectl", tf, streams) cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml") cmd.Run(cmd, []string{}) @@ -131,10 +134,10 @@ func TestDescribeListObjects(t *testing.T) { Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - buferr := bytes.NewBuffer([]byte{}) - cmd := NewCmdDescribe(tf, buf, buferr) + cmd := NewCmdDescribe("kubectl", tf, streams) cmd.Run(cmd, []string{"pods"}) if buf.String() != fmt.Sprintf("%s\n\n%s", d.Output, d.Output) { t.Errorf("unexpected output: %s", buf.String()) @@ -155,9 +158,7 @@ func TestDescribeObjectShowEvents(t *testing.T) { } tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - buferr := bytes.NewBuffer([]byte{}) - cmd := NewCmdDescribe(tf, buf, buferr) + cmd := NewCmdDescribe("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard()) cmd.Flags().Set("show-events", "true") cmd.Run(cmd, []string{"pods"}) if d.Settings.ShowEvents != true { @@ -179,9 +180,7 @@ func TestDescribeObjectSkipEvents(t *testing.T) { } tf.Namespace = "test" - buf := bytes.NewBuffer([]byte{}) - buferr := bytes.NewBuffer([]byte{}) - cmd := NewCmdDescribe(tf, buf, buferr) + cmd := NewCmdDescribe("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard()) cmd.Flags().Set("show-events", "false") cmd.Run(cmd, []string{"pods"}) if d.Settings.ShowEvents != false { @@ -193,9 +192,9 @@ func TestDescribeHelpMessage(t *testing.T) { tf := cmdtesting.NewTestFactory() defer tf.Cleanup() - buf := bytes.NewBuffer([]byte{}) - buferr := bytes.NewBuffer([]byte{}) - cmd := NewCmdDescribe(tf, buf, buferr) + streams, _, buf, _ := genericclioptions.NewTestIOStreams() + + cmd := NewCmdDescribe("kubectl", tf, streams) cmd.SetArgs([]string{"-h"}) cmd.SetOutput(buf) _, err := cmd.ExecuteC() diff --git a/pkg/kubectl/cmd/explain.go b/pkg/kubectl/cmd/explain.go index 61ef8fa3f52..58e10c5922f 100644 --- a/pkg/kubectl/cmd/explain.go +++ b/pkg/kubectl/cmd/explain.go @@ -21,9 +21,11 @@ import ( "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/kubectl/explain" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/util/i18n" @@ -51,27 +53,36 @@ var ( type ExplainOptions struct { genericclioptions.IOStreams + + CmdParent string + ApiVersion string + Recursive bool + + Mapper meta.RESTMapper + Schema openapi.Resources } -func NewExplainOptions(streams genericclioptions.IOStreams) *ExplainOptions { +func NewExplainOptions(parent string, streams genericclioptions.IOStreams) *ExplainOptions { return &ExplainOptions{ IOStreams: streams, + CmdParent: parent, } } // NewCmdExplain returns a cobra command for swagger docs -func NewCmdExplain(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { - o := NewExplainOptions(streams) +func NewCmdExplain(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { + o := NewExplainOptions(parent, streams) cmd := &cobra.Command{ Use: "explain RESOURCE", DisableFlagsInUseLine: true, Short: i18n.T("Documentation of resources"), - Long: explainLong + "\n\n" + cmdutil.ValidResourceTypeList(f), + Long: explainLong + "\n\n" + cmdutil.SuggestApiResources(parent), Example: explainExamples, Run: func(cmd *cobra.Command, args []string) { - err := o.RunExplain(f, cmd, args) - cmdutil.CheckErr(err) + cmdutil.CheckErr(o.Complete(f, cmd)) + cmdutil.CheckErr(o.Validate(args)) + cmdutil.CheckErr(o.Run(args)) }, } cmd.Flags().Bool("recursive", false, "Print the fields of fields (Currently only 1 level deep)") @@ -79,24 +90,40 @@ func NewCmdExplain(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobr return cmd } -// RunExplain executes the appropriate steps to print a model's documentation -func (o *ExplainOptions) RunExplain(f cmdutil.Factory, cmd *cobra.Command, args []string) error { +func (o *ExplainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + o.Recursive = cmdutil.GetFlagBool(cmd, "recursive") + o.ApiVersion = cmdutil.GetFlagString(cmd, "api-version") + + o.Mapper, _ = f.Object() + + var err error + o.Schema, err = f.OpenAPISchema() + if err != nil { + return err + } + return nil +} + +func (o *ExplainOptions) Validate(args []string) error { if len(args) == 0 { - fmt.Fprintf(o.ErrOut, "You must specify the type of resource to explain. %s\n\n", cmdutil.ValidResourceTypeList(f)) - return cmdutil.UsageErrorf(cmd, "Required resource not specified.") + return fmt.Errorf("You must specify the type of resource to explain. %s\n", cmdutil.SuggestApiResources(o.CmdParent)) } if len(args) > 1 { - return cmdutil.UsageErrorf(cmd, "We accept only this format: explain RESOURCE") + return fmt.Errorf("We accept only this format: explain RESOURCE\n") } - recursive := cmdutil.GetFlagBool(cmd, "recursive") - apiVersionString := cmdutil.GetFlagString(cmd, "api-version") + return nil +} + +// Run executes the appropriate steps to print a model's documentation +func (o *ExplainOptions) Run(args []string) error { + recursive := o.Recursive + apiVersionString := o.ApiVersion - mapper, _ := f.Object() // TODO: After we figured out the new syntax to separate group and resource, allow // the users to use it in explain (kubectl explain ). // Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax. - inModel, fieldsPath, err := explain.SplitAndParseResourceRequest(args[0], mapper) + inModel, fieldsPath, err := explain.SplitAndParseResourceRequest(args[0], o.Mapper) if err != nil { return err } @@ -105,10 +132,10 @@ func (o *ExplainOptions) RunExplain(f cmdutil.Factory, cmd *cobra.Command, args fullySpecifiedGVR, groupResource := schema.ParseResourceArg(inModel) gvk := schema.GroupVersionKind{} if fullySpecifiedGVR != nil { - gvk, _ = mapper.KindFor(*fullySpecifiedGVR) + gvk, _ = o.Mapper.KindFor(*fullySpecifiedGVR) } if gvk.Empty() { - gvk, err = mapper.KindFor(groupResource.WithVersion("")) + gvk, err = o.Mapper.KindFor(groupResource.WithVersion("")) if err != nil { return err } @@ -122,12 +149,7 @@ func (o *ExplainOptions) RunExplain(f cmdutil.Factory, cmd *cobra.Command, args gvk = apiVersion.WithKind(gvk.Kind) } - resources, err := f.OpenAPISchema() - if err != nil { - return err - } - - schema := resources.LookupResource(gvk) + schema := o.Schema.LookupResource(gvk) if schema == nil { return fmt.Errorf("Couldn't find resource for %q", gvk) } diff --git a/pkg/kubectl/cmd/get/get.go b/pkg/kubectl/cmd/get/get.go index d23cccae0cf..a364f2f3200 100644 --- a/pkg/kubectl/cmd/get/get.go +++ b/pkg/kubectl/cmd/get/get.go @@ -57,6 +57,8 @@ type GetOptions struct { IsGeneric bool PrintWithOpenAPICols bool + CmdParent string + resource.FilenameOptions Raw string @@ -131,26 +133,27 @@ const ( ) // NewGetOptions returns a GetOptions with default chunk size 500. -func NewGetOptions(streams genericclioptions.IOStreams) *GetOptions { +func NewGetOptions(parent string, streams genericclioptions.IOStreams) *GetOptions { return &GetOptions{ PrintFlags: NewGetPrintFlags(), - ChunkSize: 500, + CmdParent: parent, IOStreams: streams, + ChunkSize: 500, } } // NewCmdGet creates a command object for the generic "get" action, which // retrieves one or more resources from a server. -func NewCmdGet(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { - o := NewGetOptions(streams) +func NewCmdGet(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { + o := NewGetOptions(parent, streams) validArgs := cmdutil.ValidArgList(f) cmd := &cobra.Command{ Use: "get [(-o|--output=)json|yaml|wide|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE [NAME | -l label] | TYPE/NAME ...) [flags]", DisableFlagsInUseLine: true, Short: i18n.T("Display one or many resources"), - Long: getLong + "\n\n" + cmdutil.ValidResourceTypeList(f), + Long: getLong + "\n\n" + cmdutil.SuggestApiResources(parent), Example: getExample, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) @@ -258,7 +261,7 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, len(args) == 2) default: if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { - fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.ValidResourceTypeList(f)) + fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.SuggestApiResources(o.CmdParent)) fullCmdName := cmd.Parent().CommandPath() usageString := "Required resource not specified." if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") { diff --git a/pkg/kubectl/cmd/get/get_test.go b/pkg/kubectl/cmd/get/get_test.go index a7844fcd8a5..b617d0d080d 100644 --- a/pkg/kubectl/cmd/get/get_test.go +++ b/pkg/kubectl/cmd/get/get_test.go @@ -213,7 +213,7 @@ func TestGetUnknownSchemaObject(t *testing.T) { } streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Run(cmd, []string{"type", "foo"}) @@ -255,7 +255,7 @@ func TestGetSchemaObject(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.Run(cmd, []string{"replicationcontrollers", "foo"}) if !strings.Contains(buf.String(), "foo") { @@ -280,7 +280,7 @@ func TestGetObjectsWithOpenAPIOutputFormatPresent(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set(useOpenAPIPrintColumnFlagLabel, "true") cmd.Run(cmd, []string{"pods", "foo"}) @@ -334,7 +334,7 @@ func TestGetObjects(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Run(cmd, []string{"pods", "foo"}) @@ -382,7 +382,7 @@ func TestGetObjectIgnoreNotFound(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("ignore-not-found", "true") cmd.Flags().Set("output", "yaml") @@ -426,7 +426,7 @@ func TestGetSortedObjects(t *testing.T) { tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: "v1"}}} streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) // sorting with metedata.name @@ -457,7 +457,7 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml") cmd.Run(cmd, []string{}) @@ -484,7 +484,7 @@ func TestGetListObjects(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Run(cmd, []string{"pods"}) @@ -511,7 +511,7 @@ func TestGetAllListObjects(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Run(cmd, []string{"pods"}) @@ -538,7 +538,7 @@ func TestGetListComponentStatus(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Run(cmd, []string{"componentstatuses"}) @@ -588,7 +588,7 @@ func TestGetMixedGenericObjects(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("output", "json") cmd.Run(cmd, []string{"pods"}) @@ -639,7 +639,7 @@ func TestGetMultipleTypeObjects(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Run(cmd, []string{"pods,services"}) @@ -679,7 +679,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { tf.ClientConfigVal = defaultClientConfig() streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("output", "json") @@ -783,7 +783,7 @@ func TestGetMultipleTypeObjectsWithLabelSelector(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("selector", "a=b") @@ -827,7 +827,7 @@ func TestGetMultipleTypeObjectsWithFieldSelector(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("field-selector", "a=b") @@ -873,7 +873,7 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Run(cmd, []string{"services/bar", "node/foo"}) @@ -992,7 +992,7 @@ func TestWatchLabelSelector(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("watch", "true") @@ -1044,7 +1044,7 @@ func TestWatchFieldSelector(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("watch", "true") @@ -1090,7 +1090,7 @@ func TestWatchResource(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("watch", "true") @@ -1134,7 +1134,7 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("watch", "true") @@ -1179,7 +1179,7 @@ func TestWatchOnlyResource(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("watch-only", "true") @@ -1225,7 +1225,7 @@ func TestWatchOnlyList(t *testing.T) { tf.Namespace = "test" streams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdGet(tf, streams) + cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOutput(buf) cmd.Flags().Set("watch-only", "true") diff --git a/pkg/kubectl/cmd/util/printing.go b/pkg/kubectl/cmd/util/printing.go index 56127119392..ce1ea57b40e 100644 --- a/pkg/kubectl/cmd/util/printing.go +++ b/pkg/kubectl/cmd/util/printing.go @@ -234,54 +234,10 @@ func maybeWrapSortingPrinter(printer printers.ResourcePrinter, printOpts printer return printer } -// ValidResourceTypeList returns a multi-line string containing the valid resources. May -// be called before the factory is initialized. -// TODO: This function implementation should be replaced with a real implementation from the -// discovery service. -func ValidResourceTypeList(f ClientAccessFactory) string { - // TODO: Should attempt to use the cached discovery list or fallback to a static list - // that is calculated from code compiled into the factory. - return templates.LongDesc(`Valid resource types include: - - * all - * certificatesigningrequests (aka 'csr') - * clusterrolebindings - * clusterroles - * componentstatuses (aka 'cs') - * configmaps (aka 'cm') - * controllerrevisions - * cronjobs - * customresourcedefinition (aka 'crd') - * daemonsets (aka 'ds') - * deployments (aka 'deploy') - * endpoints (aka 'ep') - * events (aka 'ev') - * horizontalpodautoscalers (aka 'hpa') - * ingresses (aka 'ing') - * jobs - * limitranges (aka 'limits') - * namespaces (aka 'ns') - * networkpolicies (aka 'netpol') - * nodes (aka 'no') - * persistentvolumeclaims (aka 'pvc') - * persistentvolumes (aka 'pv') - * poddisruptionbudgets (aka 'pdb') - * podpreset - * pods (aka 'po') - * podsecuritypolicies (aka 'psp') - * podtemplates - * replicasets (aka 'rs') - * replicationcontrollers (aka 'rc') - * resourcequotas (aka 'quota') - * rolebindings - * roles - * secrets - * serviceaccounts (aka 'sa') - * services (aka 'svc') - * statefulsets (aka 'sts') - * storageclasses (aka 'sc') - - `) +// SuggestApiResources returns a suggestion to use the "api-resources" command +// to retrieve a supported list of resources +func SuggestApiResources(parent string) string { + return templates.LongDesc(fmt.Sprintf("Use \"%s api-resources\" for a complete list of supported resources.", parent)) } // Retrieve a list of handled resources from printer as valid args