[BAEL-7049] Kubernetes Operator (#15469)

* [BAEL-7049] Article Code

* [BAEL-7049] Fix README

---------

Co-authored-by: Philippe Sevestre <psevestre@gmail.com>
This commit is contained in:
psevestre 2024-01-10 23:11:13 -03:00 committed by GitHub
parent d317cbf552
commit d27a274136
28 changed files with 1073 additions and 0 deletions

View File

@ -0,0 +1,41 @@
#Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties
.flattened-pom.xml
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env

View File

@ -0,0 +1,4 @@
# Dependency-Track operator
This sample demonstrates how to create a simple operator using the Java Operator Framework. In our case, the operator will facilitate
the deployment of a Dependency-Track instance on a cluster.

View File

@ -0,0 +1,88 @@
apiVersion: v1
kind: Namespace
metadata:
name: deptrack-operator
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: deptrack-operator
namespace: deptrack-operator
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deptrack-operator
namespace: deptrack-operator
spec:
selector:
matchLabels:
app: deptrack-operator
template:
metadata:
labels:
app: deptrack-operator
spec:
serviceAccountName: deptrack-operator
containers:
- name: operator
image: deptrack-operator
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: deptrack-operator-admin
subjects:
- kind: ServiceAccount
name: deptrack-operator
namespace: deptrack-operator
roleRef:
kind: ClusterRole
name: deptrack-operator
apiGroup: ""
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: deptrack-operator
rules:
- apiGroups:
- ""
resources:
- deployments
- services
- ingresses
- configmaps
- secrets
verbs:
- '*'
- apiGroups:
- "apiextensions.k8s.io"
resources:
- customresourcedefinitions
verbs:
- '*'
- apiGroups:
- "com.baeldung"
resources:
- deptrackresources
- deptrackresources/status
verbs:
- '*'

View File

@ -0,0 +1,12 @@
apiVersion: com.baeldung/v1
kind: DeptrackResource
metadata:
namespace: test
name: deptrack1
labels:
project: tutorials
annotations:
author: Philippe Sevestre
spec:
ingressHostname: deptrack.172.31.42.16.nip.io

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>./../../parent-boot-3</relativePath>
</parent>
<artifactId>k8s-operator</artifactId>
<version>0.1.0-SNAPSHOT</version>
<name>k8s-operator</name>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<josdk.version>4.6.0</josdk.version>
<fabric8-client.version>6.9.2</fabric8-client.version>
<bouncycastle.version>1.77</bouncycastle.version>
<slf4j.version>2.0.9</slf4j.version>
<operator-framework-spring-boot.version>5.4.0</operator-framework-spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-spring-boot-starter</artifactId>
<version>${operator-framework-spring-boot.version}</version>
</dependency>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-spring-boot-starter-test</artifactId>
<version>${operator-framework-spring-boot.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>crd-generator-apt</artifactId>
<version>${fabric8-client.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>deptrack-operator</name>
</image>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,18 @@
package com.baeldung.operators.deptrack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}

View File

@ -0,0 +1,10 @@
package com.baeldung.operators.deptrack.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OperatorConfiguration {
}

View File

@ -0,0 +1,46 @@
package com.baeldung.operators.deptrack.controller.deptrack;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import com.baeldung.operators.deptrack.resources.deptrack.DeptrackApiServerDeploymentResource;
import com.baeldung.operators.deptrack.resources.deptrack.DeptrackApiServerServiceResource;
import com.baeldung.operators.deptrack.resources.deptrack.DeptrackFrontendDeploymentResource;
import com.baeldung.operators.deptrack.resources.deptrack.DeptrackFrontendServiceResource;
import com.baeldung.operators.deptrack.resources.deptrack.DeptrackIngressResource;
import com.baeldung.operators.deptrack.resources.deptrack.DeptrackResource;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ControllerConfiguration(dependents = {
@Dependent(name = DeptrackApiServerDeploymentResource.COMPONENT, type = DeptrackApiServerDeploymentResource.class),
@Dependent(name = DeptrackFrontendDeploymentResource.COMPONENT, type = DeptrackFrontendDeploymentResource.class),
@Dependent(name = DeptrackApiServerServiceResource.COMPONENT, type = DeptrackApiServerServiceResource.class),
@Dependent(name = DeptrackFrontendServiceResource.COMPONENT, type = DeptrackFrontendServiceResource.class),
@Dependent(type = DeptrackIngressResource.class )
})
@Slf4j
@RequiredArgsConstructor
@Component
public class DeptrackOperatorReconciler implements Reconciler<DeptrackResource> {
private final ApplicationContext ctx;
@PostConstruct
void onPostConstruct() {
log.info("Reconciler created");
}
@Override
public UpdateControl<DeptrackResource> reconcile(DeptrackResource resource, Context<DeptrackResource> context) throws Exception {
return UpdateControl.noUpdate();
}
}

View File

@ -0,0 +1,9 @@
package com.baeldung.operators.deptrack.resources;
public interface Constants {
String OPERATOR_NAME = "dependency-track-demo-operator";
String DEFAULT_API_SERVER_IMAGE = "dependencytrack/apiserver";
String DEFAULT_FRONTEND_IMAGE = "dependencytrack/frontend";
}

View File

@ -0,0 +1,53 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import com.baeldung.operators.deptrack.resources.Constants;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.fabric8.kubernetes.api.model.ManagedFieldsEntry;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.client.CustomResource;
public final class BuilderHelper {
private static ObjectMapper om;
static {
om = new ObjectMapper(new YAMLFactory());
}
private BuilderHelper(){}
public static <T extends CustomResource<?,?>> ObjectMetaBuilder fromPrimary(T primary, String component) {
return new ObjectMetaBuilder()
.withNamespace(primary.getMetadata().getNamespace())
.withManagedFields((List<ManagedFieldsEntry>)null)
.addToLabels("component", component)
.addToLabels("name", primary.getMetadata().getName())
.withName(primary.getMetadata().getName() + "-" + component)
.addToLabels("ManagedBy", Constants.OPERATOR_NAME);
}
public static <T> T loadTemplate(Class<T> clazz, String resource) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if ( cl == null ) {
cl = BuilderHelper.class.getClassLoader();
}
try (InputStream is = cl.getResourceAsStream(resource)){
return loadTemplate(clazz, is);
}
catch(IOException ioe) {
throw new RuntimeException("Unable to load classpath resource '" + resource + "': " + ioe.getMessage());
}
}
public static <T> T loadTemplate(Class<T> clazz, InputStream is) throws IOException{
return om.readValue(is, clazz);
}
}

View File

@ -0,0 +1,102 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import static com.baeldung.operators.deptrack.resources.deptrack.BuilderHelper.fromPrimary;
import java.util.Map;
import org.springframework.util.StringUtils;
import com.baeldung.operators.deptrack.resources.Constants;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.LabelSelectorBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.apps.DeploymentSpec;
import io.fabric8.kubernetes.api.model.apps.DeploymentSpecBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ResourceIDMatcherDiscriminator;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@KubernetesDependent( resourceDiscriminator = DeptrackApiServerDeploymentResource.Discriminator.class)
public class DeptrackApiServerDeploymentResource extends CRUDKubernetesDependentResource<Deployment, DeptrackResource> {
public static final String COMPONENT = "api-server";
private Deployment template;
public DeptrackApiServerDeploymentResource() {
super(Deployment.class);
this.template = BuilderHelper.loadTemplate(Deployment.class, "templates/api-server-deployment.yaml");
}
@Override
protected Deployment desired(DeptrackResource primary, Context<DeptrackResource> context) {
ObjectMeta meta = fromPrimary(primary,COMPONENT)
.build();
return new DeploymentBuilder(template)
.withMetadata(meta)
.withSpec(buildSpec(primary, meta))
.build();
}
private DeploymentSpec buildSpec(DeptrackResource primary, ObjectMeta primaryMeta) {
return new DeploymentSpecBuilder()
.withSelector(buildSelector(primaryMeta.getLabels()))
.withReplicas(1) // Dependenty track does not support multiple pods (yet)
.withTemplate(buildPodTemplate(primary,primaryMeta))
.build();
}
private LabelSelector buildSelector(Map<String, String> labels) {
return new LabelSelectorBuilder()
.addToMatchLabels(labels)
.build();
}
private PodTemplateSpec buildPodTemplate(DeptrackResource primary, ObjectMeta primaryMeta) {
return new PodTemplateSpecBuilder()
.withMetadata(primaryMeta)
.withSpec(buildPodSpec(primary))
.build();
}
private PodSpec buildPodSpec(DeptrackResource primary) {
// Check for version override
String imageVersion = StringUtils.hasText(primary.getSpec().getApiServerVersion())?
":" + primary.getSpec().getApiServerVersion().trim():"";
// Check for image override
String imageName = StringUtils.hasText(primary.getSpec().getApiServerImage())?
primary.getSpec().getApiServerImage().trim(): Constants.DEFAULT_API_SERVER_IMAGE;
//@formatter:off
return new PodSpecBuilder(template.getSpec().getTemplate().getSpec())
.editContainer(0) // Assumes we have a single container
.withImage(imageName + imageVersion)
.and()
.build();
//@formatter:on
}
static class Discriminator extends ResourceIDMatcherDiscriminator<Deployment,DeptrackResource> {
public Discriminator() {
super(COMPONENT, (p) -> new ResourceID(p.getMetadata()
.getName() + "-" + COMPONENT, p.getMetadata()
.getNamespace()));
}
}
}

View File

@ -0,0 +1,52 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import static com.baeldung.operators.deptrack.resources.deptrack.BuilderHelper.fromPrimary;
import java.util.HashMap;
import java.util.Map;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ResourceIDMatcherDiscriminator;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@KubernetesDependent(resourceDiscriminator = DeptrackApiServerServiceResource.Discriminator.class)
public class DeptrackApiServerServiceResource extends CRUDKubernetesDependentResource<Service, DeptrackResource> {
public static final String COMPONENT = "api-server-service";
private Service template;
public DeptrackApiServerServiceResource() {
super(Service.class);
this.template = BuilderHelper.loadTemplate(Service.class, "templates/api-server-service.yaml");
}
@Override
protected Service desired(DeptrackResource primary, Context<DeptrackResource> context) {
ObjectMeta meta = fromPrimary(primary,COMPONENT)
.build();
Map<String, String> selector = new HashMap<>(meta.getLabels());
selector.put("component", DeptrackApiServerDeploymentResource.COMPONENT);
return new ServiceBuilder(template)
.withMetadata(meta)
.editSpec()
.withSelector(selector)
.endSpec()
.build();
}
static class Discriminator extends ResourceIDMatcherDiscriminator<Service,DeptrackResource> {
public Discriminator() {
super(COMPONENT, (p) -> new ResourceID(p.getMetadata().getName() + "-" + COMPONENT,p.getMetadata().getNamespace()));
}
}
}

View File

@ -0,0 +1,102 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import static com.baeldung.operators.deptrack.resources.deptrack.BuilderHelper.fromPrimary;
import java.util.Map;
import org.springframework.util.StringUtils;
import com.baeldung.operators.deptrack.resources.Constants;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.LabelSelectorBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.apps.DeploymentSpec;
import io.fabric8.kubernetes.api.model.apps.DeploymentSpecBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ResourceIDMatcherDiscriminator;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@KubernetesDependent(resourceDiscriminator = DeptrackFrontendDeploymentResource.Discriminator.class)
public class DeptrackFrontendDeploymentResource extends CRUDKubernetesDependentResource<Deployment, DeptrackResource> {
public static final String COMPONENT = "frontend";
private Deployment template;
public DeptrackFrontendDeploymentResource() {
super(Deployment.class);
this.template = BuilderHelper.loadTemplate(Deployment.class, "templates/frontend-deployment.yaml");
}
@Override
protected Deployment desired(DeptrackResource primary, Context<DeptrackResource> context) {
ObjectMeta meta = fromPrimary(primary,COMPONENT)
.build();
return new DeploymentBuilder(template).withMetadata(meta)
.withSpec(buildSpec(primary, meta))
.build();
}
private DeploymentSpec buildSpec(DeptrackResource primary, ObjectMeta primaryMeta) {
return new DeploymentSpecBuilder().withSelector(buildSelector(primaryMeta.getLabels()))
.withReplicas(1) // Dependency track does not support multiple pods (yet)
.withTemplate(buildPodTemplate(primary, primaryMeta))
.build();
}
private LabelSelector buildSelector(Map<String, String> labels) {
return new LabelSelectorBuilder().addToMatchLabels(labels)
.build();
}
private PodTemplateSpec buildPodTemplate(DeptrackResource primary, ObjectMeta primaryMeta) {
return new PodTemplateSpecBuilder().withMetadata(primaryMeta)
.withSpec(buildPodSpec(primary))
.build();
}
private PodSpec buildPodSpec(DeptrackResource primary) {
// Check for version override
String imageVersion = StringUtils.hasText(primary.getSpec()
.getFrontendVersion()) ? ":" + primary.getSpec()
.getFrontendVersion()
.trim() : "";
// Check for image override
String imageName = StringUtils.hasText(primary.getSpec()
.getFrontendImage()) ? primary.getSpec()
.getFrontendImage()
.trim() : Constants.DEFAULT_FRONTEND_IMAGE;
return new PodSpecBuilder(template.getSpec().getTemplate().getSpec())
.editContainer(0)
.withImage(imageName + imageVersion)
.editFirstEnv()
.withName("API_BASE_URL")
.withValue("https://" + primary.getSpec().getIngressHostname())
.endEnv()
.and()
.build();
}
static class Discriminator extends ResourceIDMatcherDiscriminator<Deployment,DeptrackResource> {
public Discriminator() {
super(COMPONENT, (p) -> new ResourceID(p.getMetadata()
.getName() + "-" + COMPONENT, p.getMetadata()
.getNamespace()));
}
}
}

View File

@ -0,0 +1,59 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import static com.baeldung.operators.deptrack.resources.deptrack.BuilderHelper.fromPrimary;
import java.util.HashMap;
import java.util.Map;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ResourceIDMatcherDiscriminator;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
@KubernetesDependent(resourceDiscriminator = DeptrackFrontendServiceResource.Discriminator.class)
public class DeptrackFrontendServiceResource extends CRUDKubernetesDependentResource<Service, DeptrackResource> {
public static final String COMPONENT = "frontend-service";
private Service template;
public DeptrackFrontendServiceResource() {
super(Service.class);
this.template = BuilderHelper.loadTemplate(Service.class, "templates/frontend-service.yaml");
}
@Override
protected Service desired(DeptrackResource primary, Context<DeptrackResource> context) {
ObjectMeta meta = fromPrimary(primary,COMPONENT)
.build();
Map<String, String> selector = new HashMap<>(meta.getLabels());
selector.put("component", DeptrackFrontendDeploymentResource.COMPONENT);
return new ServiceBuilder(template)
.withMetadata(meta)
.editSpec()
.withSelector(selector)
.endSpec()
.build();
}
// static class Discriminator implements ResourceDiscriminator<Service,DeptrackResource> {
// @Override
// public Optional<Service> distinguish(Class<Service> resource, DeptrackResource primary, Context<DeptrackResource> context) {
// var ies = context.eventSourceRetriever().getResourceEventSourceFor(Service.class,COMPONENT);
// return ies.getSecondaryResource(primary);
// }
// }
static class Discriminator extends ResourceIDMatcherDiscriminator<Service,DeptrackResource> {
public Discriminator() {
super(COMPONENT, (p) -> new ResourceID(p.getMetadata().getName() + "-" + COMPONENT,p.getMetadata().getNamespace()));
}
}
}

View File

@ -0,0 +1,79 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import static com.baeldung.operators.deptrack.resources.deptrack.BuilderHelper.fromPrimary;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressRuleValue;
import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressRuleValueBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
@KubernetesDependent
public class DeptrackIngressResource extends CRUDKubernetesDependentResource<Ingress,DeptrackResource> {
private static final String COMPONENT = "ingress";
private final Ingress template;
public DeptrackIngressResource() {
super(Ingress.class);
this.template = BuilderHelper.loadTemplate(Ingress.class, "templates/ingress.yaml");
}
@Override
protected Ingress desired(DeptrackResource primary, Context<DeptrackResource> context) {
ObjectMeta meta = fromPrimary(primary,COMPONENT)
.build();
return new IngressBuilder(template)
.withMetadata(meta)
.editSpec()
.editDefaultBackend()
.editOrNewService()
.withName(primary.getFrontendServiceName())
.endService()
.endDefaultBackend()
.editFirstRule()
.withHost(primary.getSpec().getIngressHostname())
.withHttp(buildHttpRule(primary))
.endRule()
.endSpec()
.build();
}
private HTTPIngressRuleValue buildHttpRule(DeptrackResource primary) {
return new HTTPIngressRuleValueBuilder()
// Backend route
.addNewPath()
.withPath("/api")
.withPathType("Prefix")
.withNewBackend()
.withNewService()
.withName(primary.getApiServerServiceName())
.withNewPort()
.withName("http")
.endPort()
.endService()
.endBackend()
.endPath()
// Frontend route
.addNewPath()
.withPath("/")
.withPathType("Prefix")
.withNewBackend()
.withNewService()
.withName(primary.getFrontendServiceName())
.withNewPort()
.withName("http")
.endPort()
.endService()
.endBackend()
.endPath()
.build();
}
}

View File

@ -0,0 +1,23 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Version;
@Group("com.baeldung")
@Version("v1")
public class DeptrackResource extends CustomResource<DeptrackSpec, DeptrackStatus> implements Namespaced {
@JsonIgnore
public String getFrontendServiceName() {
return this.getMetadata().getName() + "-" + DeptrackFrontendServiceResource.COMPONENT;
}
@JsonIgnore
public String getApiServerServiceName() {
return this.getMetadata().getName() + "-" + DeptrackApiServerServiceResource.COMPONENT;
}
}

View File

@ -0,0 +1,32 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import java.util.Map;
import lombok.Data;
@Data
public class DeptrackSpec {
// Images
private String apiServerImage = "dependencytrack/apiserver";
private String apiServerVersion = "";
private String frontendImage = "dependencytrack/frontend";
private String frontendVersion = "";
// PVC settings: NOT IMPLEMENTED
private String pvcClass = ""; // Use default storage class
private String pvcSize = "10Gi";
// Database settings: NOT IMPLEMENTED
private String dbUrl;
private String dbDriver = "org.postgresql.Driver";
private String dbSecret;
// Ingress settings
private String ingressHostname;
private Map<String,String> ingressAnnotations;
}

View File

@ -0,0 +1,6 @@
package com.baeldung.operators.deptrack.resources.deptrack;
import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;
public class DeptrackStatus extends ObservedGenerationAwareStatus {
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -0,0 +1 @@
management.endpoint.health.probes.enabled=true

View File

@ -0,0 +1,53 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: deptrack-api
namespace: deptrack
spec:
replicas: 1
selector:
matchLabels:
ManagedBy: deptrack-operator
component: api-server
template:
metadata:
labels:
ManagedBy: deptrack-operator
component: api-server
spec:
containers:
- name: main
image: dependencytrack/apiserver:4.10.1
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: http
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
livenessProbe:
failureThreshold: 3
httpGet:
path: /api/version
port: http
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
resources:
limits:
cpu: "2"
memory: 8Gi
requests:
cpu: "1"
memory: 2Gi
restartPolicy: Always

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: deptrack-frontend
namespace: deptrack
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi

View File

@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
labels:
ManagedBy: deptrack-operator
component: api-server-service
name: deptrack-api-server
namespace: deptrack
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
ManagedBy: deptrack-operator
component: api-server
sessionAffinity: None
type: ClusterIP

View File

@ -0,0 +1,46 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: deptrack-frontend
namespace: deptrack
spec:
replicas: 1
selector:
matchLabels:
ManagedBy: deptrack-operator
component: frontend
template:
metadata:
labels:
ManagedBy: deptrack-operator
component: frontend
spec:
containers:
- name: main
env:
- name: API_BASE_URL
value: https://example.com
image: dependencytrack/frontend
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
httpGet:
path: /
port: http
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: http
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1

View File

@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
labels:
ManagedBy: deptrack-operator
component: frontend-service
name: deptrack-frontend
namespace: deptrack
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
ManagedBy: deptrack-operator
component: frontend
sessionAffinity: None
type: ClusterIP

View File

@ -0,0 +1,33 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
labels:
ManagedBy: terraform
component: ingress
name: deptrack-ingress
namespace: deptrack
spec:
defaultBackend:
service:
name: deptrack-frontend
port:
name: http
rules:
- host: example.com
http:
paths:
- backend:
service:
name: deptrack-api-server
port:
name: http
path: /api
pathType: Prefix
- backend:
service:
name: deptrack-frontend
port:
name: http
path: /
pathType: Prefix

View File

@ -0,0 +1,30 @@
package com.baeldung.operators.deptrack;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.springboot.starter.test.EnableMockOperator;
@SpringBootTest
@EnableMockOperator(crdPaths = "classpath:META-INF/fabric8/deptrackresources.com.baeldung-v1.yml")
class ApplicationUnitTest {
@Autowired
KubernetesClient client;
@Test
void whenContextLoaded_thenCrdRegistered() {
assertThat(
client
.apiextensions()
.v1()
.customResourceDefinitions()
.withName("deptrackresources.com.baeldung")
.get())
.isNotNull();
}
}

View File

@ -18,6 +18,7 @@
<module>k8s-admission-controller</module>
<module>kubernetes-spring</module>
<module>k8s-java-heap-dump</module>
<module>k8s-operator</module>
</modules>
</project>