Polish "Support authentication to private Docker registry"

See gh-22972
This commit is contained in:
Scott Frederick
2020-08-24 14:10:01 -05:00
parent e8f555e13d
commit 86fa8144f5
40 changed files with 1193 additions and 747 deletions

View File

@@ -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-example-docker,examples>>.
[[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]
----

View File

@@ -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[]

View File

@@ -0,0 +1,21 @@
import org.springframework.boot.gradle.tasks.bundling.BootJar
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
plugins {
java
id("org.springframework.boot") version "{gradle-project-version}"
}
tasks.getByName<BootJar>("bootJar") {
mainClassName = "com.example.ExampleApplication"
}
// tag::docker-auth-token[]
tasks.getByName<BootBuildImage>("bootBuildImage") {
docker {
registry {
token = "9cbaf023786cd7..."
}
}
}
// end::docker-auth-token[]

View File

@@ -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[]

View File

@@ -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>("bootJar") {
mainClassName = "com.example.ExampleApplication"
}
// tag::docker-auth-user[]
tasks.getByName<BootBuildImage>("bootBuildImage") {
docker {
registry {
username = "user"
password = "secret"
url = "https://docker.example.com/v1/"
email = "user@example.com"
}
}
}
// end::docker-auth-user[]

View File

@@ -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<DockerSpec> 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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<DockerRegistrySpec> 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;
}
}
}

View File

@@ -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());
}
}

View File

@@ -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");
}
}