Skip to content

Commit 808228f

Browse files
authored
feat: add non-apt-tied behavior annotation and configuration to core (#1013)
* feat: add non-apt-tied behavior annotation and configuration to core Fixes #1012 * fix: extract resource class from associated reconciler
1 parent d81db18 commit 808228f

File tree

4 files changed

+251
-194
lines changed

4 files changed

+251
-194
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import java.time.Duration;
4+
import java.util.ArrayList;
5+
import java.util.Collections;
6+
import java.util.List;
7+
import java.util.Optional;
8+
import java.util.Set;
9+
import java.util.function.Function;
10+
11+
import io.fabric8.kubernetes.api.model.HasMetadata;
12+
import io.javaoperatorsdk.operator.ReconcilerUtils;
13+
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
14+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
15+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
16+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
17+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
18+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
19+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
20+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
21+
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
22+
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
23+
24+
@SuppressWarnings("rawtypes")
25+
public class AnnotationControllerConfiguration<R extends HasMetadata>
26+
implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration<R> {
27+
28+
protected final Reconciler<R> reconciler;
29+
private final ControllerConfiguration annotation;
30+
private ConfigurationService service;
31+
private List<DependentResourceSpec> specs;
32+
33+
public AnnotationControllerConfiguration(Reconciler<R> reconciler) {
34+
this.reconciler = reconciler;
35+
this.annotation = reconciler.getClass().getAnnotation(ControllerConfiguration.class);
36+
}
37+
38+
@Override
39+
public String getName() {
40+
return ReconcilerUtils.getNameFor(reconciler);
41+
}
42+
43+
@Override
44+
public String getFinalizer() {
45+
if (annotation == null || annotation.finalizerName().isBlank()) {
46+
return ReconcilerUtils.getDefaultFinalizerName(getResourceClass());
47+
} else {
48+
final var finalizer = annotation.finalizerName();
49+
if (ReconcilerUtils.isFinalizerValid(finalizer)) {
50+
return finalizer;
51+
} else {
52+
throw new IllegalArgumentException(
53+
finalizer
54+
+ " is not a valid finalizer. See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers for details");
55+
}
56+
}
57+
}
58+
59+
@Override
60+
public boolean isGenerationAware() {
61+
return valueOrDefault(
62+
annotation, ControllerConfiguration::generationAwareEventProcessing, true);
63+
}
64+
65+
@Override
66+
public Set<String> getNamespaces() {
67+
return Set.of(valueOrDefault(annotation, ControllerConfiguration::namespaces, new String[] {}));
68+
}
69+
70+
@Override
71+
@SuppressWarnings("unchecked")
72+
public Class<R> getResourceClass() {
73+
return (Class<R>) Utils.getFirstTypeArgumentFromInterface(reconciler.getClass());
74+
}
75+
76+
@Override
77+
public String getLabelSelector() {
78+
return valueOrDefault(annotation, ControllerConfiguration::labelSelector, "");
79+
}
80+
81+
@Override
82+
public ConfigurationService getConfigurationService() {
83+
return service;
84+
}
85+
86+
@Override
87+
public void setConfigurationService(ConfigurationService service) {
88+
this.service = service;
89+
}
90+
91+
@Override
92+
public String getAssociatedReconcilerClassName() {
93+
return reconciler.getClass().getCanonicalName();
94+
}
95+
96+
@SuppressWarnings("unchecked")
97+
@Override
98+
public ResourceEventFilter<R> getEventFilter() {
99+
ResourceEventFilter<R> answer = null;
100+
101+
Class<ResourceEventFilter<R>>[] filterTypes =
102+
(Class<ResourceEventFilter<R>>[]) valueOrDefault(annotation,
103+
ControllerConfiguration::eventFilters, new Object[] {});
104+
if (filterTypes.length > 0) {
105+
for (var filterType : filterTypes) {
106+
try {
107+
ResourceEventFilter<R> filter = filterType.getConstructor().newInstance();
108+
109+
if (answer == null) {
110+
answer = filter;
111+
} else {
112+
answer = answer.and(filter);
113+
}
114+
} catch (Exception e) {
115+
throw new IllegalArgumentException(e);
116+
}
117+
}
118+
}
119+
return answer != null ? answer : ResourceEventFilters.passthrough();
120+
}
121+
122+
@Override
123+
public Optional<Duration> reconciliationMaxInterval() {
124+
if (annotation.reconciliationMaxInterval() != null) {
125+
if (annotation.reconciliationMaxInterval().interval() <= 0) {
126+
return Optional.empty();
127+
}
128+
return Optional.of(
129+
Duration.of(
130+
annotation.reconciliationMaxInterval().interval(),
131+
annotation.reconciliationMaxInterval().timeUnit().toChronoUnit()));
132+
} else {
133+
return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval();
134+
}
135+
}
136+
137+
public static <T> T valueOrDefault(
138+
ControllerConfiguration controllerConfiguration,
139+
Function<ControllerConfiguration, T> mapper,
140+
T defaultValue) {
141+
if (controllerConfiguration == null) {
142+
return defaultValue;
143+
} else {
144+
return mapper.apply(controllerConfiguration);
145+
}
146+
}
147+
148+
@SuppressWarnings({"rawtypes", "unchecked"})
149+
@Override
150+
public List<DependentResourceSpec> getDependentResources() {
151+
if (specs == null) {
152+
final var dependents =
153+
valueOrDefault(annotation, ControllerConfiguration::dependents, new Dependent[] {});
154+
if (dependents.length == 0) {
155+
return Collections.emptyList();
156+
}
157+
158+
specs = new ArrayList<>(dependents.length);
159+
for (Dependent dependent : dependents) {
160+
final Class<? extends DependentResource> dependentType = dependent.type();
161+
if (KubernetesDependentResource.class.isAssignableFrom(dependentType)) {
162+
final var kubeDependent = dependentType.getAnnotation(KubernetesDependent.class);
163+
final var namespaces =
164+
Utils.valueOrDefault(
165+
kubeDependent,
166+
KubernetesDependent::namespaces,
167+
this.getNamespaces().toArray(new String[0]));
168+
final var labelSelector =
169+
Utils.valueOrDefault(kubeDependent, KubernetesDependent::labelSelector, null);
170+
final var addOwnerReference =
171+
Utils.valueOrDefault(
172+
kubeDependent,
173+
KubernetesDependent::addOwnerReference,
174+
KubernetesDependent.ADD_OWNER_REFERENCE_DEFAULT);
175+
KubernetesDependentResourceConfig config =
176+
new KubernetesDependentResourceConfig(
177+
addOwnerReference, namespaces, labelSelector, getConfigurationService());
178+
specs.add(new DependentResourceSpec(dependentType, config));
179+
} else {
180+
specs.add(new DependentResourceSpec(dependentType));
181+
}
182+
}
183+
}
184+
return specs;
185+
}
186+
}

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
55

6+
import io.fabric8.kubernetes.api.model.HasMetadata;
7+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
8+
69
public class BaseConfigurationService extends AbstractConfigurationService {
710

811
private static final String LOGGER_NAME = "Default ConfigurationService implementation";
@@ -12,6 +15,10 @@ public BaseConfigurationService(Version version) {
1215
super(version);
1316
}
1417

18+
public BaseConfigurationService() {
19+
this(Utils.loadFromProperties());
20+
}
21+
1522
@Override
1623
protected void logMissingReconcilerWarning(String reconcilerKey, String reconcilersNameMessage) {
1724
logger.warn("Configuration for reconciler '{}' was not found. {}", reconcilerKey,
@@ -25,4 +32,41 @@ public String getLoggerName() {
2532
protected Logger getLogger() {
2633
return logger;
2734
}
35+
36+
@Override
37+
public <R extends HasMetadata> ControllerConfiguration<R> getConfigurationFor(
38+
Reconciler<R> reconciler) {
39+
var config = super.getConfigurationFor(reconciler);
40+
if (config == null) {
41+
if (createIfNeeded()) {
42+
// create the configuration on demand and register it
43+
config = configFor(reconciler);
44+
register(config);
45+
getLogger().info(
46+
"Created configuration for reconciler {} with name {}",
47+
reconciler.getClass().getName(),
48+
config.getName());
49+
}
50+
} else {
51+
// check that we don't have a reconciler name collision
52+
final var newControllerClassName = reconciler.getClass().getCanonicalName();
53+
if (!config.getAssociatedReconcilerClassName().equals(newControllerClassName)) {
54+
throwExceptionOnNameCollision(newControllerClassName, config);
55+
}
56+
}
57+
return config;
58+
}
59+
60+
protected <R extends HasMetadata> ControllerConfiguration<R> configFor(Reconciler<R> reconciler) {
61+
return new AnnotationControllerConfiguration<>(reconciler);
62+
}
63+
64+
protected boolean createIfNeeded() {
65+
return true;
66+
}
67+
68+
@Override
69+
public boolean checkCRDAndValidateLocalModel() {
70+
return Utils.shouldCheckCRDAndValidateLocalModel();
71+
}
2872
}

0 commit comments

Comments
 (0)