Skip to content

Commit 4d63e12

Browse files
authored
WebPage better showcase Standalone Dependent Resource (#1111)
1 parent dd5817f commit 4d63e12

File tree

9 files changed

+140
-17
lines changed

9 files changed

+140
-17
lines changed

docs/documentation/dependent-resources.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,11 @@ public class WebPageStandaloneDependentsReconciler
236236
serviceDR.reconcile(webPage, context);
237237

238238
// 5.
239-
if (webPage.isExposed()) {
240-
ingressDR.reconcile();
241-
}
239+
if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) {
240+
ingressDR.reconcile(webPage, context);
241+
} else {
242+
ingressDR.delete(webPage, context);
243+
}
242244

243245
// 6.
244246
webPage.setStatus(
@@ -269,7 +271,7 @@ There are multiple things happening here:
269271
2. Event sources are produced by the dependent resources, but needs to be explicitly registered in this case.
270272
3. The input html is validated, and error message is set in case it is invalid.
271273
4. Reconciliation is called explicitly, but here the workflow customization is fully in the hand of the developer.
272-
5. An `Ingress` is created but only in case `exposed` flag set to true on custom resource.
274+
5. An `Ingress` is created but only in case `exposed` flag set to true on custom resource. Tries to delete it if not.
273275
6. Status is set in a different way, this is just an alternative way to show, that the actual state can be read using
274276
the reference. This could be written in a same way as in the managed example.
275277

@@ -291,4 +293,4 @@ makes sure that it or the related `InformerEventSource` always return the up-to-
291293
the reconciliation, the later received related event will not trigger the reconciliation again. This is a small
292294
optimization. For example if during a reconciliation a `ConfigMap` is updated using dependent resources, this won't
293295
trigger a new reconciliation. It' does not need to, since the change in the `ConfigMap` is made by the reconciler,
294-
and the fresh version is used further.
296+
and the fresh version is used further.

sample-operators/webpage/k8s/operator.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,10 @@ rules:
8787
- webpages/status
8888
verbs:
8989
- '*'
90+
- apiGroups:
91+
- "networking.k8s.io"
92+
resources:
93+
- ingresses
94+
verbs:
95+
- '*'
96+

sample-operators/webpage/k8s/webpage.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ metadata:
55
low-level: "true"
66
name: hellows
77
spec:
8+
exposed: false
89
html: |
910
<html>
1011
<head>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.javaoperatorsdk.operator.sample;
2+
3+
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
4+
import io.javaoperatorsdk.operator.api.reconciler.Context;
5+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource;
6+
7+
import static io.javaoperatorsdk.operator.sample.Utils.*;
8+
9+
public class IngressDependentResource extends CRUKubernetesDependentResource<Ingress, WebPage> {
10+
11+
public IngressDependentResource() {
12+
super(Ingress.class);
13+
}
14+
15+
@Override
16+
protected Ingress desired(WebPage webPage, Context<WebPage> context) {
17+
return makeDesiredIngress(webPage);
18+
}
19+
20+
}

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

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

3+
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
34
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
45

6+
import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
7+
58
public class Utils {
69

710
private Utils() {}
@@ -37,4 +40,27 @@ static void simulateErrorIfRequested(WebPage webPage) throws ErrorSimulationExce
3740
throw new ErrorSimulationException("Simulating error");
3841
}
3942
}
43+
44+
static boolean isValidHtml(WebPage webPage) {
45+
// very dummy html validation
46+
var lowerCaseHtml = webPage.getSpec().getHtml().toLowerCase();
47+
return lowerCaseHtml.contains("<html>") && lowerCaseHtml.contains("</html>");
48+
}
49+
50+
static WebPage setInvalidHtmlErrorMessage(WebPage webPage) {
51+
if (webPage.getStatus() == null) {
52+
webPage.setStatus(new WebPageStatus());
53+
}
54+
webPage.getStatus().setErrorMessage("Invalid html.");
55+
return webPage;
56+
}
57+
58+
static Ingress makeDesiredIngress(WebPage webPage) {
59+
Ingress ingress = loadYaml(Ingress.class, Utils.class, "ingress.yaml");
60+
ingress.getMetadata().setName(webPage.getMetadata().getName());
61+
ingress.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
62+
ingress.getSpec().getRules().get(0).getHttp().getPaths().get(0)
63+
.getBackend().getService().setName(serviceName(webPage));
64+
return ingress;
65+
}
4066
}

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

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

