diff --git a/hack/import-restrictions.yaml b/hack/import-restrictions.yaml index c9b99ae2436..7294a8c3624 100644 --- a/hack/import-restrictions.yaml +++ b/hack/import-restrictions.yaml @@ -17,6 +17,10 @@ ignoredSubTrees: - "./pkg/apis/core/validation" +- baseImportPath: "./pkg/kubectl/genericclioptions/" + allowedImports: + - k8s.io/apimachinery + - baseImportPath: "./vendor/k8s.io/apimachinery/" allowedImports: - k8s.io/apimachinery diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index 8f15c9189c8..f4de498d53d 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -206,6 +206,7 @@ filegroup( "//pkg/kubectl/categories:all-srcs", "//pkg/kubectl/cmd:all-srcs", "//pkg/kubectl/explain:all-srcs", + "//pkg/kubectl/genericclioptions:all-srcs", "//pkg/kubectl/metricsutil:all-srcs", "//pkg/kubectl/plugins:all-srcs", "//pkg/kubectl/proxy:all-srcs", diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 7593278af56..c14bf11de3f 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -41,7 +41,6 @@ go_library( "plugin.go", "portforward.go", "proxy.go", - "record_flags.go", "replace.go", "rollingupdate.go", "run.go", @@ -79,6 +78,7 @@ go_library( "//pkg/kubectl/cmd/util/editor:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/explain:go_default_library", + "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/metricsutil:go_default_library", "//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/proxy:go_default_library", diff --git a/pkg/kubectl/cmd/annotate.go b/pkg/kubectl/cmd/annotate.go index fcd04f26717..8e4adaefd79 100644 --- a/pkg/kubectl/cmd/annotate.go +++ b/pkg/kubectl/cmd/annotate.go @@ -34,6 +34,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl" "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" ) @@ -42,21 +43,22 @@ import ( type AnnotateOptions struct { // Filename options resource.FilenameOptions + RecordFlags *genericclioptions.RecordFlags // Common user flags - overwrite bool - local bool - dryrun bool - all bool - resourceVersion string - selector string - outputFormat string - recordChangeCause bool + overwrite bool + local bool + dryrun bool + all bool + resourceVersion string + selector string + outputFormat string // results of arg parsing resources []string newAnnotations map[string]string removeAnnotations []string + Recorder genericclioptions.Recorder // Common share fields out io.Writer @@ -97,8 +99,15 @@ var ( kubectl annotate pods foo description-`)) ) +func NewAnnotateOptions(out io.Writer) *AnnotateOptions { + return &AnnotateOptions{ + out: out, + RecordFlags: genericclioptions.NewRecordFlags(), + } +} + func NewCmdAnnotate(f cmdutil.Factory, out io.Writer) *cobra.Command { - options := &AnnotateOptions{} + options := NewAnnotateOptions(out) validArgs := cmdutil.ValidArgList(f) cmd := &cobra.Command{ @@ -108,7 +117,7 @@ func NewCmdAnnotate(f cmdutil.Factory, out io.Writer) *cobra.Command { Long: annotateLong + "\n\n" + cmdutil.ValidResourceTypeList(f), Example: annotateExample, Run: func(cmd *cobra.Command, args []string) { - if err := options.Complete(out, cmd, args); err != nil { + if err := options.Complete(f, cmd, args); err != nil { cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, "%v", err)) } if err := options.Validate(); err != nil { @@ -119,6 +128,10 @@ func NewCmdAnnotate(f cmdutil.Factory, out io.Writer) *cobra.Command { ValidArgs: validArgs, ArgAliases: kubectl.ResourceAliases(validArgs), } + + // bind flag structs + options.RecordFlags.AddFlags(cmd) + cmdutil.AddPrinterFlags(cmd) cmdutil.AddIncludeUninitializedFlag(cmd) cmd.Flags().BoolVar(&options.overwrite, "overwrite", options.overwrite, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.") @@ -129,18 +142,23 @@ func NewCmdAnnotate(f cmdutil.Factory, out io.Writer) *cobra.Command { usage := "identifying the resource to update the annotation" cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmdutil.AddDryRunFlag(cmd) - cmdutil.AddRecordFlag(cmd) cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } // Complete adapts from the command line args and factory to the data required. -func (o *AnnotateOptions) Complete(out io.Writer, cmd *cobra.Command, args []string) (err error) { - o.out = out +func (o *AnnotateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + o.RecordFlags.Complete(f.Command(cmd, false)) + + var err error + o.Recorder, err = o.RecordFlags.ToRecorder() + if err != nil { + return err + } + o.outputFormat = cmdutil.GetFlagString(cmd, "output") o.dryrun = cmdutil.GetDryRunFlag(cmd) - o.recordChangeCause = cmdutil.GetRecordFlag(cmd) // retrieves resource and annotation args from args // also checks args to verify that all resources are specified before annotations @@ -174,8 +192,6 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro return err } - changeCause := f.Command(cmd, false) - includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) b := f.NewBuilder(). Unstructured(). @@ -232,9 +248,8 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro if err != nil { return err } - // If we should record change-cause, add it to new annotations - if cmdutil.ContainsChangeCause(info) || o.recordChangeCause { - o.newAnnotations[kubectl.ChangeCauseAnnotation] = changeCause + if err := o.Recorder.Record(info.Object); err != nil { + glog.V(4).Infof("error recording current command: %v", err) } if err := o.updateAnnotations(obj); err != nil { return err diff --git a/pkg/kubectl/cmd/annotate_test.go b/pkg/kubectl/cmd/annotate_test.go index 409070e26cd..7eedc2188ff 100644 --- a/pkg/kubectl/cmd/annotate_test.go +++ b/pkg/kubectl/cmd/annotate_test.go @@ -431,8 +431,8 @@ func TestAnnotateErrors(t *testing.T) { for k, v := range testCase.flags { cmd.Flags().Set(k, v) } - options := &AnnotateOptions{} - err := options.Complete(buf, cmd, testCase.args) + options := NewAnnotateOptions(buf) + err := options.Complete(tf, cmd, testCase.args) if err == nil { err = options.Validate() } @@ -488,9 +488,9 @@ func TestAnnotateObject(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(tf, buf) cmd.SetOutput(buf) - options := &AnnotateOptions{} + options := NewAnnotateOptions(buf) args := []string{"pods/foo", "a=b", "c-"} - if err := options.Complete(buf, cmd, args); err != nil { + if err := options.Complete(tf, cmd, args); err != nil { t.Fatalf("unexpected error: %v", err) } if err := options.Validate(); err != nil { @@ -542,10 +542,10 @@ func TestAnnotateObjectFromFile(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(tf, buf) cmd.SetOutput(buf) - options := &AnnotateOptions{} + options := NewAnnotateOptions(buf) options.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"} args := []string{"a=b", "c-"} - if err := options.Complete(buf, cmd, args); err != nil { + if err := options.Complete(tf, cmd, args); err != nil { t.Fatalf("unexpected error: %v", err) } if err := options.Validate(); err != nil { @@ -573,10 +573,11 @@ func TestAnnotateLocal(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(tf, buf) - options := &AnnotateOptions{local: true} + options := NewAnnotateOptions(buf) + options.local = true options.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"} args := []string{"a=b"} - if err := options.Complete(buf, cmd, args); err != nil { + if err := options.Complete(tf, cmd, args); err != nil { t.Fatalf("unexpected error: %v", err) } if err := options.Validate(); err != nil { @@ -629,9 +630,10 @@ func TestAnnotateMultipleObjects(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(tf, buf) cmd.SetOutput(buf) - options := &AnnotateOptions{all: true} + options := NewAnnotateOptions(buf) + options.all = true args := []string{"pods", "a=b", "c-"} - if err := options.Complete(buf, cmd, args); err != nil { + if err := options.Complete(tf, cmd, args); err != nil { t.Fatalf("unexpected error: %v", err) } if err := options.Validate(); err != nil { diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index 694c3ff9174..f38179c664b 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -45,16 +45,17 @@ import ( "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/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) type ApplyOptions struct { - RecordFlags *RecordFlags + RecordFlags *genericclioptions.RecordFlags FilenameOptions resource.FilenameOptions - Recorder Recorder + Recorder genericclioptions.Recorder Selector string Force bool @@ -111,7 +112,7 @@ var ( func NewApplyOptions(out, errout io.Writer) *ApplyOptions { return &ApplyOptions{ - RecordFlags: NewRecordFlags(), + RecordFlags: genericclioptions.NewRecordFlags(), Overwrite: true, Cascade: true, diff --git a/pkg/kubectl/cmd/apply_edit_last_applied.go b/pkg/kubectl/cmd/apply_edit_last_applied.go index 6d97a551fab..9a2319ee741 100644 --- a/pkg/kubectl/cmd/apply_edit_last_applied.go +++ b/pkg/kubectl/cmd/apply_edit_last_applied.go @@ -18,7 +18,6 @@ package cmd import ( "io" - "runtime" "github.com/spf13/cobra" @@ -59,11 +58,8 @@ var ( ) func NewCmdApplyEditLastApplied(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { - options := &editor.EditOptions{ - EditMode: editor.ApplyEditMode, - Output: "yaml", - WindowsLineEndings: runtime.GOOS == "windows", - } + o := editor.NewEditOptions(editor.ApplyEditMode, out, errOut) + validArgs := cmdutil.ValidArgList(f) cmd := &cobra.Command{ @@ -73,11 +69,10 @@ func NewCmdApplyEditLastApplied(f cmdutil.Factory, out, errOut io.Writer) *cobra Long: applyEditLastAppliedLong, Example: applyEditLastAppliedExample, Run: func(cmd *cobra.Command, args []string) { - options.ChangeCause = f.Command(cmd, false) - if err := options.Complete(f, out, errOut, args, cmd); err != nil { + if err := o.Complete(f, args, cmd); err != nil { cmdutil.CheckErr(err) } - if err := options.Run(); err != nil { + if err := o.Run(); err != nil { cmdutil.CheckErr(err) } }, @@ -85,12 +80,14 @@ func NewCmdApplyEditLastApplied(f cmdutil.Factory, out, errOut io.Writer) *cobra ArgAliases: kubectl.ResourceAliases(validArgs), } + // bind flag structs + o.RecordFlags.AddFlags(cmd) + usage := "to use to edit the resource" - cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) - cmd.Flags().StringVarP(&options.Output, "output", "o", options.Output, "Output format. One of: yaml|json.") - cmd.Flags().BoolVar(&options.WindowsLineEndings, "windows-line-endings", options.WindowsLineEndings, + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: yaml|json.") + cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings, "Defaults to the line ending native to your platform.") - cmdutil.AddRecordVarFlag(cmd, &options.Record) cmdutil.AddIncludeUninitializedFlag(cmd) return cmd diff --git a/pkg/kubectl/cmd/apply_set_last_applied.go b/pkg/kubectl/cmd/apply_set_last_applied.go index accfea60ebd..fb74479640d 100644 --- a/pkg/kubectl/cmd/apply_set_last_applied.go +++ b/pkg/kubectl/cmd/apply_set_last_applied.go @@ -54,8 +54,9 @@ type SetLastAppliedOptions struct { Output string PatchBufferList []PatchBuffer Factory cmdutil.Factory - Out io.Writer - ErrOut io.Writer + + Out io.Writer + ErrOut io.Writer } type PatchBuffer struct { @@ -81,8 +82,15 @@ var ( `)) ) -func NewCmdApplySetLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Command { - options := &SetLastAppliedOptions{Out: out, ErrOut: err} +func NewSetLastAppliedOptions(out, errout io.Writer) *SetLastAppliedOptions { + return &SetLastAppliedOptions{ + Out: out, + ErrOut: errout, + } +} + +func NewCmdApplySetLastApplied(f cmdutil.Factory, out, errout io.Writer) *cobra.Command { + options := NewSetLastAppliedOptions(out, errout) cmd := &cobra.Command{ Use: "set-last-applied -f FILENAME", DisableFlagsInUseLine: true, @@ -97,7 +105,6 @@ func NewCmdApplySetLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Com } cmdutil.AddDryRunFlag(cmd) - cmdutil.AddRecordFlag(cmd) cmdutil.AddPrinterFlags(cmd) cmd.Flags().BoolVar(&options.CreateAnnotation, "create-annotation", options.CreateAnnotation, "Will create 'last-applied-configuration' annotations if current objects doesn't have one") usage := "that contains the last-applied-configuration annotations" @@ -111,12 +118,9 @@ func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) o.Output = cmdutil.GetFlagString(cmd, "output") o.ShortOutput = o.Output == "name" - var err error o.Mapper, o.Typer = f.Object() - if err != nil { - return err - } + var err error o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace() return err } diff --git a/pkg/kubectl/cmd/autoscale.go b/pkg/kubectl/cmd/autoscale.go index 434849fa0d7..344fc8d89ed 100644 --- a/pkg/kubectl/cmd/autoscale.go +++ b/pkg/kubectl/cmd/autoscale.go @@ -24,9 +24,11 @@ import ( "k8s.io/kubernetes/pkg/kubectl" "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" + "github.com/golang/glog" "github.com/spf13/cobra" ) @@ -45,8 +47,22 @@ var ( kubectl autoscale rc foo --max=5 --cpu-percent=80`)) ) +type AutoscaleOptions struct { + FilenameOptions resource.FilenameOptions + RecordFlags *genericclioptions.RecordFlags + + Recorder genericclioptions.Recorder +} + +func NewAutoscaleOptions() *AutoscaleOptions { + return &AutoscaleOptions{ + FilenameOptions: resource.FilenameOptions{}, + RecordFlags: genericclioptions.NewRecordFlags(), + } +} + func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { - options := &resource.FilenameOptions{} + o := NewAutoscaleOptions() validArgs := []string{"deployment", "replicaset", "replicationcontroller"} argAliases := kubectl.ResourceAliases(validArgs) @@ -58,12 +74,16 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { Long: autoscaleLong, Example: autoscaleExample, Run: func(cmd *cobra.Command, args []string) { - err := RunAutoscale(f, out, cmd, args, options) - cmdutil.CheckErr(err) + cmdutil.CheckErr(o.Complete(f, cmd)) + cmdutil.CheckErr(o.RunAutoscale(f, out, cmd, args)) }, ValidArgs: validArgs, ArgAliases: argAliases, } + + // bind flag structs + o.RecordFlags.AddFlags(cmd) + cmdutil.AddPrinterFlags(cmd) cmd.Flags().String("generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator.")) cmd.Flags().Int32("min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.") @@ -73,14 +93,25 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().String("name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used.")) cmdutil.AddDryRunFlag(cmd) usage := "identifying the resource to autoscale." - cmdutil.AddFilenameOptionFlags(cmd, options, usage) + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) cmdutil.AddApplyAnnotationFlags(cmd) - cmdutil.AddRecordFlag(cmd) cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } -func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error { +func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + o.RecordFlags.Complete(f.Command(cmd, false)) + + var err error + o.Recorder, err = o.RecordFlags.ToRecorder() + if err != nil { + return err + } + + return nil +} + +func (o *AutoscaleOptions) RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { namespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err @@ -95,7 +126,7 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s Internal(). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options). + FilenameParam(enforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() @@ -149,11 +180,8 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s if err != nil { return err } - if cmdutil.ShouldRecord(cmd, hpa) { - if err := cmdutil.RecordChangeCause(hpa.Object, f.Command(cmd, false)); err != nil { - return err - } - object = hpa.Object + if err := o.Recorder.Record(hpa.Object); err != nil { + glog.V(4).Infof("error recording current command: %v", err) } if cmdutil.GetDryRunFlag(cmd) { return cmdutil.PrintObject(cmd, object, out) diff --git a/pkg/kubectl/cmd/create/BUILD b/pkg/kubectl/cmd/create/BUILD index a10104449a5..877344edf5e 100644 --- a/pkg/kubectl/cmd/create/BUILD +++ b/pkg/kubectl/cmd/create/BUILD @@ -27,9 +27,11 @@ go_library( "//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util/editor:go_default_library", + "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/printers:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/batch/v1beta1:go_default_library", diff --git a/pkg/kubectl/cmd/create/create.go b/pkg/kubectl/cmd/create/create.go index 66bf40555db..b696096931d 100644 --- a/pkg/kubectl/cmd/create/create.go +++ b/pkg/kubectl/cmd/create/create.go @@ -27,6 +27,7 @@ import ( "net/url" + "github.com/golang/glog" "k8s.io/apimachinery/pkg/api/meta" kruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -34,13 +35,14 @@ import ( "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/cmd/util/editor" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) type CreateOptions struct { - PrintFlags *PrintFlags - PrintObj func(obj kruntime.Object) error + PrintFlags *PrintFlags + RecordFlags *genericclioptions.RecordFlags DryRun bool @@ -50,6 +52,9 @@ type CreateOptions struct { Raw string Out io.Writer ErrOut io.Writer + + Recorder genericclioptions.Recorder + PrintObj func(obj kruntime.Object) error } var ( @@ -69,13 +74,18 @@ var ( kubectl create -f docker-registry.yaml --edit -o json`)) ) -func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { - options := &CreateOptions{ - PrintFlags: NewPrintFlags("created"), +func NewCreateOptions(out, errOut io.Writer) *CreateOptions { + return &CreateOptions{ + PrintFlags: NewPrintFlags("created"), + RecordFlags: genericclioptions.NewRecordFlags(), Out: out, ErrOut: errOut, } +} + +func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { + o := NewCreateOptions(out, errOut) cmd := &cobra.Command{ Use: "create -f FILENAME", @@ -84,32 +94,34 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { Long: createLong, Example: createExample, Run: func(cmd *cobra.Command, args []string) { - if cmdutil.IsFilenameSliceEmpty(options.FilenameOptions.Filenames) { + if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) { defaultRunFunc := cmdutil.DefaultSubCommandRun(errOut) defaultRunFunc(cmd, args) return } - cmdutil.CheckErr(options.Complete(cmd)) - cmdutil.CheckErr(options.ValidateArgs(cmd, args)) - cmdutil.CheckErr(options.RunCreate(f, cmd)) + cmdutil.CheckErr(o.Complete(f, cmd)) + cmdutil.CheckErr(o.ValidateArgs(cmd, args)) + cmdutil.CheckErr(o.RunCreate(f, cmd)) }, } + // bind flag structs + o.RecordFlags.AddFlags(cmd) + usage := "to use to create the resource" - cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) cmd.MarkFlagRequired("filename") cmdutil.AddValidateFlags(cmd) - cmd.Flags().BoolVar(&options.EditBeforeCreate, "edit", options.EditBeforeCreate, "Edit the API resource before creating") + cmd.Flags().BoolVar(&o.EditBeforeCreate, "edit", o.EditBeforeCreate, "Edit the API resource before creating") cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows", "Only relevant if --edit=true. Defaults to the line ending native to your platform.") cmdutil.AddApplyAnnotationFlags(cmd) - cmdutil.AddRecordFlag(cmd) cmdutil.AddDryRunFlag(cmd) cmdutil.AddInclude3rdPartyFlags(cmd) - cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") - cmd.Flags().StringVar(&options.Raw, "raw", options.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.") + cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.") - options.PrintFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) // create subcommands cmd.AddCommand(NewCmdCreateNamespace(f, out)) @@ -160,7 +172,15 @@ func (o *CreateOptions) ValidateArgs(cmd *cobra.Command, args []string) error { return nil } -func (o *CreateOptions) Complete(cmd *cobra.Command) error { +func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + o.RecordFlags.Complete(f.Command(cmd, false)) + + var err error + o.Recorder, err = o.RecordFlags.ToRecorder() + if err != nil { + return err + } + o.DryRun = cmdutil.GetDryRunFlag(cmd) if o.DryRun { @@ -186,7 +206,7 @@ func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error { } if o.EditBeforeCreate { - return RunEditOnCreate(f, o.Out, o.ErrOut, cmd, &o.FilenameOptions) + return RunEditOnCreate(f, o.RecordFlags, o.Out, o.ErrOut, cmd, &o.FilenameOptions) } schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate")) if err != nil { @@ -221,10 +241,8 @@ func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error { return cmdutil.AddSourceToErr("creating", info.Source, err) } - if cmdutil.ShouldRecord(cmd, info) { - if err := cmdutil.RecordChangeCause(info.Object, f.Command(cmd, false)); err != nil { - return cmdutil.AddSourceToErr("creating", info.Source, err) - } + if err := o.Recorder.Record(info.Object); err != nil { + glog.V(4).Infof("error recording current command: %v", err) } if !o.DryRun { @@ -273,21 +291,18 @@ func (o *CreateOptions) raw(f cmdutil.Factory) error { return nil } -func RunEditOnCreate(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, options *resource.FilenameOptions) error { - editOptions := &editor.EditOptions{ - EditMode: editor.EditBeforeCreateMode, - FilenameOptions: *options, - ValidateOptions: cmdutil.ValidateOptions{ - EnableValidation: cmdutil.GetFlagBool(cmd, "validate"), - }, - Output: cmdutil.GetFlagString(cmd, "output"), - WindowsLineEndings: cmdutil.GetFlagBool(cmd, "windows-line-endings"), - ApplyAnnotation: cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), - Record: cmdutil.GetFlagBool(cmd, "record"), - ChangeCause: f.Command(cmd, false), - Include3rdParty: cmdutil.GetFlagBool(cmd, "include-extended-apis"), +func RunEditOnCreate(f cmdutil.Factory, recordFlags *genericclioptions.RecordFlags, out, errOut io.Writer, cmd *cobra.Command, options *resource.FilenameOptions) error { + editOptions := editor.NewEditOptions(editor.EditBeforeCreateMode, out, errOut) + editOptions.FilenameOptions = *options + editOptions.ValidateOptions = cmdutil.ValidateOptions{ + EnableValidation: cmdutil.GetFlagBool(cmd, "validate"), } - err := editOptions.Complete(f, out, errOut, []string{}, cmd) + editOptions.Output = cmdutil.GetFlagString(cmd, "output") + editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + editOptions.RecordFlags = recordFlags + editOptions.Include3rdParty = cmdutil.GetFlagBool(cmd, "include-extended-apis") + + err := editOptions.Complete(f, []string{}, cmd) if err != nil { return err } diff --git a/pkg/kubectl/cmd/edit.go b/pkg/kubectl/cmd/edit.go index b66758c8997..c321a0e4ed1 100644 --- a/pkg/kubectl/cmd/edit.go +++ b/pkg/kubectl/cmd/edit.go @@ -19,7 +19,6 @@ package cmd import ( "fmt" "io" - "runtime" "github.com/spf13/cobra" @@ -70,13 +69,10 @@ var ( ) func NewCmdEdit(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { - options := &editor.EditOptions{ - EditMode: editor.NormalEditMode, - Output: "yaml", - WindowsLineEndings: runtime.GOOS == "windows", - ValidateOptions: cmdutil.ValidateOptions{EnableValidation: true}, - Include3rdParty: true, - } + o := editor.NewEditOptions(editor.NormalEditMode, out, errOut) + o.ValidateOptions = cmdutil.ValidateOptions{EnableValidation: true} + o.Include3rdParty = true + validArgs := cmdutil.ValidArgList(f) cmd := &cobra.Command{ @@ -86,28 +82,30 @@ func NewCmdEdit(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { Long: editLong, Example: fmt.Sprintf(editExample), Run: func(cmd *cobra.Command, args []string) { - options.ChangeCause = f.Command(cmd, false) - if err := options.Complete(f, out, errOut, args, cmd); err != nil { + if err := o.Complete(f, args, cmd); err != nil { cmdutil.CheckErr(err) } - if err := options.Run(); err != nil { + if err := o.Run(); err != nil { cmdutil.CheckErr(err) } }, ValidArgs: validArgs, ArgAliases: kubectl.ResourceAliases(validArgs), } + + // bind flag structs + o.RecordFlags.AddFlags(cmd) + usage := "to use to edit the resource" - cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) - cmdutil.AddValidateOptionFlags(cmd, &options.ValidateOptions) - cmd.Flags().StringVarP(&options.Output, "output", "o", options.Output, "Output format. One of: yaml|json.") - cmd.Flags().BoolVarP(&options.OutputPatch, "output-patch", "", options.OutputPatch, "Output the patch if the resource is edited.") - cmd.Flags().BoolVar(&options.WindowsLineEndings, "windows-line-endings", options.WindowsLineEndings, + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + cmdutil.AddValidateOptionFlags(cmd, &o.ValidateOptions) + cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: yaml|json.") + cmd.Flags().BoolVarP(&o.OutputPatch, "output-patch", "", o.OutputPatch, "Output the patch if the resource is edited.") + cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings, "Defaults to the line ending native to your platform.") - cmdutil.AddApplyAnnotationVarFlags(cmd, &options.ApplyAnnotation) - cmdutil.AddRecordVarFlag(cmd, &options.Record) - cmdutil.AddInclude3rdPartyVarFlags(cmd, &options.Include3rdParty) + cmdutil.AddApplyAnnotationVarFlags(cmd, &o.ApplyAnnotation) + cmdutil.AddInclude3rdPartyVarFlags(cmd, &o.Include3rdParty) cmdutil.AddIncludeUninitializedFlag(cmd) return cmd } diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-record/3.request b/pkg/kubectl/cmd/testdata/edit/testcase-list-record/3.request index c609f20a953..ba542f24b9d 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-record/3.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-record/3.request @@ -1,10 +1,5 @@ { "data": { "new-data3": "newivalue" - }, - "metadata": { - "annotations": { - "kubernetes.io/change-cause": "edit test cmd invocation" - } } } \ No newline at end of file diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-record/4.request b/pkg/kubectl/cmd/testdata/edit/testcase-list-record/4.request index 3520e4699fc..572a7181921 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-record/4.request +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-record/4.request @@ -1,8 +1,5 @@ { "metadata": { - "annotations": { - "kubernetes.io/change-cause": "edit test cmd invocation" - }, "labels": { "new-label2": "foo2" } diff --git a/pkg/kubectl/cmd/util/editor/BUILD b/pkg/kubectl/cmd/util/editor/BUILD index ec7ea4d3966..c0fc7acbc08 100644 --- a/pkg/kubectl/cmd/util/editor/BUILD +++ b/pkg/kubectl/cmd/util/editor/BUILD @@ -18,6 +18,7 @@ go_library( "//pkg/apis/core:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/util/crlf:go_default_library", diff --git a/pkg/kubectl/cmd/util/editor/editoptions.go b/pkg/kubectl/cmd/util/editor/editoptions.go index 9af8f0f33c3..435ae6b01af 100644 --- a/pkg/kubectl/cmd/util/editor/editoptions.go +++ b/pkg/kubectl/cmd/util/editor/editoptions.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" "reflect" + goruntime "runtime" "strings" "github.com/evanphx/json-patch" @@ -44,6 +45,7 @@ import ( api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl" 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/scheme" "k8s.io/kubernetes/pkg/kubectl/util/crlf" @@ -53,6 +55,7 @@ import ( // EditOptions contains all the options for running edit cli command. type EditOptions struct { resource.FilenameOptions + RecordFlags *genericclioptions.RecordFlags Output string OutputPatch bool @@ -67,18 +70,32 @@ type EditOptions struct { CmdNamespace string ApplyAnnotation bool - Record bool ChangeCause string Include3rdParty bool Out io.Writer ErrOut io.Writer + Recorder genericclioptions.Recorder f cmdutil.Factory editPrinterOptions *editPrinterOptions updatedResultGetter func(data []byte) *resource.Result } +func NewEditOptions(editMode EditMode, out, errOut io.Writer) *EditOptions { + return &EditOptions{ + RecordFlags: genericclioptions.NewRecordFlags(), + + EditMode: editMode, + + Output: "yaml", + WindowsLineEndings: goruntime.GOOS == "windows", + + Out: out, + ErrOut: errOut, + } +} + type editPrinterOptions struct { printer printers.ResourcePrinter ext string @@ -86,7 +103,15 @@ type editPrinterOptions struct { } // Complete completes all the required options -func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []string, cmd *cobra.Command) error { +func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error { + o.RecordFlags.Complete(f.Command(cmd, false)) + + var err error + o.Recorder, err = o.RecordFlags.ToRecorder() + if err != nil { + return err + } + if o.EditMode != NormalEditMode && o.EditMode != EditBeforeCreateMode && o.EditMode != ApplyEditMode { return fmt.Errorf("unsupported edit mode %q", o.EditMode) } @@ -138,10 +163,6 @@ func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args [] o.CmdNamespace = cmdNamespace o.f = f - // Set up writer - o.Out = out - o.ErrOut = errOut - return nil } @@ -609,10 +630,8 @@ func (o *EditOptions) visitAnnotation(annotationVisitor resource.Visitor) error return err } } - if o.Record || cmdutil.ContainsChangeCause(info) { - if err := cmdutil.RecordChangeCause(info.Object, o.ChangeCause); err != nil { - return err - } + if err := o.Recorder.Record(info.Object); err != nil { + glog.V(4).Infof("error recording current command: %v", err) } return nil diff --git a/pkg/kubectl/genericclioptions/BUILD b/pkg/kubectl/genericclioptions/BUILD new file mode 100644 index 00000000000..080abfe414a --- /dev/null +++ b/pkg/kubectl/genericclioptions/BUILD @@ -0,0 +1,30 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "record_flags.go", + ], + importpath = "k8s.io/kubernetes/pkg/kubectl/genericclioptions", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/spf13/cobra:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/genericclioptions/doc.go b/pkg/kubectl/genericclioptions/doc.go new file mode 100644 index 00000000000..2bf32d25611 --- /dev/null +++ b/pkg/kubectl/genericclioptions/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2018 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 genericclioptions contains flags which can be added to you command, bound, completed, and produce +// useful helper functions. Nothing in this package can depend on kube/kube +package genericclioptions diff --git a/pkg/kubectl/cmd/record_flags.go b/pkg/kubectl/genericclioptions/record_flags.go similarity index 71% rename from pkg/kubectl/cmd/record_flags.go rename to pkg/kubectl/genericclioptions/record_flags.go index 0749eb8ffc5..20f88546926 100644 --- a/pkg/kubectl/cmd/record_flags.go +++ b/pkg/kubectl/genericclioptions/record_flags.go @@ -14,18 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cmd +package genericclioptions import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/kubectl" ) +// ChangeCauseAnnotation is the annotation indicating a guess at "why" something was changed +const ChangeCauseAnnotation = "kubernetes.io/change-cause" + // RecordFlags contains all flags associated with the "--record" operation type RecordFlags struct { + // Record indicates the state of the recording flag. It is a pointer so a caller can opt out or rebind Record *bool changeCause string @@ -34,6 +37,10 @@ type RecordFlags struct { // ToRecorder returns a ChangeCause recorder if --record=false was not // explicitly given by the user func (f *RecordFlags) ToRecorder() (Recorder, error) { + if f == nil { + return &NoopRecorder{}, nil + } + shouldRecord := false if f.Record != nil { shouldRecord = *f.Record @@ -50,17 +57,29 @@ func (f *RecordFlags) ToRecorder() (Recorder, error) { }, nil } +// Complete is called before the command is run, but after it is invoked to finish the state of the struct before use. func (f *RecordFlags) Complete(changeCause string) error { + if f == nil { + return nil + } + f.changeCause = changeCause return nil } +// AddFlags binds the requested flags to the provided flagset +// TODO have this only take a flagset func (f *RecordFlags) AddFlags(cmd *cobra.Command) { + if f == nil { + return + } + if f.Record != nil { cmd.Flags().BoolVar(f.Record, "record", *f.Record, "Record current kubectl command in the resource annotation. If set to false, do not record the command. If set to true, record the command. If not set, default to updating the existing annotation value only if one already exists.") } } +// NewRecordFlags provides a RecordFlags with reasonable default values set for use func NewRecordFlags() *RecordFlags { record := false @@ -69,12 +88,16 @@ func NewRecordFlags() *RecordFlags { } } +// Recorder is used to record why a runtime.Object was changed in an annotation. type Recorder interface { + // Record records why a runtime.Object was changed in an annotation. Record(runtime.Object) error } +// NoopRecorder does nothing. It is a "do nothing" that can be returned so code doesn't switch on it. type NoopRecorder struct{} +// Record implements Recorder func (r *NoopRecorder) Record(obj runtime.Object) error { return nil } @@ -95,7 +118,7 @@ func (r *ChangeCauseRecorder) Record(obj runtime.Object) error { if annotations == nil { annotations = make(map[string]string) } - annotations[kubectl.ChangeCauseAnnotation] = r.changeCause + annotations[ChangeCauseAnnotation] = r.changeCause accessor.SetAnnotations(annotations) return nil }