Skip to content

Commit 0c09a5d

Browse files
committed
feat: support for declarative management of dependent resources
- Kubernetes-native resources are automatically handled without users having to deal with explicit event sources - Reconcilers and event sources can exchange information via the `EventSourceContext` - Declarative management is completely opt-in and can cohabit with "traditional" handling i.e. it's possible to have only part of the dependents being declared via annotations and others explicitly handled (though from a maintenance perspective it's better to stick to one approach or the other)
1 parent c43965e commit 0c09a5d

File tree

78 files changed

+1659
-673
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+1659
-673
lines changed

micrometer-support/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<artifactId>java-operator-sdk</artifactId>
77
<groupId>io.javaoperatorsdk</groupId>
8-
<version>2.0.2-SNAPSHOT</version>
8+
<version>2.1.0-SNAPSHOT</version>
99
</parent>
1010
<modelVersion>4.0.0</modelVersion>
1111

operator-framework-core/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>io.javaoperatorsdk</groupId>
88
<artifactId>java-operator-sdk</artifactId>
9-
<version>2.0.2-SNAPSHOT</version>
9+
<version>2.1.0-SNAPSHOT</version>
1010
<relativePath>../pom.xml</relativePath>
1111
</parent>
1212

Original file line numberDiff line numberDiff line change
@@ -1,103 +1,36 @@
11
package io.javaoperatorsdk.operator.api.config;
22

3-
import java.lang.reflect.ParameterizedType;
43
import java.util.Collections;
5-
import java.util.Set;
4+
import java.util.List;
65

76
import io.fabric8.kubernetes.api.model.HasMetadata;
87
import io.javaoperatorsdk.operator.ReconcilerUtils;
98
import io.javaoperatorsdk.operator.api.reconciler.Constants;
9+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceControllerFactory;
1010
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
1111
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
1212