33
import java.util.HashMap;
44
import java.util.Map;
5+
import java.util.Objects;
56

67
import org.apache.commons.lang3.StringUtils;
78
import org.slf4j.Logger;
@@ -13,6 +14,7 @@
1314
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
1415
import io.fabric8.kubernetes.api.model.Service;
1516
import io.fabric8.kubernetes.api.model.apps.Deployment;
17+
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
1618
import io.fabric8.kubernetes.client.KubernetesClient;
1719
import io.javaoperatorsdk.operator.ReconcilerUtils;
1820
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
@@ -27,12 +29,7 @@
2729
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
2830
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
2931

30-
import static io.javaoperatorsdk.operator.sample.Utils.configMapName;
31-
import static io.javaoperatorsdk.operator.sample.Utils.createStatus;
32-
import static io.javaoperatorsdk.operator.sample.Utils.deploymentName;
33-
import static io.javaoperatorsdk.operator.sample.Utils.handleError;
34-
import static io.javaoperatorsdk.operator.sample.Utils.serviceName;
35-
import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested;
32+
import static io.javaoperatorsdk.operator.sample.Utils.*;
3633

3734
/** Shows how to implement reconciler using the low level api directly. */
3835
@ControllerConfiguration(
@@ -65,9 +62,13 @@ public Map<String, EventSource> prepareEventSources(EventSourceContext<WebPage>
6562
new InformerEventSource<>(InformerConfiguration.from(context, Service.class)
6663
.withLabelSelector(LOW_LEVEL_LABEL_KEY)
6764
.build(), context);
65+
var ingressEventSource =
66+
new InformerEventSource<>(InformerConfiguration.from(context, Ingress.class)
67+
.withLabelSelector(LOW_LEVEL_LABEL_KEY)
68+
.build(), context);
6869
return EventSourceInitializer.nameEventSources(configMapEventSource,
6970
deploymentEventSource,
70-
serviceEventSource);
71+
serviceEventSource, ingressEventSource);
7172
}
7273

7374
@Override
@@ -76,6 +77,10 @@ public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> contex
7677
log.info("Reconciling web page: {}", webPage);
7778
simulateErrorIfRequested(webPage);
7879

80+
if (!isValidHtml(webPage)) {
81+
return UpdateControl.updateStatus(setInvalidHtmlErrorMessage(webPage));
82+
}
83+
7984
String ns = webPage.getMetadata().getNamespace();
8085
String configMapName = configMapName(webPage);
8186
String deploymentName = deploymentName(webPage);
@@ -113,6 +118,16 @@ public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> contex
113118
kubernetesClient.services().inNamespace(ns).createOrReplace(desiredService);
114119
}
115120

121+
var existingIngress = context.getSecondaryResource(Ingress.class);
122+
if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) {
123+
var desiredIngress = makeDesiredIngress(webPage);
124+
if (existingIngress.isEmpty() || !match(desiredIngress, existingIngress.get())) {
125+
kubernetesClient.resource(desiredIngress).inNamespace(ns).createOrReplace();
126+
}
127+
} else
128+
existingIngress.ifPresent(
129+
ingress -> kubernetesClient.resource(ingress).delete());
130+
116131
if (previousConfigMap != null && !StringUtils.equals(
117132
previousConfigMap.getData().get(INDEX_HTML),
118133
desiredHtmlConfigMap.getData().get(INDEX_HTML))) {
@@ -123,6 +138,16 @@ public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> contex
123138
return UpdateControl.updateStatus(webPage);
124139
}
125140

