Browse Source

Relocate `DockerConfiguration` and refactor buildpack platform code

Relate `DockerConfiguration` from `...platform.docker` to
`...platform.build` since it contains build specific concepts.

This commit also refactors a few other areas of the code to make it
easier to support credential helpers in the future.

Closes gh-45283
pull/45286/head
Phillip Webb 11 months ago
parent
commit
dd49de03ee
  1. 91
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java
  2. 74
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderDockerConfiguration.java
  3. 20
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java
  4. 30
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java
  5. 61
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConnectionConfiguration.java
  6. 35
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java
  7. 46
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java
  8. 22
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java
  9. 217
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java
  10. 9
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java
  11. 3
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationTests.java
  12. 33
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java
  13. 10
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java
  14. 10
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java
  15. 21
      spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java
  16. 61
      spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java
  17. 94
      spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java
  18. 4
      spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java
  19. 59
      spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java
  20. 86
      spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java

91
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java

@ -26,7 +26,8 @@ import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; @@ -26,7 +26,8 @@ import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener;
import org.springframework.boot.buildpack.platform.docker.TotalProgressPushListener;
import org.springframework.boot.buildpack.platform.docker.UpdateListener;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
@ -54,7 +55,7 @@ public class Builder { @@ -54,7 +55,7 @@ public class Builder {
private final DockerApi docker;
private final DockerConfiguration dockerConfiguration;
private final BuilderDockerConfiguration dockerConfiguration;
/**
* Create a new builder instance.
@ -67,8 +68,22 @@ public class Builder { @@ -67,8 +68,22 @@ public class Builder {
* Create a new builder instance.
* @param dockerConfiguration the docker configuration
* @since 2.4.0
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link #Builder(BuilderDockerConfiguration)}
*/
public Builder(DockerConfiguration dockerConfiguration) {
@Deprecated(since = "3.5.0", forRemoval = true)
@SuppressWarnings("removal")
public Builder(
org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration dockerConfiguration) {
this(BuildLog.toSystemOut(), dockerConfiguration);
}
/**
* Create a new builder instance.
* @param dockerConfiguration the docker configuration
* @since 3.5.0
*/
public Builder(BuilderDockerConfiguration dockerConfiguration) {
this(BuildLog.toSystemOut(), dockerConfiguration);
}
@ -85,17 +100,45 @@ public class Builder { @@ -85,17 +100,45 @@ public class Builder {
* @param log a logger used to record output
* @param dockerConfiguration the docker configuration
* @since 2.4.0
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link #Builder(BuildLog, BuilderDockerConfiguration)}
*/
public Builder(BuildLog log, DockerConfiguration dockerConfiguration) {
this(log, new DockerApi((dockerConfiguration != null) ? dockerConfiguration.getHost() : null,
@Deprecated(since = "3.5.0", forRemoval = true)
@SuppressWarnings("removal")
public Builder(BuildLog log,
org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration dockerConfiguration) {
this(log, adaptDeprecatedConfiguration(dockerConfiguration));
}
@SuppressWarnings("removal")
private static BuilderDockerConfiguration adaptDeprecatedConfiguration(
org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration configuration) {
if (configuration == null) {
return null;
}
DockerConnectionConfiguration connection = org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration
.asConnectionConfiguration(configuration.getHost());
return new BuilderDockerConfiguration(connection, configuration.isBindHostToBuilder(),
configuration.getBuilderRegistryAuthentication(), configuration.getPublishRegistryAuthentication());
}
/**
* Create a new builder instance.
* @param log a logger used to record output
* @param dockerConfiguration the docker configuration
* @since 3.5.0
*/
public Builder(BuildLog log, BuilderDockerConfiguration dockerConfiguration) {
this(log, new DockerApi((dockerConfiguration != null) ? dockerConfiguration.connection() : null,
BuildLogAdapter.get(log)), dockerConfiguration);
}
Builder(BuildLog log, DockerApi docker, DockerConfiguration dockerConfiguration) {
Builder(BuildLog log, DockerApi docker, BuilderDockerConfiguration dockerConfiguration) {
Assert.notNull(log, "'log' must not be null");
this.log = log;
this.docker = docker;
this.dockerConfiguration = dockerConfiguration;
this.dockerConfiguration = (dockerConfiguration != null) ? dockerConfiguration
: new BuilderDockerConfiguration();
}
public void build(BuildRequest request) throws DockerEngineException, IOException {
@ -104,8 +147,8 @@ public class Builder { @@ -104,8 +147,8 @@ public class Builder {
validateBindings(request.getBindings());
String domain = request.getBuilder().getDomain();
PullPolicy pullPolicy = request.getPullPolicy();
ImageFetcher imageFetcher = new ImageFetcher(domain, getBuilderAuthHeader(), pullPolicy,
request.getImagePlatform());
ImageFetcher imageFetcher = new ImageFetcher(domain, this.dockerConfiguration.builderRegistryAuthentication(),
pullPolicy, request.getImagePlatform());
Image builderImage = imageFetcher.fetchImage(ImageType.BUILDER, request.getBuilder());
BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage);
request = withRunImageIfNeeded(request, builderMetadata);
@ -182,8 +225,8 @@ public class Builder { @@ -182,8 +225,8 @@ public class Builder {
}
private ResolvedDockerHost getDockerHost() {
boolean bindHostToBuilder = this.dockerConfiguration != null && this.dockerConfiguration.isBindHostToBuilder();
return (bindHostToBuilder) ? ResolvedDockerHost.from(this.dockerConfiguration.getHost()) : null;
boolean bindToBuilder = this.dockerConfiguration.bindHostToBuilder();
return (bindToBuilder) ? ResolvedDockerHost.from(this.dockerConfiguration.connection()) : null;
}
private void tagImage(ImageReference sourceReference, List<ImageReference> tags) throws IOException {
@ -203,18 +246,13 @@ public class Builder { @@ -203,18 +246,13 @@ public class Builder {
private void pushImage(ImageReference reference) throws IOException {
Consumer<TotalProgressEvent> progressConsumer = this.log.pushingImage(reference);
TotalProgressPushListener listener = new TotalProgressPushListener(progressConsumer);
this.docker.image().push(reference, listener, getPublishAuthHeader());
String authHeader = authHeader(this.dockerConfiguration.publishRegistryAuthentication());
this.docker.image().push(reference, listener, authHeader);
this.log.pushedImage(reference);
}
private String getBuilderAuthHeader() {
return (this.dockerConfiguration != null && this.dockerConfiguration.getBuilderRegistryAuthentication() != null)
? this.dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader() : null;
}
private String getPublishAuthHeader() {
return (this.dockerConfiguration != null && this.dockerConfiguration.getPublishRegistryAuthentication() != null)
? this.dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader() : null;
private static String authHeader(DockerRegistryAuthentication authentication) {
return (authentication != null) ? authentication.getAuthHeader() : null;
}
/**
@ -224,15 +262,16 @@ public class Builder { @@ -224,15 +262,16 @@ public class Builder {
private final String domain;
private final String authHeader;
private final DockerRegistryAuthentication registryAuthentication;
private final PullPolicy pullPolicy;
private ImagePlatform defaultPlatform;
ImageFetcher(String domain, String authHeader, PullPolicy pullPolicy, ImagePlatform platform) {
ImageFetcher(String domain, DockerRegistryAuthentication registryAuthentication, PullPolicy pullPolicy,
ImagePlatform platform) {
this.domain = domain;
this.authHeader = authHeader;
this.registryAuthentication = registryAuthentication;
this.pullPolicy = pullPolicy;
this.defaultPlatform = platform;
}
@ -240,7 +279,8 @@ public class Builder { @@ -240,7 +279,8 @@ public class Builder {
Image fetchImage(ImageType type, ImageReference reference) throws IOException {
Assert.notNull(type, "'type' must not be null");
Assert.notNull(reference, "'reference' must not be null");
Assert.state(this.authHeader == null || reference.getDomain().equals(this.domain),
String authHeader = authHeader(this.registryAuthentication);
Assert.state(authHeader == null || reference.getDomain().equals(this.domain),
() -> String.format("%s '%s' must be pulled from the '%s' authenticated registry",
StringUtils.capitalize(type.getDescription()), reference, this.domain));
if (this.pullPolicy == PullPolicy.ALWAYS) {
@ -260,7 +300,8 @@ public class Builder { @@ -260,7 +300,8 @@ public class Builder {
private Image pullImage(ImageReference reference, ImageType imageType) throws IOException {
TotalProgressPullListener listener = new TotalProgressPullListener(
Builder.this.log.pullingImage(reference, this.defaultPlatform, imageType));
Image image = Builder.this.docker.image().pull(reference, this.defaultPlatform, listener, this.authHeader);
String authHeader = authHeader(this.registryAuthentication);
Image image = Builder.this.docker.image().pull(reference, this.defaultPlatform, listener, authHeader);
Builder.this.log.pulledImage(image, imageType);
if (this.defaultPlatform == null) {
this.defaultPlatform = ImagePlatform.from(image);

74
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderDockerConfiguration.java

@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
/*
* Copyright 2012-2025 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.build;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
/**
* {@link Builder} configuration options for Docker.
*
* @param connection the Docker host configuration
* @param bindHostToBuilder if the host resolved from the connection should be bound to
* the builder
* @param builderRegistryAuthentication the builder {@link DockerRegistryAuthentication}
* @param publishRegistryAuthentication the publish {@link DockerRegistryAuthentication}
* @author Phillip Webb
* @author Wei Jiang
* @author Scott Frederick
* @since 3.5.0
*/
public record BuilderDockerConfiguration(DockerConnectionConfiguration connection, boolean bindHostToBuilder,
DockerRegistryAuthentication builderRegistryAuthentication,
DockerRegistryAuthentication publishRegistryAuthentication) {
public BuilderDockerConfiguration() {
this(null, false, null, null);
}
public BuilderDockerConfiguration withContext(String context) {
return withConnection(new DockerConnectionConfiguration.Context(context));
}
public BuilderDockerConfiguration withHost(String address, boolean secure, String certificatePath) {
return withConnection(new DockerConnectionConfiguration.Host(address, secure, certificatePath));
}
private BuilderDockerConfiguration withConnection(DockerConnectionConfiguration hostConfiguration) {
return new BuilderDockerConfiguration(hostConfiguration, this.bindHostToBuilder,
this.builderRegistryAuthentication, this.publishRegistryAuthentication);
}
public BuilderDockerConfiguration withBindHostToBuilder(boolean bindHostToBuilder) {
return new BuilderDockerConfiguration(this.connection, bindHostToBuilder, this.builderRegistryAuthentication,
this.publishRegistryAuthentication);
}
public BuilderDockerConfiguration withBuilderRegistryAuthentication(
DockerRegistryAuthentication builderRegistryAuthentication) {
return new BuilderDockerConfiguration(this.connection, this.bindHostToBuilder, builderRegistryAuthentication,
this.publishRegistryAuthentication);
}
public BuilderDockerConfiguration withPublishRegistryAuthentication(
DockerRegistryAuthentication publishRegistryAuthentication) {
return new BuilderDockerConfiguration(this.connection, this.bindHostToBuilder,
this.builderRegistryAuthentication, publishRegistryAuthentication);
}
}

20
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java

@ -28,7 +28,7 @@ import java.util.Objects; @@ -28,7 +28,7 @@ import java.util.Objects;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.net.URIBuilder;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
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.ApiVersion;
@ -87,26 +87,32 @@ public class DockerApi { @@ -87,26 +87,32 @@ public class DockerApi {
* Create a new {@link DockerApi} instance.
*/
public DockerApi() {
this(null);
this(HttpTransport.create((DockerConnectionConfiguration) null), DockerLog.toSystemOut());
}
/**
* Create a new {@link DockerApi} instance.
* @param dockerHost the Docker daemon host information
* @since 2.4.0
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link #DockerApi(DockerConnectionConfiguration, DockerLog)}
*/
public DockerApi(DockerHostConfiguration dockerHost) {
this(dockerHost, DockerLog.toSystemOut());
@Deprecated(since = "3.5.0", forRemoval = true)
@SuppressWarnings("removal")
public DockerApi(
org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration dockerHost) {
this(org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration
.asConnectionConfiguration(dockerHost), DockerLog.toSystemOut());
}
/**
* Create a new {@link DockerApi} instance.
* @param dockerHost the Docker daemon host information
* @param connectionConfiguration the connection configuration to use
* @param log a logger used to record output
* @since 3.5.0
*/
public DockerApi(DockerHostConfiguration dockerHost, DockerLog log) {
this(HttpTransport.create(dockerHost), log);
public DockerApi(DockerConnectionConfiguration connectionConfiguration, DockerLog log) {
this(HttpTransport.create(connectionConfiguration), log);
}
/**

30
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.boot.buildpack.platform.docker.configuration;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Docker configuration options.
@ -24,7 +25,11 @@ import org.springframework.util.Assert; @@ -24,7 +25,11 @@ import org.springframework.util.Assert;
* @author Wei Jiang
* @author Scott Frederick
* @since 2.4.0
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration}.
*/
@Deprecated(since = "3.5.0", forRemoval = true)
@SuppressWarnings("removal")
public final class DockerConfiguration {
private final DockerHostConfiguration host;
@ -113,6 +118,13 @@ public final class DockerConfiguration { @@ -113,6 +118,13 @@ public final class DockerConfiguration {
new DockerRegistryUserAuthentication("", "", "", ""), this.bindHostToBuilder);
}
/**
* Docker host configuration.
*
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link DockerHostConfiguration}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
public static class DockerHostConfiguration {
private final String address;
@ -158,6 +170,24 @@ public final class DockerConfiguration { @@ -158,6 +170,24 @@ public final class DockerConfiguration {
return new DockerHostConfiguration(null, context, false, null);
}
/**
* Adapts a {@link DockerHostConfiguration} to a
* {@link DockerConnectionConfiguration}.
* @param configuration the configuration to adapt
* @return the adapted configuration
* @since 3.5.0
*/
public static DockerConnectionConfiguration asConnectionConfiguration(DockerHostConfiguration configuration) {
if (configuration != null && StringUtils.hasLength(configuration.context)) {
return new DockerConnectionConfiguration.Context(configuration.context);
}
if (configuration != null && StringUtils.hasLength(configuration.address)) {
return new DockerConnectionConfiguration.Host(configuration.address, configuration.secure,
configuration.certificatePath);
}
return null;
}
}
}

61
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConnectionConfiguration.java

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.buildpack.platform.docker.configuration;
import org.springframework.util.Assert;
/**
* Configuration for how to connect to Docker.
*
* @author Phillip Webb
* @since 3.5.0
*/
public sealed interface DockerConnectionConfiguration {
/**
* Connect to specific host.
*
* @param address the host address
* @param secure if connection is secure
* @param certificatePath a path to the certificate used for secure connections
*/
record Host(String address, boolean secure, String certificatePath) implements DockerConnectionConfiguration {
public Host(String address) {
this(address, false, null);
}
public Host {
Assert.hasLength(address, "'address' must not be empty");
}
}
/**
* Connect using a specific context reference.
*
* @param context a reference to the Docker context
*/
record Context(String context) implements DockerConnectionConfiguration {
public Context {
Assert.hasLength(context, "'context' must not be empty");
}
}
}

35
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryAuthentication.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2025 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.
@ -24,10 +24,43 @@ package org.springframework.boot.buildpack.platform.docker.configuration; @@ -24,10 +24,43 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
*/
public interface DockerRegistryAuthentication {
/**
* An empty {@link #user(String, String, String, String)} authentication.
* @since 3.5.0
*/
DockerRegistryAuthentication EMPTY_USER = DockerRegistryAuthentication.user("", "", "", "");
/**
* Returns the auth header that should be used for docker authentication.
* @return the auth header
*/
String getAuthHeader();
/**
* Factory method to that returns a new {@link DockerRegistryAuthentication} instance
* that uses a header generated by base64 encoding a JSON payload created from the
* given parameters.
* @param identityToken the identity token JSON field
* @return a new {@link DockerRegistryAuthentication} instance
* @since 3.5.0
*/
static DockerRegistryAuthentication token(String identityToken) {
return new DockerRegistryTokenAuthentication(identityToken);
}
/**
* Factory method to that returns a new {@link DockerRegistryAuthentication} instance
* that uses a header generated by base64 encoding a JSON payload created from the
* given parameters.
* @param username the username JSON field
* @param password the password JSON field
* @param serverAddress the server address JSON field
* @param email the email JSON field
* @return a new {@link DockerRegistryAuthentication} instance
* @since 3.5.0
*/
static DockerRegistryAuthentication user(String username, String password, String serverAddress, String email) {
return new DockerRegistryUserAuthentication(username, password, serverAddress, email);
}
}

46
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -21,7 +21,6 @@ import java.nio.file.Paths; @@ -21,7 +21,6 @@ import java.nio.file.Paths;
import com.sun.jna.Platform;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext;
import org.springframework.boot.buildpack.platform.system.Environment;
@ -77,30 +76,49 @@ public class ResolvedDockerHost extends DockerHost { @@ -77,30 +76,49 @@ public class ResolvedDockerHost extends DockerHost {
}
}
public static ResolvedDockerHost from(DockerHostConfiguration dockerHost) {
return from(Environment.SYSTEM, dockerHost);
/**
* Create a new {@link ResolvedDockerHost} from the given host configuration.
* @param dockerHostConfiguration the host configuration or {@code null}
* @return the resolved docker host
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link #from(DockerConnectionConfiguration)}
*/
@Deprecated(since = "3.5.0", forRemoval = true)
@SuppressWarnings("removal")
public static ResolvedDockerHost from(DockerConfiguration.DockerHostConfiguration dockerHostConfiguration) {
return from(Environment.SYSTEM,
DockerConfiguration.DockerHostConfiguration.asConnectionConfiguration(dockerHostConfiguration));
}
static ResolvedDockerHost from(Environment environment, DockerHostConfiguration dockerHost) {
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(environment);
/**
* Create a new {@link ResolvedDockerHost} from the given host configuration.
* @param connectionConfiguration the host configuration or {@code null}
* @return the resolved docker host
*/
public static ResolvedDockerHost from(DockerConnectionConfiguration connectionConfiguration) {
return from(Environment.SYSTEM, connectionConfiguration);
}
static ResolvedDockerHost from(Environment environment, DockerConnectionConfiguration connectionConfiguration) {
DockerConfigurationMetadata environmentConfiguration = DockerConfigurationMetadata.from(environment);
if (environment.get(DOCKER_CONTEXT) != null) {
DockerContext context = config.forContext(environment.get(DOCKER_CONTEXT));
DockerContext context = environmentConfiguration.forContext(environment.get(DOCKER_CONTEXT));
return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath());
}
if (dockerHost != null && dockerHost.getContext() != null) {
DockerContext context = config.forContext(dockerHost.getContext());
if (connectionConfiguration instanceof DockerConnectionConfiguration.Context contextConfiguration) {
DockerContext context = environmentConfiguration.forContext(contextConfiguration.context());
return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath());
}
if (environment.get(DOCKER_HOST) != null) {
return new ResolvedDockerHost(environment.get(DOCKER_HOST), isTrue(environment.get(DOCKER_TLS_VERIFY)),
environment.get(DOCKER_CERT_PATH));
}
if (dockerHost != null && dockerHost.getAddress() != null) {
return new ResolvedDockerHost(dockerHost.getAddress(), dockerHost.isSecure(),
dockerHost.getCertificatePath());
if (connectionConfiguration instanceof DockerConnectionConfiguration.Host addressConfiguration) {
return new ResolvedDockerHost(addressConfiguration.address(), addressConfiguration.secure(),
addressConfiguration.certificatePath());
}
if (config.getContext().getDockerHost() != null) {
DockerContext context = config.getContext();
if (environmentConfiguration.getContext().getDockerHost() != null) {
DockerContext context = environmentConfiguration.getContext();
return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath());
}
return new ResolvedDockerHost(getDefaultAddress());

22
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -24,7 +24,8 @@ import java.net.URI; @@ -24,7 +24,8 @@ import java.net.URI;
import org.apache.hc.core5.http.Header;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
@ -103,13 +104,28 @@ public interface HttpTransport { @@ -103,13 +104,28 @@ public interface HttpTransport {
* Create the most suitable {@link HttpTransport} based on the {@link DockerHost}.
* @param dockerHost the Docker host information
* @return a {@link HttpTransport} instance
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
* {@link #create(DockerConnectionConfiguration)}
*/
static HttpTransport create(DockerHostConfiguration dockerHost) {
@Deprecated(since = "3.5.0", forRemoval = true)
@SuppressWarnings("removal")
static HttpTransport create(DockerConfiguration.DockerHostConfiguration dockerHost) {
ResolvedDockerHost host = ResolvedDockerHost.from(dockerHost);
HttpTransport remote = RemoteHttpClientTransport.createIfPossible(host);
return (remote != null) ? remote : LocalHttpClientTransport.create(host);
}
/**
* Create the most suitable {@link HttpTransport} based on the {@link DockerHost}.
* @param connectionConfiguration the Docker host information
* @return a {@link HttpTransport} instance
*/
static HttpTransport create(DockerConnectionConfiguration connectionConfiguration) {
ResolvedDockerHost host = ResolvedDockerHost.from(connectionConfiguration);
HttpTransport remote = RemoteHttpClientTransport.createIfPossible(host);
return (remote != null) ? remote : LocalHttpClientTransport.create(host);
}
/**
* An HTTP operation response.
*/

217
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java

@ -32,7 +32,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; @@ -32,7 +32,7 @@ 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.DockerLog;
import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ContainerReference;
@ -49,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException @@ -49,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.BDDMockito.given;
@ -66,6 +67,15 @@ import static org.mockito.Mockito.times; @@ -66,6 +67,15 @@ import static org.mockito.Mockito.times;
*/
class BuilderTests {
private static final ImageReference PAKETO_BUILDPACKS_BUILDER = ImageReference
.of("gcr.io/paketo-buildpacks/builder");
private static final ImageReference LATEST_PAKETO_BUILDPACKS_BUILDER = PAKETO_BUILDPACKS_BUILDER.inTaggedForm();
private static final ImageReference DEFAULT_BUILDER = ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF);
private static final ImageReference BASE_CNB = ImageReference.of("docker.io/cloudfoundry/run:base-cnb");
@Test
void createWhenLogIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new Builder((BuildLog) null))
@ -108,12 +118,9 @@ class BuilderTests { @@ -108,12 +118,9 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest();
@ -121,11 +128,8 @@ class BuilderTests { @@ -121,11 +128,8 @@ class BuilderTests {
assertThat(out.toString()).contains("Running creator");
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
then(docker.image()).should()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull());
then(docker.image()).should()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull());
then(docker.image()).should().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull());
then(docker.image()).should().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull());
then(docker.image()).should().load(archive.capture(), any());
then(docker.image()).should().remove(archive.getValue().getTag(), true);
then(docker.image()).shouldHaveNoMoreInteractions();
@ -137,16 +141,14 @@ class BuilderTests { @@ -137,16 +141,14 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
DockerConfiguration dockerConfiguration = new DockerConfiguration()
.withBuilderRegistryTokenAuthentication("builder token")
.withPublishRegistryTokenAuthentication("publish token");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
DockerRegistryAuthentication builderToken = DockerRegistryAuthentication.token("builder token");
DockerRegistryAuthentication publishToken = DockerRegistryAuthentication.token("publish token");
BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration()
.withBuilderRegistryAuthentication(builderToken)
.withPublishRegistryAuthentication(publishToken);
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), regAuthEq(builderToken)))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), regAuthEq(builderToken)))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
BuildRequest request = getTestRequest().withPublish(true);
@ -154,15 +156,10 @@ class BuilderTests { @@ -154,15 +156,10 @@ class BuilderTests {
assertThat(out.toString()).contains("Running creator");
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
then(docker.image()).should().pull(eq(DEFAULT_BUILDER), isNull(), any(), regAuthEq(builderToken));
then(docker.image()).should()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
then(docker.image()).should()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
then(docker.image()).should()
.push(eq(request.getName()), any(),
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()));
.pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), regAuthEq(builderToken));
then(docker.image()).should().push(eq(request.getName()), any(), regAuthEq(publishToken));
then(docker.image()).should().load(archive.capture(), any());
then(docker.image()).should().remove(archive.getValue().getTag(), true);
then(docker.image()).shouldHaveNoMoreInteractions();
@ -174,15 +171,14 @@ class BuilderTests { @@ -174,15 +171,14 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image-with-no-run-image-tag.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), isNull(), any(), isNull()))
given(docker.image().pull(eq(LATEST_PAKETO_BUILDPACKS_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:latest")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest().withBuilder(ImageReference.of("gcr.io/paketo-buildpacks/builder"));
BuildRequest request = getTestRequest().withBuilder(PAKETO_BUILDPACKS_BUILDER);
builder.build(request);
assertThat(out.toString()).contains("Running creator");
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
@ -197,8 +193,7 @@ class BuilderTests { @@ -197,8 +193,7 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image-with-run-image-digest.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference
@ -221,15 +216,12 @@ class BuilderTests { @@ -221,15 +216,12 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image-with-empty-stack.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), isNull(), any(), isNull()))
given(docker.image().pull(eq(LATEST_PAKETO_BUILDPACKS_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest().withBuilder(ImageReference.of("gcr.io/paketo-buildpacks/builder"));
BuildRequest request = getTestRequest().withBuilder(PAKETO_BUILDPACKS_BUILDER);
builder.build(request);
assertThat(out.toString()).contains("Running creator");
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
@ -244,8 +236,7 @@ class BuilderTests { @@ -244,8 +236,7 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("example.com/custom/run:latest")), eq(ImagePlatform.from(builderImage)), any(),
@ -267,17 +258,12 @@ class BuilderTests { @@ -267,17 +258,12 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
.willReturn(builderImage);
given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb"))))
.willReturn(runImage);
given(docker.image().inspect(eq(DEFAULT_BUILDER))).willReturn(builderImage);
given(docker.image().inspect(eq(BASE_CNB))).willReturn(runImage);
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.NEVER);
builder.build(request);
@ -296,17 +282,12 @@ class BuilderTests { @@ -296,17 +282,12 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
.willReturn(builderImage);
given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb"))))
.willReturn(runImage);
given(docker.image().inspect(eq(DEFAULT_BUILDER))).willReturn(builderImage);
given(docker.image().inspect(eq(BASE_CNB))).willReturn(runImage);
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.ALWAYS);
builder.build(request);
@ -325,18 +306,15 @@ class BuilderTests { @@ -325,18 +306,15 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
given(docker.image().inspect(eq(DEFAULT_BUILDER)))
.willThrow(
new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null))
.willReturn(builderImage);
given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb"))))
given(docker.image().inspect(eq(BASE_CNB)))
.willThrow(
new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null))
.willReturn(runImage);
@ -358,12 +336,9 @@ class BuilderTests { @@ -358,12 +336,9 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest().withTags(ImageReference.of("my-application:1.2.3"));
@ -383,37 +358,31 @@ class BuilderTests { @@ -383,37 +358,31 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
DockerConfiguration dockerConfiguration = new DockerConfiguration()
.withBuilderRegistryTokenAuthentication("builder token")
.withPublishRegistryTokenAuthentication("publish token");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
DockerRegistryAuthentication builderToken = DockerRegistryAuthentication.token("builder token");
DockerRegistryAuthentication publishToken = DockerRegistryAuthentication.token("publish token");
BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration()
.withBuilderRegistryAuthentication(builderToken)
.withPublishRegistryAuthentication(publishToken);
ImageReference defaultBuilderImageReference = DEFAULT_BUILDER;
given(docker.image().pull(eq(defaultBuilderImageReference), isNull(), any(), regAuthEq(builderToken)))
.willAnswer(withPulledImage(builderImage));
ImageReference baseImageReference = BASE_CNB;
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
.pull(eq(baseImageReference), eq(ImagePlatform.from(builderImage)), any(), regAuthEq(builderToken)))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
BuildRequest request = getTestRequest().withPublish(true).withTags(ImageReference.of("my-application:1.2.3"));
ImageReference builtImageReference = ImageReference.of("my-application:1.2.3");
BuildRequest request = getTestRequest().withPublish(true).withTags(builtImageReference);
builder.build(request);
assertThat(out.toString()).contains("Running creator");
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
assertThat(out.toString()).contains("Successfully created image tag 'docker.io/library/my-application:1.2.3'");
then(docker.image()).should()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
then(docker.image()).should().pull(eq(defaultBuilderImageReference), isNull(), any(), regAuthEq(builderToken));
then(docker.image()).should()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
then(docker.image()).should()
.push(eq(request.getName()), any(),
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()));
then(docker.image()).should().tag(eq(request.getName()), eq(ImageReference.of("my-application:1.2.3")));
then(docker.image()).should()
.push(eq(ImageReference.of("my-application:1.2.3")), any(),
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()));
.pull(eq(baseImageReference), eq(ImagePlatform.from(builderImage)), any(), regAuthEq(builderToken));
then(docker.image()).should().push(eq(request.getName()), any(), regAuthEq(publishToken));
then(docker.image()).should().tag(eq(request.getName()), eq(builtImageReference));
then(docker.image()).should().push(eq(builtImageReference), any(), regAuthEq(publishToken));
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
then(docker.image()).should().load(archive.capture(), any());
then(docker.image()).should().remove(archive.getValue().getTag(), true);
@ -427,22 +396,17 @@ class BuilderTests { @@ -427,22 +396,17 @@ class BuilderTests {
DockerApi docker = mockDockerApi(platform);
Image builderImage = loadImage("image-with-platform.json");
Image runImage = loadImage("run-image-with-platform.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), eq(platform), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), eq(platform), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(platform), any(), isNull()))
.willAnswer(withPulledImage(runImage));
given(docker.image().pull(eq(BASE_CNB), eq(platform), any(), isNull())).willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest().withImagePlatform("linux/arm64/v1");
builder.build(request);
assertThat(out.toString()).contains("Running creator");
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
then(docker.image()).should()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), eq(platform), any(), isNull());
then(docker.image()).should()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(platform), any(), isNull());
then(docker.image()).should().pull(eq(DEFAULT_BUILDER), eq(platform), any(), isNull());
then(docker.image()).should().pull(eq(BASE_CNB), eq(platform), any(), isNull());
then(docker.image()).should().load(archive.capture(), any());
then(docker.image()).should().remove(archive.getValue().getTag(), true);
then(docker.image()).shouldHaveNoMoreInteractions();
@ -454,12 +418,9 @@ class BuilderTests { @@ -454,12 +418,9 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image-with-bad-stack.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest();
@ -474,12 +435,9 @@ class BuilderTests { @@ -474,12 +435,9 @@ class BuilderTests {
DockerApi docker = mockDockerApiLifecycleError();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest();
@ -492,11 +450,11 @@ class BuilderTests { @@ -492,11 +450,11 @@ class BuilderTests {
TestPrintStream out = new TestPrintStream();
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image-with-run-image-different-registry.json");
DockerConfiguration dockerConfiguration = new DockerConfiguration()
.withBuilderRegistryTokenAuthentication("builder token");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
DockerRegistryAuthentication builderToken = DockerRegistryAuthentication.token("builder token");
BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration()
.withBuilderRegistryAuthentication(builderToken);
ImageReference builderImageReference = DEFAULT_BUILDER;
given(docker.image().pull(eq(builderImageReference), any(), any(), regAuthEq(builderToken)))
.willAnswer(withPulledImage(builderImage));
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
BuildRequest request = getTestRequest();
@ -510,11 +468,10 @@ class BuilderTests { @@ -510,11 +468,10 @@ class BuilderTests {
TestPrintStream out = new TestPrintStream();
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
DockerConfiguration dockerConfiguration = new DockerConfiguration()
.withBuilderRegistryTokenAuthentication("builder token");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
DockerRegistryAuthentication builderToken = DockerRegistryAuthentication.token("builder token");
BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration()
.withBuilderRegistryAuthentication(builderToken);
given(docker.image().pull(eq(DEFAULT_BUILDER), any(), any(), regAuthEq(builderToken)))
.willAnswer(withPulledImage(builderImage));
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
BuildRequest request = getTestRequest().withRunImage(ImageReference.of("example.com/custom/run:latest"));
@ -529,11 +486,9 @@ class BuilderTests { @@ -529,11 +486,9 @@ class BuilderTests {
DockerApi docker = mockDockerApiLifecycleError();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), any(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), any(), isNull()))
.willAnswer(withPulledImage(runImage));
given(docker.image().pull(eq(BASE_CNB), any(), any(), isNull())).willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack@1.2.3");
BuildRequest request = getTestRequest().withBuildpacks(reference);
@ -548,12 +503,9 @@ class BuilderTests { @@ -548,12 +503,9 @@ class BuilderTests {
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
given(docker.image().pull(eq(DEFAULT_BUILDER), isNull(), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
any(), isNull()))
given(docker.image().pull(eq(BASE_CNB), eq(ImagePlatform.from(builderImage)), any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest().withBindings(Binding.from("/host", "/cnb"));
@ -611,7 +563,10 @@ class BuilderTests { @@ -611,7 +563,10 @@ class BuilderTests {
listener.onFinish();
return image;
};
}
private static String regAuthEq(DockerRegistryAuthentication authentication) {
return argThat(authentication.getAuthHeader()::equals);
}
static class TestPrintStream extends PrintStream {

9
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -42,7 +42,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi; @@ -42,7 +42,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi;
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.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
@ -362,7 +362,8 @@ class LifecycleTests { @@ -362,7 +362,8 @@ class LifecycleTests {
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest(trustBuilder);
createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")))
createLifecycle(request,
ResolvedDockerHost.from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376")))
.execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json"));
@ -384,7 +385,7 @@ class LifecycleTests { @@ -384,7 +385,7 @@ class LifecycleTests {
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest(trustBuilder);
createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("/var/alt.sock")))
createLifecycle(request, ResolvedDockerHost.from(new DockerConnectionConfiguration.Host("/var/alt.sock")))
.execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json"));

3
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2025 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.
@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Wei Jiang
* @author Scott Frederick
*/
@SuppressWarnings("removal")
class DockerConfigurationTests {
@Test

33
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -31,8 +31,6 @@ import org.junit.jupiter.api.condition.EnabledOnOs; @@ -31,8 +31,6 @@ import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
/**
@ -75,17 +73,6 @@ class ResolvedDockerHostTests { @@ -75,17 +73,6 @@ class ResolvedDockerHostTests {
assertThat(dockerHost.getCertificatePath()).isNull();
}
@Test
@DisabledOnOs(OS.WINDOWS)
void resolveWhenDockerHostAddressIsNullReturnsLinuxDefault() throws Exception {
this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json"));
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress(null));
assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock");
assertThat(dockerHost.isSecure()).isFalse();
assertThat(dockerHost.getCertificatePath()).isNull();
}
@Test
@DisabledOnOs(OS.WINDOWS)
void resolveWhenUsingDefaultContextReturnsLinuxDefault() {
@ -100,7 +87,7 @@ class ResolvedDockerHostTests { @@ -100,7 +87,7 @@ class ResolvedDockerHostTests {
void resolveWhenDockerHostAddressIsLocalReturnsAddress(@TempDir Path tempDir) throws IOException {
String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString();
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress(socketFilePath));
new DockerConnectionConfiguration.Host(socketFilePath));
assertThat(dockerHost.isLocalFileReference()).isTrue();
assertThat(dockerHost.isRemote()).isFalse();
assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath);
@ -112,7 +99,7 @@ class ResolvedDockerHostTests { @@ -112,7 +99,7 @@ class ResolvedDockerHostTests {
void resolveWhenDockerHostAddressIsLocalWithSchemeReturnsAddress(@TempDir Path tempDir) throws IOException {
String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString();
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress("unix://" + socketFilePath));
new DockerConnectionConfiguration.Host("unix://" + socketFilePath));
assertThat(dockerHost.isLocalFileReference()).isTrue();
assertThat(dockerHost.isRemote()).isFalse();
assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath);
@ -123,7 +110,7 @@ class ResolvedDockerHostTests { @@ -123,7 +110,7 @@ class ResolvedDockerHostTests {
@Test
void resolveWhenDockerHostAddressIsHttpReturnsAddress() {
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress("http://docker.example.com"));
new DockerConnectionConfiguration.Host("http://docker.example.com"));
assertThat(dockerHost.isLocalFileReference()).isFalse();
assertThat(dockerHost.isRemote()).isTrue();
assertThat(dockerHost.getAddress()).isEqualTo("http://docker.example.com");
@ -134,7 +121,7 @@ class ResolvedDockerHostTests { @@ -134,7 +121,7 @@ class ResolvedDockerHostTests {
@Test
void resolveWhenDockerHostAddressIsHttpsReturnsAddress() {
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress("https://docker.example.com", true, "/cert-path"));
new DockerConnectionConfiguration.Host("https://docker.example.com", true, "/cert-path"));
assertThat(dockerHost.isLocalFileReference()).isFalse();
assertThat(dockerHost.isRemote()).isTrue();
assertThat(dockerHost.getAddress()).isEqualTo("https://docker.example.com");
@ -145,7 +132,7 @@ class ResolvedDockerHostTests { @@ -145,7 +132,7 @@ class ResolvedDockerHostTests {
@Test
void resolveWhenDockerHostAddressIsTcpReturnsAddress() {
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress("tcp://192.168.99.100:2376", true, "/cert-path"));
new DockerConnectionConfiguration.Host("tcp://192.168.99.100:2376", true, "/cert-path"));
assertThat(dockerHost.isLocalFileReference()).isFalse();
assertThat(dockerHost.isRemote()).isTrue();
assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376");
@ -158,7 +145,7 @@ class ResolvedDockerHostTests { @@ -158,7 +145,7 @@ class ResolvedDockerHostTests {
String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString();
this.environment.put("DOCKER_HOST", socketFilePath);
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress("/unused"));
new DockerConnectionConfiguration.Host("/unused"));
assertThat(dockerHost.isLocalFileReference()).isTrue();
assertThat(dockerHost.isRemote()).isFalse();
assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath);
@ -171,7 +158,7 @@ class ResolvedDockerHostTests { @@ -171,7 +158,7 @@ class ResolvedDockerHostTests {
String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString();
this.environment.put("DOCKER_HOST", "unix://" + socketFilePath);
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress("/unused"));
new DockerConnectionConfiguration.Host("/unused"));
assertThat(dockerHost.isLocalFileReference()).isTrue();
assertThat(dockerHost.isRemote()).isFalse();
assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath);
@ -185,7 +172,7 @@ class ResolvedDockerHostTests { @@ -185,7 +172,7 @@ class ResolvedDockerHostTests {
this.environment.put("DOCKER_TLS_VERIFY", "1");
this.environment.put("DOCKER_CERT_PATH", "/cert-path");
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forAddress("tcp://1.1.1.1"));
new DockerConnectionConfiguration.Host("tcp://1.1.1.1"));
assertThat(dockerHost.isLocalFileReference()).isFalse();
assertThat(dockerHost.isRemote()).isTrue();
assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376");
@ -197,7 +184,7 @@ class ResolvedDockerHostTests { @@ -197,7 +184,7 @@ class ResolvedDockerHostTests {
void resolveWithDockerHostContextReturnsAddress() throws Exception {
this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json"));
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get,
DockerHostConfiguration.forContext("test-context"));
new DockerConnectionConfiguration.Context("test-context"));
assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock");
assertThat(dockerHost.isSecure()).isTrue();
assertThat(dockerHost.getCertificatePath()).isNotNull();

10
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -23,7 +23,7 @@ import java.nio.file.Path; @@ -23,7 +23,7 @@ import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@ -37,21 +37,21 @@ class HttpTransportTests { @@ -37,21 +37,21 @@ class HttpTransportTests {
@Test
void createWhenDockerHostVariableIsAddressReturnsRemote() {
HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress("tcp://192.168.1.0"));
HttpTransport transport = HttpTransport.create(new DockerConnectionConfiguration.Host("tcp://192.168.1.0"));
assertThat(transport).isInstanceOf(RemoteHttpClientTransport.class);
}
@Test
void createWhenDockerHostVariableIsFileReturnsLocal(@TempDir Path tempDir) throws IOException {
String dummySocketFilePath = Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath().toString();
HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress(dummySocketFilePath));
HttpTransport transport = HttpTransport.create(new DockerConnectionConfiguration.Host(dummySocketFilePath));
assertThat(transport).isInstanceOf(LocalHttpClientTransport.class);
}
@Test
void createWhenDockerHostVariableIsUnixSchemePrefixedFileReturnsLocal(@TempDir Path tempDir) throws IOException {
String dummySocketFilePath = "unix://" + Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath();
HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress(dummySocketFilePath));
HttpTransport transport = HttpTransport.create(new DockerConnectionConfiguration.Host(dummySocketFilePath));
assertThat(transport).isInstanceOf(LocalHttpClientTransport.class);
}

10
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -24,7 +24,7 @@ import java.nio.file.Paths; @@ -24,7 +24,7 @@ import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
import static org.assertj.core.api.Assertions.assertThat;
@ -39,7 +39,7 @@ class LocalHttpClientTransportTests { @@ -39,7 +39,7 @@ class LocalHttpClientTransportTests {
@Test
void createWhenDockerHostIsFileReturnsTransport(@TempDir Path tempDir) throws IOException {
String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString();
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(socketFilePath));
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerConnectionConfiguration.Host(socketFilePath));
LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost);
assertThat(transport).isNotNull();
assertThat(transport.getHost().toHostString()).isEqualTo(socketFilePath);
@ -48,7 +48,7 @@ class LocalHttpClientTransportTests { @@ -48,7 +48,7 @@ class LocalHttpClientTransportTests {
@Test
void createWhenDockerHostIsFileThatDoesNotExistReturnsTransport(@TempDir Path tempDir) {
String socketFilePath = Paths.get(tempDir.toString(), "dummy").toAbsolutePath().toString();
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(socketFilePath));
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerConnectionConfiguration.Host(socketFilePath));
LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost);
assertThat(transport).isNotNull();
assertThat(transport.getHost().toHostString()).isEqualTo(socketFilePath);
@ -57,7 +57,7 @@ class LocalHttpClientTransportTests { @@ -57,7 +57,7 @@ class LocalHttpClientTransportTests {
@Test
void createWhenDockerHostIsAddressReturnsTransport() {
ResolvedDockerHost dockerHost = ResolvedDockerHost
.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376"));
.from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376"));
LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost);
assertThat(transport).isNotNull();
assertThat(transport.getHost().toHostString()).isEqualTo("tcp://192.168.1.2:2376");

21
spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java

@ -23,7 +23,7 @@ import javax.net.ssl.SSLContext; @@ -23,7 +23,7 @@ import javax.net.ssl.SSLContext;
import org.apache.hc.core5.http.HttpHost;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory;
@ -42,14 +42,7 @@ class RemoteHttpClientTransportTests { @@ -42,14 +42,7 @@ class RemoteHttpClientTransportTests {
@Test
void createIfPossibleWhenDockerHostIsNotSetReturnsNull() {
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(null);
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost);
assertThat(transport).isNull();
}
@Test
void createIfPossibleWhenDockerHostIsDefaultReturnsNull() {
ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(null));
ResolvedDockerHost dockerHost = ResolvedDockerHost.from((DockerConnectionConfiguration) null);
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost);
assertThat(transport).isNull();
}
@ -57,7 +50,7 @@ class RemoteHttpClientTransportTests { @@ -57,7 +50,7 @@ class RemoteHttpClientTransportTests {
@Test
void createIfPossibleWhenDockerHostIsFileReturnsNull() {
ResolvedDockerHost dockerHost = ResolvedDockerHost
.from(DockerHostConfiguration.forAddress("unix:///var/run/socket.sock"));
.from(new DockerConnectionConfiguration.Host("unix:///var/run/socket.sock"));
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost);
assertThat(transport).isNull();
}
@ -65,7 +58,7 @@ class RemoteHttpClientTransportTests { @@ -65,7 +58,7 @@ class RemoteHttpClientTransportTests {
@Test
void createIfPossibleWhenDockerHostIsAddressReturnsTransport() {
ResolvedDockerHost dockerHost = ResolvedDockerHost
.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376"));
.from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376"));
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost);
assertThat(transport).isNotNull();
}
@ -73,7 +66,7 @@ class RemoteHttpClientTransportTests { @@ -73,7 +66,7 @@ class RemoteHttpClientTransportTests {
@Test
void createIfPossibleWhenNoTlsVerifyUsesHttp() {
ResolvedDockerHost dockerHost = ResolvedDockerHost
.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376"));
.from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376"));
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost);
assertThat(transport.getHost()).satisfies(hostOf("http", "192.168.1.2", 2376));
}
@ -83,7 +76,7 @@ class RemoteHttpClientTransportTests { @@ -83,7 +76,7 @@ class RemoteHttpClientTransportTests {
SslContextFactory sslContextFactory = mock(SslContextFactory.class);
given(sslContextFactory.forDirectory("/test-cert-path")).willReturn(SSLContext.getDefault());
ResolvedDockerHost dockerHost = ResolvedDockerHost
.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376", true, "/test-cert-path"));
.from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376", true, "/test-cert-path"));
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost, sslContextFactory);
assertThat(transport.getHost()).satisfies(hostOf("https", "192.168.1.2", 2376));
}
@ -91,7 +84,7 @@ class RemoteHttpClientTransportTests { @@ -91,7 +84,7 @@ class RemoteHttpClientTransportTests {
@Test
void createIfPossibleWhenTlsVerifyWithMissingCertPathThrowsException() {
ResolvedDockerHost dockerHost = ResolvedDockerHost
.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376", true, null));
.from(new DockerConnectionConfiguration.Host("tcp://192.168.1.2:2376", true, null));
assertThatIllegalStateException().isThrownBy(() -> RemoteHttpClientTransport.createIfPossible(dockerHost))
.withMessageContaining("Docker host TLS verification requires trust material");
}

61
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -26,7 +26,8 @@ import org.gradle.api.tasks.Input; @@ -26,7 +26,8 @@ import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
/**
* Encapsulates Docker configuration options.
@ -113,13 +114,13 @@ public abstract class DockerSpec { @@ -113,13 +114,13 @@ public abstract class DockerSpec {
}
/**
* 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.
* 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.
* @return the Docker configuration
*/
DockerConfiguration asDockerConfiguration() {
DockerConfiguration dockerConfiguration = new DockerConfiguration();
BuilderDockerConfiguration asDockerConfiguration() {
BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration();
dockerConfiguration = customizeHost(dockerConfiguration);
dockerConfiguration = dockerConfiguration.withBindHostToBuilder(getBindHostToBuilder().get());
dockerConfiguration = customizeBuilderAuthentication(dockerConfiguration);
@ -127,7 +128,7 @@ public abstract class DockerSpec { @@ -127,7 +128,7 @@ public abstract class DockerSpec {
return dockerConfiguration;
}
private DockerConfiguration customizeHost(DockerConfiguration dockerConfiguration) {
private BuilderDockerConfiguration customizeHost(BuilderDockerConfiguration dockerConfiguration) {
String context = getContext().getOrNull();
String host = getHost().getOrNull();
if (context != null && host != null) {
@ -143,36 +144,30 @@ public abstract class DockerSpec { @@ -143,36 +144,30 @@ public abstract class DockerSpec {
return dockerConfiguration;
}
private DockerConfiguration customizeBuilderAuthentication(DockerConfiguration dockerConfiguration) {
if (this.builderRegistry == null || this.builderRegistry.hasEmptyAuth()) {
return dockerConfiguration;
}
if (this.builderRegistry.hasTokenAuth() && !this.builderRegistry.hasUserAuth()) {
return dockerConfiguration.withBuilderRegistryTokenAuthentication(this.builderRegistry.getToken().get());
}
if (this.builderRegistry.hasUserAuth() && !this.builderRegistry.hasTokenAuth()) {
return dockerConfiguration.withBuilderRegistryUserAuthentication(this.builderRegistry.getUsername().get(),
this.builderRegistry.getPassword().get(), this.builderRegistry.getUrl().getOrNull(),
this.builderRegistry.getEmail().getOrNull());
}
throw new GradleException(
"Invalid Docker builder registry configuration, either token or username/password must be provided");
private BuilderDockerConfiguration customizeBuilderAuthentication(BuilderDockerConfiguration dockerConfiguration) {
return dockerConfiguration
.withBuilderRegistryAuthentication(getRegistryAuthentication("builder", this.builderRegistry, null));
}
private BuilderDockerConfiguration customizePublishAuthentication(BuilderDockerConfiguration dockerConfiguration) {
return dockerConfiguration.withPublishRegistryAuthentication(
getRegistryAuthentication("publish", this.publishRegistry, DockerRegistryAuthentication.EMPTY_USER));
}
private DockerConfiguration customizePublishAuthentication(DockerConfiguration dockerConfiguration) {
if (this.publishRegistry == null || this.publishRegistry.hasEmptyAuth()) {
return dockerConfiguration.withEmptyPublishRegistryAuthentication();
private DockerRegistryAuthentication getRegistryAuthentication(String type, DockerRegistrySpec registry,
DockerRegistryAuthentication fallback) {
if (registry == null || registry.hasEmptyAuth()) {
return fallback;
}
if (this.publishRegistry.hasTokenAuth() && !this.publishRegistry.hasUserAuth()) {
return dockerConfiguration.withPublishRegistryTokenAuthentication(this.publishRegistry.getToken().get());
if (registry.hasTokenAuth() && !registry.hasUserAuth()) {
return DockerRegistryAuthentication.token(registry.getToken().get());
}
if (this.publishRegistry.hasUserAuth() && !this.publishRegistry.hasTokenAuth()) {
return dockerConfiguration.withPublishRegistryUserAuthentication(this.publishRegistry.getUsername().get(),
this.publishRegistry.getPassword().get(), this.publishRegistry.getUrl().getOrNull(),
this.publishRegistry.getEmail().getOrNull());
if (registry.hasUserAuth() && !registry.hasTokenAuth()) {
return DockerRegistryAuthentication.user(registry.getUsername().get(), registry.getPassword().get(),
registry.getUrl().getOrNull(), registry.getEmail().getOrNull());
}
throw new GradleException(
"Invalid Docker publish registry configuration, either token or username/password must be provided");
throw new GradleException("Invalid Docker " + type
+ " registry configuration, either token or username/password must be provided");
}
/**

94
spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -24,8 +24,8 @@ import org.junit.jupiter.api.BeforeEach; @@ -24,8 +24,8 @@ import org.junit.jupiter.api.BeforeEach;
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.configuration.DockerConfiguration.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.gradle.junit.GradleProjectBuilder;
import static org.assertj.core.api.Assertions.assertThat;
@ -52,10 +52,10 @@ class DockerSpecTests { @@ -52,10 +52,10 @@ class DockerSpecTests {
@Test
void asDockerConfigurationWithDefaults() {
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
assertThat(dockerConfiguration.getHost()).isNull();
assertThat(dockerConfiguration.getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
assertThat(dockerConfiguration.connection()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -67,15 +67,14 @@ class DockerSpecTests { @@ -67,15 +67,14 @@ class DockerSpecTests {
this.dockerSpec.getHost().set("docker.example.com");
this.dockerSpec.getTlsVerify().set(true);
this.dockerSpec.getCertPath().set("/tmp/ca-cert");
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
DockerHostConfiguration host = dockerConfiguration.getHost();
assertThat(host.getAddress()).isEqualTo("docker.example.com");
assertThat(host.isSecure()).isTrue();
assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(host.getContext()).isNull();
assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse();
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection();
assertThat(host.address()).isEqualTo("docker.example.com");
assertThat(host.secure()).isTrue();
assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -85,15 +84,14 @@ class DockerSpecTests { @@ -85,15 +84,14 @@ class DockerSpecTests {
@Test
void asDockerConfigurationWithHostConfigurationNoTlsVerify() {
this.dockerSpec.getHost().set("docker.example.com");
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
DockerHostConfiguration host = dockerConfiguration.getHost();
assertThat(host.getAddress()).isEqualTo("docker.example.com");
assertThat(host.isSecure()).isFalse();
assertThat(host.getCertificatePath()).isNull();
assertThat(host.getContext()).isNull();
assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse();
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection();
assertThat(host.address()).isEqualTo("docker.example.com");
assertThat(host.secure()).isFalse();
assertThat(host.certificatePath()).isNull();
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -103,15 +101,13 @@ class DockerSpecTests { @@ -103,15 +101,13 @@ class DockerSpecTests {
@Test
void asDockerConfigurationWithContextConfiguration() {
this.dockerSpec.getContext().set("test-context");
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
DockerHostConfiguration host = dockerConfiguration.getHost();
assertThat(host.getContext()).isEqualTo("test-context");
assertThat(host.getAddress()).isNull();
assertThat(host.isSecure()).isFalse();
assertThat(host.getCertificatePath()).isNull();
assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse();
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
DockerConnectionConfiguration.Context host = (DockerConnectionConfiguration.Context) dockerConfiguration
.connection();
assertThat(host.context()).isEqualTo("test-context");
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -130,14 +126,14 @@ class DockerSpecTests { @@ -130,14 +126,14 @@ class DockerSpecTests {
void asDockerConfigurationWithBindHostToBuilder() {
this.dockerSpec.getHost().set("docker.example.com");
this.dockerSpec.getBindHostToBuilder().set(true);
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
DockerHostConfiguration host = dockerConfiguration.getHost();
assertThat(host.getAddress()).isEqualTo("docker.example.com");
assertThat(host.isSecure()).isFalse();
assertThat(host.getCertificatePath()).isNull();
assertThat(dockerConfiguration.isBindHostToBuilder()).isTrue();
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection();
assertThat(host.address()).isEqualTo("docker.example.com");
assertThat(host.secure()).isFalse();
assertThat(host.certificatePath()).isNull();
assertThat(dockerConfiguration.bindHostToBuilder()).isTrue();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -158,18 +154,18 @@ class DockerSpecTests { @@ -158,18 +154,18 @@ class DockerSpecTests {
registry.getUrl().set("https://docker2.example.com");
registry.getEmail().set("docker2@example.com");
});
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
assertThat(decoded(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"user1\"")
.contains("\"password\" : \"secret1\"")
.contains("\"email\" : \"docker1@example.com\"")
.contains("\"serveraddress\" : \"https://docker1.example.com\"");
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"user2\"")
.contains("\"password\" : \"secret2\"")
.contains("\"email\" : \"docker2@example.com\"")
.contains("\"serveraddress\" : \"https://docker2.example.com\"");
assertThat(this.dockerSpec.asDockerConfiguration().getHost()).isNull();
assertThat(dockerConfiguration.connection()).isNull();
}
@Test
@ -198,10 +194,10 @@ class DockerSpecTests { @@ -198,10 +194,10 @@ class DockerSpecTests {
void asDockerConfigurationWithTokenAuth() {
this.dockerSpec.builderRegistry((registry) -> registry.getToken().set("token1"));
this.dockerSpec.publishRegistry((registry) -> registry.getToken().set("token2"));
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
assertThat(decoded(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()))
.contains("\"identitytoken\" : \"token1\"");
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"identitytoken\" : \"token2\"");
}

4
spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java

@ -38,10 +38,10 @@ import org.springframework.boot.buildpack.platform.build.AbstractBuildLog; @@ -38,10 +38,10 @@ import org.springframework.boot.buildpack.platform.build.AbstractBuildLog;
import org.springframework.boot.buildpack.platform.build.BuildLog;
import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.Builder;
import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration;
import org.springframework.boot.buildpack.platform.build.Creator;
import org.springframework.boot.buildpack.platform.build.PullPolicy;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.boot.loader.tools.EntryWriter;
@ -262,7 +262,7 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo { @@ -262,7 +262,7 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo {
Libraries libraries = getLibraries(Collections.emptySet());
try {
BuildRequest request = getBuildRequest(libraries);
DockerConfiguration dockerConfiguration = (this.docker != null)
BuilderDockerConfiguration dockerConfiguration = (this.docker != null)
? this.docker.asDockerConfiguration(request.isPublish())
: new Docker().asDockerConfiguration(request.isPublish());
Builder builder = new Builder(new MojoBuildLog(this::getLog), dockerConfiguration);

59
spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -16,7 +16,8 @@ @@ -16,7 +16,8 @@
package org.springframework.boot.maven;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
/**
* Docker configuration options.
@ -137,14 +138,14 @@ public class Docker { @@ -137,14 +138,14 @@ public class Docker {
}
/**
* 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.
* 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 publish whether the image should be published
* @return the Docker configuration
*/
DockerConfiguration asDockerConfiguration(boolean publish) {
DockerConfiguration dockerConfiguration = new DockerConfiguration();
BuilderDockerConfiguration asDockerConfiguration(boolean publish) {
BuilderDockerConfiguration dockerConfiguration = new BuilderDockerConfiguration();
dockerConfiguration = customizeHost(dockerConfiguration);
dockerConfiguration = dockerConfiguration.withBindHostToBuilder(this.bindHostToBuilder);
dockerConfiguration = customizeBuilderAuthentication(dockerConfiguration);
@ -152,7 +153,7 @@ public class Docker { @@ -152,7 +153,7 @@ public class Docker {
return dockerConfiguration;
}
private DockerConfiguration customizeHost(DockerConfiguration dockerConfiguration) {
private BuilderDockerConfiguration customizeHost(BuilderDockerConfiguration dockerConfiguration) {
if (this.context != null && this.host != null) {
throw new IllegalArgumentException(
"Invalid Docker configuration, either context or host can be provided but not both");
@ -166,38 +167,34 @@ public class Docker { @@ -166,38 +167,34 @@ public class Docker {
return dockerConfiguration;
}
private DockerConfiguration customizeBuilderAuthentication(DockerConfiguration dockerConfiguration) {
if (this.builderRegistry == null || this.builderRegistry.isEmpty()) {
return dockerConfiguration;
}
if (this.builderRegistry.hasTokenAuth() && !this.builderRegistry.hasUserAuth()) {
return dockerConfiguration.withBuilderRegistryTokenAuthentication(this.builderRegistry.getToken());
}
if (this.builderRegistry.hasUserAuth() && !this.builderRegistry.hasTokenAuth()) {
return dockerConfiguration.withBuilderRegistryUserAuthentication(this.builderRegistry.getUsername(),
this.builderRegistry.getPassword(), this.builderRegistry.getUrl(), this.builderRegistry.getEmail());
}
throw new IllegalArgumentException(
"Invalid Docker builder registry configuration, either token or username/password must be provided");
private BuilderDockerConfiguration customizeBuilderAuthentication(BuilderDockerConfiguration dockerConfiguration) {
return dockerConfiguration
.withBuilderRegistryAuthentication(getRegistryAuthentication("builder", this.builderRegistry, null));
}
private DockerConfiguration customizePublishAuthentication(DockerConfiguration dockerConfiguration,
private BuilderDockerConfiguration customizePublishAuthentication(BuilderDockerConfiguration dockerConfiguration,
boolean publish) {
if (!publish) {
return dockerConfiguration;
}
if (this.publishRegistry == null || this.publishRegistry.isEmpty()) {
return dockerConfiguration.withEmptyPublishRegistryAuthentication();
return dockerConfiguration.withPublishRegistryAuthentication(
getRegistryAuthentication("publish", this.publishRegistry, DockerRegistryAuthentication.EMPTY_USER));
}
private DockerRegistryAuthentication getRegistryAuthentication(String type, DockerRegistry registry,
DockerRegistryAuthentication fallback) {
if (registry == null || registry.isEmpty()) {
return fallback;
}
if (this.publishRegistry.hasTokenAuth() && !this.publishRegistry.hasUserAuth()) {
return dockerConfiguration.withPublishRegistryTokenAuthentication(this.publishRegistry.getToken());
if (registry.hasTokenAuth() && !registry.hasUserAuth()) {
return DockerRegistryAuthentication.token(registry.getToken());
}
if (this.publishRegistry.hasUserAuth() && !this.publishRegistry.hasTokenAuth()) {
return dockerConfiguration.withPublishRegistryUserAuthentication(this.publishRegistry.getUsername(),
this.publishRegistry.getPassword(), this.publishRegistry.getUrl(), this.publishRegistry.getEmail());
if (registry.hasUserAuth() && !registry.hasTokenAuth()) {
return DockerRegistryAuthentication.user(registry.getUsername(), registry.getPassword(), registry.getUrl(),
registry.getEmail());
}
throw new IllegalArgumentException(
"Invalid Docker publish registry configuration, either token or username/password must be provided");
throw new IllegalArgumentException("Invalid Docker " + type
+ " registry configuration, either token or username/password must be provided");
}
/**

86
spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -20,8 +20,8 @@ import java.util.Base64; @@ -20,8 +20,8 @@ import java.util.Base64;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration;
import org.springframework.boot.buildpack.platform.build.BuilderDockerConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@ -37,10 +37,10 @@ class DockerTests { @@ -37,10 +37,10 @@ class DockerTests {
@Test
void asDockerConfigurationWithDefaults() {
Docker docker = new Docker();
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
assertThat(dockerConfiguration.getHost()).isNull();
assertThat(dockerConfiguration.getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
assertThat(dockerConfiguration.connection()).isNull();
assertThat(dockerConfiguration.builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -53,15 +53,14 @@ class DockerTests { @@ -53,15 +53,14 @@ class DockerTests {
docker.setHost("docker.example.com");
docker.setTlsVerify(true);
docker.setCertPath("/tmp/ca-cert");
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
DockerHostConfiguration host = dockerConfiguration.getHost();
assertThat(host.getAddress()).isEqualTo("docker.example.com");
assertThat(host.isSecure()).isTrue();
assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(host.getContext()).isNull();
assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse();
assertThat(createDockerConfiguration(docker).getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection();
assertThat(host.address()).isEqualTo("docker.example.com");
assertThat(host.secure()).isTrue();
assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -72,15 +71,13 @@ class DockerTests { @@ -72,15 +71,13 @@ class DockerTests {
void asDockerConfigurationWithContextConfiguration() {
Docker docker = new Docker();
docker.setContext("test-context");
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
DockerHostConfiguration host = dockerConfiguration.getHost();
assertThat(host.getContext()).isEqualTo("test-context");
assertThat(host.getAddress()).isNull();
assertThat(host.isSecure()).isFalse();
assertThat(host.getCertificatePath()).isNull();
assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse();
assertThat(createDockerConfiguration(docker).getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
DockerConnectionConfiguration.Context context = (DockerConnectionConfiguration.Context) dockerConfiguration
.connection();
assertThat(context.context()).isEqualTo("test-context");
assertThat(dockerConfiguration.bindHostToBuilder()).isFalse();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -103,14 +100,14 @@ class DockerTests { @@ -103,14 +100,14 @@ class DockerTests {
docker.setTlsVerify(true);
docker.setCertPath("/tmp/ca-cert");
docker.setBindHostToBuilder(true);
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
DockerHostConfiguration host = dockerConfiguration.getHost();
assertThat(host.getAddress()).isEqualTo("docker.example.com");
assertThat(host.isSecure()).isTrue();
assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(dockerConfiguration.isBindHostToBuilder()).isTrue();
assertThat(createDockerConfiguration(docker).getBuilderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
DockerConnectionConfiguration.Host host = (DockerConnectionConfiguration.Host) dockerConfiguration.connection();
assertThat(host.address()).isEqualTo("docker.example.com");
assertThat(host.secure()).isTrue();
assertThat(host.certificatePath()).isEqualTo("/tmp/ca-cert");
assertThat(dockerConfiguration.bindHostToBuilder()).isTrue();
assertThat(createDockerConfiguration(docker).builderRegistryAuthentication()).isNull();
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"\"")
.contains("\"password\" : \"\"")
.contains("\"email\" : \"\"")
@ -124,13 +121,13 @@ class DockerTests { @@ -124,13 +121,13 @@ class DockerTests {
new Docker.DockerRegistry("user1", "secret1", "https://docker1.example.com", "docker1@example.com"));
docker.setPublishRegistry(
new Docker.DockerRegistry("user2", "secret2", "https://docker2.example.com", "docker2@example.com"));
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
assertThat(decoded(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"user1\"")
.contains("\"password\" : \"secret1\"")
.contains("\"email\" : \"docker1@example.com\"")
.contains("\"serveraddress\" : \"https://docker1.example.com\"");
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"username\" : \"user2\"")
.contains("\"password\" : \"secret2\"")
.contains("\"email\" : \"docker2@example.com\"")
@ -160,8 +157,8 @@ class DockerTests { @@ -160,8 +157,8 @@ class DockerTests {
Docker docker = new Docker();
docker.setPublishRegistry(
new Docker.DockerRegistry("user", null, "https://docker.example.com", "docker@example.com"));
DockerConfiguration dockerConfiguration = docker.asDockerConfiguration(false);
assertThat(dockerConfiguration.getPublishRegistryAuthentication()).isNull();
BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(false);
assertThat(dockerConfiguration.publishRegistryAuthentication()).isNull();
}
@Test
@ -169,10 +166,10 @@ class DockerTests { @@ -169,10 +166,10 @@ class DockerTests {
Docker docker = new Docker();
docker.setBuilderRegistry(new Docker.DockerRegistry("token1"));
docker.setPublishRegistry(new Docker.DockerRegistry("token2"));
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
BuilderDockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
assertThat(decoded(dockerConfiguration.builderRegistryAuthentication().getAuthHeader()))
.contains("\"identitytoken\" : \"token1\"");
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
assertThat(decoded(dockerConfiguration.publishRegistryAuthentication().getAuthHeader()))
.contains("\"identitytoken\" : \"token2\"");
}
@ -196,13 +193,12 @@ class DockerTests { @@ -196,13 +193,12 @@ class DockerTests {
dockerRegistry.setToken("token");
Docker docker = new Docker();
docker.setPublishRegistry(dockerRegistry);
DockerConfiguration dockerConfiguration = docker.asDockerConfiguration(false);
assertThat(dockerConfiguration.getPublishRegistryAuthentication()).isNull();
BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(false);
assertThat(dockerConfiguration.publishRegistryAuthentication()).isNull();
}
private DockerConfiguration createDockerConfiguration(Docker docker) {
private BuilderDockerConfiguration createDockerConfiguration(Docker docker) {
return docker.asDockerConfiguration(true);
}
String decoded(String value) {

Loading…
Cancel
Save