22
33import java .io .ByteArrayInputStream ;
44import java .io .FileInputStream ;
5- import java .io .FileNotFoundException ;
65import java .io .IOException ;
76import java .io .InputStream ;
87import java .nio .charset .StandardCharsets ;
8+ import java .nio .file .Files ;
9+ import java .nio .file .Path ;
910import java .time .Duration ;
1011import java .util .ArrayList ;
1112import java .util .HashMap ;
1213import java .util .List ;
1314import java .util .Map ;
1415import java .util .function .Consumer ;
1516import java .util .function .Function ;
16- import java .util .stream .Collectors ;
1717import java .util .stream .Stream ;
1818
1919import org .junit .jupiter .api .extension .ExtensionContext ;
@@ -47,7 +47,7 @@ public class LocallyRunOperatorExtension extends AbstractOperatorExtension {
4747 private final List <LocalPortForward > localPortForwards ;
4848 private final List <Class <? extends CustomResource >> additionalCustomResourceDefinitions ;
4949 private final Map <Reconciler , RegisteredController > registeredControllers ;
50- private final List <String > additionalCrds ;
50+ private final Map <String , String > crdMappings ;
5151
5252 private LocallyRunOperatorExtension (
5353 List <ReconcilerSpec > reconcilers ,
@@ -82,7 +82,24 @@ private LocallyRunOperatorExtension(
8282 : overrider -> overrider .withKubernetesClient (kubernetesClient );
8383 this .operator = new Operator (configurationServiceOverrider );
8484 this .registeredControllers = new HashMap <>();
85- this .additionalCrds = additionalCrds ;
85+ crdMappings = getAdditionalCRDsFromFiles (additionalCrds , getKubernetesClient ());
86+ }
87+
88+ static Map <String , String > getAdditionalCRDsFromFiles (Iterable <String > additionalCrds ,
89+ KubernetesClient client ) {
90+ Map <String , String > crdMappings = new HashMap <>();
91+ additionalCrds .forEach (p -> {
92+ try (InputStream is = new FileInputStream (p )) {
93+ client .load (is ).items ().stream ()
94+ // only consider CRDs to avoid applying random resources to the cluster
95+ .filter (CustomResourceDefinition .class ::isInstance )
96+ .map (CustomResourceDefinition .class ::cast )
97+ .forEach (crd -> crdMappings .put (crd .getMetadata ().getName (), p ));
98+ } catch (Exception e ) {
99+ throw new RuntimeException ("Couldn't load CRD at " + p , e );
100+ }
101+ });
102+ return crdMappings ;
86103 }
87104
88105 /**
@@ -112,25 +129,18 @@ public static void applyCrd(Class<? extends HasMetadata> resourceClass, Kubernet
112129 public static void applyCrd (String resourceTypeName , KubernetesClient client ) {
113130 String path = "/META-INF/fabric8/" + resourceTypeName + "-v1.yml" ;
114131 try (InputStream is = LocallyRunOperatorExtension .class .getResourceAsStream (path )) {
115- applyCrd (is , path , client );
116- } catch (IllegalStateException e ) {
117- // rethrow directly
118- throw e ;
132+ if (is == null ) {
133+ throw new IllegalStateException ("Cannot find CRD at " + path );
134+ }
135+ var crdString = new String (is .readAllBytes (), StandardCharsets .UTF_8 );
136+ applyCrd (crdString , path , client );
119137 } catch (IOException e ) {
120138 throw new IllegalStateException ("Cannot apply CRD yaml: " + path , e );
121139 }
122140 }
123141
124- public static void applyCrd (CustomResourceDefinition crd , KubernetesClient client ) {
125- client .resource (crd ).serverSideApply ();
126- }
127-
128- private static void applyCrd (InputStream is , String path , KubernetesClient client ) {
142+ private static void applyCrd (String crdString , String path , KubernetesClient client ) {
129143 try {
130- if (is == null ) {
131- throw new IllegalStateException ("Cannot find CRD at " + path );
132- }
133- var crdString = new String (is .readAllBytes (), StandardCharsets .UTF_8 );
134144 LOGGER .debug ("Applying CRD: {}" , crdString );
135145 final var crd = client .load (new ByteArrayInputStream (crdString .getBytes ()));
136146 crd .serverSideApply ();
@@ -144,14 +154,40 @@ private static void applyCrd(InputStream is, String path, KubernetesClient clien
144154 }
145155 }
146156
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 );
157+ /**
158+ * Applies the CRD associated with the specified custom resource, first checking if a CRD has been
159+ * manually specified using {@link Builder#withAdditionalCRD}, otherwise assuming that its CRD
160+ * should be found in the standard location as explained in
161+ * {@link LocallyRunOperatorExtension#applyCrd(String, KubernetesClient)}
162+ *
163+ * @param crClass the custom resource class for which we want to apply the CRD
164+ */
165+ public void applyCrd (Class <? extends CustomResource > crClass ) {
166+ applyCrd (ReconcilerUtils .getResourceTypeName (crClass ));
167+ }
168+
169+ /**
170+ * Applies the CRD associated with the specified resource type name, first checking if a CRD has
171+ * been manually specified using {@link Builder#withAdditionalCRD}, otherwise assuming that its
172+ * CRD should be found in the standard location as explained in
173+ * {@link LocallyRunOperatorExtension#applyCrd(String, KubernetesClient)}
174+ *
175+ * @param resourceTypeName the resource type name associated with the CRD to be applied,
176+ * typically, given a resource type, its name would be obtained using
177+ * {@link ReconcilerUtils#getResourceTypeName(Class)}
178+ */
179+ public void applyCrd (String resourceTypeName ) {
180+ // first attempt to use a manually defined CRD
181+ final var path = crdMappings .get (resourceTypeName );
182+ if (path != null ) {
183+ try {
184+ applyCrd (Files .readString (Path .of (path )), path , getKubernetesClient ());
185+ } catch (IOException e ) {
186+ throw new IllegalStateException ("Cannot open CRD file at " + path , e );
187+ }
188+ } else {
189+ // if no manually defined CRD matches the resource type, apply the generated one
190+ applyCrd (resourceTypeName , getKubernetesClient ());
155191 }
156192 }
157193
@@ -160,7 +196,7 @@ private Stream<Reconciler> reconcilers() {
160196 }
161197
162198 public List <Reconciler > getReconcilers () {
163- return reconcilers ().collect ( Collectors . toUnmodifiableList () );
199+ return reconcilers ().toList ( );
164200 }
165201
166202 public Reconciler getFirstReconciler () {
@@ -207,7 +243,6 @@ protected void before(ExtensionContext context) {
207243 }
208244
209245 additionalCustomResourceDefinitions .forEach (this ::applyCrd );
210- Map <String , CustomResourceDefinition > unappliedCRDs = getAdditionalCRDsFromFiles ();
211246 for (var ref : reconcilers ) {
212247 final var config = operator .getConfigurationService ().getConfigurationFor (ref .reconciler );
213248 final var oconfig = override (config );
@@ -227,49 +262,30 @@ protected void before(ExtensionContext context) {
227262 final var resourceTypeName = ReconcilerUtils .getResourceTypeName (resourceClass );
228263 // only try to apply a CRD for the reconciler if it is associated to a CR
229264 if (CustomResource .class .isAssignableFrom (resourceClass )) {
230- if (unappliedCRDs .get (resourceTypeName ) != null ) {
265+ if (crdMappings .get (resourceTypeName ) != null ) {
231266 applyCrd (resourceTypeName );
232- unappliedCRDs .remove (resourceTypeName );
233- } else {
234- applyCrd (resourceClass );
267+ crdMappings .remove (resourceTypeName );
235268 }
236269 }
237270
238271 // apply yet unapplied CRDs
239272 var registeredController = this .operator .register (ref .reconciler , oconfig .build ());
240273 registeredControllers .put (ref .reconciler , registeredController );
241274 }
242- unappliedCRDs .keySet ().forEach (this ::applyCrd );
275+ crdMappings .forEach ((crdName , path ) -> {
276+ final String crdString ;
277+ try {
278+ crdString = Files .readString (Path .of (path ));
279+ } catch (IOException e ) {
280+ throw new IllegalArgumentException ("Couldn't read CRD located at " + path , e );
281+ }
282+ applyCrd (crdString , path , getKubernetesClient ());
283+ });
243284
244285 LOGGER .debug ("Starting the operator locally" );
245286 this .operator .start ();
246287 }
247288
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-
257- /**
258- * Applies the CRD associated with the specified custom resource, first checking if a CRD has been
259- * manually specified using {@link Builder#withAdditionalCRD(String)}, otherwise assuming that its
260- * CRD should be found in the standard location as explained in
261- * {@link LocallyRunOperatorExtension#applyCrd(String, KubernetesClient)}
262- *
263- * @param crClass the custom resource class for which we want to apply the CRD
264- */
265- public void applyCrd (Class <? extends CustomResource > crClass ) {
266- applyCrd (ReconcilerUtils .getResourceTypeName (crClass ));
267- }
268-
269- public void applyCrd (String resourceTypeName ) {
270- applyCrd (resourceTypeName , getKubernetesClient ());
271- }
272-
273289 @ Override
274290 protected void after (ExtensionContext context ) {
275291 super .after (context );
@@ -295,7 +311,6 @@ public static class Builder extends AbstractBuilder<Builder> {
295311 private final List <ReconcilerSpec > reconcilers ;
296312 private final List <PortForwardSpec > portForwards ;
297313 private final List <Class <? extends CustomResource >> additionalCustomResourceDefinitions ;
298- private final Map <String , String > crdMappings ;
299314 private final List <String > additionalCRDs = new ArrayList <>();
300315 private KubernetesClient kubernetesClient ;
301316
@@ -304,7 +319,6 @@ protected Builder() {
304319 this .reconcilers = new ArrayList <>();
305320 this .portForwards = new ArrayList <>();
306321 this .additionalCustomResourceDefinitions = new ArrayList <>();
307- this .crdMappings = new HashMap <>();
308322 }
309323
310324 public Builder withReconciler (
@@ -359,8 +373,10 @@ public Builder withAdditionalCustomResourceDefinition(
359373 return this ;
360374 }
361375
362- public Builder withAdditionalCRD (String path ) {
363- additionalCRDs .add (path );
376+ public Builder withAdditionalCRD (String ... paths ) {
377+ if (paths != null ) {
378+ additionalCRDs .addAll (List .of (paths ));
379+ }
364380 return this ;
365381 }
366382
0 commit comments