diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java index e7d3799eb4..04b9dbe978 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListe import org.springframework.boot.buildpack.platform.docker.TotalProgressPushListener; import org.springframework.boot.buildpack.platform.docker.UpdateListener; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; +import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; @@ -83,7 +84,8 @@ public class Builder { * @since 2.4.0 */ public Builder(BuildLog log, DockerConfiguration dockerConfiguration) { - this(log, new DockerApi(dockerConfiguration), dockerConfiguration); + this(log, new DockerApi((dockerConfiguration != null) ? dockerConfiguration.getHost() : null), + dockerConfiguration); } Builder(BuildLog log, DockerApi docker, DockerConfiguration dockerConfiguration) { @@ -147,7 +149,11 @@ public class Builder { } private void executeLifecycle(BuildRequest request, EphemeralBuilder builder) throws IOException { - try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, request, builder)) { + ResolvedDockerHost dockerHost = null; + if (this.dockerConfiguration != null && this.dockerConfiguration.isBindHostToBuilder()) { + dockerHost = ResolvedDockerHost.from(this.dockerConfiguration.getHost()); + } + try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, dockerHost, request, builder)) { lifecycle.execute(); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java index 8fa7f390d4..75195e94e4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.util.function.Consumer; import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent; +import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; @@ -47,10 +48,14 @@ class Lifecycle implements Closeable { private static final String PLATFORM_API_VERSION_KEY = "CNB_PLATFORM_API"; + private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock"; + private final BuildLog log; private final DockerApi docker; + private final ResolvedDockerHost dockerHost; + private final BuildRequest request; private final EphemeralBuilder builder; @@ -75,12 +80,15 @@ class Lifecycle implements Closeable { * Create a new {@link Lifecycle} instance. * @param log build output log * @param docker the Docker API + * @param dockerHost the Docker host information * @param request the request to process * @param builder the ephemeral builder used to run the phases */ - Lifecycle(BuildLog log, DockerApi docker, BuildRequest request, EphemeralBuilder builder) { + Lifecycle(BuildLog log, DockerApi docker, ResolvedDockerHost dockerHost, BuildRequest request, + EphemeralBuilder builder) { this.log = log; this.docker = docker; + this.dockerHost = dockerHost; this.request = request; this.builder = builder; this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion()); @@ -147,6 +155,7 @@ class Lifecycle implements Closeable { private Phase createPhase() { Phase phase = new Phase("creator", isVerboseLogging()); phase.withDaemonAccess(); + configureDaemonAccess(phase); phase.withLogLevelArg(); phase.withArgs("-app", Directory.APPLICATION); phase.withArgs("-platform", Directory.PLATFORM); @@ -176,6 +185,24 @@ class Lifecycle implements Closeable { return phase; } + private void configureDaemonAccess(Phase phase) { + if (this.dockerHost != null) { + if (this.dockerHost.isRemote()) { + phase.withEnv("DOCKER_HOST", this.dockerHost.getAddress()); + if (this.dockerHost.isSecure()) { + phase.withEnv("DOCKER_TLS_VERIFY", "1"); + phase.withEnv("DOCKER_CERT_PATH", this.dockerHost.getCertificatePath()); + } + } + else { + phase.withBinding(Binding.from(this.dockerHost.getAddress(), DOMAIN_SOCKET_PATH)); + } + } + else { + phase.withBinding(Binding.from(DOMAIN_SOCKET_PATH, DOMAIN_SOCKET_PATH)); + } + } + private boolean isVerboseLogging() { return this.request.isVerboseLogging() && this.lifecycleVersion.isEqualOrGreaterThan(LOGGING_MINIMUM_VERSION); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java index 8c0264f836..20cf2fad1f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,8 +35,6 @@ import org.springframework.util.StringUtils; */ class Phase { - private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock"; - private final String name; private final boolean verboseLogging; @@ -132,7 +130,6 @@ class Phase { void apply(ContainerConfig.Update update) { if (this.daemonAccess) { update.withUser("root"); - update.withBinding(Binding.from(DOMAIN_SOCKET_PATH, DOMAIN_SOCKET_PATH)); } update.withCommand("/cnb/lifecycle/" + this.name, StringUtils.toStringArray(this.args)); update.withLabel("author", "spring-boot"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java index 0889a42f38..1473c4f353 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.http.client.utils.URIBuilder; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; @@ -75,16 +75,16 @@ public class DockerApi { * Create a new {@link DockerApi} instance. */ public DockerApi() { - this(new DockerConfiguration()); + this(HttpTransport.create(null)); } /** * Create a new {@link DockerApi} instance. - * @param dockerConfiguration the docker configuration + * @param dockerHost the Docker daemon host information * @since 2.4.0 */ - public DockerApi(DockerConfiguration dockerConfiguration) { - this(HttpTransport.create((dockerConfiguration != null) ? dockerConfiguration.getHost() : null)); + public DockerApi(DockerHost dockerHost) { + this(HttpTransport.create(dockerHost)); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java index 6813578092..8ed7db643b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,21 +33,28 @@ public final class DockerConfiguration { private final DockerRegistryAuthentication publishAuthentication; + private final boolean bindHostToBuilder; + public DockerConfiguration() { - this(null, null, null); + this(null, null, null, false); } private DockerConfiguration(DockerHost host, DockerRegistryAuthentication builderAuthentication, - DockerRegistryAuthentication publishAuthentication) { + DockerRegistryAuthentication publishAuthentication, boolean bindHostToBuilder) { this.host = host; this.builderAuthentication = builderAuthentication; this.publishAuthentication = publishAuthentication; + this.bindHostToBuilder = bindHostToBuilder; } public DockerHost getHost() { return this.host; } + public boolean isBindHostToBuilder() { + return this.bindHostToBuilder; + } + public DockerRegistryAuthentication getBuilderRegistryAuthentication() { return this.builderAuthentication; } @@ -59,13 +66,18 @@ public final class DockerConfiguration { public DockerConfiguration withHost(String address, boolean secure, String certificatePath) { Assert.notNull(address, "Address must not be null"); return new DockerConfiguration(new DockerHost(address, secure, certificatePath), this.builderAuthentication, - this.publishAuthentication); + this.publishAuthentication, this.bindHostToBuilder); + } + + public DockerConfiguration withBindHostToBuilder(boolean bindHostToBuilder) { + return new DockerConfiguration(this.host, this.builderAuthentication, this.publishAuthentication, + bindHostToBuilder); } public DockerConfiguration withBuilderRegistryTokenAuthentication(String token) { Assert.notNull(token, "Token must not be null"); return new DockerConfiguration(this.host, new DockerRegistryTokenAuthentication(token), - this.publishAuthentication); + this.publishAuthentication, this.bindHostToBuilder); } public DockerConfiguration withBuilderRegistryUserAuthentication(String username, String password, String url, @@ -73,13 +85,13 @@ public final class DockerConfiguration { Assert.notNull(username, "Username must not be null"); Assert.notNull(password, "Password must not be null"); return new DockerConfiguration(this.host, new DockerRegistryUserAuthentication(username, password, url, email), - this.publishAuthentication); + this.publishAuthentication, this.bindHostToBuilder); } public DockerConfiguration withPublishRegistryTokenAuthentication(String token) { Assert.notNull(token, "Token must not be null"); return new DockerConfiguration(this.host, this.builderAuthentication, - new DockerRegistryTokenAuthentication(token)); + new DockerRegistryTokenAuthentication(token), this.bindHostToBuilder); } public DockerConfiguration withPublishRegistryUserAuthentication(String username, String password, String url, @@ -87,7 +99,7 @@ public final class DockerConfiguration { Assert.notNull(username, "Username must not be null"); Assert.notNull(password, "Password must not be null"); return new DockerConfiguration(this.host, this.builderAuthentication, - new DockerRegistryUserAuthentication(username, password, url, email)); + new DockerRegistryUserAuthentication(username, password, url, email), this.bindHostToBuilder); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java index 024b587f29..3db5f5541d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,10 @@ public class DockerHost { private final String certificatePath; + public DockerHost(String address) { + this(address, false, null); + } + public DockerHost(String address, boolean secure, String certificatePath) { this.address = address; this.secure = secure; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java new file mode 100644 index 0000000000..95272b19d0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.configuration; + +import java.nio.file.Files; +import java.nio.file.Paths; + +import com.sun.jna.Platform; + +import org.springframework.boot.buildpack.platform.system.Environment; + +/** + * Resolves a {@link DockerHost} from the environment, configuration, or using defaults. + * + * @author Scott Frederick + * @since 2.7.0 + */ +public class ResolvedDockerHost extends DockerHost { + + private static final String UNIX_SOCKET_PREFIX = "unix://"; + + private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock"; + + private static final String WINDOWS_NAMED_PIPE_PATH = "//./pipe/docker_engine"; + + private static final String DOCKER_HOST = "DOCKER_HOST"; + + private static final String DOCKER_TLS_VERIFY = "DOCKER_TLS_VERIFY"; + + private static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH"; + + ResolvedDockerHost(String address, boolean secure, String certificatePath) { + super(address, secure, certificatePath); + } + + @Override + public String getAddress() { + return super.getAddress().startsWith(UNIX_SOCKET_PREFIX) + ? super.getAddress().substring(UNIX_SOCKET_PREFIX.length()) : super.getAddress(); + } + + public boolean isRemote() { + return getAddress().startsWith("http") || getAddress().startsWith("tcp"); + } + + public boolean isLocalFileReference() { + try { + return Files.exists(Paths.get(getAddress())); + } + catch (Exception ex) { + return false; + } + } + + public static ResolvedDockerHost from(DockerHost dockerHost) { + return from(Environment.SYSTEM, dockerHost); + } + + static ResolvedDockerHost from(Environment environment, DockerHost dockerHost) { + if (environment.get(DOCKER_HOST) != null) { + return new ResolvedDockerHost(environment.get(DOCKER_HOST), isTrue(environment.get(DOCKER_TLS_VERIFY)), + environment.get(DOCKER_CERT_PATH)); + } + if (dockerHost != null && dockerHost.getAddress() != null) { + return new ResolvedDockerHost(dockerHost.getAddress(), dockerHost.isSecure(), + dockerHost.getCertificatePath()); + } + return new ResolvedDockerHost(Platform.isWindows() ? WINDOWS_NAMED_PIPE_PATH : DOMAIN_SOCKET_PATH, false, null); + } + + private static boolean isTrue(String value) { + try { + return (value != null) && (Integer.parseInt(value) == 1); + } + catch (NumberFormatException ex) { + return false; + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java index 432e566573..4a8461fa09 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URI; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.io.IOConsumer; -import org.springframework.boot.buildpack.platform.system.Environment; /** * HTTP transport used for docker access. @@ -90,44 +89,14 @@ public interface HttpTransport { Response delete(URI uri) throws IOException; /** - * Create the most suitable {@link HttpTransport} based on the - * {@link Environment#SYSTEM system environment}. - * @return a {@link HttpTransport} instance - */ - static HttpTransport create() { - return create(Environment.SYSTEM); - } - - /** - * Create the most suitable {@link HttpTransport} based on the - * {@link Environment#SYSTEM system environment}. - * @param dockerHost the Docker engine host configuration + * Create the most suitable {@link HttpTransport} based on the {@link DockerHost}. + * @param dockerHost the Docker host information * @return a {@link HttpTransport} instance */ static HttpTransport create(DockerHost dockerHost) { - return create(Environment.SYSTEM, dockerHost); - } - - /** - * Create the most suitable {@link HttpTransport} based on the given - * {@link Environment}. - * @param environment the source environment - * @return a {@link HttpTransport} instance - */ - static HttpTransport create(Environment environment) { - return create(environment, null); - } - - /** - * Create the most suitable {@link HttpTransport} based on the given - * {@link Environment} and {@link DockerConfiguration}. - * @param environment the source environment - * @param dockerHost the Docker engine host configuration - * @return a {@link HttpTransport} instance - */ - static HttpTransport create(Environment environment, DockerHost dockerHost) { - HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment, dockerHost); - return (remote != null) ? remote : LocalHttpClientTransport.create(environment); + ResolvedDockerHost host = ResolvedDockerHost.from(dockerHost); + HttpTransport remote = RemoteHttpClientTransport.createIfPossible(host); + return (remote != null) ? remote : LocalHttpClientTransport.create(host); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java index 9861f4d45a..297160cff3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,9 +38,9 @@ import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.apache.http.protocol.HttpContext; import org.apache.http.util.Args; +import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.socket.DomainSocket; import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket; -import org.springframework.boot.buildpack.platform.system.Environment; /** * {@link HttpClientTransport} that talks to local Docker. @@ -50,31 +50,19 @@ import org.springframework.boot.buildpack.platform.system.Environment; */ final class LocalHttpClientTransport extends HttpClientTransport { - private static final String UNIX_SOCKET_PREFIX = "unix://"; - - private static final String DOCKER_HOST = "DOCKER_HOST"; - private static final HttpHost LOCAL_DOCKER_HOST = HttpHost.create("docker://localhost"); private LocalHttpClientTransport(CloseableHttpClient client) { super(client, LOCAL_DOCKER_HOST); } - static LocalHttpClientTransport create(Environment environment) { + static LocalHttpClientTransport create(ResolvedDockerHost dockerHost) { HttpClientBuilder builder = HttpClients.custom(); - builder.setConnectionManager(new LocalConnectionManager(socketFilePath(environment))); + builder.setConnectionManager(new LocalConnectionManager(dockerHost.getAddress())); builder.setSchemePortResolver(new LocalSchemePortResolver()); return new LocalHttpClientTransport(builder.build()); } - private static String socketFilePath(Environment environment) { - String host = environment.get(DOCKER_HOST); - if (host != null && host.startsWith(UNIX_SOCKET_PREFIX)) { - return host.substring(UNIX_SOCKET_PREFIX.length()); - } - return host; - } - /** * {@link HttpClientConnectionManager} for local Docker. */ @@ -112,10 +100,6 @@ final class LocalHttpClientTransport extends HttpClientTransport { */ private static class LocalConnectionSocketFactory implements ConnectionSocketFactory { - private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock"; - - private static final String WINDOWS_NAMED_PIPE_PATH = "//./pipe/docker_engine"; - private final String host; LocalConnectionSocketFactory(String host) { @@ -125,9 +109,9 @@ final class LocalHttpClientTransport extends HttpClientTransport { @Override public Socket createSocket(HttpContext context) throws IOException { if (Platform.isWindows()) { - return NamedPipeSocket.get((this.host != null) ? this.host : WINDOWS_NAMED_PIPE_PATH); + return NamedPipeSocket.get(this.host); } - return DomainSocket.get((this.host != null) ? this.host : DOMAIN_SOCKET_PATH); + return DomainSocket.get(this.host); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java index 1655cc3f34..26453f49aa 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,6 @@ package org.springframework.boot.buildpack.platform.docker.transport; -import java.nio.file.Files; -import java.nio.file.Paths; - import javax.net.ssl.SSLContext; import org.apache.http.HttpHost; @@ -29,8 +26,8 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory; -import org.springframework.boot.buildpack.platform.system.Environment; import org.springframework.util.Assert; /** @@ -41,39 +38,20 @@ import org.springframework.util.Assert; */ final class RemoteHttpClientTransport extends HttpClientTransport { - private static final String UNIX_SOCKET_PREFIX = "unix://"; - - private static final String DOCKER_HOST = "DOCKER_HOST"; - - private static final String DOCKER_TLS_VERIFY = "DOCKER_TLS_VERIFY"; - - private static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH"; - private RemoteHttpClientTransport(CloseableHttpClient client, HttpHost host) { super(client, host); } - static RemoteHttpClientTransport createIfPossible(Environment environment, DockerHost dockerHost) { - return createIfPossible(environment, dockerHost, new SslContextFactory()); + static RemoteHttpClientTransport createIfPossible(ResolvedDockerHost dockerHost) { + return createIfPossible(dockerHost, new SslContextFactory()); } - static RemoteHttpClientTransport createIfPossible(Environment environment, DockerHost dockerHost, + static RemoteHttpClientTransport createIfPossible(ResolvedDockerHost dockerHost, SslContextFactory sslContextFactory) { - DockerHost host = getHost(environment, dockerHost); - if (host == null || host.getAddress() == null || isLocalFileReference(host.getAddress())) { + if (!dockerHost.isRemote()) { return null; } - return create(host, sslContextFactory, HttpHost.create(host.getAddress())); - } - - private static boolean isLocalFileReference(String host) { - String filePath = host.startsWith(UNIX_SOCKET_PREFIX) ? host.substring(UNIX_SOCKET_PREFIX.length()) : host; - try { - return Files.exists(Paths.get(filePath)); - } - catch (Exception ex) { - return false; - } + return create(dockerHost, sslContextFactory, HttpHost.create(dockerHost.getAddress())); } private static RemoteHttpClientTransport create(DockerHost host, SslContextFactory sslContextFactory, @@ -96,29 +74,4 @@ final class RemoteHttpClientTransport extends HttpClientTransport { return new SSLConnectionSocketFactory(sslContext); } - private static DockerHost getHost(Environment environment, DockerHost dockerHost) { - if (environment.get(DOCKER_HOST) != null) { - return new EnvironmentDockerHost(environment); - } - return dockerHost; - } - - private static class EnvironmentDockerHost extends DockerHost { - - EnvironmentDockerHost(Environment environment) { - super(environment.get(DOCKER_HOST), isTrue(environment.get(DOCKER_TLS_VERIFY)), - environment.get(DOCKER_CERT_PATH)); - } - - private static boolean isTrue(String value) { - try { - return (value != null) && (Integer.parseInt(value) == 1); - } - catch (NumberFormatException ex) { - return false; - } - } - - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java index 48347e3db6..6aa6f04c81 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java @@ -36,6 +36,8 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; @@ -212,6 +214,28 @@ class LifecycleTests { assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } + @Test + void executeWithDockerHostAndRemoteAddressExecutesPhases() throws Exception { + given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); + BuildRequest request = getTestRequest(); + createLifecycle(request, ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376"))).execute(); + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json")); + assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); + } + + @Test + void executeWithDockerHostAndLocalAddressExecutesPhases() throws Exception { + given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); + BuildRequest request = getTestRequest(); + createLifecycle(request, ResolvedDockerHost.from(new DockerHost("/var/alt.sock"))).execute(); + assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json")); + assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); + } + private DockerApi mockDockerApi() { DockerApi docker = mock(DockerApi.class); ImageApi imageApi = mock(ImageApi.class); @@ -243,8 +267,13 @@ class LifecycleTests { return createLifecycle(getTestRequest(), builder); } + private Lifecycle createLifecycle(BuildRequest request, ResolvedDockerHost dockerHost) throws IOException { + EphemeralBuilder builder = mockEphemeralBuilder(); + return new TestLifecycle(BuildLog.to(this.out), this.docker, dockerHost, request, builder); + } + private Lifecycle createLifecycle(BuildRequest request, EphemeralBuilder ephemeralBuilder) { - return new TestLifecycle(BuildLog.to(this.out), this.docker, request, ephemeralBuilder); + return new TestLifecycle(BuildLog.to(this.out), this.docker, null, request, ephemeralBuilder); } private EphemeralBuilder mockEphemeralBuilder() throws IOException { @@ -296,8 +325,9 @@ class LifecycleTests { static class TestLifecycle extends Lifecycle { - TestLifecycle(BuildLog log, DockerApi docker, BuildRequest request, EphemeralBuilder builder) { - super(log, docker, request, builder); + TestLifecycle(BuildLog log, DockerApi docker, ResolvedDockerHost dockerHost, BuildRequest request, + EphemeralBuilder builder) { + super(log, docker, dockerHost, request, builder); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java index 3045853250..8ca4ba1883 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java @@ -60,13 +60,12 @@ class PhaseTests { } @Test - void applyWhenWithDaemonAccessUpdatesConfigurationWithRootUserAndDomainSocketBinding() { + void applyWhenWithDaemonAccessUpdatesConfigurationWithRootUser() { Phase phase = new Phase("test", false); phase.withDaemonAccess(); Update update = mock(Update.class); phase.apply(update); then(update).should().withUser("root"); - then(update).should().withBinding(Binding.from("/var/run/docker.sock", "/var/run/docker.sock")); then(update).should().withCommand("/cnb/lifecycle/test", NO_ARGS); then(update).should().withLabel("author", "spring-boot"); then(update).shouldHaveNoMoreInteractions(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java new file mode 100644 index 0000000000..30a1c35830 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java @@ -0,0 +1,166 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.configuration; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ResolvedDockerHost}. + * + * @author Scott Frederick + */ +class ResolvedDockerHostTests { + + private final Map environment = new LinkedHashMap<>(); + + @Test + @DisabledOnOs(OS.WINDOWS) + void resolveWhenDockerHostIsNullReturnsLinuxDefault() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); + assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock"); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + void resolveWhenDockerHostIsNullReturnsWindowsDefault() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); + assertThat(dockerHost.getAddress()).isEqualTo("//./pipe/docker_engine"); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + void resolveWhenDockerHostAddressIsNullReturnsLinuxDefault() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerHost(null)); + assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock"); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + void resolveWhenDockerHostAddressIsLocalReturnsAddress(@TempDir Path tempDir) throws IOException { + String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + new DockerHost(socketFilePath, false, null)); + assertThat(dockerHost.isLocalFileReference()).isTrue(); + assertThat(dockerHost.isRemote()).isFalse(); + assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + void resolveWhenDockerHostAddressIsLocalWithSchemeReturnsAddress(@TempDir Path tempDir) throws IOException { + String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + new DockerHost("unix://" + socketFilePath, false, null)); + assertThat(dockerHost.isLocalFileReference()).isTrue(); + assertThat(dockerHost.isRemote()).isFalse(); + assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + void resolveWhenDockerHostAddressIsHttpReturnsAddress() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + new DockerHost("http://docker.example.com", false, null)); + assertThat(dockerHost.isLocalFileReference()).isFalse(); + assertThat(dockerHost.isRemote()).isTrue(); + assertThat(dockerHost.getAddress()).isEqualTo("http://docker.example.com"); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + void resolveWhenDockerHostAddressIsHttpsReturnsAddress() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + new DockerHost("https://docker.example.com", true, "/cert-path")); + assertThat(dockerHost.isLocalFileReference()).isFalse(); + assertThat(dockerHost.isRemote()).isTrue(); + assertThat(dockerHost.getAddress()).isEqualTo("https://docker.example.com"); + assertThat(dockerHost.isSecure()).isTrue(); + assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); + } + + @Test + void resolveWhenDockerHostAddressIsTcpReturnsAddress() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + new DockerHost("tcp://192.168.99.100:2376", true, "/cert-path")); + assertThat(dockerHost.isLocalFileReference()).isFalse(); + assertThat(dockerHost.isRemote()).isTrue(); + assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); + assertThat(dockerHost.isSecure()).isTrue(); + assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); + } + + @Test + void resolveWhenEnvironmentAddressIsLocalReturnsAddress(@TempDir Path tempDir) throws IOException { + String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); + this.environment.put("DOCKER_HOST", socketFilePath); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + new DockerHost("/unused", true, "/unused")); + assertThat(dockerHost.isLocalFileReference()).isTrue(); + assertThat(dockerHost.isRemote()).isFalse(); + assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + void resolveWhenEnvironmentAddressIsLocalWithSchemeReturnsAddress(@TempDir Path tempDir) throws IOException { + String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); + this.environment.put("DOCKER_HOST", "unix://" + socketFilePath); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + new DockerHost("/unused", true, "/unused")); + assertThat(dockerHost.isLocalFileReference()).isTrue(); + assertThat(dockerHost.isRemote()).isFalse(); + assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + void resolveWhenEnvironmentAddressIsTcpReturnsAddress() { + this.environment.put("DOCKER_HOST", "tcp://192.168.99.100:2376"); + this.environment.put("DOCKER_TLS_VERIFY", "1"); + this.environment.put("DOCKER_CERT_PATH", "/cert-path"); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + new DockerHost("tcp://1.1.1.1", false, "/unused")); + assertThat(dockerHost.isLocalFileReference()).isFalse(); + assertThat(dockerHost.isRemote()).isTrue(); + assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); + assertThat(dockerHost.isSecure()).isTrue(); + assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java index 38a65a819c..c04cd5e719 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,12 @@ package org.springframework.boot.buildpack.platform.docker.transport; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -37,31 +37,21 @@ class HttpTransportTests { @Test void createWhenDockerHostVariableIsAddressReturnsRemote() { - Map environment = Collections.singletonMap("DOCKER_HOST", "tcp://192.168.1.0"); - HttpTransport transport = HttpTransport.create(environment::get); + HttpTransport transport = HttpTransport.create(new DockerHost("tcp://192.168.1.0")); assertThat(transport).isInstanceOf(RemoteHttpClientTransport.class); } @Test void createWhenDockerHostVariableIsFileReturnsLocal(@TempDir Path tempDir) throws IOException { String dummySocketFilePath = Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath().toString(); - Map environment = Collections.singletonMap("DOCKER_HOST", dummySocketFilePath); - HttpTransport transport = HttpTransport.create(environment::get); + HttpTransport transport = HttpTransport.create(new DockerHost(dummySocketFilePath)); assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } @Test void createWhenDockerHostVariableIsUnixSchemePrefixedFileReturnsLocal(@TempDir Path tempDir) throws IOException { - String dummySocketFilePath = "unix://" - + Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath().toString(); - Map environment = Collections.singletonMap("DOCKER_HOST", dummySocketFilePath); - HttpTransport transport = HttpTransport.create(environment::get); - assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); - } - - @Test - void createWhenDoesNotHaveDockerHostVariableReturnsLocal() { - HttpTransport transport = HttpTransport.create((name) -> null); + String dummySocketFilePath = "unix://" + Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath(); + HttpTransport transport = HttpTransport.create(new DockerHost(dummySocketFilePath)); assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java new file mode 100644 index 0000000000..78ff1d0c71 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.docker.transport; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LocalHttpClientTransport} + * + * @author Scott Frederick + */ +class LocalHttpClientTransportTests { + + @Test + void createWhenDockerHostIsFileReturnsTransport(@TempDir Path tempDir) throws IOException { + String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(socketFilePath)); + LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); + assertThat(transport).isNotNull(); + } + + @Test + void createWhenDockerHostIsFileThatDoesNotExistReturnsTransport(@TempDir Path tempDir) { + String socketFilePath = Paths.get(tempDir.toString(), "dummy").toAbsolutePath().toString(); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(socketFilePath)); + LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); + assertThat(transport).isNotNull(); + } + + @Test + void createWhenDockerHostIsAddressReturnsTransport() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); + assertThat(transport).isNotNull(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java index acf3528fa3..0bf2fb3685 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,15 @@ package org.springframework.boot.buildpack.platform.docker.transport; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.function.Consumer; import javax.net.ssl.SSLContext; import org.apache.http.HttpHost; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory; import static org.assertj.core.api.Assertions.assertThat; @@ -46,99 +40,56 @@ import static org.mockito.Mockito.mock; */ class RemoteHttpClientTransportTests { - private final Map environment = new LinkedHashMap<>(); - - private final DockerConfiguration dockerConfiguration = new DockerConfiguration(); - @Test void createIfPossibleWhenDockerHostIsNotSetReturnsNull() { - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, - new DockerHost(null, false, null)); - assertThat(transport).isNull(); - } - - @Test - void createIfPossibleWithoutDockerConfigurationReturnsNull() { - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, null); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(null); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test - void createIfPossibleWhenDockerHostInEnvironmentIsFileReturnsNull(@TempDir Path tempDir) throws IOException { - String dummySocketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath() - .toString(); - this.environment.put("DOCKER_HOST", dummySocketFilePath); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, null); + void createIfPossibleWhenDockerHostIsDefaultReturnsNull() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(null)); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test - void createIfPossibleWhenDockerHostInConfigurationIsFileReturnsNull(@TempDir Path tempDir) throws IOException { - String dummySocketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath() - .toString(); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, - new DockerHost(dummySocketFilePath, false, null)); + void createIfPossibleWhenDockerHostIsFileReturnsNull() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("unix:///var/run/socket.sock")); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test - void createIfPossibleWhenDockerHostInEnvironmentIsAddressReturnsTransport() { - this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, null); + void createIfPossibleWhenDockerHostIsAddressReturnsTransport() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNotNull(); } - @Test - void createIfPossibleWhenDockerHostInConfigurationIsAddressReturnsTransport() { - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, - new DockerHost("tcp://192.168.1.2:2376", false, null)); - assertThat(transport).isNotNull(); - } - - @Test - void createIfPossibleWhenTlsVerifyInEnvironmentWithMissingCertPathThrowsException() { - this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); - this.environment.put("DOCKER_TLS_VERIFY", "1"); - assertThatIllegalArgumentException() - .isThrownBy(() -> RemoteHttpClientTransport.createIfPossible(this.environment::get, null)) - .withMessageContaining("Docker host TLS verification requires trust material"); - } - - @Test - void createIfPossibleWhenTlsVerifyInConfigurationWithMissingCertPathThrowsException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> RemoteHttpClientTransport.createIfPossible(this.environment::get, - new DockerHost("tcp://192.168.1.2:2376", true, null))) - .withMessageContaining("Docker host TLS verification requires trust material"); - } - @Test void createIfPossibleWhenNoTlsVerifyUsesHttp() { - this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, null); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport.getHost()).satisfies(hostOf("http", "192.168.1.2", 2376)); } @Test - void createIfPossibleWhenTlsVerifyInEnvironmentUsesHttps() throws Exception { - this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); - this.environment.put("DOCKER_TLS_VERIFY", "1"); - this.environment.put("DOCKER_CERT_PATH", "/test-cert-path"); + void createIfPossibleWhenTlsVerifyUsesHttps() throws Exception { SslContextFactory sslContextFactory = mock(SslContextFactory.class); given(sslContextFactory.forDirectory("/test-cert-path")).willReturn(SSLContext.getDefault()); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, - this.dockerConfiguration.getHost(), sslContextFactory); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(new DockerHost("tcp://192.168.1.2:2376", true, "/test-cert-path")); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost, sslContextFactory); assertThat(transport.getHost()).satisfies(hostOf("https", "192.168.1.2", 2376)); } @Test - void createIfPossibleWhenTlsVerifyInConfigurationUsesHttps() throws Exception { - SslContextFactory sslContextFactory = mock(SslContextFactory.class); - given(sslContextFactory.forDirectory("/test-cert-path")).willReturn(SSLContext.getDefault()); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, - this.dockerConfiguration.withHost("tcp://192.168.1.2:2376", true, "/test-cert-path").getHost(), - sslContextFactory); - assertThat(transport.getHost()).satisfies(hostOf("https", "192.168.1.2", 2376)); + void createIfPossibleWhenTlsVerifyWithMissingCertPathThrowsException() { + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376", true, null)); + assertThatIllegalArgumentException().isThrownBy(() -> RemoteHttpClientTransport.createIfPossible(dockerHost)) + .withMessageContaining("Docker host TLS verification requires trust material"); } private Consumer hostOf(String scheme, String hostName, int port) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-local.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-local.json new file mode 100644 index 0000000000..9aea011de0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-local.json @@ -0,0 +1,36 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/creator", + "-app", + "/workspace", + "-platform", + "/platform", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "-layers", + "/layers", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-daemon", + "docker.io/library/my-application:latest" + ], + "Env": [ + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "/var/alt.sock:/var/run/docker.sock", + "pack-layers-aaaaaaaaaa:/layers", + "pack-app-aaaaaaaaaa:/workspace", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-remote.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-remote.json new file mode 100644 index 0000000000..52c999f54d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-inherit-remote.json @@ -0,0 +1,36 @@ +{ + "User": "root", + "Image": "pack.local/ephemeral-builder", + "Cmd": [ + "/cnb/lifecycle/creator", + "-app", + "/workspace", + "-platform", + "/platform", + "-run-image", + "docker.io/cloudfoundry/run:latest", + "-layers", + "/layers", + "-cache-dir", + "/cache", + "-launch-cache", + "/launch-cache", + "-daemon", + "docker.io/library/my-application:latest" + ], + "Env": [ + "DOCKER_HOST=tcp://192.168.1.2:2376", + "CNB_PLATFORM_API=0.8" + ], + "Labels": { + "author": "spring-boot" + }, + "HostConfig": { + "Binds": [ + "pack-layers-aaaaaaaaaa:/layers", + "pack-app-aaaaaaaaaa:/workspace", + "pack-cache-b35197ac41ea.build:/cache", + "pack-cache-b35197ac41ea.launch:/launch-cache" + ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc index 9e5b64cf7e..3b16800e81 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -48,6 +48,9 @@ The following table summarizes the available properties: | `certPath` | Path to certificate and key files for HTTPS (required if `tlsVerify` is `true`, ignored otherwise) + +| `bindHostToBuilder` +| When `true`, the value of the `host` property will be provided to the container that is created for the CNB builder (optional) |=== For more details, see also <>. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java index b1a4d133ca..205dc37bac 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,8 @@ public class DockerSpec { private String certPath; + private boolean bindHostToBuilder; + private final DockerRegistrySpec builderRegistry; private final DockerRegistrySpec publishRegistry; @@ -85,6 +87,16 @@ public class DockerSpec { this.certPath = certPath; } + @Input + @Optional + public Boolean isBindHostToBuilder() { + return this.bindHostToBuilder; + } + + public void setBindHostToBuilder(boolean use) { + this.bindHostToBuilder = use; + } + /** * Returns the {@link DockerRegistrySpec} that configures authentication to the * builder registry. @@ -150,6 +162,7 @@ public class DockerSpec { DockerConfiguration asDockerConfiguration() { DockerConfiguration dockerConfiguration = new DockerConfiguration(); dockerConfiguration = customizeHost(dockerConfiguration); + dockerConfiguration = dockerConfiguration.withBindHostToBuilder(this.bindHostToBuilder); dockerConfiguration = customizeBuilderAuthentication(dockerConfiguration); dockerConfiguration = customizePublishAuthentication(dockerConfiguration); return dockerConfiguration; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java index 846bc7d684..db4bc46931 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,7 @@ class DockerSpecTests { assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isEqualTo(true); assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert"); + assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); assertThat(dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); assertThat(dockerSpec.asDockerConfiguration().getPublishRegistryAuthentication()).isNull(); } @@ -66,6 +67,22 @@ class DockerSpecTests { assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isEqualTo(false); assertThat(host.getCertificatePath()).isNull(); + assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); + assertThat(dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); + assertThat(dockerSpec.asDockerConfiguration().getPublishRegistryAuthentication()).isNull(); + } + + @Test + void asDockerConfigurationWithBindHostToBuilder() { + DockerSpec dockerSpec = new DockerSpec(); + dockerSpec.setHost("docker.example.com"); + dockerSpec.setBindHostToBuilder(true); + DockerConfiguration dockerConfiguration = dockerSpec.asDockerConfiguration(); + DockerHost host = dockerConfiguration.getHost(); + assertThat(host.getAddress()).isEqualTo("docker.example.com"); + assertThat(host.isSecure()).isEqualTo(false); + assertThat(host.getCertificatePath()).isNull(); + assertThat(dockerConfiguration.isBindHostToBuilder()).isTrue(); assertThat(dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); assertThat(dockerSpec.asDockerConfiguration().getPublishRegistryAuthentication()).isNull(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc index 88e0d2960c..17157e792d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -57,6 +57,9 @@ The following table summarizes the available parameters: | `certPath` | Path to certificate and key files for HTTPS (required if `tlsVerify` is `true`, ignored otherwise) + +| `bindHostToBuilder` +| When `true`, the value of the `host` property will be provided to the container that is created for the CNB builder (optional) |=== For more details, see also <>. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java index 637de7d148..b43406db4b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,8 @@ public class Docker { private String certPath; + private boolean bindHostToBuilder; + private DockerRegistry builderRegistry; private DockerRegistry publishRegistry; @@ -74,6 +76,18 @@ public class Docker { this.certPath = certPath; } + /** + * Whether to use the configured Docker host in the builder container. + * @return {@code true} to use the configured Docker host in the builder container + */ + public boolean isBindHostToBuilder() { + return this.bindHostToBuilder; + } + + void setBindHostToBuilder(boolean bindHostToBuilder) { + this.bindHostToBuilder = bindHostToBuilder; + } + /** * Configuration of the Docker registry where builder and run images are stored. * @return the registry configuration @@ -117,6 +131,7 @@ public class Docker { DockerConfiguration asDockerConfiguration() { DockerConfiguration dockerConfiguration = new DockerConfiguration(); dockerConfiguration = customizeHost(dockerConfiguration); + dockerConfiguration = dockerConfiguration.withBindHostToBuilder(this.bindHostToBuilder); dockerConfiguration = customizeBuilderAuthentication(dockerConfiguration); dockerConfiguration = customizePublishAuthentication(dockerConfiguration); return dockerConfiguration; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java index eb91cdc901..83eb3e52a7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,24 @@ class DockerTests { assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isEqualTo(true); assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert"); + assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); + assertThat(docker.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); + assertThat(docker.asDockerConfiguration().getPublishRegistryAuthentication()).isNull(); + } + + @Test + void asDockerConfigurationWithBindHostToBuilder() { + Docker docker = new Docker(); + docker.setHost("docker.example.com"); + docker.setTlsVerify(true); + docker.setCertPath("/tmp/ca-cert"); + docker.setBindHostToBuilder(true); + DockerConfiguration dockerConfiguration = docker.asDockerConfiguration(); + DockerHost host = dockerConfiguration.getHost(); + assertThat(host.getAddress()).isEqualTo("docker.example.com"); + assertThat(host.isSecure()).isEqualTo(true); + assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert"); + assertThat(dockerConfiguration.isBindHostToBuilder()).isTrue(); assertThat(docker.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); assertThat(docker.asDockerConfiguration().getPublishRegistryAuthentication()).isNull(); }