From 104e5e8f55b3a5eaeb8da63e30e61c1f05fc4781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Sun, 17 Nov 2019 00:14:07 +0100 Subject: [PATCH] ref(client): add overwrite option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan-Otto Kröpke --- cmd/helm/upgrade.go | 1 + pkg/action/rollback.go | 2 +- pkg/action/upgrade.go | 3 ++- pkg/kube/client.go | 24 +++++++++++++++++++----- pkg/kube/client_test.go | 2 +- pkg/kube/fake/fake.go | 4 ++-- pkg/kube/fake/printer.go | 2 +- pkg/kube/interface.go | 2 +- 8 files changed, 28 insertions(+), 12 deletions(-) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index eadc3b63d..02a36256a 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -147,6 +147,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { 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 updates through a replacement strategy") + f.BoolVar(&client.Overwrite, "overwrite", true, "overwrite all outside changes") 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/action/rollback.go b/pkg/action/rollback.go index 942c9d8af..8e48ba62e 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -159,7 +159,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) } - results, err := r.cfg.KubeClient.Update(current, target, r.Force) + results, err := r.cfg.KubeClient.Update(current, target, r.Force, true) if err != nil { msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index d0495a864..29d03d8a9 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -49,6 +49,7 @@ type Upgrade struct { DisableHooks bool DryRun bool Force bool + Overwrite bool ResetValues bool ReuseValues bool // Recreate will (if true) recreate pods after a rollback. @@ -236,7 +237,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) } - results, err := u.cfg.KubeClient.Update(current, target, u.Force) + results, err := u.cfg.KubeClient.Update(current, target, u.Force, u.Overwrite) if err != nil { u.cfg.recordRelease(originalRelease) return u.failRelease(upgradedRelease, results.Created, err) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index ab1f600ff..54eabc0e5 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -31,6 +31,7 @@ import ( batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -142,7 +143,7 @@ func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) { // occurs, a Result will still be returned with the error, containing all // resource updates, creations, and deletions that were attempted. These can be // used for cleanup or other logging purposes. -func (c *Client) Update(original, target ResourceList, force bool) (*Result, error) { +func (c *Client) Update(original, target ResourceList, force bool, overwrite bool) (*Result, error) { updateErrors := []string{} res := &Result{} @@ -177,7 +178,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err return errors.Errorf("no %s with the name %q found", kind, info.Name) } - if err := updateResource(c, info, originalInfo.Object, force); err != nil { + if err := updateResource(c, info, originalInfo.Object, force, overwrite); err != nil { c.Log("error updating the resource %q:\n\t %v", info.Name, err) updateErrors = append(updateErrors, err.Error()) } @@ -323,7 +324,7 @@ func deleteResource(info *resource.Info) error { return err } -func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.PatchType, error) { +func createPatch(target *resource.Info, current runtime.Object, overwrite bool) ([]byte, types.PatchType, error) { oldData, err := json.Marshal(current) if err != nil { return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing current configuration") @@ -359,6 +360,19 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P return patch, types.MergePatchType, err } + if !overwrite { + // While different objects need different merge types, the parent function + // that calls this does not try to create a patch when the data (first + // returned object) is nil. We can skip calculating the merge type as + // the returned merge type is ignored. + if apiequality.Semantic.DeepEqual(oldData, newData) { + return nil, types.StrategicMergePatchType, nil + } + + patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject) + return patch, types.StrategicMergePatchType, err + } + patchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject) if err != nil { return nil, types.StrategicMergePatchType, errors.Wrap(err, "unable to create patch metadata from object") @@ -368,14 +382,14 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P return patch, types.StrategicMergePatchType, err } -func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool) error { +func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, overwrite bool) error { var ( obj runtime.Object helper = resource.NewHelper(target.Client, target.Mapping) kind = target.Mapping.GroupVersionKind.Kind ) - patch, patchType, err := createPatch(target, currentObj) + patch, patchType, err := createPatch(target, currentObj, overwrite) if err != nil { return errors.Wrap(err, "failed to create patch") } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 9e7581d00..80c7b88a9 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -162,7 +162,7 @@ func TestUpdate(t *testing.T) { t.Fatal(err) } - if _, err := c.Update(first, second, false); err != nil { + if _, err := c.Update(first, second, false, true); err != nil { t.Fatal(err) } // TODO: Find a way to test methods that use Client Set diff --git a/pkg/kube/fake/fake.go b/pkg/kube/fake/fake.go index b3f7a393b..16ee9df41 100644 --- a/pkg/kube/fake/fake.go +++ b/pkg/kube/fake/fake.go @@ -75,11 +75,11 @@ func (f *FailingKubeClient) WatchUntilReady(resources kube.ResourceList, d time. } // Update returns the configured error if set or prints -func (f *FailingKubeClient) Update(r, modified kube.ResourceList, ignoreMe bool) (*kube.Result, error) { +func (f *FailingKubeClient) Update(r, modified kube.ResourceList, ignoreMe bool, ignoreMeToo bool) (*kube.Result, error) { if f.UpdateError != nil { return &kube.Result{}, f.UpdateError } - return f.PrintingKubeClient.Update(r, modified, ignoreMe) + return f.PrintingKubeClient.Update(r, modified, ignoreMe, ignoreMeToo) } // Build returns the configured error if set or prints diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go index ec75aa790..f50ff9643 100644 --- a/pkg/kube/fake/printer.go +++ b/pkg/kube/fake/printer.go @@ -70,7 +70,7 @@ func (p *PrintingKubeClient) WatchUntilReady(resources kube.ResourceList, _ time } // Update implements KubeClient Update. -func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ bool) (*kube.Result, error) { +func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ bool, _ bool) (*kube.Result, error) { _, err := io.Copy(p.Out, bufferize(modified)) if err != nil { return nil, err diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index 4bf61211e..2e69649cd 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -45,7 +45,7 @@ type Interface interface { // Update updates one or more resources or creates the resource // if it doesn't exist. - Update(original, target ResourceList, force bool) (*Result, error) + Update(original, target ResourceList, force bool, overwrite bool) (*Result, error) // Build creates a resource list from a Reader //