Skip to content

Commit d275670

Browse files
committed
feat: @ControllerConfiguration annotation is optional
Signed-off-by: Attila Mészáros <csviri@gmail.com>
1 parent 976a091 commit d275670

File tree

2 files changed

+125
-142
lines changed

2 files changed

+125
-142
lines changed

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

Lines changed: 121 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.fabric8.kubernetes.api.model.HasMetadata;
1515
import io.fabric8.kubernetes.client.KubernetesClient;
1616
import io.fabric8.kubernetes.client.informers.cache.ItemStore;
17-
import io.javaoperatorsdk.operator.OperatorException;
1817
import io.javaoperatorsdk.operator.ReconcilerUtils;
1918
import io.javaoperatorsdk.operator.api.config.Utils.Configurator;
2019
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver;
@@ -33,7 +32,6 @@
3332
import io.javaoperatorsdk.operator.processing.retry.Retry;
3433

3534
import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.CONTROLLER_NAME_AS_FIELD_MANAGER;
36-
import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
3735

3836
public class BaseConfigurationService extends AbstractConfigurationService {
3937

@@ -57,88 +55,6 @@ public BaseConfigurationService() {
5755
this(Utils.VERSION);
5856
}
5957

60-
@SuppressWarnings({"unchecked", "rawtypes"})
61-
private static List<DependentResourceSpec> dependentResources(
62-
Workflow annotation,
63-
ControllerConfiguration<?> controllerConfiguration) {
64-
final var dependents = annotation.dependents();
65-
66-
67-
if (dependents == null || dependents.length == 0) {
68-
return Collections.emptyList();
69-
}
70-
71-
final var specsMap = new LinkedHashMap<String, DependentResourceSpec>(dependents.length);
72-
for (Dependent dependent : dependents) {
73-
final Class<? extends DependentResource> dependentType = dependent.type();
74-
75-
final var dependentName = getName(dependent.name(), dependentType);
76-
var spec = specsMap.get(dependentName);
77-
if (spec != null) {
78-
throw new IllegalArgumentException(
79-
"A DependentResource named '" + dependentName + "' already exists: " + spec);
80-
}
81-
82-
final var name = controllerConfiguration.getName();
83-
84-
var eventSourceName = dependent.useEventSourceWithName();
85-
eventSourceName = Constants.NO_VALUE_SET.equals(eventSourceName) ? null : eventSourceName;
86-
final var context = Utils.contextFor(name, dependentType, null);
87-
spec = new DependentResourceSpec(dependentType, dependentName,
88-
Set.of(dependent.dependsOn()),
89-
Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
90-
Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),
91-
Utils.instantiate(dependent.deletePostcondition(), Condition.class, context),
92-
Utils.instantiate(dependent.activationCondition(), Condition.class, context),
93-
eventSourceName);
94-
95-
// extract potential configuration
96-
DependentResourceConfigurationResolver.configureSpecFromConfigured(spec,
97-
controllerConfiguration,
98-
dependentType);
99-
100-
specsMap.put(dependentName, spec);
101-
}
102-
return specsMap.values().stream().toList();
103-
}
104-
105-
private static <T> T valueOrDefault(
106-
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration,
107-
Function<io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration, T> mapper,
108-
T defaultValue) {
109-
if (controllerConfiguration == null) {
110-
return defaultValue;
111-
} else {
112-
return mapper.apply(controllerConfiguration);
113-
}
114-
}
115-
116-
@SuppressWarnings("rawtypes")
117-
private static String getName(String name, Class<? extends DependentResource> dependentType) {
118-
if (name.isBlank()) {
119-
name = DependentResource.defaultNameFor(dependentType);
120-
}
121-
return name;
122-
}
123-
124-
@SuppressWarnings("unused")
125-
private static <T> Configurator<T> configuratorFor(Class<T> instanceType,
126-
Reconciler<?> reconciler) {
127-
return instance -> configureFromAnnotatedReconciler(instance, reconciler);
128-
}
129-
130-
@SuppressWarnings({"unchecked", "rawtypes"})
131-
private static void configureFromAnnotatedReconciler(Object instance, Reconciler<?> reconciler) {
132-
if (instance instanceof AnnotationConfigurable configurable) {
133-
final Class<? extends Annotation> configurationClass =
134-
(Class<? extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
135-
instance.getClass(), AnnotationConfigurable.class);
136-
final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
137-
if (configAnnotation != null) {
138-
configurable.initFrom(configAnnotation);
139-
}
140-
}
141-
}
14258

