Skip to content

Commit 2195610

Browse files
authored
Add LAMBDA_DOCKER_FLAGS with testcontainers labels (#8595)
1 parent 994b385 commit 2195610

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

modules/localstack/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ dependencies {
77
testImplementation 'com.amazonaws:aws-java-sdk-s3'
88
testImplementation 'com.amazonaws:aws-java-sdk-sqs'
99
testImplementation 'com.amazonaws:aws-java-sdk-logs'
10+
testImplementation 'com.amazonaws:aws-java-sdk-lambda'
11+
testImplementation 'com.amazonaws:aws-java-sdk-core'
1012
testImplementation 'software.amazon.awssdk:s3:2.23.9'
1113
testImplementation 'org.assertj:assertj-core:3.25.2'
1214
}

modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.testcontainers.containers.wait.strategy.Wait;
1111
import org.testcontainers.utility.ComparableVersion;
1212
import org.testcontainers.utility.DockerImageName;
13+
import org.testcontainers.utility.ResourceReaper;
1314

1415
import java.net.InetAddress;
1516
import java.net.URI;
@@ -19,6 +20,7 @@
1920
import java.util.Arrays;
2021
import java.util.List;
2122
import java.util.stream.Collectors;
23+
import java.util.stream.Stream;
2224

2325
/**
2426
* Testcontainers implementation for LocalStack.
@@ -163,6 +165,34 @@ private static boolean shouldRunInLegacyMode(String version) {
163165
return true;
164166
}
165167

168+
/**
169+
* Provides a docker argument string including all default labels set on testcontainer containers
170+
* @return Argument string in the format `-l key1=value1 -l key2=value2`
171+
*/
172+
private static String internalMarkerLabels() {
173+
return Stream
174+
.concat(
175+
DockerClientFactory.DEFAULT_LABELS.entrySet().stream(),
176+
ResourceReaper.instance().getLabels().entrySet().stream()
177+
)
178+
.map(entry -> String.format("-l %s=%s", entry.getKey(), entry.getValue()))
179+
.collect(Collectors.joining(" "));
180+
}
181+
182+
/**
183+
* Configure the LocalStack container to include the default testcontainer labels on all spawned lambda containers
184+
* Necessary to properly clean up lambda containers even if the LocalStack container is killed before it gets the
185+
* chance.
186+
*/
187+
private void configureLambdaContainerLabels() {
188+
String lambdaDockerFlags = internalMarkerLabels();
189+
String existingLambdaDockerFlags = getEnvMap().get("LAMBDA_DOCKER_FLAGS");
190+
if (existingLambdaDockerFlags != null) {
191+
lambdaDockerFlags = existingLambdaDockerFlags + " " + lambdaDockerFlags;
192+
}
193+
withEnv("LAMBDA_DOCKER_FLAGS", lambdaDockerFlags);
194+
}
195+
166196
@Override
167197
protected void configure() {
168198
super.configure();
@@ -185,6 +215,7 @@ protected void configure() {
185215
}
186216

187217
exposePorts();
218+
configureLambdaContainerLabels();
188219
}
189220

190221
private void resolveHostname(String envVar) {

modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88
import com.amazonaws.services.kms.model.CreateKeyRequest;
99
import com.amazonaws.services.kms.model.CreateKeyResult;
1010
import com.amazonaws.services.kms.model.Tag;
11+
import com.amazonaws.services.lambda.AWSLambda;
12+
import com.amazonaws.services.lambda.AWSLambdaClientBuilder;
13+
import com.amazonaws.services.lambda.model.CreateFunctionRequest;
14+
import com.amazonaws.services.lambda.model.CreateFunctionResult;
15+
import com.amazonaws.services.lambda.model.FunctionCode;
16+
import com.amazonaws.services.lambda.model.GetFunctionRequest;
17+
import com.amazonaws.services.lambda.model.InvokeRequest;
18+
import com.amazonaws.services.lambda.model.InvokeResult;
19+
import com.amazonaws.services.lambda.model.Runtime;
1120
import com.amazonaws.services.logs.AWSLogs;
1221
import com.amazonaws.services.logs.AWSLogsClientBuilder;
1322
import com.amazonaws.services.logs.model.CreateLogGroupRequest;
@@ -20,12 +29,15 @@
2029
import com.amazonaws.services.sqs.AmazonSQS;
2130
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
2231
import com.amazonaws.services.sqs.model.CreateQueueResult;
32+
import com.amazonaws.waiters.WaiterParameters;
33+
import com.github.dockerjava.api.DockerClient;
2334
import lombok.extern.slf4j.Slf4j;
2435
import org.apache.commons.io.IOUtils;
2536
import org.junit.ClassRule;
2637
import org.junit.Test;
2738
import org.junit.experimental.runners.Enclosed;
2839
import org.junit.runner.RunWith;
40+
import org.testcontainers.DockerClientFactory;
2941
import org.testcontainers.containers.Container;
3042
import org.testcontainers.containers.GenericContainer;
3143
import org.testcontainers.containers.Network;
@@ -36,14 +48,21 @@
3648
import software.amazon.awssdk.regions.Region;
3749
import software.amazon.awssdk.services.s3.S3Client;
3850

51+
import java.io.ByteArrayOutputStream;
3952
import java.io.IOException;
4053
import java.net.URL;
54+
import java.nio.ByteBuffer;
4155
import java.nio.charset.StandardCharsets;
4256
import java.time.Instant;
4357
import java.time.temporal.ChronoUnit;
58+
import java.util.Collection;
59+
import java.util.Collections;
4460
import java.util.Date;
4561
import java.util.List;
62+
import java.util.Map;
4663
import java.util.Optional;
64+
import java.util.zip.ZipEntry;
65+
import java.util.zip.ZipOutputStream;
4766

4867
import static org.assertj.core.api.Assertions.assertThat;
4968

@@ -505,4 +524,90 @@ public void shouldBeAccessibleWithCredentials() throws IOException {
505524
assertThat(content).as("The object can be retrieved").isEqualTo("baz");
506525
}
507526
}
527+
528+
public static class LambdaContainerLabels {
529+
530+
@ClassRule
531+
public static LocalStackContainer localstack = new LocalStackContainer(
532+
LocalstackTestImages.LOCALSTACK_2_3_IMAGE
533+
);
534+
535+
private static byte[] createLambdaHandlerZipFile() throws IOException {
536+
StringBuilder sb = new StringBuilder();
537+
sb.append("def handler(event, context):\n");
538+
sb.append(" return event");
539+
540+
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
541+
ZipOutputStream out = new ZipOutputStream(byteOutput);
542+
ZipEntry e = new ZipEntry("handler.py");
543+
out.putNextEntry(e);
544+
545+
byte[] data = sb.toString().getBytes();
546+
out.write(data, 0, data.length);
547+
out.closeEntry();
548+
out.close();
549+
return byteOutput.toByteArray();
550+
}
551+
552+
@Test
553+
public void shouldLabelLambdaContainers() throws IOException {
554+
AWSLambda lambda = AWSLambdaClientBuilder
555+
.standard()
556+
.withEndpointConfiguration(
557+
new AwsClientBuilder.EndpointConfiguration(
558+
localstack.getEndpoint().toString(),
559+
localstack.getRegion()
560+
)
561+
)
562+
.withCredentials(
563+
new AWSStaticCredentialsProvider(
564+
new BasicAWSCredentials(localstack.getAccessKey(), localstack.getSecretKey())
565+
)
566+
)
567+
.build();
568+
569+
// create function
570+
byte[] handlerFile = createLambdaHandlerZipFile();
571+
CreateFunctionRequest createFunctionRequest = new CreateFunctionRequest()
572+
.withFunctionName("test-function")
573+
.withRuntime(Runtime.Python311)
574+
.withHandler("handler.handler")
575+
.withRole("arn:aws:iam::000000000000:role/test-role")
576+
.withCode(new FunctionCode().withZipFile(ByteBuffer.wrap(handlerFile)));
577+
CreateFunctionResult createFunctionResult = lambda.createFunction(createFunctionRequest);
578+
GetFunctionRequest getFunctionRequest = new GetFunctionRequest()
579+
.withFunctionName(createFunctionResult.getFunctionName());
580+
lambda
581+
.waiters()
582+
.functionActiveV2()
583+
.run(new WaiterParameters<GetFunctionRequest>().withRequest(getFunctionRequest));
584+
585+
// invoke function once
586+
String payload = "{\"test\": \"payload\"}";
587+
InvokeRequest invokeRequest = new InvokeRequest()
588+
.withFunctionName(createFunctionResult.getFunctionName())
589+
.withPayload(payload);
590+
InvokeResult invokeResult = lambda.invoke(invokeRequest);
591+
assertThat(StandardCharsets.UTF_8.decode(invokeResult.getPayload()).toString())
592+
.as("Invoke result not matching expected output")
593+
.isEqualTo(payload);
594+
595+
// assert that the spawned lambda containers has the testcontainers labels set
596+
DockerClient dockerClient = DockerClientFactory.instance().client();
597+
Collection<String> nameFilter = Collections.singleton(localstack.getContainerName().replace("_", "-"));
598+
com.github.dockerjava.api.model.Container lambdaContainer = dockerClient
599+
.listContainersCmd()
600+
.withNameFilter(nameFilter)
601+
.exec()
602+
.stream()
603+
.findFirst()
604+
.orElse(null);
605+
assertThat(lambdaContainer).as("Lambda container not found").isNotNull();
606+
Map<String, String> labels = lambdaContainer.getLabels();
607+
assertThat(labels.get("org.testcontainers")).as("TestContainers label not present").isEqualTo("true");
608+
assertThat(labels.get("org.testcontainers.sessionId"))
609+
.as("TestContainers session id not present")
610+
.isNotNull();
611+
}
612+
}
508613
}

0 commit comments

Comments
 (0)