Skip to content

Commit c5eb614

Browse files
committed
feat: create CDI beans for dependent resources (#281)
* feat: create CDI beans for dependent resources * chore: update to Quarkus 2.7.5.Final and JOSDK 3.0.0-SNAPSHOT * chore: adapt to latest API changes * fix: use MariaDB so that tests work by default on M1 Apple systems * fix: remove ability to configure finalizer at the operator level Finalizers are now only added when the Reconciler implements Cleaner or one of its dependent implements Deleter. Configuring finalizers at the operator level doesn't make much sense in that context.
1 parent 0a0a6f8 commit c5eb614

File tree

21 files changed

+144
-114
lines changed

21 files changed

+144
-114
lines changed

bom/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
<properties>
4242
<quarkus.version>2.7.5.Final</quarkus.version>
43-
<java-operator-sdk.version>2.2.0-SNAPSHOT</java-operator-sdk.version>
43+
<java-operator-sdk.version>3.0.0-SNAPSHOT</java-operator-sdk.version>
4444
<kubernetes-client.version>5.12.1</kubernetes-client.version>
4545
</properties>
4646
<dependencyManagement>

core/deployment/src/main/java/io/quarkiverse/operatorsdk/deployment/ContextStoredControllerConfigurations.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private boolean shouldRegenerate(String reconcilerClassName, Set<String> changed
6666
return changedClasses.contains(reconcilerClassName)
6767
|| changedClasses.contains(configuration.getResourceTypeName())
6868
|| changedResources.contains("application.properties")
69-
|| configuration.getDependentResources().stream()
69+
|| configuration.getDependentResources().values().stream()
7070
.map(dr -> dr.getDependentResourceClass().getCanonicalName())
7171
.anyMatch(changedClasses::contains);
7272
}

core/deployment/src/main/java/io/quarkiverse/operatorsdk/deployment/OperatorSDKProcessor.java

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@
99
import static io.quarkus.arc.processor.DotNames.APPLICATION_SCOPED;
1010

1111
import java.lang.annotation.Annotation;
12-
import java.util.ArrayList;
1312
import java.util.Arrays;
1413
import java.util.Collections;
1514
import java.util.HashMap;
1615
import java.util.HashSet;
1716
import java.util.List;
17+
import java.util.Map;
1818
import java.util.Optional;
1919
import java.util.Set;
2020
import java.util.function.BooleanSupplier;
21+
import java.util.function.Predicate;
2122
import java.util.stream.Collectors;
2223

2324
import javax.enterprise.inject.Instance;
@@ -39,7 +40,6 @@
3940
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
4041
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
4142
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
42-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
4343
import io.quarkiverse.operatorsdk.common.ClassUtils;
4444
import io.quarkiverse.operatorsdk.common.ClassUtils.ReconcilerInfo;
4545
import io.quarkiverse.operatorsdk.common.ConfigurationUtils;
@@ -381,55 +381,8 @@ private QuarkusControllerConfiguration createControllerConfiguration(
381381
// extract the namespaces
382382
final var namespaces = configExtractor.namespaces(name);
383383

384-
// deal with dependent resources
385-
var dependentResources = Collections.<QuarkusDependentResourceSpec> emptyList();
386-
if (controllerAnnotation != null) {
387-
final var dependents = controllerAnnotation.value("dependents");
388-
if (dependents != null) {
389-
final var dependentAnnotations = dependents.asNestedArray();
390-
dependentResources = new ArrayList<>(dependentAnnotations.length);
391-
for (AnnotationInstance dependentConfig : dependentAnnotations) {
392-
final var dependentTypeName = dependentConfig.value("type").asClass().name();
393-
final var dependentType = index.getClassByName(dependentTypeName);
394-
if (!dependentType.hasNoArgsConstructor()) {
395-
throw new IllegalArgumentException(
396-
"DependentResource implementations must provide a no-arg constructor for instantiation purposes");
397-
}
398-
399-
final var dependentClass = loadClass(dependentTypeName.toString(), DependentResource.class);
400-
registerForReflection(reflectionClasses, dependentTypeName.toString());
401-
402-
// further process Kubernetes dependents
403-
final boolean isKubernetesDependent;
404-
try {
405-
isKubernetesDependent = JandexUtil.isSubclassOf(index, dependentType,
406-
KUBERNETES_DEPENDENT_RESOURCE);
407-
} catch (BuildException e) {
408-
throw new IllegalStateException("DependentResource " + dependentType + " is not indexed", e);
409-
}
410-
Object cfg = null;
411-
if (isKubernetesDependent) {
412-
final var kubeDepConfig = dependentType.classAnnotation(KUBERNETES_DEPENDENT);
413-
final var labelSelector = getLabelSelector(kubeDepConfig);
414-
// if the dependent doesn't explicitly provide a namespace configuration, inherit the configuration from the reconciler configuration
415-
final Set<String> dependentNamespaces = ConfigurationUtils.annotationValueOrDefault(
416-
kubeDepConfig,
417-
"namespaces", v -> new HashSet<>(
418-
Arrays.asList(v.asStringArray())),
419-
() -> namespaces);
420-
final var owned = ConfigurationUtils.annotationValueOrDefault(
421-
kubeDepConfig,
422-
"owned",
423-
AnnotationValue::asBoolean,
424-
() -> KubernetesDependent.ADD_OWNER_REFERENCE_DEFAULT);
425-
cfg = new QuarkusKubernetesDependentResourceConfig(
426-
owned, dependentNamespaces.toArray(new String[0]), labelSelector);
427-
}
428-
429-
dependentResources.add(new QuarkusDependentResourceSpec(dependentClass, cfg));
430-
}
431-
}
432-
}
384+
final var dependentResources = createDependentResources(
385+
reflectionClasses, index, controllerAnnotation, namespaces, additionalBeans);
433386

434387
// create the configuration
435388
configuration = new QuarkusControllerConfiguration(
@@ -465,6 +418,71 @@ private QuarkusControllerConfiguration createControllerConfiguration(
465418
return configuration;
466419
}
467420

421+
@SuppressWarnings("unchecked")
422+
private Map<String, QuarkusDependentResourceSpec> createDependentResources(
423+
BuildProducer<ReflectiveClassBuildItem> reflectionClasses, IndexView index,
424+
AnnotationInstance controllerAnnotation, Set<String> namespaces,
425+
BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
426+
// deal with dependent resources
427+
var dependentResources = Collections.<String, QuarkusDependentResourceSpec> emptyMap();
428+
if (controllerAnnotation != null) {
429+
final var dependents = controllerAnnotation.value("dependents");
430+
if (dependents != null) {
431+
final var dependentAnnotations = dependents.asNestedArray();
432+
dependentResources = new HashMap<>(dependentAnnotations.length);
433+
for (AnnotationInstance dependentConfig : dependentAnnotations) {
434+
final var dependentTypeDN = dependentConfig.value("type").asClass().name();
435+
final var dependentType = index.getClassByName(dependentTypeDN);
436+
if (!dependentType.hasNoArgsConstructor()) {
437+
throw new IllegalArgumentException(
438+
"DependentResource implementations must provide a no-arg constructor for instantiation purposes");
439+
}
440+
441+
final var dependentTypeName = dependentTypeDN.toString();
442+
final var dependentClass = loadClass(dependentTypeName, DependentResource.class);
443+
registerForReflection(reflectionClasses, dependentTypeName);
444+
445+
// further process Kubernetes dependents
446+
final boolean isKubernetesDependent;
447+
try {
448+
isKubernetesDependent = JandexUtil.isSubclassOf(index, dependentType,
449+
KUBERNETES_DEPENDENT_RESOURCE);
450+
} catch (BuildException e) {
451+
throw new IllegalStateException("DependentResource " + dependentType + " is not indexed", e);
452+
}
453+
Object cfg = null;
454+
if (isKubernetesDependent) {
455+
final var kubeDepConfig = dependentType.classAnnotation(KUBERNETES_DEPENDENT);
456+
final var labelSelector = getLabelSelector(kubeDepConfig);
457+
// if the dependent doesn't explicitly provide a namespace configuration, inherit the configuration from the reconciler configuration
458+
final Set<String> dependentNamespaces = ConfigurationUtils.annotationValueOrDefault(
459+
kubeDepConfig,
460+
"namespaces", v -> new HashSet<>(
461+
Arrays.asList(v.asStringArray())),
462+
() -> namespaces);
463+
cfg = new QuarkusKubernetesDependentResourceConfig(dependentNamespaces.toArray(new String[0]),
464+
labelSelector);
465+
}
466+
467+
var nameField = dependentConfig.value("name");
468+
final var name = Optional.ofNullable(nameField)
469+
.map(AnnotationValue::asString)
470+
.filter(Predicate.not(String::isBlank))
471+
.orElse(DependentResource.defaultNameFor(dependentClass));
472+
dependentResources.put(name, new QuarkusDependentResourceSpec(dependentClass, cfg, name));
473+
474+
additionalBeans.produce(
475+
AdditionalBeanBuildItem.builder()
476+
.addBeanClass(dependentTypeName)
477+
.setUnremovable()
478+
.setDefaultScope(APPLICATION_SCOPED)
479+
.build());
480+
}
481+
}
482+
}
483+
return dependentResources;
484+
}
485+
468486
private String getFullResourceName(Class<? extends HasMetadata> crClass) {
469487
return ReconcilerUtils.getResourceTypeName(crClass);
470488
}

core/runtime/src/main/java/io/quarkiverse/operatorsdk/runtime/ConfigurationServiceRecorder.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public Supplier<QuarkusConfigurationService> configurationServiceSupplier(Versio
2727
configurations.forEach((name, c) -> {
2828
final var extConfig = runTimeConfiguration.controllers.get(name);
2929
// first use the operator-level configuration if set
30-
runTimeConfiguration.finalizer.ifPresent(c::setFinalizer);
3130
runTimeConfiguration.namespaces.ifPresent(c::setNamespaces);
3231

3332
// then override with controller-specific configuration if present

core/runtime/src/main/java/io/quarkiverse/operatorsdk/runtime/QuarkusConfigurationService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
import io.javaoperatorsdk.operator.api.config.AbstractConfigurationService;
1818
import io.javaoperatorsdk.operator.api.config.Cloner;
1919
import io.javaoperatorsdk.operator.api.config.Version;
20+
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
2021
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
2122
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
23+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
24+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory;
25+
import io.quarkus.arc.Arc;
2226
import io.quarkus.arc.runtime.ClientProxyUnwrapper;
2327

2428
public class QuarkusConfigurationService extends AbstractConfigurationService {
@@ -145,4 +149,14 @@ KubernetesClient getClient() {
145149
boolean shouldStartOperator() {
146150
return startOperator;
147151
}
152+
153+
@Override
154+
public DependentResourceFactory dependentResourceFactory() {
155+
return new DependentResourceFactory() {
156+
@Override
157+
public <T extends DependentResource<?, ?>> T createFrom(DependentResourceSpec<T, ?> spec) {
158+
return Arc.container().instance(spec.getDependentResourceClass()).get();
159+
}
160+
};
161+
}
148162
}

core/runtime/src/main/java/io/quarkiverse/operatorsdk/runtime/QuarkusControllerConfiguration.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import java.util.Collection;
44
import java.util.Collections;
5-
import java.util.List;
5+
import java.util.Map;
66
import java.util.Optional;
77
import java.util.Set;
88

@@ -24,7 +24,7 @@ public class QuarkusControllerConfiguration<R extends HasMetadata> implements Co
2424
private final boolean registrationDelayed;
2525
private final Optional<String> specClassName;
2626
private final Optional<String> statusClassName;
27-
private final List<? extends DependentResourceSpec<?, ?>> dependentResources;
27+
private final Map<String, ? extends DependentResourceSpec<?, ?>> dependentResources;
2828
private final Class<R> resourceClass;
2929
private String finalizer;
3030
private Set<String> namespaces;
@@ -38,9 +38,9 @@ public QuarkusControllerConfiguration(
3838
String resourceTypeName,
3939
String crVersion, boolean generationAware,
4040
Class<R> resourceClass,
41-
boolean registrationDelayed, Set<String> namespaces, String finalizer, String labelSelector,
41+
boolean registrationDelayed, Set<String> namespaces, String finalizerName, String labelSelector,
4242
Optional<String> specClassName, Optional<String> statusClassName,
43-
List<QuarkusDependentResourceSpec<?, ?>> dependentResources) {
43+
Map<String, QuarkusDependentResourceSpec<?, ?>> dependentResources) {
4444
this.associatedReconcilerClassName = associatedReconcilerClassName;
4545
this.name = name;
4646
this.resourceTypeName = resourceTypeName;
@@ -50,7 +50,7 @@ public QuarkusControllerConfiguration(
5050
this.registrationDelayed = registrationDelayed;
5151
this.retryConfiguration = ControllerConfiguration.super.getRetryConfiguration();
5252
setNamespaces(namespaces);
53-
setFinalizer(finalizer);
53+
setFinalizer(finalizerName);
5454
this.labelSelector = labelSelector;
5555
this.specClassName = specClassName;
5656
this.statusClassName = statusClassName;
@@ -87,7 +87,7 @@ public String getCrVersion() {
8787
}
8888

8989
@Override
90-
public String getFinalizer() {
90+
public String getFinalizerName() {
9191
return finalizer;
9292
}
9393

@@ -150,7 +150,7 @@ public Optional<String> getStatusClassName() {
150150

151151
@SuppressWarnings("unchecked")
152152
@Override
153-
public List<DependentResourceSpec<?, ?>> getDependentResources() {
154-
return (List<DependentResourceSpec<?, ?>>) dependentResources;
153+
public Map<String, DependentResourceSpec<?, ?>> getDependentResources() {
154+
return (Map<String, DependentResourceSpec<?, ?>>) dependentResources;
155155
}
156156
}

core/runtime/src/main/java/io/quarkiverse/operatorsdk/runtime/QuarkusDependentResourceSpec.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
public class QuarkusDependentResourceSpec<T extends DependentResource<?, ?>, C> extends DependentResourceSpec<T, C> {
88

99
@RecordableConstructor
10-
public QuarkusDependentResourceSpec(Class<T> dependentResourceClass, C dependentResourceConfig) {
11-
super(dependentResourceClass, dependentResourceConfig);
10+
public QuarkusDependentResourceSpec(Class<T> dependentResourceClass, C dependentResourceConfig, String name) {
11+
super(dependentResourceClass, dependentResourceConfig, name);
1212
}
1313

1414
public C getDependentResourceConfig() {

core/runtime/src/main/java/io/quarkiverse/operatorsdk/runtime/QuarkusKubernetesDependentResourceConfig.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,8 @@
66
public class QuarkusKubernetesDependentResourceConfig extends KubernetesDependentResourceConfig {
77

88
@RecordableConstructor
9-
public QuarkusKubernetesDependentResourceConfig(boolean addOwnerReference,
10-
String[] namespaces, String labelSelector) {
11-
super(addOwnerReference, namespaces, labelSelector);
12-
}
13-
14-
public boolean isAddOwnerReference() {
15-
return addOwnerReference();
9+
public QuarkusKubernetesDependentResourceConfig(String[] namespaces, String labelSelector) {
10+
super(namespaces, labelSelector);
1611
}
1712

1813
public String[] getNamespaces() {

core/runtime/src/main/java/io/quarkiverse/operatorsdk/runtime/RunTimeOperatorConfiguration.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,4 @@ public class RunTimeOperatorConfiguration {
4949
*/
5050
@ConfigItem
5151
public Optional<List<String>> namespaces;
52-
53-
/**
54-
* The optional name of the finalizer to use for controllers. If none is provided, one will be
55-
* automatically generated. It should be noted that having several controllers use the same finalizer might
56-
* create issues and this configuration item is mostly useful when we don't want to use finalizers at all by
57-
* default (using the {@link io.javaoperatorsdk.operator.api.reconciler.Constants#NO_FINALIZER} value). Sets the default
58-
* value for all
59-
* controllers.
60-
*/
61-
@ConfigItem
62-
public Optional<String> finalizer;
6352
}

integration-tests/src/main/java/io/quarkiverse/operatorsdk/it/AbstractReconciler.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package io.quarkiverse.operatorsdk.it;
22

33
import java.util.Collections;
4-
import java.util.List;
4+
import java.util.Map;
55

66
import io.javaoperatorsdk.operator.api.reconciler.Context;
7-
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
87
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
98
import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer;
109
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
@@ -21,15 +20,10 @@ public UpdateControl<T> reconcile(T t, Context<T> context) {
2120
}
2221

2322
@Override
24-
public DeleteControl cleanup(T resource, Context<T> context) {
25-
return RegistrableReconciler.super.cleanup(resource, context);
26-
}
27-
28-
@Override
29-
public List<EventSource> prepareEventSources(EventSourceContext<T> eventSourceContext) {
23+
public Map<String, EventSource> prepareEventSources(EventSourceContext<T> eventSourceContext) {
3024
// this method gets called when the controller gets registered
3125
initialized = true;
32-
return Collections.emptyList();
26+
return Collections.emptyMap();
3327
}
3428

3529
@Override
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package io.quarkiverse.operatorsdk.it;
22

33
import io.fabric8.kubernetes.api.model.ConfigMap;
4-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CrudKubernetesDependentResource;
4+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
55
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
66

77
@KubernetesDependent(labelSelector = CRUDDependentResource.LABEL_SELECTOR)
8-
public class CRUDDependentResource extends CrudKubernetesDependentResource<ConfigMap, ConfigMap> {
8+
public class CRUDDependentResource extends CRUDKubernetesDependentResource<ConfigMap, ConfigMap> {
99

1010
public static final String LABEL_SELECTOR = "environment=production,foo=bar";
11+
12+
public CRUDDependentResource() {
13+
super(ConfigMap.class);
14+
}
1115
}

0 commit comments

Comments
 (0)