141+
private boolean match(Ingress desiredIngress, Ingress existingIngress) {
142+
String desiredServiceName =
143+
desiredIngress.getSpec().getRules().get(0).getHttp().getPaths().get(0)
144+
.getBackend().getService().getName();
145+
String existingServiceName =
146+
existingIngress.getSpec().getRules().get(0).getHttp().getPaths().get(0)
147+
.getBackend().getService().getName();
148+
return Objects.equals(desiredServiceName, existingServiceName);
149+
}
150+
126151
private boolean match(Deployment desiredDeployment, Deployment deployment) {
127152
if (deployment == null) {
128153
return false;

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
public class WebPageSpec {
44

55
private String html;
6+
private Boolean exposed = false;
67

78
public String getHtml() {
89
return html;
@@ -12,6 +13,15 @@ public void setHtml(String html) {
1213
this.html = html;
1314
}
1415

16+
public Boolean getExposed() {
17+
return exposed;
18+
}
19+
20+
public WebPageSpec setExposed(Boolean exposed) {
21+
this.exposed = exposed;
22+
return this;
23+
}
24+
1525
@Override
1626
public String toString() {
1727
return "WebPageSpec{" +

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.fabric8.kubernetes.api.model.ConfigMap;
1010
import io.fabric8.kubernetes.api.model.Service;
1111
import io.fabric8.kubernetes.api.model.apps.Deployment;
12+
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
1213
import io.fabric8.kubernetes.client.KubernetesClient;
1314
import io.javaoperatorsdk.operator.api.reconciler.Context;
1415
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
@@ -22,9 +23,7 @@
2223
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
2324
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
2425

25-
import static io.javaoperatorsdk.operator.sample.Utils.createStatus;
26-
import static io.javaoperatorsdk.operator.sample.Utils.handleError;
27-
import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested;
26+
import static io.javaoperatorsdk.operator.sample.Utils.*;
2827

2928
/**
3029
* Shows how to implement reconciler using standalone dependent resources.
@@ -41,6 +40,7 @@ public class WebPageStandaloneDependentsReconciler
4140
private KubernetesDependentResource<ConfigMap, WebPage> configMapDR;
4241
private KubernetesDependentResource<Deployment, WebPage> deploymentDR;
4342
private KubernetesDependentResource<Service, WebPage> serviceDR;
43+
private KubernetesDependentResource<Ingress, WebPage> ingressDR;
4444

4545
public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) {
4646
createDependentResources(kubernetesClient);
@@ -49,17 +49,28 @@ public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient)
4949
@Override
5050
public Map<String, EventSource> prepareEventSources(EventSourceContext<WebPage> context) {
5151
return EventSourceInitializer.nameEventSources(configMapDR.initEventSource(context),
52-
deploymentDR.initEventSource(context), serviceDR.initEventSource(context));
52+
deploymentDR.initEventSource(context), serviceDR.initEventSource(context),
53+
ingressDR.initEventSource(context));
5354
}
5455

5556
@Override
5657
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context)
5758
throws Exception {
5859
simulateErrorIfRequested(webPage);
5960

61+
if (!isValidHtml(webPage)) {
62+
return UpdateControl.updateStatus(setInvalidHtmlErrorMessage(webPage));
63+
}
64+
6065
Arrays.asList(configMapDR, deploymentDR, serviceDR)
6166
.forEach(dr -> dr.reconcile(webPage, context));
6267

68+
if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) {
69+
ingressDR.reconcile(webPage, context);
70+
} else {
71+
ingressDR.delete(webPage, context);
72+
}
73+
6374
webPage.setStatus(
6475
createStatus(
6576
configMapDR.getSecondaryResource(webPage).orElseThrow().getMetadata().getName()));
@@ -76,11 +87,15 @@ private void createDependentResources(KubernetesClient client) {
7687
this.configMapDR = new ConfigMapDependentResource();
7788
this.deploymentDR = new DeploymentDependentResource();
7889
this.serviceDR = new ServiceDependentResource();
90+
this.ingressDR = new IngressDependentResource();
7991

80-
Arrays.asList(configMapDR, deploymentDR, serviceDR).forEach(dr -> {
92+
Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR).forEach(dr -> {
8193
dr.setKubernetesClient(client);
8294
dr.configureWith(new KubernetesDependentResourceConfig()
8395
.setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR));
8496
});
8597
}
98+
99+
100+
86101
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
name: ""
5+
annotations:
6+
nginx.ingress.kubernetes.io/rewrite-target: /$1
7+
spec:
8+
rules:
9+
- http:
10+
paths:
11+
- path: /
12+
pathType: Prefix
13+
backend:
14+
service:
15+
name: ""
16+
port:
17+
number: 80

0 commit comments

Comments
 (0)