22
33import com .github .dockerjava .api .DockerClient ;
44import com .github .dockerjava .api .model .Container ;
5+ import com .google .common .annotations .VisibleForTesting ;
56import com .google .common .base .Joiner ;
67import com .google .common .base .Splitter ;
7- import com .google .common .base .Strings ;
88import com .google .common .collect .Maps ;
99import com .google .common .collect .Sets ;
1010import com .google .common .util .concurrent .Uninterruptibles ;
5656
5757import static com .google .common .base .Preconditions .checkArgument ;
5858import static com .google .common .base .Preconditions .checkNotNull ;
59+ import static com .google .common .base .Strings .isNullOrEmpty ;
5960import static java .util .stream .Collectors .joining ;
6061import static java .util .stream .Collectors .toList ;
6162import static org .testcontainers .containers .BindMode .READ_ONLY ;
@@ -196,23 +197,36 @@ public SELF withServices(@NonNull String... services) {
196197 }
197198
198199 private void createServices () {
199- // Apply scaling
200- final String servicesWithScalingSettings = Stream .concat (services .stream (), scalingPreferences .keySet ().stream ())
201- .map (service -> "--scale " + service + "=" + scalingPreferences .getOrDefault (service , 1 ))
200+ // services that have been explicitly requested to be started. If empty, all services should be started.
201+ final String serviceNameArgs = Stream .concat (
202+ services .stream (), // services that have been specified with `withServices`
203+ scalingPreferences .keySet ().stream () // services that are implicitly needed via `withScaledService`
204+ )
205+ .distinct ()
202206 .collect (joining (" " ));
203207
204- String flags = "-d" ;
208+ // Apply scaling for the services specified using `withScaledService`
209+ final String scalingOptions = scalingPreferences .entrySet ().stream ()
210+ .map (entry -> "--scale " + entry .getKey () + "=" + entry .getValue ())
211+ .distinct ()
212+ .collect (joining (" " ));
213+
214+ String command = "up -d" ;
205215
206216 if (build ) {
207- flags += " --build" ;
217+ command += " --build" ;
208218 }
209219
210- // Run the docker-compose container, which starts up the services
211- if (Strings .isNullOrEmpty (servicesWithScalingSettings )) {
212- runWithCompose ("up " + flags );
213- } else {
214- runWithCompose ("up " + flags + " " + servicesWithScalingSettings );
220+ if (!isNullOrEmpty (scalingOptions )) {
221+ command += " " + scalingOptions ;
222+ }
223+
224+ if (!isNullOrEmpty (serviceNameArgs )) {
225+ command += " " + serviceNameArgs ;
215226 }
227+
228+ // Run the docker-compose container, which starts up the services
229+ runWithCompose (command );
216230 }
217231
218232 private void waitUntilServiceStarted () {
@@ -250,7 +264,7 @@ private void createServiceInstance(Container container) {
250264
251265 private void waitUntilServiceStarted (String serviceName , ComposeServiceWaitStrategyTarget serviceInstance ) {
252266 final WaitAllStrategy waitAllStrategy = waitStrategyMap .get (serviceName );
253- if (waitAllStrategy != null ) {
267+ if (waitAllStrategy != null ) {
254268 waitAllStrategy .waitUntilReady (serviceInstance );
255269 }
256270 }
@@ -273,24 +287,25 @@ private void runWithCompose(String cmd) {
273287 }
274288
275289 dockerCompose
276- .withCommand (cmd )
277- .withEnv (env )
278- .invoke ();
290+ .withCommand (cmd )
291+ .withEnv (env )
292+ .invoke ();
279293 }
280294
281295 private void registerContainersForShutdown () {
282296 ResourceReaper .instance ().registerFilterForCleanup (Arrays .asList (
283- new SimpleEntry <>("label" , "com.docker.compose.project=" + project )
297+ new SimpleEntry <>("label" , "com.docker.compose.project=" + project )
284298 ));
285299 }
286300
287- private List <Container > listChildContainers () {
301+ @ VisibleForTesting
302+ List <Container > listChildContainers () {
288303 return dockerClient .listContainersCmd ()
289- .withShowAll (true )
290- .exec ().stream ()
291- .filter (container -> Arrays .stream (container .getNames ()).anyMatch (name ->
292- name .startsWith ("/" + project )))
293- .collect (toList ());
304+ .withShowAll (true )
305+ .exec ().stream ()
306+ .filter (container -> Arrays .stream (container .getNames ()).anyMatch (name ->
307+ name .startsWith ("/" + project )))
308+ .collect (toList ());
294309 }
295310
296311 private void startAmbassadorContainers () {
@@ -378,12 +393,12 @@ private void addWaitStrategy(String serviceInstanceName, @NonNull WaitStrategy w
378393 }
379394
380395 /**
381- Specify the {@link WaitStrategy} to use to determine if the container is ready.
396+ * Specify the {@link WaitStrategy} to use to determine if the container is ready.
382397 *
383- * @see org.testcontainers.containers.wait.strategy.Wait#defaultWaitStrategy()
384- * @param serviceName the name of the service to wait for
398+ * @param serviceName the name of the service to wait for
385399 * @param waitStrategy the WaitStrategy to use
386400 * @return this
401+ * @see org.testcontainers.containers.wait.strategy.Wait#defaultWaitStrategy()
387402 */
388403 public SELF waitingFor (String serviceName , @ NonNull WaitStrategy waitStrategy ) {
389404 String serviceInstanceName = getServiceInstanceName (serviceName );
@@ -420,8 +435,8 @@ public Integer getServicePort(String serviceName, Integer servicePort) {
420435
421436 if (portMap == null ) {
422437 throw new IllegalArgumentException ("Could not get a port for '" + serviceName + "'. " +
423- "Testcontainers does not have an exposed port configured for '" + serviceName + "'. " +
424- "To fix, please ensure that the service '" + serviceName + "' has ports exposed using .withExposedService(...)" );
438+ "Testcontainers does not have an exposed port configured for '" + serviceName + "'. " +
439+ "To fix, please ensure that the service '" + serviceName + "' has ports exposed using .withExposedService(...)" );
425440 } else {
426441 return ambassadorContainer .getMappedPort (portMap .get (servicePort ));
427442 }
@@ -479,7 +494,7 @@ public SELF withTailChildContainers(boolean tailChildContainers) {
479494 * More than one consumer may be registered.
480495 *
481496 * @param serviceName the name of the service as set in the docker-compose.yml file
482- * @param consumer consumer that output frames should be sent to
497+ * @param consumer consumer that output frames should be sent to
483498 * @return this instance, for chaining
484499 */
485500 public SELF withLogConsumer (String serviceName , Consumer <OutputFrame > consumer ) {
@@ -579,10 +594,10 @@ public ContainerisedDockerCompose(List<File> composeFiles, String identifier) {
579594 final String containerPwd = MountableFile .forHostPath (pwd ).getFilesystemPath ();
580595
581596 final List <String > absoluteDockerComposeFiles = composeFiles .stream ()
582- .map (File ::getAbsolutePath )
583- .map (MountableFile ::forHostPath )
584- .map (MountableFile ::getFilesystemPath )
585- .collect (toList ());
597+ .map (File ::getAbsolutePath )
598+ .map (MountableFile ::forHostPath )
599+ .map (MountableFile ::getFilesystemPath )
600+ .collect (toList ());
586601 final String composeFileEnvVariableValue = Joiner .on (UNIX_PATH_SEPERATOR ).join (absoluteDockerComposeFiles ); // we always need the UNIX path separator
587602 logger ().debug ("Set env COMPOSE_FILE={}" , composeFileEnvVariableValue );
588603 addEnv (ENV_COMPOSE_FILE , composeFileEnvVariableValue );
@@ -600,8 +615,8 @@ public ContainerisedDockerCompose(List<File> composeFiles, String identifier) {
600615
601616 private String getDockerSocketHostPath () {
602617 return SystemUtils .IS_OS_WINDOWS
603- ? "/" + DOCKER_SOCKET_PATH
604- : DOCKER_SOCKET_PATH ;
618+ ? "/" + DOCKER_SOCKET_PATH
619+ : DOCKER_SOCKET_PATH ;
605620 }
606621
607622 @ Override
@@ -621,16 +636,16 @@ public void invoke() {
621636 AuditLogger .doComposeLog (this .getCommandParts (), this .getEnv ());
622637
623638 final Integer exitCode = this .dockerClient .inspectContainerCmd (getContainerId ())
624- .exec ()
625- .getState ()
626- .getExitCode ();
639+ .exec ()
640+ .getState ()
641+ .getExitCode ();
627642
628643 if (exitCode == null || exitCode != 0 ) {
629644 throw new ContainerLaunchException (
630- "Containerised Docker Compose exited abnormally with code " +
631- exitCode +
632- " whilst running command: " +
633- StringUtils .join (this .getCommandParts (), ' ' ));
645+ "Containerised Docker Compose exited abnormally with code " +
646+ exitCode +
647+ " whilst running command: " +
648+ StringUtils .join (this .getCommandParts (), ' ' ));
634649 }
635650 }
636651}
@@ -691,23 +706,23 @@ public void invoke() {
691706 logger ().info ("Local Docker Compose is running command: {}" , cmd );
692707
693708 final List <String > command = Splitter .onPattern (" " )
694- .omitEmptyStrings ()
695- .splitToList (COMPOSE_EXECUTABLE + " " + cmd );
709+ .omitEmptyStrings ()
710+ .splitToList (COMPOSE_EXECUTABLE + " " + cmd );
696711
697712 try {
698713 new ProcessExecutor ().command (command )
699- .redirectOutput (Slf4jStream .of (logger ()).asInfo ())
700- .redirectError (Slf4jStream .of (logger ()).asInfo ()) // docker-compose will log pull information to stderr
701- .environment (environment )
702- .directory (pwd )
703- .exitValueNormal ()
704- .executeNoTimeout ();
714+ .redirectOutput (Slf4jStream .of (logger ()).asInfo ())
715+ .redirectError (Slf4jStream .of (logger ()).asInfo ()) // docker-compose will log pull information to stderr
716+ .environment (environment )
717+ .directory (pwd )
718+ .exitValueNormal ()
719+ .executeNoTimeout ();
705720
706721 logger ().info ("Docker Compose has finished running" );
707722
708723 } catch (InvalidExitValueException e ) {
709724 throw new ContainerLaunchException ("Local Docker Compose exited abnormally with code " +
710- e .getExitValue () + " whilst running command: " + cmd );
725+ e .getExitValue () + " whilst running command: " + cmd );
711726
712727 } catch (Exception e ) {
713728 throw new ContainerLaunchException ("Error running local Docker Compose command: " + cmd , e );
0 commit comments