4242import io .fabric8 .kubernetes .api .model .apiextensions .v1 .CustomResourceDefinition ;
4343import io .fabric8 .kubernetes .client .CustomResource ;
4444import io .fabric8 .kubernetes .client .KubernetesClient ;
45+ import io .fabric8 .kubernetes .client .KubernetesClientBuilder ;
4546import io .fabric8 .kubernetes .client .LocalPortForward ;
4647import io .javaoperatorsdk .operator .Operator ;
4748import io .javaoperatorsdk .operator .ReconcilerUtilsInternal ;
@@ -349,6 +350,13 @@ protected void before(ExtensionContext context) {
349350 beforeStartHook .accept (this );
350351 }
351352
353+ // ExtensionContext.Store.CloseableResource registered in the class-level (parent) context
354+ // is invoked by JUnit when the class scope closes
355+ var classContext = oneNamespacePerClass ? context : context .getParent ().orElse (context );
356+ classContext
357+ .getStore (ExtensionContext .Namespace .create (LocallyRunOperatorExtension .class ))
358+ .computeIfAbsent (CrdCleanup .class , ignored -> new CrdCleanup ());
359+
352360 LOGGER .debug ("Starting the operator locally" );
353361 this .operator .start ();
354362 }
@@ -357,18 +365,12 @@ protected void before(ExtensionContext context) {
357365 protected void after (ExtensionContext context ) {
358366 super .after (context );
359367
360- var kubernetesClient = getInfrastructureKubernetesClient ();
361-
362- var iterator = appliedCRDs .iterator ();
363- while (iterator .hasNext ()) {
364- deleteCrd (iterator .next (), kubernetesClient );
365- iterator .remove ();
366- }
368+ var infrastructureKubernetesClient = getInfrastructureKubernetesClient ();
367369
368370 // if the client is used for infra client, we should not close it
369371 // either test or operator should close this client
370- if (getKubernetesClient () != getInfrastructureKubernetesClient () ) {
371- kubernetesClient .close ();
372+ if (getKubernetesClient () != infrastructureKubernetesClient ) {
373+ infrastructureKubernetesClient .close ();
372374 }
373375
374376 try {
@@ -387,12 +389,27 @@ protected void after(ExtensionContext context) {
387389 localPortForwards .clear ();
388390 }
389391
390- private void deleteCrd (AppliedCRD appliedCRD , KubernetesClient client ) {
391- if (!deleteCRDs ) {
392- LOGGER .debug ("Skipping deleting CRD because of configuration: {}" , appliedCRD );
393- return ;
392+ private static class CrdCleanup implements ExtensionContext .Store .CloseableResource {
393+ @ Override
394+ public void close () {
395+ // Create a fresh client for cleanup since operator clients may already be closed.
396+ try (var client = new KubernetesClientBuilder ().build ()) {
397+ var iterator = appliedCRDs .iterator ();
398+ while (iterator .hasNext ()) {
399+ var appliedCRD = iterator .next ();
400+ iterator .remove ();
401+ if (!deleteCRDs ) {
402+ LOGGER .debug ("Skipping deleting CRD because of configuration: {}" , appliedCRD );
403+ continue ;
404+ }
405+ try {
406+ appliedCRD .delete (client );
407+ } catch (Exception e ) {
408+ LOGGER .warn ("Failed to delete CRD: {}. Continuing with remaining CRDs." , appliedCRD , e );
409+ }
410+ }
411+ }
394412 }
395- appliedCRD .delete (client );
396413 }
397414
398415 private sealed interface AppliedCRD permits AppliedCRD .FileCRD , AppliedCRD .InstanceCRD {
0 commit comments