11package org .testcontainers .utility ;
22
33import com .github .dockerjava .api .DockerClient ;
4+ import com .github .dockerjava .api .async .ResultCallback ;
5+ import com .github .dockerjava .api .command .PullImageResultCallback ;
46import com .github .dockerjava .api .model .AuthConfig ;
5- import com .github .dockerjava .core .command .PullImageResultCallback ;
6- import com .github .dockerjava .core .command .PushImageResultCallback ;
7+ import org .intellij .lang .annotations .Language ;
78import org .junit .AfterClass ;
9+ import org .junit .Before ;
810import org .junit .BeforeClass ;
911import org .junit .ClassRule ;
1012import org .junit .Test ;
1113import org .mockito .Mockito ;
1214import org .testcontainers .DockerClientFactory ;
15+ import org .testcontainers .containers .ContainerState ;
16+ import org .testcontainers .containers .DockerComposeContainer ;
1317import org .testcontainers .containers .GenericContainer ;
1418import org .testcontainers .containers .wait .strategy .HttpWaitStrategy ;
1519import org .testcontainers .images .builder .ImageFromDockerfile ;
1620
21+ import java .io .IOException ;
22+ import java .nio .file .Files ;
23+ import java .nio .file .Path ;
24+ import java .nio .file .Paths ;
1725import java .util .concurrent .TimeUnit ;
1826
1927import static org .mockito .ArgumentMatchers .any ;
2533 * This test checks the integration between Testcontainers and an authenticated registry, but uses
2634 * a mock instance of {@link RegistryAuthLocator} - the purpose of the test is solely to ensure that
2735 * the auth locator is utilised, and that the credentials it provides flow through to the registry.
28- *
36+ * <p>
2937 * {@link RegistryAuthLocatorTest} covers actual credential scenarios at a lower level, which are
3038 * impractical to test end-to-end.
3139 */
3240public class AuthenticatedImagePullTest {
3341
42+ /**
43+ * Containerised docker image registry, with simple hardcoded credentials
44+ */
3445 @ ClassRule
35- public static GenericContainer authenticatedRegistry = new GenericContainer (new ImageFromDockerfile ()
46+ public static GenericContainer <?> authenticatedRegistry = new GenericContainer <> (new ImageFromDockerfile ()
3647 .withDockerfileFromBuilder (builder -> {
3748 builder .from ("registry:2" )
3849 .run ("htpasswd -Bbn testuser notasecret > /htpasswd" )
@@ -46,28 +57,17 @@ public class AuthenticatedImagePullTest {
4657 private static RegistryAuthLocator originalAuthLocatorSingleton ;
4758 private static DockerClient client ;
4859
49- private static String testRegistryAddress ;
5060 private static String testImageName ;
5161 private static String testImageNameWithTag ;
5262
5363 @ BeforeClass
54- public static void setUp () {
64+ public static void setUp () throws InterruptedException {
5565 originalAuthLocatorSingleton = RegistryAuthLocator .instance ();
5666 client = DockerClientFactory .instance ().client ();
5767
58- testRegistryAddress = authenticatedRegistry .getContainerIpAddress () + ":" + authenticatedRegistry .getFirstMappedPort ();
68+ String testRegistryAddress = authenticatedRegistry .getContainerIpAddress () + ":" + authenticatedRegistry .getFirstMappedPort ();
5969 testImageName = testRegistryAddress + "/alpine" ;
6070 testImageNameWithTag = testImageName + ":latest" ;
61- }
62-
63- @ AfterClass
64- public static void tearDown () {
65- RegistryAuthLocator .setInstance (originalAuthLocatorSingleton );
66- client .removeImageCmd (testImageNameWithTag ).withForce (true ).exec ();
67- }
68-
69- @ Test
70- public void testThatAuthLocatorIsUsed () throws Exception {
7171
7272 final DockerImageName expectedName = new DockerImageName (testImageNameWithTag );
7373 final AuthConfig authConfig = new AuthConfig ()
@@ -83,17 +83,77 @@ public void testThatAuthLocatorIsUsed() throws Exception {
8383
8484 // a push will use the auth locator for authentication, although that isn't the goal of this test
8585 putImageInRegistry ();
86+ }
87+
88+ @ Before
89+ public void removeImageFromLocalDocker () {
90+ // remove the image tag from local docker so that it must be pulled before use
91+ client .removeImageCmd (testImageNameWithTag ).withForce (true ).exec ();
92+ }
8693
94+ @ AfterClass
95+ public static void tearDown () {
96+ RegistryAuthLocator .setInstance (originalAuthLocatorSingleton );
97+ }
98+
99+ @ Test
100+ public void testThatAuthLocatorIsUsedForContainerCreation () {
87101 // actually start a container, which will require an authenticated pull
88- try (final GenericContainer container = new GenericContainer <>(testImageNameWithTag )
102+ try (final GenericContainer <?> container = new GenericContainer <>(testImageNameWithTag )
89103 .withCommand ("/bin/sh" , "-c" , "sleep 10" )) {
90104 container .start ();
91105
92106 assertTrue ("container started following an authenticated pull" , container .isRunning ());
93107 }
94108 }
95109
96- private void putImageInRegistry () throws InterruptedException {
110+ @ Test
111+ public void testThatAuthLocatorIsUsedForDockerfileBuild () throws IOException {
112+ // Prepare a simple temporary Dockerfile which requires our custom private image
113+ Path tempContext = Files .createTempDirectory (Paths .get ("." ), this .getClass ().getSimpleName () + "-test-" );
114+ Path tempFile = Files .createTempFile (tempContext , "test" , ".Dockerfile" );
115+ String dockerFileContent = "FROM " + testImageNameWithTag ;
116+ Files .write (tempFile , dockerFileContent .getBytes ());
117+
118+ // Start a container built from a derived image, which will require an authenticated pull
119+ try (final GenericContainer <?> container = new GenericContainer <>(
120+ new ImageFromDockerfile ()
121+ .withDockerfile (tempFile )
122+ )
123+ .withCommand ("/bin/sh" , "-c" , "sleep 10" )) {
124+ container .start ();
125+
126+ assertTrue ("container started following an authenticated pull" , container .isRunning ());
127+ }
128+ }
129+
130+ @ Test
131+ public void testThatAuthLocatorIsUsedForDockerComposePull () throws IOException {
132+ // Prepare a simple temporary Docker Compose manifest which requires our custom private image
133+ Path tempContext = Files .createTempDirectory (Paths .get ("." ), this .getClass ().getSimpleName () + "-test-" );
134+ Path tempFile = Files .createTempFile (tempContext , "test" , ".docker-compose.yml" );
135+ @ Language ("yaml" ) String composeFileContent =
136+ "version: '2.0'\n " +
137+ "services:\n " +
138+ " privateservice:\n " +
139+ " command: /bin/sh -c 'sleep 60'\n " +
140+ " image: " + testImageNameWithTag ;
141+ Files .write (tempFile , composeFileContent .getBytes ());
142+
143+ // Start the docker compose project, which will require an authenticated pull
144+ try (final DockerComposeContainer <?> compose = new DockerComposeContainer <>(tempFile .toFile ())) {
145+ compose .start ();
146+
147+ assertTrue ("container started following an authenticated pull" ,
148+ compose
149+ .getContainerByServiceName ("privateservice_1" )
150+ .map (ContainerState ::isRunning )
151+ .orElse (false )
152+ );
153+ }
154+ }
155+
156+ private static void putImageInRegistry () throws InterruptedException {
97157 // It doesn't matter which image we use for this test, but use one that's likely to have been pulled already
98158 final String dummySourceImage = TestcontainersConfiguration .getInstance ().getRyukImage ();
99159
@@ -109,10 +169,7 @@ private void putImageInRegistry() throws InterruptedException {
109169 client .tagImageCmd (id , testImageName , "latest" ).exec ();
110170
111171 client .pushImageCmd (testImageNameWithTag )
112- .exec (new PushImageResultCallback ())
172+ .exec (new ResultCallback . Adapter <> ())
113173 .awaitCompletion (1 , TimeUnit .MINUTES );
114-
115- // remove the image tag from local docker so that it must be pulled before use
116- client .removeImageCmd (testImageNameWithTag ).withForce (true ).exec ();
117174 }
118175}
0 commit comments