Skip to content

Commit 43e5a5d

Browse files
committed
feat: introduce AnnotationDependentResourceConfigurator concept
The idea is to be able to configure dependents using annotations directly so that we can remove the special handling of KubernetesDependentResource from AnnotationControllerConfiguration. This results in dependent resources being instantiated and configured directly when processed from the annotations in the managed case, thus rendering the DependentResourceFactory concept obsolete. This should also lead to further simplification later.
1 parent 899dcf8 commit 43e5a5d

File tree

12 files changed

+178
-137
lines changed

12 files changed

+178
-137
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java

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

33
import java.lang.annotation.Annotation;
44
import java.time.Duration;
5-
import java.util.Arrays;
65
import java.util.Collections;
76
import java.util.LinkedHashMap;
87
import java.util.List;
@@ -15,21 +14,17 @@
1514
import io.javaoperatorsdk.operator.OperatorException;
1615
import io.javaoperatorsdk.operator.ReconcilerUtils;
1716
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
18-
import io.javaoperatorsdk.operator.api.reconciler.Constants;
1917
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
2018
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
2119
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
2220
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
23-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
24-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
25-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
21+
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.AnnotationDependentResourceConfigurator;
2622
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
2723
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
2824
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
2925
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
3026
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
3127
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
32-
import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
3328
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
3429
import io.javaoperatorsdk.operator.processing.retry.Retry;
3530

