diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java index de06b36523..06f017011e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java @@ -24,6 +24,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener; import org.springframework.boot.buildpack.platform.docker.UpdateListener; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; 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; @@ -48,10 +49,18 @@ public class Builder { this(BuildLog.toSystemOut()); } + public Builder(DockerConfiguration dockerConfiguration) { + this(BuildLog.toSystemOut(), dockerConfiguration); + } + public Builder(BuildLog log) { this(log, new DockerApi()); } + public Builder(BuildLog log, DockerConfiguration dockerConfiguration) { + this(log, new DockerApi(dockerConfiguration)); + } + Builder(BuildLog log, DockerApi docker) { Assert.notNull(log, "Log must not be null"); this.log = log; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java index 7e9aa49776..a606819ab4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java @@ -26,6 +26,7 @@ import java.util.List; import org.apache.http.client.utils.URIBuilder; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; 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; @@ -68,7 +69,15 @@ public class DockerApi { * Create a new {@link DockerApi} instance. */ public DockerApi() { - this(HttpTransport.create()); + this(DockerConfiguration.withDefaults()); + } + + /** + * Create a new {@link DockerApi} instance. + * @param dockerConfiguration the Docker configuration options + */ + public DockerApi(DockerConfiguration dockerConfiguration) { + this(HttpTransport.create(dockerConfiguration)); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java new file mode 100644 index 0000000000..134910537e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java @@ -0,0 +1,56 @@ +/* + * 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; + +import org.springframework.util.Assert; + +/** + * Docker configuration options. + * + * @author Wei Jiang + * @author Scott Frederick + * @since 2.4.0 + */ +public final class DockerConfiguration { + + private final DockerRegistryAuthentication authentication; + + private DockerConfiguration(DockerRegistryAuthentication authentication) { + this.authentication = authentication; + } + + public DockerRegistryAuthentication getRegistryAuthentication() { + return this.authentication; + } + + public static DockerConfiguration withDefaults() { + return new DockerConfiguration(null); + } + + public static DockerConfiguration withRegistryTokenAuthentication(String token) { + Assert.notNull(token, "Token must not be null"); + return new DockerConfiguration(new DockerRegistryTokenAuthentication(token)); + } + + public static 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)); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java new file mode 100644 index 0000000000..cdaaf1962f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java @@ -0,0 +1,41 @@ +/* + * 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; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; +import org.springframework.util.Base64Utils; + +/** + * Docker registry authentication configuration. + * + * @author Scott Frederick + * @since 2.4.0 + */ +public abstract class DockerRegistryAuthentication { + + public String createAuthHeader() { + try { + return Base64Utils.encodeToUrlSafeString(SharedObjectMapper.get().writeValueAsBytes(this)); + } + catch (JsonProcessingException ex) { + throw new IllegalStateException("Error creating Docker registry authentication header", ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthentication.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthentication.java new file mode 100644 index 0000000000..757b7eb75c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthentication.java @@ -0,0 +1,39 @@ +/* + * 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; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Docker registry authentication configuration using a token. + * + * @author Scott Frederick + */ +class DockerRegistryTokenAuthentication extends DockerRegistryAuthentication { + + @JsonProperty("identitytoken") + private final String token; + + DockerRegistryTokenAuthentication(String token) { + this.token = token; + } + + String getToken() { + return this.token; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthentication.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthentication.java new file mode 100644 index 0000000000..d933e8b794 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthentication.java @@ -0,0 +1,63 @@ +/* + * 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; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Docker registry authentication configuration using user credentials. + * + * @author Scott Frederick + */ +class DockerRegistryUserAuthentication extends DockerRegistryAuthentication { + + @JsonProperty + private final String username; + + @JsonProperty + private final String password; + + @JsonProperty("serveraddress") + private final String url; + + @JsonProperty + private final String email; + + DockerRegistryUserAuthentication(String username, String password, String url, String email) { + this.username = username; + this.password = password; + this.url = url; + this.email = email; + } + + String getUsername() { + return this.username; + } + + String getPassword() { + return this.password; + } + + String getUrl() { + return this.url; + } + + String getEmail() { + return this.email; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/package-info.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/package-info.java new file mode 100644 index 0000000000..d5c3fe8a3c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Docker configuration options. + */ +package org.springframework.boot.buildpack.platform.docker.configuration; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java index 20395f41b6..67563ecff3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java @@ -36,10 +36,12 @@ 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.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Abstract base class for {@link HttpTransport} implementations backed by a @@ -55,11 +57,14 @@ abstract class HttpClientTransport implements HttpTransport { private final HttpHost host; - protected HttpClientTransport(CloseableHttpClient client, HttpHost host) { + private final String registryAuthHeader; + + protected HttpClientTransport(CloseableHttpClient client, HttpHost host, DockerConfiguration dockerConfiguration) { 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); } /** @@ -116,6 +121,15 @@ abstract class HttpClientTransport implements HttpTransport { return execute(new HttpDelete(uri)); } + private String buildRegistryAuthHeader(DockerConfiguration dockerConfiguration) { + if (dockerConfiguration == null || dockerConfiguration.getRegistryAuthentication() == null) { + return null; + } + + String authHeader = dockerConfiguration.getRegistryAuthentication().createAuthHeader(); + return (StringUtils.hasText(authHeader)) ? authHeader : null; + } + private Response execute(HttpEntityEnclosingRequestBase request, String contentType, IOConsumer writer) { request.setHeader(HttpHeaders.CONTENT_TYPE, contentType); @@ -125,6 +139,9 @@ abstract class HttpClientTransport implements HttpTransport { private Response execute(HttpUriRequest request) { try { + if (this.registryAuthHeader != null) { + request.addHeader("X-Registry-Auth", this.registryAuthHeader); + } CloseableHttpResponse response = this.client.execute(this.host, request); StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java index 07b581e426..d3669526e2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java @@ -22,6 +22,7 @@ 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.io.IOConsumer; import org.springframework.boot.buildpack.platform.system.Environment; @@ -84,7 +85,17 @@ public interface HttpTransport { * @return a {@link HttpTransport} instance */ static HttpTransport create() { - return create(Environment.SYSTEM); + return create(DockerConfiguration.withDefaults()); + } + + /** + * Create the most suitable {@link HttpTransport} based on the + * {@link Environment#SYSTEM system environment}. + * @param dockerConfiguration the Docker engine configuration + * @return a {@link HttpTransport} instance + */ + static HttpTransport create(DockerConfiguration dockerConfiguration) { + return create(Environment.SYSTEM, dockerConfiguration); } /** @@ -94,8 +105,19 @@ public interface HttpTransport { * @return a {@link HttpTransport} instance */ static HttpTransport create(Environment environment) { - HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment); - return (remote != null) ? remote : LocalHttpClientTransport.create(environment); + return create(environment, DockerConfiguration.withDefaults()); + } + + /** + * Create the most suitable {@link HttpTransport} based on the given + * {@link Environment} and {@link DockerConfiguration}. + * @param environment the source environment + * @param dockerConfiguration the Docker engine configuration + * @return a {@link HttpTransport} instance + */ + static HttpTransport create(Environment environment, DockerConfiguration dockerConfiguration) { + HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment, dockerConfiguration); + return (remote != null) ? remote : LocalHttpClientTransport.create(environment, dockerConfiguration); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java index 9861f4d45a..d6f693c6b0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java @@ -38,6 +38,7 @@ 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.DockerConfiguration; import org.springframework.boot.buildpack.platform.socket.DomainSocket; import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket; import org.springframework.boot.buildpack.platform.system.Environment; @@ -56,15 +57,15 @@ final class LocalHttpClientTransport extends HttpClientTransport { private static final HttpHost LOCAL_DOCKER_HOST = HttpHost.create("docker://localhost"); - private LocalHttpClientTransport(CloseableHttpClient client) { - super(client, LOCAL_DOCKER_HOST); + private LocalHttpClientTransport(CloseableHttpClient client, DockerConfiguration dockerConfiguration) { + super(client, LOCAL_DOCKER_HOST, dockerConfiguration); } - static LocalHttpClientTransport create(Environment environment) { + 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()); + return new LocalHttpClientTransport(builder.build(), dockerConfiguration); } private static String socketFilePath(Environment environment) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java index 1b4df90b38..af83e9a3d2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java @@ -28,6 +28,7 @@ import org.apache.http.impl.client.CloseableHttpClient; 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.ssl.SslContextFactory; import org.springframework.boot.buildpack.platform.system.Environment; import org.springframework.util.Assert; @@ -48,20 +49,23 @@ final class RemoteHttpClientTransport extends HttpClientTransport { private static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH"; - private RemoteHttpClientTransport(CloseableHttpClient client, HttpHost host) { - super(client, host); + private RemoteHttpClientTransport(CloseableHttpClient client, HttpHost host, + DockerConfiguration dockerConfiguration) { + super(client, host, dockerConfiguration); } - static RemoteHttpClientTransport createIfPossible(Environment environment) { - return createIfPossible(environment, new SslContextFactory()); + static RemoteHttpClientTransport createIfPossible(Environment environment, + DockerConfiguration dockerConfiguration) { + return createIfPossible(environment, dockerConfiguration, new SslContextFactory()); } - static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) { + static RemoteHttpClientTransport createIfPossible(Environment environment, DockerConfiguration dockerConfiguration, + SslContextFactory sslContextFactory) { String host = environment.get(DOCKER_HOST); if (host == null || isLocalFileReference(host)) { return null; } - return create(environment, sslContextFactory, HttpHost.create(host)); + return create(environment, sslContextFactory, HttpHost.create(host), dockerConfiguration); } private static boolean isLocalFileReference(String host) { @@ -75,7 +79,7 @@ final class RemoteHttpClientTransport extends HttpClientTransport { } private static RemoteHttpClientTransport create(Environment environment, SslContextFactory sslContextFactory, - HttpHost tcpHost) { + HttpHost tcpHost, DockerConfiguration dockerConfiguration) { HttpClientBuilder builder = HttpClients.custom(); boolean secure = isSecure(environment); if (secure) { @@ -83,7 +87,7 @@ final class RemoteHttpClientTransport extends HttpClientTransport { } String scheme = secure ? "https" : "http"; HttpHost httpHost = new HttpHost(tcpHost.getHostName(), tcpHost.getPort(), scheme); - return new RemoteHttpClientTransport(builder.build(), httpHost); + return new RemoteHttpClientTransport(builder.build(), httpHost, dockerConfiguration); } private static LayeredConnectionSocketFactory getSecureConnectionSocketFactory(Environment environment, diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java index dd9669f0ee..3d8028d9e7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java @@ -60,7 +60,14 @@ class BuilderTests { @Test void createWhenLogIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> new Builder(null)).withMessage("Log must not be null"); + assertThatIllegalArgumentException().isThrownBy(() -> new Builder((BuildLog) null)) + .withMessage("Log must not be null"); + } + + @Test + void createWithDockerConfiguration() { + Builder builder = new Builder(BuildLog.toSystemOut()); + assertThat(builder).isNotNull(); } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java index 66a3bd09f2..f2b9199362 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java @@ -113,6 +113,12 @@ class DockerApiTests { }; } + @Test + void createDockerApi() { + DockerApi api = new DockerApi(); + assertThat(api).isNotNull(); + } + @Nested class ImageDockerApiTests { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationTests.java new file mode 100644 index 0000000000..2786750810 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationTests.java @@ -0,0 +1,61 @@ +/* + * 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; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DockerConfiguration}. + * + * @author Wei Jiang + * @author Scott Frederick + */ +public class DockerConfigurationTests { + + @Test + void createDockerConfigurationWithDefaults() { + DockerConfiguration configuration = DockerConfiguration.withDefaults(); + assertThat(configuration.getRegistryAuthentication()).isNull(); + } + + @Test + void createDockerConfigurationWithUserAuth() { + DockerConfiguration configuration = DockerConfiguration.withRegistryUserAuthentication("user", "secret", + "https://docker.example.com", "docker@example.com"); + DockerRegistryAuthentication auth = configuration.getRegistryAuthentication(); + assertThat(auth).isNotNull(); + assertThat(auth).isInstanceOf(DockerRegistryUserAuthentication.class); + DockerRegistryUserAuthentication userAuth = (DockerRegistryUserAuthentication) auth; + assertThat(userAuth.getUrl()).isEqualTo("https://docker.example.com"); + assertThat(userAuth.getUsername()).isEqualTo("user"); + assertThat(userAuth.getPassword()).isEqualTo("secret"); + assertThat(userAuth.getEmail()).isEqualTo("docker@example.com"); + } + + @Test + void createDockerConfigurationWithTokenAuth() { + DockerConfiguration configuration = DockerConfiguration.withRegistryTokenAuthentication("token"); + DockerRegistryAuthentication auth = configuration.getRegistryAuthentication(); + assertThat(auth).isNotNull(); + assertThat(auth).isInstanceOf(DockerRegistryTokenAuthentication.class); + DockerRegistryTokenAuthentication tokenAuth = (DockerRegistryTokenAuthentication) auth; + assertThat(tokenAuth.getToken()).isEqualTo("token"); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthenticationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthenticationTests.java new file mode 100644 index 0000000000..9b3bcb76aa --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthenticationTests.java @@ -0,0 +1,43 @@ +/* + * 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; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; +import org.springframework.util.Base64Utils; +import org.springframework.util.StreamUtils; + +/** + * Tests for {@link DockerRegistryTokenAuthentication}. + */ +class DockerRegistryTokenAuthenticationTests extends AbstractJsonTests { + + @Test + void createAuthHeaderReturnsEncodedHeader() throws IOException, JSONException { + DockerRegistryTokenAuthentication auth = new DockerRegistryTokenAuthentication("tokenvalue"); + String header = auth.createAuthHeader(); + String expectedJson = StreamUtils.copyToString(getContent("auth-token.json"), StandardCharsets.UTF_8); + JSONAssert.assertEquals(expectedJson, new String(Base64Utils.decodeFromUrlSafeString(header)), false); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthenticationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthenticationTests.java new file mode 100644 index 0000000000..aa1c46f8ee --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthenticationTests.java @@ -0,0 +1,56 @@ +/* + * 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; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; +import org.springframework.util.Base64Utils; +import org.springframework.util.StreamUtils; + +/** + * Tests for {@link DockerRegistryUserAuthentication}. + */ +class DockerRegistryUserAuthenticationTests extends AbstractJsonTests { + + @Test + void createMinimalAuthHeaderReturnsEncodedHeader() throws IOException, JSONException { + DockerRegistryUserAuthentication auth = new DockerRegistryUserAuthentication("user", "secret", + "https://docker.example.com", "docker@example.com"); + JSONAssert.assertEquals(jsonContent("auth-user-full.json"), decoded(auth.createAuthHeader()), false); + } + + @Test + void createFullAuthHeaderReturnsEncodedHeader() throws IOException, JSONException { + DockerRegistryUserAuthentication auth = new DockerRegistryUserAuthentication("user", "secret", null, null); + JSONAssert.assertEquals(jsonContent("auth-user-minimal.json"), decoded(auth.createAuthHeader()), false); + } + + private String jsonContent(String s) throws IOException { + return StreamUtils.copyToString(getContent(s), StandardCharsets.UTF_8); + } + + private String decoded(String header) { + return new String(Base64Utils.decodeFromUrlSafeString(header)); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransportTests.java index c4d7173c1f..0400b4ae46 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransportTests.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; +import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHeaders; @@ -43,7 +44,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response; +import org.springframework.util.Base64Utils; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -234,6 +237,47 @@ class HttpClientTransportTests { .satisfies((ex) -> assertThat(ex.getMessage()).contains("test IO exception")); } + @Test + void getWithDockerRegistryUserAuthWillSendAuthHeader() throws IOException { + DockerConfiguration dockerConfiguration = 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); + given(this.statusLine.getStatusCode()).willReturn(200); + Response response = this.http.get(this.uri); + verify(this.client).execute(this.hostCaptor.capture(), this.requestCaptor.capture()); + HttpUriRequest request = this.requestCaptor.getValue(); + assertThat(request).isInstanceOf(HttpGet.class); + assertThat(request.getURI()).isEqualTo(this.uri); + Header[] registryAuthHeaders = request.getHeaders("X-Registry-Auth"); + assertThat(registryAuthHeaders).isNotNull(); + assertThat(new String(Base64Utils.decodeFromString(registryAuthHeaders[0].getValue()))) + .contains("\"username\" : \"user\"").contains("\"password\" : \"secret\"") + .contains("\"email\" : \"docker@example.com\"") + .contains("\"serveraddress\" : \"https://docker.example.com\""); + assertThat(response.getContent()).isSameAs(this.content); + } + + @Test + void getWithDockerRegistryTokenAuthWillSendAuthHeader() throws IOException { + DockerConfiguration dockerConfiguration = DockerConfiguration.withRegistryTokenAuthentication("token"); + this.http = new TestHttpClientTransport(this.client, dockerConfiguration); + givenClientWillReturnResponse(); + given(this.entity.getContent()).willReturn(this.content); + given(this.statusLine.getStatusCode()).willReturn(200); + Response response = this.http.get(this.uri); + verify(this.client).execute(this.hostCaptor.capture(), this.requestCaptor.capture()); + HttpUriRequest request = this.requestCaptor.getValue(); + assertThat(request).isInstanceOf(HttpGet.class); + assertThat(request.getURI()).isEqualTo(this.uri); + Header[] registryAuthHeaders = request.getHeaders("X-Registry-Auth"); + assertThat(registryAuthHeaders).isNotNull(); + assertThat(new String(Base64Utils.decodeFromString(registryAuthHeaders[0].getValue()))) + .contains("\"identitytoken\" : \"token\""); + assertThat(response.getContent()).isSameAs(this.content); + } + private String writeToString(HttpEntity entity) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); entity.writeTo(out); @@ -252,7 +296,11 @@ class HttpClientTransportTests { static class TestHttpClientTransport extends HttpClientTransport { protected TestHttpClientTransport(CloseableHttpClient client) { - super(client, HttpHost.create("docker://localhost")); + super(client, HttpHost.create("docker://localhost"), null); + } + + protected TestHttpClientTransport(CloseableHttpClient client, DockerConfiguration dockerConfiguration) { + super(client, HttpHost.create("docker://localhost"), dockerConfiguration); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java index 784fa717c3..5939957768 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java @@ -29,6 +29,7 @@ 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.ssl.SslContextFactory; import static org.assertj.core.api.Assertions.assertThat; @@ -46,9 +47,12 @@ class RemoteHttpClientTransportTests { private final Map environment = new LinkedHashMap<>(); + private final DockerConfiguration dockerConfiguration = DockerConfiguration.withDefaults(); + @Test void createIfPossibleWhenDockerHostIsNotSetReturnsNull() { - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, + this.dockerConfiguration); assertThat(transport).isNull(); } @@ -57,14 +61,16 @@ class RemoteHttpClientTransportTests { String dummySocketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath() .toString(); this.environment.put("DOCKER_HOST", dummySocketFilePath); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, + this.dockerConfiguration); assertThat(transport).isNull(); } @Test void createIfPossibleWhenDockerHostIsAddressReturnsTransport() { this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, + this.dockerConfiguration); assertThat(transport).isNotNull(); } @@ -72,15 +78,16 @@ class RemoteHttpClientTransportTests { void createIfPossibleWhenTlsVerifyWithMissingCertPathThrowsException() { 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)) + assertThatIllegalArgumentException().isThrownBy( + () -> RemoteHttpClientTransport.createIfPossible(this.environment::get, this.dockerConfiguration)) .withMessageContaining("DOCKER_CERT_PATH"); } @Test void createIfPossibleWhenNoTlsVerifyUsesHttp() { this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); - RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, + this.dockerConfiguration); assertThat(transport.getHost()).satisfies(hostOf("http", "192.168.1.2", 2376)); } @@ -92,10 +99,19 @@ class RemoteHttpClientTransportTests { SslContextFactory sslContextFactory = mock(SslContextFactory.class); given(sslContextFactory.forDirectory("/test-cert-path")).willReturn(SSLContext.getDefault()); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, - sslContextFactory); + this.dockerConfiguration, 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", + "docker@example.com")); + assertThat(transport).isNotNull(); + } + private Consumer hostOf(String scheme, String hostName, int port) { return (host) -> { assertThat(host).isNotNull(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-token.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-token.json new file mode 100644 index 0000000000..32fe9c70bc --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-token.json @@ -0,0 +1,3 @@ +{ + "identitytoken": "tokenvalue" +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-full.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-full.json new file mode 100644 index 0000000000..a3e615deb6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-full.json @@ -0,0 +1,6 @@ +{ + "username": "user", + "password": "secret", + "email": "docker@example.com", + "serveraddress": "https://docker.example.com" +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-minimal.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-minimal.json new file mode 100644 index 0000000000..7f637981f2 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-minimal.json @@ -0,0 +1,4 @@ +{ + "username": "user", + "password": "secret" +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc index 25090ae865..79bc1ab080 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -36,6 +36,37 @@ On Linux and macOS, these environment variables can be set using the command `ev +[[build-image-docker-registry]] +=== Docker Registry +If the Docker images specified by the `builder` or `runImage` parameters are stored in a private Docker image registry that requires authentication, the authentication credentials can be provided using `docker.registry` properties. +Properties are provided for user authentication or identity token authentication. +Consult the documentation for the Docker registry being used to store builder or run images for further information on supported authentication methods. + +The following table summarizes the available properties: + +|=== +| Property | Description + +| `username` +| Username for the Docker image registry user. Required for user authentication. + +| `password` +| Password for the Docker image registry user. Required for user authentication. + +| `url` +| Address of the Docker image registry. Optional for user authentication. + +| `email` +| E-mail address for the Docker image registry user. Optional for user authentication. + +| `token` +| Identity token for the Docker image registry user. Required for token authentication. +|=== + +For more details, see also <>. + + + [[build-image-customization]] === Image Customizations The plugin invokes a {buildpacks-reference}/concepts/components/builder/[builder] to orchestrate the generation of an image. @@ -186,3 +217,33 @@ The image name can be specified on the command line as well, as shown in this ex ---- $ gradle bootBuildImage --imageName=example.com/library/my-app:v1 ---- + +[[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: + +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.Groovy +---- +include::../gradle/packaging/boot-build-image-docker-auth-user.gradle[tags=docker-auth-user] +---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.Kotlin +---- +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: + +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.Groovy +---- +include::../gradle/packaging/boot-build-image-docker-auth-token.gradle[tags=docker-auth-token] +---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.Kotlin +---- +include::../gradle/packaging/boot-build-image-docker-auth-token.gradle.kts[tags=docker-auth-token] +---- diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle new file mode 100644 index 0000000000..c91c53da9e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{gradle-project-version}' +} + +bootJar { + mainClassName 'com.example.ExampleApplication' +} + +// tag::docker-auth-token[] +bootBuildImage { + docker { + registry { + token = "9cbaf023786cd7..." + } + } +} +// end::docker-auth-token[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle.kts new file mode 100644 index 0000000000..2432601bf5 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle.kts @@ -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") { + mainClassName = "com.example.ExampleApplication" +} + +// tag::docker-auth-token[] +tasks.getByName("bootBuildImage") { + docker { + registry { + token = "9cbaf023786cd7..." + } + } +} +// end::docker-auth-token[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle new file mode 100644 index 0000000000..41d6edb2ab --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{gradle-project-version}' +} + +bootJar { + mainClassName 'com.example.ExampleApplication' +} + +// tag::docker-auth-user[] +bootBuildImage { + docker { + registry { + username = "user" + password = "secret" + url = "https://docker.example.com/v1/" + email = "user@example.com" + } + } +} +// end::docker-auth-user[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle.kts new file mode 100644 index 0000000000..7c469daa3b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle.kts @@ -0,0 +1,24 @@ +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") { + mainClassName = "com.example.ExampleApplication" +} + +// tag::docker-auth-user[] +tasks.getByName("bootBuildImage") { + docker { + registry { + username = "user" + password = "secret" + url = "https://docker.example.com/v1/" + email = "user@example.com" + } + } +} +// end::docker-auth-user[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index dfdc211f2d..df8f6e3142 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import groovy.lang.Closure; +import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.JavaVersion; import org.gradle.api.Project; @@ -27,9 +29,11 @@ import org.gradle.api.Task; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; +import org.gradle.util.ConfigureUtil; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.Builder; @@ -72,6 +76,8 @@ public class BootBuildImage extends DefaultTask { private PullPolicy pullPolicy; + private DockerSpec docker = new DockerSpec(); + public BootBuildImage() { this.jar = getProject().getObjects().fileProperty(); this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class); @@ -246,9 +252,37 @@ public class BootBuildImage extends DefaultTask { this.pullPolicy = pullPolicy; } + /** + * Returns the Docker configuration the builder will use. + * @return docker configuration. + * @since 2.4.0 + */ + @Nested + public DockerSpec getDocker() { + return this.docker; + } + + /** + * Configures the Docker connection using the given {@code action}. + * @param action the action to apply + * @since 2.4.0 + */ + public void docker(Action action) { + action.execute(this.docker); + } + + /** + * Configures the Docker connection using the given {@code closure}. + * @param closure the closure to apply + * @since 2.4.0 + */ + public void docker(Closure closure) { + docker(ConfigureUtil.configureUsing(closure)); + } + @TaskAction void buildImage() throws DockerEngineException, IOException { - Builder builder = new Builder(); + Builder builder = new Builder(this.docker.asDockerConfiguration()); BuildRequest request = createRequest(); builder.build(request); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java new file mode 100644 index 0000000000..7db6300bfd --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java @@ -0,0 +1,214 @@ +/* + * 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.gradle.tasks.bundling; + +import groovy.lang.Closure; +import org.gradle.api.Action; +import org.gradle.api.GradleException; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; +import org.gradle.util.ConfigureUtil; + +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; + +/** + * Encapsulates Docker configuration options. + * + * @author Wei Jiang + * @author Scott Frederick + * @since 2.4.0 + */ +public class DockerSpec { + + private final DockerRegistrySpec registry; + + public DockerSpec() { + this.registry = new DockerRegistrySpec(); + } + + DockerSpec(DockerRegistrySpec registry) { + this.registry = registry; + } + + /** + * Returns the {@link DockerRegistrySpec} that configures registry authentication. + * @return the registry spec + */ + @Nested + public DockerRegistrySpec getRegistry() { + return this.registry; + } + + /** + * Customizes the {@link DockerRegistrySpec} that configures registry authentication. + * @param action the action to apply + */ + public void registry(Action action) { + action.execute(this.registry); + } + + /** + * Customizes the {@link DockerRegistrySpec} that configures registry authentication. + * @param closure the closure to apply + */ + public void registry(Closure closure) { + registry(ConfigureUtil.configureUsing(closure)); + } + + /** + * Returns this configuration as a {@link DockerConfiguration} instance. This method + * should only be called when the configuration is complete and will no longer be + * changed. + * @return the Docker configuration + */ + DockerConfiguration asDockerConfiguration() { + if (this.registry == null || this.registry.hasEmptyAuth()) { + return null; + } + if (this.registry.hasTokenAuth() && !this.registry.hasUserAuth()) { + return DockerConfiguration.withRegistryTokenAuthentication(this.registry.getToken()); + } + if (this.registry.hasUserAuth() && !this.registry.hasTokenAuth()) { + return DockerConfiguration.withRegistryUserAuthentication(this.registry.getUsername(), + this.registry.getPassword(), this.registry.getUrl(), this.registry.getEmail()); + } + throw new GradleException( + "Invalid Docker registry configuration, either token or username/password must be provided"); + } + + /** + * Encapsulates Docker registry authentication configuration options. + */ + public static class DockerRegistrySpec { + + private String username; + + private String password; + + private String url; + + private String email; + + private String token; + + /** + * Returns the username to use when authenticating to the Docker registry. + * @return the registry username + */ + @Input + @Optional + public String getUsername() { + return this.username; + } + + /** + * Sets the username to use when authenticating to the Docker registry. + * @param username the registry username + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Returns the password to use when authenticating to the Docker registry. + * @return the registry password + */ + @Input + @Optional + public String getPassword() { + return this.password; + } + + /** + * Sets the password to use when authenticating to the Docker registry. + * @param password the registry username + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Returns the Docker registry URL. + * @return the registry URL + */ + @Input + @Optional + public String getUrl() { + return this.url; + } + + /** + * Sets the Docker registry URL. + * @param url the registry URL + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * Returns the email address associated with the Docker registry username. + * @return the registry email address + */ + @Input + @Optional + public String getEmail() { + return this.email; + } + + /** + * Sets the email address associated with the Docker registry username. + * @param email the registry email address + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Returns the identity token to use when authenticating to the Docker registry. + * @return the registry identity token + */ + @Input + @Optional + public String getToken() { + return this.token; + } + + /** + * Sets the identity token to use when authenticating to the Docker registry. + * @param token the registry identity token + */ + public void setToken(String token) { + this.token = token; + } + + boolean hasEmptyAuth() { + return this.username == null && this.password == null && this.url == null && this.email == null + && this.token == null; + } + + boolean hasUserAuth() { + return this.getUsername() != null && this.getPassword() != null; + } + + boolean hasTokenAuth() { + return this.getToken() != null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java new file mode 100644 index 0000000000..565c2e9397 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java @@ -0,0 +1,100 @@ +/* + * 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.gradle.tasks.bundling; + +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.DockerRegistryAuthentication; +import org.springframework.util.Base64Utils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link DockerSpec}. + * + * @author Wei Jiang + * @author Scott Frederick + */ +public class DockerSpecTests { + + @Test + void asDockerConfigurationWithoutRegistry() { + DockerSpec dockerSpec = new DockerSpec(); + assertThat(dockerSpec.asDockerConfiguration()).isNull(); + } + + @Test + void asDockerConfigurationWithEmptyRegistry() { + DockerSpec dockerSpec = new DockerSpec(new DockerSpec.DockerRegistrySpec()); + assertThat(dockerSpec.asDockerConfiguration()).isNull(); + } + + @Test + void asDockerConfigurationWithUserAuth() { + DockerSpec.DockerRegistrySpec dockerRegistry = new DockerSpec.DockerRegistrySpec(); + dockerRegistry.setUsername("user"); + dockerRegistry.setPassword("secret"); + dockerRegistry.setUrl("https://docker.example.com"); + dockerRegistry.setEmail("docker@example.com"); + DockerSpec dockerSpec = new DockerSpec(dockerRegistry); + DockerConfiguration dockerConfiguration = dockerSpec.asDockerConfiguration(); + DockerRegistryAuthentication registryAuthentication = dockerConfiguration.getRegistryAuthentication(); + assertThat(registryAuthentication).isNotNull(); + assertThat(new String(Base64Utils.decodeFromString(registryAuthentication.createAuthHeader()))) + .contains("\"username\" : \"user\"").contains("\"password\" : \"secret\"") + .contains("\"email\" : \"docker@example.com\"") + .contains("\"serveraddress\" : \"https://docker.example.com\""); + } + + @Test + void asDockerConfigurationWithIncompleteUserAuthFails() { + DockerSpec.DockerRegistrySpec dockerRegistry = new DockerSpec.DockerRegistrySpec(); + dockerRegistry.setUsername("user"); + dockerRegistry.setUrl("https://docker.example.com"); + dockerRegistry.setEmail("docker@example.com"); + DockerSpec dockerSpec = new DockerSpec(dockerRegistry); + assertThatExceptionOfType(GradleException.class).isThrownBy(dockerSpec::asDockerConfiguration) + .withMessageContaining("Invalid Docker registry configuration"); + } + + @Test + void asDockerConfigurationWithTokenAuth() { + DockerSpec.DockerRegistrySpec dockerRegistry = new DockerSpec.DockerRegistrySpec(); + dockerRegistry.setToken("token"); + DockerSpec dockerSpec = new DockerSpec(dockerRegistry); + DockerConfiguration dockerConfiguration = dockerSpec.asDockerConfiguration(); + DockerRegistryAuthentication registryAuthentication = dockerConfiguration.getRegistryAuthentication(); + assertThat(registryAuthentication).isNotNull(); + assertThat(new String(Base64Utils.decodeFromString(registryAuthentication.createAuthHeader()))) + .contains("\"identitytoken\" : \"token\""); + } + + @Test + void asDockerConfigurationWithUserAndTokenAuthFails() { + DockerSpec.DockerRegistrySpec dockerRegistry = new DockerSpec.DockerRegistrySpec(); + dockerRegistry.setUsername("user"); + dockerRegistry.setPassword("secret"); + dockerRegistry.setToken("token"); + DockerSpec dockerSpec = new DockerSpec(dockerRegistry); + assertThatExceptionOfType(GradleException.class).isThrownBy(dockerSpec::asDockerConfiguration) + .withMessageContaining("Invalid Docker registry configuration"); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc index 62fb01fbdc..e5a461df21 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -59,6 +59,37 @@ On Linux and macOS, these environment variables can be set using the command `ev +[[build-image-docker-registry]] +=== Docker Registry +If the Docker images specified by the `builder` or `runImage` parameters are stored in a private Docker image registry that requires authentication, the authentication credentials can be provided using `docker.registry` parameters. +Parameters are provided for user authentication or identity token authentication. +Consult the documentation for the Docker registry being used to store builder or run images for further information on supported authentication methods. + +The following table summarizes the available parameters: + +|=== +| Parameter | Description + +| `username` +| Username for the Docker image registry user. Required for user authentication. + +| `password` +| Password for the Docker image registry user. Required for user authentication. + +| `url` +| Address of the Docker image registry. Optional for user authentication. + +| `email` +| E-mail address for the Docker image registry user. Optional for user authentication. + +| `token` +| Identity token for the Docker image registry user. Required for token authentication. +|=== + +For more details, see also <>. + + + [[build-image-customization]] === Image Customizations The plugin invokes a {buildpacks-reference}/concepts/components/builder/[builder] to orchestrate the generation of an image. @@ -252,3 +283,57 @@ The image name can be specified on the command line as well, as shown in this ex $ mvn spring-boot:build-image -Dspring-boot.build-image.imageName=example.com/library/my-app:v1 ---- + + +[[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: + +[source,xml,indent=0,subs="verbatim,attributes"] +---- + + + + + org.springframework.boot + spring-boot-maven-plugin + {gradle-project-version} + + + + user + secret + https://docker.example.com/v1/ + user@example.com + + + + + + + +---- + +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: + +[source,xml,indent=0,subs="verbatim,attributes"] +---- + + + + + org.springframework.boot + spring-boot-maven-plugin + {gradle-project-version} + + + + 9cbaf023786cd7... + + + + + + + +---- diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java index 4451199d6b..9aa3acb623 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java @@ -44,6 +44,7 @@ import org.springframework.boot.buildpack.platform.build.Builder; import org.springframework.boot.buildpack.platform.build.Creator; import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.loader.tools.EntryWriter; @@ -135,6 +136,13 @@ public class BuildImageMojo extends AbstractPackagerMojo { @Parameter(property = "spring-boot.build-image.pullPolicy", readonly = true) PullPolicy pullPolicy; + /** + * Docker configuration options. + * @since 2.4.0 + */ + @Parameter + private Docker docker; + @Override public void execute() throws MojoExecutionException { if (this.project.getPackaging().equals("pom")) { @@ -151,7 +159,9 @@ public class BuildImageMojo extends AbstractPackagerMojo { private void buildImage() throws MojoExecutionException { Libraries libraries = getLibraries(Collections.emptySet()); try { - Builder builder = new Builder(new MojoBuildLog(this::getLog)); + DockerConfiguration dockerConfiguration = (this.docker != null) ? this.docker.asDockerConfiguration() + : null; + Builder builder = new Builder(new MojoBuildLog(this::getLog), dockerConfiguration); BuildRequest request = getBuildRequest(libraries); builder.build(request); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java new file mode 100644 index 0000000000..eb413c54a6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java @@ -0,0 +1,132 @@ +/* + * 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.maven; + +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; + +/** + * Docker configuration options. + * + * @author Wei Jiang + * @author Scott Frederick + * @since 2.4.0 + */ +public class Docker { + + private DockerRegistry registry; + + /** + * Sets the {@link DockerRegistry} that configures registry authentication. + * @param registry the registry configuration + */ + public void setRegistry(DockerRegistry registry) { + this.registry = registry; + } + + /** + * Returns this configuration as a {@link DockerConfiguration} instance. This method + * should only be called when the configuration is complete and will no longer be + * changed. + * @return the Docker configuration + */ + DockerConfiguration asDockerConfiguration() { + if (this.registry == null || this.registry.isEmpty()) { + return null; + } + if (this.registry.hasTokenAuth() && !this.registry.hasUserAuth()) { + return DockerConfiguration.withRegistryTokenAuthentication(this.registry.getToken()); + } + if (this.registry.hasUserAuth() && !this.registry.hasTokenAuth()) { + 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"); + } + + /** + * Encapsulates Docker registry authentication configuration options. + */ + public static class DockerRegistry { + + private String username; + + private String password; + + private String url; + + private String email; + + private String token; + + String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + + boolean isEmpty() { + return this.username == null && this.password == null && this.url == null && this.email == null + && this.token == null; + } + + boolean hasTokenAuth() { + return this.token != null; + } + + boolean hasUserAuth() { + return this.username != null && this.password != null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java new file mode 100644 index 0000000000..eec5e792e7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java @@ -0,0 +1,105 @@ +/* + * 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.maven; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication; +import org.springframework.util.Base64Utils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link Docker}. + * + * @author Wei Jiang + * @author Scott Frederick + */ +public class DockerTests { + + @Test + void asDockerConfigurationWithoutRegistry() { + Docker docker = new Docker(); + assertThat(docker.asDockerConfiguration()).isNull(); + } + + @Test + void asDockerConfigurationWithEmptyRegistry() { + Docker.DockerRegistry dockerRegistry = new Docker.DockerRegistry(); + Docker docker = new Docker(); + docker.setRegistry(dockerRegistry); + assertThat(docker.asDockerConfiguration()).isNull(); + } + + @Test + void asDockerConfigurationWithUserAuth() { + Docker.DockerRegistry dockerRegistry = new Docker.DockerRegistry(); + dockerRegistry.setUsername("user"); + dockerRegistry.setPassword("secret"); + dockerRegistry.setUrl("https://docker.example.com"); + dockerRegistry.setEmail("docker@example.com"); + Docker docker = new Docker(); + docker.setRegistry(dockerRegistry); + DockerConfiguration dockerConfiguration = docker.asDockerConfiguration(); + DockerRegistryAuthentication registryAuthentication = dockerConfiguration.getRegistryAuthentication(); + assertThat(registryAuthentication).isNotNull(); + assertThat(new String(Base64Utils.decodeFromString(registryAuthentication.createAuthHeader()))) + .contains("\"username\" : \"user\"").contains("\"password\" : \"secret\"") + .contains("\"email\" : \"docker@example.com\"") + .contains("\"serveraddress\" : \"https://docker.example.com\""); + } + + @Test + void asDockerConfigurationWithIncompleteUserAuthFails() { + Docker.DockerRegistry dockerRegistry = new Docker.DockerRegistry(); + dockerRegistry.setUsername("user"); + dockerRegistry.setUrl("https://docker.example.com"); + dockerRegistry.setEmail("docker@example.com"); + Docker docker = new Docker(); + docker.setRegistry(dockerRegistry); + assertThatIllegalArgumentException().isThrownBy(docker::asDockerConfiguration) + .withMessageContaining("Invalid Docker registry configuration"); + } + + @Test + void asDockerConfigurationWithTokenAuth() { + Docker.DockerRegistry dockerRegistry = new Docker.DockerRegistry(); + dockerRegistry.setToken("token"); + Docker docker = new Docker(); + docker.setRegistry(dockerRegistry); + DockerConfiguration dockerConfiguration = docker.asDockerConfiguration(); + DockerRegistryAuthentication registryAuthentication = dockerConfiguration.getRegistryAuthentication(); + assertThat(registryAuthentication).isNotNull(); + assertThat(new String(Base64Utils.decodeFromString(registryAuthentication.createAuthHeader()))) + .contains("\"identitytoken\" : \"token\""); + } + + @Test + void asDockerConfigurationWithUserAndTokenAuthFails() { + Docker.DockerRegistry dockerRegistry = new Docker.DockerRegistry(); + dockerRegistry.setUsername("user"); + dockerRegistry.setPassword("secret"); + dockerRegistry.setToken("token"); + Docker docker = new Docker(); + docker.setRegistry(dockerRegistry); + assertThatIllegalArgumentException().isThrownBy(docker::asDockerConfiguration) + .withMessageContaining("Invalid Docker registry configuration"); + } + +}