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,42 @@ 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 pathAsString = crdMappings .get (resourceTypeName );
182
+ if (pathAsString != null ) {
183
+ final var path = Path .of (pathAsString );
184
+ try {
185
+ applyCrd (Files .readString (path ), pathAsString , getKubernetesClient ());
186
+ } catch (IOException e ) {
187
+ throw new IllegalStateException ("Cannot open CRD file at " + path .toAbsolutePath (), e );
188
+ }
189
+ crdMappings .remove (resourceTypeName );
190
+ } else {
191
+ // if no manually defined CRD matches the resource type, apply the generated one
192
+ applyCrd (resourceTypeName , getKubernetesClient ());
155
193
}
156
194
}
157
195
@@ -160,7 +198,7 @@ private Stream<Reconciler> reconcilers() {
160
198
}
161
199
162
200
public List <Reconciler > getReconcilers () {
163
- return reconcilers ().collect ( Collectors . toUnmodifiableList () );
201
+ return reconcilers ().toList ( );
164
202
}
165
203
166
204
public Reconciler getFirstReconciler () {
@@ -207,7 +245,6 @@ protected void before(ExtensionContext context) {
207
245
}
208
246
209
247
additionalCustomResourceDefinitions .forEach (this ::applyCrd );
210
- Map <String , CustomResourceDefinition > unappliedCRDs = getAdditionalCRDsFromFiles ();
211
248
for (var ref : reconcilers ) {
212
249
final var config = operator .getConfigurationService ().getConfigurationFor (ref .reconciler );
213
250
final var oconfig = override (config );
@@ -227,49 +264,28 @@ protected void before(ExtensionContext context) {
227
264
final var resourceTypeName = ReconcilerUtils .getResourceTypeName (resourceClass );
228
265
// only try to apply a CRD for the reconciler if it is associated to a CR
229
266
if (CustomResource .class .isAssignableFrom (resourceClass )) {
230
- if (unappliedCRDs .get (resourceTypeName ) != null ) {
231
- applyCrd (resourceTypeName );
232
- unappliedCRDs .remove (resourceTypeName );
233
- } else {
234
- applyCrd (resourceClass );
235
- }
267
+ applyCrd (resourceTypeName );
236
268
}
237
269
238
270
// apply yet unapplied CRDs
239
271
var registeredController = this .operator .register (ref .reconciler , oconfig .build ());
240
272
registeredControllers .put (ref .reconciler , registeredController );
241
273
}
242
- unappliedCRDs .keySet ().forEach (this ::applyCrd );
274
+ crdMappings .forEach ((crdName , path ) -> {
275
+ final String crdString ;
276
+ try {
277
+ crdString = Files .readString (Path .of (path ));
278
+ } catch (IOException e ) {
279
+ throw new IllegalArgumentException ("Couldn't read CRD located at " + path , e );
280
+ }
281
+ applyCrd (crdString , path , getKubernetesClient ());
282
+ });
283
+ crdMappings .clear ();
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