Add --force to upgrade and rollback

pull/2280/head
peay 8 years ago
parent 7a49e5c3e1
commit 0f26cc5522

@ -91,6 +91,7 @@ message UpgradeReleaseRequest{
int64 Timeout = 3; int64 Timeout = 3;
bool Wait = 4; bool Wait = 4;
bool Recreate = 5; bool Recreate = 5;
bool Force = 6;
} }
message UpgradeReleaseResponse{ message UpgradeReleaseResponse{
hapi.release.Release release = 1; hapi.release.Release release = 1;
@ -103,6 +104,7 @@ message RollbackReleaseRequest{
int64 Timeout = 3; int64 Timeout = 3;
bool Wait = 4; bool Wait = 4;
bool Recreate = 5; bool Recreate = 5;
bool Force = 6;
} }
message RollbackReleaseResponse{ message RollbackReleaseResponse{
hapi.release.Release release = 1; hapi.release.Release release = 1;

@ -207,6 +207,8 @@ message UpdateReleaseRequest {
// ReuseValues will cause Tiller to reuse the values from the last release. // ReuseValues will cause Tiller to reuse the values from the last release.
// This is ignored if reset_values is set. // This is ignored if reset_values is set.
bool reuse_values = 10; bool reuse_values = 10;
// Force resource update through delete/recreate if needed.
bool force = 11;
} }
// UpdateReleaseResponse is the response to an update request. // UpdateReleaseResponse is the response to an update request.
@ -230,6 +232,8 @@ message RollbackReleaseRequest {
// wait, if true, will wait until all Pods, PVCs, and Services are in a ready state // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state
// before marking the release as successful. It will wait for as long as timeout // before marking the release as successful. It will wait for as long as timeout
bool wait = 7; bool wait = 7;
// Force resource update through delete/recreate if needed.
bool force = 8;
} }
// RollbackReleaseResponse is the response to an update request. // RollbackReleaseResponse is the response to an update request.

@ -39,6 +39,7 @@ type rollbackCmd struct {
revision int32 revision int32
dryRun bool dryRun bool
recreate bool recreate bool
force bool
disableHooks bool disableHooks bool
out io.Writer out io.Writer
client helm.Interface client helm.Interface
@ -78,6 +79,7 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate a rollback") f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&rollback.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&rollback.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&rollback.force, "force", false, "force resource update through delete/recreate if needed")
f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback")
f.Int64Var(&rollback.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") f.Int64Var(&rollback.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)")
f.BoolVar(&rollback.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&rollback.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
@ -90,6 +92,7 @@ func (r *rollbackCmd) run() error {
r.name, r.name,
helm.RollbackDryRun(r.dryRun), helm.RollbackDryRun(r.dryRun),
helm.RollbackRecreate(r.recreate), helm.RollbackRecreate(r.recreate),
helm.RollbackForce(r.force),
helm.RollbackDisableHooks(r.disableHooks), helm.RollbackDisableHooks(r.disableHooks),
helm.RollbackVersion(r.revision), helm.RollbackVersion(r.revision),
helm.RollbackTimeout(r.timeout), helm.RollbackTimeout(r.timeout),

@ -62,6 +62,7 @@ type upgradeCmd struct {
client helm.Interface client helm.Interface
dryRun bool dryRun bool
recreate bool recreate bool
force bool
disableHooks bool disableHooks bool
valueFiles valueFiles valueFiles valueFiles
values []string values []string
@ -116,6 +117,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade")
f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed")
f.StringArrayVar(&upgrade.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&upgrade.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks") f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks")
f.BoolVar(&upgrade.disableHooks, "no-hooks", false, "disable pre/post upgrade hooks") f.BoolVar(&upgrade.disableHooks, "no-hooks", false, "disable pre/post upgrade hooks")
@ -198,6 +200,7 @@ func (u *upgradeCmd) run() error {
helm.UpdateValueOverrides(rawVals), helm.UpdateValueOverrides(rawVals),
helm.UpgradeDryRun(u.dryRun), helm.UpgradeDryRun(u.dryRun),
helm.UpgradeRecreate(u.recreate), helm.UpgradeRecreate(u.recreate),
helm.UpgradeForce(u.force),
helm.UpgradeDisableHooks(u.disableHooks), helm.UpgradeDisableHooks(u.disableHooks),
helm.UpgradeTimeout(u.timeout), helm.UpgradeTimeout(u.timeout),
helm.ResetValues(u.resetValues), helm.ResetValues(u.resetValues),

@ -112,7 +112,7 @@ func (r *ReleaseModuleServiceServer) RollbackRelease(ctx context.Context, in *ru
grpclog.Print("rollback") grpclog.Print("rollback")
c := bytes.NewBufferString(in.Current.Manifest) c := bytes.NewBufferString(in.Current.Manifest)
t := bytes.NewBufferString(in.Target.Manifest) t := bytes.NewBufferString(in.Target.Manifest)
err := kubeClient.Update(in.Target.Namespace, c, t, in.Recreate, in.Timeout, in.Wait) err := kubeClient.Update(in.Target.Namespace, c, t, in.Force, in.Recreate, in.Timeout, in.Wait)
return &rudderAPI.RollbackReleaseResponse{}, err return &rudderAPI.RollbackReleaseResponse{}, err
} }
@ -121,7 +121,7 @@ func (r *ReleaseModuleServiceServer) UpgradeRelease(ctx context.Context, in *rud
grpclog.Print("upgrade") grpclog.Print("upgrade")
c := bytes.NewBufferString(in.Current.Manifest) c := bytes.NewBufferString(in.Current.Manifest)
t := bytes.NewBufferString(in.Target.Manifest) t := bytes.NewBufferString(in.Target.Manifest)
err := kubeClient.Update(in.Target.Namespace, c, t, in.Recreate, in.Timeout, in.Wait) err := kubeClient.Update(in.Target.Namespace, c, t, in.Force, in.Recreate, in.Timeout, in.Wait)
// upgrade response object should be changed to include status // upgrade response object should be changed to include status
return &rudderAPI.UpgradeReleaseResponse{}, err return &rudderAPI.UpgradeReleaseResponse{}, err
} }

@ -159,6 +159,7 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts
req.Name = rlsName req.Name = rlsName
req.DisableHooks = h.opts.disableHooks req.DisableHooks = h.opts.disableHooks
req.Recreate = h.opts.recreate req.Recreate = h.opts.recreate
req.Force = h.opts.force
req.ResetValues = h.opts.resetValues req.ResetValues = h.opts.resetValues
req.ReuseValues = h.opts.reuseValues req.ReuseValues = h.opts.reuseValues
ctx := NewContext() ctx := NewContext()
@ -202,6 +203,8 @@ func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.R
opt(&h.opts) opt(&h.opts)
} }
req := &h.opts.rollbackReq req := &h.opts.rollbackReq
req.Recreate = h.opts.recreate
req.Force = h.opts.force
req.DisableHooks = h.opts.disableHooks req.DisableHooks = h.opts.disableHooks
req.DryRun = h.opts.dryRun req.DryRun = h.opts.dryRun
req.Name = rlsName req.Name = rlsName

@ -46,6 +46,8 @@ type options struct {
reuseName bool reuseName bool
// if set, performs pod restart during upgrade/rollback // if set, performs pod restart during upgrade/rollback
recreate bool recreate bool
// if set, force resource update through delete/recreate if needed
force bool
// if set, skip running hooks // if set, skip running hooks
disableHooks bool disableHooks bool
// name of release // name of release
@ -311,6 +313,13 @@ func RollbackRecreate(recreate bool) RollbackOption {
} }
} }
// RollbackForce will (if true) force resource update through delete/recreate if needed
func RollbackForce(force bool) RollbackOption {
return func(opts *options) {
opts.force = force
}
}
// RollbackVersion sets the version of the release to deploy. // RollbackVersion sets the version of the release to deploy.
func RollbackVersion(ver int32) RollbackOption { func RollbackVersion(ver int32) RollbackOption {
return func(opts *options) { return func(opts *options) {
@ -353,6 +362,13 @@ func UpgradeRecreate(recreate bool) UpdateOption {
} }
} }
// UpgradeForce will (if true) force resource update through delete/recreate if needed
func UpgradeForce(force bool) UpdateOption {
return func(opts *options) {
opts.force = force
}
}
// ContentOption allows setting optional attributes when // ContentOption allows setting optional attributes when
// performing a GetReleaseContent tiller rpc. // performing a GetReleaseContent tiller rpc.
type ContentOption func(*options) type ContentOption func(*options)

@ -211,7 +211,7 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
// not present in the target configuration // not present in the target configuration
// //
// Namespace will set the namespaces // Namespace will set the namespaces
func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, recreate bool, timeout int64, shouldWait bool) error { func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error {
original, err := c.BuildUnstructured(namespace, originalReader) original, err := c.BuildUnstructured(namespace, originalReader)
if err != nil { if err != nil {
return fmt.Errorf("failed decoding reader into objects: %s", err) return fmt.Errorf("failed decoding reader into objects: %s", err)
@ -250,7 +250,7 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader
return fmt.Errorf("no resource with the name %q found", info.Name) return fmt.Errorf("no resource with the name %q found", info.Name)
} }
if err := updateResource(c, info, originalInfo.Object, recreate); err != nil { if err := updateResource(c, info, originalInfo.Object, force, recreate); err != nil {
c.Log("error updating the resource %q:\n\t %v", info.Name, err) c.Log("error updating the resource %q:\n\t %v", info.Name, err)
updateErrors = append(updateErrors, err.Error()) updateErrors = append(updateErrors, err.Error())
} }
@ -392,7 +392,7 @@ func createPatch(mapping *meta.RESTMapping, target, current runtime.Object) ([]b
} }
} }
func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, recreate bool) error { func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error {
patch, patchType, err := createPatch(target.Mapping, target.Object, currentObj) patch, patchType, err := createPatch(target.Mapping, target.Object, currentObj)
if err != nil { if err != nil {
return fmt.Errorf("failed to create patch: %s", err) return fmt.Errorf("failed to create patch: %s", err)
@ -409,12 +409,36 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
// send patch to server // send patch to server
helper := resource.NewHelper(target.Client, target.Mapping) helper := resource.NewHelper(target.Client, target.Mapping)
obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch) obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch)
if err != nil { if err != nil {
kind := target.Mapping.GroupVersionKind.Kind
log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err)
if force {
// Attempt to delete...
if err := deleteResource(c, target); err != nil {
return err return err
} }
log.Printf("Deleted %s: %q", kind, target.Name)
// ... and recreate
if err := createResource(target); err != nil {
return fmt.Errorf("Failed to recreate resource: %s", err)
}
log.Printf("Created a new %s called %q\n", kind, target.Name)
// No need to refresh the target, as we recreated the resource based
// on it. In addition, it might not exist yet and a call to `Refresh`
// may fail.
} else {
log.Print("Use --force to force recreation of the resource")
return err
}
} else {
// When patch succeeds without needing to recreate, refresh target.
target.Refresh(obj, true) target.Refresh(obj, true)
}
if !recreate { if !recreate {
return nil return nil

@ -193,7 +193,7 @@ func TestUpdate(t *testing.T) {
reaper := &fakeReaper{} reaper := &fakeReaper{}
rf := &fakeReaperFactory{Factory: f, reaper: reaper} rf := &fakeReaperFactory{Factory: f, reaper: reaper}
c := newTestClient(rf) c := newTestClient(rf)
if err := c.Update(api.NamespaceDefault, objBody(codec, &listA), objBody(codec, &listB), false, 0, false); err != nil { if err := c.Update(api.NamespaceDefault, objBody(codec, &listA), objBody(codec, &listB), false, false, 0, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// TODO: Find a way to test methods that use Client Set // TODO: Find a way to test methods that use Client Set

@ -147,7 +147,7 @@ type KubeClient interface {
// //
// reader must contain a YAML stream (one or more YAML documents separated // reader must contain a YAML stream (one or more YAML documents separated
// by "\n---\n"). // by "\n---\n").
Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error Update(namespace string, originalReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error
Build(namespace string, reader io.Reader) (kube.Result, error) Build(namespace string, reader io.Reader) (kube.Result, error)
BuildUnstructured(namespace string, reader io.Reader) (kube.Result, error) BuildUnstructured(namespace string, reader io.Reader) (kube.Result, error)
@ -190,7 +190,7 @@ func (p *PrintingKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int
} }
// Update implements KubeClient Update. // Update implements KubeClient Update.
func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error { func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error {
_, err := io.Copy(p.Out, modifiedReader) _, err := io.Copy(p.Out, modifiedReader)
return err return err
} }

@ -48,7 +48,7 @@ func (k *mockKubeClient) Get(ns string, r io.Reader) (string, error) {
func (k *mockKubeClient) Delete(ns string, r io.Reader) error { func (k *mockKubeClient) Delete(ns string, r io.Reader) error {
return nil return nil
} }
func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error { func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error {
return nil return nil
} }
func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error {

@ -59,14 +59,14 @@ func (m *LocalReleaseModule) Create(r *release.Release, req *services.InstallRel
func (m *LocalReleaseModule) Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error { func (m *LocalReleaseModule) Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error {
c := bytes.NewBufferString(current.Manifest) c := bytes.NewBufferString(current.Manifest)
t := bytes.NewBufferString(target.Manifest) t := bytes.NewBufferString(target.Manifest)
return env.KubeClient.Update(target.Namespace, c, t, req.Recreate, req.Timeout, req.Wait) return env.KubeClient.Update(target.Namespace, c, t, req.Force, req.Recreate, req.Timeout, req.Wait)
} }
// Rollback performs a rollback from current to target release // Rollback performs a rollback from current to target release
func (m *LocalReleaseModule) Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error { func (m *LocalReleaseModule) Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error {
c := bytes.NewBufferString(current.Manifest) c := bytes.NewBufferString(current.Manifest)
t := bytes.NewBufferString(target.Manifest) t := bytes.NewBufferString(target.Manifest)
return env.KubeClient.Update(target.Namespace, c, t, req.Recreate, req.Timeout, req.Wait) return env.KubeClient.Update(target.Namespace, c, t, req.Force, req.Recreate, req.Timeout, req.Wait)
} }
// Status returns kubectl-like formatted status of release objects // Status returns kubectl-like formatted status of release objects
@ -101,6 +101,7 @@ func (m *RemoteReleaseModule) Update(current, target *release.Release, req *serv
Recreate: req.Recreate, Recreate: req.Recreate,
Timeout: req.Timeout, Timeout: req.Timeout,
Wait: req.Wait, Wait: req.Wait,
Force: req.Force,
} }
_, err := rudder.UpgradeRelease(upgrade) _, err := rudder.UpgradeRelease(upgrade)
return err return err

@ -307,7 +307,7 @@ type updateFailingKubeClient struct {
environment.PrintingKubeClient environment.PrintingKubeClient
} }
func (u *updateFailingKubeClient) Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error { func (u *updateFailingKubeClient) Update(namespace string, originalReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error {
return errors.New("Failed update in kube client") return errors.New("Failed update in kube client")
} }

Loading…
Cancel
Save