Merge pull request #62858 from deads2k/cli-32-more-record-02

Automatic merge from submit-queue (batch tested with PRs 62642, 62855, 62487, 62858, 62873). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

 final record flag cleanup

This ties off the remainder of the record flag uses.  Trying to merge different types of patches is fraught, so I added a way to get a merge patch (not a strategic patch) back from the annotation update.

@kubernetes/sig-cli-maintainers 
/assign @soltysh 
/assign @juanvallejo 

```release-note
NONE
```
pull/8/head
Kubernetes Submit Queue 2018-04-19 15:54:25 -07:00 committed by GitHub
commit 019c805ff2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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
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
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
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/golang/glog"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"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"
)
@ -72,8 +74,23 @@ var (
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 {
options := &resource.FilenameOptions{}
o := NewExposeServiceOptions()
validArgs := []string{}
resources := regexp.MustCompile(`\s*,`).Split(exposeResources, -1)
@ -88,12 +105,15 @@ func NewCmdExposeService(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: exposeLong,
Example: exposeExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunExpose(f, out, cmd, args, options)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.RunExpose(f, out, cmd, args))
},
ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs),
}
o.RecordFlags.AddFlags(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("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."))
usage := "identifying the resource to expose a service"
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(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()
if err != nil {
return err
@ -130,7 +161,7 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
Internal().
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
FilenameParam(enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(false, args...).
Flatten().
Do()
@ -247,10 +278,8 @@ func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
if err != nil {
return err
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(object, f.Command(cmd, false)); err != nil {
return err
}
if err := o.Recorder.Record(object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
info.Refresh(object, true)
if cmdutil.GetDryRunFlag(cmd) {

View File

@ -37,6 +37,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"
)
@ -45,6 +46,7 @@ import (
type LabelOptions struct {
// Filename options
resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
// Common user flags
overwrite bool
@ -61,6 +63,8 @@ type LabelOptions struct {
newLabels map[string]string
removeLabels []string
Recorder genericclioptions.Recorder
// Common shared fields
out io.Writer
errout io.Writer
@ -96,10 +100,19 @@ var (
kubectl label pods foo bar-`))
)
func NewCmdLabel(f cmdutil.Factory, out, errout io.Writer) *cobra.Command {
options := &LabelOptions{
errout: errout,
func NewLabelOptions(out, errOut io.Writer) *LabelOptions {
return &LabelOptions{
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)
@ -110,36 +123,45 @@ func NewCmdLabel(f cmdutil.Factory, out, errout io.Writer) *cobra.Command {
Long: fmt.Sprintf(labelLong, validation.LabelValueMaxLength),
Example: labelExample,
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()))
}
if err := options.Validate(); err != nil {
if err := o.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error()))
}
cmdutil.CheckErr(options.RunLabel(f, cmd))
cmdutil.CheckErr(o.RunLabel(f, cmd))
},
ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs),
}
o.RecordFlags.AddFlags(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(&options.list, "list", options.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().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().BoolVar(&options.all, "all", options.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().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.")
cmd.Flags().BoolVar(&o.list, "list", o.list, "If true, display the labels for a given resource.")
cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, label will NOT contact api-server but run locally.")
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(&o.all, "all", o.all, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
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"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}
// 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) {
o.out = out
func (o *LabelOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) 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.GetDryRunFlag(cmd)
@ -178,8 +200,6 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
return err
}
changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder().
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 {
return err
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(obj, changeCause); err != nil {
return err
}
if err := o.Recorder.Record(obj); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
newData, err := json.Marshal(obj)
if err != nil {

View File

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

View File

@ -36,6 +36,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/scheme"
"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()
type PatchOptions struct {
resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
Local bool
DryRun bool
Recorder genericclioptions.Recorder
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"}]'`))
)
func NewPatchOptions() *PatchOptions {
return &PatchOptions{
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
}
}
func NewCmdPatch(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &PatchOptions{}
o := NewPatchOptions()
validArgs := cmdutil.ValidArgList(f)
cmd := &cobra.Command{
@ -90,32 +102,47 @@ func NewCmdPatch(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: patchLong,
Example: patchExample,
Run: func(cmd *cobra.Command, args []string) {
options.OutputFormat = cmdutil.GetFlagString(cmd, "output")
options.DryRun = cmdutil.GetFlagBool(cmd, "dry-run")
err := RunPatch(f, out, cmd, args, options)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.RunPatch(f, out, cmd, args))
},
ValidArgs: 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.MarkFlagRequired("patch")
cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddDryRunFlag(cmd)
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
}
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 {
case options.Local && len(args) != 0:
case o.Local && len(args) != 0:
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().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions).
FilenameParam(enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(false, args...).
Flatten().
Do()
@ -169,38 +196,36 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return err
}
if !options.Local && !options.DryRun {
if !o.Local && !o.DryRun {
helper := resource.NewHelper(client, mapping)
patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes)
if err != nil {
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) {
// Copy the resource info and update with the result of applying the user's patch
infoCopy := *info
infoCopy.Object = patchedObj
if patch, patchType, err := cmdutil.ChangeResourcePatch(&infoCopy, f.Command(cmd, true)); err == nil {
if recordedObj, err := helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil {
glog.V(4).Infof("error recording reason: %v", err)
} else {
patchedObj = recordedObj
}
didPatch := !reflect.DeepEqual(info.Object, patchedObj)
// if the recorder makes a change, compute and create another patch
if mergePatch, err := o.Recorder.MakeRecordMergePatch(patchedObj); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
} 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)
} else {
patchedObj = recordedObj
}
}
count++
didPatch := !reflect.DeepEqual(info.Object, patchedObj)
// After computing whether we changed data, refresh the resource info with the resulting object
if err := info.Refresh(patchedObj, true); err != nil {
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)
}
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 !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)
}
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
})
if err != nil {

View File

@ -33,6 +33,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"
"k8s.io/kubernetes/pkg/kubectl/validation"
@ -64,39 +65,43 @@ var (
kubectl replace --force -f ./pod.json`))
)
type ReplaceOpts struct {
type ReplaceOptions struct {
PrintFlags *printers.PrintFlags
DeleteFlags *DeleteFlags
RecordFlags *genericclioptions.RecordFlags
DeleteOptions *DeleteOptions
PrintObj func(obj runtime.Object) error
createAnnotation bool
changeCause string
validate bool
Schema validation.Schema
Builder func() *resource.Builder
BuilderArgs []string
ShouldRecord func(info *resource.Info) bool
Namespace string
EnforceNamespace bool
Recorder genericclioptions.Recorder
Out io.Writer
ErrOut io.Writer
}
func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &ReplaceOpts{
func NewReplaceOptions(out, errOut io.Writer) *ReplaceOptions {
return &ReplaceOptions{
PrintFlags: printers.NewPrintFlags("replaced"),
DeleteFlags: NewDeleteFlags("to use to replace the resource."),
Out: out,
ErrOut: errOut,
}
}
func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
o := NewReplaceOptions(out, errOut)
cmd := &cobra.Command{
Use: "replace -f FILENAME",
@ -106,32 +111,35 @@ func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Example: replaceExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate(cmd))
cmdutil.CheckErr(options.Run())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate(cmd))
cmdutil.CheckErr(o.Run())
},
}
options.PrintFlags.AddFlags(cmd)
options.DeleteFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
o.DeleteFlags.AddFlags(cmd)
o.RecordFlags.AddFlags(cmd)
cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
return cmd
}
func (o *ReplaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.validate = cmdutil.GetFlagBool(cmd, "validate")
o.changeCause = f.Command(cmd, false)
o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.ShouldRecord = func(info *resource.Info) bool {
return cmdutil.ShouldRecord(cmd, info)
o.RecordFlags.Complete(f.Command(cmd, false))
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()
if err != nil {
return err
@ -173,7 +181,7 @@ func (o *ReplaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
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 {
return fmt.Errorf("--grace-period must have --force specified")
}
@ -189,7 +197,7 @@ func (o *ReplaceOpts) Validate(cmd *cobra.Command) error {
return nil
}
func (o *ReplaceOpts) Run() error {
func (o *ReplaceOptions) Run() error {
if o.DeleteOptions.ForceDeletion {
return o.forceReplace()
}
@ -215,10 +223,8 @@ func (o *ReplaceOpts) Run() error {
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
if o.ShouldRecord(info) {
if err := cmdutil.RecordChangeCause(info.Object, o.changeCause); err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
if err := o.Recorder.Record(info.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
// 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 {
if filename == "-" {
tempDir, err := ioutil.TempDir("", "kubectl_replace_")
@ -313,10 +319,8 @@ func (o *ReplaceOpts) forceReplace() error {
return err
}
if o.ShouldRecord(info) {
if err := cmdutil.RecordChangeCause(info.Object, o.changeCause); err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
if err := o.Recorder.Record(info.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
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/golang/glog"
"k8s.io/apimachinery/pkg/types"
batchclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/scalejob"
"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"
)
@ -58,9 +61,24 @@ var (
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
func NewCmdScale(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
o := NewScaleOptions()
validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
argAliases := kubectl.ResourceAliases(validArgs)
@ -72,14 +90,17 @@ func NewCmdScale(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Long: scaleLong,
Example: scaleExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
err := RunScale(f, out, errOut, cmd, args, shortOutput, options)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.RunScale(f, out, errOut, cmd, args, shortOutput))
},
ValidArgs: validArgs,
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().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."))
@ -88,15 +109,26 @@ func NewCmdScale(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
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).")
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddRecordFlag(cmd)
usage := "identifying the resource to set a new size"
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
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
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()
if err != nil {
return err
@ -114,7 +146,7 @@ func RunScale(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
Unstructured().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
FilenameParam(enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(all, args...).
Flatten().
LabelSelectorParam(selector).
@ -180,22 +212,20 @@ func RunScale(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
}
}
if cmdutil.ShouldRecord(cmd, info) {
patchBytes, patchType, err := cmdutil.ChangeResourcePatch(info, f.Command(cmd, true))
if err != nil {
return err
}
mapping := info.ResourceMapping()
// if the recorder makes a change, compute and create another patch
if mergePatch, err := o.Recorder.MakeRecordMergePatch(info.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
} else if len(mergePatch) > 0 {
client, err := f.UnstructuredClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
_, err = helper.Patch(info.Namespace, info.Name, patchType, patchBytes)
if err != nil {
return err
if _, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch); err != nil {
glog.V(4).Infof("error recording reason: %v", err)
}
}
counter++
cmdutil.PrintSuccess(shortOutput, out, info.Object, false, "scaled")
return nil

View File

@ -122,8 +122,6 @@ type EnvOptions struct {
Builder *resource.Builder
Infos []*resource.Info
Cmd *cobra.Command
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)
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 {
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.Resources = resources
o.Cmd = cmd
if o.DryRun {
// 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
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

View File

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

View File

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

View File

@ -45,12 +45,10 @@ type SetSelectorOptions struct {
PrintFlags *printers.PrintFlags
RecordFlags *genericclioptions.RecordFlags
local bool
dryrun bool
all bool
record bool
changeCause string
output string
local bool
dryrun bool
all bool
output string
resources []string
selector *metav1.LabelSelector
@ -141,7 +139,6 @@ func (o *SetSelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
return err
}
o.changeCause = f.Command(cmd, false)
mapper, _ := f.Object()
o.mapper = mapper

View File

@ -65,12 +65,9 @@ type SetServiceAccountOptions struct {
out io.Writer
err io.Writer
dryRun bool
cmd *cobra.Command
shortOutput bool
all bool
record bool
output string
changeCause string
local bool
updatePodSpecForObject func(runtime.Object, func(*v1.PodSpec) error) (bool, error)
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.record = cmdutil.GetRecordFlag(cmd)
o.changeCause = f.Command(cmd, false)
o.dryRun = cmdutil.GetDryRunFlag(cmd)
o.output = cmdutil.GetFlagString(cmd, "output")
o.updatePodSpecForObject = f.UpdatePodSpecForObject
o.cmd = cmd
if o.dryRun {
o.PrintFlags.Complete("%s (dry run)")

View File

@ -57,11 +57,8 @@ go_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/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/json: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/version: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"
"k8s.io/apimachinery/pkg/api/meta"
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/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl"
@ -517,64 +513,10 @@ func UpdateObject(info *resource.Info, codec runtime.Codec, updateFn func(runtim
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 {
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.
func ContainsChangeCause(info *resource.Info) bool {
annotations, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
@ -584,11 +526,6 @@ func ContainsChangeCause(info *resource.Info) bool {
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
func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) {
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
// 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.

View File

@ -19,7 +19,6 @@ package util
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"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 {
err error
expectedErr string

View File

@ -9,9 +9,11 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/kubectl/genericclioptions",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/evanphx/json-patch: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/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
import (
"github.com/evanphx/json-patch"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
)
// ChangeCauseAnnotation is the annotation indicating a guess at "why" something was changed
@ -92,6 +94,7 @@ func NewRecordFlags() *RecordFlags {
type Recorder interface {
// Record records why a runtime.Object was changed in an annotation.
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.
@ -102,6 +105,11 @@ func (r NoopRecorder) Record(obj runtime.Object) error {
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
type ChangeCauseRecorder struct {
changeCause string
@ -122,3 +130,23 @@ func (r *ChangeCauseRecorder) Record(obj runtime.Object) error {
accessor.SetAnnotations(annotations)
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)
}