22
33import java .io .ByteArrayInputStream ;
44import java .io .FileInputStream ;
5+ import java .io .FileNotFoundException ;
56import java .io .IOException ;
67import java .io .InputStream ;
78import java .nio .charset .StandardCharsets ;
2122
2223import io .fabric8 .kubernetes .api .model .HasMetadata ;
2324import io .fabric8 .kubernetes .api .model .Namespaced ;
25+ import io .fabric8 .kubernetes .api .model .apiextensions .v1 .CustomResourceDefinition ;
2426import io .fabric8 .kubernetes .client .CustomResource ;
2527import io .fabric8 .kubernetes .client .KubernetesClient ;
2628import io .fabric8 .kubernetes .client .LocalPortForward ;
@@ -45,7 +47,7 @@ public class LocallyRunOperatorExtension extends AbstractOperatorExtension {
4547 private final List <LocalPortForward > localPortForwards ;
4648 private final List <Class <? extends CustomResource >> additionalCustomResourceDefinitions ;
4749 private final Map <Reconciler , RegisteredController > registeredControllers ;
48- private final Map <String , String > crdMappings ;
50+ private final List <String > additionalCrds ;
4951
5052 private LocallyRunOperatorExtension (
5153 List <ReconcilerSpec > reconcilers ,
@@ -60,7 +62,7 @@ private LocallyRunOperatorExtension(
6062 Consumer <ConfigurationServiceOverrider > configurationServiceOverrider ,
6163 Function <ExtensionContext , String > namespaceNameSupplier ,
6264 Function <ExtensionContext , String > perClassNamespaceNameSupplier ,
63- Map <String , String > crdMappings ) {
65+ List <String > additionalCrds ) {
6466 super (
6567 infrastructure ,
6668 infrastructureTimeout ,
@@ -80,7 +82,7 @@ private LocallyRunOperatorExtension(
8082 : overrider -> overrider .withKubernetesClient (kubernetesClient );
8183 this .operator = new Operator (configurationServiceOverrider );
8284 this .registeredControllers = new HashMap <>();
83- this .crdMappings = crdMappings ;
85+ this .additionalCrds = additionalCrds ;
8486 }
8587
8688 /**
@@ -119,6 +121,10 @@ public static void applyCrd(String resourceTypeName, KubernetesClient client) {
119121 }
120122 }
121123
124+ public static void applyCrd (CustomResourceDefinition crd , KubernetesClient client ) {
125+ client .resource (crd ).serverSideApply ();
126+ }
127+
122128 private static void applyCrd (InputStream is , String path , KubernetesClient client ) {
123129 try {
124130 if (is == null ) {
@@ -138,6 +144,17 @@ private static void applyCrd(InputStream is, String path, KubernetesClient clien
138144 }
139145 }
140146
147+ public static List <CustomResourceDefinition > parseCrds (String path , KubernetesClient client ) {
148+ try (InputStream is = new FileInputStream (path )) {
149+ return client .load (new ByteArrayInputStream (is .readAllBytes ()))
150+ .items ().stream ().map (i -> (CustomResourceDefinition ) i ).collect (Collectors .toList ());
151+ } catch (FileNotFoundException e ) {
152+ throw new RuntimeException (e );
153+ } catch (IOException e ) {
154+ throw new RuntimeException (e );
155+ }
156+ }
157+
141158 private Stream <Reconciler > reconcilers () {
142159 return reconcilers .stream ().map (reconcilerSpec -> reconcilerSpec .reconciler );
143160 }
@@ -190,7 +207,7 @@ protected void before(ExtensionContext context) {
190207 }
191208
192209 additionalCustomResourceDefinitions .forEach (this ::applyCrd );
193-
210+ Map < String , CustomResourceDefinition > unappliedCRDs = getAdditionalCRDsFromFiles ();
194211 for (var ref : reconcilers ) {
195212 final var config = operator .getConfigurationService ().getConfigurationFor (ref .reconciler );
196213 final var oconfig = override (config );
@@ -207,29 +224,40 @@ protected void before(ExtensionContext context) {
207224 ref .controllerConfigurationOverrider .accept (oconfig );
208225 }
209226
210- final var unapplied = new HashMap <>(crdMappings );
211227 final var resourceTypeName = ReconcilerUtils .getResourceTypeName (resourceClass );
212228 // only try to apply a CRD for the reconciler if it is associated to a CR
213229 if (CustomResource .class .isAssignableFrom (resourceClass )) {
214- applyCrd (resourceTypeName );
215- unapplied .remove (resourceTypeName );
230+ if (unappliedCRDs .get (resourceTypeName ) != null ) {
231+ applyCrd (resourceTypeName );
232+ unappliedCRDs .remove (resourceTypeName );
233+ } else {
234+ applyCrd (resourceClass );
235+ }
216236 }
217237
218238 // apply yet unapplied CRDs
219- unapplied .keySet ().forEach (this ::applyCrd );
220-
221239 var registeredController = this .operator .register (ref .reconciler , oconfig .build ());
222240 registeredControllers .put (ref .reconciler , registeredController );
223241 }
242+ unappliedCRDs .keySet ().forEach (this ::applyCrd );
224243
225244 LOGGER .debug ("Starting the operator locally" );
226245 this .operator .start ();
227246 }
228247
248+ private Map <String , CustomResourceDefinition > getAdditionalCRDsFromFiles () {
249+ Map <String , CustomResourceDefinition > crdMappings = new HashMap <>();
250+ additionalCrds .forEach (p -> {
251+ var crds = parseCrds (p , getKubernetesClient ());
252+ crds .forEach (c -> crdMappings .put (c .getMetadata ().getName (), c ));
253+ });
254+ return crdMappings ;
255+ }
256+
229257 /**
230258 * Applies the CRD associated with the specified custom resource, first checking if a CRD has been
231- * manually specified using {@link Builder#withCRDMapping(Class, String)}, otherwise assuming that
232- * its CRD should be found in the standard location as explained in
259+ * manually specified using {@link Builder#withAdditionalCRD( String)}, otherwise assuming that its
260+ * CRD should be found in the standard location as explained in
233261 * {@link LocallyRunOperatorExtension#applyCrd(String, KubernetesClient)}
234262 *
235263 * @param crClass the custom resource class for which we want to apply the CRD
@@ -239,16 +267,7 @@ public void applyCrd(Class<? extends CustomResource> crClass) {
239267 }
240268
241269 public void applyCrd (String resourceTypeName ) {
242- final var path = crdMappings .get (resourceTypeName );
243- if (path != null ) {
244- try (InputStream inputStream = new FileInputStream (path )) {
245- applyCrd (inputStream , path , getKubernetesClient ());
246- } catch (IOException e ) {
247- throw new IllegalStateException ("Cannot apply CRD yaml: " + path , e );
248- }
249- } else {
250- applyCrd (resourceTypeName , getKubernetesClient ());
251- }
270+ applyCrd (resourceTypeName , getKubernetesClient ());
252271 }
253272
254273 @ Override
@@ -277,6 +296,7 @@ public static class Builder extends AbstractBuilder<Builder> {
277296 private final List <PortForwardSpec > portForwards ;
278297 private final List <Class <? extends CustomResource >> additionalCustomResourceDefinitions ;
279298 private final Map <String , String > crdMappings ;
299+ private final List <String > additionalCRDs = new ArrayList <>();
280300 private KubernetesClient kubernetesClient ;
281301
282302 protected Builder () {
@@ -339,13 +359,8 @@ public Builder withAdditionalCustomResourceDefinition(
339359 return this ;
340360 }
341361
342- public Builder withCRDMapping (Class <? extends CustomResource > customResourceClass ,
343- String path ) {
344- return withCRDMapping (ReconcilerUtils .getResourceTypeName (customResourceClass ), path );
345- }
346-
347- public Builder withCRDMapping (String resourceTypeName , String path ) {
348- crdMappings .put (resourceTypeName , path );
362+ public Builder withAdditionalCRD (String path ) {
363+ additionalCRDs .add (path );
349364 return this ;
350365 }
351366
@@ -360,8 +375,9 @@ public LocallyRunOperatorExtension build() {
360375 waitForNamespaceDeletion ,
361376 oneNamespacePerClass ,
362377 kubernetesClient ,
363- configurationServiceOverrider , namespaceNameSupplier , perClassNamespaceNameSupplier ,
364- crdMappings );
378+ configurationServiceOverrider , namespaceNameSupplier ,
379+ perClassNamespaceNameSupplier ,
380+ additionalCRDs );
365381 }
366382 }
367383
0 commit comments