13-
public interface ControllerConfiguration<R extends HasMetadata> {
13+
@SuppressWarnings("rawtypes")
14+
public interface ControllerConfiguration<R extends HasMetadata> extends ResourceConfiguration<R> {
1415

1516
default String getName() {
1617
return ReconcilerUtils.getDefaultReconcilerName(getAssociatedReconcilerClassName());
1718
}
1819

19-
default String getResourceTypeName() {
20-
return ReconcilerUtils.getResourceTypeName(getResourceClass());
21-
}
22-
2320
default String getFinalizer() {
2421
return ReconcilerUtils.getDefaultFinalizerName(getResourceClass());
2522
}
2623

27-
/**
28-
* Retrieves the label selector that is used to filter which custom resources are actually watched
29-
* by the associated controller. See
30-
* https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more details on
31-
* syntax.
32-
*
33-
* @return the label selector filtering watched custom resources
34-
*/
35-
default String getLabelSelector() {
36-
return null;
37-
}
38-
3924
default boolean isGenerationAware() {
4025
return true;
4126
}
4227

43-
default Class<R> getResourceClass() {
44-
ParameterizedType type = (ParameterizedType) getClass().getGenericInterfaces()[0];
45-
return (Class<R>) type.getActualTypeArguments()[0];
46-
}
47-
4828
String getAssociatedReconcilerClassName();
4929

50-
default Set<String> getNamespaces() {
51-
return Collections.emptySet();
52-
}
53-
54-
default boolean watchAllNamespaces() {
55-
return allNamespacesWatched(getNamespaces());
56-
}
57-
58-
static boolean allNamespacesWatched(Set<String> namespaces) {
59-
return namespaces == null || namespaces.isEmpty();
60-
}
61-
62-
default boolean watchCurrentNamespace() {
63-
return currentNamespaceWatched(getNamespaces());
64-
}
65-
66-
static boolean currentNamespaceWatched(Set<String> namespaces) {
67-
return namespaces != null
68-
&& namespaces.size() == 1
69-
&& namespaces.contains(
70-
Constants.WATCH_CURRENT_NAMESPACE);
71-
}
72-
73-
/**
74-
* Computes the effective namespaces based on the set specified by the user, in particular
75-
* retrieves the current namespace from the client when the user specified that they wanted to
76-
* watch the current namespace only.
77-
*
78-
* @return a Set of namespace names the associated controller will watch
79-
*/
80-
default Set<String> getEffectiveNamespaces() {
81-
var targetNamespaces = getNamespaces();
82-
if (watchCurrentNamespace()) {
83-
final var parent = getConfigurationService();
84-
if (parent == null) {
85-
throw new IllegalStateException(
86-
"Parent ConfigurationService must be set before calling this method");
87-
}
88-
targetNamespaces = Collections.singleton(parent.getClientConfiguration().getNamespace());
89-
}
90-
return targetNamespaces;
91-
}
92-
9330
default RetryConfiguration getRetryConfiguration() {
9431
return RetryConfiguration.DEFAULT;
9532
}
9633

97-
ConfigurationService getConfigurationService();
98-
99-
default void setConfigurationService(ConfigurationService service) {}
100-
10134
default boolean useFinalizer() {
10235
return !Constants.NO_FINALIZER
10336
.equals(getFinalizer());
@@ -114,4 +47,12 @@ default boolean useFinalizer() {
11447
default ResourceEventFilter<R> getEventFilter() {
11548
return ResourceEventFilters.passthrough();
11649
}
50+
51+
default List<DependentResource> getDependentResources() {
52+
return Collections.emptyList();
53+
}
54+
55+
default DependentResourceControllerFactory<R> dependentFactory() {
56+
return new DependentResourceControllerFactory<>() {};
57+
}
11758
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ public ControllerConfiguration<R> build() {
8686
labelSelector,
8787
customResourcePredicate,
8888
original.getResourceClass(),
89-
original.getConfigurationService());
89+
original.getConfigurationService(),
90+
original.getDependentResources());
9091
}
9192

9293
public static <R extends HasMetadata> ControllerConfigurationOverrider<R> override(

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

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.javaoperatorsdk.operator.api.config;
22

33
import java.util.Collections;
4+
import java.util.List;
45
import java.util.Set;
56

67
import io.fabric8.kubernetes.api.model.HasMetadata;
@@ -20,8 +21,10 @@ public class DefaultControllerConfiguration<R extends HasMetadata>
2021
private final String labelSelector;
2122
private final ResourceEventFilter<R> resourceEventFilter;
2223
private final Class<R> resourceClass;
24+
private final List<DependentResource> dependents;
2325
private ConfigurationService service;
2426

27+
// NOSONAR constructor is meant to provide all information
2528
public DefaultControllerConfiguration(
2629
String associatedControllerClassName,
2730
String name,
@@ -33,7 +36,8 @@ public DefaultControllerConfiguration(
3336
String labelSelector,
3437
ResourceEventFilter<R> resourceEventFilter,
3538
Class<R> resourceClass,
36-
ConfigurationService service) {
39+
ConfigurationService service,
40+
List<DependentResource> dependents) {
3741
this.associatedControllerClassName = associatedControllerClassName;
3842
this.name = name;
3943
this.crdName = crdName;
@@ -52,6 +56,7 @@ public DefaultControllerConfiguration(
5256
resourceClass == null ? ControllerConfiguration.super.getResourceClass()
5357
: resourceClass;
5458
setConfigurationService(service);
59+
this.dependents = dependents != null ? dependents : Collections.emptyList();
5560
}
5661

5762
@Override
@@ -102,7 +107,7 @@ public ConfigurationService getConfigurationService() {
102107
@Override
103108
public void setConfigurationService(ConfigurationService service) {
104109
if (this.service != null) {
105-
throw new RuntimeException("A ConfigurationService is already associated with '" + name
110+
throw new IllegalStateException("A ConfigurationService is already associated with '" + name
106111
+ "' ControllerConfiguration. Cannot change it once set!");
107112
}
108113
this.service = service;
@@ -122,4 +127,9 @@ public Class<R> getResourceClass() {
122127
public ResourceEventFilter<R> getEventFilter() {
123128
return resourceEventFilter;
124129
}
130+
131+
@Override
132+
public List<DependentResource> getDependentResources() {
133+
return dependents;
134+
}
125135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import java.util.Collections;
4+
import java.util.Set;
5+
6+
import io.fabric8.kubernetes.api.model.HasMetadata;
7+
8+
public class DefaultResourceConfiguration<R extends HasMetadata>
9+
implements ResourceConfiguration<R> {
10+
11+
private final String labelSelector;
12+
private final Set<String> namespaces;
13+
private final boolean watchAllNamespaces;
14+
private final Class<R> resourceClass;
15+
private ConfigurationService service;
16+
17+
public DefaultResourceConfiguration(String labelSelector, Class<R> resourceClass,
18+
String... namespaces) {
19+
this(labelSelector, resourceClass,
20+
namespaces != null ? Set.of(namespaces) : Collections.emptySet());
21+
}
22+
23+
public DefaultResourceConfiguration(String labelSelector, Class<R> resourceClass,
24+
Set<String> namespaces) {
25+
this.labelSelector = labelSelector;
26+
this.resourceClass = resourceClass;
27+
this.namespaces = namespaces != null ? namespaces : Collections.emptySet();
28+
this.watchAllNamespaces = this.namespaces.isEmpty();
29+
}
30+
31+
@Override
32+
public String getResourceTypeName() {
33+
return ResourceConfiguration.super.getResourceTypeName();
34+
}
35+
36+
@Override
37+
public String getLabelSelector() {
38+
return labelSelector;
39+
}
40+
41+
@Override
42+
public Set<String> getNamespaces() {
43+
return namespaces;
44+
}
45+
46+
@Override
47+
public boolean watchAllNamespaces() {
48+
return watchAllNamespaces;
49+
}
50+
51+
@Override
52+
public ConfigurationService getConfigurationService() {
53+
return service;
54+
}
55+
56+
@Override
57+
public Class<R> getResourceClass() {
58+
return resourceClass;
59+
}
60+
61+
@Override
62+
public void setConfigurationService(ConfigurationService service) {
63+
this.service = service;
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
public @interface Dependent {
4+
5+
Class<?> resourceType();
6+
7+
Class<? extends DependentResource> type();
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
5+
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
6+
7+
public interface DependentResource<R, P extends HasMetadata> {
8+
default EventSource initEventSource(EventSourceContext<P> context) {
9+
throw new IllegalStateException("Must be implemented if not automatically provided by the SDK");
10+
};
11+
12+
default Class<R> resourceType() {
13+
return (Class<R>) Utils.getFirstTypeArgumentFromInterface(getClass());
14+
}
15+
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public static void init(ConfigurationService configuration) {
3636
log.debug("Initialized ExecutorServiceManager executor: {}, timeout: {}",
3737
configuration.getExecutorService().getClass(),
3838
configuration.getTerminationTimeoutSeconds());
39+
log.debug("Initialized ExecutorServiceManager executor: {}, timeout: {}",
40+
configuration.getExecutorService().getClass(),
41+
configuration.getTerminationTimeoutSeconds());
3942
} else {
4043
log.debug("Already started, reusing already setup instance!");
4144
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Target;
5+
6+
import static io.javaoperatorsdk.operator.api.reconciler.Constants.EMPTY_STRING;
7+
8+
@Target({ElementType.TYPE})
9+
public @interface KubernetesDependent {
10+
11+
boolean OWNED_DEFAULT = true;
12+
boolean SKIP_UPDATE_DEFAULT = true;
13+
14+
boolean owned() default OWNED_DEFAULT;
15+
16+
boolean skipUpdateIfUnchanged() default SKIP_UPDATE_DEFAULT;
17+
18+
/**
19+
* Specified which namespaces this Controller monitors for custom resources events. If no
20+
* namespace is specified then the controller will monitor all namespaces by default.
21+
*
22+
* @return the list of namespaces this controller monitors
23+
*/
24+
String[] namespaces() default {};
25+
26+
/**
27+
* Optional label selector used to identify the set of custom resources the controller will acc
28+
* upon. The label selector can be made of multiple comma separated requirements that acts as a
29+
* logical AND operator.
30+
*
31+
* @return the label selector
32+
*/
33+
String labelSelector() default EMPTY_STRING;
34+
}

0 commit comments

Comments
 (0)