Skip to content

Commit

Permalink
refactor: extracts Kubernetes related classes
Browse files Browse the repository at this point in the history
  • Loading branch information
m4gshm committed Apr 16, 2024
1 parent e80668b commit 6c31c4d
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 233 deletions.
9 changes: 0 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ To run locally, it is highly recommended to use
``` java
package example;

import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.github.m4gshm.testcontainers.PostgresqlPod;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
Expand All @@ -42,19 +40,12 @@ import org.testcontainers.containers.JdbcDatabaseContainer;

import java.sql.SQLException;

import static io.fabric8.kubernetes.client.Config.autoConfigure;
import static java.sql.DriverManager.getConnection;

public class JdbcTest {

protected static JdbcDatabaseContainer<?> postgres = new PostgresqlPod();

private static Config getConfig() {
var config = autoConfigure(null);
config.setDefaultNamespace(true);
return config;
}

@BeforeAll
static void beforeAll() {
postgres.start();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.github.m4gshm.testcontainers;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.testcontainers.containers.Container;

import java.security.SecureRandom;

Expand All @@ -24,17 +24,16 @@ public static DefaultPodNameGenerator newDefaultPodNameGenerator(boolean useImag
return new DefaultPodNameGenerator(new SecureRandom(), "testcontainer-", useImageNamePrefix);
}

private static String imageNamePrefix(Container<?> container) {
var dockerImageName = container.getDockerImageName();
private static String imageNamePrefix(@NonNull String dockerImageName) {
var parts = dockerImageName.split(":");
var prefix = parts.length > 0 ? parts[0] : "";
return !prefix.isEmpty() ? prefix + "-" : "";
}

@Override
public String generatePodName(Container<?> container) {
public String generatePodName(@NonNull String dockerImageName) {
var maxNameLength = 63;
var prefix = this.prefix + imageNamePrefix(container);
var prefix = this.prefix + imageNamePrefix(dockerImageName);
var reminds = maxNameLength - prefix.length();
var bytes = new byte[reminds / 2];
random.nextBytes(bytes);
Expand Down
20 changes: 5 additions & 15 deletions src/main/java/io/github/m4gshm/testcontainers/GenericPod.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,31 @@
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.containers.GenericContainer;

import static java.util.Objects.requireNonNull;

/**
* General purpose pod engine implementation.
*
* @param <SELF> - implementation type.
*/
@Slf4j
public class GenericPod<SELF extends GenericPod<SELF>> extends GenericContainer<SELF> implements PodAware {

private PodEngine<SELF> podEngine;
@Delegate
private final PodEngine<SELF> podEngine;

public GenericPod(@NonNull String dockerImageName) {
super(dockerImageName);
setDockerImageName(dockerImageName);
var podEngine = this.podEngine;
requireNonNull(podEngine, "podEngine is null");
podEngine = new PodEngine<>((SELF) this, dockerImageName);
waitStrategy = new PodPortWaitStrategy();
}

@Delegate
@Override
public PodEngine<SELF> getPod() {
initPodEngine();
return podEngine;
}

private void initPodEngine() {
if (podEngine == null) {
podEngine = new PodEngine<>((SELF) this);
}
}

@Override
protected void doStart() {
configure();
podEngine.start();
}

@Override
Expand Down
14 changes: 2 additions & 12 deletions src/main/java/io/github/m4gshm/testcontainers/JdbcDatabasePod.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.containers.JdbcDatabaseContainer;

import static java.util.Objects.requireNonNull;


/**
* Base relation database pod engine that provides JDBC connection config.
Expand Down Expand Up @@ -37,25 +35,17 @@ public abstract class JdbcDatabasePod<SELF extends JdbcDatabasePod<SELF>> extend

public JdbcDatabasePod(@NonNull String dockerImageName) {
super(dockerImageName);
setDockerImageName(dockerImageName);
requireNonNull(this.podEngine, "podEngine is null");
podEngine = new PodEngine<>((SELF) this, dockerImageName);
}

@Delegate(excludes = Excludes.class)
public PodEngine<SELF> getPod() {
initPodEngine();
return podEngine;
}

private void initPodEngine() {
if (podEngine == null) {
podEngine = new PodEngine<>((SELF) this);
}
}

@Override
public void start() {
getPod().start();
podEngine.start();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodStatus;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.LocalPortForward;
import io.fabric8.kubernetes.client.dsl.ExecWatch;
import io.fabric8.kubernetes.client.dsl.PodResource;
import io.fabric8.kubernetes.client.dsl.internal.ExecWebSocketListener;
import io.fabric8.kubernetes.client.http.WebSocket;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.net.InetAddress;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import static java.lang.Boolean.TRUE;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toMap;

@Slf4j
@UtilityClass
public class PodEngineUtils {
public class KubernetesUtils {
public static final String RUNNING = "Running";
public static final String PENDING = "Pending";
public static final String UNKNOWN = "Unknown";
Expand Down Expand Up @@ -89,4 +96,32 @@ public static boolean isRunning(Pod pod) {
}).findFirst().orElse(null);
}

public static Map<Integer, LocalPortForward> startPortForward(
PodResource pod, InetAddress inetAddress, Collection<Integer> ports) {
var podName = pod.get().getMetadata().getName();
return ports.stream().collect(toMap(port -> port, port -> {
var localPortForward = inetAddress != null
? pod.portForward(port, inetAddress, 0)
: pod.portForward(port);
var localAddress = localPortForward.getLocalAddress();
var localPort = localPortForward.getLocalPort();
if (localPortForward.errorOccurred()) {
var clientThrowables = localPortForward.getClientThrowables();
if (!clientThrowables.isEmpty()) {
var throwable = clientThrowables.iterator().next();
throw new StartPodException("port forward client error", podName, throwable);
}
var serverThrowables = localPortForward.getServerThrowables();
if (!serverThrowables.isEmpty()) {
var throwable = serverThrowables.iterator().next();
throw new StartPodException("port forward server error", podName, throwable);
}
} else {
log.info("port forward local {}:{} to remote {}:{}, ", localAddress.getHostAddress(), localPort,
podName, port);
}
return localPortForward;
}));
}

}
10 changes: 1 addition & 9 deletions src/main/java/io/github/m4gshm/testcontainers/MongoDBPod.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,13 @@ public MongoDBPod() {

public MongoDBPod(@NonNull final String dockerImageName) {
super(dockerImageName);
setDockerImageName(dockerImageName);
requireNonNull(this.podEngine, "podEngine is null");
podEngine = new PodEngine<>(this, dockerImageName);
}

@Delegate
@Override
public PodEngine<MongoDBContainer> getPod() {
initPodEngine();
return podEngine;
}

private void initPodEngine() {
if (podEngine == null) {
podEngine = new PodEngine<>(this);
}
}

}
109 changes: 109 additions & 0 deletions src/main/java/io/github/m4gshm/testcontainers/PodBuilderFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package io.github.m4gshm.testcontainers;

import io.fabric8.kubernetes.api.model.ContainerBuilder;
import io.fabric8.kubernetes.api.model.ContainerPort;
import io.fabric8.kubernetes.api.model.ContainerPortBuilder;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.PodSecurityContextBuilder;
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
import io.fabric8.kubernetes.api.model.SecurityContextBuilder;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;

@Getter
@Setter
public class PodBuilderFactory {
private final Map<String, String> labels = new HashMap<>();
private final Map<Integer, Integer> hostPorts = new HashMap<>();

private String dockerImageName;
private Boolean runAsNonRoot;
private Long runAsUser;
private Long runAsGroup;
private Long fsGroup;
private boolean privilegedMode;
private boolean allowPrivilegeEscalation;
private String imagePullPolicy = "Always";
private String imagePullSecretName;
private UnaryOperator<PodBuilder> podBuilderCustomizer;
private UnaryOperator<ContainerBuilder> containerBuilderCustomizer;
private String podContainerName = "main";
private List<String> entryPoint;
private String portProtocol = "TCP";
private List<EnvVar> vars;
private String[] args;
private List<Integer> exposedPorts = new ArrayList<>();
private boolean hostPortEnabled = false;

public PodBuilder newPodBuilder() {
var containerBuilder = new ContainerBuilder()
.withImagePullPolicy(getImagePullPolicy())
.withImage(getDockerImageName())
.withSecurityContext(new SecurityContextBuilder()
.withRunAsNonRoot(getRunAsNonRoot())
.withRunAsUser(getRunAsUser())
.withRunAsGroup(getRunAsGroup())
.withPrivileged(isPrivilegedMode())
.withAllowPrivilegeEscalation(isAllowPrivilegeEscalation())
.build())
.withName(getPodContainerName())
.withArgs(getArgs())
.withCommand(getEntryPoint())
.withPorts(getContainerPorts())
.withEnv(getVars());
if (getContainerBuilderCustomizer() != null) {
containerBuilder = getContainerBuilderCustomizer().apply(containerBuilder);
}
var podBuilder = new PodBuilder()
.withMetadata(new ObjectMetaBuilder()
.addToLabels(getLabels())
.addToLabels(Map.of(
PodEngine.ORG_TESTCONTAINERS_TYPE, PodEngine.KUBECONTAINERS
))
.build())
.withSpec(new PodSpecBuilder()
.withSecurityContext(new PodSecurityContextBuilder()
.withRunAsNonRoot(getRunAsNonRoot())
.withRunAsUser(getRunAsUser())
.withFsGroup(getFsGroup())
.build())
.withImagePullSecrets(new LocalObjectReferenceBuilder()
.withName(getImagePullSecretName())
.build())
.withContainers(containerBuilder.build())
.build());

if (getPodBuilderCustomizer() != null) {
podBuilder = getPodBuilderCustomizer().apply(podBuilder);
}
return podBuilder;
}

@NotNull
protected List<ContainerPort> getContainerPorts() {
return exposedPorts.stream().map(port -> {
var portBuilder = new ContainerPortBuilder()
.withContainerPort(port)
.withProtocol(getPortProtocol());
if (isHostPortEnabled()) {
portBuilder.withHostPort(getHostPorts().get(port));
}
return portBuilder.build();
}).toList();
}

public void addHostPort(Integer port, Integer hostPort) {
getHostPorts().put(port, hostPort);
}

}
Loading

0 comments on commit 6c31c4d

Please sign in to comment.