Skip to content

Commit f591ac7

Browse files
roseo1eddumelendez
andauthored
Support DOCKER_AUTH_CONFIG env var (#6238)
Co-authored-by: Eddú Meléndez <[email protected]>
1 parent 5b738c2 commit f591ac7

File tree

3 files changed

+163
-26
lines changed

3 files changed

+163
-26
lines changed

core/src/main/java/org/testcontainers/utility/RegistryAuthLocator.java

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.databind.JsonNode;
44
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.github.dockerjava.api.exception.NotFoundException;
56
import com.github.dockerjava.api.model.AuthConfig;
67
import com.google.common.annotations.VisibleForTesting;
78
import org.apache.commons.lang3.StringUtils;
@@ -33,6 +34,8 @@ public class RegistryAuthLocator {
3334

3435
private static final String DEFAULT_REGISTRY_NAME = "https://index.docker.io/v1/";
3536

37+
private static final String DOCKER_AUTH_ENV_VAR = "DOCKER_AUTH_CONFIG";
38+
3639
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
3740

3841
private static RegistryAuthLocator instance;
@@ -43,6 +46,8 @@ public class RegistryAuthLocator {
4346

4447
private final File configFile;
4548

49+
private final String configEnv;
50+
4651
private final Map<String, Optional<AuthConfig>> cache = new ConcurrentHashMap<>();
4752

4853
/**
@@ -54,11 +59,13 @@ public class RegistryAuthLocator {
5459
@VisibleForTesting
5560
RegistryAuthLocator(
5661
File configFile,
62+
String configEnv,
5763
String commandPathPrefix,
5864
String commandExtension,
5965
Map<String, String> notFoundMessageHolderReference
6066
) {
6167
this.configFile = configFile;
68+
this.configEnv = configEnv;
6269
this.commandPathPrefix = commandPathPrefix;
6370
this.commandExtension = commandExtension;
6471

@@ -72,6 +79,7 @@ protected RegistryAuthLocator() {
7279
.getenv()
7380
.getOrDefault("DOCKER_CONFIG", System.getProperty("user.home") + "/.docker");
7481
this.configFile = new File(dockerConfigLocation + "/config.json");
82+
this.configEnv = System.getenv(DOCKER_AUTH_ENV_VAR);
7583
this.commandPathPrefix = "";
7684
this.commandExtension = "";
7785

@@ -131,15 +139,8 @@ public AuthConfig lookupAuthConfig(DockerImageName dockerImageName, AuthConfig d
131139
}
132140

133141
private Optional<AuthConfig> lookupUncachedAuthConfig(String registryName, DockerImageName dockerImageName) {
134-
log.debug(
135-
"RegistryAuthLocator has configFile: {} ({}) and commandPathPrefix: {}",
136-
configFile,
137-
configFile.exists() ? "exists" : "does not exist",
138-
commandPathPrefix
139-
);
140-
141142
try {
142-
final JsonNode config = OBJECT_MAPPER.readTree(configFile);
143+
final JsonNode config = getDockerAuthConfig();
143144
log.debug("registryName [{}] for dockerImageName [{}]", registryName, dockerImageName);
144145

145146
// use helper preferentially (per https://docs.docker.com/engine/reference/commandline/cli/)
@@ -162,15 +163,43 @@ private Optional<AuthConfig> lookupUncachedAuthConfig(String registryName, Docke
162163
}
163164
} catch (Exception e) {
164165
log.info(
165-
"Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: {}, configFile: {}. Falling back to docker-java default behaviour. Exception message: {}",
166+
"Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: {}, configFile: {}, configEnv: {}). Falling back to docker-java default behaviour. Exception message: {}",
166167
dockerImageName,
167168
configFile,
169+
DOCKER_AUTH_ENV_VAR,
168170
e.getMessage()
169171
);
170172
}
171173
return Optional.empty();
172174
}
173175

176+
private JsonNode getDockerAuthConfig() throws Exception {
177+
log.debug(
178+
"RegistryAuthLocator has configFile: {} ({}) configEnv: {} ({}) and commandPathPrefix: {}",
179+
configFile,
180+
configFile.exists() ? "exists" : "does not exist",
181+
DOCKER_AUTH_ENV_VAR,
182+
configEnv != null ? "exists" : "does not exist",
183+
commandPathPrefix
184+
);
185+
186+
if (configEnv != null) {
187+
log.debug("RegistryAuthLocator reading from environment variable: {}", DOCKER_AUTH_ENV_VAR);
188+
return OBJECT_MAPPER.readTree(configEnv);
189+
} else if (configFile.exists()) {
190+
log.debug("RegistryAuthLocator reading from configFile: {}", configFile);
191+
return OBJECT_MAPPER.readTree(configFile);
192+
}
193+
194+
throw new NotFoundException(
195+
"No config supplied. Checked in order: " +
196+
configFile +
197+
" (file not found), " +
198+
DOCKER_AUTH_ENV_VAR +
199+
" (not set)"
200+
);
201+
}
202+
174203
private AuthConfig findExistingAuthConfig(final JsonNode config, final String reposName) throws Exception {
175204
final Map.Entry<String, JsonNode> entry = findAuthNode(config, reposName);
176205

core/src/test/java/org/testcontainers/utility/RegistryAuthLocatorTest.java

Lines changed: 116 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
import com.github.dockerjava.api.model.AuthConfig;
44
import com.google.common.io.Resources;
5+
import org.apache.commons.io.FileUtils;
56
import org.apache.commons.lang3.SystemUtils;
67
import org.jetbrains.annotations.NotNull;
78
import org.junit.Test;
89

910
import java.io.File;
11+
import java.io.IOException;
12+
import java.net.URI;
1013
import java.net.URISyntaxException;
14+
import java.nio.charset.StandardCharsets;
1115
import java.util.HashMap;
1216
import java.util.Map;
1317

@@ -16,7 +20,7 @@
1620
public class RegistryAuthLocatorTest {
1721

1822
@Test
19-
public void lookupAuthConfigWithoutCredentials() throws URISyntaxException {
23+
public void lookupAuthConfigWithoutCredentials() throws URISyntaxException, IOException {
2024
final RegistryAuthLocator authLocator = createTestAuthLocator("config-empty.json");
2125

2226
final AuthConfig authConfig = authLocator.lookupAuthConfig(
@@ -32,7 +36,7 @@ public void lookupAuthConfigWithoutCredentials() throws URISyntaxException {
3236
}
3337

3438
@Test
35-
public void lookupAuthConfigWithBasicAuthCredentials() throws URISyntaxException {
39+
public void lookupAuthConfigWithBasicAuthCredentials() throws URISyntaxException, IOException {
3640
final RegistryAuthLocator authLocator = createTestAuthLocator("config-basic-auth.json");
3741

3842
final AuthConfig authConfig = authLocator.lookupAuthConfig(
@@ -48,7 +52,7 @@ public void lookupAuthConfigWithBasicAuthCredentials() throws URISyntaxException
4852
}
4953

5054
@Test
51-
public void lookupAuthConfigWithJsonKeyCredentials() throws URISyntaxException {
55+
public void lookupAuthConfigWithJsonKeyCredentials() throws URISyntaxException, IOException {
5256
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-json-key.json");
5357

5458
final AuthConfig authConfig = authLocator.lookupAuthConfig(
@@ -64,7 +68,8 @@ public void lookupAuthConfigWithJsonKeyCredentials() throws URISyntaxException {
6468
}
6569

6670
@Test
67-
public void lookupAuthConfigWithJsonKeyCredentialsPartialMatchShouldGiveNoResult() throws URISyntaxException {
71+
public void lookupAuthConfigWithJsonKeyCredentialsPartialMatchShouldGiveNoResult()
72+
throws URISyntaxException, IOException {
6873
// contains entry for registry.example.com
6974
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-json-key.json");
7075

@@ -78,7 +83,7 @@ public void lookupAuthConfigWithJsonKeyCredentialsPartialMatchShouldGiveNoResult
7883
}
7984

8085
@Test
81-
public void lookupAuthConfigUsingStore() throws URISyntaxException {
86+
public void lookupAuthConfigUsingStore() throws URISyntaxException, IOException {
8287
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-store.json");
8388

8489
final AuthConfig authConfig = authLocator.lookupAuthConfig(
@@ -98,7 +103,7 @@ public void lookupAuthConfigUsingStore() throws URISyntaxException {
98103
}
99104

100105
@Test
101-
public void lookupAuthConfigUsingHelper() throws URISyntaxException {
106+
public void lookupAuthConfigUsingHelper() throws URISyntaxException, IOException {
102107
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-helper.json");
103108

104109
final AuthConfig authConfig = authLocator.lookupAuthConfig(
@@ -118,7 +123,7 @@ public void lookupAuthConfigUsingHelper() throws URISyntaxException {
118123
}
119124

120125
@Test
121-
public void lookupAuthConfigUsingHelperWithToken() throws URISyntaxException {
126+
public void lookupAuthConfigUsingHelperWithToken() throws URISyntaxException, IOException {
122127
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-helper-using-token.json");
123128

124129
final AuthConfig authConfig = authLocator.lookupAuthConfig(
@@ -132,7 +137,7 @@ public void lookupAuthConfigUsingHelperWithToken() throws URISyntaxException {
132137
}
133138

134139
@Test
135-
public void lookupUsingHelperEmptyAuth() throws URISyntaxException {
140+
public void lookupUsingHelperEmptyAuth() throws URISyntaxException, IOException {
136141
final RegistryAuthLocator authLocator = createTestAuthLocator("config-empty-auth-with-helper.json");
137142

138143
final AuthConfig authConfig = authLocator.lookupAuthConfig(
@@ -152,7 +157,7 @@ public void lookupUsingHelperEmptyAuth() throws URISyntaxException {
152157
}
153158

154159
@Test
155-
public void lookupNonEmptyAuthWithHelper() throws URISyntaxException {
160+
public void lookupNonEmptyAuthWithHelper() throws URISyntaxException, IOException {
156161
final RegistryAuthLocator authLocator = createTestAuthLocator("config-existing-auth-with-helper.json");
157162

158163
final AuthConfig authConfig = authLocator.lookupAuthConfig(
@@ -172,7 +177,7 @@ public void lookupNonEmptyAuthWithHelper() throws URISyntaxException {
172177
}
173178

174179
@Test
175-
public void lookupAuthConfigWithCredentialsNotFound() throws URISyntaxException {
180+
public void lookupAuthConfigWithCredentialsNotFound() throws URISyntaxException, IOException {
176181
Map<String, String> notFoundMessagesReference = new HashMap<>();
177182
final RegistryAuthLocator authLocator = createTestAuthLocator(
178183
"config-with-store.json",
@@ -198,7 +203,7 @@ public void lookupAuthConfigWithCredentialsNotFound() throws URISyntaxException
198203
}
199204

200205
@Test
201-
public void lookupAuthConfigWithCredStoreEmpty() throws URISyntaxException {
206+
public void lookupAuthConfigWithCredStoreEmpty() throws URISyntaxException, IOException {
202207
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-store-empty.json");
203208

204209
DockerImageName dockerImageName = DockerImageName.parse("registry2.example.com/org/repo");
@@ -207,18 +212,106 @@ public void lookupAuthConfigWithCredStoreEmpty() throws URISyntaxException {
207212
assertThat(authConfig.getAuth()).as("CredStore field will be ignored, because value is blank").isNull();
208213
}
209214

215+
@Test
216+
public void lookupAuthConfigFromEnvVarWithCredStoreEmpty() throws URISyntaxException, IOException {
217+
final RegistryAuthLocator authLocator = createTestAuthLocator(null, "config-with-store-empty.json");
218+
219+
DockerImageName dockerImageName = DockerImageName.parse("registry2.example.com/org/repo");
220+
final AuthConfig authConfig = authLocator.lookupAuthConfig(dockerImageName, new AuthConfig());
221+
222+
assertThat(authConfig.getAuth()).as("CredStore field will be ignored, because value is blank").isNull();
223+
}
224+
225+
@Test
226+
public void lookupAuthConfigWithoutConfigFile() throws URISyntaxException, IOException {
227+
final RegistryAuthLocator authLocator = createTestAuthLocator(null);
228+
229+
final AuthConfig authConfig = authLocator.lookupAuthConfig(
230+
DockerImageName.parse("unauthenticated.registry.org/org/repo"),
231+
new AuthConfig()
232+
);
233+
234+
assertThat(authConfig.getRegistryAddress())
235+
.as("Default docker registry URL is set on auth config")
236+
.isEqualTo("https://index.docker.io/v1/");
237+
assertThat(authConfig.getUsername()).as("No username is set").isNull();
238+
assertThat(authConfig.getPassword()).as("No password is set").isNull();
239+
}
240+
241+
@Test
242+
public void lookupAuthConfigRespectsCheckOrderPreference() throws URISyntaxException, IOException {
243+
final RegistryAuthLocator authLocator = createTestAuthLocator("config-empty.json", "config-basic-auth.json");
244+
245+
final AuthConfig authConfig = authLocator.lookupAuthConfig(
246+
DockerImageName.parse("registry.example.com/org/repo"),
247+
new AuthConfig()
248+
);
249+
250+
assertThat(authConfig.getRegistryAddress())
251+
.as("Default docker registry URL is set on auth config")
252+
.isEqualTo("https://registry.example.com");
253+
assertThat(authConfig.getUsername()).as("Username is set").isEqualTo("user");
254+
assertThat(authConfig.getPassword()).as("Password is set").isEqualTo("pass");
255+
}
256+
257+
@Test
258+
public void lookupAuthConfigFromEnvironmentVariable() throws URISyntaxException, IOException {
259+
final RegistryAuthLocator authLocator = createTestAuthLocator(null, "config-basic-auth.json");
260+
261+
final AuthConfig authConfig = authLocator.lookupAuthConfig(
262+
DockerImageName.parse("registry.example.com/org/repo"),
263+
new AuthConfig()
264+
);
265+
266+
assertThat(authConfig.getRegistryAddress())
267+
.as("Default docker registry URL is set on auth config")
268+
.isEqualTo("https://registry.example.com");
269+
assertThat(authConfig.getUsername()).as("Username is set").isEqualTo("user");
270+
assertThat(authConfig.getPassword()).as("Password is set").isEqualTo("pass");
271+
}
272+
273+
@NotNull
274+
private RegistryAuthLocator createTestAuthLocator(String configName, String envConfigName)
275+
throws URISyntaxException, IOException {
276+
return createTestAuthLocator(configName, envConfigName, new HashMap<>());
277+
}
278+
210279
@NotNull
211-
private RegistryAuthLocator createTestAuthLocator(String configName) throws URISyntaxException {
212-
return createTestAuthLocator(configName, new HashMap<>());
280+
private RegistryAuthLocator createTestAuthLocator(String configName) throws URISyntaxException, IOException {
281+
return createTestAuthLocator(configName, null, new HashMap<>());
213282
}
214283

215284
@NotNull
216285
private RegistryAuthLocator createTestAuthLocator(String configName, Map<String, String> notFoundMessagesReference)
217-
throws URISyntaxException {
218-
final File configFile = new File(Resources.getResource("auth-config/" + configName).toURI());
286+
throws URISyntaxException, IOException {
287+
return createTestAuthLocator(configName, null, notFoundMessagesReference);
288+
}
219289

220-
String commandPathPrefix = configFile.getParentFile().getAbsolutePath() + "/";
290+
@NotNull
291+
private RegistryAuthLocator createTestAuthLocator(
292+
String configName,
293+
String envConfigName,
294+
Map<String, String> notFoundMessagesReference
295+
) throws URISyntaxException, IOException {
296+
File configFile = null;
297+
String commandPathPrefix = "";
221298
String commandExtension = "";
299+
String configEnv = null;
300+
301+
if (configName != null) {
302+
configFile = new File(Resources.getResource("auth-config/" + configName).toURI());
303+
304+
commandPathPrefix = configFile.getParentFile().getAbsolutePath() + "/";
305+
} else {
306+
configFile = new File(new URI("file:///not-exists.json"));
307+
}
308+
309+
if (envConfigName != null) {
310+
final File envConfigFile = new File(Resources.getResource("auth-config/" + envConfigName).toURI());
311+
configEnv = FileUtils.readFileToString(envConfigFile, StandardCharsets.UTF_8);
312+
313+
commandPathPrefix = envConfigFile.getParentFile().getAbsolutePath() + "/";
314+
}
222315

223316
if (SystemUtils.IS_OS_WINDOWS) {
224317
commandPathPrefix += "win/";
@@ -228,6 +321,12 @@ private RegistryAuthLocator createTestAuthLocator(String configName, Map<String,
228321
commandExtension = ".bat";
229322
}
230323

231-
return new RegistryAuthLocator(configFile, commandPathPrefix, commandExtension, notFoundMessagesReference);
324+
return new RegistryAuthLocator(
325+
configFile,
326+
configEnv,
327+
commandPathPrefix,
328+
commandExtension,
329+
notFoundMessagesReference
330+
);
232331
}
233332
}

docs/supported_docker_environment/index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,12 @@ Testcontainers will try to connect to a Docker daemon using the following strate
4545
* `DOCKER_CERT_PATH=~/.docker`
4646
* If Docker Machine is installed, the docker machine environment for the *first* machine found. Docker Machine needs to be on the PATH for this to succeed.
4747
* If you're going to run your tests inside a container, please read [Patterns for running tests inside a docker container](continuous_integration/dind_patterns.md) first.
48+
49+
## Docker registry authentication
50+
51+
Testcontainers will try to authenticate to registries with supplied config using the following strategies in order:
52+
53+
* Environment variables:
54+
* `DOCKER_AUTH_CONFIG`
55+
* Docker config
56+
* At location specified in `DOCKER_CONFIG` or at `{HOME}/.docker/config.json`

0 commit comments

Comments
 (0)