Skip to content

Commit f3a3572

Browse files
committed
feat: make it possible to externally configure the operator
The operator gets a client based on the configuration. ResourceControllers now get their client from the operator when they get registered. Spring Boot starter needs to get adapted.
1 parent bc65b1c commit f3a3572

File tree

24 files changed

+449
-283
lines changed

24 files changed

+449
-283
lines changed

operator-framework/src/main/java/io/javaoperatorsdk/operator/Operator.java

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,119 @@
11
package io.javaoperatorsdk.operator;
22

3-
import io.javaoperatorsdk.operator.api.ResourceController;
4-
import io.javaoperatorsdk.operator.processing.EventDispatcher;
5-
import io.javaoperatorsdk.operator.processing.EventScheduler;
6-
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
7-
import io.javaoperatorsdk.operator.processing.retry.Retry;
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Set;
6+
87
import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinition;
8+
import io.fabric8.kubernetes.client.ConfigBuilder;
99
import io.fabric8.kubernetes.client.CustomResource;
1010
import io.fabric8.kubernetes.client.CustomResourceDoneable;
1111
import io.fabric8.kubernetes.client.CustomResourceList;
12+
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
1213
import io.fabric8.kubernetes.client.KubernetesClient;
1314
import io.fabric8.kubernetes.client.dsl.MixedOperation;
1415
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext;
1516
import io.fabric8.kubernetes.client.dsl.internal.CustomResourceOperationsImpl;
1617
import io.fabric8.kubernetes.internal.KubernetesDeserializer;
18+
import io.fabric8.openshift.client.DefaultOpenShiftClient;
19+
import io.javaoperatorsdk.operator.api.ResourceController;
20+
import io.javaoperatorsdk.operator.config.ClientConfiguration;
21+
import io.javaoperatorsdk.operator.config.Configuration;
22+
import io.javaoperatorsdk.operator.config.OperatorConfiguration;
23+
import io.javaoperatorsdk.operator.processing.EventDispatcher;
24+
import io.javaoperatorsdk.operator.processing.EventScheduler;
25+
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
26+
import io.javaoperatorsdk.operator.processing.retry.Retry;
27+
import org.apache.commons.lang3.StringUtils;
1728
import org.slf4j.Logger;
1829
import org.slf4j.LoggerFactory;
1930

