Merge pull request #68069 from apelisse/kubectl-apply-dryrun

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions here: https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md.

Add --server-dry-run flag to `kubectl apply`

- Adds the flag
- changes the helper so that we can pass options for patch,
- Adds a test to make sure it doesn't change the object

**What this PR does / why we need it**:

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #

**Special notes for your reviewer**:

**Release note**:
```release-note
Add new `--server-dry-run` flag to `kubectl apply` so that the request will be sent to the server with the dry-run flag (alpha), which means that changes won't be persisted.
```
This commit is contained in:
Kubernetes Submit Queue 2018-09-02 15:31:05 -07:00 committed by GitHub
commit 058b26f38e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 110 additions and 48 deletions

View File

@ -40,12 +40,16 @@ function run_kube_apiserver() {
# Include RBAC (to exercise bootstrapping), and AlwaysAllow to allow all actions # Include RBAC (to exercise bootstrapping), and AlwaysAllow to allow all actions
AUTHORIZATION_MODE="RBAC,AlwaysAllow" AUTHORIZATION_MODE="RBAC,AlwaysAllow"
# Enable features
ENABLE_FEATURE_GATES="DryRun=true"
"${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \ "${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \
--insecure-bind-address="127.0.0.1" \ --insecure-bind-address="127.0.0.1" \
--bind-address="127.0.0.1" \ --bind-address="127.0.0.1" \
--insecure-port="${API_PORT}" \ --insecure-port="${API_PORT}" \
--authorization-mode="${AUTHORIZATION_MODE}" \ --authorization-mode="${AUTHORIZATION_MODE}" \
--secure-port="${SECURE_API_PORT}" \ --secure-port="${SECURE_API_PORT}" \
--feature-gates="${ENABLE_FEATURE_GATES}" \
--enable-admission-plugins="${ENABLE_ADMISSION_PLUGINS}" \ --enable-admission-plugins="${ENABLE_ADMISSION_PLUGINS}" \
--disable-admission-plugins="${DISABLE_ADMISSION_PLUGINS}" \ --disable-admission-plugins="${DISABLE_ADMISSION_PLUGINS}" \
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \ --etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \

View File

@ -294,7 +294,7 @@ func (o AnnotateOptions) RunAnnotate() error {
helper := resource.NewHelper(client, mapping) helper := resource.NewHelper(client, mapping)
if createdPatch { if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes) outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes, nil)
} else { } else {
outputObj, err = helper.Replace(namespace, name, false, obj) outputObj, err = helper.Replace(namespace, name, false, obj)
} }

View File

