[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:
parent
d317cbf552
commit
d27a274136
|
@ -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
|
||||
|
|
@ -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.
|
|
@ -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:
|
||||
- '*'
|
|
@ -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
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.baeldung.operators.deptrack.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OperatorConfiguration {
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.baeldung.operators.deptrack.resources.deptrack;
|
||||
|
||||
import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;
|
||||
|
||||
public class DeptrackStatus extends ObservedGenerationAwareStatus {
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
management.endpoint.health.probes.enabled=true
|
|
@ -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
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: deptrack-frontend
|
||||
namespace: deptrack
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 8Gi
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue