Skip to content

Commit 4393a22

Browse files
Use Docker CLI context to determine daemon host address for image building
Configuration files managed by the Docker CLI are now used to determine the host address of the Docker daemon used when building images using buildpacks when a host address is not configured with environment variables or build tool plugin configuration. Closes gh-36445
1 parent 283dc37 commit 4393a22

File tree

26 files changed

+660
-86
lines changed

26 files changed

+660
-86
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
4141
import org.apache.hc.core5.net.URIBuilder;
4242

43-
import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost;
43+
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
4444
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport;
4545
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response;
4646
import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
@@ -97,7 +97,7 @@ public DockerApi() {
9797
* @param dockerHost the Docker daemon host information
9898
* @since 2.4.0
9999
*/
100-
public DockerApi(DockerHost dockerHost) {
100+
public DockerApi(DockerHostConfiguration dockerHost) {
101101
this(HttpTransport.create(dockerHost));
102102
}
103103

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

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 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.
@@ -27,7 +27,7 @@
2727
*/
2828
public final class DockerConfiguration {
2929

30-
private final DockerHost host;
30+
private final DockerHostConfiguration host;
3131

3232
private final DockerRegistryAuthentication builderAuthentication;
3333

@@ -39,15 +39,15 @@ public DockerConfiguration() {
3939
this(null, null, null, false);
4040
}
4141

42-
private DockerConfiguration(DockerHost host, DockerRegistryAuthentication builderAuthentication,
42+
private DockerConfiguration(DockerHostConfiguration host, DockerRegistryAuthentication builderAuthentication,
4343
DockerRegistryAuthentication publishAuthentication, boolean bindHostToBuilder) {
4444
this.host = host;
4545
this.builderAuthentication = builderAuthentication;
4646
this.publishAuthentication = publishAuthentication;
4747
this.bindHostToBuilder = bindHostToBuilder;
4848
}
4949

50-
public DockerHost getHost() {
50+
public DockerHostConfiguration getHost() {
5151
return this.host;
5252
}
5353

@@ -65,7 +65,13 @@ public DockerRegistryAuthentication getPublishRegistryAuthentication() {
6565

6666
public DockerConfiguration withHost(String address, boolean secure, String certificatePath) {
6767
Assert.notNull(address, "Address must not be null");
68-
return new DockerConfiguration(new DockerHost(address, secure, certificatePath), this.builderAuthentication,
68+
return new DockerConfiguration(DockerHostConfiguration.forAddress(address, secure, certificatePath),
69+
this.builderAuthentication, this.publishAuthentication, this.bindHostToBuilder);
70+
}
71+
72+
public DockerConfiguration withContext(String context) {
73+
Assert.notNull(context, "Context must not be null");
74+
return new DockerConfiguration(DockerHostConfiguration.forContext(context), this.builderAuthentication,
6975
this.publishAuthentication, this.bindHostToBuilder);
7076
}
7177

@@ -107,4 +113,51 @@ public DockerConfiguration withEmptyPublishRegistryAuthentication() {
107113
new DockerRegistryUserAuthentication("", "", "", ""), this.bindHostToBuilder);
108114
}
109115

116+
public static class DockerHostConfiguration {
117+
118+
private final String address;
119+
120+
private final String context;
121+
122+
private final boolean secure;
123+
124+
private final String certificatePath;
125+
126+
public DockerHostConfiguration(String address, String context, boolean secure, String certificatePath) {
127+
this.address = address;
128+
this.context = context;
129+
this.secure = secure;
130+
this.certificatePath = certificatePath;
131+
}
132+
133+
public String getAddress() {
134+
return this.address;
135+
}
136+
137+
public String getContext() {
138+
return this.context;
139+
}
140+
141+
public boolean isSecure() {
142+
return this.secure;
143+
}
144+
145+
public String getCertificatePath() {
146+
return this.certificatePath;
147+
}
148+
149+
public static DockerHostConfiguration forAddress(String address) {
150+
return new DockerHostConfiguration(address, null, false, null);
151+
}
152+
153+
public static DockerHostConfiguration forAddress(String address, boolean secure, String certificatePath) {
154+
return new DockerHostConfiguration(address, null, secure, certificatePath);
155+
}
156+
157+
static DockerHostConfiguration forContext(String context) {
158+
return new DockerHostConfiguration(null, context, false, null);
159+
}
160+
161+
}
162+
110163
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.buildpack.platform.docker.configuration;
18+
19+
import java.io.IOException;
20+
import java.lang.invoke.MethodHandles;
21+
import java.nio.charset.StandardCharsets;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.security.MessageDigest;
25+
import java.security.NoSuchAlgorithmException;
26+
import java.util.HexFormat;
27+
28+
import com.fasterxml.jackson.core.JsonProcessingException;
29+
import com.fasterxml.jackson.databind.JsonNode;
30+
import com.fasterxml.jackson.databind.node.NullNode;
31+
32+
import org.springframework.boot.buildpack.platform.json.MappedObject;
33+
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
34+
import org.springframework.boot.buildpack.platform.system.Environment;
35+
36+
/**
37+
* Docker configuration stored in metadata files managed by the Docker CLI.
38+
*
39+
* @author Scott Frederick
40+
*/
41+
final class DockerConfigurationMetadata {
42+
43+
private static final String DOCKER_CONFIG = "DOCKER_CONFIG";
44+
45+
private static final String DEFAULT_CONTEXT = "default";
46+
47+
private static final String CONFIG_DIR = ".docker";
48+
49+
private static final String CONTEXTS_DIR = "contexts";
50+
51+
private static final String META_DIR = "meta";
52+
53+
private static final String TLS_DIR = "tls";
54+
55+
private static final String DOCKER_ENDPOINT = "docker";
56+
57+
private static final String CONFIG_FILE_NAME = "config.json";
58+
59+
private static final String CONTEXT_FILE_NAME = "meta.json";
60+
61+
private final String configLocation;
62+
63+
private final DockerConfig config;
64+
65+
private final DockerContext context;
66+
67+
private DockerConfigurationMetadata(String configLocation, DockerConfig config, DockerContext context) {
68+
this.configLocation = configLocation;
69+
this.config = config;
70+
this.context = context;
71+
}
72+
73+
DockerConfig getConfiguration() {
74+
return this.config;
75+
}
76+
77+
DockerContext getContext() {
78+
return this.context;
79+
}
80+
81+
DockerContext forContext(String context) {
82+
return createDockerContext(this.configLocation, context);
83+
}
84+
85+
static DockerConfigurationMetadata from(Environment environment) {
86+
String configLocation = (environment.get(DOCKER_CONFIG) != null) ? environment.get(DOCKER_CONFIG)
87+
: Path.of(System.getProperty("user.home"), CONFIG_DIR).toString();
88+
DockerConfig dockerConfig = createDockerConfig(configLocation);
89+
DockerContext dockerContext = createDockerContext(configLocation, dockerConfig.getCurrentContext());
90+
return new DockerConfigurationMetadata(configLocation, dockerConfig, dockerContext);
91+
}
92+
93+
private static DockerConfig createDockerConfig(String configLocation) {
94+
Path path = Path.of(configLocation, CONFIG_FILE_NAME);
95+
if (!path.toFile().exists()) {
96+
return DockerConfig.empty();
97+
}
98+
try {
99+
return DockerConfig.fromJson(readPathContent(path));
100+
}
101+
catch (JsonProcessingException ex) {
102+
throw new IllegalStateException("Error parsing Docker configuration file '" + path + "'", ex);
103+
}
104+
}
105+
106+
private static DockerContext createDockerContext(String configLocation, String currentContext) {
107+
if (currentContext == null || DEFAULT_CONTEXT.equals(currentContext)) {
108+
return DockerContext.empty();
109+
}
110+
Path metaPath = Path.of(configLocation, CONTEXTS_DIR, META_DIR, asHash(currentContext), CONTEXT_FILE_NAME);
111+
Path tlsPath = Path.of(configLocation, CONTEXTS_DIR, TLS_DIR, asHash(currentContext), DOCKER_ENDPOINT);
112+
if (!metaPath.toFile().exists()) {
113+
throw new IllegalArgumentException("Docker context '" + currentContext + "' does not exist");
114+
}
115+
try {
116+
DockerContext context = DockerContext.fromJson(readPathContent(metaPath));
117+
if (tlsPath.toFile().isDirectory()) {
118+
return context.withTlsPath(tlsPath.toString());
119+
}
120+
return context;
121+
}
122+
catch (JsonProcessingException ex) {
123+
throw new IllegalStateException("Error parsing Docker context metadata file '" + metaPath + "'", ex);
124+
}
125+
}
126+
127+
private static String asHash(String currentContext) {
128+
try {
129+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
130+
byte[] hash = digest.digest(currentContext.getBytes(StandardCharsets.UTF_8));
131+
return HexFormat.of().formatHex(hash);
132+
}
133+
catch (NoSuchAlgorithmException ex) {
134+
return null;
135+
}
136+
}
137+
138+
private static String readPathContent(Path path) {
139+
try {
140+
return Files.readString(path);
141+
}
142+
catch (IOException ex) {
143+
throw new IllegalStateException("Error reading Docker configuration file '" + path + "'", ex);
144+
}
145+
}
146+
147+
static final class DockerConfig extends MappedObject {
148+
149+
private final String currentContext;
150+
151+
private DockerConfig(JsonNode node) {
152+
super(node, MethodHandles.lookup());
153+
this.currentContext = valueAt("/currentContext", String.class);
154+
}
155+
156+
String getCurrentContext() {
157+
return this.currentContext;
158+
}
159+
160+
static DockerConfig fromJson(String json) throws JsonProcessingException {
161+
return new DockerConfig(SharedObjectMapper.get().readTree(json));
162+
}
163+
164+
static DockerConfig empty() {
165+
return new DockerConfig(NullNode.instance);
166+
}
167+
168+
}
169+
170+
static final class DockerContext extends MappedObject {
171+
172+
private final String dockerHost;
173+
174+
private final Boolean skipTlsVerify;
175+
176+
private final String tlsPath;
177+
178+
private DockerContext(JsonNode node, String tlsPath) {
179+
super(node, MethodHandles.lookup());
180+
this.dockerHost = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/Host", String.class);
181+
this.skipTlsVerify = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/SkipTLSVerify", Boolean.class);
182+
this.tlsPath = tlsPath;
183+
}
184+
185+
String getDockerHost() {
186+
return this.dockerHost;
187+
}
188+
189+
Boolean isTlsVerify() {
190+
return this.skipTlsVerify != null && !this.skipTlsVerify;
191+
}
192+
193+
String getTlsPath() {
194+
return this.tlsPath;
195+
}
196+
197+
DockerContext withTlsPath(String tlsPath) {
198+
return new DockerContext(this.getNode(), tlsPath);
199+
}
200+
201+
static DockerContext fromJson(String json) throws JsonProcessingException {
202+
return new DockerContext(SharedObjectMapper.get().readTree(json), null);
203+
}
204+
205+
static DockerContext empty() {
206+
return new DockerContext(NullNode.instance, null);
207+
}
208+
209+
}
210+
211+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 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.

0 commit comments

Comments
 (0)