@ -66,6 +66,7 @@ type ApplyOptions struct {
Selector string Selector string
DryRun bool DryRun bool
ServerDryRun bool
Prune bool Prune bool
PruneResources []pruneResource PruneResources []pruneResource
cmdBaseName string 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().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 <group/version/kind> for --prune") cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> 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.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.AddDryRunFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(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 { func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.DryRun = cmdutil.GetDryRunFlag(cmd) 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 // allow for a success message operation to be specified at print time
o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
o.PrintFlags.NamePrintFlags.Operation = operation o.PrintFlags.NamePrintFlags.Operation = operation
if o.DryRun { if o.DryRun {
o.PrintFlags.Complete("%s (dry run)") o.PrintFlags.Complete("%s (dry run)")
} }
if o.ServerDryRun {
o.PrintFlags.Complete("%s (server dry run)")
}
return o.PrintFlags.ToPrinter() return o.PrintFlags.ToPrinter()
} }
@ -354,7 +362,11 @@ func (o *ApplyOptions) Run() error {
if !o.DryRun { if !o.DryRun {
// Then create the resource and skip the three-way merge // 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 { if err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err) return cmdutil.AddSourceToErr("creating", info.Source, err)
} }
@ -402,6 +414,7 @@ func (o *ApplyOptions) Run() error {
cascade: o.DeleteOptions.Cascade, cascade: o.DeleteOptions.Cascade,
timeout: o.DeleteOptions.Timeout, timeout: o.DeleteOptions.Timeout,
gracePeriod: o.DeleteOptions.GracePeriod, gracePeriod: o.DeleteOptions.GracePeriod,
serverDryRun: o.ServerDryRun,
openapiSchema: openapiSchema, openapiSchema: openapiSchema,
} }
@ -483,9 +496,10 @@ func (o *ApplyOptions) Run() error {
labelSelector: o.Selector, labelSelector: o.Selector,
visitedUids: visitedUids, visitedUids: visitedUids,
cascade: o.DeleteOptions.Cascade, cascade: o.DeleteOptions.Cascade,
dryRun: o.DryRun, dryRun: o.DryRun,
gracePeriod: o.DeleteOptions.GracePeriod, serverDryRun: o.ServerDryRun,
gracePeriod: o.DeleteOptions.GracePeriod,
toPrinter: o.ToPrinter, toPrinter: o.ToPrinter,
@ -572,9 +586,10 @@ type pruner struct {
labelSelector string labelSelector string
fieldSelector string fieldSelector string
cascade bool cascade bool
dryRun bool serverDryRun bool
gracePeriod int dryRun bool
gracePeriod int
toPrinter func(string) (printers.ResourcePrinter, error) 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 { 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{} options := &metav1.DeleteOptions{}
if gracePeriod >= 0 { if gracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod)) options = metav1.NewDeleteOptions(int64(gracePeriod))
} }
if serverDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
policy := metav1.DeletePropagationForeground policy := metav1.DeletePropagationForeground
if !cascade { if !cascade {
policy = metav1.DeletePropagationOrphan 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 { 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 { type patcher struct {
@ -657,10 +675,11 @@ type patcher struct {
overwrite bool overwrite bool
backOff clockwork.Clock backOff clockwork.Clock
force bool force bool
cascade bool cascade bool
timeout time.Duration timeout time.Duration
gracePeriod int gracePeriod int
serverDryRun bool
openapiSchema openapi.Resources openapiSchema openapi.Resources
} }
@ -736,7 +755,12 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names
return patch, obj, nil 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 return patch, patchedObj, err
} }
@ -776,11 +800,15 @@ func (p *patcher) deleteAndCreate(original runtime.Object, modified []byte, name
if err != nil { if err != nil {
return modified, nil, err 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 { if err != nil {
// restore the original object if we fail to create the new one // restore the original object if we fail to create the new one
// but still propagate and advertise error to user // 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 { 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) 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 { } else {

View File

@ -200,7 +200,7 @@ func (o *SetLastAppliedOptions) RunSetLastApplied() error {
return err return err
} }
helper := resource.NewHelper(client, mapping) 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 { if err != nil {
return err return err
} }

View File

@ -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 // createAndRefresh creates an object from input info and refreshes info with that object
func createAndRefresh(info *resource.Info) error { 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 { if err != nil {
return err return err
} }

View File

@ -760,7 +760,7 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error {
fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err) fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err)
continue 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 { if err != nil {
fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err) fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err)
continue continue

View File

@ -304,7 +304,7 @@ func (o *LabelOptions) RunLabel() error {
helper := resource.NewHelper(client, mapping) helper := resource.NewHelper(client, mapping)
if createdPatch { if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes) outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes, nil)
} else { } else {
outputObj, err = helper.Replace(namespace, name, false, obj) outputObj, err = helper.Replace(namespace, name, false, obj)
} }

View File

@ -220,7 +220,7 @@ func (o *PatchOptions) RunPatch() error {
} }
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, nil)
if err != nil { if err != nil {
return err return err
} }
@ -231,7 +231,7 @@ func (o *PatchOptions) RunPatch() error {
if mergePatch, err := o.Recorder.MakeRecordMergePatch(patchedObj); err != nil { if mergePatch, err := o.Recorder.MakeRecordMergePatch(patchedObj); err != nil {
glog.V(4).Infof("error recording current command: %v", err) glog.V(4).Infof("error recording current command: %v", err)
} else if len(mergePatch) > 0 { } 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) glog.V(4).Infof("error recording reason: %v", err)
} else { } else {
patchedObj = recordedObj patchedObj = recordedObj

View File

@ -317,7 +317,7 @@ func (o *ReplaceOptions) forceReplace() error {
glog.V(4).Infof("error recording current command: %v", err) 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 { if err != nil {
return err return err
} }

View File

@ -174,7 +174,7 @@ func (o PauseConfig) RunPause() error {
continue 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 { if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err)) allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err))
continue continue

View File

@ -178,7 +178,7 @@ func (o ResumeOptions) RunResume() error {
continue 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 { if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err)) allErrs = append(allErrs, fmt.Errorf("failed to patch: %v", err))
continue continue

View File

@ -667,7 +667,7 @@ func (o *RunOptions) createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -244,7 +244,7 @@ func (o *ScaleOptions) RunScale() error {
return err return err
} }
helper := resource.NewHelper(client, mapping) 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) glog.V(4).Infof("error recording reason: %v", err)
} }
} }

View File

