feat(install): introduce --adopt

Signed-off-by: sukimoyoi <alice89yryr0@gmail.com>
pull/8160/head
sukimoyoi 5 years ago
parent af52d35fb6
commit 5274bb07e9
No known key found for this signature in database
GPG Key ID: 6F9011E626203133

@ -136,6 +136,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.Adopt, "adopt", false, "if set, adopt the resources already exist and aren't owned by any other releases.")
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")

@ -87,6 +87,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0])
}
instClient := action.NewInstall(cfg)
instClient.Adopt = client.Adopt
instClient.CreateNamespace = createNamespace
instClient.ChartPathOptions = client.ChartPathOptions
instClient.DryRun = client.DryRun
@ -166,6 +167,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
})
f := cmd.Flags()
f.BoolVar(&client.Adopt, "adopt", false, "if set, adopt the resources already exist and aren't owned by any other releases.")
f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")

@ -72,6 +72,7 @@ type Install struct {
ChartPathOptions
ClientOnly bool
Adopt bool
CreateNamespace bool
DryRun bool
DisableHooks bool
@ -269,7 +270,7 @@ 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 && len(resources) > 0 {
toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace)
toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace, i.Adopt)
if err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install")
}
@ -281,6 +282,12 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
return rel, nil
}
if i.Adopt {
if err := adoptExistingResource(toBeAdopted, rel.Name, rel.Namespace); err != nil {
return nil, err
}
}
if i.CreateNamespace {
ns := &v1.Namespace{
TypeMeta: metav1.TypeMeta{

@ -96,6 +96,8 @@ type Upgrade struct {
PostRenderer postrender.PostRenderer
// DisableOpenAPIValidation controls whether OpenAPI validation is enforced.
DisableOpenAPIValidation bool
// Adopt, if true, adopt the resources already exist and aren't owned by any other releases.
Adopt bool
}
// NewUpgrade creates a new Upgrade object with the given configuration.
@ -282,7 +284,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
}
}
toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace)
toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace, u.Adopt)
if err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update")
}
@ -305,6 +307,12 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
return upgradedRelease, nil
}
if u.Adopt {
if err := adoptExistingResource(toBeUpdated, upgradedRelease.Name, upgradedRelease.Namespace); err != nil {
return nil, err
}
}
u.cfg.Log("creating upgraded release for %s", upgradedRelease.Name)
if err := u.cfg.Releases.Create(upgradedRelease); err != nil {
return nil, err

@ -23,6 +23,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v3/pkg/kube"
@ -37,7 +38,36 @@ const (
helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace"
)
func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) {
func adoptExistingResource(resources kube.ResourceList, releaseName, releaseNamespace string) error {
err := resources.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
helper := resource.NewHelper(info.Client, info.Mapping)
patchData := fmt.Sprintf(`{
"metadata": {
"labels": {
"%s":"%s"
},
"annotations": {
"%s":"%s",
"%s":"%s"
}
}
}`, appManagedByLabel, appManagedByHelm, helmReleaseNameAnnotation, releaseName,
helmReleaseNamespaceAnnotation, releaseNamespace)
if _, err := helper.Patch(info.Namespace, info.Name, types.StrategicMergePatchType, ([]byte)(patchData), nil); err != nil {
return errors.Wrap(err, "could not patch the resource")
}
return nil
})
return err
}
func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string, adopt bool) (kube.ResourceList, error) {
var requireUpdate kube.ResourceList
err := resources.Visit(func(info *resource.Info, err error) error {
@ -54,10 +84,16 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN
return errors.Wrap(err, "could not get information about the resource")
}
if adopt {
if err := checkAssignedRelease(existing, releaseName); err != nil {
return fmt.Errorf("%s is already used in other release: %s", resourceString(info), err)
}
} else {
// Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace.
if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil {
return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err)
}
}
requireUpdate.Append(info)
return nil
@ -98,6 +134,20 @@ func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) er
return nil
}
func checkAssignedRelease(obj runtime.Object, releaseName string) error {
annos, err := accessor.Annotations(obj)
if err != nil {
return err
}
actual, ok := annos[helmReleaseNameAnnotation]
if ok {
if actual != releaseName {
return fmt.Errorf("adoption error: %q is the owner", actual)
}
}
return nil
}
func requireValue(meta map[string]string, k, v string) error {
actual, ok := meta[k]
if !ok {

@ -46,6 +46,25 @@ func newDeploymentResource(name, namespace string) *resource.Info {
}
}
func TestCheckAssignedRelease(t *testing.T) {
deployFoo := newDeploymentResource("foo", "ns-a")
// Don't set annotations the resource and verify no errors
err := checkAssignedRelease(deployFoo.Object, "rel-a")
assert.NoError(t, err)
// Set other release name annotation and verify annotation error message
_ = accessor.SetAnnotations(deployFoo.Object, map[string]string{
helmReleaseNameAnnotation: "rel-a",
})
err = checkAssignedRelease(deployFoo.Object, "rel-b")
assert.EqualError(t, err, `adoption error: "rel-a" is the owner`)
// Set same release name annotation andverify no errors
err = checkAssignedRelease(deployFoo.Object, "rel-a")
assert.NoError(t, err)
}
func TestCheckOwnership(t *testing.T) {
deployFoo := newDeploymentResource("foo", "ns-a")

Loading…
Cancel
Save