@@ -159,7 +154,7 @@ public Retry getRetry() {
159154

160155

161156
@SuppressWarnings("unchecked")
162-
private <T> void configureFromAnnotatedReconciler(T instance) {
157+
private void configureFromAnnotatedReconciler(Object instance) {
163158
if (instance instanceof AnnotationConfigurable) {
164159
AnnotationConfigurable configurable = (AnnotationConfigurable) instance;
165160
final Class<? extends Annotation> configurationClass =
@@ -172,6 +167,22 @@ private <T> void configureFromAnnotatedReconciler(T instance) {
172167
}
173168
}
174169

170+
@SuppressWarnings("unchecked")
171+
private void configureFromCustomAnnotation(Object instance) {
172+
if (instance instanceof AnnotationDependentResourceConfigurator) {
173+
AnnotationDependentResourceConfigurator configurator =
174+
(AnnotationDependentResourceConfigurator) instance;
175+
final Class<? extends Annotation> configurationClass =
176+
(Class<? extends Annotation>) Utils.getFirstTypeArgumentFromInterface(
177+
instance.getClass(), AnnotationDependentResourceConfigurator.class);
178+
final var configAnnotation = instance.getClass().getAnnotation(configurationClass);
179+
// always called even if the annotation is null so that implementations can provide default
180+
// values
181+
final var config = configurator.configFrom(configAnnotation, this);
182+
configurator.configureWith(config);
183+
}
184+
}
185+
175186
@Override
176187
@SuppressWarnings("unchecked")
177188
public Optional<OnAddFilter<P>> onAddFilter() {
@@ -209,20 +220,22 @@ public List<DependentResourceSpec> getDependentResources() {
209220

210221
final var specsMap = new LinkedHashMap<String, DependentResourceSpec>(dependents.length);
211222
for (Dependent dependent : dependents) {
212-
Object config = null;
213223
final Class<? extends DependentResource> dependentType = dependent.type();
214-
if (KubernetesDependentResource.class.isAssignableFrom(dependentType)) {
215-
config = createKubernetesResourceConfig(dependentType);
216-
}
217224

218225
final var name = getName(dependent, dependentType);
219226
var spec = specsMap.get(name);
220227
if (spec != null) {
221228
throw new IllegalArgumentException(
222229
"A DependentResource named '" + name + "' already exists: " + spec);
223230
}
231+
232+
final var dependentResource = Utils.instantiateAndConfigureIfNeeded(dependentType,
233+
DependentResource.class,
234+
Utils.contextFor(this, dependentType, Dependent.class),
235+
this::configureFromCustomAnnotation);
236+
224237
final var context = Utils.contextFor(this, dependentType, null);
225-
spec = new DependentResourceSpec(dependentType, config, name,
238+
spec = new DependentResourceSpec(dependentResource, name,
226239
Set.of(dependent.dependsOn()),
227240
Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
228241
Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),
@@ -243,47 +256,6 @@ private String getName(Dependent dependent, Class<? extends DependentResource> d
243256
return name;
244257
}
245258

246-
@SuppressWarnings({"rawtypes", "unchecked"})
247-
private Object createKubernetesResourceConfig(Class<? extends DependentResource> dependentType) {
248-
249-
Object config;
250-
final var kubeDependent = dependentType.getAnnotation(KubernetesDependent.class);
251-
252-
var namespaces = getNamespaces();
253-
var configuredNS = false;
254-
String labelSelector = null;
255-
OnAddFilter<? extends HasMetadata> onAddFilter = null;
256-
OnUpdateFilter<? extends HasMetadata> onUpdateFilter = null;
257-
OnDeleteFilter<? extends HasMetadata> onDeleteFilter = null;
258-
GenericFilter<? extends HasMetadata> genericFilter = null;
259-
if (kubeDependent != null) {
260-
if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES,
261-
kubeDependent.namespaces())) {
262-
namespaces = Set.of(kubeDependent.namespaces());
263-
configuredNS = true;
264-
}
265-
266-
final var fromAnnotation = kubeDependent.labelSelector();
267-
labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation;
268-
269-
270-
final var context =
271-
Utils.contextFor(this, dependentType, null);
272-
onAddFilter = Utils.instantiate(kubeDependent.onAddFilter(), OnAddFilter.class, context);
273-
onUpdateFilter =
274-
Utils.instantiate(kubeDependent.onUpdateFilter(), OnUpdateFilter.class, context);
275-
onDeleteFilter =
276-
Utils.instantiate(kubeDependent.onDeleteFilter(), OnDeleteFilter.class, context);
277-
genericFilter =
278-
Utils.instantiate(kubeDependent.genericFilter(), GenericFilter.class, context);
279-
}
280-
281-
config =
282-
new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, onAddFilter,
283-
onUpdateFilter, onDeleteFilter, genericFilter);
284-
285-
return config;
286-
}
287259

288260
public static <T> T valueOrDefault(
289261
ControllerConfiguration controllerConfiguration,

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ default ObjectMapper getObjectMapper() {
140140
return Serialization.jsonMapper();
141141
}
142142

143+
@Deprecated(forRemoval = true)
143144
default DependentResourceFactory dependentResourceFactory() {
144-
return new DependentResourceFactory() {};
145+
return null;
145146
}
146147

147148
default Optional<LeaderElectionConfiguration> getLeaderElectionConfiguration() {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import io.fabric8.kubernetes.api.model.HasMetadata;
1313
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
14+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
15+
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
1416
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
1517
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
1618
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
@@ -159,42 +161,58 @@ public ControllerConfigurationOverrider<R> withGenericFilter(GenericFilter<R> ge
159161
return this;
160162
}
161163

164+
@SuppressWarnings("unchecked")
162165
public ControllerConfigurationOverrider<R> replacingNamedDependentResourceConfig(String name,
163166
Object dependentResourceConfig) {
164167

165168
var current = namedDependentResourceSpecs.get(name);
166169
if (current == null) {
167170
throw new IllegalArgumentException("Cannot find a DependentResource named: " + name);
168171
}
169-
replaceConfig(name, dependentResourceConfig, current);
170-
return this;
171-
}
172172

173-
private void replaceConfig(String name, Object newConfig, DependentResourceSpec<?, ?> current) {
174-
namedDependentResourceSpecs.put(name,
175-
new DependentResourceSpec<>(current.getDependentResourceClass(), newConfig, name,
176-
current.getDependsOn(), current.getReadyCondition(), current.getReconcileCondition(),
177-
current.getDeletePostCondition()));
173+
var dependentResource = current.getDependentResource();
174+
if (dependentResource instanceof DependentResourceConfigurator) {
175+
var configurator = (DependentResourceConfigurator) dependentResource;
176+
configurator.configureWith(dependentResourceConfig);
177+
}
178+
179+
return this;
178180
}
179181

180182
@SuppressWarnings("unchecked")
181183
public ControllerConfiguration<R> build() {
184+
// todo: this should be abstracted by introducing an interface to deal with listening to
185+
// namespace changes as possibly other things than the informers might be interested in reacting
186+
// to such changes
182187
// propagate namespaces if needed
183-
final List<DependentResourceSpec> newDependentSpecs;
188+
184189
final var hasModifiedNamespaces = !original.getNamespaces().equals(namespaces);
185-
newDependentSpecs = namedDependentResourceSpecs.entrySet().stream()
186-
.map(drsEntry -> {
187-
final var spec = drsEntry.getValue();
188-
189-
// if the spec has a config and it's a KubernetesDependentResourceConfig, update the
190-
// namespaces if needed, otherwise, just return the existing spec
191-
final Optional<?> maybeConfig = spec.getDependentResourceConfiguration();
192-
return maybeConfig.filter(KubernetesDependentResourceConfig.class::isInstance)
193-
.map(KubernetesDependentResourceConfig.class::cast)
194-
.filter(Predicate.not(KubernetesDependentResourceConfig::wereNamespacesConfigured))
195-
.map(c -> updateSpec(drsEntry.getKey(), spec, c))
196-
.orElse(drsEntry.getValue());
197-
}).collect(Collectors.toUnmodifiableList());
190+
final var newDependentSpecs = namedDependentResourceSpecs.values().stream()
191+
.map(spec -> {
192+
// if the dependent resource has a config and it's a KubernetesDependentResourceConfig,
193+
// update
194+
// the namespaces if needed, otherwise, do nothing
195+
DependentResource dependent = spec.getDependentResource();
196+
Optional<DependentResourceSpec> updated = Optional.empty();
197+
if (hasModifiedNamespaces && dependent instanceof DependentResourceConfigurator) {
198+
DependentResourceConfigurator configurator = (DependentResourceConfigurator) dependent;
199+
final Optional<?> config = configurator.configuration();
200+
updated = config.filter(KubernetesDependentResourceConfig.class::isInstance)
201+
.map(KubernetesDependentResourceConfig.class::cast)
202+
.filter(Predicate.not(KubernetesDependentResourceConfig::wereNamespacesConfigured))
203+
.map(c -> {
204+
// update the namespaces of the config, configure the dependent with it and update
205+
// the spec
206+
c.setNamespaces(namespaces);
207+
configurator.configureWith(c);
208+
return new DependentResourceSpec(dependent, spec.getName(), spec.getDependsOn(),
209+
spec.getReadyCondition(), spec.getReconcileCondition(),
210+
spec.getDeletePostCondition());
211+
});
212+
}
213+
214+
return updated.orElse(spec);
215+
}).collect(Collectors.toList());
198216

199217
return new DefaultControllerConfiguration<>(
200218
original.getAssociatedReconcilerClassName(),
@@ -215,14 +233,6 @@ public ControllerConfiguration<R> build() {
215233
newDependentSpecs);
216234
}
217235

218-
@SuppressWarnings({"rawtypes", "unchecked"})
219-
private DependentResourceSpec<?, ?> updateSpec(String name, DependentResourceSpec spec,
220-
KubernetesDependentResourceConfig c) {
221-
return new DependentResourceSpec(spec.getDependentResourceClass(),
222-
c.setNamespaces(namespaces), name, spec.getDependsOn(), spec.getReadyCondition(),
223-
spec.getReconcileCondition(), spec.getDeletePostCondition());
224-
}
225-
226236
public static <R extends HasMetadata> ControllerConfigurationOverrider<R> override(
227237
ControllerConfiguration<R> original) {
228238
return new ControllerConfigurationOverrider<>(original);

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import java.util.Optional;
55
import java.util.Set;
66

7+
import io.fabric8.kubernetes.api.model.HasMetadata;
78
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
9+
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
810
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
911

10-
public class DependentResourceSpec<T extends DependentResource<?, ?>, C> {
12+
public class DependentResourceSpec<R, P extends HasMetadata, C> {
1113

12-
private final Class<T> dependentResourceClass;
13-
14-
private final C dependentResourceConfig;
14+
private final DependentResource<R, P> dependentResource;
1515

1616
private final String name;
1717

@@ -23,24 +23,29 @@ public class DependentResourceSpec<T extends DependentResource<?, ?>, C> {
2323

2424
private final Condition<?, ?> deletePostCondition;
2525

26-
public DependentResourceSpec(Class<T> dependentResourceClass, C dependentResourceConfig,
26+
public DependentResourceSpec(DependentResource<R, P> dependentResource,
2727
String name, Set<String> dependsOn, Condition<?, ?> readyCondition,
2828
Condition<?, ?> reconcileCondition, Condition<?, ?> deletePostCondition) {
29-
this.dependentResourceClass = dependentResourceClass;
30-
this.dependentResourceConfig = dependentResourceConfig;
29+
this.dependentResource = dependentResource;
3130
this.name = name;
3231
this.dependsOn = dependsOn;
3332
this.readyCondition = readyCondition;
3433
this.reconcileCondition = reconcileCondition;
3534
this.deletePostCondition = deletePostCondition;
3635
}
3736

38-
public Class<T> getDependentResourceClass() {
39-
return dependentResourceClass;
37+
@SuppressWarnings("unchecked")
38+
public Class<DependentResource<R, P>> getDependentResourceClass() {
39+
return (Class<DependentResource<R, P>>) dependentResource.getClass();
4040
}
4141

42+
@SuppressWarnings({"unchecked", "rawtypes"})
4243
public Optional<C> getDependentResourceConfiguration() {
43-
return Optional.ofNullable(dependentResourceConfig);
44+
if (dependentResource instanceof DependentResourceConfigurator) {
45+
var configurator = (DependentResourceConfigurator) dependentResource;
46+
return configurator.configuration();
47+
}
48+
return Optional.empty();
4449
}
4550

4651
public String getName() {
@@ -50,8 +55,7 @@ public String getName() {
5055
@Override
5156
public String toString() {
5257
return "DependentResourceSpec{ name='" + name +
53-
"', type=" + dependentResourceClass.getCanonicalName() +
54-
", config=" + dependentResourceConfig + '}';
58+
"', type=" + getDependentResourceClass().getCanonicalName() + '}';
5559
}
5660

5761
@Override
@@ -62,7 +66,7 @@ public boolean equals(Object o) {
6266
if (o == null || getClass() != o.getClass()) {
6367
return false;
6468
}
65-
DependentResourceSpec<?, ?> that = (DependentResourceSpec<?, ?>) o;
69+
DependentResourceSpec<?, ?, ?> that = (DependentResourceSpec<?, ?, ?>) o;
6670
return name.equals(that.name);
6771
}
6872

@@ -89,4 +93,8 @@ public Condition getReconcileCondition() {
8993
public Condition getDeletePostCondition() {
9094
return deletePostCondition;
9195
}
96+
97+
public DependentResource<R, P> getDependentResource() {
98+
return dependentResource;
99+
}
92100
}
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
package io.javaoperatorsdk.operator.api.reconciler.dependent;
22

3-
import java.lang.reflect.InvocationTargetException;
4-
3+
import io.javaoperatorsdk.operator.api.config.Utils;
54
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
65

6+
@Deprecated
7+
@SuppressWarnings({"rawtypes", "unchecked"})
78
public interface DependentResourceFactory {
89

9-
default <T extends DependentResource<?, ?>> T createFrom(DependentResourceSpec<T, ?> spec) {
10+
default DependentResource createFrom(DependentResourceSpec spec) {
1011
return createFrom(spec.getDependentResourceClass());
1112
}
1213

13-
default <T extends DependentResource<?, ?>> T createFrom(Class<T> dependentResourceClass) {
14-
try {
15-
return dependentResourceClass.getConstructor().newInstance();
16-
} catch (InstantiationException | NoSuchMethodException | IllegalAccessException
17-
| InvocationTargetException e) {
18-
throw new IllegalArgumentException("Cannot instantiate DependentResource "
19-
+ dependentResourceClass.getCanonicalName(), e);
20-
}
14+
default <T extends DependentResource> T createFrom(Class<T> dependentResourceClass) {
15+
return (T) Utils.instantiate(dependentResourceClass, DependentResource.class, null);
2116
}
2217

2318
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.javaoperatorsdk.operator.api.reconciler.dependent.managed;
2+
3+
import java.lang.annotation.Annotation;
4+
5+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
6+
7+
public interface AnnotationDependentResourceConfigurator<A extends Annotation, C>
8+
extends DependentResourceConfigurator<C> {
9+
10+
C configFrom(A annotation, ControllerConfiguration<?> parentConfiguration);
11+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package io.javaoperatorsdk.operator.api.reconciler.dependent.managed;
22

3+
import java.util.Optional;
4+
35
public interface DependentResourceConfigurator<C> {
46
void configureWith(C config);
7+
8+
Optional<C> configuration();
59
}

0 commit comments

Comments
 (0)