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 3e4d65b2e7..4e064e1e11 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 @@ -16,6 +16,7 @@ package org.citrusframework.testcontainers.actions; +import java.net.URL; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; @@ -30,6 +31,9 @@ import org.citrusframework.testcontainers.TestContainersSettings; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; import org.testcontainers.utility.MountableFile; import static org.citrusframework.testcontainers.TestcontainersHelper.getEnvVarName; @@ -127,6 +131,8 @@ public static abstract class AbstractBuilder, T ex protected final Map volumeMounts = new HashMap<>(); + protected WaitStrategy waitStrategy; + private boolean autoRemoveResources = TestContainersSettings.isAutoRemoveResources(); public B containerName(String name) { @@ -244,6 +250,43 @@ public B addPortBindings(List bindings) { return self; } + public B waitFor(WaitStrategy waitStrategy) { + this.waitStrategy = waitStrategy; + return self; + } + + public B waitFor(URL url) { + if ("https".equals(url.getProtocol())) { + this.waitStrategy = Wait.forHttps(url.getPath()); + } else { + this.waitStrategy = Wait.forHttp(url.getPath()); + } + return self; + } + + public B waitFor(String logMessage) { + return waitFor(logMessage, 1); + } + + public B waitFor(String logMessage, int times) { + this.waitStrategy = Wait.forLogMessage(logMessage, times); + return self; + } + + public B waitStrategyDisabled() { + this.waitStrategy = new WaitStrategy() { + @Override + public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) { + } + + @Override + public WaitStrategy withStartupTimeout(Duration startupTimeout) { + return this; + } + }; + return self; + } + public B withVolumeMount(MountableFile mountableFile, String containerPath) { this.volumeMounts.put(mountableFile, containerPath); return self; @@ -279,8 +322,6 @@ protected C buildContainer() { } } - container.withStartupTimeout(startupTimeout); - return container; } @@ -293,6 +334,12 @@ protected void configureContainer(C container) { volumeMounts.forEach(container::withCopyFileToContainer); + if (waitStrategy != null) { + container.waitingFor(waitStrategy); + } + + container.withStartupTimeout(startupTimeout); + if (!commandLine.isEmpty()) { container.withCommand(commandLine.toArray(String[]::new)); } 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 49c46043bf..6fdf84752f 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 @@ -16,6 +16,8 @@ package org.citrusframework.testcontainers.xml; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; @@ -28,6 +30,7 @@ import jakarta.xml.bind.annotation.XmlType; import jakarta.xml.bind.annotation.XmlValue; import org.citrusframework.TestActor; +import org.citrusframework.exceptions.CitrusRuntimeException; import org.citrusframework.spi.ReferenceResolver; import org.citrusframework.spi.ReferenceResolverAware; import org.citrusframework.spi.Resources; @@ -42,6 +45,7 @@ import org.citrusframework.testcontainers.postgresql.StartPostgreSQLAction; import org.citrusframework.testcontainers.redpanda.StartRedpandaAction; import org.citrusframework.util.ObjectHelper; +import org.citrusframework.util.StringUtils; @XmlRootElement(name = "start") public class Start extends AbstractTestcontainersAction.Builder, Start> implements ReferenceResolverAware { @@ -197,6 +201,20 @@ private void configureStartActionBuilder(StartTestcontainersAction.AbstractBuild container.getLabels().getLabels().forEach(label -> builder.withLabel(label.getName(), label.getValue())); } + if (container.getWaitFor() != null) { + if (container.getWaitFor().isDisabled()) { + builder.waitStrategyDisabled(); + } else if (StringUtils.hasText(container.getWaitFor().getLogMessage())) { + builder.waitFor(container.getWaitFor().getLogMessage()); + } else if (StringUtils.hasText(container.getWaitFor().getUrl())) { + try { + builder.waitFor(new URL(container.getWaitFor().getUrl())); + } catch (MalformedURLException e) { + throw new CitrusRuntimeException("Invalid Http(s) URL to wait for: %s".formatted(container.getWaitFor().getUrl()), e); + } + } + } + if (container.getExposedPorts() != null) { container.getExposedPorts().getPorts().forEach(builder::addExposedPort); } @@ -217,6 +235,7 @@ private void configureStartActionBuilder(StartTestcontainersAction.AbstractBuild "environmentVariables", "exposedPorts", "portBindings", + "waitFor", "volumeMounts" }) public static class Container { @@ -245,6 +264,9 @@ public static class Container { @XmlElement protected Labels labels; + @XmlElement(name = "wait-for") + protected WaitFor waitFor; + @XmlElement(name = "exposed-ports") protected ExposedPorts exposedPorts; @@ -318,6 +340,14 @@ public void setLabels(Labels labels) { this.labels = labels; } + public void setWaitFor(WaitFor waitFor) { + this.waitFor = waitFor; + } + + public WaitFor getWaitFor() { + return waitFor; + } + public ExposedPorts getExposedPorts() { return exposedPorts; } @@ -389,6 +419,44 @@ public void setMountPath(String mountPath) { } } + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "") + public static class WaitFor { + + @XmlAttribute(name = "log-message") + private String logMessage; + + @XmlAttribute + private String url; + + @XmlAttribute + private boolean disabled; + + public String getLogMessage() { + return logMessage; + } + + public void setLogMessage(String logMessage) { + this.logMessage = logMessage; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public boolean isDisabled() { + return disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + } + @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "ports" 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 6b33dcaea4..6ab23f8698 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 @@ -16,10 +16,13 @@ package org.citrusframework.testcontainers.yaml; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.List; import org.citrusframework.TestActor; +import org.citrusframework.exceptions.CitrusRuntimeException; import org.citrusframework.spi.ReferenceResolver; import org.citrusframework.spi.ReferenceResolverAware; import org.citrusframework.spi.Resources; @@ -34,6 +37,7 @@ import org.citrusframework.testcontainers.postgresql.StartPostgreSQLAction; import org.citrusframework.testcontainers.redpanda.StartRedpandaAction; import org.citrusframework.util.ObjectHelper; +import org.citrusframework.util.StringUtils; public class Start extends AbstractTestcontainersAction.Builder, Start> implements ReferenceResolverAware { @@ -174,6 +178,20 @@ private void configureStartActionBuilder(StartTestcontainersAction.AbstractBuild container.getLabels().forEach(label -> builder.withLabel(label.getName(), label.getValue())); + if (container.getWaitFor() != null) { + if (container.getWaitFor().isDisabled()) { + builder.waitStrategyDisabled(); + } else if (StringUtils.hasText(container.getWaitFor().getLogMessage())) { + builder.waitFor(container.getWaitFor().getLogMessage()); + } else if (StringUtils.hasText(container.getWaitFor().getUrl())) { + try { + builder.waitFor(new URL(container.getWaitFor().getUrl())); + } catch (MalformedURLException e) { + throw new CitrusRuntimeException("Invalid Http(s) URL to wait for: %s".formatted(container.getWaitFor().getUrl()), e); + } + } + } + container.getExposedPorts().forEach(builder::addExposedPort); container.getPortBindings().forEach(builder::addPortBinding); @@ -200,6 +218,8 @@ public static class Container { protected List