2
2
3
3
import java .io .ByteArrayInputStream ;
4
4
import java .io .FileInputStream ;
5
- import java .io .FileNotFoundException ;
6
5
import java .io .IOException ;
7
6
import java .io .InputStream ;
8
7
import java .nio .charset .StandardCharsets ;
8
+ import java .nio .file .Files ;
9
+ import java .nio .file .Path ;
9
10
import java .time .Duration ;
10
11
import java .util .ArrayList ;
11
12
import java .util .HashMap ;
12
13
import java .util .List ;
13
14
import java .util .Map ;
14
15
import java .util .function .Consumer ;
15
16
import java .util .function .Function ;
16
- import java .util .stream .Collectors ;
17
17
import java .util .stream .Stream ;
18
18
19
19
import org .junit .jupiter .api .extension .ExtensionContext ;
@@ -47,7 +47,7 @@ public class LocallyRunOperatorExtension extends AbstractOperatorExtension {
47
47
private final List <LocalPortForward > localPortForwards ;
48
48
private final List <Class <? extends CustomResource >> additionalCustomResourceDefinitions ;
49
49
private final Map <Reconciler , RegisteredController > registeredControllers ;
50
- private final List <String > additionalCrds ;
50
+ private final Map <String , String > crdMappings ;
51
51
52
52
private LocallyRunOperatorExtension (
53
53
List <ReconcilerSpec > reconcilers ,
@@ -82,7 +82,24 @@ private LocallyRunOperatorExtension(
82
82
: overrider -> overrider .withKubernetesClient (kubernetesClient );
83
83
this .operator = new Operator (configurationServiceOverrider );
84
84
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 ;
86
103
}
87
104
88
105
/**
@@ -112,25 +129,18 @@ public static void applyCrd(Class<? extends HasMetadata> resourceClass, Kubernet
112
129
public static void applyCrd (String resourceTypeName , KubernetesClient client ) {
113
130
String path = "/META-INF/fabric8/" + resourceTypeName + "-v1.yml" ;
114
131
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 );
119
137
} catch (IOException e ) {
120
138
throw new IllegalStateException ("Cannot apply CRD yaml: " + path , e );
121
139
}
122
140
}
123
141
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 ) {
129
143
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 );
134
144
LOGGER .debug ("Applying CRD: {}" , crdString );
135
145
final var crd = client .load (new ByteArrayInputStream (crdString .getBytes ()));
136
146
crd .serverSideApply ();
@@ -144,14 +154,40 @@ private static void applyCrd(InputStream is, String path, KubernetesClient clien
144
154
}
145
155
}
146
156
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 ());
155
191
}
156
192
}
157
193
@@ -160,7 +196,7 @@ private Stream<Reconciler> reconcilers() {
160
196
}
161
197
162
198
public List <Reconciler > getReconcilers () {
163
- return reconcilers ().collect ( Collectors . toUnmodifiableList () );
199
+ return reconcilers ().toList ( );
164
200
}
165
201
166
202
public Reconciler getFirstReconciler () {
@@ -207,7 +243,6 @@ protected void before(ExtensionContext context) {
207
243
}
208
244
209
245
additionalCustomResourceDefinitions .forEach (this ::applyCrd );
210
- Map <String , CustomResourceDefinition > unappliedCRDs = getAdditionalCRDsFromFiles ();
211
246
for (var ref : reconcilers ) {
212
247
final var config = operator .getConfigurationService ().getConfigurationFor (ref .reconciler );
213
248
final var oconfig = override (config );
@@ -227,49 +262,30 @@ protected void before(ExtensionContext context) {
227
262
final var resourceTypeName = ReconcilerUtils .getResourceTypeName (resourceClass );
228
263
// only try to apply a CRD for the reconciler if it is associated to a CR
229
264
if (CustomResource .class .isAssignableFrom (resourceClass )) {
230
- if (unappliedCRDs .get (resourceTypeName ) != null ) {
265
+ if (crdMappings .get (resourceTypeName ) != null ) {
231
266
applyCrd (resourceTypeName );
232
- unappliedCRDs .remove (resourceTypeName );
233
- } else {
234
- applyCrd (resourceClass );
267
+ crdMappings .remove (resourceTypeName );
235
268
}
236
269
}
237
270
238
271
// apply yet unapplied CRDs
239
272
var registeredController = this .operator .register (ref .reconciler , oconfig .build ());
240
273
registeredControllers .put (ref .reconciler , registeredController );
241
274
}
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
+ });
243
284
244
285
LOGGER .debug ("Starting the operator locally" );
245
286
this .operator .start ();
246
287
}
247
288
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
-
273
289
@ Override
274
290
protected void after (ExtensionContext context ) {
275
291
super .after (context );
@@ -295,7 +311,6 @@ public static class Builder extends AbstractBuilder<Builder> {
295
311
private final List <ReconcilerSpec > reconcilers ;
296
312
private final List <PortForwardSpec > portForwards ;
297
313
private final List <Class <? extends CustomResource >> additionalCustomResourceDefinitions ;
298
- private final Map <String , String > crdMappings ;
299
314
private final List <String > additionalCRDs = new ArrayList <>();
300
315
private KubernetesClient kubernetesClient ;
301
316
@@ -304,7 +319,6 @@ protected Builder() {
304
319
this .reconcilers = new ArrayList <>();
305
320
this .portForwards = new ArrayList <>();
306
321
this .additionalCustomResourceDefinitions = new ArrayList <>();
307
- this .crdMappings = new HashMap <>();
308
322
}
309
323
310
324
public Builder withReconciler (
@@ -359,8 +373,10 @@ public Builder withAdditionalCustomResourceDefinition(
359
373
return this ;
360
374
}
361
375
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
+ }
364
380
return this ;
365
381
}
366
382
0 commit comments