Skip to content

Commit 8b08afc

Browse files
authored
Merge pull request #1862 from viccuad/feat/remove-helm-annot
feat(controller): Strip helm bookkeeping annots on migration
2 parents 9b7ac35 + 38fb23b commit 8b08afc

3 files changed

Lines changed: 127 additions & 1 deletion

File tree

internal/constants/constants.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,15 @@ const (
4444
ComponentLabelKey = "app.kubernetes.io/component"
4545
PartOfLabelKey = "app.kubernetes.io/part-of"
4646
PartOfLabelValue = "kubewarden"
47-
ManagedByKey = "app.kubernetes.io/managed-by"
47+
48+
ManagedByKey = "app.kubernetes.io/managed-by"
49+
// ManagedByKeyLabelValue is set via Helm chart templates.
50+
ManagedByKeyLabelValue = "kubewarden-controller"
51+
52+
// HelmResourcePolicy and others re Helm annotations.
53+
HelmResourcePolicy = "helm.sh/resource-policy"
54+
HelmMetaReleaseName = "meta.helm.sh/release-name"
55+
HelmMetaReleaseNamespace = "meta.helm.sh/release-namespace"
4856

4957
// DefaultsManagedByLabelKey is the label key for resources managed by DefaultsApplier.
5058
DefaultsManagedByLabelKey = "kubewarden.io/managed-by"

internal/controller/defaults_applier.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ func (r *DefaultsApplierReconciler) applyResource(ctx context.Context, desired *
114114
// does not end up in our managed field set.
115115
unstructured.RemoveNestedField(desired.Object, "status")
116116

117+
// When adopting a resource that was Helm-managed before the 1.37 chart unification,
118+
// strip the Helm bookkeeping annotations from the live object. This must happen
119+
// before the server-side Apply below, while the live object still reports Helm ownership.
120+
if err := r.stripHelmBookkeeping(ctx, desired); err != nil {
121+
return err
122+
}
123+
117124
// Stamp the ownership label used by cleanupStale to identify managed resources.
118125
labels := desired.GetLabels()
119126
if labels == nil {
@@ -133,6 +140,66 @@ func (r *DefaultsApplierReconciler) applyResource(ctx context.Context, desired *
133140
return nil
134141
}
135142

143+
// stripHelmBookkeeping removes Helm bookkeeping annotations from the live resource when it
144+
// was previously Helm-managed and the desired state explicitly claims controller ownership.
145+
//
146+
// This handles the 1.37 migration where the default PolicyServer and recommended policies
147+
// moved from Helm ownership to controller ownership. Requiring both conditions avoids
148+
// stripping annotations from resources that carry managed-by: Helm for unrelated reasons.
149+
//
150+
// The Helm annotations cannot be removed by the server-side apply in applyResource: they are
151+
// owned by Helm's field manager and are absent from our applied configuration,
152+
// so the API server leaves them untouched. A dedicated merge patch is required.
153+
func (r *DefaultsApplierReconciler) stripHelmBookkeeping(ctx context.Context, desired *unstructured.Unstructured) error {
154+
if desired.GetLabels()[constants.ManagedByKey] != constants.ManagedByKeyLabelValue {
155+
// exit if we are not taking ownership
156+
return nil
157+
}
158+
159+
live := &unstructured.Unstructured{}
160+
live.SetGroupVersionKind(desired.GroupVersionKind())
161+
if err := r.Get(ctx, client.ObjectKeyFromObject(desired), live); err != nil {
162+
if apierrors.IsNotFound(err) {
163+
// fresh resource: nothing to strip
164+
return nil
165+
}
166+
return fmt.Errorf("failed to get live resource: %w", err)
167+
}
168+
169+
if live.GetLabels()[constants.ManagedByKey] != "Helm" {
170+
// exit if it wasn't previously owned by Helm
171+
return nil
172+
}
173+
174+
orig := live.DeepCopy()
175+
annotations := live.GetAnnotations()
176+
changed := false
177+
helmBookkeepingAnnotations := []string{
178+
constants.HelmResourcePolicy,
179+
constants.HelmMetaReleaseName,
180+
constants.HelmMetaReleaseNamespace,
181+
}
182+
for _, key := range helmBookkeepingAnnotations {
183+
if _, found := annotations[key]; found {
184+
delete(annotations, key)
185+
changed = true
186+
}
187+
}
188+
if !changed {
189+
// exit if there was no annotations to remove
190+
return nil
191+
}
192+
live.SetAnnotations(annotations)
193+
194+
if err := r.Patch(ctx, live, client.MergeFrom(orig)); err != nil {
195+
return fmt.Errorf("failed to strip Helm annotations: %w", err)
196+
}
197+
198+
r.Log.Info("Stripped Helm bookkeeping annotations from adopted resource",
199+
"resource", client.ObjectKeyFromObject(live), "kind", live.GetKind())
200+
return nil
201+
}
202+
136203
// isSupportedGVK returns true if the GroupVersionKind is a known Kubewarden resource type.
137204
func isSupportedGVK(gvk schema.GroupVersionKind) bool {
138205
if gvk.Group != constants.KubewardenPoliciesGroup {

internal/controller/defaults_applier_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,57 @@ var _ = Describe("DefaultsApplierReconciler", func() {
279279
})
280280
})
281281

282+
Context("when a previously Helm-managed PolicyServer is adopted by the controller", func() {
283+
It("should remove Helm annotations and update the managed-by label on reconcile", func() {
284+
// Simulate a PolicyServer that was created by Helm before the 1.37 migration.
285+
// It carries Helm bookkeeping annotations and managed-by label.
286+
helmPS := policiesv1.NewPolicyServerFactory().WithName(policyServerName).WithoutFinalizers().Build()
287+
helmPS.Labels = map[string]string{
288+
"app.kubernetes.io/managed-by": "Helm",
289+
}
290+
helmPS.Annotations = map[string]string{
291+
constants.HelmResourcePolicy: "keep",
292+
constants.HelmMetaReleaseName: "kubewarden-defaults",
293+
constants.HelmMetaReleaseNamespace: "kubewarden",
294+
}
295+
Expect(k8sClient.Create(ctx, helmPS)).To(Succeed())
296+
297+
// Now the ConfigMap is created, triggering the controller to adopt the resource.
298+
// Mirror the behavior of the chart: the ConfigMap YAML sets `app.kubernetes.io/managed-by`
299+
// to `kubewarden-controller`, so when we reconcile, applyManagedKeys overwrites the
300+
// Helm value during the same reconcile that strips the Helm annotations.
301+
ps := policiesv1.NewPolicyServerFactory().WithName(policyServerName).WithoutFinalizers().Build()
302+
ps.Labels = map[string]string{
303+
constants.ManagedByKey: constants.ManagedByKeyLabelValue,
304+
}
305+
policyServerYAML := marshalPolicyServer(ps)
306+
307+
cm := &corev1.ConfigMap{
308+
ObjectMeta: metav1.ObjectMeta{
309+
Name: configMapName,
310+
Namespace: deploymentsNamespace,
311+
},
312+
Data: map[string]string{
313+
"policyserver-default": policyServerYAML,
314+
},
315+
}
316+
Expect(k8sClient.Create(ctx, cm)).To(Succeed())
317+
318+
adoptedPS := &policiesv1.PolicyServer{}
319+
Eventually(func(g Gomega) {
320+
g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: policyServerName}, adoptedPS)).To(Succeed())
321+
// Helm annotations must be gone.
322+
g.Expect(adoptedPS.Annotations).NotTo(HaveKey(constants.HelmResourcePolicy))
323+
g.Expect(adoptedPS.Annotations).NotTo(HaveKey(constants.HelmMetaReleaseName))
324+
g.Expect(adoptedPS.Annotations).NotTo(HaveKey(constants.HelmMetaReleaseNamespace))
325+
// managed-by label must be updated to the controller value.
326+
g.Expect(adoptedPS.Labels).To(HaveKeyWithValue(constants.ManagedByKey, constants.ManagedByKeyLabelValue))
327+
// Ownership label must be stamped.
328+
g.Expect(adoptedPS.Labels).To(HaveKeyWithValue(constants.DefaultsManagedByLabelKey, constants.DefaultsManagedByLabelValue))
329+
}, timeout, pollInterval).Should(Succeed())
330+
})
331+
})
332+
282333
Context("when ConfigMap has malformed YAML", func() {
283334
It("should skip the malformed entry and continue with others", func() {
284335
ps := policiesv1.NewPolicyServerFactory().WithName(policyServerName).WithoutFinalizers().Build()

0 commit comments

Comments
 (0)