Skip to content

feat: add managed dependent webpage reconciler implementation #1050

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

public interface KubernetesClientAware {
void setKubernetesClient(KubernetesClient kubernetesClient);

KubernetesClient getKubernetesClient();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<P> context) {
return super.desired(primary, context);
Expand Down
2 changes: 1 addition & 1 deletion sample-operators/webpage/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<version>2.2.0-SNAPSHOT</version>
</parent>

<artifactId>webpage</artifactId>
<artifactId>sample-webpage-operator</artifactId>
<name>Operator SDK - Samples - WebPage</name>
<description>Provisions an nginx Webserver based on a CRD with give html</description>
<packaging>jar</packaging>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ConfigMap, WebPage>
implements PrimaryToSecondaryMapper<WebPage> {

private static final Logger log = LoggerFactory.getLogger(ConfigMapDependentResource.class);

public ConfigMapDependentResource() {
super(ConfigMap.class);
}

@Override
protected ConfigMap desired(WebPage webPage, Context<WebPage> context) {
Map<String, String> 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<WebPage> 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());
}
}
Original file line number Diff line number Diff line change
@@ -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<Deployment, WebPage> {

public DeploymentDependentResource() {
super(Deployment.class);
}

@Override
protected Deployment desired(WebPage webPage, Context<WebPage> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Service, WebPage> {

public ServiceDependentResource() {
super(Service.class);
}

@Override
protected Service desired(WebPage webPage, Context<WebPage> context) {
Service service = loadYaml(Service.class, getClass(), "service.yaml");
service.getMetadata().setName(serviceName(webPage));
service.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
Map<String, String> labels = new HashMap<>();
labels.put("app", deploymentName(webPage));
service.getSpec().setSelector(labels);
return service;
}
}
Original file line number Diff line number Diff line change
@@ -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<WebPage> 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");
}
}
}
Original file line number Diff line number Diff line change
@@ -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<WebPage>, ErrorStatusHandler<WebPage> {

static final String SELECTOR = "managed";

@Override
public ErrorStatusUpdateControl<WebPage> updateErrorStatus(WebPage resource,
Context<WebPage> context, Exception e) {
return handleError(resource, e);
}

@Override
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context)
throws Exception {
simulateErrorIfRequested(webPage);

final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
.getMetadata().getName();
webPage.setStatus(createStatus(name));
return UpdateControl.updateStatus(webPage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -35,11 +52,9 @@ public WebPageReconciler(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}

InformerEventSource configMapEventSource;

@Override
public List<EventSource> prepareEventSources(EventSourceContext<WebPage> context) {
configMapEventSource =
var configMapEventSource =
new InformerEventSource<>(InformerConfiguration.from(context, ConfigMap.class)
.withLabelSelector(LOW_LEVEL_LABEL_KEY)
.build(), context);
Expand All @@ -55,11 +70,11 @@ public List<EventSource> prepareEventSources(EventSourceContext<WebPage> context
}

@Override
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> 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);
Expand Down Expand Up @@ -107,14 +122,6 @@ public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> 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;
Expand Down Expand Up @@ -196,22 +203,9 @@ public static Map<String, String> 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<WebPage> updateErrorStatus(
WebPage resource, Context<WebPage> context, Exception e) {
resource.getStatus().setErrorMessage("Error: " + e.getMessage());
return ErrorStatusUpdateControl.updateStatus(resource);
return handleError(resource, e);
}
}
Loading