github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/action/validate.go (about) 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 "github.com/stefanmcshane/helm/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 }