...

Source file src/helm.sh/helm/v3/pkg/action/validate.go

Documentation: helm.sh/helm/v3/pkg/action

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package action
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/pkg/errors"
    23  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    24  	"k8s.io/apimachinery/pkg/api/meta"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/cli-runtime/pkg/resource"
    27  
    28  	"helm.sh/helm/v3/pkg/kube"
    29  )
    30  
    31  var accessor = meta.NewAccessor()
    32  
    33  const (
    34  	appManagedByLabel              = "app.kubernetes.io/managed-by"
    35  	appManagedByHelm               = "Helm"
    36  	helmReleaseNameAnnotation      = "meta.helm.sh/release-name"
    37  	helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace"
    38  )
    39  
    40  func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) {
    41  	var requireUpdate kube.ResourceList
    42  
    43  	err := resources.Visit(func(info *resource.Info, err error) error {
    44  		if err != nil {
    45  			return err
    46  		}
    47  
    48  		helper := resource.NewHelper(info.Client, info.Mapping)
    49  		existing, err := helper.Get(info.Namespace, info.Name)
    50  		if err != nil {
    51  			if apierrors.IsNotFound(err) {
    52  				return nil
    53  			}
    54  			return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info))
    55  		}
    56  
    57  		// Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace.
    58  		if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil {
    59  			return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err)
    60  		}
    61  
    62  		requireUpdate.Append(info)
    63  		return nil
    64  	})
    65  
    66  	return requireUpdate, err
    67  }
    68  
    69  func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error {
    70  	lbls, err := accessor.Labels(obj)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	annos, err := accessor.Annotations(obj)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	var errs []error
    80  	if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil {
    81  		errs = append(errs, fmt.Errorf("label validation error: %s", err))
    82  	}
    83  	if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil {
    84  		errs = append(errs, fmt.Errorf("annotation validation error: %s", err))
    85  	}
    86  	if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil {
    87  		errs = append(errs, fmt.Errorf("annotation validation error: %s", err))
    88  	}
    89  
    90  	if len(errs) > 0 {
    91  		err := errors.New("invalid ownership metadata")
    92  		for _, e := range errs {
    93  			err = fmt.Errorf("%w; %s", err, e)
    94  		}
    95  		return err
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  func requireValue(meta map[string]string, k, v string) error {
   102  	actual, ok := meta[k]
   103  	if !ok {
   104  		return fmt.Errorf("missing key %q: must be set to %q", k, v)
   105  	}
   106  	if actual != v {
   107  		return fmt.Errorf("key %q must equal %q: current value is %q", k, v, actual)
   108  	}
   109  	return nil
   110  }
   111  
   112  // setMetadataVisitor adds release tracking metadata to all resources. If force is enabled, existing
   113  // ownership metadata will be overwritten. Otherwise an error will be returned if any resource has an
   114  // existing and conflicting value for the managed by label or Helm release/namespace annotations.
   115  func setMetadataVisitor(releaseName, releaseNamespace string, force bool) resource.VisitorFunc {
   116  	return func(info *resource.Info, err error) error {
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		if !force {
   122  			if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil {
   123  				return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err)
   124  			}
   125  		}
   126  
   127  		if err := mergeLabels(info.Object, map[string]string{
   128  			appManagedByLabel: appManagedByHelm,
   129  		}); err != nil {
   130  			return fmt.Errorf(
   131  				"%s labels could not be updated: %s",
   132  				resourceString(info), err,
   133  			)
   134  		}
   135  
   136  		if err := mergeAnnotations(info.Object, map[string]string{
   137  			helmReleaseNameAnnotation:      releaseName,
   138  			helmReleaseNamespaceAnnotation: releaseNamespace,
   139  		}); err != nil {
   140  			return fmt.Errorf(
   141  				"%s annotations could not be updated: %s",
   142  				resourceString(info), err,
   143  			)
   144  		}
   145  
   146  		return nil
   147  	}
   148  }
   149  
   150  func resourceString(info *resource.Info) string {
   151  	_, k := info.Mapping.GroupVersionKind.ToAPIVersionAndKind()
   152  	return fmt.Sprintf(
   153  		"%s %q in namespace %q",
   154  		k, info.Name, info.Namespace,
   155  	)
   156  }
   157  
   158  func mergeLabels(obj runtime.Object, labels map[string]string) error {
   159  	current, err := accessor.Labels(obj)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	return accessor.SetLabels(obj, mergeStrStrMaps(current, labels))
   164  }
   165  
   166  func mergeAnnotations(obj runtime.Object, annotations map[string]string) error {
   167  	current, err := accessor.Annotations(obj)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	return accessor.SetAnnotations(obj, mergeStrStrMaps(current, annotations))
   172  }
   173  
   174  // merge two maps, always taking the value on the right
   175  func mergeStrStrMaps(current, desired map[string]string) map[string]string {
   176  	result := make(map[string]string)
   177  	for k, v := range current {
   178  		result[k] = v
   179  	}
   180  	for k, desiredVal := range desired {
   181  		result[k] = desiredVal
   182  	}
   183  	return result
   184  }
   185  

View as plain text