Merge pull request #6542 from thomastaylor312/fix/atomic_delete

fix(action): Protects against current resource conflicts
pull/6536/head
Taylor Thomas 6 years ago committed by GitHub
commit 11d5a269a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -225,6 +225,18 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest")
} }
// Install requires an extra validation step of checking that resources
// don't already exist before we actually create resources. If we continue
// forward and create the release object with resources that already exist,
// we'll end up in a state where we will delete those resources upon
// deleting the release because the manifest will be pointing at that
// resource
if !i.ClientOnly {
if err := existingResourceConflict(resources); err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install")
}
}
// Bail out here if it is a dry run // Bail out here if it is a dry run
if i.DryRun { if i.DryRun {
rel.Info.Description = "Dry run complete" rel.Info.Description = "Dry run complete"

@ -24,6 +24,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
@ -88,13 +89,6 @@ func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface
u.cfg.Releases.MaxHistory = u.MaxHistory u.cfg.Releases.MaxHistory = u.MaxHistory
if !u.DryRun {
u.cfg.Log("creating upgraded release for %s", name)
if err := u.cfg.Releases.Create(upgradedRelease); err != nil {
return nil, err
}
}
u.cfg.Log("performing update for %s", name) u.cfg.Log("performing update for %s", name)
res, err := u.performUpgrade(currentRelease, upgradedRelease) res, err := u.performUpgrade(currentRelease, upgradedRelease)
if err != nil { if err != nil {
@ -196,12 +190,6 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
} }
func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) { func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) {
if u.DryRun {
u.cfg.Log("dry run for %s", upgradedRelease.Name)
upgradedRelease.Info.Description = "Dry run complete"
return upgradedRelease, nil
}
current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest)) current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest))
if err != nil { if err != nil {
return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest")
@ -211,6 +199,34 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest")
} }
// Do a basic diff using gvk + name to figure out what new resources are being created so we can validate they don't already exist
existingResources := make(map[string]bool)
for _, r := range current {
existingResources[objectKey(r)] = true
}
var toBeCreated kube.ResourceList
for _, r := range target {
if !existingResources[objectKey(r)] {
toBeCreated = append(toBeCreated, r)
}
}
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")
}
if u.DryRun {
u.cfg.Log("dry run for %s", upgradedRelease.Name)
upgradedRelease.Info.Description = "Dry run complete"
return upgradedRelease, nil
}
u.cfg.Log("creating upgraded release for %s", upgradedRelease.Name)
if err := u.cfg.Releases.Create(upgradedRelease); err != nil {
return nil, err
}
// pre-upgrade hooks // pre-upgrade hooks
if !u.DisableHooks { if !u.DisableHooks {
if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil { if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil {
@ -396,3 +412,8 @@ func recreate(cfg *Configuration, resources kube.ResourceList) error {
} }
return nil return nil
} }
func objectKey(r *resource.Info) string {
gvk := r.Object.GetObjectKind().GroupVersionKind()
return fmt.Sprintf("%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Name)
}

@ -0,0 +1,47 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"fmt"
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/pkg/kube"
)
func existingResourceConflict(resources kube.ResourceList) error {
err := resources.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
helper := resource.NewHelper(info.Client, info.Mapping)
if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil {
if apierrors.IsNotFound(err) {
return nil
}
return errors.Wrap(err, "could not get information about the resource")
}
return fmt.Errorf("existing resource conflict: kind: %s, namespace: %s, name: %s", info.Mapping.GroupVersionKind.Kind, info.Namespace, info.Name)
})
return err
}

@ -192,10 +192,14 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
c.Log("Deleting %q in %s...", info.Name, info.Namespace) c.Log("Deleting %q in %s...", info.Name, info.Namespace)
res.Deleted = append(res.Deleted, info) res.Deleted = append(res.Deleted, info)
if err := deleteResource(info); err != nil { if err := deleteResource(info); err != nil {
if apierrors.IsNotFound(err) {
c.Log("Attempted to delete %q, but the resource was missing", info.Name)
} else {
c.Log("Failed to delete %q, err: %s", info.Name, err) c.Log("Failed to delete %q, err: %s", info.Name, err)
return res, errors.Wrapf(err, "Failed to delete %q", info.Name) return res, errors.Wrapf(err, "Failed to delete %q", info.Name)
} }
} }
}
return res, nil return res, nil
} }

Loading…
Cancel
Save