diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/KubernetesClientAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/KubernetesClientAware.java index 0e787753a5..2a28813c35 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/KubernetesClientAware.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/KubernetesClientAware.java @@ -4,4 +4,6 @@ public interface KubernetesClientAware { void setKubernetesClient(KubernetesClient kubernetesClient); + + KubernetesClient getKubernetesClient(); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java index 233deb020d..32606a5ebd 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java @@ -156,6 +156,11 @@ public void setKubernetesClient(KubernetesClient kubernetesClient) { this.client = kubernetesClient; } + @Override + public KubernetesClient getKubernetesClient() { + return client; + } + @Override protected R desired(P primary, Context

context) { return super.desired(primary, context); diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml index 93e32953c3..f91cd7ee8a 100644 --- a/sample-operators/webpage/pom.xml +++ b/sample-operators/webpage/pom.xml @@ -10,7 +10,7 @@ 2.2.0-SNAPSHOT - webpage + sample-webpage-operator Operator SDK - Samples - WebPage Provisions an nginx Webserver based on a CRD with give html jar diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java new file mode 100644 index 0000000000..8cacd49765 --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ConfigMapDependentResource.java @@ -0,0 +1,65 @@ +package io.javaoperatorsdk.operator.sample; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; + +import static io.javaoperatorsdk.operator.sample.Utils.configMapName; +import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; + +// this annotation only activates when using managed dependents and is not otherwise needed +@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) +class ConfigMapDependentResource extends CRUKubernetesDependentResource + implements PrimaryToSecondaryMapper { + + private static final Logger log = LoggerFactory.getLogger(ConfigMapDependentResource.class); + + public ConfigMapDependentResource() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(WebPage webPage, Context context) { + Map data = new HashMap<>(); + data.put("index.html", webPage.getSpec().getHtml()); + return new ConfigMapBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName(configMapName(webPage)) + .withNamespace(webPage.getMetadata().getNamespace()) + .build()) + .withData(data) + .build(); + } + + @Override + public ConfigMap update(ConfigMap actual, ConfigMap target, WebPage primary, + Context context) { + var res = super.update(actual, target, primary, context); + var ns = actual.getMetadata().getNamespace(); + log.info("Restarting pods because HTML has changed in {}", + ns); + getKubernetesClient() + .pods() + .inNamespace(ns) + .withLabel("app", deploymentName(primary)) + .delete(); + return res; + } + + @Override + public ResourceID associatedSecondaryID(WebPage primary) { + return new ResourceID(configMapName(primary), primary.getMetadata().getNamespace()); + } +} diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java new file mode 100644 index 0000000000..0fd907f245 --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java @@ -0,0 +1,45 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; +import static io.javaoperatorsdk.operator.sample.Utils.configMapName; +import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; + +// this annotation only activates when using managed dependents and is not otherwise needed +@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) +class DeploymentDependentResource extends CRUDKubernetesDependentResource { + + public DeploymentDependentResource() { + super(Deployment.class); + } + + @Override + protected Deployment desired(WebPage webPage, Context context) { + var deploymentName = deploymentName(webPage); + Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); + deployment.getMetadata().setName(deploymentName); + deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); + + deployment + .getSpec() + .getTemplate() + .getMetadata() + .getLabels() + .put("app", deploymentName); + deployment + .getSpec() + .getTemplate() + .getSpec() + .getVolumes() + .get(0) + .setConfigMap( + new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); + return deployment; + } +} diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ErrorSimulationException.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ErrorSimulationException.java index e2d3f3c1dd..94efc05baa 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ErrorSimulationException.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ErrorSimulationException.java @@ -1,6 +1,6 @@ package io.javaoperatorsdk.operator.sample; -public class ErrorSimulationException extends RuntimeException { +public class ErrorSimulationException extends Exception { public ErrorSimulationException(String message) { super(message); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java new file mode 100644 index 0000000000..cb06ab594b --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/ServiceDependentResource.java @@ -0,0 +1,33 @@ +package io.javaoperatorsdk.operator.sample; + +import java.util.HashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.Service; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; + +import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; +import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; +import static io.javaoperatorsdk.operator.sample.Utils.serviceName; + +// this annotation only activates when using managed dependents and is not otherwise needed +@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) +class ServiceDependentResource extends CRUDKubernetesDependentResource { + + public ServiceDependentResource() { + super(Service.class); + } + + @Override + protected Service desired(WebPage webPage, Context context) { + Service service = loadYaml(Service.class, getClass(), "service.yaml"); + service.getMetadata().setName(serviceName(webPage)); + service.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + Map labels = new HashMap<>(); + labels.put("app", deploymentName(webPage)); + service.getSpec().setSelector(labels); + return service; + } +} diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java new file mode 100644 index 0000000000..f08c78f5db --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/Utils.java @@ -0,0 +1,40 @@ +package io.javaoperatorsdk.operator.sample; + +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; + +public class Utils { + + private Utils() {} + + static WebPageStatus createStatus(String configMapName) { + WebPageStatus status = new WebPageStatus(); + status.setHtmlConfigMap(configMapName); + status.setAreWeGood(true); + status.setErrorMessage(null); + return status; + } + + static String configMapName(WebPage nginx) { + return nginx.getMetadata().getName() + "-html"; + } + + static String deploymentName(WebPage nginx) { + return nginx.getMetadata().getName(); + } + + static String serviceName(WebPage webPage) { + return webPage.getMetadata().getName(); + } + + static ErrorStatusUpdateControl handleError(WebPage resource, Exception e) { + resource.getStatus().setErrorMessage("Error: " + e.getMessage()); + return ErrorStatusUpdateControl.updateStatus(resource); + } + + static void simulateErrorIfRequested(WebPage webPage) throws ErrorSimulationException { + if (webPage.getSpec().getHtml().contains("error")) { + // special case just to showcase error if doing a demo + throw new ErrorSimulationException("Simulating error"); + } + } +} diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java new file mode 100644 index 0000000000..f257893f8c --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java @@ -0,0 +1,48 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +import static io.javaoperatorsdk.operator.sample.Utils.createStatus; +import static io.javaoperatorsdk.operator.sample.Utils.handleError; +import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested; +import static io.javaoperatorsdk.operator.sample.WebPageManagedDependentsReconciler.SELECTOR; + +/** + * Shows how to implement a reconciler with managed dependent resources. + */ +@ControllerConfiguration( + labelSelector = SELECTOR, + dependents = { + @Dependent(type = ConfigMapDependentResource.class), + @Dependent(type = DeploymentDependentResource.class), + @Dependent(type = ServiceDependentResource.class) + }) +public class WebPageManagedDependentsReconciler + implements Reconciler, ErrorStatusHandler { + + static final String SELECTOR = "managed"; + + @Override + public ErrorStatusUpdateControl updateErrorStatus(WebPage resource, + Context context, Exception e) { + return handleError(resource, e); + } + + @Override + public UpdateControl reconcile(WebPage webPage, Context context) + throws Exception { + simulateErrorIfRequested(webPage); + + final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() + .getMetadata().getName(); + webPage.setStatus(createStatus(name)); + return UpdateControl.updateStatus(webPage); + } +} diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java index 0277da8199..30958dbc84 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java @@ -30,7 +30,7 @@ public static void main(String[] args) throws IOException { if (WEBPAGE_RECONCILER_ENV_VALUE.equals(System.getenv(WEBPAGE_RECONCILER_ENV))) { operator.register(new WebPageReconciler(client)); } else { - operator.register(new WebPageReconcilerDependentResources(client)); + operator.register(new WebPageStandaloneDependentsReconciler(client)); } operator.installShutdownHook(); operator.start(); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index 570f2ffb53..3d74dcb00b 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -8,16 +8,33 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import static io.javaoperatorsdk.operator.sample.Utils.configMapName; +import static io.javaoperatorsdk.operator.sample.Utils.createStatus; +import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; +import static io.javaoperatorsdk.operator.sample.Utils.handleError; +import static io.javaoperatorsdk.operator.sample.Utils.serviceName; +import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested; + /** Shows how to implement reconciler using the low level api directly. */ @ControllerConfiguration( labelSelector = WebPageReconciler.LOW_LEVEL_LABEL_KEY) @@ -35,11 +52,9 @@ public WebPageReconciler(KubernetesClient kubernetesClient) { this.kubernetesClient = kubernetesClient; } - InformerEventSource configMapEventSource; - @Override public List prepareEventSources(EventSourceContext context) { - configMapEventSource = + var configMapEventSource = new InformerEventSource<>(InformerConfiguration.from(context, ConfigMap.class) .withLabelSelector(LOW_LEVEL_LABEL_KEY) .build(), context); @@ -55,11 +70,11 @@ public List prepareEventSources(EventSourceContext context } @Override - public UpdateControl reconcile(WebPage webPage, Context context) { + public UpdateControl reconcile(WebPage webPage, Context context) + throws Exception { log.info("Reconciling web page: {}", webPage); - if (webPage.getSpec().getHtml().contains("error")) { - throw new ErrorSimulationException("Simulating error"); - } + simulateErrorIfRequested(webPage); + String ns = webPage.getMetadata().getNamespace(); String configMapName = configMapName(webPage); String deploymentName = deploymentName(webPage); @@ -107,14 +122,6 @@ public UpdateControl reconcile(WebPage webPage, Context contex return UpdateControl.updateStatus(webPage); } - private WebPageStatus createStatus(String configMapName) { - WebPageStatus status = new WebPageStatus(); - status.setHtmlConfigMap(configMapName); - status.setAreWeGood(true); - status.setErrorMessage(null); - return status; - } - private boolean match(Deployment desiredDeployment, Deployment deployment) { if (deployment == null) { return false; @@ -196,22 +203,9 @@ public static Map lowLevelLabel() { return labels; } - private static String configMapName(WebPage nginx) { - return nginx.getMetadata().getName() + "-html"; - } - - private static String deploymentName(WebPage nginx) { - return nginx.getMetadata().getName(); - } - - private static String serviceName(WebPage nginx) { - return nginx.getMetadata().getName(); - } - @Override public ErrorStatusUpdateControl updateErrorStatus( WebPage resource, Context context, Exception e) { - resource.getStatus().setErrorMessage("Error: " + e.getMessage()); - return ErrorStatusUpdateControl.updateStatus(resource); + return handleError(resource, e); } } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java deleted file mode 100644 index 7698c3a16d..0000000000 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java +++ /dev/null @@ -1,206 +0,0 @@ -package io.javaoperatorsdk.operator.sample; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.fabric8.kubernetes.api.model.ConfigMapBuilder; -import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; -import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; -import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; -import io.javaoperatorsdk.operator.api.reconciler.Reconciler; -import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUKubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.PrimaryToSecondaryMapper; - -import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; - -/** - * Shows how to implement reconciler using standalone dependent resources. - */ -@ControllerConfiguration( - labelSelector = WebPageReconcilerDependentResources.DEPENDENT_RESOURCE_LABEL_SELECTOR) -public class WebPageReconcilerDependentResources - implements Reconciler, ErrorStatusHandler, EventSourceInitializer { - - public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level"; - private static final Logger log = - LoggerFactory.getLogger(WebPageReconcilerDependentResources.class); - private final KubernetesClient kubernetesClient; - - private KubernetesDependentResource configMapDR; - private KubernetesDependentResource deploymentDR; - private KubernetesDependentResource serviceDR; - - public WebPageReconcilerDependentResources(KubernetesClient kubernetesClient) { - this.kubernetesClient = kubernetesClient; - createDependentResources(kubernetesClient); - } - - @Override - public List prepareEventSources(EventSourceContext context) { - return List.of( - configMapDR.initEventSource(context), - deploymentDR.initEventSource(context), - serviceDR.initEventSource(context)); - } - - @Override - public UpdateControl reconcile(WebPage webPage, Context context) { - if (webPage.getSpec().getHtml().contains("error")) { - // special case just to showcase error if doing a demo - throw new ErrorSimulationException("Simulating error"); - } - - configMapDR.reconcile(webPage, context); - deploymentDR.reconcile(webPage, context); - serviceDR.reconcile(webPage, context); - - webPage.setStatus( - createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); - return UpdateControl.updateStatus(webPage); - } - - private WebPageStatus createStatus(String configMapName) { - WebPageStatus status = new WebPageStatus(); - status.setHtmlConfigMap(configMapName); - status.setAreWeGood(true); - status.setErrorMessage(null); - return status; - } - - @Override - public ErrorStatusUpdateControl updateErrorStatus( - WebPage resource, Context retryInfo, Exception e) { - resource.getStatus().setErrorMessage("Error: " + e.getMessage()); - return ErrorStatusUpdateControl.updateStatus(resource); - } - - private void createDependentResources(KubernetesClient client) { - this.configMapDR = new ConfigMapDependentResource(); - this.configMapDR.setKubernetesClient(client); - configMapDR.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); - - this.deploymentDR = - new CRUDKubernetesDependentResource<>(Deployment.class) { - - @Override - protected Deployment desired(WebPage webPage, Context context) { - var deploymentName = deploymentName(webPage); - Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); - deployment.getMetadata().setName(deploymentName); - deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); - deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); - - deployment - .getSpec() - .getTemplate() - .getMetadata() - .getLabels() - .put("app", deploymentName); - deployment - .getSpec() - .getTemplate() - .getSpec() - .getVolumes() - .get(0) - .setConfigMap( - new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); - return deployment; - } - }; - deploymentDR.setKubernetesClient(client); - deploymentDR.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); - - this.serviceDR = - new CRUDKubernetesDependentResource<>(Service.class) { - - @Override - protected Service desired(WebPage webPage, Context context) { - Service service = loadYaml(Service.class, getClass(), "service.yaml"); - service.getMetadata().setName(serviceName(webPage)); - service.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); - Map labels = new HashMap<>(); - labels.put("app", deploymentName(webPage)); - service.getSpec().setSelector(labels); - return service; - } - }; - serviceDR.setKubernetesClient(client); - serviceDR.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); - } - - public static String configMapName(WebPage nginx) { - return nginx.getMetadata().getName() + "-html"; - } - - public static String deploymentName(WebPage nginx) { - return nginx.getMetadata().getName(); - } - - public static String serviceName(WebPage webPage) { - return webPage.getMetadata().getName(); - } - - private class ConfigMapDependentResource - extends CRUKubernetesDependentResource - implements PrimaryToSecondaryMapper { - - public ConfigMapDependentResource() { - super(ConfigMap.class); - } - - @Override - protected ConfigMap desired(WebPage webPage, Context context) { - Map data = new HashMap<>(); - data.put("index.html", webPage.getSpec().getHtml()); - return new ConfigMapBuilder() - .withMetadata( - new ObjectMetaBuilder() - .withName(WebPageReconcilerDependentResources.configMapName(webPage)) - .withNamespace(webPage.getMetadata().getNamespace()) - .build()) - .withData(data) - .build(); - } - - @Override - public ConfigMap update(ConfigMap actual, ConfigMap target, WebPage primary, - Context context) { - var res = super.update(actual, target, primary, context); - var ns = actual.getMetadata().getNamespace(); - log.info("Restarting pods because HTML has changed in {}", ns); - kubernetesClient - .pods() - .inNamespace(ns) - .withLabel("app", deploymentName(primary)) - .delete(); - return res; - } - - @Override - public ResourceID associatedSecondaryID(WebPage primary) { - return new ResourceID(configMapName(primary), primary.getMetadata().getNamespace()); - } - } -} diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java new file mode 100644 index 0000000000..180c6ade65 --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java @@ -0,0 +1,92 @@ +package io.javaoperatorsdk.operator.sample; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; +import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; + +import static io.javaoperatorsdk.operator.sample.Utils.createStatus; +import static io.javaoperatorsdk.operator.sample.Utils.handleError; +import static io.javaoperatorsdk.operator.sample.Utils.simulateErrorIfRequested; + +/** + * Shows how to implement reconciler using standalone dependent resources. + */ +@ControllerConfiguration( + labelSelector = WebPageStandaloneDependentsReconciler.DEPENDENT_RESOURCE_LABEL_SELECTOR) +public class WebPageStandaloneDependentsReconciler + implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + + public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level"; + private static final Logger log = + LoggerFactory.getLogger(WebPageStandaloneDependentsReconciler.class); + + private KubernetesDependentResource configMapDR; + private KubernetesDependentResource deploymentDR; + private KubernetesDependentResource serviceDR; + + public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) { + createDependentResources(kubernetesClient); + } + + @Override + public List prepareEventSources(EventSourceContext context) { + return List.of( + configMapDR.initEventSource(context), + deploymentDR.initEventSource(context), + serviceDR.initEventSource(context)); + } + + @Override + public UpdateControl reconcile(WebPage webPage, Context context) + throws Exception { + simulateErrorIfRequested(webPage); + + configMapDR.reconcile(webPage, context); + deploymentDR.reconcile(webPage, context); + serviceDR.reconcile(webPage, context); + + webPage.setStatus( + createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); + return UpdateControl.updateStatus(webPage); + } + + @Override + public ErrorStatusUpdateControl updateErrorStatus( + WebPage resource, Context retryInfo, Exception e) { + return handleError(resource, e); + } + + private void createDependentResources(KubernetesClient client) { + this.configMapDR = new ConfigMapDependentResource(); + this.configMapDR.setKubernetesClient(client); + configMapDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + + this.deploymentDR = new DeploymentDependentResource(); + deploymentDR.setKubernetesClient(client); + deploymentDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + + this.serviceDR = new ServiceDependentResource(); + serviceDR.setKubernetesClient(client); + serviceDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + } +} diff --git a/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorAbstractTest.java b/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorAbstractTest.java index 092ad6f25a..65a3084989 100644 --- a/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorAbstractTest.java +++ b/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorAbstractTest.java @@ -20,7 +20,8 @@ import io.fabric8.kubernetes.client.LocalPortForward; import io.javaoperatorsdk.operator.junit.AbstractOperatorExtension; -import static io.javaoperatorsdk.operator.sample.WebPageReconcilerDependentResources.serviceName; +import static io.javaoperatorsdk.operator.sample.Utils.deploymentName; +import static io.javaoperatorsdk.operator.sample.Utils.serviceName; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -50,8 +51,7 @@ void testAddingWebPage() { .until( () -> { var actual = operator().get(WebPage.class, TEST_PAGE); - var deployment = operator().get(Deployment.class, - WebPageReconcilerDependentResources.deploymentName(webPage)); + var deployment = operator().get(Deployment.class, deploymentName(webPage)); return Boolean.TRUE.equals(actual.getStatus().getAreWeGood()) && Objects.equals(deployment.getSpec().getReplicas(), diff --git a/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorDependentResourcesE2E.java b/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorDependentResourcesE2E.java index 3bfdd3f45b..df024b890a 100644 --- a/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorDependentResourcesE2E.java +++ b/sample-operators/webpage/src/test/java/io/javaoperatorsdk/operator/sample/WebPageOperatorDependentResourcesE2E.java @@ -18,7 +18,7 @@ public WebPageOperatorDependentResourcesE2E() throws FileNotFoundException {} isLocal() ? OperatorExtension.builder() .waitForNamespaceDeletion(false) - .withReconciler(new WebPageReconcilerDependentResources(client)) + .withReconciler(new WebPageStandaloneDependentsReconciler(client)) .build() : E2EOperatorExtension.builder() .waitForNamespaceDeletion(false)