Add Docker configuration authentication to Maven and Gradle plugins

Update the Maven and Gradle plugins to make use of the new Docker
configuration authentication support.

See gh-45269

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
This commit is contained in:
Dmytro Nosan 2025-04-24 14:01:31 -07:00 committed by Phillip Webb
parent e6fbccef7e
commit da61d63db8
7 changed files with 66 additions and 29 deletions

View File

@ -100,6 +100,18 @@ The following table summarizes the available properties for `docker.builderRegis
For more details, see also xref:packaging-oci-image.adoc#build-image.examples.docker[examples].
[NOTE]
====
If credentials are not provided, the plugin reads the user's existing Docker configuration file (typically located at `$HOME/.docker/config.json`) to determine authentication methods.
Using these methods, the plugin attempts to provide authentication credentials for the requested image.
The plugin supports the following authentication methods:
- *Credential Helpers*: External tools configured in the Docker configuration file to provide credentials for specific registries. For example, tools like `osxkeychain` or `ecr-login` handle authentication for certain registries.
- *Credential Store*: A default fallback mechanism that securely stores and retrieves credentials (e.g., `desktop` for Docker Desktop).
- *Static Credentials*: Credentials that are stored directly in the Docker configuration file under the `auths` section.
====
[[build-image.customization]]

View File

@ -145,13 +145,14 @@ public abstract class DockerSpec {
}
private BuilderDockerConfiguration customizeBuilderAuthentication(BuilderDockerConfiguration dockerConfiguration) {
return dockerConfiguration
.withBuilderRegistryAuthentication(getRegistryAuthentication("builder", this.builderRegistry, null));
return dockerConfiguration.withBuilderRegistryAuthentication(getRegistryAuthentication("builder",
this.builderRegistry, DockerRegistryAuthentication.configuration(null)));
}
private BuilderDockerConfiguration customizePublishAuthentication(BuilderDockerConfiguration dockerConfiguration) {
return dockerConfiguration.withPublishRegistryAuthentication(
getRegistryAuthentication("publish", this.publishRegistry, DockerRegistryAuthentication.EMPTY_USER));
return dockerConfiguration
.withPublishRegistryAuthentication(getRegistryAuthentication("publish", this.publishRegistry,
DockerRegistryAuthentication.configuration(DockerRegistryAuthentication.EMPTY_USER)));
}
private DockerRegistryAuthentication getRegistryAuthentication(String type, DockerRegistrySpec registry,

View File

@ -54,7 +54,7 @@ class DockerSpecTests {
void asDockerConfigurationWithDefaults() {
BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
assertThat(dockerConfiguration.connection()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -73,7 +73,7 @@ class DockerSpecTests {
assertThat(host.secure()).isTrue();
assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -90,7 +90,7 @@ class DockerSpecTests {
assertThat(host.secure()).isFalse();
assertThat(host.certificatePath()).isNull();
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -106,7 +106,7 @@ class DockerSpecTests {
.connection();
assertThat(host.context()).isEqualTo("test-context");
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -132,7 +132,7 @@ class DockerSpecTests {
assertThat(host.secure()).isFalse();
assertThat(host.certificatePath()).isNull();
assertThat(dockerConfiguration.bindHostToBuilder()).isTrue();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -213,7 +213,7 @@ class DockerSpecTests {
}
String decoded(String value) {
return new String(Base64.getDecoder().decode(value));
return (value != null) ? new String(Base64.getDecoder().decode(value)) : value;
}
}

View File

@ -115,6 +115,18 @@ The following table summarizes the available parameters for `docker.builderRegis
For more details, see also xref:build-image.adoc#build-image.examples.docker[examples].
[NOTE]
====
If credentials are not provided, the plugin reads the user's existing Docker configuration file (typically located at `$HOME/.docker/config.json`) to determine authentication methods.
Using these methods, the plugin attempts to provide authentication credentials for the requested image.
The plugin supports the following authentication methods:
- *Credential Helpers*: External tools configured in the Docker configuration file to provide credentials for specific registries. For example, tools like `osxkeychain` or `ecr-login` handle authentication for certain registries.
- *Credential Store*: A default fallback mechanism that securely stores and retrieves credentials (e.g., `desktop` for Docker Desktop).
- *Static Credentials*: Credentials that are stored directly in the Docker configuration file under the `auths` section.
====
[[build-image.customization]]

View File

