mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
Merge pull request #6542 from thomastaylor312/fix/atomic_delete
fix(action): Protects against current resource conflicts
This commit is contained in:
commit
11d5a269a7
4 changed files with 99 additions and 15 deletions
|
|
@ -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")
|
||||
}
|
||||
|
||||
// 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
|
||||
if i.DryRun {
|
||||
rel.Info.Description = "Dry run complete"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
|
||||
"helm.sh/helm/pkg/chart"
|
||||
"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
|
||||
|
||||
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)
|
||||
res, err := u.performUpgrade(currentRelease, upgradedRelease)
|
||||
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) {
|
||||
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))
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
// 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
|
||||
if !u.DisableHooks {
|
||||
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
|
||||
}
|
||||
|
||||
func objectKey(r *resource.Info) string {
|
||||
gvk := r.Object.GetObjectKind().GroupVersionKind()
|
||||
return fmt.Sprintf("%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Name)
|
||||
}
|
||||
|
|
|
|||
47
pkg/action/validate.go
Normal file
47
pkg/action/validate.go
Normal file
|
|
@ -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,8 +192,12 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
|
|||
c.Log("Deleting %q in %s...", info.Name, info.Namespace)
|
||||
res.Deleted = append(res.Deleted, info)
|
||||
if err := deleteResource(info); err != nil {
|
||||
c.Log("Failed to delete %q, err: %s", info.Name, err)
|
||||
return res, errors.Wrapf(err, "Failed to delete %q", info.Name)
|
||||
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)
|
||||
return res, errors.Wrapf(err, "Failed to delete %q", info.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
|
|
|
|||
Loading…
Reference in a new issue