From e8f555e13dba5a29ecde34875aed737b8bee878c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=9C=E4=B8=BA?= Date: Sun, 16 Aug 2020 08:51:55 +0800 Subject: [PATCH 1/2] Support authentication to private docker registry This commit adds the ability to configure Docker image registry authentication credentials in the Maven and Gradle plugins. The authentication credentials are passed to the Docker daemon with all daemon API calls, and the daemon forwards the credentials to the image registry when necessary. This makes it possible to use builder and run images stored in a private Docker registry. See gh-22972 --- .../buildpack/platform/build/Builder.java | 11 +- .../buildpack/platform/docker/DockerApi.java | 30 +++- .../configuration/DockerConfiguration.java | 49 +++++++ .../DockerRegistryConfiguration.java | 129 ++++++++++++++++++ .../docker/configuration/package-info.java | 20 +++ .../docker/transport/HttpTransport.java | 33 ++++- .../transport/LocalHttpClientTransport.java | 9 +- .../transport/RemoteHttpClientTransport.java | 23 +++- .../platform/build/BuilderTests.java | 10 +- .../platform/docker/DockerApiTests.java | 45 ++++++ .../DockerConfigurationTests.java | 35 +++++ .../DockerRegistryConfigurationTests.java | 79 +++++++++++ .../docker/transport/HttpTransportTests.java | 12 ++ .../RemoteHttpClientTransportTests.java | 14 ++ .../gradle/tasks/bundling/BootBuildImage.java | 27 +++- .../boot/gradle/tasks/bundling/Docker.java | 53 +++++++ .../gradle/tasks/bundling/DockerRegistry.java | 98 +++++++++++++ .../tasks/bundling/DockerRegistryTests.java | 47 +++++++ .../boot/maven/BuildImageMojo.java | 12 +- .../springframework/boot/maven/Docker.java | 53 +++++++ .../boot/maven/DockerRegistry.java | 98 +++++++++++++ .../boot/maven/DockerRegistryTests.java | 47 +++++++ 22 files changed, 922 insertions(+), 12 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfiguration.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/package-info.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/Docker.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistry.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistryTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DockerRegistry.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerRegistryTests.java 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..0f3b69bb8d 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,8 +49,16 @@ public class Builder { this(BuildLog.toSystemOut()); } + public Builder(DockerConfiguration dockerConfiguration) { + this(BuildLog.toSystemOut(), dockerConfiguration); + } + public Builder(BuildLog log) { - this(log, new DockerApi()); + this(log, new DockerApi(new DockerConfiguration())); + } + + public Builder(BuildLog log, DockerConfiguration dockerConfiguration) { + this(log, new DockerApi(dockerConfiguration)); } Builder(BuildLog log, DockerApi docker) { 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..79a9237773 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 @@ -24,8 +24,12 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import org.apache.http.Header; import org.apache.http.client.utils.URIBuilder; +import org.apache.http.message.BasicHeader; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; 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 +72,15 @@ public class DockerApi { * Create a new {@link DockerApi} instance. */ public DockerApi() { - this(HttpTransport.create()); + this(new DockerConfiguration()); + } + + /** + * Create a new {@link DockerApi} instance. + * @param dockerConfiguration the Docker configuration options. + */ + public DockerApi(DockerConfiguration dockerConfiguration) { + this(HttpTransport.create(createDockerEngineAuthenticationHeaders(dockerConfiguration))); } /** @@ -84,6 +96,22 @@ public class DockerApi { this.volume = new VolumeApi(); } + static Collection
createDockerEngineAuthenticationHeaders(DockerConfiguration dockerConfiguration) { + Assert.notNull(dockerConfiguration, "Docker configuration must not be null"); + + DockerRegistryConfiguration dockerRegistryConfiguration = dockerConfiguration.getDockerRegistryConfiguration(); + if (dockerRegistryConfiguration == null) { + return Collections.emptyList(); + } + + String dockerRegistryAuthToken = dockerRegistryConfiguration.createDockerRegistryAuthToken(); + if (StringUtils.isEmpty(dockerRegistryAuthToken)) { + return Collections.emptyList(); + } + + return Arrays.asList(new BasicHeader("X-Registry-Auth", dockerRegistryAuthToken)); + } + private HttpTransport http() { return this.http; } 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..fbffe6cb83 --- /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,49 @@ +/* + * 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 configuration options. + * + * @author Wei Jiang + * @since 2.4.0 + */ +public class DockerConfiguration { + + /** + * The docker registry configuration. + */ + private DockerRegistryConfiguration dockerRegistryConfiguration; + + public DockerConfiguration() { + super(); + } + + public DockerConfiguration(DockerRegistryConfiguration dockerRegistryConfiguration) { + super(); + this.dockerRegistryConfiguration = dockerRegistryConfiguration; + } + + public DockerRegistryConfiguration getDockerRegistryConfiguration() { + return this.dockerRegistryConfiguration; + } + + public void setDockerRegistryConfiguration(DockerRegistryConfiguration dockerRegistryConfiguration) { + this.dockerRegistryConfiguration = dockerRegistryConfiguration; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfiguration.java new file mode 100644 index 0000000000..5699f2b2e6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfiguration.java @@ -0,0 +1,129 @@ +/* + * 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 com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; +import org.springframework.util.Base64Utils; +import org.springframework.util.StringUtils; + +/** + * Docker registry configuration options. + * + * @author Wei Jiang + * @since 2.4.0 + */ +public class DockerRegistryConfiguration { + + /** + * Docker registry server address. + */ + @JsonProperty("serveraddress") + private String url; + + /** + * Docker registry authentication username. + */ + private String username; + + /** + * Docker registry authentication password. + */ + private String password; + + /** + * Docker registry authentication email. + */ + private String email; + + /** + * Docker registry authentication identity token. + */ + @JsonIgnore + private String token; + + public DockerRegistryConfiguration() { + super(); + } + + public DockerRegistryConfiguration(String url, String username, String password, String email, String token) { + super(); + this.url = url; + this.username = username; + this.password = password; + this.email = email; + this.token = token; + } + + public String createDockerRegistryAuthToken() { + if (!StringUtils.isEmpty(this.getToken())) { + return this.getToken(); + } + + try { + return Base64Utils.encodeToString(SharedObjectMapper.get().writeValueAsBytes(this)); + } + catch (IOException ex) { + throw new IllegalStateException("create docker registry authentication token failed.", ex); + } + } + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + +} 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/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..f374a23b96 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 @@ -21,6 +21,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; +import java.util.Collection; +import java.util.Collections; + +import org.apache.http.Header; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.system.Environment; @@ -84,7 +88,17 @@ public interface HttpTransport { * @return a {@link HttpTransport} instance */ static HttpTransport create() { - return create(Environment.SYSTEM); + return create(Collections.emptyList()); + } + + /** + * Create the most suitable {@link HttpTransport} based on the + * {@link Environment#SYSTEM system environment}. + * @param dockerEngineAuthenticationHeaders authentication headerS for Docker engine. + * @return a {@link HttpTransport} instance + */ + static HttpTransport create(Collection
dockerEngineAuthenticationHeaders) { + return create(Environment.SYSTEM, dockerEngineAuthenticationHeaders); } /** @@ -94,8 +108,21 @@ 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, Collections.emptyList()); + } + + /** + * Create the most suitable {@link HttpTransport} based on the given + * {@link Environment}. + * @param environment the source environment + * @param dockerEngineAuthenticationHeaders authentication headerS for Docker engine. + * @return a {@link HttpTransport} instance + */ + static HttpTransport create(Environment environment, Collection
dockerEngineAuthenticationHeaders) { + HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment, + dockerEngineAuthenticationHeaders); + return (remote != null) ? remote + : LocalHttpClientTransport.create(environment, dockerEngineAuthenticationHeaders); } /** 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..6c4b2a99a9 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 @@ -21,8 +21,10 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; +import java.util.Collection; import com.sun.jna.Platform; +import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; @@ -41,6 +43,7 @@ import org.apache.http.util.Args; import org.springframework.boot.buildpack.platform.socket.DomainSocket; import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket; import org.springframework.boot.buildpack.platform.system.Environment; +import org.springframework.util.CollectionUtils; /** * {@link HttpClientTransport} that talks to local Docker. @@ -60,10 +63,14 @@ final class LocalHttpClientTransport extends HttpClientTransport { super(client, LOCAL_DOCKER_HOST); } - static LocalHttpClientTransport create(Environment environment) { + static LocalHttpClientTransport create(Environment environment, + Collection
dockerEngineAuthenticationHeaders) { HttpClientBuilder builder = HttpClients.custom(); builder.setConnectionManager(new LocalConnectionManager(socketFilePath(environment))); builder.setSchemePortResolver(new LocalSchemePortResolver()); + if (!CollectionUtils.isEmpty(dockerEngineAuthenticationHeaders)) { + builder.setDefaultHeaders(dockerEngineAuthenticationHeaders); + } return new LocalHttpClientTransport(builder.build()); } 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..8ccc7fcc85 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 @@ -18,9 +18,12 @@ package org.springframework.boot.buildpack.platform.docker.transport; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; import javax.net.ssl.SSLContext; +import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.conn.socket.LayeredConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; @@ -31,6 +34,7 @@ import org.apache.http.impl.client.HttpClients; import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory; import org.springframework.boot.buildpack.platform.system.Environment; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * {@link HttpClientTransport} that talks to a remote Docker. @@ -53,15 +57,25 @@ final class RemoteHttpClientTransport extends HttpClientTransport { } static RemoteHttpClientTransport createIfPossible(Environment environment) { - return createIfPossible(environment, new SslContextFactory()); + return createIfPossible(environment, Collections.emptyList()); + } + + static RemoteHttpClientTransport createIfPossible(Environment environment, + Collection
dockerEngineAuthenticationHeaders) { + return createIfPossible(environment, new SslContextFactory(), dockerEngineAuthenticationHeaders); } static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) { + return createIfPossible(environment, sslContextFactory, Collections.emptyList()); + } + + static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory, + Collection
dockerEngineAuthenticationHeaders) { 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), dockerEngineAuthenticationHeaders); } private static boolean isLocalFileReference(String host) { @@ -75,12 +89,15 @@ final class RemoteHttpClientTransport extends HttpClientTransport { } private static RemoteHttpClientTransport create(Environment environment, SslContextFactory sslContextFactory, - HttpHost tcpHost) { + HttpHost tcpHost, Collection
dockerEngineAuthenticationHeaders) { HttpClientBuilder builder = HttpClients.custom(); boolean secure = isSecure(environment); if (secure) { builder.setSSLSocketFactory(getSecureConnectionSocketFactory(environment, sslContextFactory)); } + if (!CollectionUtils.isEmpty(dockerEngineAuthenticationHeaders)) { + builder.setDefaultHeaders(dockerEngineAuthenticationHeaders); + } String scheme = secure ? "https" : "http"; HttpHost httpHost = new HttpHost(tcpHost.getHostName(), tcpHost.getPort(), scheme); return new RemoteHttpClientTransport(builder.build(), httpHost); 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..740cf75b41 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 @@ -30,6 +30,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener; +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.ContainerReference; import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; @@ -60,7 +61,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(), new DockerConfiguration()); + 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..b953349b53 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 @@ -20,7 +20,9 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; +import java.util.Collection; +import org.apache.http.Header; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -34,6 +36,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; 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; @@ -48,6 +52,7 @@ import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; +import org.springframework.util.Base64Utils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -113,6 +118,46 @@ class DockerApiTests { }; } + @Test + void createDockerApi() { + DockerApi api = new DockerApi(); + assertThat(api).isNotNull(); + } + + @Test + void createDockerApiWithDockerConfiguration() { + DockerApi api = new DockerApi(new DockerConfiguration()); + assertThat(api).isNotNull(); + } + + @Test + void createWhenDockerConfigurationIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new DockerApi((DockerConfiguration) null)) + .withMessage("Docker configuration must not be null"); + } + + @Test + void createDockerEngineAuthenticationHeaders() { + DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); + dockerRegistryConfiguration.setUsername("username"); + dockerRegistryConfiguration.setPassword("password"); + dockerRegistryConfiguration.setEmail("mock@spring.com"); + dockerRegistryConfiguration.setUrl("http://mock.docker.registry"); + DockerConfiguration dockerConfiguration = new DockerConfiguration(); + dockerConfiguration.setDockerRegistryConfiguration(dockerRegistryConfiguration); + Collection
dockerEngineAuthenticationHeaders = DockerApi + .createDockerEngineAuthenticationHeaders(dockerConfiguration); + assertThat(dockerEngineAuthenticationHeaders.size() == 1).isTrue(); + Header header = dockerEngineAuthenticationHeaders.iterator().next(); + assertThat(header.getName()).isEqualTo("X-Registry-Auth"); + assertThat(header.getValue()).isEqualTo( + "ewogICJ1c2VybmFtZSIgOiAidXNlcm5hbWUiLAogICJwYXNzd29yZCIgOiAicGFzc3dvcmQiLAogICJlbWFpbCIgOiAibW9ja0BzcHJpbmcuY29tIiwKICAic2VydmVyYWRkcmVzcyIgOiAiaHR0cDovL21vY2suZG9ja2VyLnJlZ2lzdHJ5Igp9"); + assertThat(new String(Base64Utils.decodeFromString(header.getValue()))) + .isEqualTo("{\n" + " \"username\" : \"username\",\n" + " \"password\" : \"password\",\n" + + " \"email\" : \"mock@spring.com\",\n" + + " \"serveraddress\" : \"http://mock.docker.registry\"\n" + "}"); + } + @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..592a7f4af9 --- /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,35 @@ +/* + * 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 + */ +public class DockerConfigurationTests { + + @Test + void createDockerConfiguration() { + assertThat(new DockerConfiguration()).isNotNull(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigurationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigurationTests.java new file mode 100644 index 0000000000..0d1aa79b28 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigurationTests.java @@ -0,0 +1,79 @@ +/* + * 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 org.springframework.util.Base64Utils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DockerRegistryConfiguration}. + * + * @author Wei Jiang + */ +public class DockerRegistryConfigurationTests { + + @Test + void createDockerRegistryAuthTokenWithToken() { + DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); + dockerRegistryConfiguration.setToken("mockToken"); + assertThat(dockerRegistryConfiguration.createDockerRegistryAuthToken()).isEqualTo("mockToken"); + } + + @Test + void createDockerRegistryAuthTokenWithoutToken() { + DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); + dockerRegistryConfiguration.setUsername("username"); + dockerRegistryConfiguration.setPassword("password"); + dockerRegistryConfiguration.setEmail("mock@spring.com"); + dockerRegistryConfiguration.setUrl("http://mock.docker.registry"); + String token = dockerRegistryConfiguration.createDockerRegistryAuthToken(); + assertThat(token).isEqualTo( + "ewogICJ1c2VybmFtZSIgOiAidXNlcm5hbWUiLAogICJwYXNzd29yZCIgOiAicGFzc3dvcmQiLAogICJlbWFpbCIgOiAibW9ja0BzcHJpbmcuY29tIiwKICAic2VydmVyYWRkcmVzcyIgOiAiaHR0cDovL21vY2suZG9ja2VyLnJlZ2lzdHJ5Igp9"); + assertThat(new String(Base64Utils.decodeFromString(token))).isEqualTo("{\n" + " \"username\" : \"username\",\n" + + " \"password\" : \"password\",\n" + " \"email\" : \"mock@spring.com\",\n" + + " \"serveraddress\" : \"http://mock.docker.registry\"\n" + "}"); + } + + @Test + void createDockerRegistryAuthTokenWithUsernameAndPassword() { + DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); + dockerRegistryConfiguration.setUsername("username"); + dockerRegistryConfiguration.setPassword("password"); + String token = dockerRegistryConfiguration.createDockerRegistryAuthToken(); + assertThat(dockerRegistryConfiguration.getEmail()).isNull(); + assertThat(dockerRegistryConfiguration.getUrl()).isNull(); + assertThat(token).isEqualTo( + "ewogICJ1c2VybmFtZSIgOiAidXNlcm5hbWUiLAogICJwYXNzd29yZCIgOiAicGFzc3dvcmQiLAogICJlbWFpbCIgOiBudWxsLAogICJzZXJ2ZXJhZGRyZXNzIiA6IG51bGwKfQ=="); + assertThat(new String(Base64Utils.decodeFromString(token))).isEqualTo("{\n" + " \"username\" : \"username\",\n" + + " \"password\" : \"password\",\n" + " \"email\" : null,\n" + " \"serveraddress\" : null\n" + "}"); + } + + @Test + void createDockerRegistryAuthTokenWithTokenAndUsername() { + DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); + dockerRegistryConfiguration.setToken("mockToken"); + dockerRegistryConfiguration.setUsername("username"); + dockerRegistryConfiguration.setPassword("password"); + dockerRegistryConfiguration.setEmail("mock@spring.com"); + dockerRegistryConfiguration.setUrl("http://mock.docker.registry"); + assertThat(dockerRegistryConfiguration.createDockerRegistryAuthToken()).isEqualTo("mockToken"); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java index 38a65a819c..3c4a8bbcc2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java @@ -19,9 +19,13 @@ package org.springframework.boot.buildpack.platform.docker.transport; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Map; +import org.apache.http.Header; +import org.apache.http.message.BasicHeader; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -65,4 +69,12 @@ class HttpTransportTests { assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } + @Test + void createWithDockerEngineAuthenticationHeaders() { + Collection
dockerEngineAuthenticationHeaders = Arrays.asList(new BasicHeader("X-Registry-Auth", + "eyJ1c2VybmFtZSI6ICJ1c2VybmFtZSIsInBhc3N3b3JkIjogInBhc3N3b3JkIiwiZW1haWwiOiAibW9ja0BzcHJpbmcuY29tIiwic2VydmVyYWRkcmVzcyI6ICJodHRwOi8vbW9jay5kb2NrZXIucmVnaXN0cnkifQ==")); + HttpTransport transport = HttpTransport.create((name) -> null, dockerEngineAuthenticationHeaders); + assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/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..86c636bfec 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 @@ -19,13 +19,17 @@ package org.springframework.boot.buildpack.platform.docker.transport; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; import javax.net.ssl.SSLContext; +import org.apache.http.Header; import org.apache.http.HttpHost; +import org.apache.http.message.BasicHeader; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -68,6 +72,16 @@ class RemoteHttpClientTransportTests { assertThat(transport).isNotNull(); } + @Test + void createWithDockerEngineAuthenticationHeaders() { + Collection
dockerEngineAuthenticationHeaders = Arrays.asList(new BasicHeader("X-Registry-Auth", + "eyJ1c2VybmFtZSI6ICJ1c2VybmFtZSIsInBhc3N3b3JkIjogInBhc3N3b3JkIiwiZW1haWwiOiAibW9ja0BzcHJpbmcuY29tIiwic2VydmVyYWRkcmVzcyI6ICJodHRwOi8vbW9jay5kb2NrZXIucmVnaXN0cnkifQ==")); + this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); + RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, + dockerEngineAuthenticationHeaders); + assertThat(transport).isNotNull(); + } + @Test void createIfPossibleWhenTlsVerifyWithMissingCertPathThrowsException() { this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); 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..a1aa9aa108 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 @@ -35,6 +35,7 @@ import org.springframework.boot.buildpack.platform.build.BuildRequest; 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.configuration.DockerConfiguration; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; @@ -72,6 +73,8 @@ public class BootBuildImage extends DefaultTask { private PullPolicy pullPolicy; + private Docker docker; + public BootBuildImage() { this.jar = getProject().getObjects().fileProperty(); this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class); @@ -246,9 +249,31 @@ public class BootBuildImage extends DefaultTask { this.pullPolicy = pullPolicy; } + /** + * Returns the Docker configuration with the builder will be used. + * @return docker configuration. + */ + @Input + @Optional + public Docker getDocker() { + return this.docker; + } + + /** + * Sets the Docker configuration with the builder will be used. + * @param docker docker configuration. + */ + @Option(option = "docker", description = "The Docker configuration to use") + public void setDocker(Docker docker) { + this.docker = docker; + } + @TaskAction void buildImage() throws DockerEngineException, IOException { - Builder builder = new Builder(); + DockerConfiguration dockerConfiguration = (this.docker != null) ? this.docker.getDockerConfiguration() + : new DockerConfiguration(); + + Builder builder = new Builder(dockerConfiguration); 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/Docker.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/Docker.java new file mode 100644 index 0000000000..188775c9eb --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/Docker.java @@ -0,0 +1,53 @@ +/* + * 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.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; + +/** + * Docker configuration options. + * + * @author Wei Jiang + * @since 2.4.0 + */ +public class Docker { + + /** + * The docker registry configuration. + */ + private DockerRegistry registry; + + public DockerRegistry getRegistry() { + return this.registry; + } + + public void setRegistry(DockerRegistry registry) { + this.registry = registry; + } + + public DockerConfiguration getDockerConfiguration() { + DockerRegistryConfiguration dockerRegistryConfiguration = null; + + if (this.registry != null) { + dockerRegistryConfiguration = this.registry.getDockerRegistryConfiguration(); + } + + return new DockerConfiguration(dockerRegistryConfiguration); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistry.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistry.java new file mode 100644 index 0000000000..0a320ec7bd --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistry.java @@ -0,0 +1,98 @@ +/* + * 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.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; + +/** + * Docker registry configuration options. + * + * @author Wei Jiang + * @since 2.4.0 + */ +public class DockerRegistry { + + /** + * Docker registry server address. + */ + private String url; + + /** + * Docker registry authentication username. + */ + private String username; + + /** + * Docker registry authentication password. + */ + private String password; + + /** + * Docker registry authentication email. + */ + private String email; + + /** + * Docker registry authentication identity token. + */ + private String token; + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + + public DockerRegistryConfiguration getDockerRegistryConfiguration() { + return new DockerRegistryConfiguration(this.url, this.username, this.password, this.email, this.token); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistryTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistryTests.java new file mode 100644 index 0000000000..bc86e24cba --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistryTests.java @@ -0,0 +1,47 @@ +/* + * 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.junit.jupiter.api.Test; + +import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DockerRegistry}. + * + * @author Wei Jiang + */ +public class DockerRegistryTests { + + @Test + void getDockerRegistryConfiguration() { + DockerRegistry dockerRegistry = new DockerRegistry(); + dockerRegistry.setUsername("username"); + dockerRegistry.setPassword("password"); + dockerRegistry.setEmail("mock@spring.com"); + dockerRegistry.setUrl("http://mock.docker.registry"); + DockerRegistryConfiguration dockerRegistryConfiguration = dockerRegistry.getDockerRegistryConfiguration(); + assertThat(dockerRegistryConfiguration).isNotNull(); + assertThat(dockerRegistryConfiguration.getUsername()).isEqualTo(dockerRegistry.getUsername()); + assertThat(dockerRegistryConfiguration.getPassword()).isEqualTo(dockerRegistry.getPassword()); + assertThat(dockerRegistryConfiguration.getEmail()).isEqualTo(dockerRegistry.getEmail()); + assertThat(dockerRegistryConfiguration.getUrl()).isEqualTo(dockerRegistry.getUrl()); + } + +} 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..52e0dfafb7 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.getDockerConfiguration() + : new DockerConfiguration(); + 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..14e43af95a --- /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,53 @@ +/* + * 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; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; + +/** + * Docker configuration options. + * + * @author Wei Jiang + * @since 2.4.0 + */ +public class Docker { + + /** + * The docker registry configuration. + */ + private DockerRegistry registry; + + public DockerRegistry getRegistry() { + return this.registry; + } + + public void setRegistry(DockerRegistry registry) { + this.registry = registry; + } + + public DockerConfiguration getDockerConfiguration() { + DockerRegistryConfiguration dockerRegistryConfiguration = null; + + if (this.registry != null) { + dockerRegistryConfiguration = this.registry.getDockerRegistryConfiguration(); + } + + return new DockerConfiguration(dockerRegistryConfiguration); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DockerRegistry.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DockerRegistry.java new file mode 100644 index 0000000000..8020eb261e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DockerRegistry.java @@ -0,0 +1,98 @@ +/* + * 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.DockerRegistryConfiguration; + +/** + * Docker registry configuration options. + * + * @author Wei Jiang + * @since 2.4.0 + */ +public class DockerRegistry { + + /** + * Docker registry server address. + */ + private String url; + + /** + * Docker registry authentication username. + */ + private String username; + + /** + * Docker registry authentication password. + */ + private String password; + + /** + * Docker registry authentication email. + */ + private String email; + + /** + * Docker registry authentication identity token. + */ + private String token; + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + + public DockerRegistryConfiguration getDockerRegistryConfiguration() { + return new DockerRegistryConfiguration(this.url, this.username, this.password, this.email, this.token); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerRegistryTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerRegistryTests.java new file mode 100644 index 0000000000..2fcba6fbd9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerRegistryTests.java @@ -0,0 +1,47 @@ +/* + * 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.DockerRegistryConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DockerRegistry}. + * + * @author Wei Jiang + */ +public class DockerRegistryTests { + + @Test + void getDockerRegistryConfiguration() { + DockerRegistry dockerRegistry = new DockerRegistry(); + dockerRegistry.setUsername("username"); + dockerRegistry.setPassword("password"); + dockerRegistry.setEmail("mock@spring.com"); + dockerRegistry.setUrl("http://mock.docker.registry"); + DockerRegistryConfiguration dockerRegistryConfiguration = dockerRegistry.getDockerRegistryConfiguration(); + assertThat(dockerRegistryConfiguration).isNotNull(); + assertThat(dockerRegistryConfiguration.getUsername()).isEqualTo(dockerRegistry.getUsername()); + assertThat(dockerRegistryConfiguration.getPassword()).isEqualTo(dockerRegistry.getPassword()); + assertThat(dockerRegistryConfiguration.getEmail()).isEqualTo(dockerRegistry.getEmail()); + assertThat(dockerRegistryConfiguration.getUrl()).isEqualTo(dockerRegistry.getUrl()); + } + +} From 86fa8144f57ecfa59d02400a4ad26cd6fb297fe1 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Mon, 24 Aug 2020 14:10:01 -0500 Subject: [PATCH 2/2] Polish "Support authentication to private Docker registry" See gh-22972 --- .../buildpack/platform/build/Builder.java | 2 +- .../buildpack/platform/docker/DockerApi.java | 25 +- .../configuration/DockerConfiguration.java | 35 +-- .../DockerRegistryAuthentication.java | 41 ++++ .../DockerRegistryConfiguration.java | 129 ----------- .../DockerRegistryTokenAuthentication.java | 39 ++++ .../DockerRegistryUserAuthentication.java | 63 ++++++ .../docker/transport/HttpClientTransport.java | 19 +- .../docker/transport/HttpTransport.java | 27 +-- .../transport/LocalHttpClientTransport.java | 16 +- .../transport/RemoteHttpClientTransport.java | 35 +-- .../platform/build/BuilderTests.java | 3 +- .../platform/docker/DockerApiTests.java | 39 ---- .../DockerConfigurationTests.java | 30 ++- .../DockerRegistryConfigurationTests.java | 79 ------- ...ockerRegistryTokenAuthenticationTests.java | 43 ++++ ...DockerRegistryUserAuthenticationTests.java | 56 +++++ .../transport/HttpClientTransportTests.java | 50 +++- .../docker/transport/HttpTransportTests.java | 12 - .../RemoteHttpClientTransportTests.java | 42 ++-- .../docker/configuration/auth-token.json | 3 + .../docker/configuration/auth-user-full.json | 6 + .../configuration/auth-user-minimal.json | 4 + .../docs/asciidoc/packaging-oci-image.adoc | 61 +++++ .../boot-build-image-docker-auth-token.gradle | 18 ++ ...t-build-image-docker-auth-token.gradle.kts | 21 ++ .../boot-build-image-docker-auth-user.gradle | 21 ++ ...ot-build-image-docker-auth-user.gradle.kts | 24 ++ .../gradle/tasks/bundling/BootBuildImage.java | 39 ++-- .../boot/gradle/tasks/bundling/Docker.java | 53 ----- .../gradle/tasks/bundling/DockerRegistry.java | 98 -------- .../gradle/tasks/bundling/DockerSpec.java | 214 ++++++++++++++++++ .../tasks/bundling/DockerRegistryTests.java | 47 ---- .../tasks/bundling/DockerSpecTests.java | 100 ++++++++ .../docs/asciidoc/packaging-oci-image.adoc | 85 +++++++ .../boot/maven/BuildImageMojo.java | 4 +- .../springframework/boot/maven/Docker.java | 105 +++++++-- .../boot/maven/DockerRegistry.java | 98 -------- .../boot/maven/DockerRegistryTests.java | 47 ---- .../boot/maven/DockerTests.java | 105 +++++++++ 40 files changed, 1192 insertions(+), 746 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfiguration.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthentication.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthentication.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryTokenAuthenticationTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryUserAuthenticationTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-token.json create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-full.json create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/auth-user-minimal.json create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-token.gradle.kts create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-docker-auth-user.gradle.kts delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/Docker.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistry.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistryTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DockerRegistry.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerRegistryTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java 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 0f3b69bb8d..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 @@ -54,7 +54,7 @@ public class Builder { } public Builder(BuildLog log) { - this(log, new DockerApi(new DockerConfiguration())); + this(log, new DockerApi()); } public Builder(BuildLog log, DockerConfiguration dockerConfiguration) { 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 79a9237773..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 @@ -24,12 +24,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import org.apache.http.Header; import org.apache.http.client.utils.URIBuilder; -import org.apache.http.message.BasicHeader; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; 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; @@ -72,15 +69,15 @@ public class DockerApi { * Create a new {@link DockerApi} instance. */ public DockerApi() { - this(new DockerConfiguration()); + this(DockerConfiguration.withDefaults()); } /** * Create a new {@link DockerApi} instance. - * @param dockerConfiguration the Docker configuration options. + * @param dockerConfiguration the Docker configuration options */ public DockerApi(DockerConfiguration dockerConfiguration) { - this(HttpTransport.create(createDockerEngineAuthenticationHeaders(dockerConfiguration))); + this(HttpTransport.create(dockerConfiguration)); } /** @@ -96,22 +93,6 @@ public class DockerApi { this.volume = new VolumeApi(); } - static Collection
createDockerEngineAuthenticationHeaders(DockerConfiguration dockerConfiguration) { - Assert.notNull(dockerConfiguration, "Docker configuration must not be null"); - - DockerRegistryConfiguration dockerRegistryConfiguration = dockerConfiguration.getDockerRegistryConfiguration(); - if (dockerRegistryConfiguration == null) { - return Collections.emptyList(); - } - - String dockerRegistryAuthToken = dockerRegistryConfiguration.createDockerRegistryAuthToken(); - if (StringUtils.isEmpty(dockerRegistryAuthToken)) { - return Collections.emptyList(); - } - - return Arrays.asList(new BasicHeader("X-Registry-Auth", dockerRegistryAuthToken)); - } - private HttpTransport http() { return this.http; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java index fbffe6cb83..134910537e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java @@ -16,34 +16,41 @@ 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 class DockerConfiguration { +public final class DockerConfiguration { + + private final DockerRegistryAuthentication authentication; - /** - * The docker registry configuration. - */ - private DockerRegistryConfiguration dockerRegistryConfiguration; + private DockerConfiguration(DockerRegistryAuthentication authentication) { + this.authentication = authentication; + } - public DockerConfiguration() { - super(); + public DockerRegistryAuthentication getRegistryAuthentication() { + return this.authentication; } - public DockerConfiguration(DockerRegistryConfiguration dockerRegistryConfiguration) { - super(); - this.dockerRegistryConfiguration = dockerRegistryConfiguration; + public static DockerConfiguration withDefaults() { + return new DockerConfiguration(null); } - public DockerRegistryConfiguration getDockerRegistryConfiguration() { - return this.dockerRegistryConfiguration; + public static DockerConfiguration withRegistryTokenAuthentication(String token) { + Assert.notNull(token, "Token must not be null"); + return new DockerConfiguration(new DockerRegistryTokenAuthentication(token)); } - public void setDockerRegistryConfiguration(DockerRegistryConfiguration dockerRegistryConfiguration) { - this.dockerRegistryConfiguration = dockerRegistryConfiguration; + 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/DockerRegistryConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfiguration.java deleted file mode 100644 index 5699f2b2e6..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfiguration.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; -import org.springframework.util.Base64Utils; -import org.springframework.util.StringUtils; - -/** - * Docker registry configuration options. - * - * @author Wei Jiang - * @since 2.4.0 - */ -public class DockerRegistryConfiguration { - - /** - * Docker registry server address. - */ - @JsonProperty("serveraddress") - private String url; - - /** - * Docker registry authentication username. - */ - private String username; - - /** - * Docker registry authentication password. - */ - private String password; - - /** - * Docker registry authentication email. - */ - private String email; - - /** - * Docker registry authentication identity token. - */ - @JsonIgnore - private String token; - - public DockerRegistryConfiguration() { - super(); - } - - public DockerRegistryConfiguration(String url, String username, String password, String email, String token) { - super(); - this.url = url; - this.username = username; - this.password = password; - this.email = email; - this.token = token; - } - - public String createDockerRegistryAuthToken() { - if (!StringUtils.isEmpty(this.getToken())) { - return this.getToken(); - } - - try { - return Base64Utils.encodeToString(SharedObjectMapper.get().writeValueAsBytes(this)); - } - catch (IOException ex) { - throw new IllegalStateException("create docker registry authentication token failed.", ex); - } - } - - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getUsername() { - return this.username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getEmail() { - return this.email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getToken() { - return this.token; - } - - public void setToken(String token) { - this.token = token; - } - -} 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/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 f374a23b96..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 @@ -21,11 +21,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; -import java.util.Collection; -import java.util.Collections; - -import org.apache.http.Header; +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; @@ -88,17 +85,17 @@ public interface HttpTransport { * @return a {@link HttpTransport} instance */ static HttpTransport create() { - return create(Collections.emptyList()); + return create(DockerConfiguration.withDefaults()); } /** * Create the most suitable {@link HttpTransport} based on the * {@link Environment#SYSTEM system environment}. - * @param dockerEngineAuthenticationHeaders authentication headerS for Docker engine. + * @param dockerConfiguration the Docker engine configuration * @return a {@link HttpTransport} instance */ - static HttpTransport create(Collection
dockerEngineAuthenticationHeaders) { - return create(Environment.SYSTEM, dockerEngineAuthenticationHeaders); + static HttpTransport create(DockerConfiguration dockerConfiguration) { + return create(Environment.SYSTEM, dockerConfiguration); } /** @@ -108,21 +105,19 @@ public interface HttpTransport { * @return a {@link HttpTransport} instance */ static HttpTransport create(Environment environment) { - return create(environment, Collections.emptyList()); + return create(environment, DockerConfiguration.withDefaults()); } /** * Create the most suitable {@link HttpTransport} based on the given - * {@link Environment}. + * {@link Environment} and {@link DockerConfiguration}. * @param environment the source environment - * @param dockerEngineAuthenticationHeaders authentication headerS for Docker engine. + * @param dockerConfiguration the Docker engine configuration * @return a {@link HttpTransport} instance */ - static HttpTransport create(Environment environment, Collection
dockerEngineAuthenticationHeaders) { - HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment, - dockerEngineAuthenticationHeaders); - return (remote != null) ? remote - : LocalHttpClientTransport.create(environment, dockerEngineAuthenticationHeaders); + 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 6c4b2a99a9..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 @@ -21,10 +21,8 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; -import java.util.Collection; import com.sun.jna.Platform; -import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; @@ -40,10 +38,10 @@ 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; -import org.springframework.util.CollectionUtils; /** * {@link HttpClientTransport} that talks to local Docker. @@ -59,19 +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, - Collection
dockerEngineAuthenticationHeaders) { + static LocalHttpClientTransport create(Environment environment, DockerConfiguration dockerConfiguration) { HttpClientBuilder builder = HttpClients.custom(); builder.setConnectionManager(new LocalConnectionManager(socketFilePath(environment))); builder.setSchemePortResolver(new LocalSchemePortResolver()); - if (!CollectionUtils.isEmpty(dockerEngineAuthenticationHeaders)) { - builder.setDefaultHeaders(dockerEngineAuthenticationHeaders); - } - 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 8ccc7fcc85..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 @@ -18,12 +18,9 @@ package org.springframework.boot.buildpack.platform.docker.transport; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Collection; -import java.util.Collections; import javax.net.ssl.SSLContext; -import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.conn.socket.LayeredConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; @@ -31,10 +28,10 @@ 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; -import org.springframework.util.CollectionUtils; /** * {@link HttpClientTransport} that talks to a remote Docker. @@ -52,30 +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); - } - - static RemoteHttpClientTransport createIfPossible(Environment environment) { - return createIfPossible(environment, Collections.emptyList()); + private RemoteHttpClientTransport(CloseableHttpClient client, HttpHost host, + DockerConfiguration dockerConfiguration) { + super(client, host, dockerConfiguration); } static RemoteHttpClientTransport createIfPossible(Environment environment, - Collection
dockerEngineAuthenticationHeaders) { - return createIfPossible(environment, new SslContextFactory(), dockerEngineAuthenticationHeaders); - } - - static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) { - return createIfPossible(environment, sslContextFactory, Collections.emptyList()); + DockerConfiguration dockerConfiguration) { + return createIfPossible(environment, dockerConfiguration, new SslContextFactory()); } - static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory, - Collection
dockerEngineAuthenticationHeaders) { + 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), dockerEngineAuthenticationHeaders); + return create(environment, sslContextFactory, HttpHost.create(host), dockerConfiguration); } private static boolean isLocalFileReference(String host) { @@ -89,18 +79,15 @@ final class RemoteHttpClientTransport extends HttpClientTransport { } private static RemoteHttpClientTransport create(Environment environment, SslContextFactory sslContextFactory, - HttpHost tcpHost, Collection
dockerEngineAuthenticationHeaders) { + HttpHost tcpHost, DockerConfiguration dockerConfiguration) { HttpClientBuilder builder = HttpClients.custom(); boolean secure = isSecure(environment); if (secure) { builder.setSSLSocketFactory(getSecureConnectionSocketFactory(environment, sslContextFactory)); } - if (!CollectionUtils.isEmpty(dockerEngineAuthenticationHeaders)) { - builder.setDefaultHeaders(dockerEngineAuthenticationHeaders); - } 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 740cf75b41..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 @@ -30,7 +30,6 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener; -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.ContainerReference; import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; @@ -67,7 +66,7 @@ class BuilderTests { @Test void createWithDockerConfiguration() { - Builder builder = new Builder(BuildLog.toSystemOut(), new DockerConfiguration()); + Builder builder = new Builder(BuildLog.toSystemOut()); assertThat(builder).isNotNull(); } 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 b953349b53..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 @@ -20,9 +20,7 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; -import java.util.Collection; -import org.apache.http.Header; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -36,8 +34,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; 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; @@ -52,7 +48,6 @@ import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; -import org.springframework.util.Base64Utils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -124,40 +119,6 @@ class DockerApiTests { assertThat(api).isNotNull(); } - @Test - void createDockerApiWithDockerConfiguration() { - DockerApi api = new DockerApi(new DockerConfiguration()); - assertThat(api).isNotNull(); - } - - @Test - void createWhenDockerConfigurationIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> new DockerApi((DockerConfiguration) null)) - .withMessage("Docker configuration must not be null"); - } - - @Test - void createDockerEngineAuthenticationHeaders() { - DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); - dockerRegistryConfiguration.setUsername("username"); - dockerRegistryConfiguration.setPassword("password"); - dockerRegistryConfiguration.setEmail("mock@spring.com"); - dockerRegistryConfiguration.setUrl("http://mock.docker.registry"); - DockerConfiguration dockerConfiguration = new DockerConfiguration(); - dockerConfiguration.setDockerRegistryConfiguration(dockerRegistryConfiguration); - Collection
dockerEngineAuthenticationHeaders = DockerApi - .createDockerEngineAuthenticationHeaders(dockerConfiguration); - assertThat(dockerEngineAuthenticationHeaders.size() == 1).isTrue(); - Header header = dockerEngineAuthenticationHeaders.iterator().next(); - assertThat(header.getName()).isEqualTo("X-Registry-Auth"); - assertThat(header.getValue()).isEqualTo( - "ewogICJ1c2VybmFtZSIgOiAidXNlcm5hbWUiLAogICJwYXNzd29yZCIgOiAicGFzc3dvcmQiLAogICJlbWFpbCIgOiAibW9ja0BzcHJpbmcuY29tIiwKICAic2VydmVyYWRkcmVzcyIgOiAiaHR0cDovL21vY2suZG9ja2VyLnJlZ2lzdHJ5Igp9"); - assertThat(new String(Base64Utils.decodeFromString(header.getValue()))) - .isEqualTo("{\n" + " \"username\" : \"username\",\n" + " \"password\" : \"password\",\n" - + " \"email\" : \"mock@spring.com\",\n" - + " \"serveraddress\" : \"http://mock.docker.registry\"\n" + "}"); - } - @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 index 592a7f4af9..2786750810 100644 --- 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 @@ -24,12 +24,38 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link DockerConfiguration}. * * @author Wei Jiang + * @author Scott Frederick */ public class DockerConfigurationTests { @Test - void createDockerConfiguration() { - assertThat(new DockerConfiguration()).isNotNull(); + 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/DockerRegistryConfigurationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigurationTests.java deleted file mode 100644 index 0d1aa79b28..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigurationTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 org.springframework.util.Base64Utils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link DockerRegistryConfiguration}. - * - * @author Wei Jiang - */ -public class DockerRegistryConfigurationTests { - - @Test - void createDockerRegistryAuthTokenWithToken() { - DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); - dockerRegistryConfiguration.setToken("mockToken"); - assertThat(dockerRegistryConfiguration.createDockerRegistryAuthToken()).isEqualTo("mockToken"); - } - - @Test - void createDockerRegistryAuthTokenWithoutToken() { - DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); - dockerRegistryConfiguration.setUsername("username"); - dockerRegistryConfiguration.setPassword("password"); - dockerRegistryConfiguration.setEmail("mock@spring.com"); - dockerRegistryConfiguration.setUrl("http://mock.docker.registry"); - String token = dockerRegistryConfiguration.createDockerRegistryAuthToken(); - assertThat(token).isEqualTo( - "ewogICJ1c2VybmFtZSIgOiAidXNlcm5hbWUiLAogICJwYXNzd29yZCIgOiAicGFzc3dvcmQiLAogICJlbWFpbCIgOiAibW9ja0BzcHJpbmcuY29tIiwKICAic2VydmVyYWRkcmVzcyIgOiAiaHR0cDovL21vY2suZG9ja2VyLnJlZ2lzdHJ5Igp9"); - assertThat(new String(Base64Utils.decodeFromString(token))).isEqualTo("{\n" + " \"username\" : \"username\",\n" - + " \"password\" : \"password\",\n" + " \"email\" : \"mock@spring.com\",\n" - + " \"serveraddress\" : \"http://mock.docker.registry\"\n" + "}"); - } - - @Test - void createDockerRegistryAuthTokenWithUsernameAndPassword() { - DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); - dockerRegistryConfiguration.setUsername("username"); - dockerRegistryConfiguration.setPassword("password"); - String token = dockerRegistryConfiguration.createDockerRegistryAuthToken(); - assertThat(dockerRegistryConfiguration.getEmail()).isNull(); - assertThat(dockerRegistryConfiguration.getUrl()).isNull(); - assertThat(token).isEqualTo( - "ewogICJ1c2VybmFtZSIgOiAidXNlcm5hbWUiLAogICJwYXNzd29yZCIgOiAicGFzc3dvcmQiLAogICJlbWFpbCIgOiBudWxsLAogICJzZXJ2ZXJhZGRyZXNzIiA6IG51bGwKfQ=="); - assertThat(new String(Base64Utils.decodeFromString(token))).isEqualTo("{\n" + " \"username\" : \"username\",\n" - + " \"password\" : \"password\",\n" + " \"email\" : null,\n" + " \"serveraddress\" : null\n" + "}"); - } - - @Test - void createDockerRegistryAuthTokenWithTokenAndUsername() { - DockerRegistryConfiguration dockerRegistryConfiguration = new DockerRegistryConfiguration(); - dockerRegistryConfiguration.setToken("mockToken"); - dockerRegistryConfiguration.setUsername("username"); - dockerRegistryConfiguration.setPassword("password"); - dockerRegistryConfiguration.setEmail("mock@spring.com"); - dockerRegistryConfiguration.setUrl("http://mock.docker.registry"); - assertThat(dockerRegistryConfiguration.createDockerRegistryAuthToken()).isEqualTo("mockToken"); - } - -} 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/HttpTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java index 3c4a8bbcc2..38a65a819c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java @@ -19,13 +19,9 @@ package org.springframework.boot.buildpack.platform.docker.transport; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Map; -import org.apache.http.Header; -import org.apache.http.message.BasicHeader; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -69,12 +65,4 @@ class HttpTransportTests { assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } - @Test - void createWithDockerEngineAuthenticationHeaders() { - Collection
dockerEngineAuthenticationHeaders = Arrays.asList(new BasicHeader("X-Registry-Auth", - "eyJ1c2VybmFtZSI6ICJ1c2VybmFtZSIsInBhc3N3b3JkIjogInBhc3N3b3JkIiwiZW1haWwiOiAibW9ja0BzcHJpbmcuY29tIiwic2VydmVyYWRkcmVzcyI6ICJodHRwOi8vbW9jay5kb2NrZXIucmVnaXN0cnkifQ==")); - HttpTransport transport = HttpTransport.create((name) -> null, dockerEngineAuthenticationHeaders); - assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/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 86c636bfec..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 @@ -19,20 +19,17 @@ package org.springframework.boot.buildpack.platform.docker.transport; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; import javax.net.ssl.SSLContext; -import org.apache.http.Header; import org.apache.http.HttpHost; -import org.apache.http.message.BasicHeader; 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; @@ -50,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(); } @@ -61,24 +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); - assertThat(transport).isNotNull(); - } - - @Test - void createWithDockerEngineAuthenticationHeaders() { - Collection
dockerEngineAuthenticationHeaders = Arrays.asList(new BasicHeader("X-Registry-Auth", - "eyJ1c2VybmFtZSI6ICJ1c2VybmFtZSIsInBhc3N3b3JkIjogInBhc3N3b3JkIiwiZW1haWwiOiAibW9ja0BzcHJpbmcuY29tIiwic2VydmVyYWRkcmVzcyI6ICJodHRwOi8vbW9jay5kb2NrZXIucmVnaXN0cnkifQ==")); - this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get, - dockerEngineAuthenticationHeaders); + this.dockerConfiguration); assertThat(transport).isNotNull(); } @@ -86,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)); } @@ -106,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 a1aa9aa108..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,15 +29,16 @@ 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; import org.springframework.boot.buildpack.platform.build.Creator; import org.springframework.boot.buildpack.platform.build.PullPolicy; -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.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; @@ -73,7 +76,7 @@ public class BootBuildImage extends DefaultTask { private PullPolicy pullPolicy; - private Docker docker; + private DockerSpec docker = new DockerSpec(); public BootBuildImage() { this.jar = getProject().getObjects().fileProperty(); @@ -250,30 +253,36 @@ public class BootBuildImage extends DefaultTask { } /** - * Returns the Docker configuration with the builder will be used. + * Returns the Docker configuration the builder will use. * @return docker configuration. + * @since 2.4.0 */ - @Input - @Optional - public Docker getDocker() { + @Nested + public DockerSpec getDocker() { return this.docker; } /** - * Sets the Docker configuration with the builder will be used. - * @param docker docker configuration. + * Configures the Docker connection using the given {@code action}. + * @param action the action to apply + * @since 2.4.0 */ - @Option(option = "docker", description = "The Docker configuration to use") - public void setDocker(Docker docker) { - this.docker = docker; + 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 { - DockerConfiguration dockerConfiguration = (this.docker != null) ? this.docker.getDockerConfiguration() - : new DockerConfiguration(); - - Builder builder = new Builder(dockerConfiguration); + 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/Docker.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/Docker.java deleted file mode 100644 index 188775c9eb..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/Docker.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; - -/** - * Docker configuration options. - * - * @author Wei Jiang - * @since 2.4.0 - */ -public class Docker { - - /** - * The docker registry configuration. - */ - private DockerRegistry registry; - - public DockerRegistry getRegistry() { - return this.registry; - } - - public void setRegistry(DockerRegistry registry) { - this.registry = registry; - } - - public DockerConfiguration getDockerConfiguration() { - DockerRegistryConfiguration dockerRegistryConfiguration = null; - - if (this.registry != null) { - dockerRegistryConfiguration = this.registry.getDockerRegistryConfiguration(); - } - - return new DockerConfiguration(dockerRegistryConfiguration); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistry.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistry.java deleted file mode 100644 index 0a320ec7bd..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistry.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; - -/** - * Docker registry configuration options. - * - * @author Wei Jiang - * @since 2.4.0 - */ -public class DockerRegistry { - - /** - * Docker registry server address. - */ - private String url; - - /** - * Docker registry authentication username. - */ - private String username; - - /** - * Docker registry authentication password. - */ - private String password; - - /** - * Docker registry authentication email. - */ - private String email; - - /** - * Docker registry authentication identity token. - */ - private String token; - - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getUsername() { - return this.username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getEmail() { - return this.email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getToken() { - return this.token; - } - - public void setToken(String token) { - this.token = token; - } - - public DockerRegistryConfiguration getDockerRegistryConfiguration() { - return new DockerRegistryConfiguration(this.url, this.username, this.password, this.email, this.token); - } - -} 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/DockerRegistryTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistryTests.java deleted file mode 100644 index bc86e24cba..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerRegistryTests.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.junit.jupiter.api.Test; - -import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link DockerRegistry}. - * - * @author Wei Jiang - */ -public class DockerRegistryTests { - - @Test - void getDockerRegistryConfiguration() { - DockerRegistry dockerRegistry = new DockerRegistry(); - dockerRegistry.setUsername("username"); - dockerRegistry.setPassword("password"); - dockerRegistry.setEmail("mock@spring.com"); - dockerRegistry.setUrl("http://mock.docker.registry"); - DockerRegistryConfiguration dockerRegistryConfiguration = dockerRegistry.getDockerRegistryConfiguration(); - assertThat(dockerRegistryConfiguration).isNotNull(); - assertThat(dockerRegistryConfiguration.getUsername()).isEqualTo(dockerRegistry.getUsername()); - assertThat(dockerRegistryConfiguration.getPassword()).isEqualTo(dockerRegistry.getPassword()); - assertThat(dockerRegistryConfiguration.getEmail()).isEqualTo(dockerRegistry.getEmail()); - assertThat(dockerRegistryConfiguration.getUrl()).isEqualTo(dockerRegistry.getUrl()); - } - -} 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 52e0dfafb7..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 @@ -159,8 +159,8 @@ public class BuildImageMojo extends AbstractPackagerMojo { private void buildImage() throws MojoExecutionException { Libraries libraries = getLibraries(Collections.emptySet()); try { - DockerConfiguration dockerConfiguration = (this.docker != null) ? this.docker.getDockerConfiguration() - : new DockerConfiguration(); + 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 index 14e43af95a..eb413c54a6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java @@ -17,37 +17,116 @@ package org.springframework.boot.maven; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryConfiguration; /** * Docker configuration options. * * @author Wei Jiang + * @author Scott Frederick * @since 2.4.0 */ public class Docker { - /** - * The docker registry configuration. - */ private DockerRegistry registry; - public DockerRegistry getRegistry() { - return this.registry; - } - + /** + * Sets the {@link DockerRegistry} that configures registry authentication. + * @param registry the registry configuration + */ public void setRegistry(DockerRegistry registry) { this.registry = registry; } - public DockerConfiguration getDockerConfiguration() { - DockerRegistryConfiguration dockerRegistryConfiguration = null; + /** + * 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; + } - if (this.registry != null) { - dockerRegistryConfiguration = this.registry.getDockerRegistryConfiguration(); + boolean hasUserAuth() { + return this.username != null && this.password != null; } - return new DockerConfiguration(dockerRegistryConfiguration); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DockerRegistry.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DockerRegistry.java deleted file mode 100644 index 8020eb261e..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/DockerRegistry.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.DockerRegistryConfiguration; - -/** - * Docker registry configuration options. - * - * @author Wei Jiang - * @since 2.4.0 - */ -public class DockerRegistry { - - /** - * Docker registry server address. - */ - private String url; - - /** - * Docker registry authentication username. - */ - private String username; - - /** - * Docker registry authentication password. - */ - private String password; - - /** - * Docker registry authentication email. - */ - private String email; - - /** - * Docker registry authentication identity token. - */ - private String token; - - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getUsername() { - return this.username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getEmail() { - return this.email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getToken() { - return this.token; - } - - public void setToken(String token) { - this.token = token; - } - - public DockerRegistryConfiguration getDockerRegistryConfiguration() { - return new DockerRegistryConfiguration(this.url, this.username, this.password, this.email, this.token); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerRegistryTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerRegistryTests.java deleted file mode 100644 index 2fcba6fbd9..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerRegistryTests.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.DockerRegistryConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link DockerRegistry}. - * - * @author Wei Jiang - */ -public class DockerRegistryTests { - - @Test - void getDockerRegistryConfiguration() { - DockerRegistry dockerRegistry = new DockerRegistry(); - dockerRegistry.setUsername("username"); - dockerRegistry.setPassword("password"); - dockerRegistry.setEmail("mock@spring.com"); - dockerRegistry.setUrl("http://mock.docker.registry"); - DockerRegistryConfiguration dockerRegistryConfiguration = dockerRegistry.getDockerRegistryConfiguration(); - assertThat(dockerRegistryConfiguration).isNotNull(); - assertThat(dockerRegistryConfiguration.getUsername()).isEqualTo(dockerRegistry.getUsername()); - assertThat(dockerRegistryConfiguration.getPassword()).isEqualTo(dockerRegistry.getPassword()); - assertThat(dockerRegistryConfiguration.getEmail()).isEqualTo(dockerRegistry.getEmail()); - assertThat(dockerRegistryConfiguration.getUrl()).isEqualTo(dockerRegistry.getUrl()); - } - -} 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"); + } + +}