diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java
index b5e3fffcf0..7d51c74c5f 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java
@@ -1,10 +1,7 @@
package io.javaoperatorsdk.operator.api.config;
import java.lang.annotation.Annotation;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
-import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -17,21 +14,17 @@
import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
-import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
-import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
-import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
-import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.AnnotationDependentResourceConfigurator;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
import io.javaoperatorsdk.operator.processing.retry.Retry;
@@ -41,10 +34,6 @@
public class AnnotationControllerConfiguration
implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration
{
- private static final String CONTROLLER_CONFIG_ANNOTATION =
- ControllerConfiguration.class.getSimpleName();
- private static final String KUBE_DEPENDENT_NAME = KubernetesDependent.class.getSimpleName();
-
protected final Reconciler
reconciler;
private final ControllerConfiguration annotation;
private List specs;
@@ -152,71 +141,70 @@ public Optional maxReconciliationInterval() {
@Override
public RateLimiter getRateLimiter() {
final Class extends RateLimiter> rateLimiterClass = annotation.rateLimiter();
- return instantiateAndConfigureIfNeeded(rateLimiterClass, RateLimiter.class,
- CONTROLLER_CONFIG_ANNOTATION);
+ return Utils.instantiateAndConfigureIfNeeded(rateLimiterClass, RateLimiter.class,
+ Utils.contextFor(this, null, null), this::configureFromAnnotatedReconciler);
}
@Override
public Retry getRetry() {
final Class extends Retry> retryClass = annotation.retry();
- return instantiateAndConfigureIfNeeded(retryClass, Retry.class, CONTROLLER_CONFIG_ANNOTATION);
+ return Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class,
+ Utils.contextFor(this, null, null), this::configureFromAnnotatedReconciler);
}
+
@SuppressWarnings("unchecked")
- protected T instantiateAndConfigureIfNeeded(Class extends T> targetClass,
- Class expectedType, String context) {
- try {
- final Constructor extends T> constructor = targetClass.getDeclaredConstructor();
- constructor.setAccessible(true);
- final var instance = constructor.newInstance();
- if (instance instanceof AnnotationConfigurable) {
- AnnotationConfigurable configurable = (AnnotationConfigurable) instance;
- final Class extends Annotation> configurationClass =
- (Class extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
- targetClass, AnnotationConfigurable.class);
- final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
- if (configAnnotation != null) {
- configurable.initFrom(configAnnotation);
- }
+ private void configureFromAnnotatedReconciler(Object instance) {
+ if (instance instanceof AnnotationConfigurable) {
+ AnnotationConfigurable configurable = (AnnotationConfigurable) instance;
+ final Class extends Annotation> configurationClass =
+ (Class extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
+ instance.getClass(), AnnotationConfigurable.class);
+ final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
+ if (configAnnotation != null) {
+ configurable.initFrom(configAnnotation);
}
- return instance;
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException
- | NoSuchMethodException e) {
- throw new OperatorException("Couldn't instantiate " + expectedType.getSimpleName() + " '"
- + targetClass.getName() + "' for '" + getName()
- + "' reconciler in " + context
- + ". You need to provide an accessible no-arg constructor.", e);
}
}
- @Override
@SuppressWarnings("unchecked")
- public Optional> onAddFilter() {
- return (Optional>) createFilter(annotation.onAddFilter(), OnAddFilter.class,
- CONTROLLER_CONFIG_ANNOTATION);
+ private void configureFromCustomAnnotation(Object instance) {
+ if (instance instanceof AnnotationDependentResourceConfigurator) {
+ AnnotationDependentResourceConfigurator configurator =
+ (AnnotationDependentResourceConfigurator) instance;
+ final Class extends Annotation> configurationClass =
+ (Class extends Annotation>) Utils.getFirstTypeArgumentFromInterface(
+ instance.getClass(), AnnotationDependentResourceConfigurator.class);
+ final var configAnnotation = instance.getClass().getAnnotation(configurationClass);
+ // always called even if the annotation is null so that implementations can provide default
+ // values
+ final var config = configurator.configFrom(configAnnotation, this);
+ configurator.configureWith(config);
+ }
}
- protected Optional extends T> createFilter(Class extends T> filter, Class defaultValue,
- String origin) {
- if (defaultValue.equals(filter)) {
- return Optional.empty();
- } else {
- return Optional.of(instantiateAndConfigureIfNeeded(filter, defaultValue, origin));
- }
+ @Override
+ @SuppressWarnings("unchecked")
+ public Optional> onAddFilter() {
+ return Optional.ofNullable(
+ Utils.instantiate(annotation.onAddFilter(), OnAddFilter.class,
+ Utils.contextFor(this, null, null)));
}
@SuppressWarnings("unchecked")
@Override
public Optional> onUpdateFilter() {
- return (Optional>) createFilter(annotation.onUpdateFilter(),
- OnUpdateFilter.class, CONTROLLER_CONFIG_ANNOTATION);
+ return Optional.ofNullable(
+ Utils.instantiate(annotation.onUpdateFilter(), OnUpdateFilter.class,
+ Utils.contextFor(this, null, null)));
}
@SuppressWarnings("unchecked")
@Override
public Optional> genericFilter() {
- return (Optional>) createFilter(annotation.genericFilter(),
- GenericFilter.class, CONTROLLER_CONFIG_ANNOTATION);
+ return Optional.ofNullable(
+ Utils.instantiate(annotation.genericFilter(), GenericFilter.class,
+ Utils.contextFor(this, null, null)));
}
@SuppressWarnings({"rawtypes", "unchecked"})
@@ -232,11 +220,7 @@ public List getDependentResources() {
final var specsMap = new LinkedHashMap(dependents.length);
for (Dependent dependent : dependents) {
- Object config = null;
final Class extends DependentResource> dependentType = dependent.type();
- if (KubernetesDependentResource.class.isAssignableFrom(dependentType)) {
- config = createKubernetesResourceConfig(dependentType);
- }
final var name = getName(dependent, dependentType);
var spec = specsMap.get(name);
@@ -244,12 +228,18 @@ public List getDependentResources() {
throw new IllegalArgumentException(
"A DependentResource named '" + name + "' already exists: " + spec);
}
- final var context = "DependentResource of type '" + dependentType.getName() + "'";
- spec = new DependentResourceSpec(dependentType, config, name,
+
+ final var dependentResource = Utils.instantiateAndConfigureIfNeeded(dependentType,
+ DependentResource.class,
+ Utils.contextFor(this, dependentType, Dependent.class),
+ this::configureFromCustomAnnotation);
+
+ final var context = Utils.contextFor(this, dependentType, null);
+ spec = new DependentResourceSpec(dependentResource, name,
Set.of(dependent.dependsOn()),
- instantiateConditionIfNotDefault(dependent.readyPostcondition(), context),
- instantiateConditionIfNotDefault(dependent.reconcilePrecondition(), context),
- instantiateConditionIfNotDefault(dependent.deletePostcondition(), context));
+ Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
+ Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),
+ Utils.instantiate(dependent.deletePostcondition(), Condition.class, context));
specsMap.put(name, spec);
}
@@ -258,14 +248,6 @@ public List getDependentResources() {
return specs;
}
- protected Condition, ?> instantiateConditionIfNotDefault(Class extends Condition> condition,
- String context) {
- if (condition != Condition.class) {
- return instantiateAndConfigureIfNeeded(condition, Condition.class, context);
- }
- return null;
- }
-
private String getName(Dependent dependent, Class extends DependentResource> dependentType) {
var name = dependent.name();
if (name.isBlank()) {
@@ -274,51 +256,6 @@ private String getName(Dependent dependent, Class extends DependentResource> d
return name;
}
- @SuppressWarnings({"rawtypes", "unchecked"})
- private Object createKubernetesResourceConfig(Class extends DependentResource> dependentType) {
-
- Object config;
- final var kubeDependent = dependentType.getAnnotation(KubernetesDependent.class);
-
- var namespaces = getNamespaces();
- var configuredNS = false;
- String labelSelector = null;
- OnAddFilter extends HasMetadata> onAddFilter = null;
- OnUpdateFilter extends HasMetadata> onUpdateFilter = null;
- OnDeleteFilter extends HasMetadata> onDeleteFilter = null;
- GenericFilter extends HasMetadata> genericFilter = null;
- if (kubeDependent != null) {
- if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES,
- kubeDependent.namespaces())) {
- namespaces = Set.of(kubeDependent.namespaces());
- configuredNS = true;
- }
-
- final var fromAnnotation = kubeDependent.labelSelector();
- labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation;
-
-
- final var context =
- KUBE_DEPENDENT_NAME + " annotation on " + dependentType.getName() + " DependentResource";
- onAddFilter = createFilter(kubeDependent.onAddFilter(), OnAddFilter.class, context)
- .orElse(null);
- onUpdateFilter =
- createFilter(kubeDependent.onUpdateFilter(), OnUpdateFilter.class, context)
- .orElse(null);
- onDeleteFilter =
- createFilter(kubeDependent.onDeleteFilter(), OnDeleteFilter.class, context)
- .orElse(null);
- genericFilter =
- createFilter(kubeDependent.genericFilter(), GenericFilter.class, context)
- .orElse(null);
- }
-
- config =
- new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS, onAddFilter,
- onUpdateFilter, onDeleteFilter, genericFilter);
-
- return config;
- }
public static T valueOrDefault(
ControllerConfiguration controllerConfiguration,
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
index a7f50bcf6c..ae175333a3 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
@@ -140,8 +140,9 @@ default ObjectMapper getObjectMapper() {
return Serialization.jsonMapper();
}
+ @Deprecated(forRemoval = true)
default DependentResourceFactory dependentResourceFactory() {
- return new DependentResourceFactory() {};
+ return null;
}
default Optional getLeaderElectionConfiguration() {
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
index e9cae2dccf..524888f3d5 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
@@ -11,6 +11,8 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
@@ -159,6 +161,7 @@ public ControllerConfigurationOverrider withGenericFilter(GenericFilter ge
return this;
}
+ @SuppressWarnings("unchecked")
public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name,
Object dependentResourceConfig) {
@@ -166,35 +169,50 @@ public ControllerConfigurationOverrider replacingNamedDependentResourceConfig
if (current == null) {
throw new IllegalArgumentException("Cannot find a DependentResource named: " + name);
}
- replaceConfig(name, dependentResourceConfig, current);
- return this;
- }
- private void replaceConfig(String name, Object newConfig, DependentResourceSpec, ?> current) {
- namedDependentResourceSpecs.put(name,
- new DependentResourceSpec<>(current.getDependentResourceClass(), newConfig, name,
- current.getDependsOn(), current.getReadyCondition(), current.getReconcileCondition(),
- current.getDeletePostCondition()));
+ var dependentResource = current.getDependentResource();
+ if (dependentResource instanceof DependentResourceConfigurator) {
+ var configurator = (DependentResourceConfigurator) dependentResource;
+ configurator.configureWith(dependentResourceConfig);
+ }
+
+ return this;
}
@SuppressWarnings("unchecked")
public ControllerConfiguration build() {
+ // todo: this should be abstracted by introducing an interface to deal with listening to
+ // namespace changes as possibly other things than the informers might be interested in reacting
+ // to such changes
// propagate namespaces if needed
- final List newDependentSpecs;
+
final var hasModifiedNamespaces = !original.getNamespaces().equals(namespaces);
- newDependentSpecs = namedDependentResourceSpecs.entrySet().stream()
- .map(drsEntry -> {
- final var spec = drsEntry.getValue();
-
- // if the spec has a config and it's a KubernetesDependentResourceConfig, update the
- // namespaces if needed, otherwise, just return the existing spec
- final Optional> maybeConfig = spec.getDependentResourceConfiguration();
- return maybeConfig.filter(KubernetesDependentResourceConfig.class::isInstance)
- .map(KubernetesDependentResourceConfig.class::cast)
- .filter(Predicate.not(KubernetesDependentResourceConfig::wereNamespacesConfigured))
- .map(c -> updateSpec(drsEntry.getKey(), spec, c))
- .orElse(drsEntry.getValue());
- }).collect(Collectors.toUnmodifiableList());
+ final var newDependentSpecs = namedDependentResourceSpecs.values().stream()
+ .map(spec -> {
+ // if the dependent resource has a config and it's a KubernetesDependentResourceConfig,
+ // update
+ // the namespaces if needed, otherwise, do nothing
+ DependentResource dependent = spec.getDependentResource();
+ Optional updated = Optional.empty();
+ if (hasModifiedNamespaces && dependent instanceof DependentResourceConfigurator) {
+ DependentResourceConfigurator configurator = (DependentResourceConfigurator) dependent;
+ final Optional> config = configurator.configuration();
+ updated = config.filter(KubernetesDependentResourceConfig.class::isInstance)
+ .map(KubernetesDependentResourceConfig.class::cast)
+ .filter(Predicate.not(KubernetesDependentResourceConfig::wereNamespacesConfigured))
+ .map(c -> {
+ // update the namespaces of the config, configure the dependent with it and update
+ // the spec
+ c.setNamespaces(namespaces);
+ configurator.configureWith(c);
+ return new DependentResourceSpec(dependent, spec.getName(), spec.getDependsOn(),
+ spec.getReadyCondition(), spec.getReconcileCondition(),
+ spec.getDeletePostCondition());
+ });
+ }
+
+ return updated.orElse(spec);
+ }).collect(Collectors.toList());
return new DefaultControllerConfiguration<>(
original.getAssociatedReconcilerClassName(),
@@ -215,14 +233,6 @@ public ControllerConfiguration build() {
newDependentSpecs);
}
- @SuppressWarnings({"rawtypes", "unchecked"})
- private DependentResourceSpec, ?> updateSpec(String name, DependentResourceSpec spec,
- KubernetesDependentResourceConfig c) {
- return new DependentResourceSpec(spec.getDependentResourceClass(),
- c.setNamespaces(namespaces), name, spec.getDependsOn(), spec.getReadyCondition(),
- spec.getReconcileCondition(), spec.getDeletePostCondition());
- }
-
public static ControllerConfigurationOverrider override(
ControllerConfiguration original) {
return new ControllerConfigurationOverrider<>(original);
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java
index c9f636101a..efcf9d6e3b 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Utils.java
@@ -1,18 +1,23 @@
package io.javaoperatorsdk.operator.api.config;
import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
+import java.util.Optional;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.javaoperatorsdk.operator.OperatorException;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
public class Utils {
@@ -106,17 +111,41 @@ public static Class> getFirstTypeArgumentFromExtendedClass(Class> clazz) {
public static Class> getFirstTypeArgumentFromInterface(Class> clazz,
Class> expectedImplementedInterface) {
- return Arrays.stream(clazz.getGenericInterfaces())
- .filter(type -> type.getTypeName().startsWith(expectedImplementedInterface.getName())
- && type instanceof ParameterizedType)
- .map(ParameterizedType.class::cast)
- .findFirst()
- .map(t -> (Class>) t.getActualTypeArguments()[0])
- .orElseThrow(() -> new RuntimeException(
- "Couldn't retrieve generic parameter type from " + clazz.getSimpleName()
- + " because it doesn't implement "
- + expectedImplementedInterface.getSimpleName()
- + " directly"));
+ if (expectedImplementedInterface.isAssignableFrom(clazz)) {
+ final var genericInterfaces = clazz.getGenericInterfaces();
+ Optional extends Class>> target = Optional.empty();
+ if (genericInterfaces.length > 0) {
+ // try to find the target interface among them
+ target = Arrays.stream(genericInterfaces)
+ .filter(type -> type.getTypeName().startsWith(expectedImplementedInterface.getName())
+ && type instanceof ParameterizedType)
+ .map(ParameterizedType.class::cast)
+ .findFirst()
+ .map(t -> {
+ final Type argument = t.getActualTypeArguments()[0];
+ if (argument instanceof Class) {
+ return (Class>) argument;
+ }
+ throw new IllegalArgumentException(clazz.getSimpleName() + " implements "
+ + expectedImplementedInterface.getSimpleName()
+ + " but indirectly. Java type erasure doesn't allow to retrieve the generic type from it. Retrieved type was: "
+ + argument);
+ });
+ }
+
+ if (target.isPresent()) {
+ return target.get();
+ }
+
+ // try the parent
+ var parent = clazz.getSuperclass();
+ if (!Object.class.equals(parent)) {
+ return getFirstTypeArgumentFromInterface(parent, expectedImplementedInterface);
+ }
+ }
+ throw new IllegalArgumentException("Couldn't retrieve generic parameter type from "
+ + clazz.getSimpleName() + " because it or its superclasses don't implement "
+ + expectedImplementedInterface.getSimpleName());
}
public static Class> getFirstTypeArgumentFromSuperClassOrInterface(Class> clazz,
@@ -144,4 +173,58 @@ public static Class> getFirstTypeArgumentFromSuperClassOrInterface(Class> cl
"Couldn't retrieve generic parameter type from " + clazz.getSimpleName(), e);
}
}
+
+ public static T instantiateAndConfigureIfNeeded(Class extends T> targetClass,
+ Class expectedType, String context, Configurator configurator) {
+ // if class to instantiate equals the expected interface, we cannot instantiate it so just
+ // return null as it means we passed on void-type default value
+ if (expectedType.equals(targetClass)) {
+ return null;
+ }
+
+ try {
+ final Constructor extends T> constructor = targetClass.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ final var instance = constructor.newInstance();
+
+ if (configurator != null) {
+ configurator.configure(instance);
+ }
+
+ return instance;
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException
+ | NoSuchMethodException e) {
+ throw new OperatorException("Couldn't instantiate " + expectedType.getSimpleName() + " '"
+ + targetClass.getName() + "': you need to provide an accessible no-arg constructor."
+ + (context != null ? " Context: " + context : ""), e);
+ }
+ }
+
+ public static T instantiate(Class extends T> toInstantiate, Class expectedType,
+ String context) {
+ return instantiateAndConfigureIfNeeded(toInstantiate, expectedType, context, null);
+ }
+
+ @FunctionalInterface
+ public interface Configurator {
+ void configure(T instance);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static String contextFor(ControllerConfiguration> controllerConfiguration,
+ Class extends DependentResource> dependentType,
+ Class extends Annotation> configurationAnnotation) {
+ final var annotationName =
+ configurationAnnotation != null ? configurationAnnotation.getSimpleName()
+ : io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
+ .getSimpleName();
+ var context = "annotation: " + annotationName + ", ";
+ if (dependentType != null) {
+ context += "DependentResource: " + dependentType.getName() + ", ";
+ }
+ context += "reconciler: " + controllerConfiguration.getName();
+
+
+ return context;
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java
index f146d127d0..2038418995 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java
@@ -4,14 +4,14 @@
import java.util.Optional;
import java.util.Set;
+import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
-public class DependentResourceSpec, C> {
+public class DependentResourceSpec {
- private final Class dependentResourceClass;
-
- private final C dependentResourceConfig;
+ private final DependentResource dependentResource;
private final String name;
@@ -23,11 +23,10 @@ public class DependentResourceSpec, C> {
private final Condition, ?> deletePostCondition;
- public DependentResourceSpec(Class dependentResourceClass, C dependentResourceConfig,
+ public DependentResourceSpec(DependentResource dependentResource,
String name, Set dependsOn, Condition, ?> readyCondition,
Condition, ?> reconcileCondition, Condition, ?> deletePostCondition) {
- this.dependentResourceClass = dependentResourceClass;
- this.dependentResourceConfig = dependentResourceConfig;
+ this.dependentResource = dependentResource;
this.name = name;
this.dependsOn = dependsOn;
this.readyCondition = readyCondition;
@@ -35,12 +34,18 @@ public DependentResourceSpec(Class dependentResourceClass, C dependentResourc
this.deletePostCondition = deletePostCondition;
}
- public Class getDependentResourceClass() {
- return dependentResourceClass;
+ @SuppressWarnings("unchecked")
+ public Class> getDependentResourceClass() {
+ return (Class>) dependentResource.getClass();
}
+ @SuppressWarnings({"unchecked", "rawtypes"})
public Optional getDependentResourceConfiguration() {
- return Optional.ofNullable(dependentResourceConfig);
+ if (dependentResource instanceof DependentResourceConfigurator) {
+ var configurator = (DependentResourceConfigurator) dependentResource;
+ return configurator.configuration();
+ }
+ return Optional.empty();
}
public String getName() {
@@ -50,8 +55,7 @@ public String getName() {
@Override
public String toString() {
return "DependentResourceSpec{ name='" + name +
- "', type=" + dependentResourceClass.getCanonicalName() +
- ", config=" + dependentResourceConfig + '}';
+ "', type=" + getDependentResourceClass().getCanonicalName() + '}';
}
@Override
@@ -62,7 +66,7 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
- DependentResourceSpec, ?> that = (DependentResourceSpec, ?>) o;
+ DependentResourceSpec, ?, ?> that = (DependentResourceSpec, ?, ?>) o;
return name.equals(that.name);
}
@@ -89,4 +93,8 @@ public Condition getReconcileCondition() {
public Condition getDeletePostCondition() {
return deletePostCondition;
}
+
+ public DependentResource getDependentResource() {
+ return dependentResource;
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java
index 11b6e1f059..139d70b002 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java
@@ -1,23 +1,18 @@
package io.javaoperatorsdk.operator.api.reconciler.dependent;
-import java.lang.reflect.InvocationTargetException;
-
+import io.javaoperatorsdk.operator.api.config.Utils;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+@Deprecated
+@SuppressWarnings({"rawtypes", "unchecked"})
public interface DependentResourceFactory {
- default > T createFrom(DependentResourceSpec spec) {
+ default DependentResource createFrom(DependentResourceSpec spec) {
return createFrom(spec.getDependentResourceClass());
}
- default > T createFrom(Class dependentResourceClass) {
- try {
- return dependentResourceClass.getConstructor().newInstance();
- } catch (InstantiationException | NoSuchMethodException | IllegalAccessException
- | InvocationTargetException e) {
- throw new IllegalArgumentException("Cannot instantiate DependentResource "
- + dependentResourceClass.getCanonicalName(), e);
- }
+ default T createFrom(Class dependentResourceClass) {
+ return (T) Utils.instantiate(dependentResourceClass, DependentResource.class, null);
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/AnnotationDependentResourceConfigurator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/AnnotationDependentResourceConfigurator.java
new file mode 100644
index 0000000000..d65249b753
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/AnnotationDependentResourceConfigurator.java
@@ -0,0 +1,11 @@
+package io.javaoperatorsdk.operator.api.reconciler.dependent.managed;
+
+import java.lang.annotation.Annotation;
+
+import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
+
+public interface AnnotationDependentResourceConfigurator
+ extends DependentResourceConfigurator {
+
+ C configFrom(A annotation, ControllerConfiguration> parentConfiguration);
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DependentResourceConfigurator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DependentResourceConfigurator.java
index bbb4f75da9..2b361626aa 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DependentResourceConfigurator.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/DependentResourceConfigurator.java
@@ -1,5 +1,9 @@
package io.javaoperatorsdk.operator.api.reconciler.dependent.managed;
+import java.util.Optional;
+
public interface DependentResourceConfigurator {
void configureWith(C config);
+
+ Optional configuration();
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
index 930e5fd5b4..971b510e80 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
@@ -1,5 +1,6 @@
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Optional;
import java.util.Set;
@@ -13,19 +14,25 @@
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.javaoperatorsdk.operator.OperatorException;
+import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.config.Utils;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.AnnotationDependentResourceConfigurator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.Matcher;
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper;
+import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
+import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers;
@@ -34,7 +41,7 @@
public abstract class KubernetesDependentResource
extends AbstractEventSourceHolderDependentResource>
implements KubernetesClientAware,
- DependentResourceConfigurator {
+ AnnotationDependentResourceConfigurator {
private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class);
@@ -236,4 +243,45 @@ private void cleanupAfterEventFiltering(ResourceID resourceID) {
eventSource().cleanupOnCreateOrUpdateEventFiltering(resourceID);
}
+ @Override
+ @SuppressWarnings("unchecked")
+ public KubernetesDependentResourceConfig configFrom(KubernetesDependent kubeDependent,
+ ControllerConfiguration> parentConfiguration) {
+ var namespaces = parentConfiguration.getNamespaces();
+ var configuredNS = false;
+ String labelSelector = null;
+ OnAddFilter extends HasMetadata> onAddFilter = null;
+ OnUpdateFilter extends HasMetadata> onUpdateFilter = null;
+ OnDeleteFilter extends HasMetadata> onDeleteFilter = null;
+ GenericFilter extends HasMetadata> genericFilter = null;
+ if (kubeDependent != null) {
+ if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES,
+ kubeDependent.namespaces())) {
+ namespaces = Set.of(kubeDependent.namespaces());
+ configuredNS = true;
+ }
+
+ final var fromAnnotation = kubeDependent.labelSelector();
+ labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation;
+
+ final var context =
+ Utils.contextFor(parentConfiguration, getClass(), kubeDependent.annotationType());
+ onAddFilter = Utils.instantiate(kubeDependent.onAddFilter(), OnAddFilter.class, context);
+ onUpdateFilter =
+ Utils.instantiate(kubeDependent.onUpdateFilter(), OnUpdateFilter.class, context);
+ onDeleteFilter =
+ Utils.instantiate(kubeDependent.onDeleteFilter(), OnDeleteFilter.class, context);
+ genericFilter =
+ Utils.instantiate(kubeDependent.genericFilter(), GenericFilter.class, context);
+ }
+
+ return new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS,
+ onAddFilter,
+ onUpdateFilter, onDeleteFilter, genericFilter);
+ }
+
+ @Override
+ public Optional configuration() {
+ return Optional.ofNullable(kubernetesDependentResourceConfig);
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java
index aad9475518..fbeadb31b7 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/ManagedWorkflowSupport.java
@@ -12,10 +12,8 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
import io.javaoperatorsdk.operator.processing.dependent.workflow.builder.WorkflowBuilder;
@@ -72,20 +70,15 @@ public Workflow
createWorkflow(
return workflowBuilder.build();
}
- @SuppressWarnings({"rawtypes", "unchecked"})
+ @SuppressWarnings({"rawtypes"})
public DependentResource createAndConfigureFrom(DependentResourceSpec spec,
KubernetesClient client) {
- final var dependentResource =
- ConfigurationServiceProvider.instance().dependentResourceFactory().createFrom(spec);
+ final var dependentResource = spec.getDependentResource();
if (dependentResource instanceof KubernetesClientAware) {
((KubernetesClientAware) dependentResource).setKubernetesClient(client);
}
- if (dependentResource instanceof DependentResourceConfigurator) {
- final var configurator = (DependentResourceConfigurator) dependentResource;
- spec.getDependentResourceConfiguration().ifPresent(configurator::configureWith);
- }
return dependentResource;
}
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverriderTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverriderTest.java
index 8b973b8159..e46f027d94 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverriderTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverriderTest.java
@@ -9,10 +9,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
-import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
-import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -24,12 +21,6 @@ class ConfigurationServiceOverriderTest {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final LeaderElectionConfiguration LEADER_ELECTION_CONFIGURATION =
new LeaderElectionConfiguration("foo", "fooNS");
- private static final DependentResourceFactory FACTORY = new DependentResourceFactory() {
- @Override
- public > T createFrom(DependentResourceSpec spec) {
- return DependentResourceFactory.super.createFrom(spec);
- }
- };
private static final Cloner CLONER = new Cloner() {
@Override
@@ -86,11 +77,6 @@ public Cloner getResourceCloner() {
return CLONER;
}
- @Override
- public DependentResourceFactory dependentResourceFactory() {
- return FACTORY;
- }
-
@Override
public Optional getLeaderElectionConfiguration() {
return Optional.of(LEADER_ELECTION_CONFIGURATION);
@@ -121,7 +107,6 @@ public R clone(R object) {
overridden.concurrentReconciliationThreads());
assertNotEquals(config.getTerminationTimeoutSeconds(),
overridden.getTerminationTimeoutSeconds());
- assertNotEquals(config.dependentResourceFactory(), overridden.dependentResourceFactory());
assertNotEquals(config.getClientConfiguration(), overridden.getClientConfiguration());
assertNotEquals(config.getExecutorService(), overridden.getExecutorService());
assertNotEquals(config.getMetrics(), overridden.getMetrics());
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java
index 127cd535b1..0f8cf1e2e4 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverriderTest.java
@@ -14,6 +14,7 @@
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
@@ -73,7 +74,10 @@ public NamedDependentResource() {
}
}
- private static class ExternalDependentResource implements DependentResource