From a29365b3c663f1c182ea78461825ae28b8573915 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Thu, 20 Feb 2020 19:42:47 -0500 Subject: [PATCH] Adopt resources into release with correct instance and managed-by labels Signed-off-by: Jacob LeGrone --- pkg/action/install.go | 8 ++++++-- pkg/action/upgrade.go | 13 ++++++++++-- pkg/action/validate.go | 45 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 79abfae33..ac57b921c 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -38,6 +38,7 @@ import ( "helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/engine" "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/kube" kubefake "helm.sh/helm/v3/pkg/kube/fake" "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" @@ -242,6 +243,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // Mark this release as in-progress rel.SetStatus(release.StatusPendingInstall, "Initial install underway") + var adoptedResources kube.ResourceList resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) if err != nil { return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") @@ -254,9 +256,11 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // deleting the release because the manifest will be pointing at that // resource if !i.ClientOnly && !isUpgrade { - if err := existingResourceConflict(resources); err != nil { + toBeUpdated, err := existingResourceConflict(resources, rel.Name) + if err != nil { return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") } + adoptedResources = toBeUpdated } // Bail out here if it is a dry run @@ -291,7 +295,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // At this point, we can do the install. Note that before we were detecting whether to // do an update, but it's not clear whether we WANT to do an update if the re-use is set // to true, since that is basically an upgrade operation. - if _, err := i.cfg.KubeClient.Create(resources); err != nil { + if _, err := i.cfg.KubeClient.Update(adoptedResources, resources, false); err != nil { return i.failRelease(rel, err) } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 08b638171..be64fa618 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -216,10 +216,19 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea } } - if err := existingResourceConflict(toBeCreated); err != nil { - return nil, errors.Wrap(err, "rendered manifests contain a new resource that already exists. Unable to continue with update") + toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name) + if err != nil { + return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update") } + toBeUpdated.Visit(func(r *resource.Info, err error) error { + if err != nil { + return err + } + current.Append(r) + return nil + }) + if u.DryRun { u.cfg.Log("dry run for %s", upgradedRelease.Name) if len(u.Description) > 0 { diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 6bbfc5e8d..feecc4ff3 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -21,13 +21,37 @@ import ( "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/cli-runtime/pkg/resource" "helm.sh/helm/v3/pkg/kube" ) -func existingResourceConflict(resources kube.ResourceList) error { - err := resources.Visit(func(info *resource.Info, err error) error { +var ( + managedByReq, _ = labels.NewRequirement("app.kubernetes.io/managed-by", selection.Equals, []string{"Helm"}) + accessor = meta.NewAccessor() +) + +func newReleaseSelector(release string) (labels.Selector, error) { + releaseReq, err := labels.NewRequirement("app.kubernetes.io/instance", selection.Equals, []string{release}) + if err != nil { + return nil, err + } + + return labels.Parse(fmt.Sprintf("%s,%s", releaseReq, managedByReq)) +} + +func existingResourceConflict(resources kube.ResourceList, release string) (kube.ResourceList, error) { + sel, err := newReleaseSelector(release) + if err != nil { + return nil, err + } + + requireUpdate := kube.ResourceList{} + + err = resources.Visit(func(info *resource.Info, err error) error { if err != nil { return err } @@ -42,7 +66,20 @@ func existingResourceConflict(resources kube.ResourceList) error { return errors.Wrap(err, "could not get information about the resource") } - return fmt.Errorf("existing resource conflict: namespace: %s, name: %s, existing_kind: %s, new_kind: %s", info.Namespace, info.Name, existing.GetObjectKind().GroupVersionKind(), info.Mapping.GroupVersionKind) + // Allow adoption of the resource if it already has app.kubernetes.io/instance and app.kubernetes.io/managed-by labels. + lbls, err := accessor.Labels(existing) + if err == nil { + set := labels.Set(lbls) + if sel.Matches(set) { + requireUpdate = append(requireUpdate, info) + return nil + } + } + + return fmt.Errorf( + "existing resource conflict: namespace: %s, name: %s, existing_kind: %s, new_kind: %s", + info.Namespace, info.Name, existing.GetObjectKind().GroupVersionKind(), info.Mapping.GroupVersionKind) }) - return err + + return requireUpdate, err }