From 86fa8144f57ecfa59d02400a4ad26cd6fb297fe1 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Mon, 24 Aug 2020 14:10:01 -0500 Subject: [PATCH] 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 | 107 +++++++-- .../boot/maven/DockerRegistry.java | 98 -------- .../boot/maven/DockerRegistryTests.java | 47 ---- .../boot/maven/DockerTests.java | 105 +++++++++ 40 files changed, 1193 insertions(+), 747 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 0f3b69bb8d8..06f017011e3 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 79a92377735..a606819ab46 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 fbffe6cb83f..134910537ed 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 { - /** - * The docker registry configuration. - */ - private DockerRegistryConfiguration dockerRegistryConfiguration; + private final DockerRegistryAuthentication authentication; - public DockerConfiguration() { - super(); + private DockerConfiguration(DockerRegistryAuthentication authentication) { + this.authentication = authentication; } - public DockerConfiguration(DockerRegistryConfiguration dockerRegistryConfiguration) { - super(); - this.dockerRegistryConfiguration = dockerRegistryConfiguration; + public DockerRegistryAuthentication getRegistryAuthentication() { + return this.authentication; } - public DockerRegistryConfiguration getDockerRegistryConfiguration() { - return this.dockerRegistryConfiguration; + public static DockerConfiguration withDefaults() { + return new DockerConfiguration(null); } - public void setDockerRegistryConfiguration(DockerRegistryConfiguration dockerRegistryConfiguration) { - this.dockerRegistryConfiguration = dockerRegistryConfiguration; + public static DockerConfiguration withRegistryTokenAuthentication(String token) { + Assert.notNull(token, "Token must not be null"); + return new DockerConfiguration(new DockerRegistryTokenAuthentication(token)); + } + + public static DockerConfiguration withRegistryUserAuthentication(String username, String password, String url, + String email) { + Assert.notNull(username, "Username must not be null"); + Assert.notNull(password, "Password must not be null"); + return new DockerConfiguration(new DockerRegistryUserAuthentication(username, password, url, email)); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java new file mode 100644 index 00000000000..cdaaf1962fa --- /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 5699f2b2e62..00000000000 --- 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 00000000000..757b7eb75c9 --- /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 00000000000..d933e8b7947 --- /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 20395f41b66..67563ecff3f 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 f374a23b967..d3669526e2a 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 6c4b2a99a9a..d6f693c6b01 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 8ccc7fcc85e..af83e9a3d2a 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); + DockerConfiguration dockerConfiguration) { + return createIfPossible(environment, dockerConfiguration, new SslContextFactory()); } - static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) { - return createIfPossible(environment, sslContextFactory, Collections.emptyList()); - } - - 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 740cf75b413..3d8028d9e7f 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 b953349b531..f2b91993620 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 592a7f4af9f..2786750810f 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 0d1aa79b281..00000000000 --- 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 00000000000..9b3bcb76aac --- /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 00000000000..aa1c46f8eea --- /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 c4d7173c1f7..0400b4ae464 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 3c4a8bbcc2b..38a65a819c9 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 86c636bfec5..59399577685 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 00000000000..32fe9c70bc1 --- /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 00000000000..a3e615deb6d --- /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 00000000000..7f637981f2a --- /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 25090ae8654..79bc1ab0804 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 00000000000..c91c53da9eb --- /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 00000000000..2432601bf58 --- /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 00000000000..41d6edb2aba --- /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 00000000000..7c469daa3be --- /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 a1aa9aa1089..df8f6e3142f 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 188775c9eb2..00000000000 --- 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 0a320ec7bd4..00000000000 --- 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 00000000000..7db6300bfdf --- /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 bc86e24cba3..00000000000 --- 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 00000000000..565c2e9397a --- /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 62fb01fbdcc..e5a461df21f 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 52e0dfafb74..9aa3acb6231 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 14e43af95ac..eb413c54a6f 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; - - if (this.registry != null) { - dockerRegistryConfiguration = this.registry.getDockerRegistryConfiguration(); + /** + * Returns this configuration as a {@link DockerConfiguration} instance. This method + * should only be called when the configuration is complete and will no longer be + * changed. + * @return the Docker configuration + */ + DockerConfiguration asDockerConfiguration() { + if (this.registry == null || this.registry.isEmpty()) { + return null; + } + if (this.registry.hasTokenAuth() && !this.registry.hasUserAuth()) { + return DockerConfiguration.withRegistryTokenAuthentication(this.registry.getToken()); + } + if (this.registry.hasUserAuth() && !this.registry.hasTokenAuth()) { + return DockerConfiguration.withRegistryUserAuthentication(this.registry.getUsername(), + this.registry.getPassword(), this.registry.getUrl(), this.registry.getEmail()); + } + + throw new IllegalArgumentException( + "Invalid Docker registry configuration, either token or username/password must be provided"); + } + + /** + * Encapsulates Docker registry authentication configuration options. + */ + public static class DockerRegistry { + + private String username; + + private String password; + + private String url; + + private String email; + + private String token; + + String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + + boolean isEmpty() { + return this.username == null && this.password == null && this.url == null && this.email == null + && this.token == null; + } + + boolean hasTokenAuth() { + return this.token != null; + } + + boolean hasUserAuth() { + return this.username != null && this.password != null; } - 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 8020eb261ec..00000000000 --- 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 2fcba6fbd9b..00000000000 --- 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 00000000000..eec5e792e70 --- /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"); + } + +}