Merge branch '2.7.x'

pull/30220/head
Scott Frederick 3 years ago
commit 5e07db7d21

@ -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();
}
}

@ -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);
}

@ -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");

@ -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));
}
/**

@ -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);
}
}

@ -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;

@ -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;
}
}
}

@ -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);
}
/**

@ -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

@ -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;
}
}
}
}

@ -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

@ -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();

@ -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<String, String> 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");
}
}

@ -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<String, String> 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<String, String> 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<String, String> 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);
}

@ -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();
}
}

@ -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<String, String> 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<HttpHost> hostOf(String scheme, String hostName, int port) {

@ -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"
]
}
}

@ -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"
]
}
}

@ -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 <<build-image.examples.docker,examples>>.

@ -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.
@ -39,6 +39,8 @@ public class DockerSpec {
private String certPath;
private boolean bindHostToBuilder;
private final DockerRegistrySpec builderRegistry;
private final DockerRegistrySpec publishRegistry;
@ -83,6 +85,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.
@ -130,6 +142,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;

@ -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();
}

@ -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 <<build-image.examples.docker,examples>>.

@ -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;

@ -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();
}

Loading…
Cancel
Save