context) {
+ throw new IllegalStateException("Must be implemented if not automatically provided by the SDK");
+ };
+
+ default Class {
+ void initContext(P primary, Context context);
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
index fad4bc8573..5870820f0f 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
@@ -5,6 +5,8 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import io.javaoperatorsdk.operator.api.config.Dependent;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceController;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
@Retention(RetentionPolicy.RUNTIME)
@@ -56,4 +58,14 @@
*/
@SuppressWarnings("rawtypes")
Class extends ResourceEventFilter>[] eventFilters() default {};
+
+ /**
+ * Optional list of classes providing {@link DependentResourceController} implementations
+ * encapsulating logic to handle the associated
+ * {@link io.javaoperatorsdk.operator.processing.Controller}'s reconciliation of dependent
+ * resources
+ *
+ * @return the list of {@link DependentResourceController} implementations
+ */
+ Dependent[] dependents() default {};
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
index 3d924c2753..c2553a5d57 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java
@@ -5,7 +5,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.processing.Controller;
-public class DefaultContext implements Context {
+public class DefaultContext extends MapAttributeHolder implements Context {
private final RetryInfo retryInfo;
private final Controller controller;
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java
index 0a93d33d40..026af88923 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContext.java
@@ -11,7 +11,7 @@
*
* @param the type associated with the primary resource that is handled by your reconciler
*/
-public class EventSourceContext {
+public class EventSourceContext extends MapAttributeHolder {
private final ResourceCache primaryCache;
private final ConfigurationService configurationService;
@@ -49,7 +49,7 @@ public ConfigurationService getConfigurationService() {
/**
* Provides access to the {@link KubernetesClient} used by the current
* {@link io.javaoperatorsdk.operator.Operator} instance.
- *
+ *
* @return the {@link KubernetesClient} used by the current
* {@link io.javaoperatorsdk.operator.Operator} instance.
*/
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContextInjector.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContextInjector.java
new file mode 100644
index 0000000000..af57fe1b32
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContextInjector.java
@@ -0,0 +1,5 @@
+package io.javaoperatorsdk.operator.api.reconciler;
+
+public interface EventSourceContextInjector {
+ void injectInto(EventSourceContext context);
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/MapAttributeHolder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/MapAttributeHolder.java
new file mode 100644
index 0000000000..aee5ee7e54
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/MapAttributeHolder.java
@@ -0,0 +1,28 @@
+package io.javaoperatorsdk.operator.api.reconciler;
+
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MapAttributeHolder {
+
+ private final ConcurrentHashMap attributes = new ConcurrentHashMap();
+
+ public context) {
+ return delegate.initEventSource(context);
+ }
+
+ public boolean creatable() {
+ return builder != null;
+ }
+
+ public boolean updatable() {
+ return updater != null;
+ }
+
+ public boolean deletable() {
+ return cleaner != null;
+ }
+
+ @Override
+ public void createOrReplace(R dependentResource, Context context) {
+ persister.createOrReplace(dependentResource, context);
+ }
+
+ @Override
+ public R getFor(P primary, Context context) {
+ return persister.getFor(primary, context);
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceControllerFactory.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceControllerFactory.java
new file mode 100644
index 0000000000..f814cd7a1a
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceControllerFactory.java
@@ -0,0 +1,14 @@
+package io.javaoperatorsdk.operator.api.reconciler.dependent;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.config.DependentResource;
+
+public interface DependentResourceControllerFactory {
+
+ default ) delegate
+ : configuration.getAssociatedResourceIdentifier();
+
+ this.configuration = InformerConfiguration.from(configuration)
+ .withPrimaryResourcesRetriever(associatedPrimaries)
+ .withAssociatedSecondaryResourceIdentifier(associatedSecondary)
+ .build();
+ this.owned = owned;
+ }
+
+ @Override
+ protected Persister context) {
+ this.client = context.getClient();
+ informer = new InformerEventSource<>(configuration, context);
+ return informer;
+ }
+
+ @Override
+ public void createOrReplace(R dependentResource, Context context) {
+ client.resource(dependentResource).createOrReplace();
+ }
+
+ @Override
+ public R getFor(P primary, Context context) {
+ return informer.getAssociated(primary).orElse(null);
+ }
+
+ public boolean owned() {
+ return owned;
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Persister.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Persister.java
new file mode 100644
index 0000000000..ab34372917
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Persister.java
@@ -0,0 +1,11 @@
+package io.javaoperatorsdk.operator.api.reconciler.dependent;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+
+public interface Persister {
+ void initWith(EventSourceContext context);
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java
index 9feabf40bf..2a224a87df 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java
@@ -1,34 +1,27 @@
package io.javaoperatorsdk.operator.processing.event.source.controller;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.client.KubernetesClientException;
-import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
-import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
import io.javaoperatorsdk.operator.MissingCRDException;
-import io.javaoperatorsdk.operator.api.config.ConfigurationService;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.processing.Controller;
import io.javaoperatorsdk.operator.processing.MDCUtils;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
-import io.javaoperatorsdk.operator.processing.event.source.AbstractResourceEventSource;
+import io.javaoperatorsdk.operator.processing.event.source.ResourceCache;
+import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource;
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getName;
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID;
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion;
public class ControllerResourceEventSource associatedWith;
+ private final boolean skipUpdateEventPropagationIfNoChange;
+
+ private InformerConfiguration(ConfigurationService service, String labelSelector,
+ Class associatedWith,
+ boolean skipUpdateEventPropagationIfNoChange, Set getAssociatedResourceIdentifier() {
+ return associatedWith;
+ }
+
+ public boolean isSkipUpdateEventPropagationIfNoChange() {
+ return skipUpdateEventPropagationIfNoChange;
+ }
+
+ public static class InformerConfigurationBuilder associatedWith;
+ private boolean skipUpdateEventPropagationIfNoChange = true;
+ private Set associatedWith) {
+ this.associatedWith = associatedWith;
+ return this;
+ }
+
+ public InformerConfigurationBuilder context, Class
- implements ResourceCache associatedWith;
- private final boolean skipUpdateEventPropagationIfNoChange;
+ private final InformerConfiguration context) {
+ super(context.getClient().resources(configuration.getResourceClass()), configuration);
+ this.configuration = configuration;
- public InformerEventSource(KubernetesClient client, Class associatedWith,
- boolean skipUpdateEventPropagationIfNoChange) {
- this(client.informers().sharedIndexInformerFor(type, 0), resourceToTargetResourceIDSet,
- associatedWith,
- skipUpdateEventPropagationIfNoChange);
+ final var associatedResourceIdentifier = configuration.getAssociatedResourceIdentifier();
+ if (associatedResourceIdentifier instanceof EventSourceContextAware) {
+ ((EventSourceContextAware) associatedResourceIdentifier).initWith(context);
+ }
}
- InformerEventSource(KubernetesClient client, Class associatedWith,
- boolean skipUpdateEventPropagationIfNoChange) {
- super(sharedInformer.getApiTypeClass());
- this.sharedInformer = sharedInformer;
- this.secondaryToPrimaryResourcesIdSet = resourceToTargetResourceIDSet;
- this.skipUpdateEventPropagationIfNoChange = skipUpdateEventPropagationIfNoChange;
- if (sharedInformer.isRunning()) {
- log.warn(
- "Informer is already running on event source creation, this is not desirable and may " +
- "lead to non deterministic behavior.");
+ @Override
+ public void onUpdate(T oldObject, T newObject) {
+ if (newObject == null) {
+ // this is a fix for this potential issue with informer:
+ // https://github.com/java-operator-sdk/java-operator-sdk/issues/830
+ propagateEvent(oldObject);
+ return;
}
- this.associatedWith =
- Objects.requireNonNullElseGet(associatedWith, () -> ResourceID::fromResource);
-
- sharedInformer.addEventHandler(new ResourceEventHandler<>() {
- @Override
- public void onAdd(T t) {
- propagateEvent(t);
- }
-
- @Override
- public void onUpdate(T oldObject, T newObject) {
- if (newObject == null) {
- // this is a fix for this potential issue with informer:
- // https://github.com/java-operator-sdk/java-operator-sdk/issues/830
- propagateEvent(oldObject);
- return;
- }
-
- if (InformerEventSource.this.skipUpdateEventPropagationIfNoChange &&
- oldObject.getMetadata().getResourceVersion()
- .equals(newObject.getMetadata().getResourceVersion())) {
- return;
- }
- propagateEvent(newObject);
- }
+ if (configuration.isSkipUpdateEventPropagationIfNoChange() &&
+ oldObject.getMetadata().getResourceVersion()
+ .equals(newObject.getMetadata().getResourceVersion())) {
+ return;
+ }
+ propagateEvent(newObject);
+ }
- @Override
- public void onDelete(T t, boolean b) {
- propagateEvent(t);
- }
- });
+ @Override
+ public void onDelete(T t, boolean b) {
+ propagateEvent(t);
}
private void propagateEvent(T object) {
- var primaryResourceIdSet = secondaryToPrimaryResourcesIdSet.associatedPrimaryResources(object);
+ var primaryResourceIdSet =
+ configuration.getPrimaryResourcesRetriever().associatedPrimaryResources(object);
if (primaryResourceIdSet.isEmpty()) {
return;
}
@@ -126,16 +84,12 @@ private void propagateEvent(T object) {
@Override
public void start() {
- sharedInformer.run();
+ manager().start();
}
@Override
public void stop() {
- sharedInformer.close();
- }
-
- private Store