diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java index 442e398d83..c36a16cd38 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -21,6 +22,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.LocalPortForward; @@ -45,7 +47,7 @@ public class LocallyRunOperatorExtension extends AbstractOperatorExtension { private final List localPortForwards; private final List> additionalCustomResourceDefinitions; private final Map registeredControllers; - private final Map crdMappings; + private final List additionalCrds; private LocallyRunOperatorExtension( List reconcilers, @@ -60,7 +62,7 @@ private LocallyRunOperatorExtension( Consumer configurationServiceOverrider, Function namespaceNameSupplier, Function perClassNamespaceNameSupplier, - Map crdMappings) { + List additionalCrds) { super( infrastructure, infrastructureTimeout, @@ -80,7 +82,7 @@ private LocallyRunOperatorExtension( : overrider -> overrider.withKubernetesClient(kubernetesClient); this.operator = new Operator(configurationServiceOverrider); this.registeredControllers = new HashMap<>(); - this.crdMappings = crdMappings; + this.additionalCrds = additionalCrds; } /** @@ -119,6 +121,10 @@ public static void applyCrd(String resourceTypeName, KubernetesClient client) { } } + public static void applyCrd(CustomResourceDefinition crd, KubernetesClient client) { + client.resource(crd).serverSideApply(); + } + private static void applyCrd(InputStream is, String path, KubernetesClient client) { try { if (is == null) { @@ -138,6 +144,17 @@ private static void applyCrd(InputStream is, String path, KubernetesClient clien } } + public static List parseCrds(String path, KubernetesClient client) { + try (InputStream is = new FileInputStream(path)) { + return client.load(new ByteArrayInputStream(is.readAllBytes())) + .items().stream().map(i -> (CustomResourceDefinition) i).collect(Collectors.toList()); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private Stream reconcilers() { return reconcilers.stream().map(reconcilerSpec -> reconcilerSpec.reconciler); } @@ -190,7 +207,7 @@ protected void before(ExtensionContext context) { } additionalCustomResourceDefinitions.forEach(this::applyCrd); - + Map unappliedCRDs = getAdditionalCRDsFromFiles(); for (var ref : reconcilers) { final var config = operator.getConfigurationService().getConfigurationFor(ref.reconciler); final var oconfig = override(config); @@ -207,29 +224,40 @@ protected void before(ExtensionContext context) { ref.controllerConfigurationOverrider.accept(oconfig); } - final var unapplied = new HashMap<>(crdMappings); final var resourceTypeName = ReconcilerUtils.getResourceTypeName(resourceClass); // only try to apply a CRD for the reconciler if it is associated to a CR if (CustomResource.class.isAssignableFrom(resourceClass)) { - applyCrd(resourceTypeName); - unapplied.remove(resourceTypeName); + if (unappliedCRDs.get(resourceTypeName) != null) { + applyCrd(resourceTypeName); + unappliedCRDs.remove(resourceTypeName); + } else { + applyCrd(resourceClass); + } } // apply yet unapplied CRDs - unapplied.keySet().forEach(this::applyCrd); - var registeredController = this.operator.register(ref.reconciler, oconfig.build()); registeredControllers.put(ref.reconciler, registeredController); } + unappliedCRDs.keySet().forEach(this::applyCrd); LOGGER.debug("Starting the operator locally"); this.operator.start(); } + private Map getAdditionalCRDsFromFiles() { + Map crdMappings = new HashMap<>(); + additionalCrds.forEach(p -> { + var crds = parseCrds(p, getKubernetesClient()); + crds.forEach(c -> crdMappings.put(c.getMetadata().getName(), c)); + }); + return crdMappings; + } + /** * Applies the CRD associated with the specified custom resource, first checking if a CRD has been - * manually specified using {@link Builder#withCRDMapping(Class, String)}, otherwise assuming that - * its CRD should be found in the standard location as explained in + * manually specified using {@link Builder#withAdditionalCRD(String)}, otherwise assuming that its + * CRD should be found in the standard location as explained in * {@link LocallyRunOperatorExtension#applyCrd(String, KubernetesClient)} * * @param crClass the custom resource class for which we want to apply the CRD @@ -239,16 +267,7 @@ public void applyCrd(Class crClass) { } public void applyCrd(String resourceTypeName) { - final var path = crdMappings.get(resourceTypeName); - if (path != null) { - try (InputStream inputStream = new FileInputStream(path)) { - applyCrd(inputStream, path, getKubernetesClient()); - } catch (IOException e) { - throw new IllegalStateException("Cannot apply CRD yaml: " + path, e); - } - } else { - applyCrd(resourceTypeName, getKubernetesClient()); - } + applyCrd(resourceTypeName, getKubernetesClient()); } @Override @@ -277,6 +296,7 @@ public static class Builder extends AbstractBuilder { private final List portForwards; private final List> additionalCustomResourceDefinitions; private final Map crdMappings; + private final List additionalCRDs = new ArrayList<>(); private KubernetesClient kubernetesClient; protected Builder() { @@ -339,13 +359,8 @@ public Builder withAdditionalCustomResourceDefinition( return this; } - public Builder withCRDMapping(Class customResourceClass, - String path) { - return withCRDMapping(ReconcilerUtils.getResourceTypeName(customResourceClass), path); - } - - public Builder withCRDMapping(String resourceTypeName, String path) { - crdMappings.put(resourceTypeName, path); + public Builder withAdditionalCRD(String path) { + additionalCRDs.add(path); return this; } @@ -360,8 +375,9 @@ public LocallyRunOperatorExtension build() { waitForNamespaceDeletion, oneNamespacePerClass, kubernetesClient, - configurationServiceOverrider, namespaceNameSupplier, perClassNamespaceNameSupplier, - crdMappings); + configurationServiceOverrider, namespaceNameSupplier, + perClassNamespaceNameSupplier, + additionalCRDs); } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDMappingInTestExtensionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDMappingInTestExtensionIT.java index a722f34b92..09b52f1078 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDMappingInTestExtensionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDMappingInTestExtensionIT.java @@ -28,7 +28,7 @@ public class CRDMappingInTestExtensionIT { LocallyRunOperatorExtension operator = LocallyRunOperatorExtension.builder() .withReconciler(new TestReconciler()) - .withCRDMapping("tests.crd.example", "src/test/crd/test.crd") + .withAdditionalCRD("src/test/resources/crd/test.crd") .build(); @Test diff --git a/operator-framework/src/test/crd/test.crd b/operator-framework/src/test/resources/crd/test.crd similarity index 100% rename from operator-framework/src/test/crd/test.crd rename to operator-framework/src/test/resources/crd/test.crd