| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 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" | 
					
						
							| 
									
										
										
										
											2025-05-09 14:33:25 +08:00
										 |  |  | 	"maps" | 
					
						
							| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	apierrors "k8s.io/apimachinery/pkg/api/errors" | 
					
						
							| 
									
										
										
										
											2020-02-21 08:42:47 +08:00
										 |  |  | 	"k8s.io/apimachinery/pkg/api/meta" | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 	"k8s.io/apimachinery/pkg/runtime" | 
					
						
							| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | 	"k8s.io/cli-runtime/pkg/resource" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-27 05:33:51 +08:00
										 |  |  | 	"helm.sh/helm/v4/pkg/kube" | 
					
						
							| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | var accessor = meta.NewAccessor() | 
					
						
							| 
									
										
										
										
											2020-02-21 08:42:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | const ( | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 	appManagedByLabel              = "app.kubernetes.io/managed-by" | 
					
						
							|  |  |  | 	appManagedByHelm               = "Helm" | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | 	helmReleaseNameAnnotation      = "meta.helm.sh/release-name" | 
					
						
							|  |  |  | 	helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace" | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2020-02-21 08:42:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 21:09:23 +08:00
										 |  |  | // requireAdoption returns the subset of resources that already exist in the cluster.
 | 
					
						
							|  |  |  | func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { | 
					
						
							|  |  |  | 	var requireUpdate kube.ResourceList | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err := resources.Visit(func(info *resource.Info, err error) error { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		helper := resource.NewHelper(info.Client, info.Mapping) | 
					
						
							|  |  |  | 		_, err = helper.Get(info.Namespace, info.Name) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			if apierrors.IsNotFound(err) { | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-11-16 11:07:40 +08:00
										 |  |  | 			return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err) | 
					
						
							| 
									
										
										
										
											2024-03-13 21:09:23 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		requireUpdate.Append(info) | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return requireUpdate, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) { | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 	var requireUpdate kube.ResourceList | 
					
						
							| 
									
										
										
										
											2020-02-21 08:42:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | 	err := resources.Visit(func(info *resource.Info, err error) error { | 
					
						
							| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		helper := resource.NewHelper(info.Client, info.Mapping) | 
					
						
							| 
									
										
										
										
											2020-09-26 00:15:06 +08:00
										 |  |  | 		existing, err := helper.Get(info.Namespace, info.Name) | 
					
						
							| 
									
										
										
										
											2019-11-07 23:30:01 +08:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | 			if apierrors.IsNotFound(err) { | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-11-16 11:07:40 +08:00
										 |  |  | 			return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err) | 
					
						
							| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 		// 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) | 
					
						
							| 
									
										
										
										
											2020-02-21 08:42:47 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 		requireUpdate.Append(info) | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2020-02-21 08:42:47 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return requireUpdate, err | 
					
						
							| 
									
										
										
										
											2019-10-01 06:05:41 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error { | 
					
						
							|  |  |  | 	lbls, err := accessor.Labels(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	annos, err := accessor.Annotations(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var errs []error | 
					
						
							|  |  |  | 	if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil { | 
					
						
							|  |  |  | 		errs = append(errs, fmt.Errorf("label validation error: %s", err)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil { | 
					
						
							|  |  |  | 		errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil { | 
					
						
							|  |  |  | 		errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(errs) > 0 { | 
					
						
							| 
									
										
										
										
											2024-11-19 01:54:09 +08:00
										 |  |  | 		return fmt.Errorf("invalid ownership metadata; %w", joinErrors(errs, "; ")) | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func requireValue(meta map[string]string, k, v string) error { | 
					
						
							|  |  |  | 	actual, ok := meta[k] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return fmt.Errorf("missing key %q: must be set to %q", k, v) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if actual != v { | 
					
						
							|  |  |  | 		return fmt.Errorf("key %q must equal %q: current value is %q", k, v, actual) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-16 02:31:48 +08:00
										 |  |  | // setMetadataVisitor adds release tracking metadata to all resources. If forceOwnership is enabled, existing
 | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | // ownership metadata will be overwritten. Otherwise an error will be returned if any resource has an
 | 
					
						
							|  |  |  | // existing and conflicting value for the managed by label or Helm release/namespace annotations.
 | 
					
						
							| 
									
										
										
										
											2025-06-16 02:31:48 +08:00
										 |  |  | func setMetadataVisitor(releaseName, releaseNamespace string, forceOwnership bool) resource.VisitorFunc { | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | 	return func(info *resource.Info, err error) error { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-16 02:31:48 +08:00
										 |  |  | 		if !forceOwnership { | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 			if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil { | 
					
						
							|  |  |  | 				return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if err := mergeLabels(info.Object, map[string]string{ | 
					
						
							|  |  |  | 			appManagedByLabel: appManagedByHelm, | 
					
						
							|  |  |  | 		}); err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf( | 
					
						
							|  |  |  | 				"%s labels could not be updated: %s", | 
					
						
							|  |  |  | 				resourceString(info), err, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := mergeAnnotations(info.Object, map[string]string{ | 
					
						
							|  |  |  | 			helmReleaseNameAnnotation:      releaseName, | 
					
						
							|  |  |  | 			helmReleaseNamespaceAnnotation: releaseNamespace, | 
					
						
							|  |  |  | 		}); err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf( | 
					
						
							|  |  |  | 				"%s annotations could not be updated: %s", | 
					
						
							|  |  |  | 				resourceString(info), err, | 
					
						
							|  |  |  | 			) | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func resourceString(info *resource.Info) string { | 
					
						
							|  |  |  | 	_, k := info.Mapping.GroupVersionKind.ToAPIVersionAndKind() | 
					
						
							|  |  |  | 	return fmt.Sprintf( | 
					
						
							|  |  |  | 		"%s %q in namespace %q", | 
					
						
							|  |  |  | 		k, info.Name, info.Namespace, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func mergeLabels(obj runtime.Object, labels map[string]string) error { | 
					
						
							|  |  |  | 	current, err := accessor.Labels(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return accessor.SetLabels(obj, mergeStrStrMaps(current, labels)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func mergeAnnotations(obj runtime.Object, annotations map[string]string) error { | 
					
						
							|  |  |  | 	current, err := accessor.Annotations(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return accessor.SetAnnotations(obj, mergeStrStrMaps(current, annotations)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // merge two maps, always taking the value on the right
 | 
					
						
							|  |  |  | func mergeStrStrMaps(current, desired map[string]string) map[string]string { | 
					
						
							|  |  |  | 	result := make(map[string]string) | 
					
						
							| 
									
										
										
										
											2025-05-09 14:33:25 +08:00
										 |  |  | 	maps.Copy(result, current) | 
					
						
							|  |  |  | 	maps.Copy(result, desired) | 
					
						
							| 
									
										
										
										
											2020-03-11 10:22:15 +08:00
										 |  |  | 	return result | 
					
						
							| 
									
										
										
										
											2020-02-21 09:53:27 +08:00
										 |  |  | } |