@ -487,7 +487,7 @@ func (o *EnvOptions) RunEnv() error {
continue 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 { if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch env update to pod template: %v\n", err)) allErrs = append(allErrs, fmt.Errorf("failed to patch env update to pod template: %v\n", err))
continue continue

View File

@ -282,7 +282,7 @@ func (o *SetImageOptions) Run() error {
} }
// patch the change // 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 { if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch image update to pod template: %v\n", err)) allErrs = append(allErrs, fmt.Errorf("failed to patch image update to pod template: %v\n", err))
continue continue

View File

@ -276,7 +276,7 @@ func (o *SetResourcesOptions) Run() error {
continue 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 { if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch limit update to pod template %v\n", err)) allErrs = append(allErrs, fmt.Errorf("failed to patch limit update to pod template %v\n", err))
continue continue

View File

@ -184,7 +184,7 @@ func (o *SetSelectorOptions) RunSelector() error {
return o.PrintObj(info.Object, o.Out) 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 { if err != nil {
return err return err
} }

View File

@ -44,7 +44,7 @@ var (
serviceaccountLong = templates.LongDesc(i18n.T(` serviceaccountLong = templates.LongDesc(i18n.T(`
Update ServiceAccount of pod template resources. Update ServiceAccount of pod template resources.
Possible resources (case insensitive) can be: Possible resources (case insensitive) can be:
` + serviceaccountResources)) ` + serviceaccountResources))
serviceaccountExample = templates.Examples(i18n.T(` serviceaccountExample = templates.Examples(i18n.T(`
@ -202,7 +202,7 @@ func (o *SetServiceAccountOptions) Run() error {
} }
continue 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 { if err != nil {
patchErrs = append(patchErrs, fmt.Errorf("failed to patch ServiceAccountName %v", err)) patchErrs = append(patchErrs, fmt.Errorf("failed to patch ServiceAccountName %v", err))
continue continue

View File

@ -255,7 +255,7 @@ func (o *SubjectOptions) Run(fn updateSubjects) error {
continue 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 { if err != nil {
allErrs = append(allErrs, fmt.Errorf("failed to patch subjects to rolebinding: %v\n", err)) allErrs = append(allErrs, fmt.Errorf("failed to patch subjects to rolebinding: %v\n", err))
continue continue

View File

@ -274,7 +274,7 @@ func (o TaintOptions) RunTaint() error {
var outputObj runtime.Object var outputObj runtime.Object
if createdPatch { if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes) outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes, nil)
} else { } else {
outputObj, err = helper.Replace(namespace, name, false, obj) outputObj, err = helper.Replace(namespace, name, false, obj)
} }

View File

@ -503,7 +503,7 @@ func (o *EditOptions) annotationPatch(update *resource.Info) error {
return err return err
} }
helper := resource.NewHelper(client, mapping) 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 { if err != nil {
return err return err
} }
@ -632,7 +632,7 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor
fmt.Fprintf(o.Out, "Patch: %s\n", string(patch)) 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 { if err != nil {
fmt.Fprintln(o.ErrOut, results.addError(err, info)) fmt.Fprintln(o.ErrOut, results.addError(err, info))
return nil return nil

View File

@ -108,13 +108,16 @@ func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.Delet
Get() 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 { if modify {
// Attempt to version the object based on client logic. // Attempt to version the object based on client logic.
version, err := metadataAccessor.ResourceVersion(obj) version, err := metadataAccessor.ResourceVersion(obj)
if err != nil { if err != nil {
// We don't know how to clear the version on this object, so send it to the server as is // 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 version != "" {
if err := metadataAccessor.SetResourceVersion(obj, ""); err != nil { 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) { 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).Body(obj).Do().Get() 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). return m.RESTClient.Patch(pt).
NamespaceIfScoped(namespace, m.NamespaceScoped). NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource). Resource(m.Resource).
Name(name). Name(name).
VersionedParams(options, metav1.ParameterCodec).
Body(data). Body(data).
Do(). Do().
Get() Get()

View File

@ -228,7 +228,7 @@ func TestHelperCreate(t *testing.T) {
RESTClient: client, RESTClient: client,
NamespaceScoped: true, NamespaceScoped: true,
} }
_, err := modifier.Create("bar", tt.Modify, tt.Object) _, err := modifier.Create("bar", tt.Modify, tt.Object, nil)
if (err != nil) != tt.Err { if (err != nil) != tt.Err {
t.Errorf("%d: unexpected error: %t %v", i, tt.Err, err) t.Errorf("%d: unexpected error: %t %v", i, tt.Err, err)
} }

View File

@ -656,7 +656,7 @@ func RetrieveLazy(info *Info, err error) error {
// CreateAndRefresh creates an object from input info and refreshes info with that object // CreateAndRefresh creates an object from input info and refreshes info with that object
func CreateAndRefresh(info *Info) error { 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 { if err != nil {
return err return err
} }

View File

@ -75,6 +75,23 @@ run_kubectl_apply_tests() {
# cleanup # cleanup
kubectl delete pods selector-test-pod 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 ## kubectl apply --prune
# Pre-Condition: no POD exists # Pre-Condition: no POD exists