diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index d85a64feb8..f31c2a479c 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -40,15 +40,18 @@ import ( "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/strategicpatch" + "k8s.io/kubernetes/pkg/util/wait" ) type ApplyOptions struct { FilenameOptions resource.FilenameOptions Selector string + Force bool Prune bool Cascade bool GracePeriod int PruneResources []pruneResource + Timeout time.Duration } const ( @@ -101,6 +104,8 @@ func NewCmdApply(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().BoolVar(&options.Prune, "prune", false, "Automatically delete resource objects that do not appear in the configs") cmd.Flags().BoolVar(&options.Cascade, "cascade", true, "Only relevant during a prune. If true, cascade the deletion of the resources managed by pruned resources (e.g. Pods created by a ReplicationController).") cmd.Flags().IntVar(&options.GracePeriod, "grace-period", -1, "Period of time in seconds given to pruned resources to terminate gracefully. Ignored if negative.") + cmd.Flags().BoolVar(&options.Force, "force", false, "Delete and re-create the specified resource") + cmd.Flags().DurationVar(&options.Timeout, "timeout", 0, "Only relevant during a force apply. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).") cmdutil.AddValidateFlags(cmd) cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on") cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.") @@ -246,7 +251,19 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App if !dryRun { overwrite := cmdutil.GetFlagBool(cmd, "overwrite") helper := resource.NewHelper(info.Client, info.Mapping) - patcher := NewPatcher(encoder, decoder, info.Mapping, helper, overwrite) + patcher := &patcher{ + encoder: encoder, + decoder: decoder, + mapping: info.Mapping, + helper: helper, + clientsetFunc: f.ClientSet, + overwrite: overwrite, + backOff: clockwork.NewRealClock(), + force: options.Force, + cascade: options.Cascade, + timeout: options.Timeout, + gracePeriod: options.GracePeriod, + } patchBytes, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name) if err != nil { @@ -438,10 +455,17 @@ func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, shortOutput } func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient) error { - if !p.cascade { - return resource.NewHelper(c, mapping).Delete(namespace, name) + return runDelete(namespace, name, mapping, c, nil, p.cascade, p.gracePeriod, p.clientsetFunc) +} + +func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient, helper *resource.Helper, cascade bool, gracePeriod int, clientsetFunc func() (*internalclientset.Clientset, error)) error { + if !cascade { + if helper == nil { + helper = resource.NewHelper(c, mapping) + } + return helper.Delete(namespace, name) } - cs, err := p.clientsetFunc() + cs, err := clientsetFunc() if err != nil { return err } @@ -452,32 +476,35 @@ func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping, c res } return resource.NewHelper(c, mapping).Delete(namespace, name) } - if err := r.Stop(namespace, name, 2*time.Minute, api.NewDeleteOptions(int64(p.gracePeriod))); err != nil { + var options *api.DeleteOptions + if gracePeriod >= 0 { + options = api.NewDeleteOptions(int64(gracePeriod)) + } + if err := r.Stop(namespace, name, 2*time.Minute, options); err != nil { return err } return nil } +func (p *patcher) delete(namespace, name string) error { + return runDelete(namespace, name, p.mapping, nil, p.helper, p.cascade, p.gracePeriod, p.clientsetFunc) +} + type patcher struct { encoder runtime.Encoder decoder runtime.Decoder - mapping *meta.RESTMapping - helper *resource.Helper + mapping *meta.RESTMapping + helper *resource.Helper + clientsetFunc func() (*internalclientset.Clientset, error) overwrite bool backOff clockwork.Clock -} -func NewPatcher(encoder runtime.Encoder, decoder runtime.Decoder, mapping *meta.RESTMapping, helper *resource.Helper, overwrite bool) *patcher { - return &patcher{ - encoder: encoder, - decoder: decoder, - mapping: mapping, - helper: helper, - overwrite: overwrite, - backOff: clockwork.NewRealClock(), - } + force bool + cascade bool + timeout time.Duration + gracePeriod int } func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string) ([]byte, error) { @@ -527,6 +554,30 @@ func (p *patcher) patch(current runtime.Object, modified []byte, source, namespa } patchBytes, err = p.patchSimple(current, modified, source, namespace, name) } - + if err != nil && p.force { + patchBytes, err = p.deleteAndCreate(modified, namespace, name) + } return patchBytes, err } + +func (p *patcher) deleteAndCreate(modified []byte, namespace, name string) ([]byte, error) { + err := p.delete(namespace, name) + if err != nil { + return modified, err + } + err = wait.PollImmediate(kubectl.Interval, p.timeout, func() (bool, error) { + if _, err := p.helper.Get(namespace, name, false); !errors.IsNotFound(err) { + return false, err + } + return true, nil + }) + if err != nil { + return modified, err + } + versionedObject, _, err := p.decoder.Decode(modified, nil, nil) + if err != nil { + return modified, err + } + _, err = p.helper.Create(namespace, true, versionedObject) + return modified, err +}