diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java index c077a7876d..40b32043af 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java @@ -7,7 +7,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.ReconcilerUtils; -import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfiguration; +import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.api.reconciler.Constants; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters; @@ -50,7 +50,7 @@ default ResourceEventFilter getEventFilter() { return ResourceEventFilters.passthrough(); } - default List getDependentResources() { + default List getDependentResources() { return Collections.emptyList(); } 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 b21ca22b46..141b58ded0 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 @@ -3,9 +3,12 @@ import java.time.Duration; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; 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.processing.event.source.controller.ResourceEventFilter; public class ControllerConfigurationOverrider { @@ -18,6 +21,7 @@ public class ControllerConfigurationOverrider { private ResourceEventFilter customResourcePredicate; private final ControllerConfiguration original; private Duration reconciliationMaxInterval; + private List dependentResourceSpecs; private ControllerConfigurationOverrider(ControllerConfiguration original) { finalizer = original.getFinalizer(); @@ -27,8 +31,8 @@ private ControllerConfigurationOverrider(ControllerConfiguration original) { labelSelector = original.getLabelSelector(); customResourcePredicate = original.getEventFilter(); reconciliationMaxInterval = original.reconciliationMaxInterval().orElse(null); + dependentResourceSpecs = original.getDependentResources(); this.original = original; - } public ControllerConfigurationOverrider withFinalizer(String finalizer) { @@ -84,6 +88,41 @@ public ControllerConfigurationOverrider withReconciliationMaxInterval( return this; } + /** + * If a {@link DependentResourceSpec} already exists with the same dependentResourceClass it will + * be replaced. Otherwise, an exception is thrown; + * + * @param dependentResourceSpec to add or replace + */ + public void replaceDependentResourceConfig(DependentResourceSpec dependentResourceSpec) { + var currentConfig = + findConfigForDependentResourceClass(dependentResourceSpec.getDependentResourceClass()); + if (currentConfig.isEmpty()) { + throw new IllegalStateException("Cannot find DependentResource config for class: " + + dependentResourceSpec.getDependentResourceClass()); + } + dependentResourceSpecs.remove(currentConfig.get()); + dependentResourceSpecs.add(dependentResourceSpec); + } + + public void addNewDependentResourceConfig(DependentResourceSpec dependentResourceSpec) { + var currentConfig = + findConfigForDependentResourceClass(dependentResourceSpec.getDependentResourceClass()); + if (currentConfig.isPresent()) { + throw new IllegalStateException( + "Config already present for class: " + + dependentResourceSpec.getDependentResourceClass()); + } + dependentResourceSpecs.add(dependentResourceSpec); + } + + private Optional findConfigForDependentResourceClass( + Class dependentResourceClass) { + return dependentResourceSpecs.stream() + .filter(dc -> dc.getDependentResourceClass().equals(dependentResourceClass)) + .findFirst(); + } + public ControllerConfiguration build() { return new DefaultControllerConfiguration<>( original.getAssociatedReconcilerClassName(), @@ -98,7 +137,7 @@ public ControllerConfiguration build() { original.getResourceClass(), reconciliationMaxInterval, original.getConfigurationService(), - original.getDependentResources()); + dependentResourceSpecs); } public static ControllerConfigurationOverrider override( diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java index 188311cfdb..8ce59dd01e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java @@ -7,7 +7,7 @@ import java.util.Set; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfiguration; +import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; public class DefaultControllerConfiguration @@ -21,7 +21,7 @@ public class DefaultControllerConfiguration private final boolean generationAware; private final RetryConfiguration retryConfiguration; private final ResourceEventFilter resourceEventFilter; - private final List dependents; + private final List dependents; private final Duration reconciliationMaxInterval; // NOSONAR constructor is meant to provide all information @@ -38,7 +38,7 @@ public DefaultControllerConfiguration( Class resourceClass, Duration reconciliationMaxInterval, ConfigurationService service, - List dependents) { + List dependents) { super(labelSelector, resourceClass, namespaces); this.associatedControllerClassName = associatedControllerClassName; this.name = name; @@ -102,7 +102,7 @@ public ResourceEventFilter getEventFilter() { } @Override - public List getDependentResources() { + public List getDependentResources() { return dependents; } 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 3944cd3ecc..8d9990ee9c 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 @@ -2,10 +2,12 @@ import java.io.IOException; import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Date; import java.util.Properties; +import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,8 +71,17 @@ public static boolean debugThreadPool() { return Boolean.getBoolean(System.getProperty(DEBUG_THREAD_POOL_ENV_KEY, "false")); } + public static Class getFirstTypeArgumentFromExtendedClass(Class clazz) { + Type type = clazz.getGenericSuperclass(); + return (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + } + public static Class getFirstTypeArgumentFromInterface(Class clazz) { ParameterizedType type = (ParameterizedType) clazz.getGenericInterfaces()[0]; return (Class) type.getActualTypeArguments()[0]; } + + public static T valueOrDefault(C annotation, Function mapper, T defaultValue) { + return annotation == null ? defaultValue : mapper.apply(annotation); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/Dependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/Dependent.java deleted file mode 100644 index 017f4363a6..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/Dependent.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.javaoperatorsdk.operator.api.config.dependent; - -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; - -public @interface Dependent { - - Class resourceType(); - - Class type(); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfiguration.java deleted file mode 100644 index c0f428c12d..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceConfiguration.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.javaoperatorsdk.operator.api.config.dependent; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; - -public interface DependentResourceConfiguration { - - Class> getDependentResourceClass(); - - Class getResourceClass(); -} 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 new file mode 100644 index 0000000000..aea90b5e36 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/DependentResourceSpec.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.operator.api.config.dependent; + +import java.util.Optional; + +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; + +public class DependentResourceSpec, C> { + + private final Class dependentResourceClass; + + private final C dependentResourceConfig; + + public DependentResourceSpec(Class dependentResourceClass) { + this(dependentResourceClass, null); + } + + public DependentResourceSpec(Class dependentResourceClass, C dependentResourceConfig) { + this.dependentResourceClass = dependentResourceClass; + this.dependentResourceConfig = dependentResourceConfig; + } + + public Class getDependentResourceClass() { + return dependentResourceClass; + } + + public Optional getDependentResourceConfiguration() { + return Optional.ofNullable(dependentResourceConfig); + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/KubernetesDependentResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/KubernetesDependentResourceConfiguration.java deleted file mode 100644 index 6872322e4f..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/KubernetesDependentResourceConfiguration.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.javaoperatorsdk.operator.api.config.dependent; - -import java.util.Set; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.config.ConfigurationService; -import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryResourcesRetriever; - -public interface KubernetesDependentResourceConfiguration - extends InformerConfiguration, DependentResourceConfiguration { - - class DefaultKubernetesDependentResourceConfiguration - extends DefaultInformerConfiguration - implements KubernetesDependentResourceConfiguration { - - private final boolean owned; - private final Class> dependentResourceClass; - - protected DefaultKubernetesDependentResourceConfiguration( - ConfigurationService service, - String labelSelector, Class resourceClass, - PrimaryResourcesRetriever secondaryToPrimaryResourcesIdSet, - AssociatedSecondaryResourceIdentifier

associatedWith, - boolean skipUpdateEventPropagationIfNoChange, Set namespaces, boolean owned, - Class> dependentResourceClass) { - super(service, labelSelector, resourceClass, secondaryToPrimaryResourcesIdSet, associatedWith, - skipUpdateEventPropagationIfNoChange, namespaces); - this.owned = owned; - this.dependentResourceClass = dependentResourceClass; - } - - public boolean isOwned() { - return owned; - } - - @Override - public Class> getDependentResourceClass() { - return dependentResourceClass; - } - - @Override - public Class getResourceClass() { - return super.getResourceClass(); - } - } - - static KubernetesDependentResourceConfiguration from( - InformerConfiguration cfg, boolean owned, - Class dependentResourceClass) { - return new DefaultKubernetesDependentResourceConfiguration(cfg.getConfigurationService(), - cfg.getLabelSelector(), cfg.getResourceClass(), cfg.getPrimaryResourcesRetriever(), - cfg.getAssociatedResourceIdentifier(), cfg.isSkipUpdateEventPropagationIfNoChange(), - cfg.getNamespaces(), owned, - (Class>) dependentResourceClass); - } - - boolean isOwned(); - - @Override - default Class getResourceClass() { - return InformerConfiguration.super.getResourceClass(); - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java index c4d747a990..acb5bd23c4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerConfiguration.java @@ -22,13 +22,12 @@ class DefaultInformerConfiguration private final PrimaryResourcesRetriever secondaryToPrimaryResourcesIdSet; private final AssociatedSecondaryResourceIdentifier

associatedWith; - private final boolean skipUpdateEventPropagationIfNoChange; protected DefaultInformerConfiguration(ConfigurationService service, String labelSelector, Class resourceClass, PrimaryResourcesRetriever secondaryToPrimaryResourcesIdSet, AssociatedSecondaryResourceIdentifier

associatedWith, - boolean skipUpdateEventPropagationIfNoChange, Set namespaces) { + Set namespaces) { super(labelSelector, resourceClass, namespaces); setConfigurationService(service); this.secondaryToPrimaryResourcesIdSet = @@ -36,7 +35,6 @@ protected DefaultInformerConfiguration(ConfigurationService service, String labe Mappers.fromOwnerReference()); this.associatedWith = Objects.requireNonNullElseGet(associatedWith, () -> ResourceID::fromResource); - this.skipUpdateEventPropagationIfNoChange = skipUpdateEventPropagationIfNoChange; } public PrimaryResourcesRetriever getPrimaryResourcesRetriever() { @@ -47,22 +45,16 @@ public AssociatedSecondaryResourceIdentifier

getAssociatedResourceIdentifier( return associatedWith; } - public boolean isSkipUpdateEventPropagationIfNoChange() { - return skipUpdateEventPropagationIfNoChange; - } } PrimaryResourcesRetriever getPrimaryResourcesRetriever(); AssociatedSecondaryResourceIdentifier

getAssociatedResourceIdentifier(); - boolean isSkipUpdateEventPropagationIfNoChange(); - class InformerConfigurationBuilder { private PrimaryResourcesRetriever secondaryToPrimaryResourcesIdSet; private AssociatedSecondaryResourceIdentifier

associatedWith; - private boolean skipUpdateEventPropagationIfNoChange = true; private Set namespaces; private String labelSelector; private final Class resourceClass; @@ -86,16 +78,6 @@ public InformerConfigurationBuilder withAssociatedSecondaryResourceIdentif return this; } - public InformerConfigurationBuilder withoutSkippingEventPropagationIfUnchanged() { - this.skipUpdateEventPropagationIfNoChange = false; - return this; - } - - public InformerConfigurationBuilder skippingEventPropagationIfUnchanged( - boolean skipIfUnchanged) { - this.skipUpdateEventPropagationIfNoChange = skipIfUnchanged; - return this; - } public InformerConfigurationBuilder withNamespaces(String... namespaces) { this.namespaces = namespaces != null ? Set.of(namespaces) : Collections.emptySet(); @@ -115,7 +97,7 @@ public InformerConfigurationBuilder withLabelSelector(String labelSelector public InformerConfiguration build() { return new DefaultInformerConfiguration<>(configurationService, labelSelector, resourceClass, - secondaryToPrimaryResourcesIdSet, associatedWith, skipUpdateEventPropagationIfNoChange, + secondaryToPrimaryResourcesIdSet, associatedWith, namespaces); } } @@ -136,8 +118,6 @@ static InformerConfigurationBuild configuration.getConfigurationService()) .withNamespaces(configuration.getNamespaces()) .withLabelSelector(configuration.getLabelSelector()) - .skippingEventPropagationIfUnchanged( - configuration.isSkipUpdateEventPropagationIfNoChange()) .withAssociatedSecondaryResourceIdentifier( configuration.getAssociatedResourceIdentifier()) .withPrimaryResourcesRetriever(configuration.getPrimaryResourcesRetriever()); 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 85eed38025..7d4c1fd0e8 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 @@ -4,10 +4,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.concurrent.TimeUnit; -import io.javaoperatorsdk.operator.api.config.dependent.Dependent; -import io.javaoperatorsdk.operator.processing.dependent.DependentResourceController; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter; @Retention(RetentionPolicy.RUNTIME) @@ -51,26 +49,27 @@ */ String labelSelector() default Constants.EMPTY_STRING; - /** * Optional list of classes providing custom {@link ResourceEventFilter}. * * @return the list of event filters. */ - @SuppressWarnings("rawtypes") Class[] eventFilters() default {}; + /** + * Optional configuration of the maximal interval the SDK will wait for a reconciliation request + * to happen before one will be automatically triggered. + * + * @return the maximal interval configuration + */ ReconciliationMaxInterval reconciliationMaxInterval() default @ReconciliationMaxInterval( - interval = 10, timeUnit = TimeUnit.HOURS); - + interval = 10); /** - * 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 + * Optional list of {@link Dependent} configurations which associate a resource type to a + * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource} implementation + * + * @return the list of {@link Dependent} configurations */ Dependent[] dependents() default {}; } 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 deleted file mode 100644 index af57fe1b32..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceContextInjector.java +++ /dev/null @@ -1,5 +0,0 @@ -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/dependent/AbstractDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/AbstractDependentResource.java index ea8b8bc06b..beac9ef4bb 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/AbstractDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/AbstractDependentResource.java @@ -3,8 +3,8 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.api.reconciler.Context; -public abstract class AbstractDependentResource - implements DependentResource { +public abstract class AbstractDependentResource + implements DependentResource { @Override public void reconcile(P primary, Context context) { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java new file mode 100644 index 0000000000..21f0b4f55b --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/Dependent.java @@ -0,0 +1,6 @@ +package io.javaoperatorsdk.operator.api.reconciler.dependent; + +public @interface Dependent { + + Class type(); +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java index b097554107..5208480166 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java @@ -3,24 +3,19 @@ import java.util.Optional; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.processing.event.source.EventSource; -public interface DependentResource { +public interface DependentResource { Optional eventSource(EventSourceContext

context); - @SuppressWarnings("unchecked") - default Class resourceType() { - return (Class) Utils.getFirstTypeArgumentFromInterface(getClass()); - } - void reconcile(P primary, Context context); void delete(P primary, Context context); Optional getResource(P primaryResource); + default void configureWith(C config) {} } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DesiredSupplier.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DesiredSupplier.java deleted file mode 100644 index b93d75950b..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DesiredSupplier.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler.dependent; - -import io.javaoperatorsdk.operator.api.reconciler.Context; - -@FunctionalInterface -public interface DesiredSupplier { - - R getDesired(P primary, Context context); - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/KubernetesClientAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/KubernetesClientAware.java new file mode 100644 index 0000000000..b8ef888ec3 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/KubernetesClientAware.java @@ -0,0 +1,7 @@ +package io.javaoperatorsdk.operator.api.reconciler.dependent; + +import io.fabric8.kubernetes.client.KubernetesClient; + +public interface KubernetesClientAware { + void setKubernetesClient(KubernetesClient kubernetesClient); +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/StandaloneKubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/StandaloneKubernetesDependentResource.java deleted file mode 100644 index d4d1e00dc2..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/StandaloneKubernetesDependentResource.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.javaoperatorsdk.operator.api.reconciler.dependent; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryResourcesRetriever; -import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; - -// todo shorter name -public class StandaloneKubernetesDependentResource - extends KubernetesDependentResource { - - private final DesiredSupplier desiredSupplier; - private final Class resourceType; - private AssociatedSecondaryResourceIdentifier

associatedSecondaryResourceIdentifier = - ResourceID::fromResource; - private PrimaryResourcesRetriever primaryResourcesRetriever = Mappers.fromOwnerReference(); - - public StandaloneKubernetesDependentResource( - Class resourceType, DesiredSupplier desiredSupplier) { - this(null, resourceType, desiredSupplier); - } - - public StandaloneKubernetesDependentResource( - KubernetesClient client, Class resourceType, DesiredSupplier desiredSupplier) { - super(client); - this.desiredSupplier = desiredSupplier; - this.resourceType = resourceType; - } - - @Override - protected R desired(P primary, Context context) { - return desiredSupplier.getDesired(primary, context); - } - - public Class resourceType() { - return resourceType; - } - - public StandaloneKubernetesDependentResource setAssociatedSecondaryResourceIdentifier( - AssociatedSecondaryResourceIdentifier

associatedSecondaryResourceIdentifier) { - this.associatedSecondaryResourceIdentifier = associatedSecondaryResourceIdentifier; - return this; - } - - public StandaloneKubernetesDependentResource setPrimaryResourcesRetriever( - PrimaryResourcesRetriever primaryResourcesRetriever) { - this.primaryResourcesRetriever = primaryResourcesRetriever; - return this; - } - - @Override - protected AssociatedSecondaryResourceIdentifier

getDefaultAssociatedSecondaryResourceIdentifier() { - return this.associatedSecondaryResourceIdentifier; - } - - @Override - protected PrimaryResourcesRetriever getDefaultPrimaryResourcesRetriever() { - return this.primaryResourcesRetriever; - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java index f3ad2dd8ec..89aabe0d9b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java @@ -217,7 +217,6 @@ public void start() throws OperatorException { eventSourceManager.getControllerResourceEventSource().getResourceCache(), configurationService(), kubernetesClient); - dependents.injectInto(context); prepareEventSources(context).forEach(eventSourceManager::registerEventSource); eventSourceManager.start(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DependentResourceController.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DependentResourceController.java deleted file mode 100644 index e49a55a73a..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DependentResourceController.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfiguration; -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.DependentResource; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; - -@Ignore -public class DependentResourceController, D extends DependentResource> - implements DependentResource { - - private final D delegate; - private final C configuration; - - public DependentResourceController(D delegate, C configuration) { - this.delegate = delegate; - this.configuration = initConfiguration(delegate, configuration); - } - - protected C initConfiguration(D delegate, C configuration) { - // default implementation just returns the specified one - return configuration; - } - - @Override - public Class resourceType() { - return delegate.resourceType(); - } - - @Override - public void delete(P primary, Context context) { - delegate.delete(primary, context); - } - - @Override - public Optional getResource(P primaryResource) { - return delegate.getResource(primaryResource); - } - - - @Override - public Optional eventSource(EventSourceContext

context) { - return delegate.eventSource(context); - } - - - public C getConfiguration() { - return configuration; - } - - protected D delegate() { - return delegate; - } - - @Override - public void reconcile(P resource, Context context) { - delegate.reconcile(resource, context); - } - - -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DependentResourceManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DependentResourceManager.java index ac791526fc..6f3a98b4dd 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DependentResourceManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/DependentResourceManager.java @@ -3,65 +3,63 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.config.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfiguration; -import io.javaoperatorsdk.operator.api.config.dependent.KubernetesDependentResourceConfiguration; +import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.ContextInitializer; import io.javaoperatorsdk.operator.api.reconciler.DeleteControl; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContextInjector; import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; import io.javaoperatorsdk.operator.api.reconciler.Ignore; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; -import io.javaoperatorsdk.operator.api.reconciler.dependent.KubernetesDependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.KubernetesClientAware; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @SuppressWarnings({"rawtypes", "unchecked"}) @Ignore -public class DependentResourceManager

implements EventSourceInitializer

, - EventSourceContextInjector, Reconciler

{ - private final Reconciler

reconciler; - private final ControllerConfiguration

configuration; - private List dependents; +public class DependentResourceManager

+ implements EventSourceInitializer

, Reconciler

{ + + private static final Logger log = LoggerFactory.getLogger(DependentResourceManager.class); + private final Reconciler

reconciler; + private final ControllerConfiguration

controllerConfiguration; + private List dependents; public DependentResourceManager(Controller

controller) { this.reconciler = controller.getReconciler(); - this.configuration = controller.getConfiguration(); + this.controllerConfiguration = controller.getConfiguration(); } @Override public List prepareEventSources(EventSourceContext

context) { - final List configured = configuration.getDependentResources(); - dependents = new ArrayList<>(configured.size()); - - List sources = new ArrayList<>(configured.size() + 5); - configured.forEach(dependent -> { - final var dependentResourceController = from(dependent, context.getClient()); - dependents.add(dependentResourceController); - dependentResourceController.eventSource(context) - .ifPresent(es -> sources.add((EventSource) es)); - - }); - + final var dependentResources = controllerConfiguration.getDependentResources(); + final var sources = new ArrayList(dependentResources.size()); + dependents = + dependentResources.stream() + .map( + drc -> { + final var dependentResource = + from(drc, context.getClient()); + dependentResource + .eventSource(context) + .ifPresent(es -> sources.add((EventSource) es)); + return dependentResource; + }) + .collect(Collectors.toList()); return sources; } - @Override - public void injectInto(EventSourceContext context) { - if (reconciler instanceof EventSourceContextInjector) { - EventSourceContextInjector injector = (EventSourceContextInjector) reconciler; - injector.injectInto(context); - } - } - @Override public UpdateControl

reconcile(P resource, Context context) { initContextIfNeeded(resource, context); @@ -76,7 +74,6 @@ public DeleteControl cleanup(P resource, Context context) { return Reconciler.super.cleanup(resource, context); } - private void initContextIfNeeded(P resource, Context context) { if (reconciler instanceof ContextInitializer) { final var initializer = (ContextInitializer

) reconciler; @@ -84,29 +81,21 @@ private void initContextIfNeeded(P resource, Context context) { } } - private DependentResourceController from(DependentResourceConfiguration config, + private DependentResource from(DependentResourceSpec dependentResourceSpec, KubernetesClient client) { try { - final var dependentResource = - (DependentResource) config.getDependentResourceClass().getConstructor() - .newInstance(); - if (config instanceof KubernetesDependentResourceConfiguration) { - if (dependentResource instanceof KubernetesDependentResource) { - final var kubeDependentResource = (KubernetesDependentResource) dependentResource; - kubeDependentResource.setClient(client); - return new KubernetesDependentResourceController(kubeDependentResource, - (KubernetesDependentResourceConfiguration) config); - } else { - throw new IllegalArgumentException("A " - + KubernetesDependentResourceConfiguration.class.getCanonicalName() - + " must be associated to a " + KubernetesDependentResource.class.getCanonicalName()); - } - } else { - return new DependentResourceController(dependentResource, config); + DependentResource dependentResource = + (DependentResource) dependentResourceSpec.getDependentResourceClass() + .getConstructor().newInstance(); + if (dependentResource instanceof KubernetesClientAware) { + ((KubernetesClientAware) dependentResource).setKubernetesClient(client); } - } catch (NoSuchMethodException | InvocationTargetException | InstantiationException - | IllegalAccessException e) { - throw new IllegalArgumentException(e); + dependentResourceSpec.getDependentResourceConfiguration() + .ifPresent(dependentResource::configureWith); + return dependentResource; + } catch (InstantiationException | NoSuchMethodException | IllegalAccessException + | InvocationTargetException e) { + throw new IllegalStateException(e); } } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/KubernetesDependentResourceController.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/KubernetesDependentResourceController.java deleted file mode 100644 index 733460ef31..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/KubernetesDependentResourceController.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.javaoperatorsdk.operator.processing.dependent; - -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.config.dependent.KubernetesDependentResourceConfiguration; -import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.Ignore; -import io.javaoperatorsdk.operator.api.reconciler.dependent.KubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryResourcesRetriever; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; - -@Ignore -public class KubernetesDependentResourceController - extends - DependentResourceController, KubernetesDependentResource> { - - public KubernetesDependentResourceController(KubernetesDependentResource delegate, - KubernetesDependentResourceConfiguration configuration) { - super(delegate, configuration); - } - - @SuppressWarnings("unchecked") - @Override - protected KubernetesDependentResourceConfiguration initConfiguration( - KubernetesDependentResource delegate, - KubernetesDependentResourceConfiguration configuration) { - // todo: check if we can validate that types actually match properly - final var associatedPrimaries = - (delegate instanceof PrimaryResourcesRetriever) - ? (PrimaryResourcesRetriever) delegate - : configuration.getPrimaryResourcesRetriever(); - final var associatedSecondary = - (delegate instanceof AssociatedSecondaryResourceIdentifier) - ? (AssociatedSecondaryResourceIdentifier

) delegate - : configuration.getAssociatedResourceIdentifier(); - - final var augmented = InformerConfiguration.from(configuration) - .withPrimaryResourcesRetriever(associatedPrimaries) - .withAssociatedSecondaryResourceIdentifier(associatedSecondary) - .build(); - return KubernetesDependentResourceConfiguration.from(augmented, configuration.isOwned(), - configuration.getDependentResourceClass()); - } - - @Override - public Optional eventSource(EventSourceContext

context) { - var informer = new InformerEventSource<>(getConfiguration(), context); - // todo have this implemented with nicer abstractions - delegate().setInformerEventSource(informer); - return super.eventSource(context); - } -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/KubernetesDependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java similarity index 73% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/KubernetesDependent.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java index e7d2ad3158..c0922f569f 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/dependent/KubernetesDependent.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java @@ -1,4 +1,4 @@ -package io.javaoperatorsdk.operator.api.config.dependent; +package io.javaoperatorsdk.operator.processing.dependent.kubernetes; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -11,16 +11,14 @@ @Target({ElementType.TYPE}) public @interface KubernetesDependent { - boolean OWNED_DEFAULT = true; - boolean SKIP_UPDATE_DEFAULT = true; + boolean ADD_OWNER_REFERENCE_DEFAULT = true; - boolean owned() default OWNED_DEFAULT; - - boolean skipUpdateIfUnchanged() default SKIP_UPDATE_DEFAULT; + boolean addOwnerReference() default ADD_OWNER_REFERENCE_DEFAULT; /** * Specified which namespaces this Controller monitors for custom resources events. If no - * namespace is specified then the controller will monitor all namespaces by default. + * namespace is specified then the controller will monitor the namespaces configured for the + * controller. * * @return the list of namespaces this controller monitors */ @@ -34,4 +32,5 @@ * @return the label selector */ String labelSelector() default EMPTY_STRING; + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java similarity index 55% rename from operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/KubernetesDependentResource.java rename to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java index e8a4a6779e..a805c12b11 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/KubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java @@ -1,6 +1,7 @@ -package io.javaoperatorsdk.operator.api.reconciler.dependent; +package io.javaoperatorsdk.operator.processing.dependent.kubernetes; import java.util.Optional; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,9 +9,13 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.ReconcilerUtils; +import io.javaoperatorsdk.operator.api.config.ConfigurationService; +import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.AbstractDependentResource; +import io.javaoperatorsdk.operator.api.reconciler.dependent.KubernetesClientAware; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @@ -19,25 +24,56 @@ import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; public abstract class KubernetesDependentResource - extends AbstractDependentResource { + extends AbstractDependentResource + implements KubernetesClientAware { private static final Logger log = LoggerFactory.getLogger(KubernetesDependentResource.class); protected KubernetesClient client; - private boolean explicitDelete = false; - private boolean owned = true; private InformerEventSource informerEventSource; + private boolean addOwnerReference; - public KubernetesDependentResource() { - this(null); + @Override + public void configureWith(KubernetesDependentResourceConfig config) { + configureWith(config.getConfigurationService(), config.labelSelector(), + Set.of(config.namespaces()), config.addOwnerReference()); } - public KubernetesDependentResource(KubernetesClient client) { - this.client = client; + @SuppressWarnings("unchecked") + private void configureWith(ConfigurationService service, String labelSelector, + Set namespaces, boolean addOwnerReference) { + final var primaryResourcesRetriever = + (this instanceof PrimaryResourcesRetriever) ? (PrimaryResourcesRetriever) this + : Mappers.fromOwnerReference(); + final AssociatedSecondaryResourceIdentifier

secondaryResourceIdentifier = + (this instanceof AssociatedSecondaryResourceIdentifier) + ? (AssociatedSecondaryResourceIdentifier

) this + : ResourceID::fromResource; + InformerConfiguration ic = + InformerConfiguration.from(service, resourceType()) + .withLabelSelector(labelSelector) + .withNamespaces(namespaces) + .withPrimaryResourcesRetriever(primaryResourcesRetriever) + .withAssociatedSecondaryResourceIdentifier(secondaryResourceIdentifier) + .build(); + this.addOwnerReference = addOwnerReference; + informerEventSource = new InformerEventSource<>(ic, client); + } + + /** + * Use to share informers between event more resources. + * + * @param informerEventSource informer to use + * @param addOwnerReference to the created resource + */ + public void configureWith(InformerEventSource informerEventSource, + boolean addOwnerReference) { + this.informerEventSource = informerEventSource; + this.addOwnerReference = addOwnerReference; } protected void beforeCreateOrUpdate(R desired, P primary) { - if (owned) { + if (addOwnerReference) { desired.addOwnerReference(primary); } } @@ -69,42 +105,17 @@ protected R update(R actual, R target, P primary, Context context) { .replace(target); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override public Optional eventSource(EventSourceContext

context) { - if (informerEventSource != null) { - return Optional.of(informerEventSource); + if (informerEventSource == null) { + configureWith(context.getConfigurationService(), null, null, + KubernetesDependent.ADD_OWNER_REFERENCE_DEFAULT); + log.warn("Using default configuration for " + resourceType().getSimpleName() + + " KubernetesDependentResource, call configureWith to provide configuration"); } - var informerConfig = initInformerConfiguration(context); - informerEventSource = new InformerEventSource(informerConfig, context); return Optional.of(informerEventSource); } - @SuppressWarnings("unchecked") - private InformerConfiguration initInformerConfiguration(EventSourceContext

context) { - PrimaryResourcesRetriever associatedPrimaries = - (this instanceof PrimaryResourcesRetriever) ? (PrimaryResourcesRetriever) this - : getDefaultPrimaryResourcesRetriever(); - - AssociatedSecondaryResourceIdentifier

associatedSecondary = - (this instanceof AssociatedSecondaryResourceIdentifier) - ? (AssociatedSecondaryResourceIdentifier

) this - : getDefaultAssociatedSecondaryResourceIdentifier(); - - return InformerConfiguration.from(context, resourceType()) - .withPrimaryResourcesRetriever(associatedPrimaries) - .withAssociatedSecondaryResourceIdentifier(associatedSecondary) - .build(); - } - - protected AssociatedSecondaryResourceIdentifier

getDefaultAssociatedSecondaryResourceIdentifier() { - return ResourceID::fromResource; - } - - protected PrimaryResourcesRetriever getDefaultPrimaryResourcesRetriever() { - return Mappers.fromOwnerReference(); - } - public KubernetesDependentResource setInformerEventSource( InformerEventSource informerEventSource) { this.informerEventSource = informerEventSource; @@ -113,38 +124,24 @@ public KubernetesDependentResource setInformerEventSource( @Override public void delete(P primary, Context context) { - if (explicitDelete) { + if (addOwnerReference) { var resource = getResource(primary); resource.ifPresent(r -> client.resource(r).delete()); } } + @SuppressWarnings("unchecked") + protected Class resourceType() { + return (Class) Utils.getFirstTypeArgumentFromExtendedClass(getClass()); + } + @Override public Optional getResource(P primaryResource) { return informerEventSource.getAssociated(primaryResource); } - public KubernetesDependentResource setClient(KubernetesClient client) { - this.client = client; - return this; - } - - - public KubernetesDependentResource setExplicitDelete(boolean explicitDelete) { - this.explicitDelete = explicitDelete; - return this; - } - - public boolean isExplicitDelete() { - return explicitDelete; - } - - public boolean isOwned() { - return owned; - } - - public KubernetesDependentResource setOwned(boolean owned) { - this.owned = owned; - return this; + @Override + public void setKubernetesClient(KubernetesClient kubernetesClient) { + this.client = kubernetesClient; } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java new file mode 100644 index 0000000000..4464f3fd7d --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java @@ -0,0 +1,62 @@ +package io.javaoperatorsdk.operator.processing.dependent.kubernetes; + +import io.javaoperatorsdk.operator.api.config.ConfigurationService; + +import static io.javaoperatorsdk.operator.api.reconciler.Constants.EMPTY_STRING; +import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent.ADD_OWNER_REFERENCE_DEFAULT; + +public class KubernetesDependentResourceConfig { + + private boolean addOwnerReference = ADD_OWNER_REFERENCE_DEFAULT; + private String[] namespaces = new String[0]; + private String labelSelector = EMPTY_STRING; + private ConfigurationService configurationService; + + public KubernetesDependentResourceConfig() {} + + public KubernetesDependentResourceConfig(boolean addOwnerReference, String[] namespaces, + String labelSelector, ConfigurationService configurationService) { + this.addOwnerReference = addOwnerReference; + this.namespaces = namespaces; + this.labelSelector = labelSelector; + this.configurationService = configurationService; + } + + public KubernetesDependentResourceConfig setAddOwnerReference( + boolean addOwnerReference) { + this.addOwnerReference = addOwnerReference; + return this; + } + + public KubernetesDependentResourceConfig setNamespaces(String[] namespaces) { + this.namespaces = namespaces; + return this; + } + + public KubernetesDependentResourceConfig setLabelSelector(String labelSelector) { + this.labelSelector = labelSelector; + return this; + } + + public KubernetesDependentResourceConfig setConfigurationService( + ConfigurationService configurationService) { + this.configurationService = configurationService; + return this; + } + + public boolean addOwnerReference() { + return addOwnerReference; + } + + public String[] namespaces() { + return namespaces; + } + + public String labelSelector() { + return labelSelector; + } + + public ConfigurationService getConfigurationService() { + return configurationService; + } +} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSourceContextAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSourceContextAware.java deleted file mode 100644 index 95042d0c41..0000000000 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSourceContextAware.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.javaoperatorsdk.operator.processing.event.source; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; - -public interface EventSourceContextAware

{ - void initWith(EventSourceContext

context); -} diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java index 3def733bd8..b29209f9e5 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java @@ -5,12 +5,12 @@ import java.util.stream.Stream; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; import io.javaoperatorsdk.operator.processing.event.Event; import io.javaoperatorsdk.operator.processing.event.EventHandler; -import io.javaoperatorsdk.operator.processing.event.source.EventSourceContextAware; import io.javaoperatorsdk.operator.processing.event.source.ResourceCache; public class InformerEventSource @@ -23,17 +23,12 @@ public InformerEventSource(InformerConfiguration configuration, EventSourceContext

context) { super(context.getClient().resources(configuration.getResourceClass()), configuration); this.configuration = configuration; + } - // init mappers with context if needed - final var primaryResourcesRetriever = configuration.getPrimaryResourcesRetriever(); - if (primaryResourcesRetriever instanceof EventSourceContextAware) { - ((EventSourceContextAware) primaryResourcesRetriever).initWith(context); - } - - final var associatedResourceIdentifier = configuration.getAssociatedResourceIdentifier(); - if (associatedResourceIdentifier instanceof EventSourceContextAware) { - ((EventSourceContextAware) associatedResourceIdentifier).initWith(context); - } + public InformerEventSource(InformerConfiguration configuration, + KubernetesClient client) { + super(client.resources(configuration.getResourceClass()), configuration); + this.configuration = configuration; } @Override @@ -50,9 +45,8 @@ public void onUpdate(T oldObject, T newObject) { return; } - if (configuration.isSkipUpdateEventPropagationIfNoChange() && - oldObject.getMetadata().getResourceVersion() - .equals(newObject.getMetadata().getResourceVersion())) { + if (oldObject.getMetadata().getResourceVersion() + .equals(newObject.getMetadata().getResourceVersion())) { return; } propagateEvent(newObject); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java index f0a1a1185d..1dcfa2aa0e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/ManagedInformerEventSource.java @@ -34,7 +34,6 @@ public void start() { super.start(); } - @Override public void stop() { super.stop(); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java new file mode 100644 index 0000000000..88dd8db469 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/UtilsTest.java @@ -0,0 +1,30 @@ +package io.javaoperatorsdk.operator.api.config; + +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class UtilsTest { + + @Test + void getsFirstTypeArgumentFromExtendedClass() { + Class res = + Utils.getFirstTypeArgumentFromExtendedClass(TestKubernetesDependentResource.class); + assertThat(res).isEqualTo(Deployment.class); + } + + public static class TestKubernetesDependentResource + extends KubernetesDependentResource { + + @Override + protected Deployment desired(TestCustomResource primary, Context context) { + return null; + } + } +} diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java index 76d848c896..c445acf67c 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -15,6 +16,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.api.config.ConfigurationService; +import io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.processing.Controller; import io.javaoperatorsdk.operator.processing.retry.Retry; @@ -37,7 +39,11 @@ private OperatorExtension( boolean preserveNamespaceOnError, boolean waitForNamespaceDeletion, boolean oneNamespacePerClass) { - super(configurationService, infrastructure, infrastructureTimeout, oneNamespacePerClass, + super( + configurationService, + infrastructure, + infrastructureTimeout, + oneNamespacePerClass, preserveNamespaceOnError, waitForNamespaceDeletion); this.reconcilers = reconcilers; @@ -86,6 +92,9 @@ protected void before(ExtensionContext context) { if (ref.retry != null) { oconfig.withRetry(ref.retry); } + if (ref.controllerConfigurationOverrider != null) { + ref.controllerConfigurationOverrider.accept(oconfig); + } final var kubernetesClient = getKubernetesClient(); try (InputStream is = getClass().getResourceAsStream(path)) { @@ -127,6 +136,19 @@ protected Builder() { this.reconcilers = new ArrayList<>(); } + public Builder withReconciler( + Reconciler value, Consumer configurationOverrider) { + return withReconciler(value, null, configurationOverrider); + } + + public Builder withReconciler( + Reconciler value, + Retry retry, + Consumer configurationOverrider) { + reconcilers.add(new ReconcilerSpec(value, retry, configurationOverrider)); + return this; + } + @SuppressWarnings("rawtypes") public Builder withReconciler(Reconciler value) { reconcilers.add(new ReconcilerSpec(value, null)); @@ -165,10 +187,19 @@ public OperatorExtension build() { private static class ReconcilerSpec { final Reconciler reconciler; final Retry retry; + final Consumer controllerConfigurationOverrider; public ReconcilerSpec(Reconciler reconciler, Retry retry) { + this(reconciler, retry, null); + } + + public ReconcilerSpec( + Reconciler reconciler, + Retry retry, + Consumer controllerConfigurationOverrider) { this.reconciler = reconciler; this.retry = retry; + this.controllerConfigurationOverrider = controllerConfigurationOverrider; } } } diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationControllerConfiguration.java similarity index 50% rename from operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java rename to operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationControllerConfiguration.java index 19e31e932b..dd1e005fc1 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationControllerConfiguration.java @@ -1,37 +1,34 @@ package io.javaoperatorsdk.operator.config.runtime; import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Function; +import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.HasMetadata; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.ConfigurationService; -import io.javaoperatorsdk.operator.api.config.dependent.Dependent; -import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfiguration; -import io.javaoperatorsdk.operator.api.config.dependent.KubernetesDependent; -import io.javaoperatorsdk.operator.api.config.dependent.KubernetesDependentResourceConfiguration; -import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.api.config.Utils; +import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; 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.processing.event.source.controller.ResourceEventFilter; import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters; @SuppressWarnings("rawtypes") -public class AnnotationConfiguration +public class AnnotationControllerConfiguration implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration { private final Reconciler reconciler; private final ControllerConfiguration annotation; private ConfigurationService service; - private List dependentConfigurations; - public AnnotationConfiguration(Reconciler reconciler) { + public AnnotationControllerConfiguration(Reconciler reconciler) { this.reconciler = reconciler; this.annotation = reconciler.getClass().getAnnotation(ControllerConfiguration.class); } @@ -50,16 +47,17 @@ public String getFinalizer() { if (ReconcilerUtils.isFinalizerValid(finalizer)) { return finalizer; } else { - throw new IllegalArgumentException(finalizer - + " is not a valid finalizer. See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers for details"); + throw new IllegalArgumentException( + finalizer + + " is not a valid finalizer. See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers for details"); } } } @Override public boolean isGenerationAware() { - return valueOrDefault(annotation, ControllerConfiguration::generationAwareEventProcessing, - true); + return valueOrDefault( + annotation, ControllerConfiguration::generationAwareEventProcessing, true); } @Override @@ -99,8 +97,7 @@ public ResourceEventFilter getEventFilter() { Class>[] filterTypes = (Class>[]) valueOrDefault(annotation, - ControllerConfiguration::eventFilters, - new Object[] {}); + ControllerConfiguration::eventFilters, new Object[] {}); if (filterTypes.length > 0) { for (var filterType : filterTypes) { try { @@ -116,9 +113,7 @@ public ResourceEventFilter getEventFilter() { } } } - return answer != null - ? answer - : ResourceEventFilters.passthrough(); + return answer != null ? answer : ResourceEventFilters.passthrough(); } @Override @@ -127,8 +122,10 @@ public Optional reconciliationMaxInterval() { if (annotation.reconciliationMaxInterval().interval() <= 0) { return Optional.empty(); } - return Optional.of(Duration.of(annotation.reconciliationMaxInterval().interval(), - annotation.reconciliationMaxInterval().timeUnit().toChronoUnit())); + return Optional.of( + Duration.of( + annotation.reconciliationMaxInterval().interval(), + annotation.reconciliationMaxInterval().timeUnit().toChronoUnit())); } else { return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval(); } @@ -146,59 +143,41 @@ public static T valueOrDefault( } @Override - public List getDependentResources() { - if (dependentConfigurations == null) { - final var dependents = valueOrDefault(annotation, ControllerConfiguration::dependents, - new Dependent[] {}); - if (dependents.length > 0) { - dependentConfigurations = new ArrayList<>(dependents.length); - for (Dependent dependent : dependents) { - final Class dependentType = dependent.type(); - final var resourceType = dependent.resourceType(); - - if (HasMetadata.class.isAssignableFrom(resourceType)) { - final var kubeDependent = dependentType.getAnnotation(KubernetesDependent.class); - final var namespaces = - valueOrDefault(kubeDependent, KubernetesDependent::namespaces, - this.getNamespaces().toArray(new String[0])); - final var labelSelector = - valueOrDefault(kubeDependent, KubernetesDependent::labelSelector, null); - final var owned = valueOrDefault(kubeDependent, KubernetesDependent::owned, - KubernetesDependent.OWNED_DEFAULT); - final var skipIfUnchanged = - valueOrDefault(kubeDependent, KubernetesDependent::skipUpdateIfUnchanged, - KubernetesDependent.SKIP_UPDATE_DEFAULT); - final var configuration = InformerConfiguration.from(service, resourceType) - .withLabelSelector(labelSelector) - .skippingEventPropagationIfUnchanged(skipIfUnchanged) - .withNamespaces(namespaces) - .build(); - - dependentConfigurations.add( - KubernetesDependentResourceConfiguration.from(configuration, owned, dependentType)); - } else { - dependentConfigurations.add(new DependentResourceConfiguration() { - @Override - public Class getDependentResourceClass() { - return dependentType; - } - - @Override - public Class getResourceClass() { - return resourceType; - } - }); - } - } + public List getDependentResources() { + final var dependents = + valueOrDefault(annotation, ControllerConfiguration::dependents, new Dependent[] {}); + if (dependents.length == 0) { + return Collections.emptyList(); + } + + List resourceSpecs = new ArrayList<>(dependents.length); + for (Dependent dependent : dependents) { + + final Class dependentType = dependent.type(); + if (KubernetesDependentResource.class.isAssignableFrom(dependentType)) { + final var kubeDependent = dependentType.getAnnotation(KubernetesDependent.class); + final var namespaces = + Utils.valueOrDefault( + kubeDependent, + KubernetesDependent::namespaces, + this.getNamespaces().toArray(new String[0])); + final var labelSelector = + Utils.valueOrDefault(kubeDependent, KubernetesDependent::labelSelector, null); + final var addOwnerReference = + Utils.valueOrDefault( + kubeDependent, + KubernetesDependent::addOwnerReference, + KubernetesDependent.ADD_OWNER_REFERENCE_DEFAULT); + KubernetesDependentResourceConfig config = + new KubernetesDependentResourceConfig( + addOwnerReference, namespaces, labelSelector, getConfigurationService()); + resourceSpecs.add(new DependentResourceSpec(dependentType, config)); } else { - dependentConfigurations = Collections.emptyList(); + resourceSpecs.add(new DependentResourceSpec(dependentType)); } } - return dependentConfigurations; - } - - private static T valueOrDefault(C annotation, Function mapper, T defaultValue) { - return annotation == null ? defaultValue : mapper.apply(annotation); + return Arrays.stream(dependents) + .map(d -> new DependentResourceSpec(d.type())) + .collect(Collectors.toList()); } } - diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationService.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationService.java index 44fc674028..21af48cfd1 100644 --- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationService.java +++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/DefaultConfigurationService.java @@ -30,7 +30,7 @@ ControllerConfiguration getConfigurationFor( if (config == null) { if (createIfNeeded) { // create the configuration on demand and register it - config = new AnnotationConfiguration<>(reconciler); + config = new AnnotationControllerConfiguration<>(reconciler); register(config); getLogger().info( "Created configuration for reconciler {} with name {}", diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java index 695cc30a31..b168d8fb47 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java @@ -9,8 +9,8 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.utils.Serialization; import io.javaoperatorsdk.operator.api.reconciler.*; -import io.javaoperatorsdk.operator.api.reconciler.dependent.StandaloneKubernetesDependentResource; import io.javaoperatorsdk.operator.junit.KubernetesClientAware; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; @@ -23,24 +23,10 @@ public class StandaloneDependentTestReconciler private KubernetesClient kubernetesClient; - StandaloneKubernetesDependentResource configMapDependent; + KubernetesDependentResource configMapDependent; public StandaloneDependentTestReconciler() { - configMapDependent = - new StandaloneKubernetesDependentResource<>(Deployment.class, (primary, context) -> { - Deployment deployment = loadYaml(Deployment.class, "nginx-deployment.yaml"); - deployment.getMetadata().setName(primary.getMetadata().getName()); - deployment.getMetadata().setNamespace(primary.getMetadata().getNamespace()); - return deployment; - }) { - @Override - protected boolean match(Deployment actual, Deployment target, Context context) { - return Objects.equals(actual.getSpec().getReplicas(), target.getSpec().getReplicas()) && - actual.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() - .equals( - target.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); - } - }; + configMapDependent = new DeploymentDependentResource(); } @Override @@ -59,7 +45,7 @@ public UpdateControl reconcile( @Override public void setKubernetesClient(KubernetesClient kubernetesClient) { this.kubernetesClient = kubernetesClient; - configMapDependent.setClient(kubernetesClient); + configMapDependent.setKubernetesClient(kubernetesClient); } @Override @@ -74,4 +60,26 @@ private T loadYaml(Class clazz, String yaml) { throw new IllegalStateException("Cannot find yaml on classpath: " + yaml); } } + + private class DeploymentDependentResource extends + KubernetesDependentResource { + + @Override + protected Deployment desired(StandaloneDependentTestCustomResource primary, Context context) { + Deployment deployment = StandaloneDependentTestReconciler.this.loadYaml(Deployment.class, + "nginx-deployment.yaml"); + deployment.getMetadata().setName(primary.getMetadata().getName()); + deployment.getMetadata().setNamespace(primary.getMetadata().getNamespace()); + return deployment; + } + + @Override + protected boolean match(Deployment actual, Deployment target, Context context) { + return Objects.equals(actual.getSpec().getReplicas(), target.getSpec().getReplicas()) && + actual.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + .equals( + target.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); + } + + } } diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperator.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperator.java index d9012637cf..4fddf8cadc 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperator.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperator.java @@ -15,6 +15,9 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider; +import io.javaoperatorsdk.operator.api.config.ControllerConfigurationOverrider; +import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; +import io.javaoperatorsdk.operator.config.runtime.AnnotationControllerConfiguration; import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; import io.javaoperatorsdk.operator.monitoring.micrometer.MicrometerMetrics; import io.micrometer.core.instrument.logging.LoggingMeterRegistry; @@ -32,7 +35,17 @@ public static void main(String[] args) throws IOException { new ConfigurationServiceOverrider(DefaultConfigurationService.instance()) .withMetrics(new MicrometerMetrics(new LoggingMeterRegistry())) .build()); - operator.register(new MySQLSchemaReconciler(MySQLDbConfig.loadFromEnvironmentVars())); + + MySQLSchemaReconciler schemaReconciler = new MySQLSchemaReconciler(); + ControllerConfigurationOverrider configOverrider = + ControllerConfigurationOverrider + .override(new AnnotationControllerConfiguration(schemaReconciler)); + + configOverrider.replaceDependentResourceConfig( + new DependentResourceSpec(SchemaDependentResource.class, + new ResourcePollerConfig(500, MySQLDbConfig.loadFromEnvironmentVars()))); + + operator.register(schemaReconciler, configOverrider.build()); operator.installShutdownHook(); operator.start(); diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java index 63c37ac3d7..8965ae2a89 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java @@ -6,17 +6,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fabric8.kubernetes.api.model.Secret; -import io.javaoperatorsdk.operator.api.config.dependent.Dependent; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.ContextInitializer; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContextInjector; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.RetryInfo; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.sample.schema.Schema; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; @@ -25,12 +22,12 @@ // todo handle this, should work with finalizer @ControllerConfiguration(finalizerName = NO_FINALIZER, dependents = { - @Dependent(resourceType = Secret.class, type = SecretDependentResource.class), - @Dependent(resourceType = Schema.class, type = SchemaDependentResource.class) + @Dependent(type = SecretDependentResource.class), + @Dependent(type = SchemaDependentResource.class) }) public class MySQLSchemaReconciler implements Reconciler, ErrorStatusHandler, - ContextInitializer, EventSourceContextInjector { + ContextInitializer { static final String SECRET_FORMAT = "%s-secret"; static final String USERNAME_FORMAT = "%s-user"; @@ -38,21 +35,10 @@ public class MySQLSchemaReconciler static final String MYSQL_SECRET_NAME = "mysql.secret.name"; static final String MYSQL_SECRET_USERNAME = "mysql.secret.user.name"; static final String MYSQL_SECRET_PASSWORD = "mysql.secret.user.password"; - static final String MYSQL_DB_CONFIG = "mysql.db.config"; static final String BUILT_SCHEMA = "built schema"; static final Logger log = LoggerFactory.getLogger(MySQLSchemaReconciler.class); - private final MySQLDbConfig mysqlDbConfig; - - public MySQLSchemaReconciler(MySQLDbConfig mysqlDbConfig) { - this.mysqlDbConfig = mysqlDbConfig; - } - - @SuppressWarnings("rawtypes") - @Override - public void injectInto(EventSourceContext context) { - context.put(MYSQL_DB_CONFIG, mysqlDbConfig); - } + public MySQLSchemaReconciler() {} @Override public void initContext(MySQLSchema primary, Context context) { diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/ResourcePollerConfig.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/ResourcePollerConfig.java new file mode 100644 index 0000000000..924c3978d1 --- /dev/null +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/ResourcePollerConfig.java @@ -0,0 +1,21 @@ +package io.javaoperatorsdk.operator.sample; + +public class ResourcePollerConfig { + + private final int pollPeriod; + private final MySQLDbConfig mySQLDbConfig; + + + public ResourcePollerConfig(int pollPeriod, MySQLDbConfig mySQLDbConfig) { + this.pollPeriod = pollPeriod; + this.mySQLDbConfig = mySQLDbConfig; + } + + public int getPollPeriod() { + return pollPeriod; + } + + public MySQLDbConfig getMySQLDbConfig() { + return mySQLDbConfig; + } +} diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaDependentResource.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaDependentResource.java index 957f0b34ff..62ce1356a2 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaDependentResource.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SchemaDependentResource.java @@ -15,16 +15,22 @@ import static java.lang.String.format; -public class SchemaDependentResource extends AbstractDependentResource { +public class SchemaDependentResource extends + AbstractDependentResource { - private static final int POLL_PERIOD = 500; private MySQLDbConfig dbConfig; + private int pollPeriod; + + @Override + public void configureWith(ResourcePollerConfig config) { + this.dbConfig = config.getMySQLDbConfig(); + this.pollPeriod = config.getPollPeriod(); + } @Override public Optional eventSource(EventSourceContext context) { - dbConfig = context.getMandatory(MySQLSchemaReconciler.MYSQL_DB_CONFIG, MySQLDbConfig.class); return Optional.of(new PerResourcePollingEventSource<>( - new SchemaPollingResourceSupplier(dbConfig), context.getPrimaryCache(), POLL_PERIOD, + new SchemaPollingResourceSupplier(dbConfig), context.getPrimaryCache(), pollPeriod, Schema.class)); } diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SecretDependentResource.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SecretDependentResource.java index 4f6bd1759c..884cc6d2c9 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SecretDependentResource.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/SecretDependentResource.java @@ -5,7 +5,7 @@ import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; @@ -18,8 +18,16 @@ private static String encode(String value) { return Base64.getEncoder().encodeToString(value.getBytes()); } + // An alternative would be to override reconcile() method and exclude the update part. + @Override + protected Secret update(Secret actual, Secret target, MySQLSchema primary, Context context) { + throw new IllegalStateException( + "Secret should not be updated. Secret: " + target + " for custom resource: " + + primary); + } + @Override - public Secret desired(MySQLSchema schema, Context context) { + protected Secret desired(MySQLSchema schema, Context context) { return new SecretBuilder() .withNewMetadata() .withName(context.getMandatory(MYSQL_SECRET_NAME, String.class)) @@ -32,14 +40,6 @@ public Secret desired(MySQLSchema schema, Context context) { .build(); } - // An alternative would be to override reconcile() method and exclude the update part. - @Override - protected Secret update(Secret actual, Secret target, MySQLSchema primary, Context context) { - throw new IllegalStateException( - "Secret should not be updated. Secret: " + target + " for custom resource: " - + primary); - } - @Override protected boolean match(Secret actual, Secret target, Context context) { return ResourceID.fromResource(actual).equals(ResourceID.fromResource(target)); diff --git a/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java b/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java index 0c1be5efc0..f433671c08 100644 --- a/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java +++ b/sample-operators/mysql-schema/src/test/java/io/javaoperatorsdk/operator/sample/MySQLSchemaOperatorE2E.java @@ -17,6 +17,7 @@ import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.LocalPortForward; +import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; import io.javaoperatorsdk.operator.junit.AbstractOperatorExtension; import io.javaoperatorsdk.operator.junit.E2EOperatorExtension; @@ -31,25 +32,20 @@ class MySQLSchemaOperatorE2E { - final static Logger log = LoggerFactory.getLogger(MySQLSchemaOperatorE2E.class); + static final Logger log = LoggerFactory.getLogger(MySQLSchemaOperatorE2E.class); - final static KubernetesClient client = new DefaultKubernetesClient(); + static final KubernetesClient client = new DefaultKubernetesClient(); - final static String MY_SQL_NS = "mysql"; + static final String MY_SQL_NS = "mysql"; private static List infrastructure = new ArrayList<>(); + static { - infrastructure - .add(new NamespaceBuilder() - .withNewMetadata() - .withName(MY_SQL_NS) - .endMetadata() - .build()); + infrastructure.add( + new NamespaceBuilder().withNewMetadata().withName(MY_SQL_NS).endMetadata().build()); try { - infrastructure.addAll( - client.load(new FileInputStream("k8s/mysql-deployment.yaml")).get()); - infrastructure.addAll( - client.load(new FileInputStream("k8s/mysql-service.yaml")).get()); + infrastructure.addAll(client.load(new FileInputStream("k8s/mysql-deployment.yaml")).get()); + infrastructure.addAll(client.load(new FileInputStream("k8s/mysql-service.yaml")).get()); } catch (FileNotFoundException e) { e.printStackTrace(); } @@ -63,20 +59,26 @@ boolean isLocal() { } @RegisterExtension - AbstractOperatorExtension operator = isLocal() ? OperatorExtension.builder() - .withConfigurationService(DefaultConfigurationService.instance()) - .withReconciler( - new MySQLSchemaReconciler(new MySQLDbConfig("127.0.0.1", "3306", "root", "password"))) - .withInfrastructure(infrastructure) - .build() - : E2EOperatorExtension.builder() - .withConfigurationService(DefaultConfigurationService.instance()) - .withOperatorDeployment( - client.load(new FileInputStream("k8s/operator.yaml")).get()) - .withInfrastructure(infrastructure) - .build(); - - + AbstractOperatorExtension operator = + isLocal() + ? OperatorExtension.builder() + .withConfigurationService(DefaultConfigurationService.instance()) + .withReconciler( + new MySQLSchemaReconciler(), + c -> { + c.replaceDependentResourceConfig( + new DependentResourceSpec( + SchemaDependentResource.class, + new ResourcePollerConfig( + 700, new MySQLDbConfig("127.0.0.1", "3306", "root", "password")))); + }) + .withInfrastructure(infrastructure) + .build() + : E2EOperatorExtension.builder() + .withConfigurationService(DefaultConfigurationService.instance()) + .withOperatorDeployment(client.load(new FileInputStream("k8s/operator.yaml")).get()) + .withInfrastructure(infrastructure) + .build(); public MySQLSchemaOperatorE2E() throws FileNotFoundException {} @@ -85,28 +87,23 @@ public void test() throws IOException { // Opening a port-forward if running locally LocalPortForward portForward = null; if (isLocal()) { - String podName = client - .pods() - .inNamespace(MY_SQL_NS) - .withLabel("app", "mysql") - .list() - .getItems() - .get(0) - .getMetadata() - .getName(); - - portForward = client - .pods() - .inNamespace(MY_SQL_NS) - .withName(podName) - .portForward(3306, 3306); + String podName = + client + .pods() + .inNamespace(MY_SQL_NS) + .withLabel("app", "mysql") + .list() + .getItems() + .get(0) + .getMetadata() + .getName(); + + portForward = client.pods().inNamespace(MY_SQL_NS).withName(podName).portForward(3306, 3306); } MySQLSchema testSchema = new MySQLSchema(); - testSchema.setMetadata(new ObjectMetaBuilder() - .withName("mydb1") - .withNamespace(operator.getNamespace()) - .build()); + testSchema.setMetadata( + new ObjectMetaBuilder().withName("mydb1").withNamespace(operator.getNamespace()).build()); testSchema.setSpec(new SchemaSpec()); testSchema.getSpec().setEncoding("utf8"); @@ -114,19 +111,25 @@ public void test() throws IOException { client.resource(testSchema).createOrReplace(); log.info("Waiting 2 minutes for expected resources to be created and updated"); - await().atMost(2, MINUTES).ignoreExceptions().untilAsserted(() -> { - MySQLSchema updatedSchema = - client.resources(MySQLSchema.class).inNamespace(operator.getNamespace()) - .withName(testSchema.getMetadata().getName()).get(); - assertThat(updatedSchema.getStatus(), is(notNullValue())); - assertThat(updatedSchema.getStatus().getStatus(), equalTo("CREATED")); - assertThat(updatedSchema.getStatus().getSecretName(), is(notNullValue())); - assertThat(updatedSchema.getStatus().getUserName(), is(notNullValue())); - }); + await() + .atMost(2, MINUTES) + .ignoreExceptions() + .untilAsserted( + () -> { + MySQLSchema updatedSchema = + client + .resources(MySQLSchema.class) + .inNamespace(operator.getNamespace()) + .withName(testSchema.getMetadata().getName()) + .get(); + assertThat(updatedSchema.getStatus(), is(notNullValue())); + assertThat(updatedSchema.getStatus().getStatus(), equalTo("CREATED")); + assertThat(updatedSchema.getStatus().getSecretName(), is(notNullValue())); + assertThat(updatedSchema.getStatus().getUserName(), is(notNullValue())); + }); if (portForward != null) { portForward.close(); } } - } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java index b681ccf6b6..a0c22ce9b1 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java @@ -3,18 +3,19 @@ import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; -import io.javaoperatorsdk.operator.api.config.dependent.KubernetesDependent; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; @KubernetesDependent(labelSelector = "app.kubernetes.io/managed-by=tomcat-operator") -public class DeploymentDependentResource - extends KubernetesDependentResource { +public class DeploymentDependentResource extends KubernetesDependentResource { - public DeploymentDependentResource() {} + private static String tomcatImage(Tomcat tomcat) { + return "tomcat:" + tomcat.getSpec().getVersion(); + } @Override - public Deployment desired(Tomcat tomcat, Context context) { + protected Deployment desired(Tomcat tomcat, Context context) { Deployment deployment = TomcatReconciler.loadYaml(Deployment.class, "deployment.yaml"); final ObjectMeta tomcatMetadata = tomcat.getMetadata(); final String tomcatName = tomcatMetadata.getName(); @@ -31,7 +32,8 @@ public Deployment desired(Tomcat tomcat, Context context) { .withReplicas(tomcat.getSpec().getReplicas()) // set tomcat version .editTemplate() - // make sure label selector matches label (which has to be matched by service selector too) + // make sure label selector matches label (which has to be matched by service selector + // too) .editMetadata().addToLabels("app", tomcatName).endMetadata() .editSpec() .editFirstContainer().withImage(tomcatImage(tomcat)).endContainer() @@ -42,13 +44,10 @@ public Deployment desired(Tomcat tomcat, Context context) { return deployment; } - private String tomcatImage(Tomcat tomcat) { - return "tomcat:" + tomcat.getSpec().getVersion(); - } - @Override public boolean match(Deployment fetched, Deployment target, Context context) { return fetched.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() .equals(target.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); } + } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java index 16a6aaff2e..df690f6abd 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java @@ -4,14 +4,12 @@ import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; public class ServiceDependentResource extends KubernetesDependentResource { - public ServiceDependentResource() {} - @Override - public Service desired(Tomcat tomcat, Context context) { + protected Service desired(Tomcat tomcat, Context context) { final ObjectMeta tomcatMetadata = tomcat.getMetadata(); return new ServiceBuilder(TomcatReconciler.loadYaml(Service.class, "service.yaml")) .editMetadata() @@ -23,4 +21,5 @@ public Service desired(Tomcat tomcat, Context context) { .endSpec() .build(); } + } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java index d2d2a6c96c..c84ff61eb1 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatReconciler.java @@ -7,15 +7,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentStatus; import io.fabric8.kubernetes.client.utils.Serialization; -import io.javaoperatorsdk.operator.api.config.dependent.Dependent; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; @@ -26,8 +25,8 @@ @ControllerConfiguration( finalizerName = NO_FINALIZER, dependents = { - @Dependent(resourceType = Deployment.class, type = DeploymentDependentResource.class), - @Dependent(resourceType = Service.class, type = ServiceDependentResource.class) + @Dependent(type = DeploymentDependentResource.class), + @Dependent(type = ServiceDependentResource.class) }) public class TomcatReconciler implements Reconciler { diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index 28d6ed3037..cc9bce6c6d 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -15,8 +15,9 @@ import io.fabric8.kubernetes.client.utils.Serialization; import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.StandaloneKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; @@ -30,9 +31,9 @@ public class WebPageReconciler private final KubernetesClient kubernetesClient; - private StandaloneKubernetesDependentResource configMapDR; - private StandaloneKubernetesDependentResource deploymentDR; - private StandaloneKubernetesDependentResource serviceDR; + private KubernetesDependentResource configMapDR; + private KubernetesDependentResource deploymentDR; + private KubernetesDependentResource serviceDR; public WebPageReconciler(KubernetesClient kubernetesClient) { this.kubernetesClient = kubernetesClient; @@ -42,9 +43,9 @@ public WebPageReconciler(KubernetesClient kubernetesClient) { @Override public List prepareEventSources(EventSourceContext context) { List eventSources = new ArrayList<>(3); - configMapDR.eventSource(context).ifPresent(es -> eventSources.add(es)); - deploymentDR.eventSource(context).ifPresent(es -> eventSources.add(es)); - serviceDR.eventSource(context).ifPresent(es -> eventSources.add(es)); + configMapDR.eventSource(context).ifPresent(eventSources::add); + deploymentDR.eventSource(context).ifPresent(eventSources::add); + serviceDR.eventSource(context).ifPresent(eventSources::add); return eventSources; } @@ -83,97 +84,71 @@ public Optional updateErrorStatus( } private void createDependentResources(KubernetesClient client) { - this.configMapDR = - new StandaloneKubernetesDependentResource<>( - client, - ConfigMap.class, - (WebPage webPage, Context context) -> { - Map data = new HashMap<>(); - data.put("index.html", webPage.getSpec().getHtml()); - return new ConfigMapBuilder() - .withMetadata( - new ObjectMetaBuilder() - .withName(configMapName(webPage)) - .withNamespace(webPage.getMetadata().getNamespace()) - .build()) - .withData(data) - .build(); - }) { - @Override - protected boolean match(ConfigMap actual, ConfigMap target, Context context) { - return StringUtils.equals( - actual.getData().get("index.html"), target.getData().get("index.html")); - } + this.configMapDR = new ConfigMapDependentResource(); + + this.deploymentDR = + new KubernetesDependentResource<>() { @Override - protected ConfigMap update( - ConfigMap actual, ConfigMap target, WebPage primary, Context context) { - var cm = super.update(actual, target, primary, context); - var ns = actual.getMetadata().getNamespace(); - log.info("Restarting pods because HTML has changed in {}", ns); - kubernetesClient - .pods() - .inNamespace(ns) - .withLabel("app", deploymentName(primary)) - .delete(); - return cm; + protected Deployment desired(WebPage webPage, Context context) { + var deploymentName = deploymentName(webPage); + Deployment deployment = loadYaml(Deployment.class, "deployment.yaml"); + deployment.getMetadata().setName(deploymentName); + deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); + + deployment + .getSpec() + .getTemplate() + .getMetadata() + .getLabels() + .put("app", deploymentName); + deployment + .getSpec() + .getTemplate() + .getSpec() + .getVolumes() + .get(0) + .setConfigMap( + new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); + return deployment; } - }; - configMapDR.setAssociatedSecondaryResourceIdentifier( - primary -> new ResourceID(configMapName(primary), primary.getMetadata().getNamespace())); - this.deploymentDR = - new StandaloneKubernetesDependentResource<>( - client, - Deployment.class, - (webPage, context) -> { - var deploymentName = deploymentName(webPage); - Deployment deployment = loadYaml(Deployment.class, "deployment.yaml"); - deployment.getMetadata().setName(deploymentName); - deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); - deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); - - deployment - .getSpec() - .getTemplate() - .getMetadata() - .getLabels() - .put("app", deploymentName); - deployment - .getSpec() - .getTemplate() - .getSpec() - .getVolumes() - .get(0) - .setConfigMap( - new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); - return deployment; - }) { @Override protected boolean match(Deployment actual, Deployment target, Context context) { // todo comparator return true; } + + @Override + protected Class resourceType() { + return Deployment.class; + } }; this.serviceDR = - new StandaloneKubernetesDependentResource<>( - client, - Service.class, - (webPage, context) -> { - Service service = loadYaml(Service.class, "service.yaml"); - service.getMetadata().setName(serviceName(webPage)); - service.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); - Map labels = new HashMap<>(); - labels.put("app", deploymentName(webPage)); - service.getSpec().setSelector(labels); - return service; - }) { + new KubernetesDependentResource<>() { + + @Override + protected Service desired(WebPage webPage, Context context) { + Service service = loadYaml(Service.class, "service.yaml"); + service.getMetadata().setName(serviceName(webPage)); + service.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + Map labels = new HashMap<>(); + labels.put("app", deploymentName(webPage)); + service.getSpec().setSelector(labels); + return service; + } protected boolean match(Service actual, Service target, Context context) { // todo comparator return true; } + + @Override + protected Class resourceType() { + return Service.class; + } }; } @@ -196,4 +171,48 @@ private T loadYaml(Class clazz, String yaml) { throw new IllegalStateException("Cannot find yaml on classpath: " + yaml); } } + + private class ConfigMapDependentResource extends KubernetesDependentResource + implements + AssociatedSecondaryResourceIdentifier { + + @Override + protected ConfigMap desired(WebPage webPage, Context context) { + Map data = new HashMap<>(); + data.put("index.html", webPage.getSpec().getHtml()); + return new ConfigMapBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName(WebPageReconciler.configMapName(webPage)) + .withNamespace(webPage.getMetadata().getNamespace()) + .build()) + .withData(data) + .build(); + } + + @Override + protected boolean match(ConfigMap actual, ConfigMap target, Context context) { + return StringUtils.equals( + actual.getData().get("index.html"), target.getData().get("index.html")); + } + + @Override + protected ConfigMap update( + ConfigMap actual, ConfigMap target, WebPage primary, Context context) { + var cm = super.update(actual, target, primary, context); + var ns = actual.getMetadata().getNamespace(); + log.info("Restarting pods because HTML has changed in {}", ns); + kubernetesClient + .pods() + .inNamespace(ns) + .withLabel("app", deploymentName(primary)) + .delete(); + return cm; + } + + @Override + public ResourceID associatedSecondaryID(WebPage primary) { + return new ResourceID(configMapName(primary), primary.getMetadata().getNamespace()); + } + } }