Skip to content

feat: initial support for dependent resources #726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public Operator(ConfigurationService configurationService) {
public Operator(KubernetesClient kubernetesClient, ConfigurationService configurationService) {
this.kubernetesClient = kubernetesClient;
this.configurationService = configurationService;
ExecutorServiceManager.init(configurationService);
}

/** Adds a shutdown hook that automatically calls {@link #close()} when the app shuts down. */
Expand Down Expand Up @@ -85,7 +86,6 @@ public void start() {
throw new OperatorException(error, e);
}

ExecutorServiceManager.init(configurationService);
controllers.start();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Locale;

import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;

Expand All @@ -19,7 +20,7 @@ public static String getNameFor(Class<? extends Reconciler> reconcilerClass) {
final var annotation = reconcilerClass.getAnnotation(ControllerConfiguration.class);
if (annotation != null) {
final var name = annotation.name();
if (!ControllerConfiguration.EMPTY_STRING.equals(name)) {
if (!Constants.EMPTY_STRING.equals(name)) {
return name;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

public interface Cloner {

/**
* Returns a deep copy of the given object if not {@code null} or {@code null} otherwise.
*
* @param object the object to be cloned
* @param <R> the type of the object to be cloned
* @return a deep copy of the given object if it isn't {@code null}, {@code null} otherwise
*/
<R extends HasMetadata> R clone(R object);

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public interface ConfigurationService {

@Override
public HasMetadata clone(HasMetadata object) {
if (object == null) {
return null;
}
try {
return OBJECT_MAPPER.readValue(OBJECT_MAPPER.writeValueAsString(object), object.getClass());
} catch (JsonProcessingException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,117 +1,45 @@
package io.javaoperatorsdk.operator.api.config;

import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.Set;
import java.util.List;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.CustomResource;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;

public interface ControllerConfiguration<R extends HasMetadata> {
public interface ControllerConfiguration<R extends HasMetadata> extends
ResourceConfiguration<R, ControllerConfiguration<R>> {

default String getName() {
return ReconcilerUtils.getDefaultReconcilerName(getAssociatedReconcilerClassName());
}

default String getResourceTypeName() {
return CustomResource.getCRDName(getResourceClass());
}

default String getFinalizer() {
return ReconcilerUtils.getDefaultFinalizerName(getResourceTypeName());
}

/**
* Retrieves the label selector that is used to filter which custom resources are actually watched
* by the associated controller. See
* https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more details on
* syntax.
*
* @return the label selector filtering watched custom resources
*/
default String getLabelSelector() {
return null;
}

default boolean isGenerationAware() {
return true;
}

default Class<R> getResourceClass() {
ParameterizedType type = (ParameterizedType) getClass().getGenericInterfaces()[0];
return (Class<R>) type.getActualTypeArguments()[0];
}

String getAssociatedReconcilerClassName();

default Set<String> getNamespaces() {
return Collections.emptySet();
}

default boolean watchAllNamespaces() {
return allNamespacesWatched(getNamespaces());
}

static boolean allNamespacesWatched(Set<String> namespaces) {
return namespaces == null || namespaces.isEmpty();
}

default boolean watchCurrentNamespace() {
return currentNamespaceWatched(getNamespaces());
}

static boolean currentNamespaceWatched(Set<String> namespaces) {
return namespaces != null
&& namespaces.size() == 1
&& namespaces.contains(
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.WATCH_CURRENT_NAMESPACE);
}

/**
* Computes the effective namespaces based on the set specified by the user, in particular
* retrieves the current namespace from the client when the user specified that they wanted to
* watch the current namespace only.
*
* @return a Set of namespace names the associated controller will watch
*/
default Set<String> getEffectiveNamespaces() {
var targetNamespaces = getNamespaces();
if (watchCurrentNamespace()) {
final var parent = getConfigurationService();
if (parent == null) {
throw new IllegalStateException(
"Parent ConfigurationService must be set before calling this method");
}
targetNamespaces = Collections.singleton(parent.getClientConfiguration().getNamespace());
}
return targetNamespaces;
}

default RetryConfiguration getRetryConfiguration() {
return RetryConfiguration.DEFAULT;
}

ConfigurationService getConfigurationService();

default void setConfigurationService(ConfigurationService service) {}

default boolean useFinalizer() {
return !io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.NO_FINALIZER
.equals(getFinalizer());
return !Constants.NO_FINALIZER.equals(getFinalizer());
}

@Override
default ResourceEventFilter<R, ControllerConfiguration<R>> getEventFilter() {
return ResourceConfiguration.super.getEventFilter();
}

/**
* Allow controllers to filter events before they are provided to the
* {@link io.javaoperatorsdk.operator.processing.event.EventHandler}. Note that the provided
* filter is combined with {@link #isGenerationAware()} to compute the final set of fiolters that
* should be applied;
*
* @return filter
*/
default ResourceEventFilter<R> getEventFilter() {
return ResourceEventFilters.passthrough();
default List<? extends DependentResource> getDependentResources() {
return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class ControllerConfigurationOverrider<R extends HasMetadata> {
private final Set<String> namespaces;
private RetryConfiguration retry;
private String labelSelector;
private ResourceEventFilter<R> customResourcePredicate;
private ResourceEventFilter<R, ControllerConfiguration<R>> customResourcePredicate;
private final ControllerConfiguration<R> original;

private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
Expand Down Expand Up @@ -69,7 +69,7 @@ public ControllerConfigurationOverrider<R> withLabelSelector(String labelSelecto
}

public ControllerConfigurationOverrider<R> withCustomResourcePredicate(
ResourceEventFilter<R> customResourcePredicate) {
ResourceEventFilter<R, ControllerConfiguration<R>> customResourcePredicate) {
this.customResourcePredicate = customResourcePredicate;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,125 +1,77 @@
package io.javaoperatorsdk.operator.api.config;

import java.util.Collections;
import java.util.Set;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;

public class DefaultControllerConfiguration<R extends HasMetadata>
extends DefaultResourceConfiguration<R, ControllerConfiguration<R>>
implements ControllerConfiguration<R> {

private final String associatedControllerClassName;
private final String name;
private final String crdName;
private final String finalizer;
private final boolean generationAware;
private final Set<String> namespaces;
private final boolean watchAllNamespaces;
private final RetryConfiguration retryConfiguration;
private final String labelSelector;
private final ResourceEventFilter<R> resourceEventFilter;
private final Class<R> resourceClass;
private ConfigurationService service;
private final ResourceEventFilter<R, ControllerConfiguration<R>> resourceEventFilter;
private final boolean generationAware;

public DefaultControllerConfiguration(
String associatedControllerClassName,
String name,
String crdName,
String resourceName,
String finalizer,
boolean generationAware,
Set<String> namespaces,
RetryConfiguration retryConfiguration,
String labelSelector,
ResourceEventFilter<R> resourceEventFilter,
ResourceEventFilter<R, ControllerConfiguration<R>> resourceEventFilter,
Class<R> resourceClass,
ConfigurationService service) {
super(resourceName, resourceClass, namespaces, labelSelector, service);
this.associatedControllerClassName = associatedControllerClassName;
this.name = name;
this.crdName = crdName;
this.finalizer = finalizer;
this.generationAware = generationAware;
this.namespaces =
namespaces != null ? Collections.unmodifiableSet(namespaces) : Collections.emptySet();
this.watchAllNamespaces = this.namespaces.isEmpty();
this.retryConfiguration =
retryConfiguration == null
? ControllerConfiguration.super.getRetryConfiguration()
: retryConfiguration;
this.labelSelector = labelSelector;
this.resourceEventFilter = resourceEventFilter;
this.resourceClass =
resourceClass == null ? ControllerConfiguration.super.getResourceClass()
: resourceClass;
setConfigurationService(service);
}

@Override
public String getName() {
return name;
}

@Override
public String getResourceTypeName() {
return crdName;
}

@Override
public String getFinalizer() {
return finalizer;
}

@Override
public boolean isGenerationAware() {
return generationAware;
}

@Override
public String getAssociatedReconcilerClassName() {
return associatedControllerClassName;
}

@Override
public Set<String> getNamespaces() {
return namespaces;
}

@Override
public boolean watchAllNamespaces() {
return watchAllNamespaces;
}

@Override
public RetryConfiguration getRetryConfiguration() {
return retryConfiguration;
}

@Override
public ConfigurationService getConfigurationService() {
return service;
}

@Override
public void setConfigurationService(ConfigurationService service) {
if (this.service != null) {
throw new RuntimeException("A ConfigurationService is already associated with '" + name
+ "' ControllerConfiguration. Cannot change it once set!");
}
this.service = service;
}

@Override
public String getLabelSelector() {
return labelSelector;
public boolean isGenerationAware() {
return generationAware;
}

@Override
public Class<R> getResourceClass() {
return resourceClass;
public ResourceEventFilter<R, ControllerConfiguration<R>> getEventFilter() {
return resourceEventFilter;
}

@Override
public ResourceEventFilter<R> getEventFilter() {
return resourceEventFilter;
protected String identifierForException() {
return "'" + name + "' ControllerConfiguration";
}
}
Loading