Commit 54288678 authored by Scott Frederick's avatar Scott Frederick

Configure Docker host in build plugins

This commit adds the ability to configure the Maven and Gradle
plugins to use a remote Docker daemon using build file
configuration, as an alternative to setting environment variables
to specify remote host connection details.

Fixes gh-23400
parent 1c6e37b2
......@@ -69,7 +69,7 @@ public class DockerApi {
* Create a new {@link DockerApi} instance.
*/
public DockerApi() {
this(DockerConfiguration.withDefaults());
this(new DockerConfiguration());
}
/**
......
......@@ -27,30 +27,42 @@ import org.springframework.util.Assert;
*/
public final class DockerConfiguration {
private final DockerHost host;
private final DockerRegistryAuthentication authentication;
private DockerConfiguration(DockerRegistryAuthentication authentication) {
public DockerConfiguration() {
this(null, null);
}
private DockerConfiguration(DockerHost host, DockerRegistryAuthentication authentication) {
this.host = host;
this.authentication = authentication;
}
public DockerHost getHost() {
return this.host;
}
public DockerRegistryAuthentication getRegistryAuthentication() {
return this.authentication;
}
public static DockerConfiguration withDefaults() {
return new DockerConfiguration(null);
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.authentication);
}
public static DockerConfiguration withRegistryTokenAuthentication(String token) {
public DockerConfiguration withRegistryTokenAuthentication(String token) {
Assert.notNull(token, "Token must not be null");
return new DockerConfiguration(new DockerRegistryTokenAuthentication(token));
return new DockerConfiguration(this.host, new DockerRegistryTokenAuthentication(token));
}
public static DockerConfiguration withRegistryUserAuthentication(String username, String password, String url,
public DockerConfiguration withRegistryUserAuthentication(String username, String password, String url,
String email) {
Assert.notNull(username, "Username must not be null");
Assert.notNull(password, "Password must not be null");
return new DockerConfiguration(new DockerRegistryUserAuthentication(username, password, url, email));
return new DockerConfiguration(this.host, new DockerRegistryUserAuthentication(username, password, url, email));
}
}
/*
* Copyright 2012-2020 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;
/**
* Docker host connection options.
*
* @author Scott Frederick
* @since 2.4.0
*/
public class DockerHost {
private final String address;
private final boolean secure;
private final String certificatePath;
protected DockerHost(String address, boolean secure, String certificatePath) {
this.address = address;
this.secure = secure;
this.certificatePath = certificatePath;
}
public String getAddress() {
return this.address;
}
public boolean isSecure() {
return this.secure;
}
public String getCertificatePath() {
return this.certificatePath;
}
}
......@@ -36,7 +36,6 @@ import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
......@@ -60,12 +59,13 @@ abstract class HttpClientTransport implements HttpTransport {
private final String registryAuthHeader;
protected HttpClientTransport(CloseableHttpClient client, HttpHost host, DockerConfiguration dockerConfiguration) {
protected HttpClientTransport(CloseableHttpClient client, HttpHost host,
DockerRegistryAuthentication authentication) {
Assert.notNull(client, "Client must not be null");
Assert.notNull(host, "Host must not be null");
this.client = client;
this.host = host;
this.registryAuthHeader = buildRegistryAuthHeader(dockerConfiguration);
this.registryAuthHeader = buildRegistryAuthHeader(authentication);
}
/**
......@@ -122,9 +122,7 @@ abstract class HttpClientTransport implements HttpTransport {
return execute(new HttpDelete(uri));
}
private String buildRegistryAuthHeader(DockerConfiguration dockerConfiguration) {
DockerRegistryAuthentication authentication = (dockerConfiguration != null)
? dockerConfiguration.getRegistryAuthentication() : null;
private String buildRegistryAuthHeader(DockerRegistryAuthentication authentication) {
String authHeader = (authentication != null) ? authentication.createAuthHeader() : null;
return (StringUtils.hasText(authHeader)) ? authHeader : null;
}
......
......@@ -85,7 +85,7 @@ public interface HttpTransport {
* @return a {@link HttpTransport} instance
*/
static HttpTransport create() {
return create(DockerConfiguration.withDefaults());
return create(new DockerConfiguration());
}
/**
......@@ -105,7 +105,7 @@ public interface HttpTransport {
* @return a {@link HttpTransport} instance
*/
static HttpTransport create(Environment environment) {
return create(environment, DockerConfiguration.withDefaults());
return create(environment, new DockerConfiguration());
}
/**
......
......@@ -39,6 +39,7 @@ import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
import org.springframework.boot.buildpack.platform.socket.DomainSocket;
import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket;
import org.springframework.boot.buildpack.platform.system.Environment;
......@@ -57,15 +58,16 @@ final class LocalHttpClientTransport extends HttpClientTransport {
private static final HttpHost LOCAL_DOCKER_HOST = HttpHost.create("docker://localhost");
private LocalHttpClientTransport(CloseableHttpClient client, DockerConfiguration dockerConfiguration) {
super(client, LOCAL_DOCKER_HOST, dockerConfiguration);
private LocalHttpClientTransport(CloseableHttpClient client, DockerRegistryAuthentication authentication) {
super(client, LOCAL_DOCKER_HOST, authentication);
}
static LocalHttpClientTransport create(Environment environment, DockerConfiguration dockerConfiguration) {
HttpClientBuilder builder = HttpClients.custom();
builder.setConnectionManager(new LocalConnectionManager(socketFilePath(environment)));
builder.setSchemePortResolver(new LocalSchemePortResolver());
return new LocalHttpClientTransport(builder.build(), dockerConfiguration);
return new LocalHttpClientTransport(builder.build(),
(dockerConfiguration != null) ? dockerConfiguration.getRegistryAuthentication() : null);
}
private static String socketFilePath(Environment environment) {
......
......@@ -29,6 +29,8 @@ import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
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.DockerRegistryAuthentication;
import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory;
import org.springframework.boot.buildpack.platform.system.Environment;
import org.springframework.util.Assert;
......@@ -50,8 +52,8 @@ final class RemoteHttpClientTransport extends HttpClientTransport {
private static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH";
private RemoteHttpClientTransport(CloseableHttpClient client, HttpHost host,
DockerConfiguration dockerConfiguration) {
super(client, host, dockerConfiguration);
DockerRegistryAuthentication authentication) {
super(client, host, authentication);
}
static RemoteHttpClientTransport createIfPossible(Environment environment,
......@@ -61,11 +63,11 @@ final class RemoteHttpClientTransport extends HttpClientTransport {
static RemoteHttpClientTransport createIfPossible(Environment environment, DockerConfiguration dockerConfiguration,
SslContextFactory sslContextFactory) {
String host = environment.get(DOCKER_HOST);
if (host == null || isLocalFileReference(host)) {
DockerHost host = getHost(environment, dockerConfiguration);
if (host == null || host.getAddress() == null || isLocalFileReference(host.getAddress())) {
return null;
}
return create(environment, sslContextFactory, HttpHost.create(host), dockerConfiguration);
return create(host, dockerConfiguration, sslContextFactory, HttpHost.create(host.getAddress()));
}
private static boolean isLocalFileReference(String host) {
......@@ -78,35 +80,53 @@ final class RemoteHttpClientTransport extends HttpClientTransport {
}
}
private static RemoteHttpClientTransport create(Environment environment, SslContextFactory sslContextFactory,
HttpHost tcpHost, DockerConfiguration dockerConfiguration) {
private static RemoteHttpClientTransport create(DockerHost host, DockerConfiguration dockerConfiguration,
SslContextFactory sslContextFactory, HttpHost tcpHost) {
HttpClientBuilder builder = HttpClients.custom();
boolean secure = isSecure(environment);
if (secure) {
builder.setSSLSocketFactory(getSecureConnectionSocketFactory(environment, sslContextFactory));
if (host.isSecure()) {
builder.setSSLSocketFactory(getSecureConnectionSocketFactory(host, sslContextFactory));
}
String scheme = secure ? "https" : "http";
String scheme = host.isSecure() ? "https" : "http";
HttpHost httpHost = new HttpHost(tcpHost.getHostName(), tcpHost.getPort(), scheme);
return new RemoteHttpClientTransport(builder.build(), httpHost, dockerConfiguration);
return new RemoteHttpClientTransport(builder.build(), httpHost,
(dockerConfiguration != null) ? dockerConfiguration.getRegistryAuthentication() : null);
}
private static LayeredConnectionSocketFactory getSecureConnectionSocketFactory(Environment environment,
private static LayeredConnectionSocketFactory getSecureConnectionSocketFactory(DockerHost host,
SslContextFactory sslContextFactory) {
String directory = environment.get(DOCKER_CERT_PATH);
String directory = host.getCertificatePath();
Assert.hasText(directory,
() -> DOCKER_TLS_VERIFY + " requires trust material location to be specified with " + DOCKER_CERT_PATH);
() -> "Docker host TLS verification requires trust material location to be specified with certificate path");
SSLContext sslContext = sslContextFactory.forDirectory(directory);
return new SSLConnectionSocketFactory(sslContext);
}
private static boolean isSecure(Environment environment) {
String secure = environment.get(DOCKER_TLS_VERIFY);
try {
return (secure != null) && (Integer.parseInt(secure) == 1);
private static DockerHost getHost(Environment environment, DockerConfiguration dockerConfiguration) {
if (environment.get(DOCKER_HOST) != null) {
return new EnvironmentDockerHost(environment);
}
catch (NumberFormatException ex) {
return false;
if (dockerConfiguration != null && dockerConfiguration.getHost() != null) {
return dockerConfiguration.getHost();
}
return null;
}
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;
}
}
}
}
......@@ -30,13 +30,13 @@ public class DockerConfigurationTests {
@Test
void createDockerConfigurationWithDefaults() {
DockerConfiguration configuration = DockerConfiguration.withDefaults();
DockerConfiguration configuration = new DockerConfiguration();
assertThat(configuration.getRegistryAuthentication()).isNull();
}
@Test
void createDockerConfigurationWithUserAuth() {
DockerConfiguration configuration = DockerConfiguration.withRegistryUserAuthentication("user", "secret",
DockerConfiguration configuration = new DockerConfiguration().withRegistryUserAuthentication("user", "secret",
"https://docker.example.com", "docker@example.com");
DockerRegistryAuthentication auth = configuration.getRegistryAuthentication();
assertThat(auth).isNotNull();
......@@ -50,7 +50,7 @@ public class DockerConfigurationTests {
@Test
void createDockerConfigurationWithTokenAuth() {
DockerConfiguration configuration = DockerConfiguration.withRegistryTokenAuthentication("token");
DockerConfiguration configuration = new DockerConfiguration().withRegistryTokenAuthentication("token");
DockerRegistryAuthentication auth = configuration.getRegistryAuthentication();
assertThat(auth).isNotNull();
assertThat(auth).isInstanceOf(DockerRegistryTokenAuthentication.class);
......
......@@ -239,8 +239,8 @@ class HttpClientTransportTests {
@Test
void getWithDockerRegistryUserAuthWillSendAuthHeader() throws IOException {
DockerConfiguration dockerConfiguration = DockerConfiguration.withRegistryUserAuthentication("user", "secret",
"https://docker.example.com", "docker@example.com");
DockerConfiguration dockerConfiguration = new DockerConfiguration().withRegistryUserAuthentication("user",
"secret", "https://docker.example.com", "docker@example.com");
this.http = new TestHttpClientTransport(this.client, dockerConfiguration);
givenClientWillReturnResponse();
given(this.entity.getContent()).willReturn(this.content);
......@@ -261,7 +261,7 @@ class HttpClientTransportTests {
@Test
void getWithDockerRegistryTokenAuthWillSendAuthHeader() throws IOException {
DockerConfiguration dockerConfiguration = DockerConfiguration.withRegistryTokenAuthentication("token");
DockerConfiguration dockerConfiguration = new DockerConfiguration().withRegistryTokenAuthentication("token");
this.http = new TestHttpClientTransport(this.client, dockerConfiguration);
givenClientWillReturnResponse();
given(this.entity.getContent()).willReturn(this.content);
......@@ -300,7 +300,7 @@ class HttpClientTransportTests {
}
protected TestHttpClientTransport(CloseableHttpClient client, DockerConfiguration dockerConfiguration) {
super(client, HttpHost.create("docker://localhost"), dockerConfiguration);
super(client, HttpHost.create("docker://localhost"), dockerConfiguration.getRegistryAuthentication());
}
}
......
......@@ -47,7 +47,7 @@ class RemoteHttpClientTransportTests {
private final Map<String, String> environment = new LinkedHashMap<>();
private final DockerConfiguration dockerConfiguration = DockerConfiguration.withDefaults();
private final DockerConfiguration dockerConfiguration = new DockerConfiguration();
@Test
void createIfPossibleWhenDockerHostIsNotSetReturnsNull() {
......@@ -57,7 +57,13 @@ class RemoteHttpClientTransportTests {
}
@Test
void createIfPossibleWhenDockerHostIsFileReturnsNull(@TempDir Path tempDir) throws IOException {
void createIfPossibleWithoutDockerConfigurationReturnsNull() {
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, null);
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);
......@@ -67,7 +73,16 @@ class RemoteHttpClientTransportTests {
}
@Test
void createIfPossibleWhenDockerHostIsAddressReturnsTransport() {
void createIfPossibleWhenDockerHostInConfigurationIsFileReturnsNull(@TempDir Path tempDir) throws IOException {
String dummySocketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath()
.toString();
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get,
this.dockerConfiguration.withHost(dummySocketFilePath, false, null));
assertThat(transport).isNull();
}
@Test
void createIfPossibleWhenDockerHostInEnvironmentIsAddressReturnsTransport() {
this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376");
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get,
this.dockerConfiguration);
......@@ -75,12 +90,27 @@ class RemoteHttpClientTransportTests {
}
@Test
void createIfPossibleWhenTlsVerifyWithMissingCertPathThrowsException() {
void createIfPossibleWhenDockerHostInConfigurationIsAddressReturnsTransport() {
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get,
this.dockerConfiguration.withHost("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, this.dockerConfiguration))
.withMessageContaining("DOCKER_CERT_PATH");
.withMessageContaining("Docker host TLS verification requires trust material");
}
@Test
void createIfPossibleWhenTlsVerifyInConfigurationWithMissingCertPathThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RemoteHttpClientTransport.createIfPossible(this.environment::get,
this.dockerConfiguration.withHost("tcp://192.168.1.2:2376", true, null)))
.withMessageContaining("Docker host TLS verification requires trust material");
}
@Test
......@@ -92,7 +122,7 @@ class RemoteHttpClientTransportTests {
}
@Test
void createIfPossibleWhenTlsVerifyUsesHttps() throws Exception {
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");
......@@ -103,11 +133,21 @@ class RemoteHttpClientTransportTests {
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"),
sslContextFactory);
assertThat(transport.getHost()).satisfies(hostOf("https", "192.168.1.2", 2376));
}
@Test
void createIfPossibleWithDockerConfigurationUserAuthReturnsTransport() {
this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376");
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get,
DockerConfiguration.withRegistryUserAuthentication("user", "secret", "http://docker.example.com",
new DockerConfiguration().withRegistryUserAuthentication("user", "secret", "http://docker.example.com",
"docker@example.com"));
assertThat(transport).isNotNull();
}
......
......@@ -34,6 +34,24 @@ The following table shows the environment variables and their values:
On Linux and macOS, these environment variables can be set using the command `eval $(minikube docker-env)` after minikube has been started.
Docker daemon connection information can also be provided using `docker` properties in the plugin configuration.
The following table summarizes the available properties:
|===
| Property | Description
| `host`
| URL containing the host and port for the Docker daemon - e.g. `tcp://192.168.99.100:2376`
| `tlsVerify`
| Enable secure HTTPS protocol when set to `true` (optional)
| `certPath`
| Path to certificate and key files for HTTPS (required if `tlsVerify` is `true`, ignored otherwise)
|===
For more details, see also <<build-image-example-docker,examples>>.
[[build-image-docker-registry]]
......@@ -220,7 +238,21 @@ The image name can be specified on the command line as well, as shown in this ex
[[build-image-example-docker]]
==== Docker Configuration
If the builder or run image are stored in a private Docker registry that supports user authentication, authentication details can be provided as shown in the following example:
If you need the plugin to communicate with the Docker daemon using a remote connection instead of the default local connection, the connection details can be provided using `docker` properties as shown in the following example:
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
.Groovy
----
include::../gradle/packaging/boot-build-image-docker-host.gradle[tags=docker-host]
----
[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"]
.Kotlin
----
include::../gradle/packaging/boot-build-image-docker-host.gradle.kts[tags=docker-host]
----
If the builder or run image are stored in a private Docker registry that supports user authentication, authentication details can be provided using `docker.registry` properties as shown in the following example:
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
.Groovy
......@@ -234,7 +266,7 @@ include::../gradle/packaging/boot-build-image-docker-auth-user.gradle[tags=docke
include::../gradle/packaging/boot-build-image-docker-auth-user.gradle.kts[tags=docker-auth-user]
----
If the builder or run image is stored in a private Docker registry that supports token authentication, the token value can be provided as shown in the following example:
If the builder or run image is stored in a private Docker registry that supports token authentication, the token value can be provided using `docker.registry` as shown in the following example:
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
.Groovy
......
plugins {
id 'java'
id 'org.springframework.boot' version '{gradle-project-version}'
}
bootJar {
mainClassName 'com.example.ExampleApplication'
}
// tag::docker-host[]
bootBuildImage {
docker {
host = "tcp://192.168.99.100:2376"
tlsVerify = true
certPath = "/home/users/.minikube/certs"
}
}
// end::docker-host[]
import org.springframework.boot.gradle.tasks.bundling.BootJar
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
plugins {
java
id("org.springframework.boot") version "{gradle-project-version}"
}
tasks.getByName<BootJar>("bootJar") {
mainClassName = "com.example.ExampleApplication"
}
// tag::docker-host[]
tasks.getByName<BootBuildImage>("bootBuildImage") {
docker {
host = "tcp://192.168.99.100:2376"
tlsVerify = true
certPath = "/home/users/.minikube/certs"
}
}
// end::docker-host[]
......@@ -35,6 +35,12 @@ import org.springframework.boot.buildpack.platform.docker.configuration.DockerCo
*/
public class DockerSpec {
private String host;
private boolean tlsVerify;
private String certPath;
private final DockerRegistrySpec registry;
public DockerSpec() {
......@@ -45,6 +51,36 @@ public class DockerSpec {
this.registry = registry;
}
@Input
@Optional
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
@Input
@Optional
public Boolean isTlsVerify() {
return this.tlsVerify;
}
public void setTlsVerify(boolean tlsVerify) {
this.tlsVerify = tlsVerify;
}
@Input
@Optional
public String getCertPath() {
return this.certPath;
}
public void setCertPath(String certPath) {
this.certPath = certPath;
}
/**
* Returns the {@link DockerRegistrySpec} that configures registry authentication.
* @return the registry spec
......@@ -77,14 +113,28 @@ public class DockerSpec {
* @return the Docker configuration
*/
DockerConfiguration asDockerConfiguration() {
DockerConfiguration dockerConfiguration = new DockerConfiguration();
dockerConfiguration = customizeHost(dockerConfiguration);
dockerConfiguration = customizeAuthentication(dockerConfiguration);
return dockerConfiguration;
}
private DockerConfiguration customizeHost(DockerConfiguration dockerConfiguration) {
if (this.host != null) {
return dockerConfiguration.withHost(this.host, this.tlsVerify, this.certPath);
}
return dockerConfiguration;
}
private DockerConfiguration customizeAuthentication(DockerConfiguration dockerConfiguration) {
if (this.registry == null || this.registry.hasEmptyAuth()) {
return null;
return dockerConfiguration;
}
if (this.registry.hasTokenAuth() && !this.registry.hasUserAuth()) {
return DockerConfiguration.withRegistryTokenAuthentication(this.registry.getToken());
return dockerConfiguration.withRegistryTokenAuthentication(this.registry.getToken());
}
if (this.registry.hasUserAuth() && !this.registry.hasTokenAuth()) {
return DockerConfiguration.withRegistryUserAuthentication(this.registry.getUsername(),
return dockerConfiguration.withRegistryUserAuthentication(this.registry.getUsername(),
this.registry.getPassword(), this.registry.getUrl(), this.registry.getEmail());
}
throw new GradleException(
......
......@@ -20,6 +20,7 @@ import org.gradle.api.GradleException;
import org.junit.jupiter.api.Test;
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.DockerRegistryAuthentication;
import org.springframework.util.Base64Utils;
......@@ -35,15 +36,36 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
public class DockerSpecTests {
@Test
void asDockerConfigurationWithoutRegistry() {
void asDockerConfigurationWithDefaults() {
DockerSpec dockerSpec = new DockerSpec();
assertThat(dockerSpec.asDockerConfiguration()).isNull();
assertThat(dockerSpec.asDockerConfiguration().getHost()).isNull();
assertThat(dockerSpec.asDockerConfiguration().getRegistryAuthentication()).isNull();
}
@Test
void asDockerConfigurationWithEmptyRegistry() {
DockerSpec dockerSpec = new DockerSpec(new DockerSpec.DockerRegistrySpec());
assertThat(dockerSpec.asDockerConfiguration()).isNull();
void asDockerConfigurationWithHostConfiguration() {
DockerSpec dockerSpec = new DockerSpec();
dockerSpec.setHost("docker.example.com");
dockerSpec.setTlsVerify(true);
dockerSpec.setCertPath("/tmp/ca-cert");
DockerConfiguration dockerConfiguration = dockerSpec.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(dockerSpec.asDockerConfiguration().getRegistryAuthentication()).isNull();
}
@Test
void asDockerConfigurationWithHostConfigurationNoTlsVerify() {
DockerSpec dockerSpec = new DockerSpec();
dockerSpec.setHost("docker.example.com");
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(dockerSpec.asDockerConfiguration().getRegistryAuthentication()).isNull();
}
@Test
......@@ -61,6 +83,7 @@ public class DockerSpecTests {
.contains("\"username\" : \"user\"").contains("\"password\" : \"secret\"")
.contains("\"email\" : \"docker@example.com\"")
.contains("\"serveraddress\" : \"https://docker.example.com\"");
assertThat(dockerSpec.asDockerConfiguration().getHost()).isNull();
}
@Test
......
......@@ -57,6 +57,24 @@ The following table shows the environment variables and their values:
On Linux and macOS, these environment variables can be set using the command `eval $(minikube docker-env)` after minikube has been started.
Docker daemon connection information can also be provided using `docker` parameters in the plugin configuration.
The following table summarizes the available parameters:
|===
| Parameter | Description
| `host`
| URL containing the host and port for the Docker daemon - e.g. `tcp://192.168.99.100:2376`
| `tlsVerify`
| Enable secure HTTPS protocol when set to `true` (optional)
| `certPath`
| Path to certificate and key files for HTTPS (required if `tlsVerify` is `true`, ignored otherwise)
|===
For more details, see also <<build-image-example-docker,examples>>.
[[build-image-docker-registry]]
......@@ -287,7 +305,31 @@ The image name can be specified on the command line as well, as shown in this ex
[[build-image-example-docker]]
==== Docker Configuration
If the builder or run image are stored in a private Docker registry that supports user authentication, authentication details can be provided as shown in the following example:
If you need the plugin to communicate with the Docker daemon using a remote connection instead of the default local connection, the connection details can be provided using `docker` parameters as shown in the following example:
[source,xml,indent=0,subs="verbatim,attributes"]
----
<project>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>{gradle-project-version}</version>
<configuration>
<docker>
<host>tcp://192.168.99.100:2376</host>
<tlsVerify>true</tlsVerify>
<certPath>/home/user/.minikube/certs</certPath>
</docker>
</configuration>
</plugin>
</plugins>
</build>
</project>
----
If the builder or run image are stored in a private Docker registry that supports user authentication, authentication details can be provided using `docker.registry` parameters as shown in the following example:
[source,xml,indent=0,subs="verbatim,attributes"]
----
......@@ -314,7 +356,7 @@ If the builder or run image are stored in a private Docker registry that support
</project>
----
If the builder or run image is stored in a private Docker registry that supports token authentication, the token value can be provided as shown in the following example:
If the builder or run image is stored in a private Docker registry that supports token authentication, the token value can be provided using `docker.registry` parameters as shown in the following example:
[source,xml,indent=0,subs="verbatim,attributes"]
----
......
......@@ -27,8 +27,38 @@ import org.springframework.boot.buildpack.platform.docker.configuration.DockerCo
*/
public class Docker {
private String host;
private boolean tlsVerify;
private String certPath;
private DockerRegistry registry;
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
public boolean isTlsVerify() {
return this.tlsVerify;
}
public void setTlsVerify(boolean tlsVerify) {
this.tlsVerify = tlsVerify;
}
public String getCertPath() {
return this.certPath;
}
public void setCertPath(String certPath) {
this.certPath = certPath;
}
/**
* Sets the {@link DockerRegistry} that configures registry authentication.
* @param registry the registry configuration
......@@ -44,17 +74,30 @@ public class Docker {
* @return the Docker configuration
*/
DockerConfiguration asDockerConfiguration() {
DockerConfiguration dockerConfiguration = new DockerConfiguration();
dockerConfiguration = customizeHost(dockerConfiguration);
dockerConfiguration = customizeAuthentication(dockerConfiguration);
return dockerConfiguration;
}
private DockerConfiguration customizeHost(DockerConfiguration dockerConfiguration) {
if (this.host != null) {
return dockerConfiguration.withHost(this.host, this.tlsVerify, this.certPath);
}
return dockerConfiguration;
}
private DockerConfiguration customizeAuthentication(DockerConfiguration dockerConfiguration) {
if (this.registry == null || this.registry.isEmpty()) {
return null;
return dockerConfiguration;
}
if (this.registry.hasTokenAuth() && !this.registry.hasUserAuth()) {
return DockerConfiguration.withRegistryTokenAuthentication(this.registry.getToken());
return dockerConfiguration.withRegistryTokenAuthentication(this.registry.getToken());
}
if (this.registry.hasUserAuth() && !this.registry.hasTokenAuth()) {
return DockerConfiguration.withRegistryUserAuthentication(this.registry.getUsername(),
return dockerConfiguration.withRegistryUserAuthentication(this.registry.getUsername(),
this.registry.getPassword(), this.registry.getUrl(), this.registry.getEmail());
}
throw new IllegalArgumentException(
"Invalid Docker registry configuration, either token or username/password must be provided");
}
......
......@@ -19,6 +19,7 @@ package org.springframework.boot.maven;
import org.junit.jupiter.api.Test;
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.DockerRegistryAuthentication;
import org.springframework.util.Base64Utils;
......@@ -34,17 +35,24 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
public class DockerTests {
@Test
void asDockerConfigurationWithoutRegistry() {
void asDockerConfigurationWithDefaults() {
Docker docker = new Docker();
assertThat(docker.asDockerConfiguration()).isNull();
assertThat(docker.asDockerConfiguration().getHost()).isNull();
assertThat(docker.asDockerConfiguration().getRegistryAuthentication()).isNull();
}
@Test
void asDockerConfigurationWithEmptyRegistry() {
Docker.DockerRegistry dockerRegistry = new Docker.DockerRegistry();
void asDockerConfigurationWithHostConfiguration() {
Docker docker = new Docker();
docker.setRegistry(dockerRegistry);
assertThat(docker.asDockerConfiguration()).isNull();
docker.setHost("docker.example.com");
docker.setTlsVerify(true);
docker.setCertPath("/tmp/ca-cert");
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(docker.asDockerConfiguration().getRegistryAuthentication()).isNull();
}
@Test
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment