From 4d5a62303ef45ea56d479f5b7086742e532772d7 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Wed, 16 Oct 2019 14:23:01 -0700 Subject: [PATCH] fix(kube): replace rather than delete/create Signed-off-by: Matthew Fisher --- cmd/helm/upgrade.go | 2 +- pkg/kube/client.go | 53 +++++++++++++++++------------------------ pkg/kube/client_test.go | 2 +- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 22dd23970..282ddff0a 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -146,7 +146,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.DryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") - f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed") + f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 1513c72d2..06df8490e 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -72,7 +72,7 @@ func New(getter genericclioptions.RESTClientGetter) *Client { var nopLogger = func(_ string, _ ...interface{}) {} -// Test connectivity to the Client +// IsReachable tests connectivity to the cluster func (c *Client) IsReachable() error { client, _ := c.Factory.KubernetesClientSet() _, err := client.ServerVersion() @@ -369,52 +369,43 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P } func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool) error { + var ( + obj runtime.Object + helper = resource.NewHelper(target.Client, target.Mapping) + kind = target.Mapping.GroupVersionKind.Kind + ) + patch, patchType, err := createPatch(target, currentObj) if err != nil { return errors.Wrap(err, "failed to create patch") } - if patch == nil { + + if patch == nil || string(patch) == "{}" { c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) // This needs to happen to make sure that tiller has the latest info from the API // Otherwise there will be no labels and other functions that use labels will panic if err := target.Get(); err != nil { - return errors.Wrap(err, "error trying to refresh resource information") + return errors.Wrap(err, "failed to refresh resource information") + } + return nil + } + + // if --force is applied, attempt to replace the existing resource with the new object. + if force { + obj, err = helper.Replace(target.Namespace, target.Name, true, target.Object) + if err != nil { + return errors.Wrap(err, "failed to replace object") } + log.Printf("Replaced %q with kind %s for kind %s\n", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) } else { // send patch to server - helper := resource.NewHelper(target.Client, target.Mapping) - - obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch, nil) + obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, 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(target); err != nil { - return err - } - log.Printf("Deleted %s: %q", kind, target.Name) - - // ... and recreate - if err := createResource(target); err != nil { - return errors.Wrap(err, "failed to recreate resource") - } - 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) return nil } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index a0c678b50..9e7581d00 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -181,7 +181,7 @@ func TestUpdate(t *testing.T) { "/namespaces/default/pods/starfish:PATCH", "/namespaces/default/pods/otter:GET", "/namespaces/default/pods/otter:GET", - "/namespaces/default/pods/otter:PATCH", + "/namespaces/default/pods/otter:GET", "/namespaces/default/pods/dolphin:GET", "/namespaces/default/pods:POST", "/namespaces/default/pods/squid:DELETE",