diff --git a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
index 6da5857adaa..a4b2224e1d2 100644
--- a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
+++ b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
@@ -21,17 +21,18 @@
import java.util.stream.Collectors;
/**
- *
Container for LocalStack, 'A fully functional local AWS cloud stack'.
- * {@link LocalStackContainer#withServices(Service...)} should be used to select which services
- * are to be launched. See {@link Service} for available choices.
+ * Testcontainers implementation for LocalStack.
*/
@Slf4j
public class LocalStackContainer extends GenericContainer {
static final int PORT = 4566;
+ @Deprecated
private static final String HOSTNAME_EXTERNAL_ENV_VAR = "HOSTNAME_EXTERNAL";
+ private static final String LOCALSTACK_HOST_ENV_VAR = "LOCALSTACK_HOST";
+
private final List services = new ArrayList<>();
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("localstack/localstack");
@@ -66,6 +67,8 @@ public class LocalStackContainer extends GenericContainer {
*/
private final boolean servicesEnvVarRequired;
+ private final boolean isVersion2;
+
/**
* @deprecated use {@link LocalStackContainer(DockerImageName)} instead
*/
@@ -92,18 +95,31 @@ public LocalStackContainer(final DockerImageName dockerImageName) {
/**
* @param dockerImageName image name to use for Localstack
* @param useLegacyMode if true, each AWS service is exposed on a different port
+ * @deprecated use {@link LocalStackContainer(DockerImageName)} instead
*/
+ @Deprecated
public LocalStackContainer(final DockerImageName dockerImageName, boolean useLegacyMode) {
super(dockerImageName);
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
this.legacyMode = useLegacyMode;
- this.servicesEnvVarRequired = isServicesEnvVarRequired(dockerImageName.getVersionPart());
+ String version = dockerImageName.getVersionPart();
+ this.servicesEnvVarRequired = isServicesEnvVarRequired(version);
+ this.isVersion2 = isVersion2(version);
withFileSystemBind(DockerClientFactory.instance().getRemoteDockerUnixSocketPath(), "/var/run/docker.sock");
waitingFor(Wait.forLogMessage(".*Ready\\.\n", 1));
}
+ private static boolean isVersion2(String version) {
+ if (version.equals("latest")) {
+ return true;
+ }
+
+ ComparableVersion comparableVersion = new ComparableVersion(version);
+ return comparableVersion.isGreaterThanOrEqualTo("2.0.0");
+ }
+
private static boolean isServicesEnvVarRequired(String version) {
if (version.equals("latest")) {
return false;
@@ -141,7 +157,7 @@ private static boolean shouldRunInLegacyMode(String version) {
protected void configure() {
super.configure();
- if (servicesEnvVarRequired) {
+ if (this.servicesEnvVarRequired) {
Preconditions.check("services list must not be empty", !services.isEmpty());
}
@@ -152,26 +168,30 @@ protected void configure() {
}
}
+ if (this.isVersion2) {
+ resolveHostname(LOCALSTACK_HOST_ENV_VAR);
+ } else {
+ resolveHostname(HOSTNAME_EXTERNAL_ENV_VAR);
+ }
+
+ exposePorts();
+ }
+
+ private void resolveHostname(String envVar) {
String hostnameExternalReason;
- if (getEnvMap().containsKey(HOSTNAME_EXTERNAL_ENV_VAR)) {
+ if (getEnvMap().containsKey(envVar)) {
// do nothing
hostnameExternalReason = "explicitly as environment variable";
} else if (getNetwork() != null && getNetworkAliases() != null && getNetworkAliases().size() >= 1) {
- withEnv(HOSTNAME_EXTERNAL_ENV_VAR, getNetworkAliases().get(getNetworkAliases().size() - 1)); // use the last network alias set
+ withEnv(envVar, getNetworkAliases().get(getNetworkAliases().size() - 1)); // use the last network alias set
hostnameExternalReason = "to match last network alias on container with non-default network";
} else {
- withEnv(HOSTNAME_EXTERNAL_ENV_VAR, getHost());
+ withEnv(envVar, getHost());
hostnameExternalReason = "to match host-routable address for container";
}
- logger()
- .info(
- "{} environment variable set to {} ({})",
- HOSTNAME_EXTERNAL_ENV_VAR,
- getEnvMap().get(HOSTNAME_EXTERNAL_ENV_VAR),
- hostnameExternalReason
- );
- exposePorts();
+ logger()
+ .info("{} environment variable set to {} ({})", envVar, getEnvMap().get(envVar), hostnameExternalReason);
}
private void exposePorts() {
diff --git a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java
index b99c455ae19..30682388af5 100644
--- a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java
+++ b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java
@@ -30,6 +30,7 @@
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.localstack.LocalStackContainer.Service;
+import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
@@ -265,6 +266,11 @@ public static class WithNetwork {
.withEnv("AWS_SECRET_ACCESS_KEY", "secretkey")
.withEnv("AWS_REGION", "eu-west-1");
+ @Test
+ public void localstackHostEnVarIsSet() {
+ assertThat(localstackInDockerNetwork.getEnvMap().get("HOSTNAME_EXTERNAL")).isEqualTo("localstack");
+ }
+
@Test
public void s3TestOverDockerNetwork() throws Exception {
runAwsCliAgainstDockerNetworkContainer(
@@ -357,17 +363,92 @@ public static class WithoutServices {
@Test
public void s3ServiceStartLazily() {
- S3Client s3 = S3Client
- .builder()
- .endpointOverride(localstack.getEndpointOverride(Service.S3))
- .credentialsProvider(
- StaticCredentialsProvider.create(
- AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())
+ try (
+ S3Client s3 = S3Client
+ .builder()
+ .endpointOverride(localstack.getEndpointOverride(Service.S3))
+ .credentialsProvider(
+ StaticCredentialsProvider.create(
+ AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())
+ )
)
+ .region(Region.of(localstack.getRegion()))
+ .build()
+ ) {
+ assertThat(s3.listBuckets().buckets()).as("S3 Service is started lazily").isEmpty();
+ }
+ }
+ }
+
+ public static class WithVersion2 {
+
+ private static Network network = Network.newNetwork();
+
+ @ClassRule
+ public static LocalStackContainer localstack = new LocalStackContainer(
+ DockerImageName.parse("localstack/localstack:2.0")
+ )
+ .withNetwork(network)
+ .withNetworkAliases("localstack");
+
+ @ClassRule
+ public static GenericContainer> awsCliInDockerNetwork = new GenericContainer<>(
+ LocalstackTestImages.AWS_CLI_IMAGE
+ )
+ .withNetwork(network)
+ .withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint("tail"))
+ .withCommand(" -f /dev/null")
+ .withEnv("AWS_ACCESS_KEY_ID", "accesskey")
+ .withEnv("AWS_SECRET_ACCESS_KEY", "secretkey")
+ .withEnv("AWS_REGION", "eu-west-1");
+
+ @Test
+ public void localstackHostEnVarIsSet() {
+ assertThat(localstack.getEnvMap().get("LOCALSTACK_HOST")).isEqualTo("localstack");
+ }
+
+ @Test
+ public void sqsTestOverDockerNetwork() throws Exception {
+ final String queueCreationResponse = runAwsCliAgainstDockerNetworkContainer(
+ "sqs create-queue --queue-name baz"
+ );
+
+ assertThat(queueCreationResponse)
+ .as("Created queue has external hostname URL")
+ .contains("http://localstack:" + LocalStackContainer.PORT);
+
+ runAwsCliAgainstDockerNetworkContainer(
+ String.format(
+ "sqs send-message --endpoint http://localstack:%d --queue-url http://localstack:%d/queue/baz --message-body test",
+ LocalStackContainer.PORT,
+ LocalStackContainer.PORT
)
- .region(Region.of(localstack.getRegion()))
- .build();
- assertThat(s3.listBuckets().buckets()).as("S3 Service is started lazily").isEmpty();
+ );
+ final String message = runAwsCliAgainstDockerNetworkContainer(
+ String.format(
+ "sqs receive-message --endpoint http://localstack:%d --queue-url http://localstack:%d/queue/baz",
+ LocalStackContainer.PORT,
+ LocalStackContainer.PORT
+ )
+ );
+
+ assertThat(message).as("the sent message can be received").contains("\"Body\": \"test\"");
+ }
+
+ private String runAwsCliAgainstDockerNetworkContainer(String command) throws Exception {
+ final String[] commandParts = String
+ .format(
+ "/usr/local/bin/aws --region eu-west-1 %s --endpoint-url http://localstack:%d --no-verify-ssl",
+ command,
+ LocalStackContainer.PORT
+ )
+ .split(" ");
+ final Container.ExecResult execResult = awsCliInDockerNetwork.execInContainer(commandParts);
+ assertThat(execResult.getExitCode()).isEqualTo(0);
+
+ final String logs = execResult.getStdout() + execResult.getStderr();
+ log.info(logs);
+ return logs;
}
}
}