Skip to content

Commit cb1fa11

Browse files
authored
feat: add managed dependent webpage reconciler implementation (#1050)
* feat: give explicit access to client from KubernetesClientAware * refactor: WebPageReconcilerDependentResources -> WebPageStandaloneDependentsReconciler * refactor: extract methods into Utils, extract dependent resources * fix: make Utils.simulateErrorIfRequested throw exception * feat: add managed dependent reconciler implementation * fix: unify artifactId
1 parent 7245f55 commit cb1fa11

File tree

15 files changed

+362
-244
lines changed

15 files changed

+362
-244
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/KubernetesClientAware.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44

55
public interface KubernetesClientAware {
66
void setKubernetesClient(KubernetesClient kubernetesClient);
7+
8+
KubernetesClient getKubernetesClient();
79
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ public void setKubernetesClient(KubernetesClient kubernetesClient) {
156156
this.client = kubernetesClient;
157157
}
158158

159+
@Override
160+
public KubernetesClient getKubernetesClient() {
161+
return client;
162+
}
163+
159164
@Override
160165
protected R desired(P primary, Context<P> context) {
161166
return super.desired(primary, context);

sample-operators/webpage/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<version>3.0.0-SNAPSHOT</version>
1111
</parent>
1212

13-
<artifactId>webpage</artifactId>
13+
<artifactId>sample-webpage-operator</artifactId>
1414
<name>Operator SDK - Samples - WebPage</name>
1515
<description>Provisions an nginx Webserver based on a CRD with give html</description>
1616
<packaging>jar</packaging>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package io.javaoperatorsdk.operator.sample;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
import io.fabric8.kubernetes.api.model.ConfigMap;
10+
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
11+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
12+
import io.javaoperatorsdk.operator.api.reconciler.Context;
13+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource;
14+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
15+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
16+
import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper;
17+
18+
import static io.javaoperatorsdk.operator.sample.Utils.configMapName;
19+
import static io.javaoperatorsdk.operator.sample.Utils.deploymentName;
20+
21+
// this annotation only activates when using managed dependents and is not otherwise needed
22+
@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)
23+
class ConfigMapDependentResource extends CRUKubernetesDependentResource<ConfigMap, WebPage>
24+
implements PrimaryToSecondaryMapper<WebPage> {
25+
26+
private static final Logger log = LoggerFactory.getLogger(ConfigMapDependentResource.class);
27+
28+
public ConfigMapDependentResource() {
29+
super(ConfigMap.class);
30+
}
31+
32+
@Override
33+
protected ConfigMap desired(WebPage webPage, Context<WebPage> context) {
34+
Map<String, String> data = new HashMap<>();
35+
data.put("index.html", webPage.getSpec().getHtml());
36+
return new ConfigMapBuilder()
37+
.withMetadata(
38+
new ObjectMetaBuilder()
39+
.withName(configMapName(webPage))
40+
.withNamespace(webPage.getMetadata().getNamespace())
41+
.build())
42+
.withData(data)
43+
.build();
44+
}
45+
46+
@Override
47+
public ConfigMap update(ConfigMap actual, ConfigMap target, WebPage primary,
48+
Context<WebPage> context) {
49+
var res = super.update(actual, target, primary, context);
50+
var ns = actual.getMetadata().getNamespace();
51+
log.info("Restarting pods because HTML has changed in {}",
52+
ns);
53+
getKubernetesClient()
54+
.pods()
55+
.inNamespace(ns)
56+
.withLabel("app", deploymentName(primary))
57+
.delete();
58+
return res;
59+
}
60+
61+
@Override
62+
public ResourceID associatedSecondaryID(WebPage primary) {
63+
return new ResourceID(configMapName(primary), primary.getMetadata().getNamespace());
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.javaoperatorsdk.operator.sample;
2+
3+
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder;
4+
import io.fabric8.kubernetes.api.model.apps.Deployment;
5+
import io.javaoperatorsdk.operator.api.reconciler.Context;
6+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
7+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
8+
9+
import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
10+
import static io.javaoperatorsdk.operator.sample.Utils.configMapName;
11+
import static io.javaoperatorsdk.operator.sample.Utils.deploymentName;
12+
13+
// this annotation only activates when using managed dependents and is not otherwise needed
14+
@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)
15+
class DeploymentDependentResource extends CRUDKubernetesDependentResource<Deployment, WebPage> {
16+
17+
public DeploymentDependentResource() {
18+
super(Deployment.class);
19+
}
20+
21+
@Override
22+
protected Deployment desired(WebPage webPage, Context<WebPage> context) {
23+
var deploymentName = deploymentName(webPage);
24+
Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
25+
deployment.getMetadata().setName(deploymentName);
26+
deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
27+
deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
28+
29+
deployment
30+
.getSpec()
31+
.getTemplate()
32+
.getMetadata()
33+
.getLabels()
34+
.put("app", deploymentName);
35+
deployment
36+
.getSpec()
37+
.getTemplate()
38+
.getSpec()
39+
.getVolumes()
40+
.get(0)
41+
.setConfigMap(
42+
new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
43+
return deployment;
44+
}
45+
}

sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ErrorSimulationException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.javaoperatorsdk.operator.sample;
22

3-
public class ErrorSimulationException extends RuntimeException {
3+
public class ErrorSimulationException extends Exception {
44

55
public ErrorSimulationException(String message) {
66
super(message);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.javaoperatorsdk.operator.sample;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
import io.fabric8.kubernetes.api.model.Service;
7+
import io.javaoperatorsdk.operator.api.reconciler.Context;
8+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
9+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
10+
11+
import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
12+
import static io.javaoperatorsdk.operator.sample.Utils.deploymentName;
13+
import static io.javaoperatorsdk.operator.sample.Utils.serviceName;
14+
15+
// this annotation only activates when using managed dependents and is not otherwise needed
16+
@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)
17+
class ServiceDependentResource extends CRUDKubernetesDependentResource<Service, WebPage> {
18+
19+
public ServiceDependentResource() {
20+
super(Service.class);
21+
}
22+
23+
@Override
24+
protected Service desired(WebPage webPage, Context<WebPage> context) {
25+
Service service = loadYaml(Service.class, getClass(), "service.yaml");
26+
service.getMetadata().setName(serviceName(webPage));
27+
service.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
28+
Map<String, String> labels = new HashMap<>();
29+
labels.put("app", deploymentName(webPage));
30+
service.getSpec().setSelector(labels);
31+
return service;
32+
}
33+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.javaoperatorsdk.operator.sample;
2+
3+
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
4+
5+
public class Utils {
6+
7+
private Utils() {}
8+
9+
static WebPageStatus createStatus(String configMapName) {
10+
WebPageStatus status = new WebPageStatus();
11+
status.setHtmlConfigMap(configMapName);
12+
status.setAreWeGood(true);
13+
status.setErrorMessage(null);
14+
return status;
15+
}
16+
17+
static String configMapName(WebPage nginx) {
18+
return nginx.getMetadata().getName() + "-html";
19+
}
20+
21+
static String deploymentName(WebPage nginx) {
22+
return nginx.getMetadata().getName();
23+
}
24+
25+
static String serviceName(WebPage webPage) {
26+
return webPage.getMetadata().getName();
27+
}
28+
29+
static ErrorStatusUpdateControl<WebPage> handleError(WebPage resource, Exception e) {
30+
resource.getStatus().setErrorMessage("Error: " + e.getMessage());
31+
return ErrorStatusUpdateControl.updateStatus(resource);
32+
}
33+
34+
static void simulateErrorIfRequested(WebPage webPage) throws ErrorSimulationException {
35+
if (webPage.getSpec().getHtml().contains("error")) {
36+
// special case just to showcase error if doing a demo
37+
throw new ErrorSimulationException("Simulating error");
38+
}
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.javaoperatorsdk.operator.sample;
2+
3+
import io.fabric8.kubernetes.api.model.ConfigMap;
4+
import io.javaoperatorsdk.operator.api.reconciler.Context;
5+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
6+
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler;
7+
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
8+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
9+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
10+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
11+
12+
import static io.javaoperatorsdk.operator.sample.Utils.createStatus;
13+
import static io.javaoperatorsdk.operator.sample.Utils.handleError;
14+
import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested;
15+
import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR;
16+
17+
/**
18+
* Shows how to implement a reconciler with managed dependent resources.
19+
*/
20+
@ControllerConfiguration(
21+
labelSelector = SELECTOR,
22+
dependents = {
23+
@Dependent(type = ConfigMapDependentResource.class),
24+
@Dependent(type = DeploymentDependentResource.class),
25+
@Dependent(type = ServiceDependentResource.class)
26+
})
27+
public class WebPageManagedDependentsReconciler
28+
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage> {
29+
30+
static final String SELECTOR = "managed";
31+
32+
@Override
33+
public ErrorStatusUpdateControl<WebPage> updateErrorStatus(WebPage resource,
34+
Context<WebPage> context, Exception e) {
35+
return handleError(resource, e);
36+
}
37+
38+
@Override
39+
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context)
40+
throws Exception {
41+
simulateErrorIfRequested(webPage);
42+
43+
final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
44+
.getMetadata().getName();
45+
webPage.setStatus(createStatus(name));
46+
return UpdateControl.updateStatus(webPage);
47+
}
48+
}

sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static void main(String[] args) throws IOException {
3030
if (WEBPAGE_RECONCILER_ENV_VALUE.equals(System.getenv(WEBPAGE_RECONCILER_ENV))) {
3131
operator.register(new WebPageReconciler(client));
3232
} else {
33-
operator.register(new WebPageReconcilerDependentResources(client));
33+
operator.register(new WebPageStandaloneDependentsReconciler(client));
3434
}
3535
operator.installShutdownHook();
3636
operator.start();

sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,33 @@
88
import org.slf4j.Logger;
99
import org.slf4j.LoggerFactory;
1010

11-
import io.fabric8.kubernetes.api.model.*;
11+
import io.fabric8.kubernetes.api.model.ConfigMap;
12+
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
13+
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder;
14+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
15+
import io.fabric8.kubernetes.api.model.Service;
1216
import io.fabric8.kubernetes.api.model.apps.Deployment;
1317
import io.fabric8.kubernetes.client.KubernetesClient;
1418
import io.javaoperatorsdk.operator.ReconcilerUtils;
1519
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
16-
import io.javaoperatorsdk.operator.api.reconciler.*;
1720
import io.javaoperatorsdk.operator.api.reconciler.Context;
21+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
22+
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler;
23+
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
24+
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
25+
import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer;
26+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
27+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
1828
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
1929
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
2030

31+
import static io.javaoperatorsdk.operator.sample.Utils.configMapName;
32+
import static io.javaoperatorsdk.operator.sample.Utils.createStatus;
33+
import static io.javaoperatorsdk.operator.sample.Utils.deploymentName;
34+
import static io.javaoperatorsdk.operator.sample.Utils.handleError;
35+
import static io.javaoperatorsdk.operator.sample.Utils.serviceName;
36+
import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested;
37+
2138
/** Shows how to implement reconciler using the low level api directly. */
2239
@ControllerConfiguration(
2340
labelSelector = WebPageReconciler.LOW_LEVEL_LABEL_KEY)
@@ -35,11 +52,9 @@ public WebPageReconciler(KubernetesClient kubernetesClient) {
3552
this.kubernetesClient = kubernetesClient;
3653
}
3754

38-
InformerEventSource configMapEventSource;
39-
4055
@Override
4156
public List<EventSource> prepareEventSources(EventSourceContext<WebPage> context) {
42-
configMapEventSource =
57+
var configMapEventSource =
4358
new InformerEventSource<>(InformerConfiguration.from(context, ConfigMap.class)
4459
.withLabelSelector(LOW_LEVEL_LABEL_KEY)
4560
.build(), context);
@@ -55,11 +70,11 @@ public List<EventSource> prepareEventSources(EventSourceContext<WebPage> context
5570
}
5671

5772
@Override
58-
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
73+
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context)
74+
throws Exception {
5975
log.info("Reconciling web page: {}", webPage);
60-
if (webPage.getSpec().getHtml().contains("error")) {
61-
throw new ErrorSimulationException("Simulating error");
62-
}
76+
simulateErrorIfRequested(webPage);
77+
6378
String ns = webPage.getMetadata().getNamespace();
6479
String configMapName = configMapName(webPage);
6580
String deploymentName = deploymentName(webPage);
@@ -107,14 +122,6 @@ public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> contex
107122
return UpdateControl.updateStatus(webPage);
108123
}
109124

110-
private WebPageStatus createStatus(String configMapName) {
111-
WebPageStatus status = new WebPageStatus();
112-
status.setHtmlConfigMap(configMapName);
113-
status.setAreWeGood(true);
114-
status.setErrorMessage(null);
115-
return status;
116-
}
117-
118125
private boolean match(Deployment desiredDeployment, Deployment deployment) {
119126
if (deployment == null) {
120127
return false;
@@ -196,22 +203,9 @@ public static Map<String, String> lowLevelLabel() {
196203
return labels;
197204
}
198205

199-
private static String configMapName(WebPage nginx) {
200-
return nginx.getMetadata().getName() + "-html";
201-
}
202-
203-
private static String deploymentName(WebPage nginx) {
204-
return nginx.getMetadata().getName();
205-
}
206-
207-
private static String serviceName(WebPage nginx) {
208-
return nginx.getMetadata().getName();
209-
}
210-
211206
@Override
212207
public ErrorStatusUpdateControl<WebPage> updateErrorStatus(
213208
WebPage resource, Context<WebPage> context, Exception e) {
214-
resource.getStatus().setErrorMessage("Error: " + e.getMessage());
215-
return ErrorStatusUpdateControl.updateStatus(resource);
209+
return handleError(resource, e);
216210
}
217211
}

0 commit comments

Comments
 (0)