14359
@Override
14460
protected void logMissingReconcilerWarning(String reconcilerKey, String reconcilersNameMessage) {
@@ -193,96 +109,119 @@ protected <P extends HasMetadata> ControllerConfiguration<P> configFor(Reconcile
193109
final var annotation = reconciler.getClass().getAnnotation(
194110
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class);
195111

196-
if (annotation == null) {
197-
throw new OperatorException(
198-
"Missing mandatory @"
199-
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
200-
.getSimpleName()
201-
+
202-
" annotation for reconciler: " + reconciler);
112+
ResolvedControllerConfiguration<P> config = controllerConfiguration(reconciler, annotation);
113+
114+
final var workflowAnnotation = reconciler.getClass().getAnnotation(
115+
io.javaoperatorsdk.operator.api.reconciler.Workflow.class);
116+
if (workflowAnnotation != null) {
117+
final var specs = dependentResources(workflowAnnotation, config);
118+
WorkflowSpec workflowSpec = new WorkflowSpec() {
119+
@Override
120+
public List<DependentResourceSpec> getDependentResourceSpecs() {
121+
return specs;
122+
}
123+
124+
@Override
125+
public boolean isExplicitInvocation() {
126+
return workflowAnnotation.explicitInvocation();
127+
}
128+
129+
@Override
130+
public boolean handleExceptionsInReconciler() {
131+
return workflowAnnotation.handleExceptionsInReconciler();
132+
}
133+
134+
};
135+
config.setWorkflowSpec(workflowSpec);
203136
}
137+
138+
return config;
139+
}
140+
141+
@SuppressWarnings({"unchecked"})
142+
private <P extends HasMetadata> ResolvedControllerConfiguration<P> controllerConfiguration(
143+
Reconciler<P> reconciler,
144+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) {
204145
Class<Reconciler<P>> reconcilerClass = (Class<Reconciler<P>>) reconciler.getClass();
205146
final var resourceClass = getResourceClassResolver().getPrimaryResourceClass(reconcilerClass);
206147

207148
final var name = ReconcilerUtils.getNameFor(reconciler);
208-
final var generationAware = valueOrDefault(
149+
final var generationAware = valueOrDefaultFromAnnotation(
209150
annotation,
210151
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::generationAwareEventProcessing,
211-
true);
152+
"generationAwareEventProcessing");
212153
final var associatedReconcilerClass =
213154
ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconciler.getClass());
214155

215156
final var context = Utils.contextFor(name);
216-
final Class<? extends Retry> retryClass = annotation.retry();
157+
final Class<? extends Retry> retryClass =
158+
valueOrDefaultFromAnnotation(annotation,
159+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::retry,
160+
"retry");
217161
final var retry = Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class,
218162
context, configuratorFor(Retry.class, reconciler));
219163

220-
final Class<? extends RateLimiter> rateLimiterClass = annotation.rateLimiter();
164+
165+
final Class<? extends RateLimiter> rateLimiterClass = valueOrDefaultFromAnnotation(annotation,
166+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::rateLimiter,
167+
"rateLimiter");
221168
final var rateLimiter = Utils.instantiateAndConfigureIfNeeded(rateLimiterClass,
222169
RateLimiter.class, context, configuratorFor(RateLimiter.class, reconciler));
223170

224-
final var reconciliationInterval = annotation.maxReconciliationInterval();
171+
final var reconciliationInterval = valueOrDefaultFromAnnotation(annotation,
172+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::maxReconciliationInterval,
173+
"maxReconciliationInterval");
225174
long interval = -1;
226175
TimeUnit timeUnit = null;
227176
if (reconciliationInterval != null && reconciliationInterval.interval() > 0) {
228177
interval = reconciliationInterval.interval();
229178
timeUnit = reconciliationInterval.timeUnit();
230179
}
231180

181+
var fieldManager = valueOrDefaultFromAnnotation(annotation,
182+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::fieldManager,
183+
"fieldManager");
232184
final var dependentFieldManager =
233-
annotation.fieldManager().equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name
234-
: annotation.fieldManager();
185+
fieldManager.equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name
186+
: fieldManager;
235187

188+
var informerListLimitValue = valueOrDefaultFromAnnotation(annotation,
189+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::informerListLimit,
190+
"informerListLimit");
236191
final var informerListLimit =
237-
annotation.informerListLimit() == Constants.NO_LONG_VALUE_SET ? null
238-
: annotation.informerListLimit();
192+
informerListLimitValue == Constants.NO_LONG_VALUE_SET ? null
193+
: informerListLimitValue;
239194

240-
final var config = new ResolvedControllerConfiguration<P>(
195+
return new ResolvedControllerConfiguration<P>(
241196
resourceClass, name, generationAware,
242197
associatedReconcilerClass, retry, rateLimiter,
243198
ResolvedControllerConfiguration.getMaxReconciliationInterval(interval, timeUnit),
244-
Utils.instantiate(annotation.onAddFilter(), OnAddFilter.class, context),
245-
Utils.instantiate(annotation.onUpdateFilter(), OnUpdateFilter.class, context),
246-
Utils.instantiate(annotation.genericFilter(), GenericFilter.class, context),
247-
Set.of(valueOrDefault(annotation,
199+
Utils.instantiate(valueOrDefaultFromAnnotation(annotation,
200+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::onAddFilter,
201+
"onAddFilter"), OnAddFilter.class, context),
202+
Utils.instantiate(valueOrDefaultFromAnnotation(annotation,
203+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::onUpdateFilter,
204+
"onUpdateFilter"), OnUpdateFilter.class, context),
205+
Utils.instantiate(valueOrDefaultFromAnnotation(annotation,
206+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::genericFilter,
207+
"genericFilter"), GenericFilter.class, context),
208+
Set.of(valueOrDefaultFromAnnotation(annotation,
248209
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::namespaces,
249-
DEFAULT_NAMESPACES_SET.toArray(String[]::new))),
250-
valueOrDefault(annotation,
210+
"namespaces")),
211+
valueOrDefaultFromAnnotation(annotation,
251212
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::finalizerName,
252-
Constants.NO_VALUE_SET),
253-
valueOrDefault(annotation,
213+
"finalizerName"),
214+
valueOrDefaultFromAnnotation(annotation,
254215
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::labelSelector,
255-
Constants.NO_VALUE_SET),
216+
"labelSelector"),
256217
null,
257-
Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager,
218+
Utils.instantiate(
219+
valueOrDefaultFromAnnotation(annotation,
220+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::itemStore,
221+
"itemStore"),
222+
ItemStore.class, context),
223+
dependentFieldManager,
258224
this, informerListLimit);
259-
260-
261-
final var workflowAnnotation = reconciler.getClass().getAnnotation(
262-
io.javaoperatorsdk.operator.api.reconciler.Workflow.class);
263-
if (workflowAnnotation != null) {
264-
final var specs = dependentResources(workflowAnnotation, config);
265-
WorkflowSpec workflowSpec = new WorkflowSpec() {
266-
@Override
267-
public List<DependentResourceSpec> getDependentResourceSpecs() {
268-
return specs;
269-
}
270-
271-
@Override
272-
public boolean isExplicitInvocation() {
273-
return workflowAnnotation.explicitInvocation();
274-
}
275-
276-
@Override
277-
public boolean handleExceptionsInReconciler() {
278-
return workflowAnnotation.handleExceptionsInReconciler();
279-
}
280-
281-
};
282-
config.setWorkflowSpec(workflowSpec);
283-
}
284-
285-
return config;
286225
}
287226

288227
protected boolean createIfNeeded() {
@@ -293,4 +232,48 @@ protected boolean createIfNeeded() {
293232
public boolean checkCRDAndValidateLocalModel() {
294233
return Utils.shouldCheckCRDAndValidateLocalModel();
295234
}
235+
236+
@SuppressWarnings("unchecked")
237+
private static <T> T valueOrDefaultFromAnnotation(
238+
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration,
239+
Function<io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration, T> mapper,
240+
String defaultMethodName) {
241+
try {
242+
if (controllerConfiguration == null) {
243+
return (T) io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
244+
.getDeclaredMethod(defaultMethodName).getDefaultValue();
245+
} else {
246+
return mapper.apply(controllerConfiguration);
247+
}
248+
} catch (NoSuchMethodException e) {
249+
throw new RuntimeException(e);
250+
}
251+
}
252+
253+
@SuppressWarnings("rawtypes")
254+
private static String getName(String name, Class<? extends DependentResource> dependentType) {
255+
if (name.isBlank()) {
256+
name = DependentResource.defaultNameFor(dependentType);
257+
}
258+
return name;
259+
}
260+
261+
@SuppressWarnings("unused")
262+
private static <T> Configurator<T> configuratorFor(Class<T> instanceType,
263+
Reconciler<?> reconciler) {
264+
return instance -> configureFromAnnotatedReconciler(instance, reconciler);
265+
}
266+
267+
@SuppressWarnings({"unchecked", "rawtypes"})
268+
private static void configureFromAnnotatedReconciler(Object instance, Reconciler<?> reconciler) {
269+
if (instance instanceof AnnotationConfigurable configurable) {
270+
final Class<? extends Annotation> configurationClass =
271+
(Class<? extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
272+
instance.getClass(), AnnotationConfigurable.class);
273+
final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
274+
if (configAnnotation != null) {
275+
configurable.initFrom(configAnnotation);
276+
}
277+
}
278+
}
296279
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/config/BaseConfigurationServiceTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99
import java.util.Optional;
1010
import java.util.concurrent.TimeUnit;
1111

12-
import org.junit.jupiter.api.Assertions;
1312
import org.junit.jupiter.api.Test;
1413

1514
import io.fabric8.kubernetes.api.model.ConfigMap;
1615
import io.fabric8.kubernetes.api.model.HasMetadata;
17-
import io.javaoperatorsdk.operator.OperatorException;
1816
import io.javaoperatorsdk.operator.api.config.AnnotationConfigurable;
1917
import io.javaoperatorsdk.operator.api.config.BaseConfigurationService;
2018
import io.javaoperatorsdk.operator.api.config.dependent.ConfigurationConverter;
@@ -104,9 +102,11 @@ void getDependentResources() {
104102
}
105103

106104
@Test
107-
void missingAnnotationThrowsException() {
105+
void missingAnnotationCreatesDefaultConfig() {
108106
final var reconciler = new MissingAnnotationReconciler();
109-
Assertions.assertThrows(OperatorException.class, () -> configFor(reconciler));
107+
var config = configFor(reconciler);
108+
// todo asserts
109+
110110
}
111111

112112
@SuppressWarnings("rawtypes")

0 commit comments

Comments
 (0)