forked from testcontainers/testcontainers-java
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AuthenticatedImagePullTest.java
185 lines (155 loc) · 7.65 KB
/
AuthenticatedImagePullTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package org.testcontainers.utility;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.PullImageResultCallback;
import com.github.dockerjava.api.model.AuthConfig;
import org.intellij.lang.annotations.Language;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.mockito.Mockito;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.ContainerState;
import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
import org.testcontainers.images.builder.ImageFromDockerfile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
/**
* This test checks the integration between Testcontainers and an authenticated registry, but uses
* a mock instance of {@link RegistryAuthLocator} - the purpose of the test is solely to ensure that
* the auth locator is utilised, and that the credentials it provides flow through to the registry.
* <p>
* {@link RegistryAuthLocatorTest} covers actual credential scenarios at a lower level, which are
* impractical to test end-to-end.
*/
public class AuthenticatedImagePullTest {
/**
* Containerised docker image registry, with simple hardcoded credentials
*/
@ClassRule
public static GenericContainer<?> authenticatedRegistry = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfileFromBuilder(builder -> {
builder.from("registry:2")
.run("htpasswd -Bbn testuser notasecret > /htpasswd")
.env("REGISTRY_AUTH", "htpasswd")
.env("REGISTRY_AUTH_HTPASSWD_PATH", "/htpasswd")
.env("REGISTRY_AUTH_HTPASSWD_REALM", "Test");
}))
.withExposedPorts(5000)
.waitingFor(new HttpWaitStrategy());
private static RegistryAuthLocator originalAuthLocatorSingleton;
private static DockerClient client;
private static String testImageName;
private static String testImageNameWithTag;
@BeforeClass
public static void setUp() throws InterruptedException {
originalAuthLocatorSingleton = RegistryAuthLocator.instance();
client = DockerClientFactory.instance().client();
String testRegistryAddress = authenticatedRegistry.getContainerIpAddress() + ":" + authenticatedRegistry.getFirstMappedPort();
testImageName = testRegistryAddress + "/alpine";
testImageNameWithTag = testImageName + ":latest";
final DockerImageName expectedName = new DockerImageName(testImageNameWithTag);
final AuthConfig authConfig = new AuthConfig()
.withUsername("testuser")
.withPassword("notasecret")
.withRegistryAddress("http://" + testRegistryAddress);
// Replace the RegistryAuthLocator singleton with our mock, for the duration of this test
final RegistryAuthLocator mockAuthLocator = Mockito.mock(RegistryAuthLocator.class);
RegistryAuthLocator.setInstance(mockAuthLocator);
when(mockAuthLocator.lookupAuthConfig(eq(expectedName), any()))
.thenReturn(authConfig);
// a push will use the auth locator for authentication, although that isn't the goal of this test
putImageInRegistry();
}
@Before
public void removeImageFromLocalDocker() {
// remove the image tag from local docker so that it must be pulled before use
client.removeImageCmd(testImageNameWithTag).withForce(true).exec();
}
@AfterClass
public static void tearDown() {
RegistryAuthLocator.setInstance(originalAuthLocatorSingleton);
}
@Test
public void testThatAuthLocatorIsUsedForContainerCreation() {
// actually start a container, which will require an authenticated pull
try (final GenericContainer<?> container = new GenericContainer<>(testImageNameWithTag)
.withCommand("/bin/sh", "-c", "sleep 10")) {
container.start();
assertTrue("container started following an authenticated pull", container.isRunning());
}
}
@Test
public void testThatAuthLocatorIsUsedForDockerfileBuild() throws IOException {
// Prepare a simple temporary Dockerfile which requires our custom private image
Path tempFile = getLocalTempFile(".Dockerfile");
String dockerFileContent = "FROM " + testImageNameWithTag;
Files.write(tempFile, dockerFileContent.getBytes());
// Start a container built from a derived image, which will require an authenticated pull
try (final GenericContainer<?> container = new GenericContainer<>(
new ImageFromDockerfile()
.withDockerfile(tempFile)
)
.withCommand("/bin/sh", "-c", "sleep 10")) {
container.start();
assertTrue("container started following an authenticated pull", container.isRunning());
}
}
@Test
public void testThatAuthLocatorIsUsedForDockerComposePull() throws IOException {
// Prepare a simple temporary Docker Compose manifest which requires our custom private image
Path tempFile = getLocalTempFile(".docker-compose.yml");
@Language("yaml") String composeFileContent =
"version: '2.0'\n" +
"services:\n" +
" privateservice:\n" +
" command: /bin/sh -c 'sleep 60'\n" +
" image: " + testImageNameWithTag;
Files.write(tempFile, composeFileContent.getBytes());
// Start the docker compose project, which will require an authenticated pull
try (final DockerComposeContainer<?> compose = new DockerComposeContainer<>(tempFile.toFile())) {
compose.start();
assertTrue("container started following an authenticated pull",
compose
.getContainerByServiceName("privateservice_1")
.map(ContainerState::isRunning)
.orElse(false)
);
}
}
private Path getLocalTempFile(String s) throws IOException {
Path projectRoot = Paths.get(".");
Path tempDirectory = Files.createTempDirectory(projectRoot, this.getClass().getSimpleName() + "-test-");
Path relativeTempDirectory = projectRoot.relativize(tempDirectory);
Path tempFile = Files.createTempFile(relativeTempDirectory, "test", s);
tempDirectory.toFile().deleteOnExit();
tempFile.toFile().deleteOnExit();
return tempFile;
}
private static void putImageInRegistry() throws InterruptedException {
// It doesn't matter which image we use for this test, but use one that's likely to have been pulled already
final String dummySourceImage = TestcontainersConfiguration.getInstance().getRyukImage();
client.pullImageCmd(dummySourceImage)
.exec(new PullImageResultCallback())
.awaitCompletion(1, TimeUnit.MINUTES);
final String id = client.inspectImageCmd(dummySourceImage)
.exec()
.getId();
// push the image to the registry
client.tagImageCmd(id, testImageName, "latest").exec();
client.pushImageCmd(testImageNameWithTag)
.exec(new ResultCallback.Adapter<>())
.awaitCompletion(1, TimeUnit.MINUTES);
}
}