Skip to content

Commit 8a58f48

Browse files
authored
Merge pull request #238 from halkyonio/quarkus-support
Extract controller configuration handling and provide Quarkus extension
2 parents 488d0ef + 06033fc commit 8a58f48

File tree

122 files changed

+1917
-421
lines changed

Some content is hidden

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

122 files changed

+1917
-421
lines changed

README.md

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,15 @@ Add [dependency](https://search.maven.org/search?q=a:operator-framework) to your
6363
</dependency>
6464
```
6565

66-
Main method initializing the Operator and registering a controller..
66+
Main method initializing the Operator and registering a controller.
6767

6868
```java
6969
public class Runner {
7070

7171
public static void main(String[] args) {
72-
Operator operator = new Operator(new DefaultKubernetesClient());
73-
operator.registerController(new WebServerController());
72+
Operator operator = new Operator(new DefaultKubernetesClient(),
73+
DefaultConfigurationService.instance());
74+
operator.register(new WebServerController());
7475
}
7576
}
7677
```
@@ -135,18 +136,62 @@ public class WebServerSpec {
135136
}
136137
}
137138
```
139+
140+
#### Quarkus
141+
142+
A [Quarkus](https://quarkus.io) extension is also provided to ease the development of Quarkus-based operators.
143+
144+
Add [this dependency] (https://search.maven.org/search?q=a:operator-framework-quarkus-extension)
145+
to your project:
146+
147+
```xml
148+
<dependency>
149+
<groupId>io.javaoperatorsdk</groupId>
150+
<artifactId>operator-framework-quarkus-extension</artifactId>
151+
<version>{see https://search.maven.org/search?q=a:operator-framework-quarkus-extension for latest version}</version>
152+
</dependency>
153+
```
154+
155+
Create an Application, Quarkus will automatically create and inject a `KubernetesClient`, `Operator`
156+
and `ConfigurationService` instances that your application can use, as shown below:
157+
158+
```java
159+
@QuarkusMain
160+
public class QuarkusOperator implements QuarkusApplication {
161+
162+
@Inject KubernetesClient client;
163+
164+
@Inject Operator operator;
165+
166+
@Inject ConfigurationService configuration;
167+
168+
public static void main(String... args) {
169+
Quarkus.run(QuarkusOperator.class, args);
170+
}
171+
172+
@Override
173+
public int run(String... args) throws Exception {
174+
final var config = configuration.getConfigurationFor(new CustomServiceController(client));
175+
System.out.println("CR class: " + config.getCustomResourceClass());
176+
System.out.println("Doneable class = " + config.getDoneableClass());
177+
178+
Quarkus.waitForExit();
179+
return 0;
180+
}
181+
}
182+
```
138183

139184
#### Spring Boot
140185

141186
You can also let Spring Boot wire your application together and automatically register the controllers.
142187

143-
Add [this dependency](https://search.maven.org/search?q=a:spring-boot-operator-framework-starter) to your project:
188+
Add [this dependency](https://search.maven.org/search?q=a:operator-framework-spring-boot-starter) to your project:
144189

145190
```xml
146191
<dependency>
147192
<groupId>io.javaoperatorsdk</groupId>
148-
<artifactId>spring-boot-operator-framework-starter</artifactId>
149-
<version>{see https://search.maven.org/search?q=a:spring-boot-operator-framework-starter for latest version}</version>
193+
<artifactId>operator-framework-spring-boot-starter</artifactId>
194+
<version>{see https://search.maven.org/search?q=a:operator-framework-spring-boot-starter for latest version}</version>
150195
</dependency>
151196
```
152197

operator-framework-core/pom.xml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.javaoperatorsdk</groupId>
8+
<artifactId>java-operator-sdk</artifactId>
9+
<version>1.5.1-SNAPSHOT</version>
10+
<relativePath>../pom.xml</relativePath>
11+
</parent>
12+
13+
<artifactId>operator-framework-core</artifactId>
14+
<name>Operator SDK - Framework - Core</name>
15+
<description>Core framework for implementing Kubernetes operators</description>
16+
<packaging>jar</packaging>
17+
18+
<properties>
19+
<java.version>11</java.version>
20+
<maven.compiler.source>11</maven.compiler.source>
21+
<maven.compiler.target>11</maven.compiler.target>
22+
</properties>
23+
24+
<build>
25+
<plugins>
26+
<plugin>
27+
<groupId>org.apache.maven.plugins</groupId>
28+
<artifactId>maven-surefire-plugin</artifactId>
29+
<version>${surefire.version}</version>
30+
</plugin>
31+
</plugins>
32+
</build>
33+
34+
35+
<dependencies>
36+
<dependency>
37+
<groupId>io.fabric8</groupId>
38+
<artifactId>openshift-client</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.slf4j</groupId>
42+
<artifactId>slf4j-api</artifactId>
43+
</dependency>
44+
45+
<dependency>
46+
<groupId>org.junit.jupiter</groupId>
47+
<artifactId>junit-jupiter-api</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
<dependency>
51+
<groupId>org.junit.jupiter</groupId>
52+
<artifactId>junit-jupiter-engine</artifactId>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.mockito</groupId>
57+
<artifactId>mockito-core</artifactId>
58+
<scope>test</scope>
59+
</dependency>
60+
<dependency>
61+
<groupId>org.apache.logging.log4j</groupId>
62+
<artifactId>log4j-slf4j-impl</artifactId>
63+
<version>2.13.3</version>
64+
<scope>test</scope>
65+
</dependency>
66+
<dependency>
67+
<groupId>org.assertj</groupId>
68+
<artifactId>assertj-core</artifactId>
69+
<version>3.18.0</version>
70+
<scope>test</scope>
71+
</dependency>
72+
</dependencies>
73+
</project>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.javaoperatorsdk.operator.api.ResourceController;
5+
import java.util.Locale;
6+
7+
public class ControllerUtils {
8+
9+
private static final String FINALIZER_NAME_SUFFIX = "/finalizer";
10+
11+
public static String getDefaultFinalizerName(String crdName) {
12+
return crdName + FINALIZER_NAME_SUFFIX;
13+
}
14+
15+
public static boolean hasGivenFinalizer(CustomResource resource, String finalizer) {
16+
return resource.getMetadata().getFinalizers() != null
17+
&& resource.getMetadata().getFinalizers().contains(finalizer);
18+
}
19+
20+
public static String getDefaultNameFor(ResourceController controller) {
21+
return getDefaultNameFor(controller.getClass());
22+
}
23+
24+
public static String getDefaultNameFor(Class<? extends ResourceController> controllerClass) {
25+
return getDefaultResourceControllerName(controllerClass.getSimpleName());
26+
}
27+
28+
public static String getDefaultResourceControllerName(String rcControllerClassName) {
29+
final var lastDot = rcControllerClassName.lastIndexOf('.');
30+
if (lastDot > 0) {
31+
rcControllerClassName = rcControllerClassName.substring(lastDot);
32+
}
33+
return rcControllerClassName.toLowerCase(Locale.ROOT);
34+
}
35+
}

operator-framework/src/main/java/io/javaoperatorsdk/operator/Operator.java renamed to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java

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

3-
import static io.javaoperatorsdk.operator.ControllerUtils.getCrdName;
4-
import static io.javaoperatorsdk.operator.ControllerUtils.getCustomResourceClass;
5-
63
import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinition;
74
import io.fabric8.kubernetes.client.CustomResource;
85
import io.fabric8.kubernetes.client.CustomResourceDoneable;
@@ -13,11 +10,13 @@
1310
import io.fabric8.kubernetes.client.dsl.internal.CustomResourceOperationsImpl;
1411
import io.fabric8.kubernetes.internal.KubernetesDeserializer;
1512
import io.javaoperatorsdk.operator.api.ResourceController;
13+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
1614
import io.javaoperatorsdk.operator.processing.CustomResourceCache;
1715
import io.javaoperatorsdk.operator.processing.DefaultEventHandler;
1816
import io.javaoperatorsdk.operator.processing.EventDispatcher;
1917
import io.javaoperatorsdk.operator.processing.event.DefaultEventSourceManager;
2018
import io.javaoperatorsdk.operator.processing.event.internal.CustomResourceEventSource;
19+
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
2120
import io.javaoperatorsdk.operator.processing.retry.Retry;
2221
import java.util.Arrays;
2322
import java.util.HashMap;
@@ -30,11 +29,21 @@ public class Operator {
3029

3130
private static final Logger log = LoggerFactory.getLogger(Operator.class);
3231
private final KubernetesClient k8sClient;
32+
private final ConfigurationService configurationService;
3333
private Map<Class<? extends CustomResource>, CustomResourceOperationsImpl> customResourceClients =
3434
new HashMap<>();
3535

36-
public Operator(KubernetesClient k8sClient) {
36+
public Operator(KubernetesClient k8sClient, ConfigurationService configurationService) {
3737
this.k8sClient = k8sClient;
38+
this.configurationService = configurationService;
39+
}
40+
41+
public <R extends CustomResource> void register(ResourceController<R> controller)
42+
throws OperatorException {
43+
final var configuration = configurationService.getConfigurationFor(controller);
44+
final var retry = GenericRetry.fromConfiguration(configuration.getRetryConfiguration());
45+
final var targetNamespaces = configuration.getNamespaces().toArray(new String[] {});
46+
registerController(controller, configuration.watchAllNamespaces(), retry, targetNamespaces);
3847
}
3948

4049
public <R extends CustomResource> void registerControllerForAllNamespaces(
@@ -65,16 +74,14 @@ private <R extends CustomResource> void registerController(
6574
Retry retry,
6675
String... targetNamespaces)
6776
throws OperatorException {
68-
Class<R> resClass = getCustomResourceClass(controller);
77+
final var configuration = configurationService.getConfigurationFor(controller);
78+
Class<R> resClass = configuration.getCustomResourceClass();
6979
CustomResourceDefinitionContext crd = getCustomResourceDefinitionForController(controller);
7080
KubernetesDeserializer.registerCustomKind(crd.getVersion(), crd.getKind(), resClass);
71-
String finalizer = ControllerUtils.getFinalizer(controller);
81+
String finalizer = configuration.getFinalizer();
7282
MixedOperation client =
7383
k8sClient.customResources(
74-
crd,
75-
resClass,
76-
CustomResourceList.class,
77-
ControllerUtils.getCustomResourceDoneableClass(controller));
84+
crd, resClass, CustomResourceList.class, configuration.getDoneableClass());
7885
EventDispatcher eventDispatcher =
7986
new EventDispatcher(
8087
controller, finalizer, new EventDispatcher.CustomResourceFacade(client));
@@ -98,7 +105,7 @@ private <R extends CustomResource> void registerController(
98105
watchAllNamespaces,
99106
targetNamespaces,
100107
defaultEventHandler,
101-
ControllerUtils.getGenerationEventProcessing(controller),
108+
configuration.isGenerationAware(),
102109
finalizer);
103110
eventSourceManager.registerCustomResourceEventSource(customResourceEventSource);
104111

@@ -133,7 +140,7 @@ private CustomResourceEventSource createCustomResourceEventSource(
133140

134141
private CustomResourceDefinitionContext getCustomResourceDefinitionForController(
135142
ResourceController controller) {
136-
String crdName = getCrdName(controller);
143+
final var crdName = configurationService.getConfigurationFor(controller).getCRDName();
137144
CustomResourceDefinition customResourceDefinition =
138145
k8sClient.customResourceDefinitions().withName(crdName).get();
139146
if (customResourceDefinition == null) {

operator-framework/src/main/java/io/javaoperatorsdk/operator/api/Controller.java renamed to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/Controller.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
@Retention(RetentionPolicy.RUNTIME)
99
@Target({ElementType.TYPE})
1010
public @interface Controller {
11+
1112
String NULL = "";
1213

1314
String crdName();
1415

16+
String name() default NULL;
17+
1518
/**
1619
* Optional finalizer name, if it is not, the crdName will be used as the name of the finalizer
1720
* too.
@@ -24,4 +27,8 @@
2427
* href="https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#status-subresource">here</a>
2528
*/
2629
boolean generationAwareEventProcessing() default true;
30+
31+
boolean isClusterScoped() default false;
32+
33+
String[] namespaces() default {};
2734
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.fabric8.kubernetes.client.CustomResource;
44
import io.javaoperatorsdk.operator.processing.event.EventSourceManager;
5+
import java.util.Locale;
56

67
public interface ResourceController<R extends CustomResource> {
78

@@ -37,4 +38,20 @@ public interface ResourceController<R extends CustomResource> {
3738
* @param eventSourceManager
3839
*/
3940
default void init(EventSourceManager eventSourceManager) {}
41+
42+
default String getName() {
43+
final var clazz = getClass();
44+
45+
// if the controller annotation has a name attribute, use it
46+
final var annotation = clazz.getAnnotation(Controller.class);
47+
if (annotation != null) {
48+
final var name = annotation.name();
49+
if (!Controller.NULL.equals(name)) {
50+
return name;
51+
}
52+
}
53+
54+
// otherwise, use the lower-cased class name
55+
return clazz.getSimpleName().toLowerCase(Locale.ROOT);
56+
}
4057
}
Lines changed: 15 additions & 0 deletions
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.client.Config;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.javaoperatorsdk.operator.api.ResourceController;
6+
7+
public interface ConfigurationService {
8+
9+
<R extends CustomResource> ControllerConfiguration<R> getConfigurationFor(
10+
ResourceController<R> controller);
11+
12+
default Config getClientConfiguration() {
13+
return Config.autoConfigure(null);
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.fabric8.kubernetes.client.CustomResourceDoneable;
5+
import java.util.Collections;
6+
import java.util.Set;
7+
8+
public interface ControllerConfiguration<R extends CustomResource> {
9+
10+
String WATCH_ALL_NAMESPACES_MARKER = "ALL_NAMESPACES";
11+
12+
String getName();
13+
14+
String getCRDName();
15+
16+
String getFinalizer();
17+
18+
boolean isGenerationAware();
19+
20+
Class<R> getCustomResourceClass();
21+
22+
Class<? extends CustomResourceDoneable<R>> getDoneableClass();
23+
24+
default boolean isClusterScoped() {
25+
return false;
26+
}
27+
28+
default Set<String> getNamespaces() {
29+
return Collections.emptySet();
30+
}
31+
32+
default boolean watchAllNamespaces() {
33+
return getNamespaces().contains(WATCH_ALL_NAMESPACES_MARKER);
34+
}
35+
36+
default RetryConfiguration getRetryConfiguration() {
37+
return RetryConfiguration.DEFAULT;
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
public class DefaultRetryConfiguration implements RetryConfiguration {}

0 commit comments

Comments
 (0)