1
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
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
113
114
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
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