Skip to content

Commit 93b01c6

Browse files
UPSTREAM: <carry>: [OTE]: Add webhook cleanup validation on extension uninstall
Assisted-by: Cursor
1 parent b2e343a commit 93b01c6

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,5 +590,15 @@
590590
"source": "openshift:payload:olmv1",
591591
"lifecycle": "blocking",
592592
"environmentSelector": {}
593+
},
594+
{
595+
"name": "[sig-olmv1][OCPFeatureGate:NewOLMWebhookProviderOpenshiftServiceCA] OLMv1 operator with webhooks should clean up webhooks when the extension is uninstalled [Serial]",
596+
"labels": {},
597+
"resources": {
598+
"isolation": {}
599+
},
600+
"source": "openshift:payload:olmv1",
601+
"lifecycle": "blocking",
602+
"environmentSelector": {}
593603
}
594604
]

openshift/tests-extension/pkg/env/cluster.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
configv1 "github.com/openshift/api/config/v1"
1212
imagev1 "github.com/openshift/api/image/v1"
1313
operatorv1 "github.com/openshift/api/operator/v1"
14+
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
1415
appsv1 "k8s.io/api/apps/v1"
1516
batchv1 "k8s.io/api/batch/v1"
1617
corev1 "k8s.io/api/core/v1"
@@ -88,6 +89,7 @@ func initTestEnv() *TestEnv {
8889
utilruntime.Must(rbacv1.AddToScheme(scheme))
8990
utilruntime.Must(batchv1.AddToScheme(scheme))
9091
utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
92+
utilruntime.Must(admissionregistrationv1.AddToScheme(scheme))
9193
utilruntime.Must(olmv1.AddToScheme(scheme))
9294

9395
if isOcp {

openshift/tests-extension/test/webhooks.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
//nolint:staticcheck // ST1001: dot-imports for readability
1515
. "github.com/onsi/gomega"
1616

17+
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
1718
corev1 "k8s.io/api/core/v1"
1819
apierrors "k8s.io/apimachinery/pkg/api/errors"
1920
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -64,6 +65,8 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMWebhookProviderOpenshiftServi
6465

6566
By("ensuring no ClusterExtension and CRD from a previous run")
6667
helpers.EnsureCleanupClusterExtension(ctx, webhookOperatorPackageName, webhookOperatorCRDName)
68+
By("ensuring no stale webhook configurations from previous tests")
69+
ensureCleanupWebhookConfigurations(ctx, k8sClient, "vwebhooktest", "mwebhooktest")
6770

6871
// Build webhook operator bundle and catalog using the consolidated helper
6972
// Note: {{ TEST-BUNDLE }} and {{ NAMESPACE }} will be auto-filled
@@ -237,8 +240,145 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMWebhookProviderOpenshiftServi
237240
g.Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred(), fmt.Sprintf("failed to delete test resource %s: %v", resourceName, err))
238241
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
239242
})
243+
244+
// Test that OLMv1 cleans up webhook configurations when an extension is uninstalled.
245+
// This validates that no webhook configurations are left behind after ClusterExtension deletion.
246+
It("should clean up webhooks when the extension is uninstalled [Serial]", func(ctx SpecContext) {
247+
By("verifying that the webhook operator's webhooks are present")
248+
Eventually(func(g Gomega) {
249+
whList := &admissionregistrationv1.ValidatingWebhookConfigurationList{}
250+
err := k8sClient.List(ctx, whList)
251+
g.Expect(err).ToNot(HaveOccurred(), "failed to list ValidatingWebhookConfigurations")
252+
operatorWebhookFound := false
253+
for _, wh := range whList.Items {
254+
if strings.HasPrefix(wh.Name, "vwebhooktest") {
255+
operatorWebhookFound = true
256+
break
257+
}
258+
}
259+
g.Expect(operatorWebhookFound).To(BeTrue(), "operator validating webhooks should exist")
260+
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
261+
262+
By("verifying that the webhook operator's mutating webhooks are present")
263+
Eventually(func(g Gomega) {
264+
mwhList := &admissionregistrationv1.MutatingWebhookConfigurationList{}
265+
err := k8sClient.List(ctx, mwhList)
266+
g.Expect(err).ToNot(HaveOccurred(), "failed to list MutatingWebhookConfigurations")
267+
operatorMutatingWebhookFound := false
268+
for _, mwh := range mwhList.Items {
269+
if strings.HasPrefix(mwh.Name, "mwebhooktest") {
270+
operatorMutatingWebhookFound = true
271+
break
272+
}
273+
}
274+
g.Expect(operatorMutatingWebhookFound).To(BeTrue(), "operator mutating webhooks should exist")
275+
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
276+
277+
By("uninstalling the ClusterExtension")
278+
ceName := webhookOperatorInstallNamespace
279+
ce := &olmv1.ClusterExtension{
280+
ObjectMeta: metav1.ObjectMeta{Name: ceName},
281+
}
282+
err := k8sClient.Delete(ctx, ce, client.PropagationPolicy(metav1.DeletePropagationBackground))
283+
Expect(err).ToNot(HaveOccurred(), "failed to delete ClusterExtension")
284+
285+
By("waiting for ClusterExtension to be fully deleted")
286+
Eventually(func(g Gomega) {
287+
tempCE := &olmv1.ClusterExtension{}
288+
err := k8sClient.Get(ctx, client.ObjectKey{Name: ceName}, tempCE)
289+
g.Expect(apierrors.IsNotFound(err)).To(BeTrue(), "ClusterExtension should be deleted")
290+
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
291+
292+
By("verifying that operator-created webhook configurations are cleaned up")
293+
Eventually(func(g Gomega) {
294+
whList := &admissionregistrationv1.ValidatingWebhookConfigurationList{}
295+
err := k8sClient.List(ctx, whList)
296+
g.Expect(err).ToNot(HaveOccurred(), "failed to list ValidatingWebhookConfigurations")
297+
operatorWebhookFound := false
298+
for _, wh := range whList.Items {
299+
if strings.HasPrefix(wh.Name, "vwebhooktest") {
300+
operatorWebhookFound = true
301+
break
302+
}
303+
}
304+
g.Expect(operatorWebhookFound).To(BeFalse(), "operator validating webhooks should be deleted")
305+
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
306+
307+
By("verifying that mutating webhook configurations are cleaned up")
308+
Eventually(func(g Gomega) {
309+
mwhList := &admissionregistrationv1.MutatingWebhookConfigurationList{}
310+
err := k8sClient.List(ctx, mwhList)
311+
g.Expect(err).ToNot(HaveOccurred(), "failed to list MutatingWebhookConfigurations")
312+
operatorMutatingWebhookFound := false
313+
for _, mwh := range mwhList.Items {
314+
if strings.HasPrefix(mwh.Name, "mwebhooktest") {
315+
operatorMutatingWebhookFound = true
316+
break
317+
}
318+
}
319+
g.Expect(operatorMutatingWebhookFound).To(BeFalse(), "operator mutating webhooks should be deleted")
320+
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
321+
322+
})
240323
})
241324

325+
func ensureCleanupWebhookConfigurations(ctx context.Context, k8sClient client.Client, validatingPrefix, mutatingPrefix string) {
326+
// Clean up any stale validating webhooks
327+
whList := &admissionregistrationv1.ValidatingWebhookConfigurationList{}
328+
if err := k8sClient.List(ctx, whList); err == nil {
329+
for _, wh := range whList.Items {
330+
if validatingPrefix != "" && strings.HasPrefix(wh.Name, validatingPrefix) {
331+
By(fmt.Sprintf("deleting stale validating webhook: %s", wh.Name))
332+
if err := k8sClient.Delete(ctx, &wh, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil && !apierrors.IsNotFound(err) {
333+
fmt.Fprintf(GinkgoWriter, "Warning: Failed to delete stale validating webhook %s: %v\n", wh.Name, err)
334+
}
335+
}
336+
}
337+
} else if !apierrors.IsNotFound(err) {
338+
fmt.Fprintf(GinkgoWriter, "Warning: Failed to list ValidatingWebhookConfigurations during cleanup: %v\n", err)
339+
}
340+
341+
// Clean up any stale mutating webhooks
342+
mwhList := &admissionregistrationv1.MutatingWebhookConfigurationList{}
343+
if err := k8sClient.List(ctx, mwhList); err == nil {
344+
for _, mwh := range mwhList.Items {
345+
if mutatingPrefix != "" && strings.HasPrefix(mwh.Name, mutatingPrefix) {
346+
By(fmt.Sprintf("deleting stale mutating webhook: %s", mwh.Name))
347+
if err := k8sClient.Delete(ctx, &mwh, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil && !apierrors.IsNotFound(err) {
348+
fmt.Fprintf(GinkgoWriter, "Warning: Failed to delete stale mutating webhook %s: %v\n", mwh.Name, err)
349+
}
350+
}
351+
}
352+
} else if !apierrors.IsNotFound(err) {
353+
fmt.Fprintf(GinkgoWriter, "Warning: Failed to list MutatingWebhookConfigurations during cleanup: %v\n", err)
354+
}
355+
356+
// Verify all stale webhooks are gone
357+
Eventually(func(g Gomega) {
358+
whList := &admissionregistrationv1.ValidatingWebhookConfigurationList{}
359+
err := k8sClient.List(ctx, whList)
360+
g.Expect(err).ToNot(HaveOccurred(), "failed to list ValidatingWebhookConfigurations")
361+
staleWebhooks := []string{}
362+
for _, wh := range whList.Items {
363+
if validatingPrefix != "" && strings.HasPrefix(wh.Name, validatingPrefix) {
364+
staleWebhooks = append(staleWebhooks, wh.Name)
365+
}
366+
}
367+
g.Expect(staleWebhooks).To(BeEmpty(), "stale validating webhooks still exist: %v", staleWebhooks)
368+
369+
mwhList := &admissionregistrationv1.MutatingWebhookConfigurationList{}
370+
err = k8sClient.List(ctx, mwhList)
371+
g.Expect(err).ToNot(HaveOccurred(), "failed to list MutatingWebhookConfigurations")
372+
staleMutatingWebhooks := []string{}
373+
for _, mwh := range mwhList.Items {
374+
if mutatingPrefix != "" && strings.HasPrefix(mwh.Name, mutatingPrefix) {
375+
staleMutatingWebhooks = append(staleMutatingWebhooks, mwh.Name)
376+
}
377+
}
378+
g.Expect(staleMutatingWebhooks).To(BeEmpty(), "stale mutating webhooks still exist: %v", staleMutatingWebhooks)
379+
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
380+
}
381+
242382
var webhookTestV1 = schema.GroupVersionResource{
243383
Group: "webhook.operators.coreos.io",
244384
Version: "v1",

0 commit comments

Comments
 (0)