final record flag cleanup

This commit is contained in:
David Eads 2018-04-19 10:41:17 -04:00
parent 3856891198
commit ecd9a6be2e
17 changed files with 265 additions and 244 deletions

View File

@ -592,9 +592,10 @@ run_pod_tests() {
# Post-condition: valid-pod's record annotation still contains command with --record=true # Post-condition: valid-pod's record annotation still contains command with --record=true
kube::test::get_object_assert 'pod valid-pod' "{{range$annotations_field}}{{.}}:{{end}}" ".*--record=true.*" kube::test::get_object_assert 'pod valid-pod' "{{range$annotations_field}}{{.}}:{{end}}" ".*--record=true.*"
### Record label change with unspecified flag and previous change already recorded ### Record label change with specified flag and previous change already recorded
### we are no longer tricked by data from another user into revealing more information about our client
# Command # Command
kubectl label pods valid-pod new-record-change=true "${kube_flags[@]}" kubectl label pods valid-pod new-record-change=true --record=true "${kube_flags[@]}"
# Post-condition: valid-pod's record annotation contains new change # Post-condition: valid-pod's record annotation contains new change
kube::test::get_object_assert 'pod valid-pod' "{{range$annotations_field}}{{.}}:{{end}}" ".*new-record-change=true.*" kube::test::get_object_assert 'pod valid-pod' "{{range$annotations_field}}{{.}}:{{end}}" ".*new-record-change=true.*"

View File

@ -23,11 +23,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 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/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
) )
@ -72,8 +74,23 @@ var (
kubectl expose deployment nginx --port=80 --target-port=8000`)) kubectl expose deployment nginx --port=80 --target-port=8000`))
) )
type ExposeServiceOptions struct {
FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
Recorder genericclioptions.Recorder
}
func NewExposeServiceOptions() *ExposeServiceOptions {
return &ExposeServiceOptions{
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
}
}
func NewCmdExposeService(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdExposeService(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{} o := NewExposeServiceOptions()
validArgs := []string{} validArgs := []string{}
resources := regexp.MustCompile(`\s*,`).Split(exposeResources, -1) resources := regexp.MustCompile(`\s*,`).Split(exposeResources, -1)
@ -88,12 +105,15 @@ func NewCmdExposeService(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: exposeLong, Long: exposeLong,
Example: exposeExample, Example: exposeExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := RunExpose(f, out, cmd, args, options) cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(err) cmdutil.CheckErr(o.RunExpose(f, out, cmd, args))
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs), ArgAliases: kubectl.ResourceAliases(validArgs),
} }
o.RecordFlags.AddFlags(cmd)
cmdutil.AddPrinterFlags(cmd) cmdutil.AddPrinterFlags(cmd)
cmd.Flags().String("generator", "service/v2", i18n.T("The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.")) cmd.Flags().String("generator", "service/v2", i18n.T("The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'."))
cmd.Flags().String("protocol", "", i18n.T("The network protocol for the service to be created. Default is 'TCP'.")) cmd.Flags().String("protocol", "", i18n.T("The network protocol for the service to be created. Default is 'TCP'."))
@ -112,14 +132,25 @@ func NewCmdExposeService(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().String("cluster-ip", "", i18n.T("ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service.")) cmd.Flags().String("cluster-ip", "", i18n.T("ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service."))
usage := "identifying the resource to expose a service" usage := "identifying the resource to expose a service"
cmdutil.AddFilenameOptionFlags(cmd, options, usage) cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
return cmd return cmd
} }
func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error { func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error
o.RecordFlags.Complete(f.Command(cmd, false))
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
return err
}
func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
namespace, enforceNamespace, err := f.DefaultNamespace() namespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
@ -130,7 +161,7 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
Internal(). Internal().
ContinueOnError(). ContinueOnError().
NamespaceParam(namespace).DefaultNamespace(). NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options). FilenameParam(enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(false, args...). ResourceTypeOrNameArgs(false, args...).
Flatten(). Flatten().
Do() Do()
@ -247,10 +278,8 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
if err != nil { if err != nil {
return err return err
} }
if cmdutil.ShouldRecord(cmd, info) { if err := o.Recorder.Record(object); err != nil {
if err := cmdutil.RecordChangeCause(object, f.Command(cmd, false)); err != nil { glog.V(4).Infof("error recording current command: %v", err)
return err
}
} }
info.Refresh(object, true) info.Refresh(object, true)
if cmdutil.GetDryRunFlag(cmd) { if cmdutil.GetDryRunFlag(cmd) {

View File

@ -37,6 +37,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 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/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
) )
@ -45,6 +46,7 @@ import (
type LabelOptions struct { type LabelOptions struct {
// Filename options // Filename options
resource.FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
// Common user flags // Common user flags
overwrite bool overwrite bool
@ -61,6 +63,8 @@ type LabelOptions struct {
newLabels map[string]string newLabels map[string]string
removeLabels []string removeLabels []string
Recorder genericclioptions.Recorder
// Common shared fields // Common shared fields
out io.Writer out io.Writer
errout io.Writer errout io.Writer
@ -96,10 +100,19 @@ var (
kubectl label pods foo bar-`)) kubectl label pods foo bar-`))
) )
func NewCmdLabel(f cmdutil.Factory, out, errout io.Writer) *cobra.Command { func NewLabelOptions(out, errOut io.Writer) *LabelOptions {
options := &LabelOptions{ return &LabelOptions{
errout: errout, RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
out: out,
errout: errOut,
} }
}
func NewCmdLabel(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
o := NewLabelOptions(out, errOut)
validArgs := cmdutil.ValidArgList(f) validArgs := cmdutil.ValidArgList(f)
@ -110,36 +123,45 @@ func NewCmdLabel(f cmdutil.Factory, out, errout io.Writer) *cobra.Command {
Long: fmt.Sprintf(labelLong, validation.LabelValueMaxLength), Long: fmt.Sprintf(labelLong, validation.LabelValueMaxLength),
Example: labelExample, Example: labelExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if err := options.Complete(out, cmd, args); err != nil { if err := o.Complete(f, cmd, args); err != nil {
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error())) cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error()))
} }
if err := options.Validate(); err != nil { if err := o.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error())) cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error()))
} }
cmdutil.CheckErr(options.RunLabel(f, cmd)) cmdutil.CheckErr(o.RunLabel(f, cmd))
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs), ArgAliases: kubectl.ResourceAliases(validArgs),
} }
o.RecordFlags.AddFlags(cmd)
cmdutil.AddPrinterFlags(cmd) cmdutil.AddPrinterFlags(cmd)
cmd.Flags().BoolVar(&options.overwrite, "overwrite", options.overwrite, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.") cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.")
cmd.Flags().BoolVar(&options.list, "list", options.list, "If true, display the labels for a given resource.") cmd.Flags().BoolVar(&o.list, "list", o.list, "If true, display the labels for a given resource.")
cmd.Flags().BoolVar(&options.local, "local", options.local, "If true, label will NOT contact api-server but run locally.") cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, label will NOT contact api-server but run locally.")
cmd.Flags().StringVarP(&options.selector, "selector", "l", options.selector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2).") cmd.Flags().StringVarP(&o.selector, "selector", "l", o.selector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2).")
cmd.Flags().BoolVar(&options.all, "all", options.all, "Select all resources, including uninitialized ones, in the namespace of the specified resource types") cmd.Flags().BoolVar(&o.all, "all", o.all, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
cmd.Flags().StringVar(&options.resourceVersion, "resource-version", options.resourceVersion, i18n.T("If non-empty, the labels update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.")) cmd.Flags().StringVar(&o.resourceVersion, "resource-version", o.resourceVersion, i18n.T("If non-empty, the labels update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource."))
usage := "identifying the resource to update the labels" usage := "identifying the resource to update the labels"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd) cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd return cmd
} }
// Complete adapts from the command line args and factory to the data required. // Complete adapts from the command line args and factory to the data required.
func (o *LabelOptions) Complete(out io.Writer, cmd *cobra.Command, args []string) (err error) { func (o *LabelOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.out = out var err error
o.RecordFlags.Complete(f.Command(cmd, false))
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.outputFormat = cmdutil.GetFlagString(cmd, "output") o.outputFormat = cmdutil.GetFlagString(cmd, "output")
o.dryrun = cmdutil.GetDryRunFlag(cmd) o.dryrun = cmdutil.GetDryRunFlag(cmd)
@ -178,8 +200,6 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
return err return err
} }
changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder(). b := f.NewBuilder().
Unstructured(). Unstructured().
@ -242,10 +262,8 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
if err := labelFunc(obj, o.overwrite, o.resourceVersion, o.newLabels, o.removeLabels); err != nil { if err := labelFunc(obj, o.overwrite, o.resourceVersion, o.newLabels, o.removeLabels); err != nil {
return err return err
} }
if cmdutil.ShouldRecord(cmd, info) { if err := o.Recorder.Record(obj); err != nil {
if err := cmdutil.RecordChangeCause(obj, changeCause); err != nil { glog.V(4).Infof("error recording current command: %v", err)
return err
}
} }
newData, err := json.Marshal(obj) newData, err := json.Marshal(obj)
if err != nil { if err != nil {

View File

@ -29,7 +29,6 @@ import (
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
) )
@ -335,8 +334,8 @@ func TestLabelErrors(t *testing.T) {
for k, v := range testCase.flags { for k, v := range testCase.flags {
cmd.Flags().Set(k, v) cmd.Flags().Set(k, v)
} }
opts := LabelOptions{} opts := NewLabelOptions(buf, buf)
err := opts.Complete(buf, cmd, testCase.args) err := opts.Complete(tf, cmd, testCase.args)
if err == nil { if err == nil {
err = opts.Validate() err = opts.Validate()
} }
@ -392,9 +391,9 @@ func TestLabelForResourceFromFile(t *testing.T) {
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(tf, buf, buf) cmd := NewCmdLabel(tf, buf, buf)
opts := LabelOptions{FilenameOptions: resource.FilenameOptions{ opts := NewLabelOptions(buf, buf)
Filenames: []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}}} opts.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
err := opts.Complete(buf, cmd, []string{"a=b"}) err := opts.Complete(tf, cmd, []string{"a=b"})
if err == nil { if err == nil {
err = opts.Validate() err = opts.Validate()
} }
@ -425,10 +424,10 @@ func TestLabelLocal(t *testing.T) {
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(tf, buf, buf) cmd := NewCmdLabel(tf, buf, buf)
opts := LabelOptions{FilenameOptions: resource.FilenameOptions{ opts := NewLabelOptions(buf, buf)
Filenames: []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}}, opts.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
local: true} opts.local = true
err := opts.Complete(buf, cmd, []string{"a=b"}) err := opts.Complete(tf, cmd, []string{"a=b"})
if err == nil { if err == nil {
err = opts.Validate() err = opts.Validate()
} }
@ -482,9 +481,10 @@ func TestLabelMultipleObjects(t *testing.T) {
tf.ClientConfigVal = defaultClientConfig() tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
opts := LabelOptions{all: true} opts := NewLabelOptions(buf, buf)
opts.all = true
cmd := NewCmdLabel(tf, buf, buf) cmd := NewCmdLabel(tf, buf, buf)
err := opts.Complete(buf, cmd, []string{"pods", "a=b"}) err := opts.Complete(tf, cmd, []string{"pods", "a=b"})
if err == nil { if err == nil {
err = opts.Validate() err = opts.Validate()
} }

View File

@ -36,6 +36,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 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/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
@ -47,10 +48,13 @@ var patchTypes = map[string]types.PatchType{"json": types.JSONPatchType, "merge"
// referencing the cmd.Flags() // referencing the cmd.Flags()
type PatchOptions struct { type PatchOptions struct {
resource.FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
Local bool Local bool
DryRun bool DryRun bool
Recorder genericclioptions.Recorder
OutputFormat string OutputFormat string
} }
@ -79,8 +83,16 @@ var (
kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'`)) kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'`))
) )
func NewPatchOptions() *PatchOptions {
return &PatchOptions{
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
}
}
func NewCmdPatch(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdPatch(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &PatchOptions{} o := NewPatchOptions()
validArgs := cmdutil.ValidArgList(f) validArgs := cmdutil.ValidArgList(f)
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -90,32 +102,47 @@ func NewCmdPatch(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: patchLong, Long: patchLong,
Example: patchExample, Example: patchExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
options.OutputFormat = cmdutil.GetFlagString(cmd, "output") cmdutil.CheckErr(o.Complete(f, cmd))
options.DryRun = cmdutil.GetFlagBool(cmd, "dry-run") cmdutil.CheckErr(o.RunPatch(f, out, cmd, args))
err := RunPatch(f, out, cmd, args, options)
cmdutil.CheckErr(err)
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs), ArgAliases: kubectl.ResourceAliases(validArgs),
} }
o.RecordFlags.AddFlags(cmd)
cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.") cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.")
cmd.MarkFlagRequired("patch") cmd.MarkFlagRequired("patch")
cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List())) cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
cmdutil.AddPrinterFlags(cmd) cmdutil.AddPrinterFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
usage := "identifying the resource to update" usage := "identifying the resource to update"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmd.Flags().BoolVar(&options.Local, "local", options.Local, "If true, patch will operate on the content of the file, not the server-side resource.") cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, patch will operate on the content of the file, not the server-side resource.")
return cmd return cmd
} }
func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *PatchOptions) error { func (o *PatchOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error
o.RecordFlags.Complete(f.Command(cmd, false))
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run")
return err
}
func (o *PatchOptions) RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
switch { switch {
case options.Local && len(args) != 0: case o.Local && len(args) != 0:
return fmt.Errorf("cannot specify --local and server resources") return fmt.Errorf("cannot specify --local and server resources")
} }
@ -148,7 +175,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
Unstructured(). Unstructured().
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(false, args...). ResourceTypeOrNameArgs(false, args...).
Flatten(). Flatten().
Do() Do()
@ -169,38 +196,36 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return err return err
} }
if !options.Local && !options.DryRun { if !o.Local && !o.DryRun {
helper := resource.NewHelper(client, mapping) helper := resource.NewHelper(client, mapping)
patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes) patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes)
if err != nil { if err != nil {
return err return err
} }
// Record the change as a second patch to avoid trying to merge with a user's patch data
if cmdutil.ShouldRecord(cmd, info) { didPatch := !reflect.DeepEqual(info.Object, patchedObj)
// Copy the resource info and update with the result of applying the user's patch
infoCopy := *info // if the recorder makes a change, compute and create another patch
infoCopy.Object = patchedObj if mergePatch, err := o.Recorder.MakeRecordMergePatch(patchedObj); err != nil {
if patch, patchType, err := cmdutil.ChangeResourcePatch(&infoCopy, f.Command(cmd, true)); err == nil { glog.V(4).Infof("error recording current command: %v", err)
if recordedObj, err := helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil { } else if len(mergePatch) > 0 {
if recordedObj, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch); err != nil {
glog.V(4).Infof("error recording reason: %v", err) glog.V(4).Infof("error recording reason: %v", err)
} else { } else {
patchedObj = recordedObj patchedObj = recordedObj
} }
} }
}
count++ count++
didPatch := !reflect.DeepEqual(info.Object, patchedObj)
// After computing whether we changed data, refresh the resource info with the resulting object // After computing whether we changed data, refresh the resource info with the resulting object
if err := info.Refresh(patchedObj, true); err != nil { if err := info.Refresh(patchedObj, true); err != nil {
return err return err
} }
if len(options.OutputFormat) > 0 && options.OutputFormat != "name" { if len(o.OutputFormat) > 0 && o.OutputFormat != "name" {
return cmdutil.PrintObject(cmd, info.Object, out) return cmdutil.PrintObject(cmd, info.Object, out)
} }
cmdutil.PrintSuccess(options.OutputFormat == "name", out, info.Object, false, patchOperation(didPatch)) cmdutil.PrintSuccess(o.OutputFormat == "name", out, info.Object, false, patchOperation(didPatch))
// if object was not successfully patched, exit with error code 1 // if object was not successfully patched, exit with error code 1
if !didPatch { if !didPatch {
@ -239,11 +264,11 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
} }
} }
if len(options.OutputFormat) > 0 && options.OutputFormat != "name" { if len(o.OutputFormat) > 0 && o.OutputFormat != "name" {
return cmdutil.PrintObject(cmd, info.Object, out) return cmdutil.PrintObject(cmd, info.Object, out)
} }
cmdutil.PrintSuccess(options.OutputFormat == "name", out, info.Object, options.DryRun, patchOperation(didPatch)) cmdutil.PrintSuccess(o.OutputFormat == "name", out, info.Object, o.DryRun, patchOperation(didPatch))
return nil return nil
}) })
if err != nil { if err != nil {

View File

@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 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/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/kubectl/validation"
@ -64,39 +65,43 @@ var (
kubectl replace --force -f ./pod.json`)) kubectl replace --force -f ./pod.json`))
) )
type ReplaceOpts struct { type ReplaceOptions struct {
PrintFlags *printers.PrintFlags PrintFlags *printers.PrintFlags
DeleteFlags *DeleteFlags DeleteFlags *DeleteFlags
RecordFlags *genericclioptions.RecordFlags
DeleteOptions *DeleteOptions DeleteOptions *DeleteOptions
PrintObj func(obj runtime.Object) error PrintObj func(obj runtime.Object) error
createAnnotation bool createAnnotation bool
changeCause string
validate bool validate bool
Schema validation.Schema Schema validation.Schema
Builder func() *resource.Builder Builder func() *resource.Builder
BuilderArgs []string BuilderArgs []string
ShouldRecord func(info *resource.Info) bool
Namespace string Namespace string
EnforceNamespace bool EnforceNamespace bool
Recorder genericclioptions.Recorder
Out io.Writer Out io.Writer
ErrOut io.Writer ErrOut io.Writer
} }
func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { func NewReplaceOptions(out, errOut io.Writer) *ReplaceOptions {
options := &ReplaceOpts{ return &ReplaceOptions{
PrintFlags: printers.NewPrintFlags("replaced"), PrintFlags: printers.NewPrintFlags("replaced"),
DeleteFlags: NewDeleteFlags("to use to replace the resource."), DeleteFlags: NewDeleteFlags("to use to replace the resource."),
Out: out, Out: out,
ErrOut: errOut, ErrOut: errOut,
} }
}
func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
o := NewReplaceOptions(out, errOut)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "replace -f FILENAME", Use: "replace -f FILENAME",
@ -106,32 +111,35 @@ func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Example: replaceExample, Example: replaceExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
cmdutil.CheckErr(options.Complete(f, cmd, args)) cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate(cmd)) cmdutil.CheckErr(o.Validate(cmd))
cmdutil.CheckErr(options.Run()) cmdutil.CheckErr(o.Run())
}, },
} }
options.PrintFlags.AddFlags(cmd) o.PrintFlags.AddFlags(cmd)
options.DeleteFlags.AddFlags(cmd) o.DeleteFlags.AddFlags(cmd)
o.RecordFlags.AddFlags(cmd)
cmd.MarkFlagRequired("filename") cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd) cmdutil.AddValidateFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
return cmd return cmd
} }
func (o *ReplaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.validate = cmdutil.GetFlagBool(cmd, "validate") var err error
o.changeCause = f.Command(cmd, false)
o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.ShouldRecord = func(info *resource.Info) bool { o.RecordFlags.Complete(f.Command(cmd, false))
return cmdutil.ShouldRecord(cmd, info) o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
} }
o.validate = cmdutil.GetFlagBool(cmd, "validate")
o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
printer, err := o.PrintFlags.ToPrinter() printer, err := o.PrintFlags.ToPrinter()
if err != nil { if err != nil {
return err return err
@ -173,7 +181,7 @@ func (o *ReplaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
return nil return nil
} }
func (o *ReplaceOpts) Validate(cmd *cobra.Command) error { func (o *ReplaceOptions) Validate(cmd *cobra.Command) error {
if o.DeleteOptions.GracePeriod >= 0 && !o.DeleteOptions.ForceDeletion { if o.DeleteOptions.GracePeriod >= 0 && !o.DeleteOptions.ForceDeletion {
return fmt.Errorf("--grace-period must have --force specified") return fmt.Errorf("--grace-period must have --force specified")
} }
@ -189,7 +197,7 @@ func (o *ReplaceOpts) Validate(cmd *cobra.Command) error {
return nil return nil
} }
func (o *ReplaceOpts) Run() error { func (o *ReplaceOptions) Run() error {
if o.DeleteOptions.ForceDeletion { if o.DeleteOptions.ForceDeletion {
return o.forceReplace() return o.forceReplace()
} }
@ -215,10 +223,8 @@ func (o *ReplaceOpts) Run() error {
return cmdutil.AddSourceToErr("replacing", info.Source, err) return cmdutil.AddSourceToErr("replacing", info.Source, err)
} }
if o.ShouldRecord(info) { if err := o.Recorder.Record(info.Object); err != nil {
if err := cmdutil.RecordChangeCause(info.Object, o.changeCause); err != nil { glog.V(4).Infof("error recording current command: %v", err)
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
} }
// Serialize the object with the annotation applied. // Serialize the object with the annotation applied.
@ -232,7 +238,7 @@ func (o *ReplaceOpts) Run() error {
}) })
} }
func (o *ReplaceOpts) forceReplace() error { func (o *ReplaceOptions) forceReplace() error {
for i, filename := range o.DeleteOptions.FilenameOptions.Filenames { for i, filename := range o.DeleteOptions.FilenameOptions.Filenames {
if filename == "-" { if filename == "-" {
tempDir, err := ioutil.TempDir("", "kubectl_replace_") tempDir, err := ioutil.TempDir("", "kubectl_replace_")
@ -313,10 +319,8 @@ func (o *ReplaceOpts) forceReplace() error {
return err return err
} }
if o.ShouldRecord(info) { if err := o.Recorder.Record(info.Object); err != nil {
if err := cmdutil.RecordChangeCause(info.Object, o.changeCause); err != nil { glog.V(4).Infof("error recording current command: %v", err)
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
} }
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)

View File

@ -22,11 +22,14 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/types"
batchclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion" batchclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/scalejob" "k8s.io/kubernetes/pkg/kubectl/cmd/scalejob"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 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/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
) )
@ -58,9 +61,24 @@ var (
kubectl scale --replicas=3 statefulset/web`)) kubectl scale --replicas=3 statefulset/web`))
) )
type ScaleOptions struct {
FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
Recorder genericclioptions.Recorder
}
func NewScaleOptions() *ScaleOptions {
return &ScaleOptions{
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
}
}
// NewCmdScale returns a cobra command with the appropriate configuration and flags to run scale // NewCmdScale returns a cobra command with the appropriate configuration and flags to run scale
func NewCmdScale(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { func NewCmdScale(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &resource.FilenameOptions{} o := NewScaleOptions()
validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"} validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
argAliases := kubectl.ResourceAliases(validArgs) argAliases := kubectl.ResourceAliases(validArgs)
@ -72,14 +90,17 @@ func NewCmdScale(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Long: scaleLong, Long: scaleLong,
Example: scaleExample, Example: scaleExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
err := RunScale(f, out, errOut, cmd, args, shortOutput, options) cmdutil.CheckErr(o.RunScale(f, out, errOut, cmd, args, shortOutput))
cmdutil.CheckErr(err)
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: argAliases, ArgAliases: argAliases,
} }
o.RecordFlags.AddFlags(cmd)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().Bool("all", false, "Select all resources in the namespace of the specified resource types") cmd.Flags().Bool("all", false, "Select all resources in the namespace of the specified resource types")
cmd.Flags().String("resource-version", "", i18n.T("Precondition for resource version. Requires that the current resource version match this value in order to scale.")) cmd.Flags().String("resource-version", "", i18n.T("Precondition for resource version. Requires that the current resource version match this value in order to scale."))
@ -88,15 +109,26 @@ func NewCmdScale(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd.MarkFlagRequired("replicas") cmd.MarkFlagRequired("replicas")
cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).") cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddRecordFlag(cmd)
usage := "identifying the resource to set a new size" usage := "identifying the resource to set a new size"
cmdutil.AddFilenameOptionFlags(cmd, options, usage) cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
return cmd return cmd
} }
func (o *ScaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error
o.RecordFlags.Complete(f.Command(cmd, false))
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
return err
}
// RunScale executes the scaling // RunScale executes the scaling
func RunScale(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *resource.FilenameOptions) error { func (o *ScaleOptions) RunScale(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, shortOutput bool) error {
cmdNamespace, enforceNamespace, err := f.DefaultNamespace() cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
@ -114,7 +146,7 @@ func RunScale(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
Unstructured(). Unstructured().
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options). FilenameParam(enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(all, args...). ResourceTypeOrNameArgs(all, args...).
Flatten(). Flatten().
LabelSelectorParam(selector). LabelSelectorParam(selector).
@ -180,22 +212,20 @@ func RunScale(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
} }
} }
if cmdutil.ShouldRecord(cmd, info) { // if the recorder makes a change, compute and create another patch
patchBytes, patchType, err := cmdutil.ChangeResourcePatch(info, f.Command(cmd, true)) if mergePatch, err := o.Recorder.MakeRecordMergePatch(info.Object); err != nil {
if err != nil { glog.V(4).Infof("error recording current command: %v", err)
return err } else if len(mergePatch) > 0 {
}
mapping := info.ResourceMapping()
client, err := f.UnstructuredClientForMapping(mapping) client, err := f.UnstructuredClientForMapping(mapping)
if err != nil { if err != nil {
return err return err
} }
helper := resource.NewHelper(client, mapping) helper := resource.NewHelper(client, mapping)
_, err = helper.Patch(info.Namespace, info.Name, patchType, patchBytes) if _, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch); err != nil {
if err != nil { glog.V(4).Infof("error recording reason: %v", err)
return err
} }
} }
counter++ counter++
cmdutil.PrintSuccess(shortOutput, out, info.Object, false, "scaled") cmdutil.PrintSuccess(shortOutput, out, info.Object, false, "scaled")
return nil return nil

View File

@ -122,8 +122,6 @@ type EnvOptions struct {
Builder *resource.Builder Builder *resource.Builder
Infos []*resource.Info Infos []*resource.Info
Cmd *cobra.Command
UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error)
} }
@ -193,7 +191,7 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
} }
resources, envArgs, ok := envutil.SplitEnvironmentFromResources(args) resources, envArgs, ok := envutil.SplitEnvironmentFromResources(args)
if !ok { if !ok {
return cmdutil.UsageErrorf(o.Cmd, "all resources must be specified before environment changes: %s", strings.Join(args, " ")) return cmdutil.UsageErrorf(cmd, "all resources must be specified before environment changes: %s", strings.Join(args, " "))
} }
if len(o.Filenames) == 0 && len(resources) < 1 { if len(o.Filenames) == 0 && len(resources) < 1 {
return cmdutil.UsageErrorf(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>") return cmdutil.UsageErrorf(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>")
@ -213,7 +211,6 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
o.EnvArgs = envArgs o.EnvArgs = envArgs
o.Resources = resources o.Resources = resources
o.Cmd = cmd
if o.DryRun { if o.DryRun {
// TODO(juanvallejo): This can be cleaned up even further by creating // TODO(juanvallejo): This can be cleaned up even further by creating
@ -229,7 +226,7 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
o.PrintObj = printer.PrintObj o.PrintObj = printer.PrintObj
if o.List && len(o.Output) > 0 { if o.List && len(o.Output) > 0 {
return cmdutil.UsageErrorf(o.Cmd, "--list and --output may not be specified together") return cmdutil.UsageErrorf(cmd, "--list and --output may not be specified together")
} }
return nil return nil

View File

@ -50,9 +50,7 @@ type SetImageOptions struct {
DryRun bool DryRun bool
All bool All bool
Output string Output string
ChangeCause string
Local bool Local bool
Cmd *cobra.Command
ResolveImage func(in string) (string, error) ResolveImage func(in string) (string, error)
PrintObj printers.ResourcePrinterFunc PrintObj printers.ResourcePrinterFunc
@ -138,11 +136,9 @@ func (o *SetImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
} }
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
o.ChangeCause = f.Command(cmd, false)
o.DryRun = cmdutil.GetDryRunFlag(cmd) o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.Output = cmdutil.GetFlagString(cmd, "output") o.Output = cmdutil.GetFlagString(cmd, "output")
o.ResolveImage = f.ResolveImage o.ResolveImage = f.ResolveImage
o.Cmd = cmd
if o.DryRun { if o.DryRun {
o.PrintFlags.Complete("%s (dry run)") o.PrintFlags.Complete("%s (dry run)")

View File

@ -75,9 +75,7 @@ type SetResourcesOptions struct {
ContainerSelector string ContainerSelector string
Output string Output string
All bool All bool
ChangeCause string
Local bool Local bool
Cmd *cobra.Command
DryRun bool DryRun bool
@ -157,9 +155,7 @@ func (o *SetResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, ar
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
o.Output = cmdutil.GetFlagString(cmd, "output") o.Output = cmdutil.GetFlagString(cmd, "output")
o.ChangeCause = f.Command(cmd, false) o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.Cmd = cmd
o.DryRun = cmdutil.GetDryRunFlag(o.Cmd)
if o.DryRun { if o.DryRun {
o.PrintFlags.Complete("%s (dry run)") o.PrintFlags.Complete("%s (dry run)")

View File

@ -48,8 +48,6 @@ type SetSelectorOptions struct {
local bool local bool
dryrun bool dryrun bool
all bool all bool
record bool
changeCause string
output string output string
resources []string resources []string
@ -141,7 +139,6 @@ func (o *SetSelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
return err return err
} }
o.changeCause = f.Command(cmd, false)
mapper, _ := f.Object() mapper, _ := f.Object()
o.mapper = mapper o.mapper = mapper

View File

@ -65,12 +65,9 @@ type SetServiceAccountOptions struct {
out io.Writer out io.Writer
err io.Writer err io.Writer
dryRun bool dryRun bool
cmd *cobra.Command
shortOutput bool shortOutput bool
all bool all bool
record bool
output string output string
changeCause string
local bool local bool
updatePodSpecForObject func(runtime.Object, func(*v1.PodSpec) error) (bool, error) updatePodSpecForObject func(runtime.Object, func(*v1.PodSpec) error) (bool, error)
infos []*resource.Info infos []*resource.Info
@ -132,12 +129,9 @@ func (o *SetServiceAccountOptions) Complete(f cmdutil.Factory, cmd *cobra.Comman
} }
o.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name" o.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
o.record = cmdutil.GetRecordFlag(cmd)
o.changeCause = f.Command(cmd, false)
o.dryRun = cmdutil.GetDryRunFlag(cmd) o.dryRun = cmdutil.GetDryRunFlag(cmd)
o.output = cmdutil.GetFlagString(cmd, "output") o.output = cmdutil.GetFlagString(cmd, "output")
o.updatePodSpecForObject = f.UpdatePodSpecForObject o.updatePodSpecForObject = f.UpdatePodSpecForObject
o.cmd = cmd
if o.dryRun { if o.dryRun {
o.PrintFlags.Complete("%s (dry run)") o.PrintFlags.Complete("%s (dry run)")

View File

@ -58,11 +58,8 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",

View File

@ -34,13 +34,9 @@ import (
kerrors "k8s.io/apimachinery/pkg/api/errors" kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
@ -517,64 +513,10 @@ func UpdateObject(info *resource.Info, codec runtime.Codec, updateFn func(runtim
return info.Object, nil return info.Object, nil
} }
func AddRecordFlag(cmd *cobra.Command) {
cmd.Flags().Bool("record", false, "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.")
}
func GetRecordFlag(cmd *cobra.Command) bool {
return GetFlagBool(cmd, "record")
}
func GetDryRunFlag(cmd *cobra.Command) bool { func GetDryRunFlag(cmd *cobra.Command) bool {
return GetFlagBool(cmd, "dry-run") return GetFlagBool(cmd, "dry-run")
} }
// RecordChangeCause annotate change-cause to input runtime object.
func RecordChangeCause(obj runtime.Object, changeCause string) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
annotations := accessor.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
annotations[kubectl.ChangeCauseAnnotation] = changeCause
accessor.SetAnnotations(annotations)
return nil
}
// ChangeResourcePatch creates a patch between the origin input resource info
// and the annotated with change-cause input resource info.
func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, types.PatchType, error) {
// Get a versioned object
obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion())
if err != nil {
return nil, types.StrategicMergePatchType, err
}
oldData, err := json.Marshal(obj)
if err != nil {
return nil, types.StrategicMergePatchType, err
}
if err := RecordChangeCause(obj, changeCause); err != nil {
return nil, types.StrategicMergePatchType, err
}
newData, err := json.Marshal(obj)
if err != nil {
return nil, types.StrategicMergePatchType, err
}
switch obj := obj.(type) {
case *unstructured.Unstructured:
patch, err := jsonpatch.CreateMergePatch(oldData, newData)
return patch, types.MergePatchType, err
default:
patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
return patch, types.StrategicMergePatchType, err
}
}
// ContainsChangeCause checks if input resource info contains change-cause annotation. // ContainsChangeCause checks if input resource info contains change-cause annotation.
func ContainsChangeCause(info *resource.Info) bool { func ContainsChangeCause(info *resource.Info) bool {
annotations, err := info.Mapping.MetadataAccessor.Annotations(info.Object) annotations, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
@ -584,11 +526,6 @@ func ContainsChangeCause(info *resource.Info) bool {
return len(annotations[kubectl.ChangeCauseAnnotation]) > 0 return len(annotations[kubectl.ChangeCauseAnnotation]) > 0
} }
// ShouldRecord checks if we should record current change cause
func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool {
return GetRecordFlag(cmd) || (ContainsChangeCause(info) && !cmd.Flags().Changed("record"))
}
// GetResourcesAndPairs retrieves resources and "KEY=VALUE or KEY-" pair args from given args // GetResourcesAndPairs retrieves resources and "KEY=VALUE or KEY-" pair args from given args
func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) { func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) {
foundPair := false foundPair := false
@ -703,22 +640,6 @@ func RequireNoArguments(c *cobra.Command, args []string) {
} }
} }
// OutputsRawFormat determines if a command's output format is machine parsable
// or returns false if it is human readable (name, wide, etc.)
func OutputsRawFormat(cmd *cobra.Command) bool {
output := GetFlagString(cmd, "output")
if output == "json" ||
output == "yaml" ||
output == "go-template" ||
output == "go-template-file" ||
output == "jsonpath" ||
output == "jsonpath-file" {
return true
}
return false
}
// StripComments will transform a YAML file into JSON, thus dropping any comments // StripComments will transform a YAML file into JSON, thus dropping any comments
// in it. Note that if the given file has a syntax error, the transformation will // in it. Note that if the given file has a syntax error, the transformation will
// fail and we will manually drop all comments from the file. // fail and we will manually drop all comments from the file.

View File

@ -19,7 +19,6 @@ package util
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"strings" "strings"
"syscall" "syscall"
@ -195,19 +194,6 @@ func TestMerge(t *testing.T) {
} }
} }
type fileHandler struct {
data []byte
}
func (f *fileHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/error" {
res.WriteHeader(http.StatusNotFound)
return
}
res.WriteHeader(http.StatusOK)
res.Write(f.data)
}
type checkErrTestCase struct { type checkErrTestCase struct {
err error err error
expectedErr string expectedErr string

View File

@ -9,9 +9,11 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/kubectl/genericclioptions", importpath = "k8s.io/kubernetes/pkg/kubectl/genericclioptions",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
], ],
) )

View File

@ -17,10 +17,12 @@ limitations under the License.
package genericclioptions package genericclioptions
import ( import (
"github.com/evanphx/json-patch"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
) )
// ChangeCauseAnnotation is the annotation indicating a guess at "why" something was changed // ChangeCauseAnnotation is the annotation indicating a guess at "why" something was changed
@ -92,6 +94,7 @@ func NewRecordFlags() *RecordFlags {
type Recorder interface { type Recorder interface {
// Record records why a runtime.Object was changed in an annotation. // Record records why a runtime.Object was changed in an annotation.
Record(runtime.Object) error Record(runtime.Object) error
MakeRecordMergePatch(runtime.Object) ([]byte, error)
} }
// NoopRecorder does nothing. It is a "do nothing" that can be returned so code doesn't switch on it. // NoopRecorder does nothing. It is a "do nothing" that can be returned so code doesn't switch on it.
@ -102,6 +105,11 @@ func (r NoopRecorder) Record(obj runtime.Object) error {
return nil return nil
} }
// MakeRecordMergePatch implements Recorder
func (r NoopRecorder) MakeRecordMergePatch(obj runtime.Object) ([]byte, error) {
return nil, nil
}
// ChangeCauseRecorder annotates a "change-cause" to an input runtime object // ChangeCauseRecorder annotates a "change-cause" to an input runtime object
type ChangeCauseRecorder struct { type ChangeCauseRecorder struct {
changeCause string changeCause string
@ -122,3 +130,23 @@ func (r *ChangeCauseRecorder) Record(obj runtime.Object) error {
accessor.SetAnnotations(annotations) accessor.SetAnnotations(annotations)
return nil return nil
} }
// MakeRecordMergePatch produces a merge patch for updating the recording annotation.
func (r *ChangeCauseRecorder) MakeRecordMergePatch(obj runtime.Object) ([]byte, error) {
// copy so we don't mess with the original
objCopy := obj.DeepCopyObject()
if err := r.Record(objCopy); err != nil {
return nil, err
}
oldData, err := json.Marshal(obj)
if err != nil {
return nil, err
}
newData, err := json.Marshal(objCopy)
if err != nil {
return nil, err
}
return jsonpatch.CreateMergePatch(oldData, newData)
}