Skip to content

Commit 958e28d

Browse files
nosanphilwebb
authored andcommitted
Update DockerConfigurationMetadata to support credentials
Update `DockerConfigurationMetadata` with support for `credsStore`, `credHelpers` and `auth` sections. These values will be required to support credential helper based authentication. See gh-45269 Signed-off-by: Dmytro Nosan <[email protected]>
1 parent dd49de0 commit 958e28d

File tree

3 files changed

+139
-2
lines changed

3 files changed

+139
-2
lines changed

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,7 +23,11 @@
2323
import java.nio.file.Path;
2424
import java.security.MessageDigest;
2525
import java.security.NoSuchAlgorithmException;
26+
import java.util.Base64;
27+
import java.util.Collections;
2628
import java.util.HexFormat;
29+
import java.util.LinkedHashMap;
30+
import java.util.Map;
2731

2832
import com.fasterxml.jackson.core.JsonProcessingException;
2933
import com.fasterxml.jackson.databind.JsonNode;
@@ -148,15 +152,50 @@ static final class DockerConfig extends MappedObject {
148152

149153
private final String currentContext;
150154

155+
private final String credsStore;
156+
157+
private final Map<String, String> credHelpers;
158+
159+
private final Map<String, Auth> auths;
160+
151161
private DockerConfig(JsonNode node) {
152162
super(node, MethodHandles.lookup());
153163
this.currentContext = valueAt("/currentContext", String.class);
164+
this.credsStore = valueAt("/credsStore", String.class);
165+
this.credHelpers = extractCredHelpers();
166+
this.auths = extractAuths();
167+
}
168+
169+
private Map<String, Auth> extractAuths() {
170+
Map<String, Auth> auths = new LinkedHashMap<>();
171+
getNode().at("/auths")
172+
.fields()
173+
.forEachRemaining((entry) -> auths.put(entry.getKey(), new Auth(entry.getValue())));
174+
return Map.copyOf(auths);
175+
}
176+
177+
@SuppressWarnings("unchecked")
178+
private Map<String, String> extractCredHelpers() {
179+
Map<String, String> credHelpers = valueAt("/credHelpers", Map.class);
180+
return (credHelpers != null) ? Map.copyOf(credHelpers) : Collections.emptyMap();
154181
}
155182

156183
String getCurrentContext() {
157184
return this.currentContext;
158185
}
159186

187+
String getCredsStore() {
188+
return this.credsStore;
189+
}
190+
191+
Map<String, String> getCredHelpers() {
192+
return this.credHelpers;
193+
}
194+
195+
Map<String, Auth> getAuths() {
196+
return this.auths;
197+
}
198+
160199
static DockerConfig fromJson(String json) throws JsonProcessingException {
161200
return new DockerConfig(SharedObjectMapper.get().readTree(json));
162201
}
@@ -167,6 +206,45 @@ static DockerConfig empty() {
167206

168207
}
169208

209+
static final class Auth extends MappedObject {
210+
211+
private final String username;
212+
213+
private final String password;
214+
215+
private final String email;
216+
217+
Auth(JsonNode node) {
218+
super(node, MethodHandles.lookup());
219+
String username = valueAt("/username", String.class);
220+
String password = valueAt("/password", String.class);
221+
String auth = valueAt("/auth", String.class);
222+
if (auth != null) {
223+
String[] parts = new String(Base64.getDecoder().decode(auth)).split(":", 2);
224+
if (parts.length == 2) {
225+
username = parts[0];
226+
password = parts[1];
227+
}
228+
}
229+
this.username = username;
230+
this.password = password;
231+
this.email = valueAt("/email", String.class);
232+
}
233+
234+
String getUsername() {
235+
return this.username;
236+
}
237+
238+
String getPassword() {
239+
return this.password;
240+
}
241+
242+
String getEmail() {
243+
return this.email;
244+
}
245+
246+
}
247+
170248
static final class DockerContext extends MappedObject {
171249

172250
private final String dockerHost;

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626

2727
import org.junit.jupiter.api.Test;
2828

29+
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerConfig;
2930
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext;
3031
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
3132

@@ -46,6 +47,9 @@ void configWithContextIsRead() throws Exception {
4647
this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json"));
4748
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
4849
assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("test-context");
50+
assertThat(config.getConfiguration().getAuths()).isEmpty();
51+
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
52+
assertThat(config.getConfiguration().getCredsStore()).isNull();
4953
assertThat(config.getContext().getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock");
5054
assertThat(config.getContext().isTlsVerify()).isFalse();
5155
assertThat(config.getContext().getTlsPath()).isNull();
@@ -56,6 +60,9 @@ void configWithoutContextIsRead() throws Exception {
5660
this.environment.put("DOCKER_CONFIG", pathToResource("without-context/config.json"));
5761
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
5862
assertThat(config.getConfiguration().getCurrentContext()).isNull();
63+
assertThat(config.getConfiguration().getAuths()).isEmpty();
64+
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
65+
assertThat(config.getConfiguration().getCredsStore()).isNull();
5966
assertThat(config.getContext().getDockerHost()).isNull();
6067
assertThat(config.getContext().isTlsVerify()).isFalse();
6168
assertThat(config.getContext().getTlsPath()).isNull();
@@ -66,6 +73,9 @@ void configWithDefaultContextIsRead() throws Exception {
6673
this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json"));
6774
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
6875
assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("default");
76+
assertThat(config.getConfiguration().getAuths()).isEmpty();
77+
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
78+
assertThat(config.getConfiguration().getCredsStore()).isNull();
6979
assertThat(config.getContext().getDockerHost()).isNull();
7080
assertThat(config.getContext().isTlsVerify()).isFalse();
7181
assertThat(config.getContext().getTlsPath()).isNull();
@@ -95,10 +105,38 @@ void configIsEmptyWhenConfigFileDoesNotExist() {
95105
this.environment.put("DOCKER_CONFIG", "docker-config-dummy-path");
96106
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
97107
assertThat(config.getConfiguration().getCurrentContext()).isNull();
108+
assertThat(config.getConfiguration().getAuths()).isEmpty();
109+
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
110+
assertThat(config.getConfiguration().getCredsStore()).isNull();
98111
assertThat(config.getContext().getDockerHost()).isNull();
99112
assertThat(config.getContext().isTlsVerify()).isFalse();
100113
}
101114

115+
@Test
116+
void configWithAuthIsRead() throws Exception {
117+
this.environment.put("DOCKER_CONFIG", pathToResource("with-auth/config.json"));
118+
DockerConfigurationMetadata metadata = DockerConfigurationMetadata.from(this.environment::get);
119+
DockerConfig configuration = metadata.getConfiguration();
120+
assertThat(configuration.getCredsStore()).isEqualTo("desktop");
121+
assertThat(configuration.getCredHelpers()).hasSize(3)
122+
.containsEntry("azurecr.io", "acr-env")
123+
.containsEntry("ecr.us-east-1.amazonaws.com", "ecr-login")
124+
.containsEntry("gcr.io", "gcr");
125+
assertThat(configuration.getAuths()).hasSize(3).hasEntrySatisfying("https://index.docker.io/v1/", (auth) -> {
126+
assertThat(auth.getUsername()).isEqualTo("username");
127+
assertThat(auth.getPassword()).isEqualTo("password");
128+
assertThat(auth.getEmail()).isEqualTo("[email protected]");
129+
}).hasEntrySatisfying("custom-registry.example.com", (auth) -> {
130+
assertThat(auth.getUsername()).isEqualTo("customUser");
131+
assertThat(auth.getPassword()).isEqualTo("customPass");
132+
assertThat(auth.getEmail()).isNull();
133+
}).hasEntrySatisfying("my-registry.example.com", (auth) -> {
134+
assertThat(auth.getUsername()).isEqualTo("user");
135+
assertThat(auth.getPassword()).isEqualTo("password");
136+
assertThat(auth.getEmail()).isNull();
137+
});
138+
}
139+
102140
private String pathToResource(String resource) throws URISyntaxException {
103141
URL url = getClass().getResource(resource);
104142
return Paths.get(url.toURI()).getParent().toAbsolutePath().toString();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"auths": {
3+
"https://index.docker.io/v1/": {
4+
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
5+
"email": "[email protected]"
6+
},
7+
"custom-registry.example.com": {
8+
"auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz"
9+
},
10+
"my-registry.example.com": {
11+
"username": "user",
12+
"password": "password"
13+
}
14+
},
15+
"credsStore": "desktop",
16+
"credHelpers": {
17+
"gcr.io": "gcr",
18+
"ecr.us-east-1.amazonaws.com": "ecr-login",
19+
"azurecr.io": "acr-env"
20+
}
21+
}

0 commit comments

Comments
 (0)