Skip to content

Commit

Permalink
feat: decouple ui container from backend
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosthe19916 committed Oct 11, 2024
1 parent dfec951 commit ce962cf
Show file tree
Hide file tree
Showing 15 changed files with 440 additions and 38 deletions.
3 changes: 3 additions & 0 deletions src/main/java/org/trustify/operator/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
@ConfigMapping(prefix = "related.image")
public interface Config {

@WithName("ui")
String uiImage();

@WithName("server")
String serverImage();

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/trustify/operator/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class Constants {

//
public static final String TRUSTI_NAME = "trustify";
public static final String TRUSTI_UI_NAME = "trustify-ui";
public static final String TRUSTI_SERVER_NAME = "trustify-server";
public static final String TRUSTI_DB_NAME = "trustify-db";

Expand All @@ -20,6 +21,9 @@ public class Constants {
public static final Map<String, String> SERVER_SELECTOR_LABELS = Map.of(
"trustify-operator/group", "server"
);
public static final Map<String, String> UI_SELECTOR_LABELS = Map.of(
"trustify-operator/group", "ui"
);

//
public static final Integer HTTP_PORT = 8080;
Expand All @@ -33,6 +37,9 @@ public class Constants {
public static final String DB_DEPLOYMENT_SUFFIX = "-" + TRUSTI_DB_NAME + "-deployment";
public static final String DB_SERVICE_SUFFIX = "-" + TRUSTI_DB_NAME + "-service";

public static final String UI_DEPLOYMENT_SUFFIX = "-" + TRUSTI_UI_NAME + "-deployment";
public static final String UI_SERVICE_SUFFIX = "-" + TRUSTI_UI_NAME + "-service";

public static final String SERVER_DEPLOYMENT_SUFFIX = "-" + TRUSTI_SERVER_NAME + "-deployment";
public static final String SERVER_SERVICE_SUFFIX = "-" + TRUSTI_SERVER_NAME + "-service";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import java.util.List;

public record TrustifySpec(
@JsonPropertyDescription("Custom Trustify UI image to be used. For internal use only")
String uiImage,

@JsonPropertyDescription("Custom Trustify Server image to be used. For internal use only")
String serverImage,

Expand All @@ -32,6 +35,10 @@ public record TrustifySpec(
@JsonPropertyDescription("In this section you can configure features related to HTTP and HTTPS")
HttpSpec httpSpec,

@JsonProperty("uiResourceLimits")
@JsonPropertyDescription("In this section you can configure resource limits settings for the UI.")
ResourcesLimitSpec uiResourceLimitSpec,

@JsonProperty("serverResourceLimits")
@JsonPropertyDescription("In this section you can configure resource limits settings for the Server.")
ResourcesLimitSpec serverResourceLimitSpec
Expand All @@ -46,6 +53,8 @@ public TrustifySpec() {
null,
null,
null,
null,
null,
null
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ServerDeploymentDiscriminator implements ResourceDiscriminator<Depl
public Optional<Deployment> distinguish(Class<Deployment> resource, Trustify cr, Context<Trustify> context) {
String deploymentName = ServerDeployment.getDeploymentName(cr);
ResourceID resourceID = new ResourceID(deploymentName, cr.getMetadata().getNamespace());
var informerEventSource = (InformerEventSource<Deployment, Trustify>) context.eventSourceRetriever().getResourceEventSourceFor(Deployment.class, TrustifyReconciler.SERVER_DEPLOYMENT_EVENT_SOURCE);
var informerEventSource = (InformerEventSource<Deployment, Trustify>) context.eventSourceRetriever().getResourceEventSourceFor(Deployment.class, TrustifyReconciler.DEPLOYMENT_EVENT_SOURCE);
return informerEventSource.get(resourceID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ServerServiceDiscriminator implements ResourceDiscriminator<Service
public Optional<Service> distinguish(Class<Service> resource, Trustify cr, Context<Trustify> context) {
String serviceName = ServerService.getServiceName(cr);
ResourceID resourceID = new ResourceID(serviceName, cr.getMetadata().getNamespace());
var informerEventSource = (InformerEventSource<Service, Trustify>) context.eventSourceRetriever().getResourceEventSourceFor(Service.class, TrustifyReconciler.SERVER_SERVICE_EVENT_SOURCE);
var informerEventSource = (InformerEventSource<Service, Trustify>) context.eventSourceRetriever().getResourceEventSourceFor(Service.class, TrustifyReconciler.SERVICE_EVENT_SOURCE);
return informerEventSource.get(resourceID);
}
}
221 changes: 221 additions & 0 deletions src/main/java/org/trustify/operator/cdrs/v2alpha1/ui/UIDeployment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package org.trustify.operator.cdrs.v2alpha1.ui;

import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.*;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.processing.dependent.Matcher;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.trustify.operator.Config;
import org.trustify.operator.Constants;
import org.trustify.operator.cdrs.v2alpha1.Trustify;
import org.trustify.operator.cdrs.v2alpha1.TrustifySpec;
import org.trustify.operator.cdrs.v2alpha1.server.ServerDeployment;
import org.trustify.operator.cdrs.v2alpha1.server.ServerService;
import org.trustify.operator.utils.CRDUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@KubernetesDependent(labelSelector = UIDeployment.LABEL_SELECTOR, resourceDiscriminator = UIDeploymentDiscriminator.class)
@ApplicationScoped
public class UIDeployment extends CRUDKubernetesDependentResource<Deployment, Trustify>
implements Matcher<Deployment, Trustify>, Condition<Deployment, Trustify> {

public static final String LABEL_SELECTOR = "app.kubernetes.io/managed-by=trustify-operator,component=ui";

@Inject
Config config;

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

@Override
protected Deployment desired(Trustify cr, Context<Trustify> context) {
return newDeployment(cr, context);
}

@Override
public Result<Deployment> match(Deployment actual, Trustify cr, Context<Trustify> context) {
final var container = actual.getSpec()
.getTemplate().getSpec().getContainers()
.stream()
.findFirst();

return Result.nonComputed(container
.map(c -> c.getImage() != null)
.orElse(false)
);
}

@Override
public boolean isMet(DependentResource<Deployment, Trustify> dependentResource, Trustify primary, Context<Trustify> context) {
return context.getSecondaryResource(Deployment.class, new UIDeploymentDiscriminator())
.map(deployment -> {
final var status = deployment.getStatus();
if (status != null) {
final var readyReplicas = status.getReadyReplicas();
return readyReplicas != null && readyReplicas >= 1;
}
return false;
})
.orElse(false);
}

@SuppressWarnings("unchecked")
private Deployment newDeployment(Trustify cr, Context<Trustify> context) {
final var contextLabels = (Map<String, String>) context.managedDependentResourceContext()
.getMandatory(Constants.CONTEXT_LABELS_KEY, Map.class);

return new DeploymentBuilder()
.withNewMetadata()
.withName(getDeploymentName(cr))
.withNamespace(cr.getMetadata().getNamespace())
.withLabels(contextLabels)
.addToLabels("component", "ui")
.addToLabels(Map.of(
"app.openshift.io/runtime", "nodejs"
))
.withAnnotations(Map.of("app.openshift.io/connects-to", """
[{"apiVersion": "apps/v1", "kind":"Deployment", "name": "%s"}]
""".formatted(ServerDeployment.getDeploymentName(cr))
))
.withOwnerReferences(CRDUtils.getOwnerReference(cr))
.endMetadata()
.withSpec(getDeploymentSpec(cr, context))
.build();
}

@SuppressWarnings("unchecked")
private DeploymentSpec getDeploymentSpec(Trustify cr, Context<Trustify> context) {
final var contextLabels = (Map<String, String>) context.managedDependentResourceContext()
.getMandatory(Constants.CONTEXT_LABELS_KEY, Map.class);

Map<String, String> selectorLabels = Constants.UI_SELECTOR_LABELS;
String image = Optional.ofNullable(cr.getSpec().uiImage()).orElse(config.uiImage());
String imagePullPolicy = Optional.ofNullable(cr.getSpec().imagePullPolicy()).orElse(config.imagePullPolicy());

TrustifySpec.ResourcesLimitSpec resourcesLimitSpec = CRDUtils.getValueFromSubSpec(cr.getSpec(), TrustifySpec::uiResourceLimitSpec)
.orElse(null);

return new DeploymentSpecBuilder()
.withStrategy(new DeploymentStrategyBuilder()
.withType("Recreate")
.build()
)
.withReplicas(1)
.withSelector(new LabelSelectorBuilder()
.withMatchLabels(selectorLabels)
.build()
)
.withTemplate(new PodTemplateSpecBuilder()
.withNewMetadata()
.withLabels(Stream
.concat(contextLabels.entrySet().stream(), selectorLabels.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
)
.endMetadata()
.withSpec(new PodSpecBuilder()
.withRestartPolicy("Always")
.withTerminationGracePeriodSeconds(60L)
.withImagePullSecrets(cr.getSpec().imagePullSecrets())
.withContainers(new ContainerBuilder()
.withName(Constants.TRUSTI_UI_NAME)
.withImage(image)
.withImagePullPolicy(imagePullPolicy)
.withEnv(getEnvVars(cr))
.withPorts(
new ContainerPortBuilder()
.withName("http")
.withProtocol("TCP")
.withContainerPort(Constants.HTTP_PORT)
.build()
)
.withLivenessProbe(new ProbeBuilder()
.withExec(new ExecActionBuilder()
.withCommand(
"/bin/sh",
"-c",
"ps -A | grep node"
)
.build()
)
.withInitialDelaySeconds(10)
.withTimeoutSeconds(1)
.withPeriodSeconds(5)
.withSuccessThreshold(1)
.withFailureThreshold(3)
.build()
)
.withReadinessProbe(new ProbeBuilder()
.withHttpGet(new HTTPGetActionBuilder()
.withPath("/")
.withNewPort(Constants.HTTP_PORT)
.withScheme("HTTP")
.build()
)
.withInitialDelaySeconds(10)
.withTimeoutSeconds(1)
.withPeriodSeconds(5)
.withSuccessThreshold(1)
.withFailureThreshold(3)
.build()
)
.withResources(new ResourceRequirementsBuilder()
.withRequests(Map.of(
"cpu", new Quantity(CRDUtils.getValueFromSubSpec(resourcesLimitSpec, TrustifySpec.ResourcesLimitSpec::cpuRequest).orElse("100m")),
"memory", new Quantity(CRDUtils.getValueFromSubSpec(resourcesLimitSpec, TrustifySpec.ResourcesLimitSpec::memoryRequest).orElse("350Mi"))
))
.withLimits(Map.of(
"cpu", new Quantity(CRDUtils.getValueFromSubSpec(resourcesLimitSpec, TrustifySpec.ResourcesLimitSpec::cpuLimit).orElse("500m")),
"memory", new Quantity(CRDUtils.getValueFromSubSpec(resourcesLimitSpec, TrustifySpec.ResourcesLimitSpec::memoryLimit).orElse("800Mi"))
))
.build()
)
.build()
)
.build()
)
.build()
)
.build();
}

private List<EnvVar> getEnvVars(Trustify cr) {
return Arrays.asList(
new EnvVarBuilder()
.withName("AUTH_REQUIRED")
.withValue("false")
.build(),
new EnvVarBuilder()
.withName("ANALYTICS_ENABLED")
.withValue("false")
.build(),
new EnvVarBuilder()
.withName("TRUSTIFY_API_URL")
.withValue(ServerService.getServiceName(cr))
.build(),
new EnvVarBuilder()
.withName("UI_INGRESS_PROXY_BODY_SIZE")
.withValue("50m")
.build(),
new EnvVarBuilder()
.withName("NODE_EXTRA_CA_CERTS")
.withValue("/opt/app-root/src/ca.crt")
.build()
);
}

public static String getDeploymentName(Trustify cr) {
return cr.getMetadata().getName() + Constants.UI_DEPLOYMENT_SUFFIX;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.trustify.operator.cdrs.v2alpha1.ui;

import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
import org.trustify.operator.cdrs.v2alpha1.Trustify;
import org.trustify.operator.controllers.TrustifyReconciler;

import java.util.Optional;

public class UIDeploymentDiscriminator implements ResourceDiscriminator<Deployment, Trustify> {
@Override
public Optional<Deployment> distinguish(Class<Deployment> resource, Trustify cr, Context<Trustify> context) {
String deploymentName = UIDeployment.getDeploymentName(cr);
ResourceID resourceID = new ResourceID(deploymentName, cr.getMetadata().getNamespace());
var informerEventSource = (InformerEventSource<Deployment, Trustify>) context.eventSourceRetriever().getResourceEventSourceFor(Deployment.class, TrustifyReconciler.DEPLOYMENT_EVENT_SOURCE);
return informerEventSource.get(resourceID);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.trustify.operator.cdrs.v2alpha1.server;
package org.trustify.operator.cdrs.v2alpha1.ui;

import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.api.model.networking.v1.IngressTLS;
Expand All @@ -15,11 +15,11 @@
import java.util.Collections;
import java.util.Map;

@KubernetesDependent(labelSelector = ServerIngress.LABEL_SELECTOR, resourceDiscriminator = ServerIngressDiscriminator.class)
@KubernetesDependent(labelSelector = UIIngress.LABEL_SELECTOR, resourceDiscriminator = UIIngressDiscriminator.class)
@ApplicationScoped
public class ServerIngress extends ServerIngressBase {
public class UIIngress extends UIIngressBase {

public static final String LABEL_SELECTOR = "app.kubernetes.io/managed-by=trustify-operator,component=server,component-variant=https";
public static final String LABEL_SELECTOR = "app.kubernetes.io/managed-by=trustify-operator,component=ui,component-variant=https";

@Override
@SuppressWarnings("unchecked")
Expand All @@ -29,7 +29,7 @@ protected Ingress desired(Trustify cr, Context<Trustify> context) {
context,
getIngressName(cr),
Map.of(
"component", "server",
"component", "ui",
"component-variant", "https"
),
Collections.emptyMap()
Expand All @@ -38,7 +38,7 @@ protected Ingress desired(Trustify cr, Context<Trustify> context) {

@Override
public boolean isMet(DependentResource<Ingress, Trustify> dependentResource, Trustify primary, Context<Trustify> context) {
return context.getSecondaryResource(Ingress.class, new ServerIngressDiscriminator())
return context.getSecondaryResource(Ingress.class, new UIIngressDiscriminator())
.map(in -> {
final var status = in.getStatus();
if (status != null) {
Expand Down
Loading

0 comments on commit ce962cf

Please sign in to comment.