@@ -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+
242382var webhookTestV1 = schema.GroupVersionResource {
243383 Group : "webhook.operators.coreos.io" ,
244384 Version : "v1" ,
0 commit comments