From 967280b58e7637eb330558bef69bffa160853043 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Thu, 30 Aug 2018 06:33:34 -0700 Subject: [PATCH] Add --server-dry-run flag to `kubectl apply` --- hack/make-rules/test-cmd.sh | 4 ++ pkg/kubectl/cmd/annotate.go | 2 +- pkg/kubectl/cmd/apply.go | 64 +++++++++++++------ pkg/kubectl/cmd/apply_set_last_applied.go | 2 +- pkg/kubectl/cmd/create/create.go | 2 +- pkg/kubectl/cmd/drain.go | 2 +- pkg/kubectl/cmd/label.go | 2 +- pkg/kubectl/cmd/patch.go | 4 +- pkg/kubectl/cmd/replace.go | 2 +- pkg/kubectl/cmd/rollout/rollout_pause.go | 2 +- pkg/kubectl/cmd/rollout/rollout_resume.go | 2 +- pkg/kubectl/cmd/run.go | 2 +- pkg/kubectl/cmd/scale.go | 2 +- pkg/kubectl/cmd/set/set_env.go | 2 +- pkg/kubectl/cmd/set/set_image.go | 2 +- pkg/kubectl/cmd/set/set_resources.go | 2 +- pkg/kubectl/cmd/set/set_selector.go | 2 +- pkg/kubectl/cmd/set/set_serviceaccount.go | 4 +- pkg/kubectl/cmd/set/set_subject.go | 2 +- pkg/kubectl/cmd/taint.go | 2 +- pkg/kubectl/cmd/util/editor/editoptions.go | 4 +- .../pkg/genericclioptions/resource/helper.go | 25 ++++++-- .../genericclioptions/resource/helper_test.go | 2 +- .../pkg/genericclioptions/resource/visitor.go | 2 +- test/cmd/apply.sh | 17 +++++ 25 files changed, 110 insertions(+), 48 deletions(-) diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index 4e92fdc61b..40133432d5 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -40,12 +40,16 @@ function run_kube_apiserver() { # Include RBAC (to exercise bootstrapping), and AlwaysAllow to allow all actions AUTHORIZATION_MODE="RBAC,AlwaysAllow" + # Enable features + ENABLE_FEATURE_GATES="DryRun=true" + "${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \ --insecure-bind-address="127.0.0.1" \ --bind-address="127.0.0.1" \ --insecure-port="${API_PORT}" \ --authorization-mode="${AUTHORIZATION_MODE}" \ --secure-port="${SECURE_API_PORT}" \ + --feature-gates="${ENABLE_FEATURE_GATES}" \ --enable-admission-plugins="${ENABLE_ADMISSION_PLUGINS}" \ --disable-admission-plugins="${DISABLE_ADMISSION_PLUGINS}" \ --etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \ diff --git a/pkg/kubectl/cmd/annotate.go b/pkg/kubectl/cmd/annotate.go index f3c3bcae82..9c6d96a393 100644 --- a/pkg/kubectl/cmd/annotate.go +++ b/pkg/kubectl/cmd/annotate.go @@ -294,7 +294,7 @@ func (o AnnotateOptions) RunAnnotate() error { helper := resource.NewHelper(client, mapping) if createdPatch { - outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes) + outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes, nil) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index ecc535736d..b4d588c089 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -66,6 +66,7 @@ type ApplyOptions struct { Selector string DryRun bool + ServerDryRun bool Prune bool PruneResources []pruneResource cmdBaseName string @@ -172,6 +173,7 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.") cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with for --prune") cmd.Flags().BoolVar(&o.OpenApiPatch, "openapi-patch", o.OpenApiPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.") + cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.") cmdutil.AddDryRunFlag(cmd) cmdutil.AddIncludeUninitializedFlag(cmd) @@ -186,13 +188,19 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { o.DryRun = cmdutil.GetDryRunFlag(cmd) + if o.DryRun && o.ServerDryRun { + return fmt.Errorf("--dry-run and --server-dry-run can't be used together") + } + // allow for a success message operation to be specified at print time o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { o.PrintFlags.NamePrintFlags.Operation = operation if o.DryRun { o.PrintFlags.Complete("%s (dry run)") } - + if o.ServerDryRun { + o.PrintFlags.Complete("%s (server dry run)") + } return o.PrintFlags.ToPrinter() } @@ -354,7 +362,11 @@ func (o *ApplyOptions) Run() error { if !o.DryRun { // Then create the resource and skip the three-way merge - obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) + options := metav1.CreateOptions{} + if o.ServerDryRun { + options.DryRun = []string{metav1.DryRunAll} + } + obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, &options) if err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } @@ -402,6 +414,7 @@ func (o *ApplyOptions) Run() error { cascade: o.DeleteOptions.Cascade, timeout: o.DeleteOptions.Timeout, gracePeriod: o.DeleteOptions.GracePeriod, + serverDryRun: o.ServerDryRun, openapiSchema: openapiSchema, } @@ -483,9 +496,10 @@ func (o *ApplyOptions) Run() error { labelSelector: o.Selector, visitedUids: visitedUids, - cascade: o.DeleteOptions.Cascade, - dryRun: o.DryRun, - gracePeriod: o.DeleteOptions.GracePeriod, + cascade: o.DeleteOptions.Cascade, + dryRun: o.DryRun, + serverDryRun: o.ServerDryRun, + gracePeriod: o.DeleteOptions.GracePeriod, toPrinter: o.ToPrinter, @@ -572,9 +586,10 @@ type pruner struct { labelSelector string fieldSelector string - cascade bool - dryRun bool - gracePeriod int + cascade bool + serverDryRun bool + dryRun bool + gracePeriod int toPrinter func(string) (printers.ResourcePrinter, error) @@ -629,14 +644,17 @@ func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, includeUnini } func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping) error { - return runDelete(namespace, name, mapping, p.dynamicClient, p.cascade, p.gracePeriod) + return runDelete(namespace, name, mapping, p.dynamicClient, p.cascade, p.gracePeriod, p.serverDryRun) } -func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Interface, cascade bool, gracePeriod int) error { +func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Interface, cascade bool, gracePeriod int, serverDryRun bool) error { options := &metav1.DeleteOptions{} if gracePeriod >= 0 { options = metav1.NewDeleteOptions(int64(gracePeriod)) } + if serverDryRun { + options.DryRun = []string{metav1.DryRunAll} + } policy := metav1.DeletePropagationForeground if !cascade { policy = metav1.DeletePropagationOrphan @@ -646,7 +664,7 @@ func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Inte } func (p *patcher) delete(namespace, name string) error { - return runDelete(namespace, name, p.mapping, p.dynamicClient, p.cascade, p.gracePeriod) + return runDelete(namespace, name, p.mapping, p.dynamicClient, p.cascade, p.gracePeriod, p.serverDryRun) } type patcher struct { @@ -657,10 +675,11 @@ type patcher struct { overwrite bool backOff clockwork.Clock - force bool - cascade bool - timeout time.Duration - gracePeriod int + force bool + cascade bool + timeout time.Duration + gracePeriod int + serverDryRun bool openapiSchema openapi.Resources } @@ -736,7 +755,12 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names return patch, obj, nil } - patchedObj, err := p.helper.Patch(namespace, name, patchType, patch) + options := metav1.UpdateOptions{} + if p.serverDryRun { + options.DryRun = []string{metav1.DryRunAll} + } + + patchedObj, err := p.helper.Patch(namespace, name, patchType, patch, &options) return patch, patchedObj, err } @@ -776,11 +800,15 @@ func (p *patcher) deleteAndCreate(original runtime.Object, modified []byte, name if err != nil { return modified, nil, err } - createdObject, err := p.helper.Create(namespace, true, versionedObject) + options := metav1.CreateOptions{} + if p.serverDryRun { + options.DryRun = []string{metav1.DryRunAll} + } + createdObject, err := p.helper.Create(namespace, true, versionedObject, &options) if err != nil { // restore the original object if we fail to create the new one // but still propagate and advertise error to user - recreated, recreateErr := p.helper.Create(namespace, true, original) + recreated, recreateErr := p.helper.Create(namespace, true, original, &options) if recreateErr != nil { err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v\n", err, recreateErr) } else { diff --git a/pkg/kubectl/cmd/apply_set_last_applied.go b/pkg/kubectl/cmd/apply_set_last_applied.go index efcd8e1cc8..899e7311fb 100644 --- a/pkg/kubectl/cmd/apply_set_last_applied.go +++ b/pkg/kubectl/cmd/apply_set_last_applied.go @@ -200,7 +200,7 @@ func (o *SetLastAppliedOptions) RunSetLastApplied() error { return err } helper := resource.NewHelper(client, mapping) - finalObj, err = helper.Patch(o.namespace, info.Name, patch.PatchType, patch.Patch) + finalObj, err = helper.Patch(o.namespace, info.Name, patch.PatchType, patch.Patch, nil) if err != nil { return err } diff --git a/pkg/kubectl/cmd/create/create.go b/pkg/kubectl/cmd/create/create.go index e69fe6493b..6e5fe28e1f 100644 --- a/pkg/kubectl/cmd/create/create.go +++ b/pkg/kubectl/cmd/create/create.go @@ -318,7 +318,7 @@ func RunEditOnCreate(f cmdutil.Factory, printFlags *genericclioptions.PrintFlags // createAndRefresh creates an object from input info and refreshes info with that object func createAndRefresh(info *resource.Info) error { - 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, nil) if err != nil { return err } diff --git a/pkg/kubectl/cmd/drain.go b/pkg/kubectl/cmd/drain.go index e62947c645..1c0ddc1a6c 100644 --- a/pkg/kubectl/cmd/drain.go +++ b/pkg/kubectl/cmd/drain.go @@ -760,7 +760,7 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error { fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err) continue } - _, err = helper.Patch(o.Namespace, nodeInfo.Name, types.StrategicMergePatchType, patchBytes) + _, err = helper.Patch(o.Namespace, nodeInfo.Name, types.StrategicMergePatchType, patchBytes, nil) if err != nil { fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err) continue diff --git a/pkg/kubectl/cmd/label.go b/pkg/kubectl/cmd/label.go index 57c46b6f26..4aedac04ef 100644 --- a/pkg/kubectl/cmd/label.go +++ b/pkg/kubectl/cmd/label.go @@ -304,7 +304,7 @@ func (o *LabelOptions) RunLabel() error { helper := resource.NewHelper(client, mapping) if createdPatch { - outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes) + outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes, nil) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index c5a25e399e..e5eb13b38b 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -220,7 +220,7 @@ func (o *PatchOptions) RunPatch() error { } helper := resource.NewHelper(client, mapping) - patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes) + patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes, nil) if err != nil { return err } @@ -231,7 +231,7 @@ func (o *PatchOptions) RunPatch() error { 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 { + if recordedObj, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch, nil); err != nil { glog.V(4).Infof("error recording reason: %v", err) } else { patchedObj = recordedObj diff --git a/pkg/kubectl/cmd/replace.go b/pkg/kubectl/cmd/replace.go index 6bd3f8aee1..d5ed3379f6 100644 --- a/pkg/kubectl/cmd/replace.go +++ b/pkg/kubectl/cmd/replace.go @@ -317,7 +317,7 @@ func (o *ReplaceOptions) forceReplace() error { glog.V(4).Infof("error recording current command: %v", 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, nil) if err != nil { return err } diff --git a/pkg/kubectl/cmd/rollout/rollout_pause.go b/pkg/kubectl/cmd/rollout/rollout_pause.go index d2c4104160..e9ba5a86ae 100644 --- a/pkg/kubectl/cmd/rollout/rollout_pause.go +++ b/pkg/kubectl/cmd/rollout/rollout_pause.go @@ -174,7 +174,7 @@ func (o PauseConfig) RunPause() error { continue } - obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) + obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) if err != nil { allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err)) continue diff --git a/pkg/kubectl/cmd/rollout/rollout_resume.go b/pkg/kubectl/cmd/rollout/rollout_resume.go index 53e4993c7d..2544335551 100644 --- a/pkg/kubectl/cmd/rollout/rollout_resume.go +++ b/pkg/kubectl/cmd/rollout/rollout_resume.go @@ -178,7 +178,7 @@ func (o ResumeOptions) RunResume() error { continue } - obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) + obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) if err != nil { allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err)) continue diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index effaacc4dd..6d241809ed 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -667,7 +667,7 @@ func (o *RunOptions) createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command if err != nil { return nil, err } - actualObj, err = resource.NewHelper(client, mapping).Create(namespace, false, obj) + actualObj, err = resource.NewHelper(client, mapping).Create(namespace, false, obj, nil) if err != nil { return nil, err } diff --git a/pkg/kubectl/cmd/scale.go b/pkg/kubectl/cmd/scale.go index 698d59ba6a..932ae8702b 100644 --- a/pkg/kubectl/cmd/scale.go +++ b/pkg/kubectl/cmd/scale.go @@ -244,7 +244,7 @@ func (o *ScaleOptions) RunScale() error { return err } helper := resource.NewHelper(client, mapping) - if _, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch); err != nil { + if _, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch, nil); err != nil { glog.V(4).Infof("error recording reason: %v", err) } } diff --git a/pkg/kubectl/cmd/set/set_env.go b/pkg/kubectl/cmd/set/set_env.go index 575cc480a3..0a31560708 100644 --- a/pkg/kubectl/cmd/set/set_env.go +++ b/pkg/kubectl/cmd/set/set_env.go @@ -487,7 +487,7 @@ func (o *EnvOptions) RunEnv() error { continue } - actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) + actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) if err != nil { allErrs = append(allErrs, fmt.Errorf("failed to patch env update to pod template: %v\n", err)) continue diff --git a/pkg/kubectl/cmd/set/set_image.go b/pkg/kubectl/cmd/set/set_image.go index 59d7024dae..c97102e31c 100644 --- a/pkg/kubectl/cmd/set/set_image.go +++ b/pkg/kubectl/cmd/set/set_image.go @@ -282,7 +282,7 @@ func (o *SetImageOptions) Run() error { } // patch the change - actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) + actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) if err != nil { allErrs = append(allErrs, fmt.Errorf("failed to patch image update to pod template: %v\n", err)) continue diff --git a/pkg/kubectl/cmd/set/set_resources.go b/pkg/kubectl/cmd/set/set_resources.go index 826c84bc72..d2010a79dd 100644 --- a/pkg/kubectl/cmd/set/set_resources.go +++ b/pkg/kubectl/cmd/set/set_resources.go @@ -276,7 +276,7 @@ func (o *SetResourcesOptions) Run() error { continue } - actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) + actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) if err != nil { allErrs = append(allErrs, fmt.Errorf("failed to patch limit update to pod template %v\n", err)) continue diff --git a/pkg/kubectl/cmd/set/set_selector.go b/pkg/kubectl/cmd/set/set_selector.go index 2d1b1a53f4..8593f92fbd 100644 --- a/pkg/kubectl/cmd/set/set_selector.go +++ b/pkg/kubectl/cmd/set/set_selector.go @@ -184,7 +184,7 @@ func (o *SetSelectorOptions) RunSelector() error { return o.PrintObj(info.Object, o.Out) } - actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) + actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) if err != nil { return err } diff --git a/pkg/kubectl/cmd/set/set_serviceaccount.go b/pkg/kubectl/cmd/set/set_serviceaccount.go index 4b55d85c4b..2a41b12780 100644 --- a/pkg/kubectl/cmd/set/set_serviceaccount.go +++ b/pkg/kubectl/cmd/set/set_serviceaccount.go @@ -44,7 +44,7 @@ var ( serviceaccountLong = templates.LongDesc(i18n.T(` Update ServiceAccount of pod template resources. - Possible resources (case insensitive) can be: + Possible resources (case insensitive) can be: ` + serviceaccountResources)) serviceaccountExample = templates.Examples(i18n.T(` @@ -202,7 +202,7 @@ func (o *SetServiceAccountOptions) Run() error { } continue } - actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) + actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) if err != nil { patchErrs = append(patchErrs, fmt.Errorf("failed to patch ServiceAccountName %v", err)) continue diff --git a/pkg/kubectl/cmd/set/set_subject.go b/pkg/kubectl/cmd/set/set_subject.go index ce81f47675..7e0e896be2 100644 --- a/pkg/kubectl/cmd/set/set_subject.go +++ b/pkg/kubectl/cmd/set/set_subject.go @@ -255,7 +255,7 @@ func (o *SubjectOptions) Run(fn updateSubjects) error { continue } - actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) + actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil) if err != nil { allErrs = append(allErrs, fmt.Errorf("failed to patch subjects to rolebinding: %v\n", err)) continue diff --git a/pkg/kubectl/cmd/taint.go b/pkg/kubectl/cmd/taint.go index 617bc452b0..cb43d03567 100644 --- a/pkg/kubectl/cmd/taint.go +++ b/pkg/kubectl/cmd/taint.go @@ -274,7 +274,7 @@ func (o TaintOptions) RunTaint() error { var outputObj runtime.Object if createdPatch { - outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes) + outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes, nil) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } diff --git a/pkg/kubectl/cmd/util/editor/editoptions.go b/pkg/kubectl/cmd/util/editor/editoptions.go index ade6969c4a..f94f3cd2c5 100644 --- a/pkg/kubectl/cmd/util/editor/editoptions.go +++ b/pkg/kubectl/cmd/util/editor/editoptions.go @@ -503,7 +503,7 @@ func (o *EditOptions) annotationPatch(update *resource.Info) error { return err } helper := resource.NewHelper(client, mapping) - _, err = helper.Patch(o.CmdNamespace, update.Name, patchType, patch) + _, err = helper.Patch(o.CmdNamespace, update.Name, patchType, patch, nil) if err != nil { return err } @@ -632,7 +632,7 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor fmt.Fprintf(o.Out, "Patch: %s\n", string(patch)) } - patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, patchType, patch) + patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, patchType, patch, nil) if err != nil { fmt.Fprintln(o.ErrOut, results.addError(err, info)) return nil diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper.go index 52a4057e08..059d518af2 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper.go @@ -108,13 +108,16 @@ func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.Delet Get() } -func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runtime.Object, error) { +func (m *Helper) Create(namespace string, modify bool, obj runtime.Object, options *metav1.CreateOptions) (runtime.Object, error) { + if options == nil { + options = &metav1.CreateOptions{} + } if modify { // Attempt to version the object based on client logic. version, err := metadataAccessor.ResourceVersion(obj) if err != nil { // We don't know how to clear the version on this object, so send it to the server as is - return m.createResource(m.RESTClient, m.Resource, namespace, obj) + return m.createResource(m.RESTClient, m.Resource, namespace, obj, options) } if version != "" { if err := metadataAccessor.SetResourceVersion(obj, ""); err != nil { @@ -123,17 +126,27 @@ func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runt } } - return m.createResource(m.RESTClient, m.Resource, namespace, obj) + return m.createResource(m.RESTClient, m.Resource, namespace, obj, options) } -func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object) (runtime.Object, error) { - return c.Post().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Body(obj).Do().Get() +func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object, options *metav1.CreateOptions) (runtime.Object, error) { + return c.Post(). + NamespaceIfScoped(namespace, m.NamespaceScoped). + Resource(resource). + VersionedParams(options, metav1.ParameterCodec). + Body(obj). + Do(). + Get() } -func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte) (runtime.Object, error) { +func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte, options *metav1.UpdateOptions) (runtime.Object, error) { + if options == nil { + options = &metav1.UpdateOptions{} + } return m.RESTClient.Patch(pt). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(m.Resource). Name(name). + VersionedParams(options, metav1.ParameterCodec). Body(data). Do(). Get() diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper_test.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper_test.go index eb9b868b9e..d3b419028d 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper_test.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/helper_test.go @@ -228,7 +228,7 @@ func TestHelperCreate(t *testing.T) { RESTClient: client, NamespaceScoped: true, } - _, err := modifier.Create("bar", tt.Modify, tt.Object) + _, err := modifier.Create("bar", tt.Modify, tt.Object, nil) if (err != nil) != tt.Err { t.Errorf("%d: unexpected error: %t %v", i, tt.Err, err) } diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go index 237ba73c50..32c1a691a5 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go @@ -656,7 +656,7 @@ func RetrieveLazy(info *Info, err error) error { // CreateAndRefresh creates an object from input info and refreshes info with that object func CreateAndRefresh(info *Info) error { - obj, err := NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) + obj, err := NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil) if err != nil { return err } diff --git a/test/cmd/apply.sh b/test/cmd/apply.sh index 1e90f85300..58c90902c6 100755 --- a/test/cmd/apply.sh +++ b/test/cmd/apply.sh @@ -75,6 +75,23 @@ run_kubectl_apply_tests() { # cleanup kubectl delete pods selector-test-pod + ## kubectl apply --server-dry-run + # Pre-Condition: no POD exists + kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" '' + + # apply dry-run + kubectl apply --server-dry-run -f hack/testdata/pod.yaml "${kube_flags[@]}" + # No pod exists + kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" '' + # apply non dry-run creates the pod + kubectl apply -f hack/testdata/pod.yaml "${kube_flags[@]}" + # apply changes + kubectl apply --server-dry-run -f hack/testdata/pod-apply.yaml "${kube_flags[@]}" + # Post-Condition: label still has initial value + kube::test::get_object_assert 'pods test-pod' "{{${labels_field}.name}}" 'test-pod-label' + + # clean-up + kubectl delete -f hack/testdata/pod.yaml "${kube_flags[@]}" ## kubectl apply --prune # Pre-Condition: no POD exists