20-
import java.util.Arrays;
21-
import java.util.HashMap;
22-
import java.util.Map;
23-
2431
@SuppressWarnings("rawtypes")
2532
public class Operator {
26-
33+
2734
private final static Logger log = LoggerFactory.getLogger(Operator.class);
2835
private final KubernetesClient k8sClient;
36+
private final Configuration config;
2937
private Map<Class<? extends CustomResource>, CustomResourceOperationsImpl> customResourceClients = new HashMap<>();
30-
38+
3139
public Operator(KubernetesClient k8sClient) {
3240
this.k8sClient = k8sClient;
41+
this.config = Configuration.defaultConfiguration();
3342
}
34-
35-
36-
public <R extends CustomResource> void registerControllerForAllNamespaces(ResourceController<R> controller) throws OperatorException {
37-
registerController(controller, true, GenericRetry.defaultLimitedExponentialRetry());
43+
44+
public Operator() {
45+
this(Configuration.defaultConfiguration());
3846
}
39-
40-
public <R extends CustomResource> void registerControllerForAllNamespaces(ResourceController<R> controller, Retry retry) throws OperatorException {
41-
registerController(controller, true, retry);
47+
48+
public Operator(Configuration config) {
49+
this.config = config;
50+
ConfigBuilder cb = new ConfigBuilder();
51+
52+
final ClientConfiguration clientCfg = config.getClientConfiguration();
53+
cb.withTrustCerts(clientCfg.isTrustSelfSignedCertificates());
54+
if (StringUtils.isNotBlank(clientCfg.getUsername())) {
55+
cb.withUsername(clientCfg.getUsername());
56+
}
57+
if (StringUtils.isNotBlank(clientCfg.getPassword())) {
58+
cb.withUsername(clientCfg.getPassword());
59+
}
60+
if (StringUtils.isNotBlank(clientCfg.getMasterUrl())) {
61+
cb.withMasterUrl(clientCfg.getMasterUrl());
62+
}
63+
64+
config.getOperatorConfiguration().getWatchedNamespaceIfUnique().ifPresent(cb::withNamespace);
65+
this.k8sClient = clientCfg.isOpenshift() ? new DefaultOpenShiftClient(cb.build()) : new DefaultKubernetesClient(cb.build());
4266
}
43-
44-
public <R extends CustomResource> void registerController(ResourceController<R> controller, String... targetNamespaces) throws OperatorException {
45-
registerController(controller, false, GenericRetry.defaultLimitedExponentialRetry(), targetNamespaces);
67+
68+
public KubernetesClient getClient() {
69+
return k8sClient;
4670
}
47-
48-
public <R extends CustomResource> void registerController(ResourceController<R> controller, Retry retry, String... targetNamespaces) throws OperatorException {
49-
registerController(controller, false, retry, targetNamespaces);
71+
72+
public <R extends CustomResource> void registerController(ResourceController<R> controller) throws OperatorException {
73+
registerController(controller, GenericRetry.defaultLimitedExponentialRetry());
5074
}
51-
75+
5276
@SuppressWarnings("rawtypes")
53-
private <R extends CustomResource> void registerController(ResourceController<R> controller,
54-
boolean watchAllNamespaces, Retry retry, String... targetNamespaces) throws OperatorException {
77+
public <R extends CustomResource> void registerController(ResourceController<R> controller, Retry retry) throws OperatorException {
78+
controller.setClient(this.k8sClient);
5579
Class<R> resClass = ControllerUtils.getCustomResourceClass(controller);
5680
CustomResourceDefinitionContext crd = getCustomResourceDefinitionForController(controller);
5781
KubernetesDeserializer.registerCustomKind(crd.getVersion(), crd.getKind(), resClass);
5882
String finalizer = ControllerUtils.getFinalizer(controller);
5983
MixedOperation client = k8sClient.customResources(crd, resClass, CustomResourceList.class, ControllerUtils.getCustomResourceDoneableClass(controller));
6084
EventDispatcher eventDispatcher = new EventDispatcher(controller,
61-
finalizer, new EventDispatcher.CustomResourceFacade(client), ControllerUtils.getGenerationEventProcessing(controller));
85+
finalizer, new EventDispatcher.CustomResourceFacade(client), ControllerUtils.getGenerationEventProcessing(controller));
6286
EventScheduler eventScheduler = new EventScheduler(eventDispatcher, retry);
63-
registerWatches(controller, client, resClass, watchAllNamespaces, targetNamespaces, eventScheduler);
87+
registerWatches(controller, client, resClass, eventScheduler);
6488
}
65-
66-
89+
90+
6791
private <R extends CustomResource> void registerWatches(ResourceController<R> controller, MixedOperation client,
6892
Class<R> resClass,
69-
boolean watchAllNamespaces, String[] targetNamespaces, EventScheduler eventScheduler) {
70-
93+
EventScheduler eventScheduler) {
94+
7195
CustomResourceOperationsImpl crClient = (CustomResourceOperationsImpl) client;
72-
if (watchAllNamespaces) {
96+
final OperatorConfiguration operatorCfg = config.getOperatorConfiguration();
97+
final String namespaces;
98+
if (operatorCfg.isWatchingAllNamespaces()) {
7399
crClient.inAnyNamespace().watch(eventScheduler);
74-
} else if (targetNamespaces.length == 0) {
100+
namespaces = "all namespaces";
101+
} else if (operatorCfg.isWatchingCurrentNamespace()) {
75102
client.watch(eventScheduler);
103+
namespaces = "client namespace (" + crClient.getNamespace() + ")";
76104
} else {
77-
for (String targetNamespace : targetNamespaces) {
105+
final Set<String> cfgNamespaces = operatorCfg.getNamespaces();
106+
for (String targetNamespace : cfgNamespaces) {
78107
crClient.inNamespace(targetNamespace).watch(eventScheduler);
79108
log.debug("Registered controller for namespace: {}", targetNamespace);
80109
}
110+
namespaces = String.join(", ", cfgNamespaces);
81111
}
82112
customResourceClients.put(resClass, (CustomResourceOperationsImpl) client);
83113
log.info("Registered Controller: '{}' for CRD: '{}' for namespaces: {}", controller.getClass().getSimpleName(),
84-
resClass, targetNamespaces.length == 0 ? "[all/client namespace]" : Arrays.toString(targetNamespaces));
114+
resClass, namespaces);
85115
}
86-
116+
87117
private CustomResourceDefinitionContext getCustomResourceDefinitionForController(ResourceController controller) {
88118
String crdName = ControllerUtils.getCrdName(controller);
89119
CustomResourceDefinition customResourceDefinition = k8sClient.customResourceDefinitions().withName(crdName).get();
@@ -93,20 +123,20 @@ private CustomResourceDefinitionContext getCustomResourceDefinitionForController
93123
CustomResourceDefinitionContext context = CustomResourceDefinitionContext.fromCrd(customResourceDefinition);
94124
return context;
95125
}
96-
126+
97127
public Map<Class<? extends CustomResource>, CustomResourceOperationsImpl> getCustomResourceClients() {
98128
return customResourceClients;
99129
}
100-
130+
101131
public <T extends CustomResource, L extends CustomResourceList<T>, D extends CustomResourceDoneable<T>> CustomResourceOperationsImpl<T, L, D>
102132
getCustomResourceClients(Class<T> customResourceClass) {
103133
return customResourceClients.get(customResourceClass);
104134
}
105-
135+
106136
private String getKind(CustomResourceDefinition crd) {
107137
return crd.getSpec().getNames().getKind();
108138
}
109-
139+
110140
private String getApiVersion(CustomResourceDefinition crd) {
111141
return crd.getSpec().getGroup() + "/" + crd.getSpec().getVersion();
112142
}

operator-framework/src/main/java/io/javaoperatorsdk/operator/api/ResourceController.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.javaoperatorsdk.operator.api;
22

33
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.fabric8.kubernetes.client.KubernetesClient;
45

56
public interface ResourceController<R extends CustomResource> {
67

@@ -27,4 +28,6 @@ public interface ResourceController<R extends CustomResource> {
2728
* <b>However we will always call an update if there is no finalizer on object and its not marked for deletion.</b>
2829
*/
2930
UpdateControl<R> createOrUpdateResource(R resource, Context<R> context);
31+
32+
void setClient(KubernetesClient client);
3033
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.javaoperatorsdk.operator.config;
2+
3+
public class ClientConfiguration {
4+
private boolean openshift = false;
5+
private String username;
6+
private String password;
7+
private String masterUrl;
8+
private boolean trustSelfSignedCertificates = false;
9+
10+
public boolean isOpenshift() {
11+
return openshift;
12+
}
13+
14+
public ClientConfiguration setOpenshift(boolean openshift) {
15+
this.openshift = openshift;
16+
return this;
17+
}
18+
19+
public String getUsername() {
20+
return username;
21+
}
22+
23+
public ClientConfiguration setUsername(String username) {
24+
this.username = username;
25+
return this;
26+
}
27+
28+
public String getPassword() {
29+
return password;
30+
}
31+
32+
public ClientConfiguration setPassword(String password) {
33+
this.password = password;
34+
return this;
35+
}
36+
37+
public String getMasterUrl() {
38+
return masterUrl;
39+
}
40+
41+
public ClientConfiguration setMasterUrl(String masterUrl) {
42+
this.masterUrl = masterUrl;
43+
return this;
44+
}
45+
46+
public boolean isTrustSelfSignedCertificates() {
47+
return trustSelfSignedCertificates;
48+
}
49+
50+
public ClientConfiguration setTrustSelfSignedCertificates(boolean trustSelfSignedCertificates) {
51+
this.trustSelfSignedCertificates = trustSelfSignedCertificates;
52+
return this;
53+
}
54+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.javaoperatorsdk.operator.config;
2+
3+
public class Configuration {
4+
private ClientConfiguration client;
5+
private OperatorConfiguration operator;
6+
7+
public ClientConfiguration getClientConfiguration() {
8+
return client;
9+
}
10+
11+
public OperatorConfiguration getOperatorConfiguration() {
12+
return operator;
13+
}
14+
15+
public static Configuration defaultConfiguration() {
16+
return new Configuration();
17+
}
18+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.javaoperatorsdk.operator.config;
2+
3+
import java.util.Arrays;
4+
import java.util.HashSet;
5+
import java.util.Optional;
6+
import java.util.Set;
7+
8+
public class OperatorConfiguration {
9+
10+
private Set<String> namespaces = new HashSet<>();
11+
public static String ALL_NAMESPACES = "all_namespaces";
12+
13+
public Set<String> getNamespaces() {
14+
return namespaces;
15+
}
16+
17+
public OperatorConfiguration setNamespaces(Set<String> namespaces) {
18+
this.namespaces = namespaces;
19+
return this;
20+
}
21+
22+
public OperatorConfiguration addWatchedNamespaces(String... namespaces) {
23+
this.namespaces.addAll(Arrays.asList(namespaces));
24+
return this;
25+
}
26+
27+
public OperatorConfiguration watchAllNamespaces() {
28+
this.namespaces.add(ALL_NAMESPACES);
29+
return this;
30+
}
31+
32+
public boolean isWatchingAllNamespaces() {
33+
return namespaces.contains(ALL_NAMESPACES);
34+
}
35+
36+
public boolean isWatchingCurrentNamespace() {
37+
return namespaces.isEmpty();
38+
}
39+
40+
public Optional<String> getWatchedNamespaceIfUnique() {
41+
return namespaces.stream().findFirst();
42+
}
43+
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package io.javaoperatorsdk.operator;
22

3+
import java.util.List;
4+
import java.util.concurrent.TimeUnit;
5+
import java.util.stream.Collectors;
6+
7+
import io.fabric8.kubernetes.api.model.ConfigMap;
38
import io.javaoperatorsdk.operator.sample.TestCustomResource;
49
import io.javaoperatorsdk.operator.sample.TestCustomResourceController;
5-
import io.fabric8.kubernetes.api.model.ConfigMap;
6-
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
7-
import io.fabric8.kubernetes.client.KubernetesClient;
810
import org.awaitility.Awaitility;
911
import org.junit.jupiter.api.BeforeAll;
1012
import org.junit.jupiter.api.BeforeEach;
@@ -13,10 +15,6 @@
1315
import org.slf4j.Logger;
1416
import org.slf4j.LoggerFactory;
1517

16-
import java.util.List;
17-
import java.util.concurrent.TimeUnit;
18-
import java.util.stream.Collectors;
19-
2018
import static org.assertj.core.api.Assertions.assertThat;
2119

2220
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -31,9 +29,7 @@ public class ConcurrencyIT {
3129

3230
@BeforeAll
3331
public void setup() {
34-
KubernetesClient k8sClient = new DefaultKubernetesClient();
35-
integrationTest.initialize(k8sClient, new TestCustomResourceController(k8sClient, true),
36-
"test-crd.yaml");
32+
integrationTest.initialize(new TestCustomResourceController(true), "test-crd.yaml");
3733
}
3834

3935
@BeforeEach

operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
package io.javaoperatorsdk.operator;
22

3+
import java.util.HashMap;
4+
import java.util.concurrent.TimeUnit;
5+
6+
import io.fabric8.kubernetes.api.model.ConfigMap;
7+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
38
import io.javaoperatorsdk.operator.sample.TestCustomResource;
49
import io.javaoperatorsdk.operator.sample.TestCustomResourceController;
510
import io.javaoperatorsdk.operator.sample.TestCustomResourceSpec;
6-
import io.fabric8.kubernetes.api.model.ConfigMap;
7-
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
8-
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
9-
import io.fabric8.kubernetes.client.KubernetesClient;
1011
import org.junit.jupiter.api.Test;
1112
import org.junit.jupiter.api.TestInstance;
1213
import org.slf4j.Logger;
1314
import org.slf4j.LoggerFactory;
1415

15-
import java.util.HashMap;
16-
import java.util.concurrent.TimeUnit;
17-
1816
import static org.assertj.core.api.Assertions.assertThat;
1917
import static org.awaitility.Awaitility.await;
2018

@@ -25,8 +23,7 @@ public class ControllerExecutionIT {
2523
private IntegrationTestSupport integrationTestSupport = new IntegrationTestSupport();
2624

2725
public void initAndCleanup(boolean controllerStatusUpdate) {
28-
KubernetesClient k8sClient = new DefaultKubernetesClient();
29-
integrationTestSupport.initialize(k8sClient, new TestCustomResourceController(k8sClient, controllerStatusUpdate), "test-crd.yaml");
26+
integrationTestSupport.initialize(new TestCustomResourceController(controllerStatusUpdate), "test-crd.yaml");
3027
integrationTestSupport.cleanup();
3128
}
3229

0 commit comments

Comments
 (0)