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
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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[]
|
||||
@@ -0,0 +1,21 @@
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user