Skip to content

Commit

Permalink
Add Testcontainers Docker compose support
Browse files Browse the repository at this point in the history
- Users can call `compose up` and `compose down` commands as part of the test
- By default requires Docker compose binary being installed
  • Loading branch information
christophd committed Dec 10, 2024
1 parent 07228fc commit 560aff7
Show file tree
Hide file tree
Showing 20 changed files with 1,499 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.citrusframework.testcontainers;

import java.net.URL;
import java.time.Duration;

import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;

/**
* Utility class helps to create proper Testcontainers wait strategies from different input.
*/
public final class WaitStrategyHelper {

private WaitStrategyHelper() {
// prevent instantiation of utility class
}

public static WaitStrategy getNoopStrategy() {
return new WaitStrategy() {
@Override
public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) {
}

@Override
public WaitStrategy withStartupTimeout(Duration startupTimeout) {
return this;
}
};
}

public static WaitStrategy waitFor(URL url) {
if ("https".equals(url.getProtocol())) {
return Wait.forHttps(url.getPath());
} else {
return Wait.forHttp(url.getPath());
}
}

public static WaitStrategy waitFor(String logMessage) {
return waitFor(logMessage, 1);
}

public static WaitStrategy waitFor(String logMessage, int times) {
return Wait.forLogMessage(logMessage, times);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
import org.citrusframework.spi.Resource;
import org.citrusframework.spi.Resources;
import org.citrusframework.testcontainers.TestContainersSettings;
import org.citrusframework.testcontainers.WaitStrategyHelper;
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;
Expand Down Expand Up @@ -256,11 +256,7 @@ public B waitFor(WaitStrategy waitStrategy) {
}

public B waitFor(URL url) {
if ("https".equals(url.getProtocol())) {
this.waitStrategy = Wait.forHttps(url.getPath());
} else {
this.waitStrategy = Wait.forHttp(url.getPath());
}
this.waitStrategy = WaitStrategyHelper.waitFor(url);
return self;
}

Expand All @@ -274,16 +270,7 @@ public B waitFor(String logMessage, int times) {
}

public B waitStrategyDisabled() {
this.waitStrategy = new WaitStrategy() {
@Override
public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) {
}

@Override
public WaitStrategy withStartupTimeout(Duration startupTimeout) {
return this;
}
};
this.waitStrategy = WaitStrategyHelper.getNoopStrategy();
return self;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
package org.citrusframework.testcontainers.actions;

import org.citrusframework.TestActionBuilder;
import org.citrusframework.spi.Resource;
import org.citrusframework.testcontainers.aws2.StartLocalStackAction;
import org.citrusframework.testcontainers.compose.ComposeDownAction;
import org.citrusframework.testcontainers.compose.ComposeUpAction;
import org.citrusframework.testcontainers.kafka.StartKafkaAction;
import org.citrusframework.testcontainers.mongodb.StartMongoDBAction;
import org.citrusframework.testcontainers.postgresql.StartPostgreSQLAction;
Expand Down Expand Up @@ -85,6 +88,15 @@ public KafkaActionBuilder kafka() {
return new KafkaActionBuilder();
}


/**
* Manage Docker compose.
* @return
*/
public ComposeActionBuilder compose() {
return new ComposeActionBuilder();
}

@Override
public TestcontainersAction build() {
ObjectHelper.assertNotNull(delegate, "Missing delegate action to build");
Expand Down Expand Up @@ -128,6 +140,48 @@ public StopTestcontainersAction.Builder stop() {
}
}

public class ComposeActionBuilder {
/**
* Start compose testcontainers instance.
*/
public ComposeUpAction.Builder up() {
ComposeUpAction.Builder builder = new ComposeUpAction.Builder();
delegate = builder;
return builder;
}

/**
* Start compose testcontainers instance.
*/
public ComposeUpAction.Builder up(String filePath) {
ComposeUpAction.Builder builder = new ComposeUpAction.Builder()
.file(filePath);
delegate = builder;
return builder;
}



/**
* Start compose testcontainers instance.
*/
public ComposeUpAction.Builder up(Resource fileResource) {
ComposeUpAction.Builder builder = new ComposeUpAction.Builder()
.file(fileResource);
delegate = builder;
return builder;
}

/**
* Stop compose testcontainers instance.
*/
public ComposeDownAction.Builder down() {
ComposeDownAction.Builder builder = new ComposeDownAction.Builder();
delegate = builder;
return builder;
}
}

public class LocalStackActionBuilder {
/**
* Start LocalStack testcontainers instance.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.citrusframework.testcontainers.compose;

import java.util.Optional;

import org.citrusframework.testcontainers.TestContainersSettings;

public class ComposeContainerSettings {

private static final String COMPOSE_PROPERTY_PREFIX = TestContainersSettings.TESTCONTAINERS_PROPERTY_PREFIX + "compose.";
private static final String COMPOSE_ENV_PREFIX = TestContainersSettings.TESTCONTAINERS_ENV_PREFIX + "COMPOSE_";

private static final String CONTAINER_NAME_PROPERTY = COMPOSE_PROPERTY_PREFIX + "container.name";
private static final String CONTAINER_NAME_ENV = COMPOSE_ENV_PREFIX + "CONTAINER_NAME";

private static final String USE_COMPOSE_BINARY_PROPERTY = COMPOSE_PROPERTY_PREFIX + "use.compose.binary";
private static final String USE_COMPOSE_BINARY_ENV = COMPOSE_ENV_PREFIX + "USE_COMPOSE_BINARY";
public static final String USE_COMPOSE_BINARY_DEFAULT = "true";

private static final String STARTUP_TIMEOUT_PROPERTY = COMPOSE_PROPERTY_PREFIX + "startup.timeout";
private static final String STARTUP_TIMEOUT_ENV = COMPOSE_ENV_PREFIX + "STARTUP_TIMEOUT";

private ComposeContainerSettings() {
// prevent instantiation of utility class
}

/**
* LocalStack container name.
* @return the container name.
*/
public static String getContainerName() {
return System.getProperty(CONTAINER_NAME_PROPERTY,
System.getenv(CONTAINER_NAME_ENV) != null ? System.getenv(CONTAINER_NAME_ENV) : "");
}

/**
* Time in seconds to wait for the container to startup and accept connections.
* @return
*/
public static int getStartupTimeout() {
return Optional.ofNullable(System.getProperty(STARTUP_TIMEOUT_PROPERTY, System.getenv(STARTUP_TIMEOUT_ENV)))
.map(Integer::parseInt)
.orElseGet(TestContainersSettings::getStartupTimeout);
}

/**
* Uses local Docker compose binary when set to true.
* If enabled the experience is closest to using the Docker compose commands (e.g. docker compose up).
* @return
*/
public static boolean isUseComposeBinary() {
return Boolean.parseBoolean(System.getProperty(USE_COMPOSE_BINARY_PROPERTY,
System.getenv(USE_COMPOSE_BINARY_ENV) != null ? System.getenv(USE_COMPOSE_BINARY_ENV) : USE_COMPOSE_BINARY_DEFAULT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.citrusframework.testcontainers.compose;

import org.citrusframework.context.TestContext;
import org.citrusframework.testcontainers.actions.AbstractTestcontainersAction;
import org.testcontainers.containers.ComposeContainer;

public class ComposeDownAction extends AbstractTestcontainersAction {

private final String containerName;
private final ComposeContainer container;

public ComposeDownAction(Builder builder) {
super("compose-down", builder);

this.containerName = builder.containerName;
this.container = builder.container;
}

@Override
public void doExecute(TestContext context) {
if (container != null) {
container.stop();
} else if (containerName != null && context.getReferenceResolver().isResolvable(containerName)) {
Object maybeContainer = context.getReferenceResolver().resolve(containerName);
if (maybeContainer instanceof ComposeContainer composeContainer) {
composeContainer.stop();
}
}
}

/**
* Action builder.
*/
public static class Builder extends AbstractTestcontainersAction.Builder<ComposeDownAction, Builder> {

protected String containerName;
private ComposeContainer container;

public Builder containerName(String name) {
this.containerName = name;
return this;
}

public Builder container(ComposeContainer container) {
this.container = container;
return this;
}

@Override
public ComposeDownAction doBuild() {
return new ComposeDownAction(this);
}
}
}
Loading

0 comments on commit 560aff7

Please sign in to comment.