From bc4f2aa58f2b24312791248bdd1322f865101566 Mon Sep 17 00:00:00 2001 From: Christoph Deppisch Date: Thu, 14 Nov 2024 23:35:33 +0100 Subject: [PATCH] chore(citrus-testcontainers): Support generic container --- .../actions/StartTestcontainersAction.java | 101 +++++++++++- .../actions/TestcontainersActionBuilder.java | 28 ++++ .../testcontainers/xml/Start.java | 146 +++++++++++++++++- .../testcontainers/yaml/Start.java | 76 ++++++++- .../xml/start-container-test.xml | 9 +- .../yaml/start-container-test.yaml | 6 + .../camel/yaml/CreateContext.java | 2 +- .../citrus-testcase-4.4.0-SNAPSHOT.xsd | 26 ++++ .../schema/xml/testcase/citrus-testcase.xsd | 26 ++++ 9 files changed, 400 insertions(+), 20 deletions(-) diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StartTestcontainersAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StartTestcontainersAction.java index d58b961cd5..62f4233846 100644 --- a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StartTestcontainersAction.java +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StartTestcontainersAction.java @@ -19,13 +19,18 @@ import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.citrusframework.context.TestContext; +import org.citrusframework.spi.Resource; +import org.citrusframework.spi.Resources; import org.citrusframework.testcontainers.TestContainersSettings; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; +import org.testcontainers.utility.MountableFile; import static org.citrusframework.testcontainers.TestcontainersHelper.getEnvVarName; import static org.citrusframework.testcontainers.actions.TestcontainersActionBuilder.testcontainers; @@ -77,11 +82,17 @@ protected void exposeConnectionSettings(C container, TestContext context) { dockerContainerName = dockerContainerName.substring(1); } - String containerType = containerName.toUpperCase().replaceAll("-", "_").replaceAll("\\.", "_"); - context.setVariable(getEnvVarName(containerType, "HOST"), container.getHost()); - context.setVariable(getEnvVarName(containerType, "CONTAINER_IP"), container.getHost()); - context.setVariable(getEnvVarName(containerType, "CONTAINER_ID"), dockerContainerId); - context.setVariable(getEnvVarName(containerType, "CONTAINER_NAME"), dockerContainerName); + if (containerName != null) { + String containerType = containerName.toUpperCase().replaceAll("-", "_").replaceAll("\\.", "_"); + context.setVariable(getEnvVarName(containerType, "HOST"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_IP"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_ID"), dockerContainerId); + context.setVariable(getEnvVarName(containerType, "CONTAINER_NAME"), dockerContainerName); + + if (!container.getExposedPorts().isEmpty()) { + context.setVariable(getEnvVarName(containerType, "PORT"), container.getFirstMappedPort()); + } + } } } @@ -110,6 +121,12 @@ public static abstract class AbstractBuilder, T ex protected C container; protected Network network; protected Duration startupTimeout = Duration.ofSeconds(TestContainersSettings.getStartupTimeout()); + + protected final Set exposedPorts = new HashSet<>(); + protected final List portBindings = new ArrayList<>(); + + protected final Map volumeMounts = new HashMap<>(); + private boolean autoRemoveResources = TestContainersSettings.isAutoRemoveResources(); public B containerName(String name) { @@ -193,6 +210,60 @@ public B autoRemove(boolean enabled) { return self; } + public B addExposedPort(int port) { + this.exposedPorts.add(port); + return self; + } + + public B addExposedPorts(int... ports) { + for (int port : ports) { + addExposedPort(port); + } + return self; + } + + public B addExposedPorts(List ports) { + exposedPorts.addAll(ports); + return self; + } + + public B addPortBinding(String binding) { + this.portBindings.add(binding); + return self; + } + + public B addPortBindings(String... bindings) { + for (String binding : bindings) { + addPortBinding(binding); + } + return self; + } + + public B addPortBindings(List bindings) { + portBindings.addAll(bindings); + return self; + } + + public B withVolumeMount(MountableFile mountableFile, String containerPath) { + this.volumeMounts.put(mountableFile, containerPath); + return self; + } + + public B withVolumeMount(String mountableFile, String mountPath) { + return withVolumeMount(Resources.create(mountableFile), mountPath); + } + + public B withVolumeMount(Resource mountableFile, String mountPath) { + if (mountableFile instanceof Resources.ClasspathResource) { + this.volumeMounts.put(MountableFile.forClasspathResource(mountableFile.getLocation()), mountPath); + } else if (mountableFile instanceof Resources.FileSystemResource) { + this.volumeMounts.put(MountableFile.forHostPath(mountableFile.getFile().getAbsolutePath()), mountPath); + } else { + this.volumeMounts.put(MountableFile.forHostPath(mountableFile.getLocation()), mountPath); + } + return self; + } + protected void prepareBuild() { } @@ -205,7 +276,11 @@ public T build() { if (network != null) { container.withNetwork(network); - container.withNetworkAliases(containerName); + if (serviceName != null) { + container.withNetworkAliases(serviceName); + } else if (containerName != null) { + container.withNetworkAliases(containerName); + } } container.withStartupTimeout(startupTimeout); @@ -214,10 +289,24 @@ public T build() { container.withLabels(labels); container.withEnv(env); + exposedPorts.forEach(container::addExposedPort); + container.setPortBindings(portBindings); + + volumeMounts.forEach((mountableFile, containerPath) -> + container.withCopyFileToContainer(mountableFile, containerPath)); + if (!commandLine.isEmpty()) { container.withCommand(commandLine.toArray(String[]::new)); } + if (containerName == null && image != null) { + if (image.contains(":")) { + containerName = image.split(":")[0]; + } else { + containerName = image; + } + } + return doBuild(); } } diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersActionBuilder.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersActionBuilder.java index 269ce9534b..6d124cbd4c 100644 --- a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersActionBuilder.java +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersActionBuilder.java @@ -37,6 +37,14 @@ public static TestcontainersActionBuilder testcontainers() { return new TestcontainersActionBuilder(); } + /** + * Manage generic testcontainers. + * @return + */ + public GenericContainerActionBuilder container() { + return new GenericContainerActionBuilder(); + } + /** * Manage LocalStack testcontainers. * @return @@ -100,6 +108,26 @@ public StopTestcontainersAction.Builder stop() { return builder; } + public class GenericContainerActionBuilder { + /** + * Start generic testcontainers instance. + */ + public StartTestcontainersAction.Builder start() { + StartTestcontainersAction.Builder builder = new StartTestcontainersAction.Builder<>(); + delegate = builder; + return builder; + } + + /** + * Stop generic testcontainers instance. + */ + public StopTestcontainersAction.Builder stop() { + StopTestcontainersAction.Builder builder = new StopTestcontainersAction.Builder(); + delegate = builder; + return builder; + } + } + public class LocalStackActionBuilder { /** * Start LocalStack testcontainers instance. diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Start.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Start.java index a6cd42fbf8..737b459fff 100644 --- a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Start.java +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Start.java @@ -187,15 +187,24 @@ private void configureStartActionBuilder(StartTestcontainersAction.AbstractBuild } if (container.getEnvironmentVariables() != null) { - container.getEnvironmentVariables().getVariables().forEach(variable -> { - builder.withEnv(variable.getName(), variable.getValue()); - }); + container.getEnvironmentVariables().getVariables().forEach(variable -> builder.withEnv(variable.getName(), variable.getValue())); } if (container.getLabels() != null) { - container.getLabels().getLabels().forEach(label -> { - builder.withLabel(label.getName(), label.getValue()); - }); + container.getLabels().getLabels().forEach(label -> builder.withLabel(label.getName(), label.getValue())); + } + + if (container.getExposedPorts() != null) { + container.getExposedPorts().getPorts().forEach(builder::addExposedPort); + } + + if (container.getPortBindings() != null) { + container.getPortBindings().getBindings().forEach(builder::addPortBinding); + } + + if (container.getVolumeMounts() != null) { + container.getVolumeMounts().getMounts().forEach(mount -> + builder.withVolumeMount(mount.getFile(), mount.getMountPath())); } } @@ -203,6 +212,9 @@ private void configureStartActionBuilder(StartTestcontainersAction.AbstractBuild @XmlType(name = "", propOrder = { "labels", "environmentVariables", + "exposedPorts", + "portBindings", + "volumeMounts" }) public static class Container { @@ -230,6 +242,15 @@ public static class Container { @XmlElement protected Labels labels; + @XmlElement(name = "exposed-ports") + protected ExposedPorts exposedPorts; + + @XmlElement(name = "port-bindings") + protected PortBindings portBindings; + + @XmlElement(name = "volume-mounts") + protected VolumeMounts volumeMounts; + public String getName() { return name; } @@ -293,6 +314,119 @@ public Labels getLabels() { public void setLabels(Labels labels) { this.labels = labels; } + + public ExposedPorts getExposedPorts() { + return exposedPorts; + } + + public void setExposedPorts(ExposedPorts exposedPorts) { + this.exposedPorts = exposedPorts; + } + + public PortBindings getPortBindings() { + return portBindings; + } + + public void setPortBindings(PortBindings portBindings) { + this.portBindings = portBindings; + } + + public VolumeMounts getVolumeMounts() { + return volumeMounts; + } + + public void setVolumeMounts(VolumeMounts volumeMounts) { + this.volumeMounts = volumeMounts; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "mounts" + }) + public static class VolumeMounts { + + @XmlElement(name = "mount") + private List mounts; + + public List getMounts() { + if (mounts == null) { + mounts = new ArrayList<>(); + } + return mounts; + } + + public void setMounts(List mounts) { + this.mounts = mounts; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "") + public static class Mount { + + @XmlAttribute(name = "file", required = true) + protected String file; + @XmlAttribute(name = "mount-path", required = true) + protected String mountPath; + + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } + + public String getMountPath() { + return mountPath; + } + + public void setMountPath(String mountPath) { + this.mountPath = mountPath; + } + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "ports" + }) + public static class ExposedPorts { + + @XmlElement(name = "port") + private List ports; + + public List getPorts() { + if (ports == null) { + ports = new ArrayList<>(); + } + return ports; + } + + public void setPorts(List ports) { + this.ports = ports; + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "bindings" + }) + public static class PortBindings { + + @XmlElement(name = "binding") + private List bindings; + + public List getBindings() { + if (bindings == null) { + bindings = new ArrayList<>(); + } + return bindings; + } + + public void setBindings(List bindings) { + this.bindings = bindings; + } + } } @XmlAccessorType(XmlAccessType.FIELD) diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/yaml/Start.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/yaml/Start.java index ef0a6f55ef..8426c99249 100644 --- a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/yaml/Start.java +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/yaml/Start.java @@ -167,13 +167,16 @@ private void configureStartActionBuilder(StartTestcontainersAction.AbstractBuild builder.withCommand(container.getCommand().split(" ")); } - container.getEnv().forEach(variable -> { - builder.withEnv(variable.getName(), variable.getValue()); - }); + container.getEnv().forEach(variable -> builder.withEnv(variable.getName(), variable.getValue())); - container.getLabels().forEach(label -> { - builder.withLabel(label.getName(), label.getValue()); - }); + container.getLabels().forEach(label -> builder.withLabel(label.getName(), label.getValue())); + + container.getExposedPorts().forEach(builder::addExposedPort); + + container.getPortBindings().forEach(builder::addPortBinding); + + container.getVolumeMounts().forEach(mount -> + builder.withVolumeMount(mount.getFile(), mount.getMountPath())); } public static class Container { @@ -194,6 +197,12 @@ public static class Container { protected List