44import com .amazonaws .auth .AWSStaticCredentialsProvider ;
55import com .amazonaws .auth .BasicAWSCredentials ;
66import com .amazonaws .client .builder .AwsClientBuilder ;
7- import org .jetbrains .annotations .Nullable ;
8- import org .junit .rules .ExternalResource ;
7+ import lombok .Getter ;
8+ import lombok .RequiredArgsConstructor ;
9+ import lombok .experimental .FieldDefaults ;
910import org .rnorth .ducttape .Preconditions ;
1011import org .testcontainers .containers .GenericContainer ;
11- import org .testcontainers .containers .wait .LogMessageWaitStrategy ;
12+ import org .testcontainers .containers .wait .strategy . Wait ;
1213
1314import java .net .InetAddress ;
1415import java .net .UnknownHostException ;
16+ import java .util .ArrayList ;
1517import java .util .Arrays ;
18+ import java .util .List ;
1619import java .util .stream .Collectors ;
1720
18- import static org .testcontainers .containers .BindMode .READ_WRITE ;
19-
2021/**
2122 * <p>Container for Atlassian Labs Localstack, 'A fully functional local AWS cloud stack'.</p>
2223 * <p>{@link LocalStackContainer#withServices(Service...)} should be used to select which services
2526 * {@link LocalStackContainer#getDefaultCredentialsProvider()}
2627 * be used to obtain compatible endpoint configuration and credentials, respectively.</p>
2728 */
28- public class LocalStackContainer extends ExternalResource {
29-
30- @ Nullable private GenericContainer delegate ;
31- private Service [] services ;
32-
33- @ Override
34- protected void before () throws Throwable {
29+ public class LocalStackContainer extends GenericContainer <LocalStackContainer > {
3530
36- Preconditions . check ( "services list must not be empty" , services != null && services . length > 0 ) ;
31+ public static final String VERSION = "0.8.6" ;
3732
38- final String servicesList = Arrays
39- .stream (services )
40- .map (Service ::getLocalStackName )
41- .collect (Collectors .joining ("," ));
33+ private final List <Service > services = new ArrayList <>();
4234
43- final Integer [] portsList = Arrays
44- .stream (services )
45- .map (Service ::getPort )
46- .collect (Collectors .toSet ()).toArray (new Integer []{});
35+ public LocalStackContainer () {
36+ this (VERSION );
37+ }
4738
48- delegate = new GenericContainer ("localstack/localstack:0.8.5" )
49- .withExposedPorts (portsList )
50- .withFileSystemBind ("//var/run/docker.sock" , "/var/run/docker.sock" , READ_WRITE )
51- .waitingFor (new LogMessageWaitStrategy ().withRegEx (".*Ready\\ .\n " ))
52- .withEnv ("SERVICES" , servicesList );
39+ public LocalStackContainer (String version ) {
40+ super ("localstack/localstack:" + version );
5341
54- delegate .start ();
42+ withFileSystemBind ("//var/run/docker.sock" , "/var/run/docker.sock" );
43+ waitingFor (Wait .forLogMessage (".*Ready\\ .\n " , 1 ));
5544 }
5645
5746 @ Override
58- protected void after () {
47+ protected void configure () {
48+ super .configure ();
49+
50+ Preconditions .check ("services list must not be empty" , !services .isEmpty ());
5951
60- Preconditions . check ( "delegate must have been created by before()" , delegate != null );
52+ withEnv ( "SERVICES" , services . stream (). map ( Service :: getLocalStackName ). collect ( Collectors . joining ( "," )) );
6153
62- delegate .stop ();
54+ for (Service service : services ) {
55+ addExposedPort (service .getPort ());
56+ }
6357 }
6458
6559 /**
@@ -68,8 +62,8 @@ protected void after() {
6862 * @return this container object
6963 */
7064 public LocalStackContainer withServices (Service ... services ) {
71- this .services = services ;
72- return this ;
65+ this .services . addAll ( Arrays . asList ( services )) ;
66+ return self () ;
7367 }
7468
7569 /**
@@ -85,19 +79,14 @@ public LocalStackContainer withServices(Service... services) {
8579 * @return an {@link AwsClientBuilder.EndpointConfiguration}
8680 */
8781 public AwsClientBuilder .EndpointConfiguration getEndpointConfiguration (Service service ) {
88-
89- if (delegate == null ) {
90- throw new IllegalStateException ("LocalStack has not been started yet!" );
91- }
92-
93- final String address = delegate .getContainerIpAddress ();
82+ final String address = getContainerIpAddress ();
9483 String ipAddress = address ;
9584 try {
9685 ipAddress = InetAddress .getByName (address ).getHostAddress ();
9786 } catch (UnknownHostException ignored ) {
9887
9988 }
100- ipAddress = ipAddress + ".xip .io" ;
89+ ipAddress = ipAddress + ".nip .io" ;
10190 while (true ) {
10291 try {
10392 //noinspection ResultOfMethodCallIgnored
@@ -112,7 +101,7 @@ public AwsClientBuilder.EndpointConfiguration getEndpointConfiguration(Service s
112101 "http://" +
113102 ipAddress +
114103 ":" +
115- delegate . getMappedPort (service .getPort ()), "us-east-1" );
104+ getMappedPort (service .getPort ()), "us-east-1" );
116105 }
117106
118107 /**
@@ -130,6 +119,9 @@ public AWSCredentialsProvider getDefaultCredentialsProvider() {
130119 return new AWSStaticCredentialsProvider (new BasicAWSCredentials ("accesskey" , "secretkey" ));
131120 }
132121
122+ @ RequiredArgsConstructor
123+ @ Getter
124+ @ FieldDefaults (makeFinal = true )
133125 public enum Service {
134126 API_GATEWAY ("apigateway" , 4567 ),
135127 KINESIS ("kinesis" , 4568 ),
@@ -149,18 +141,8 @@ public enum Service {
149141 CLOUDFORMATION ("cloudformation" , 4581 ),
150142 CLOUDWATCH ("cloudwatch" , 4582 );
151143
152- private final String localStackName ;
153- private final int port ;
154-
155- Service (String localstackName , int port ) {
156- this .localStackName = localstackName ;
157- this .port = port ;
158- }
159-
160- public String getLocalStackName () {
161- return localStackName ;
162- }
144+ String localStackName ;
163145
164- public Integer getPort () { return port ; }
146+ int port ;
165147 }
166148}
0 commit comments