@ -262,9 +262,9 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo {
Libraries libraries = getLibraries(Collections.emptySet());
try {
BuildRequest request = getBuildRequest(libraries);
BuilderDockerConfiguration dockerConfiguration = (this.docker != null)
? this.docker.asDockerConfiguration(request.isPublish())
: new Docker().asDockerConfiguration(request.isPublish());
Docker docker = (this.docker != null) ? this.docker : new Docker();
BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(getLog(),
request.isPublish());
Builder builder = new Builder(new MojoBuildLog(this::getLog), dockerConfiguration);
builder.build(request);
}

View File

@ -16,6 +16,8 @@
package org.springframework.boot.maven;
import org.apache.maven.plugin.logging.Log;
import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
@ -141,15 +143,16 @@ public class Docker {
* Returns this configuration as a {@link BuilderDockerConfiguration} instance. This
* method should only be called when the configuration is complete and will no longer
* be changed.
* @param log the output log
* @param publish whether the image should be published
* @return the Docker configuration
*/
BuilderDockerConfiguration asDockerConfiguration(boolean publish) {
BuilderDockerConfiguration asDockerConfiguration(Log log, boolean publish) {
BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration();
dockerConfiguration = customizeHost(dockerConfiguration);
dockerConfiguration = dockerConfiguration.withBindHostToBuilder(this.bindHostToBuilder);
dockerConfiguration = customizeBuilderAuthentication(dockerConfiguration);
dockerConfiguration = customizePublishAuthentication(dockerConfiguration, publish);
dockerConfiguration = customizeBuilderAuthentication(log, dockerConfiguration);
dockerConfiguration = customizePublishAuthentication(log, dockerConfiguration, publish);
return dockerConfiguration;
}
@ -167,18 +170,23 @@ public class Docker {
return dockerConfiguration;
}
private BuilderDockerConfiguration customizeBuilderAuthentication(BuilderDockerConfiguration dockerConfiguration) {
return dockerConfiguration
.withBuilderRegistryAuthentication(getRegistryAuthentication("builder", this.builderRegistry, null));
private BuilderDockerConfiguration customizeBuilderAuthentication(Log log,
BuilderDockerConfiguration dockerConfiguration) {
DockerRegistryAuthentication authentication = DockerRegistryAuthentication.configuration(null,
(message, ex) -> log.warn(message));
return dockerConfiguration.withBuilderRegistryAuthentication(
getRegistryAuthentication("builder", this.builderRegistry, authentication));
}
private BuilderDockerConfiguration customizePublishAuthentication(BuilderDockerConfiguration dockerConfiguration,
boolean publish) {
private BuilderDockerConfiguration customizePublishAuthentication(Log log,
BuilderDockerConfiguration dockerConfiguration, boolean publish) {
if (!publish) {
return dockerConfiguration;
}
DockerRegistryAuthentication authentication = DockerRegistryAuthentication
.configuration(DockerRegistryAuthentication.EMPTY_USER, (message, ex) -> log.warn(message));
return dockerConfiguration.withPublishRegistryAuthentication(
getRegistryAuthentication("publish", this.publishRegistry, DockerRegistryAuthentication.EMPTY_USER));
getRegistryAuthentication("publish", this.publishRegistry, authentication));
}
private DockerRegistryAuthentication getRegistryAuthentication(String type, DockerRegistry registry,

View File

@ -18,6 +18,8 @@ package org.springframework.boot.maven;
import java.util.Base64;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugin.logging.SystemStreamLog;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration;
@ -34,12 +36,14 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*/
class DockerTests {
private final Log log = new SystemStreamLog();
@Test
void asDockerConfigurationWithDefaults() {
Docker docker = new Docker();
BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
assertThat(dockerConfiguration.connection()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -59,7 +63,7 @@ class DockerTests {
assertThat(host.secure()).isTrue();
assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication()).isNull();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -76,7 +80,7 @@ class DockerTests {
.connection();
assertThat(context.context()).isEqualTo("test-context");
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication()).isNull();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -106,7 +110,7 @@ class DockerTests {
assertThat(host.secure()).isTrue();
assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(dockerConfiguration.bindHostToBuilder()).isTrue();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication()).isNull();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication().getAuthHeader()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
@ -157,7 +161,7 @@ class DockerTests {
Docker docker = new Docker();
docker.setPublishRegistry(
new Docker.DockerRegistry("user", null, "https://docker.example.com", "docker@example.com"));
BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(false);
BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(this.log, false);
assertThat(dockerConfiguration.publishRegistryAuthentication()).isNull();
}
@ -193,12 +197,12 @@ class DockerTests {
dockerRegistry.setToken("token");
Docker docker = new Docker();
docker.setPublishRegistry(dockerRegistry);
BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(false);
BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(this.log, false);
assertThat(dockerConfiguration.publishRegistryAuthentication()).isNull();
}
private BuilderDockerConfiguration createDockerConfiguration(Docker docker) {
return docker.asDockerConfiguration(true);
return docker.asDockerConfiguration(this.log, true);
}
String decoded(String value) {