Merge 355478480b
into 53e0685c7a
This commit is contained in:
commit
5bd1c6678b
|
@ -27,6 +27,7 @@ import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListe
|
|||
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.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;
|
||||
|
@ -102,9 +103,8 @@ public class Builder {
|
|||
Assert.notNull(request, "'request' must not be null");
|
||||
this.log.start(request);
|
||||
validateBindings(request.getBindings());
|
||||
String domain = request.getBuilder().getDomain();
|
||||
PullPolicy pullPolicy = request.getPullPolicy();
|
||||
ImageFetcher imageFetcher = new ImageFetcher(domain, getBuilderAuthHeader(), pullPolicy,
|
||||
ImageFetcher imageFetcher = new ImageFetcher(getBuilderRegistryAuthentication(), pullPolicy,
|
||||
request.getImagePlatform());
|
||||
Image builderImage = imageFetcher.fetchImage(ImageType.BUILDER, request.getBuilder());
|
||||
BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage);
|
||||
|
@ -203,18 +203,20 @@ 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());
|
||||
this.docker.image().push(reference, listener, getPublishAuthHeader(reference));
|
||||
this.log.pushedImage(reference);
|
||||
}
|
||||
|
||||
private String getBuilderAuthHeader() {
|
||||
return (this.dockerConfiguration != null && this.dockerConfiguration.getBuilderRegistryAuthentication() != null)
|
||||
? this.dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader() : null;
|
||||
private DockerRegistryAuthentication getBuilderRegistryAuthentication() {
|
||||
if (this.dockerConfiguration != null) {
|
||||
return this.dockerConfiguration.getBuilderRegistryAuthentication();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getPublishAuthHeader() {
|
||||
private String getPublishAuthHeader(ImageReference imageReference) {
|
||||
return (this.dockerConfiguration != null && this.dockerConfiguration.getPublishRegistryAuthentication() != null)
|
||||
? this.dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader() : null;
|
||||
? this.dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader(imageReference) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,17 +224,14 @@ public class Builder {
|
|||
*/
|
||||
private class ImageFetcher {
|
||||
|
||||
private final String domain;
|
||||
|
||||
private final String authHeader;
|
||||
private final DockerRegistryAuthentication authentication;
|
||||
|
||||
private final PullPolicy pullPolicy;
|
||||
|
||||
private ImagePlatform defaultPlatform;
|
||||
|
||||
ImageFetcher(String domain, String authHeader, PullPolicy pullPolicy, ImagePlatform platform) {
|
||||
this.domain = domain;
|
||||
this.authHeader = authHeader;
|
||||
ImageFetcher(DockerRegistryAuthentication authentication, PullPolicy pullPolicy, ImagePlatform platform) {
|
||||
this.authentication = authentication;
|
||||
this.pullPolicy = pullPolicy;
|
||||
this.defaultPlatform = platform;
|
||||
}
|
||||
|
@ -240,27 +239,25 @@ 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.format("%s '%s' must be pulled from the '%s' authenticated registry",
|
||||
StringUtils.capitalize(type.getDescription()), reference, this.domain));
|
||||
String authHeader = getAuthHeader(reference);
|
||||
if (this.pullPolicy == PullPolicy.ALWAYS) {
|
||||
return checkPlatformMismatch(pullImage(reference, type), reference);
|
||||
return checkPlatformMismatch(pullImage(authHeader, reference, type), reference);
|
||||
}
|
||||
try {
|
||||
return checkPlatformMismatch(Builder.this.docker.image().inspect(reference), reference);
|
||||
}
|
||||
catch (DockerEngineException ex) {
|
||||
if (this.pullPolicy == PullPolicy.IF_NOT_PRESENT && ex.getStatusCode() == 404) {
|
||||
return checkPlatformMismatch(pullImage(reference, type), reference);
|
||||
return checkPlatformMismatch(pullImage(authHeader, reference, type), reference);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private Image pullImage(ImageReference reference, ImageType imageType) throws IOException {
|
||||
private Image pullImage(String authHeader, 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);
|
||||
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);
|
||||
|
@ -278,6 +275,10 @@ public class Builder {
|
|||
return image;
|
||||
}
|
||||
|
||||
private String getAuthHeader(ImageReference reference) {
|
||||
return (this.authentication != null) ? this.authentication.getAuthHeader(reference) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class PlatformMismatchException extends RuntimeException {
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 java.lang.invoke.MethodHandles;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.json.MappedObject;
|
||||
|
||||
/**
|
||||
* A class that represents credentials for a server.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class Credentials extends MappedObject {
|
||||
|
||||
/**
|
||||
* If the secret being stored is an identity token, the username should be set to
|
||||
* {@code <token>}.
|
||||
*/
|
||||
private static final String TOKEN_USERNAME = "<token>";
|
||||
|
||||
private final String serverUrl;
|
||||
|
||||
private final String username;
|
||||
|
||||
private final String secret;
|
||||
|
||||
/**
|
||||
* Create a new {@link Credentials} instance from the given JSON node.
|
||||
* @param node the JSON node to read from
|
||||
*/
|
||||
Credentials(JsonNode node) {
|
||||
super(node, MethodHandles.lookup());
|
||||
this.serverUrl = valueAt("/ServerURL", String.class);
|
||||
this.username = valueAt("/Username", String.class);
|
||||
this.secret = valueAt("/Secret", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the secret being stored is an identity token.
|
||||
* @return true if the secret is an identity token, false otherwise
|
||||
*/
|
||||
boolean isIdentityToken() {
|
||||
return TOKEN_USERNAME.equals(this.username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server URL associated with this credential.
|
||||
* @return the server URL
|
||||
*/
|
||||
String getServerUrl() {
|
||||
return this.serverUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username associated with the credential.
|
||||
* @return the username
|
||||
*/
|
||||
String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secret associated with this credential.
|
||||
* @return the secret
|
||||
*/
|
||||
String getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.Platform;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link DockerCredentialHelper} that retrieves Docker
|
||||
* credentials using a specified credential helper.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class DefaultDockerCredentialHelper implements DockerCredentialHelper {
|
||||
|
||||
private static final String USR_LOCAL_BIN = "/usr/local/bin/";
|
||||
|
||||
private static final String CREDENTIALS_NOT_FOUND = "credentials not found in native keychain";
|
||||
|
||||
private static final String CREDENTIALS_URL_MISSING = "no credentials server URL";
|
||||
|
||||
private static final String CREDENTIALS_USERNAME_MISSING = "no credentials username";
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultDockerCredentialHelper} instance using the specified
|
||||
* credential helper name.
|
||||
* @param name the full name of the Docker credential helper, e.g.,
|
||||
* {@code docker-credential-osxkeychain}, {@code docker-credential-desktop}, etc.
|
||||
*/
|
||||
DefaultDockerCredentialHelper(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Credentials get(String serverUrl) throws IOException {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(true);
|
||||
if (Platform.isWindows()) {
|
||||
processBuilder.command("cmd", "/c");
|
||||
}
|
||||
processBuilder.command(this.name, "get");
|
||||
Process process = startProcess(processBuilder);
|
||||
try (OutputStream os = process.getOutputStream()) {
|
||||
os.write(serverUrl.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
int exitCode;
|
||||
try {
|
||||
exitCode = process.waitFor();
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
if (exitCode != 0) {
|
||||
try (InputStream is = process.getInputStream()) {
|
||||
String message = new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||
if (isCredentialsNotFoundError(message)) {
|
||||
return null;
|
||||
}
|
||||
throw new IOException("%s' exited with code %d: %s".formatted(process, exitCode, message));
|
||||
}
|
||||
}
|
||||
try (InputStream is = process.getInputStream()) {
|
||||
return new Credentials(SharedObjectMapper.get().readTree(is));
|
||||
}
|
||||
}
|
||||
|
||||
private Process startProcess(ProcessBuilder processBuilder) throws IOException {
|
||||
try {
|
||||
return processBuilder.start();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
if (!Platform.isMac()) {
|
||||
throw ex;
|
||||
}
|
||||
List<String> command = new ArrayList<>(processBuilder.command());
|
||||
command.set(0, USR_LOCAL_BIN + command.get(0));
|
||||
return processBuilder.command(command).start();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCredentialsNotFoundError(String message) {
|
||||
return switch (message.trim()) {
|
||||
case CREDENTIALS_NOT_FOUND, CREDENTIALS_URL_MISSING, CREDENTIALS_USERNAME_MISSING -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.Auth;
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerConfig;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.system.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
/**
|
||||
* A default implementation of {@link DockerRegistryAuthentication} that provides
|
||||
* authentication using a Docker configuration file, leveraging both credential helpers
|
||||
* and static credentials.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class DefaultDockerRegistryAuthentication implements DockerRegistryAuthentication {
|
||||
|
||||
private static final String DEFAULT_DOMAIN = "docker.io";
|
||||
|
||||
private static final String INDEX_URL = "https://index.docker.io/v1/";
|
||||
|
||||
private final Map<String, String> cache = new ConcurrentHashMap<>();
|
||||
|
||||
private final Function<String, DockerCredentialHelper> dockerCredentialHelperFactory;
|
||||
|
||||
private final Supplier<DockerConfig> dockerConfigSupplier;
|
||||
|
||||
DefaultDockerRegistryAuthentication() {
|
||||
this(Environment.SYSTEM, DockerCredentialHelper::ofSuffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code DockerConfigFileDockerRegistryAuthentication} instance using the
|
||||
* provided {@link Environment} and {@link DockerCredentialHelper} factory.
|
||||
* @param environment the environment from which to retrieve environment variables
|
||||
* @param dockerCredentialHelperFactory the factory to create a
|
||||
* {@link DockerCredentialHelper} instance based on the provided credential helper
|
||||
* name. The factory is invoked with the credential helper name. For example,
|
||||
* {@code desktop}, {@code osxkeychain}, etc.
|
||||
*/
|
||||
DefaultDockerRegistryAuthentication(Environment environment,
|
||||
Function<String, DockerCredentialHelper> dockerCredentialHelperFactory) {
|
||||
this.dockerConfigSupplier = SingletonSupplier
|
||||
.of(() -> DockerConfigurationMetadata.from(environment).getConfiguration());
|
||||
this.dockerCredentialHelperFactory = dockerCredentialHelperFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthHeader(ImageReference imageReference) {
|
||||
// TODO: Should the authentication header be cached? The Docker CLI does not cache
|
||||
// it, whereas testcontainers does. Is caching safe in this context?
|
||||
return this.cache.computeIfAbsent(getServerUrl(imageReference), (serverUrl) -> {
|
||||
DockerConfig dockerConfig = this.dockerConfigSupplier.get();
|
||||
return getAuthentication(dockerConfig, serverUrl).getAuthHeader(imageReference);
|
||||
});
|
||||
}
|
||||
|
||||
private String getServerUrl(ImageReference imageReference) {
|
||||
String domain = imageReference.getDomain();
|
||||
return DEFAULT_DOMAIN.equals(domain) ? INDEX_URL : domain;
|
||||
}
|
||||
|
||||
private DockerRegistryAuthentication getAuthentication(DockerConfig dockerConfig, String serverUrl) {
|
||||
RegistryAuth auth = getAuth(dockerConfig, serverUrl);
|
||||
Credentials credentials = getCredentials(dockerConfig, serverUrl);
|
||||
if (credentials != null && credentials.isIdentityToken()) {
|
||||
return new DockerRegistryTokenAuthentication(credentials.getSecret());
|
||||
}
|
||||
if (credentials != null) {
|
||||
return new DockerRegistryUserAuthentication(credentials.getUsername(), credentials.getSecret(),
|
||||
(credentials.getServerUrl() != null) ? credentials.getServerUrl() : serverUrl,
|
||||
(auth != null) ? auth.email() : null);
|
||||
}
|
||||
if (auth != null) {
|
||||
return new DockerRegistryUserAuthentication(auth.username(), auth.password(), auth.serverUrl(),
|
||||
auth.email());
|
||||
}
|
||||
return new DockerRegistryUserAuthentication("", "", "", "");
|
||||
}
|
||||
|
||||
private Credentials getCredentials(DockerConfig dockerConfig, String serverUrl) {
|
||||
try {
|
||||
// TODO: The Docker CLI prioritizes the credential helper over the
|
||||
// credential store, even when the helper is empty:
|
||||
// https://github.com/docker/cli/blob/c8f9187157753e366ee9f25524b56f90913b47a5/cli/config/configfile/file.go#L280
|
||||
// Should the credential store be considered if the credential helper is
|
||||
// empty? The latter approach is followed by Testcontainers.
|
||||
String helper = dockerConfig.getCredHelpers().getOrDefault(serverUrl, dockerConfig.getCredsStore());
|
||||
return StringUtils.hasText(helper) ? this.dockerCredentialHelperFactory.apply(helper).get(serverUrl) : null;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// TODO: Errors encountered while retrieving credentials from the
|
||||
// credential helpers are ignored by the Docker CLI.
|
||||
System.err.printf("Error retrieving credentials for '%s' due to: %s%n", serverUrl, ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private RegistryAuth getAuth(DockerConfig dockerConfig, String serverUrl) {
|
||||
return dockerConfig.getAuths()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter((entry) -> entry.getKey().equals(serverUrl) || entry.getKey().endsWith("://" + serverUrl))
|
||||
.map((entry) -> new RegistryAuth(entry.getKey(), entry.getValue()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private record RegistryAuth(String serverUrl, String username, String password, String email) {
|
||||
private RegistryAuth(String serverUrl, Auth auth) {
|
||||
this(serverUrl, auth.getUsername(), auth.getPassword(), auth.getEmail());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -94,6 +94,11 @@ public final class DockerConfiguration {
|
|||
this.publishAuthentication, this.bindHostToBuilder);
|
||||
}
|
||||
|
||||
public DockerConfiguration withBuilderRegistryDefaultAuthentication() {
|
||||
return new DockerConfiguration(this.host, new DefaultDockerRegistryAuthentication(), this.publishAuthentication,
|
||||
this.bindHostToBuilder);
|
||||
}
|
||||
|
||||
public DockerConfiguration withPublishRegistryTokenAuthentication(String token) {
|
||||
Assert.notNull(token, "'token' must not be null");
|
||||
return new DockerConfiguration(this.host, this.builderAuthentication,
|
||||
|
@ -113,6 +118,11 @@ public final class DockerConfiguration {
|
|||
new DockerRegistryUserAuthentication("", "", "", ""), this.bindHostToBuilder);
|
||||
}
|
||||
|
||||
public DockerConfiguration withPublishRegistryDefaultAuthentication() {
|
||||
return new DockerConfiguration(this.host, this.builderAuthentication, new DefaultDockerRegistryAuthentication(),
|
||||
this.bindHostToBuilder);
|
||||
}
|
||||
|
||||
public static class DockerHostConfiguration {
|
||||
|
||||
private final String address;
|
||||
|
|
|
@ -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,11 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
@ -148,15 +152,50 @@ final class DockerConfigurationMetadata {
|
|||
|
||||
private final String currentContext;
|
||||
|
||||
private final String credsStore;
|
||||
|
||||
private final Map<String, String> credHelpers;
|
||||
|
||||
private final Map<String, Auth> auths;
|
||||
|
||||
private DockerConfig(JsonNode node) {
|
||||
super(node, MethodHandles.lookup());
|
||||
this.currentContext = valueAt("/currentContext", String.class);
|
||||
this.credsStore = valueAt("/credsStore", String.class);
|
||||
this.credHelpers = extractCredHelpers();
|
||||
this.auths = extractAuths();
|
||||
}
|
||||
|
||||
private Map<String, Auth> extractAuths() {
|
||||
Map<String, Auth> auths = new LinkedHashMap<>();
|
||||
getNode().at("/auths")
|
||||
.fields()
|
||||
.forEachRemaining((entry) -> auths.put(entry.getKey(), new Auth(entry.getValue())));
|
||||
return Map.copyOf(auths);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, String> extractCredHelpers() {
|
||||
Map<String, String> credHelpers = valueAt("/credHelpers", Map.class);
|
||||
return (credHelpers != null) ? Map.copyOf(credHelpers) : Collections.emptyMap();
|
||||
}
|
||||
|
||||
String getCurrentContext() {
|
||||
return this.currentContext;
|
||||
}
|
||||
|
||||
String getCredsStore() {
|
||||
return this.credsStore;
|
||||
}
|
||||
|
||||
Map<String, String> getCredHelpers() {
|
||||
return this.credHelpers;
|
||||
}
|
||||
|
||||
Map<String, Auth> getAuths() {
|
||||
return this.auths;
|
||||
}
|
||||
|
||||
static DockerConfig fromJson(String json) throws JsonProcessingException {
|
||||
return new DockerConfig(SharedObjectMapper.get().readTree(json));
|
||||
}
|
||||
|
@ -167,6 +206,45 @@ final class DockerConfigurationMetadata {
|
|||
|
||||
}
|
||||
|
||||
static final class Auth extends MappedObject {
|
||||
|
||||
private final String username;
|
||||
|
||||
private final String password;
|
||||
|
||||
private final String email;
|
||||
|
||||
Auth(JsonNode node) {
|
||||
super(node, MethodHandles.lookup());
|
||||
String username = valueAt("/username", String.class);
|
||||
String password = valueAt("/password", String.class);
|
||||
String auth = valueAt("/auth", String.class);
|
||||
if (auth != null) {
|
||||
String[] parts = new String(Base64.getDecoder().decode(auth)).split(":", 2);
|
||||
if (parts.length == 2) {
|
||||
username = parts[0];
|
||||
password = parts[1];
|
||||
}
|
||||
}
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.email = valueAt("/email", String.class);
|
||||
}
|
||||
|
||||
String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
String getEmail() {
|
||||
return this.email;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class DockerContext extends MappedObject {
|
||||
|
||||
private final String dockerHost;
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 java.io.IOException;
|
||||
|
||||
/**
|
||||
* Docker credential helper used to retrieve credentials for servers.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
interface DockerCredentialHelper {
|
||||
|
||||
/**
|
||||
* Retrieves the credential associated with the specified URL.
|
||||
* @param serverUrl the server URL for which the credential is requested
|
||||
* @return the {@link Credentials} containing authentication information for the given
|
||||
* server, or {@code null} if no credential is available for the given server.
|
||||
* @throws IOException if an I/O error occurs while retrieving the credential.
|
||||
*/
|
||||
Credentials get(String serverUrl) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a {@link DockerCredentialHelper} instance using the specified suffix.
|
||||
* @param suffix the suffix of the credential helper, for example {@code gcr},
|
||||
* {@code ecr-login}, {@code desktop}, {@code osxkeychain}, etc.
|
||||
* @return a {@link DefaultDockerCredentialHelper} instance, with the full name of the
|
||||
* helper. e.g., {@code docker-credential-gcr}
|
||||
*/
|
||||
static DockerCredentialHelper ofSuffix(String suffix) {
|
||||
return new DefaultDockerCredentialHelper("docker-credential-" + suffix.trim());
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
|
||||
/**
|
||||
* Docker registry authentication configuration.
|
||||
*
|
||||
|
@ -27,7 +29,26 @@ public interface DockerRegistryAuthentication {
|
|||
/**
|
||||
* Returns the auth header that should be used for docker authentication.
|
||||
* @return the auth header
|
||||
* @throws IllegalStateException if the auth header cannot be created
|
||||
* @deprecated since 3.5.0 for removal in 4.0.0 in favor of
|
||||
* {@link #getAuthHeader(ImageReference)}
|
||||
*
|
||||
*/
|
||||
String getAuthHeader();
|
||||
@Deprecated(since = "3.5.0", forRemoval = true)
|
||||
default String getAuthHeader() {
|
||||
throw new IllegalStateException("Use getAuthHeader(ImageReference) instead of getAuthHeader()");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the auth header that should be used for docker authentication.
|
||||
* @param imageReference the image reference
|
||||
* @return the auth header
|
||||
* @throws IllegalStateException if the auth header cannot be created
|
||||
* @since 3.5.0
|
||||
*
|
||||
*/
|
||||
default String getAuthHeader(ImageReference imageReference) {
|
||||
return getAuthHeader();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,6 +21,7 @@ import java.util.Base64;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||
|
||||
/**
|
||||
|
@ -35,6 +36,12 @@ class JsonEncodedDockerRegistryAuthentication implements DockerRegistryAuthentic
|
|||
private String authHeader;
|
||||
|
||||
@Override
|
||||
public String getAuthHeader(ImageReference imageReference) {
|
||||
return this.authHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
public String getAuthHeader() {
|
||||
return this.authHeader;
|
||||
}
|
||||
|
|
|
@ -142,11 +142,14 @@ class BuilderTests {
|
|||
.withPublishRegistryTokenAuthentication("publish token");
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)))))
|
||||
.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())))
|
||||
any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
|
||||
BuildRequest request = getTestRequest().withPublish(true);
|
||||
|
@ -156,13 +159,15 @@ class BuilderTests {
|
|||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))));
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of("docker.io/cloudfoundry/run:base-cnb"))));
|
||||
then(docker.image()).should()
|
||||
.push(eq(request.getName()), any(),
|
||||
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()));
|
||||
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader(request.getName())));
|
||||
then(docker.image()).should().load(archive.capture(), any());
|
||||
then(docker.image()).should().remove(archive.getValue().getTag(), true);
|
||||
then(docker.image()).shouldHaveNoMoreInteractions();
|
||||
|
@ -388,11 +393,14 @@ class BuilderTests {
|
|||
.withPublishRegistryTokenAuthentication("publish token");
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)))))
|
||||
.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())))
|
||||
any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))))
|
||||
.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"));
|
||||
|
@ -403,17 +411,20 @@ class BuilderTests {
|
|||
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))));
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of("docker.io/cloudfoundry/run:base-cnb"))));
|
||||
then(docker.image()).should()
|
||||
.push(eq(request.getName()), any(),
|
||||
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()));
|
||||
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader(request.getName())));
|
||||
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()));
|
||||
eq(dockerConfiguration.getPublishRegistryAuthentication()
|
||||
.getAuthHeader(ImageReference.of("my-application:1.2.3"))));
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
then(docker.image()).should().load(archive.capture(), any());
|
||||
then(docker.image()).should().remove(archive.getValue().getTag(), true);
|
||||
|
@ -487,42 +498,6 @@ class BuilderTests {
|
|||
.withMessage("Builder lifecycle 'creator' failed with status code 9");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenDetectedRunImageInDifferentAuthenticatedRegistryThrowsException() throws Exception {
|
||||
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())))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
|
||||
BuildRequest request = getTestRequest();
|
||||
assertThatIllegalStateException().isThrownBy(() -> builder.build(request))
|
||||
.withMessage(
|
||||
"Run image 'example.com/custom/run:latest' must be pulled from the 'docker.io' authenticated registry");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenRequestedRunImageInDifferentAuthenticatedRegistryThrowsException() throws Exception {
|
||||
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())))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
|
||||
BuildRequest request = getTestRequest().withRunImage(ImageReference.of("example.com/custom/run:latest"));
|
||||
assertThatIllegalStateException().isThrownBy(() -> builder.build(request))
|
||||
.withMessage(
|
||||
"Run image 'example.com/custom/run:latest' must be pulled from the 'docker.io' authenticated registry");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenRequestedBuildpackNotInBuilderThrowsException() throws Exception {
|
||||
TestPrintStream out = new TestPrintStream();
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithResource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Credentials}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class CredentialsTests {
|
||||
|
||||
@Test
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"ServerURL": "https://index.docker.io/v1/",
|
||||
"Username": "<token>",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
void shouldCreateIdentityTokenCredentials() throws IOException {
|
||||
Credentials credentials = getCredentials("credentials.json");
|
||||
assertThat(credentials.getUsername()).isEqualTo("<token>");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
assertThat(credentials.getServerUrl()).isEqualTo("https://index.docker.io/v1/");
|
||||
assertThat(credentials.isIdentityToken()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"ServerURL": "https://index.docker.io/v1/",
|
||||
"Username": "user",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
void shouldCreateUsernamePasswordCredentials() throws IOException {
|
||||
Credentials credentials = getCredentials("credentials.json");
|
||||
assertThat(credentials.getUsername()).isEqualTo("user");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
assertThat(credentials.getServerUrl()).isEqualTo("https://index.docker.io/v1/");
|
||||
assertThat(credentials.isIdentityToken()).isFalse();
|
||||
}
|
||||
|
||||
private Credentials getCredentials(String name) throws IOException {
|
||||
try (InputStream inputStream = new ClassPathResource(name).getInputStream()) {
|
||||
return new Credentials(SharedObjectMapper.get().readTree(inputStream));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.sun.jna.Platform;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultDockerCredentialHelper}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class DefaultDockerCredentialHelperTests {
|
||||
|
||||
private DefaultDockerCredentialHelper helper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
String name = "docker-credential-test";
|
||||
if (Platform.isWindows()) {
|
||||
name += ".bat";
|
||||
}
|
||||
this.helper = new DefaultDockerCredentialHelper(
|
||||
new ClassPathResource(name, getClass()).getFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnCredentialsForUser() throws IOException {
|
||||
Credentials credentials = this.helper.get("user.example.com");
|
||||
assertThat(credentials).isNotNull();
|
||||
assertThat(credentials.isIdentityToken()).isFalse();
|
||||
assertThat(credentials.getServerUrl()).isEqualTo("user.example.com");
|
||||
assertThat(credentials.getUsername()).isEqualTo("username");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnCredentialsForToken() throws IOException {
|
||||
Credentials credentials = this.helper.get("token.example.com");
|
||||
assertThat(credentials).isNotNull();
|
||||
assertThat(credentials.isIdentityToken()).isTrue();
|
||||
assertThat(credentials.getServerUrl()).isEqualTo("token.example.com");
|
||||
assertThat(credentials.getUsername()).isEqualTo("<token>");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNullCredentialsWhenCredentialsNotFoundError() throws IOException {
|
||||
Credentials credentials = this.helper.get("credentials.missing.example.com");
|
||||
assertThat(credentials).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNullCredentialsWhenUsernameMissingError() throws IOException {
|
||||
Credentials credentials = this.helper.get("username.missing.example.com");
|
||||
assertThat(credentials).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNullCredentialsWhenServerUrlMissingError() throws IOException {
|
||||
Credentials credentials = this.helper.get("url.missing.example.com");
|
||||
assertThat(credentials).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIOExceptionWhenUnknownError() {
|
||||
assertThatIOException().isThrownBy(() -> this.helper.get("invalid.example.com"))
|
||||
.withMessageContaining("Unknown error");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIOExceptionWhenCommandDoesNotExist() {
|
||||
String name = "docker-credential-%s".formatted(UUID.randomUUID().toString());
|
||||
assertThatIOException().isThrownBy(() -> new DefaultDockerCredentialHelper(name).get("invalid.example.com"))
|
||||
.withMessageContaining(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||
import org.springframework.boot.testsupport.classpath.resources.ResourcesRoot;
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithResource;
|
||||
import org.springframework.boot.testsupport.system.CapturedOutput;
|
||||
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultDockerRegistryAuthentication}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class DefaultDockerRegistryAuthenticationTests {
|
||||
|
||||
private final Map<String, String> environment = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, DockerCredentialHelper> dockerCredentialHelpers = new LinkedHashMap<>();
|
||||
|
||||
private final DefaultDockerRegistryAuthentication authentication = new DefaultDockerRegistryAuthentication(
|
||||
this.environment::get, this.dockerCredentialHelpers::get);
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"https://index.docker.io/v1/": {
|
||||
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
|
||||
"email": "test@gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateAuthHeaderFromAuthForDockerDomain(@ResourcesRoot Path directory) throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest");
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "https://index.docker.io/v1/")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "password")
|
||||
.containsEntry("email", "test@gmail.com");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"https://index.docker.io/v1/": {
|
||||
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
|
||||
"email": "test@gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateAuthHeaderFromAuthForLegacyDockerDomain(@ResourcesRoot Path directory) throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("index.docker.io/ubuntu:latest");
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "https://index.docker.io/v1/")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "password")
|
||||
.containsEntry("email", "test@gmail.com");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"my-registry.example.com": {
|
||||
"auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateAuthHeaderFromAuthForCustomDomain(@ResourcesRoot Path directory) throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("my-registry.example.com/ubuntu:latest");
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "my-registry.example.com")
|
||||
.containsEntry("username", "customUser")
|
||||
.containsEntry("password", "customPass")
|
||||
.containsEntry("email", null);
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"https://my-registry.example.com": {
|
||||
"auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateAuthHeaderFromAuthForCustomDomainWithLegacyFormat(@ResourcesRoot Path directory)
|
||||
throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("my-registry.example.com/ubuntu:latest");
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "https://my-registry.example.com")
|
||||
.containsEntry("username", "customUser")
|
||||
.containsEntry("password", "customPass")
|
||||
.containsEntry("email", null);
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateAuthHeaderFromEmptyCredentialsWhenEmptyConfig(@ResourcesRoot Path directory) throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest");
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "")
|
||||
.containsEntry("username", "")
|
||||
.containsEntry("password", "")
|
||||
.containsEntry("email", "");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"credsStore": "desktop"
|
||||
}
|
||||
""")
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"ServerURL": "https://index.docker.io/v1/",
|
||||
"Username": "<token>",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateAuthHeaderFromCredsStore(@ResourcesRoot Path directory) throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest");
|
||||
DockerCredentialHelper helper = mock(DockerCredentialHelper.class);
|
||||
this.dockerCredentialHelpers.put("desktop", helper);
|
||||
given(helper.get("https://index.docker.io/v1/")).willReturn(getCredentials("credentials.json"));
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(1).containsEntry("identitytoken", "secret");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"gcr.io": {
|
||||
"email": "test@gmail.com"
|
||||
}
|
||||
},
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr"
|
||||
}
|
||||
}
|
||||
""")
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"ServerURL": "https://my-gcr.io",
|
||||
"Username": "username",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateAuthHeaderFromCredHelperAndUseEmailFromAuth(@ResourcesRoot Path directory) throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
DockerCredentialHelper helper = mock(DockerCredentialHelper.class);
|
||||
this.dockerCredentialHelpers.put("gcr", helper);
|
||||
given(helper.get("gcr.io")).willReturn(getCredentials("credentials.json"));
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "https://my-gcr.io")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "secret")
|
||||
.containsEntry("email", "test@gmail.com");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr"
|
||||
}
|
||||
}
|
||||
""")
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"Username": "username",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateAuthHeaderFromCredHelperAndUseProvidedServerUrl(@ResourcesRoot Path directory) throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
DockerCredentialHelper helper = mock(DockerCredentialHelper.class);
|
||||
this.dockerCredentialHelpers.put("gcr", helper);
|
||||
given(helper.get("gcr.io")).willReturn(getCredentials("credentials.json"));
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "gcr.io")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "secret")
|
||||
.containsEntry("email", null);
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"gcr.io": {
|
||||
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
|
||||
"email": "test@gmail.com"
|
||||
}
|
||||
},
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr"
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateHeaderFromAuthWhenFailedToGetCredentials(@ResourcesRoot Path directory, CapturedOutput output)
|
||||
throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
DockerCredentialHelper helper = mock(DockerCredentialHelper.class);
|
||||
this.dockerCredentialHelpers.put("gcr", helper);
|
||||
given(helper.get("gcr.io")).willThrow(new IOException("Failed to obtain credentials for registry"));
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(output.getErr())
|
||||
.contains("Error retrieving credentials for 'gcr.io' due to: Failed to obtain credentials for registry");
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "gcr.io")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "password")
|
||||
.containsEntry("email", "test@gmail.com");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr"
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void shouldCreateEmptyAuthHeaderWhenFailedToGetCredentials(@ResourcesRoot Path directory, CapturedOutput output)
|
||||
throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
DockerCredentialHelper helper = mock(DockerCredentialHelper.class);
|
||||
this.dockerCredentialHelpers.put("gcr", helper);
|
||||
given(helper.get("gcr.io")).willThrow(new IOException("Failed to obtain credentials for registry"));
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(output.getErr())
|
||||
.contains("Error retrieving credentials for 'gcr.io' due to: Failed to obtain credentials for registry");
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "")
|
||||
.containsEntry("username", "")
|
||||
.containsEntry("password", "")
|
||||
.containsEntry("email", "");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": ""
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void emptyCredHelperShouldOverrideCredStore(@ResourcesRoot Path directory) throws IOException {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
String authHeader = this.authentication.getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "")
|
||||
.containsEntry("username", "")
|
||||
.containsEntry("password", "")
|
||||
.containsEntry("email", "");
|
||||
}
|
||||
|
||||
private Credentials getCredentials(String name) throws IOException {
|
||||
try (InputStream inputStream = new ClassPathResource(name).getInputStream()) {
|
||||
return new Credentials(SharedObjectMapper.get().readTree(inputStream));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> decode(String authHeader) throws IOException {
|
||||
assertThat(authHeader).isNotNull();
|
||||
return SharedObjectMapper.get().readValue(Base64.getDecoder().decode(authHeader), new TypeReference<>() {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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,6 +26,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerConfig;
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext;
|
||||
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
|
||||
|
||||
|
@ -46,6 +47,9 @@ class DockerConfigurationMetadataTests extends AbstractJsonTests {
|
|||
this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json"));
|
||||
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
|
||||
assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("test-context");
|
||||
assertThat(config.getConfiguration().getAuths()).isEmpty();
|
||||
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
|
||||
assertThat(config.getConfiguration().getCredsStore()).isNull();
|
||||
assertThat(config.getContext().getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock");
|
||||
assertThat(config.getContext().isTlsVerify()).isFalse();
|
||||
assertThat(config.getContext().getTlsPath()).isNull();
|
||||
|
@ -56,6 +60,9 @@ class DockerConfigurationMetadataTests extends AbstractJsonTests {
|
|||
this.environment.put("DOCKER_CONFIG", pathToResource("without-context/config.json"));
|
||||
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
|
||||
assertThat(config.getConfiguration().getCurrentContext()).isNull();
|
||||
assertThat(config.getConfiguration().getAuths()).isEmpty();
|
||||
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
|
||||
assertThat(config.getConfiguration().getCredsStore()).isNull();
|
||||
assertThat(config.getContext().getDockerHost()).isNull();
|
||||
assertThat(config.getContext().isTlsVerify()).isFalse();
|
||||
assertThat(config.getContext().getTlsPath()).isNull();
|
||||
|
@ -66,6 +73,9 @@ class DockerConfigurationMetadataTests extends AbstractJsonTests {
|
|||
this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json"));
|
||||
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
|
||||
assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("default");
|
||||
assertThat(config.getConfiguration().getAuths()).isEmpty();
|
||||
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
|
||||
assertThat(config.getConfiguration().getCredsStore()).isNull();
|
||||
assertThat(config.getContext().getDockerHost()).isNull();
|
||||
assertThat(config.getContext().isTlsVerify()).isFalse();
|
||||
assertThat(config.getContext().getTlsPath()).isNull();
|
||||
|
@ -95,10 +105,38 @@ class DockerConfigurationMetadataTests extends AbstractJsonTests {
|
|||
this.environment.put("DOCKER_CONFIG", "docker-config-dummy-path");
|
||||
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
|
||||
assertThat(config.getConfiguration().getCurrentContext()).isNull();
|
||||
assertThat(config.getConfiguration().getAuths()).isEmpty();
|
||||
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
|
||||
assertThat(config.getConfiguration().getCredsStore()).isNull();
|
||||
assertThat(config.getContext().getDockerHost()).isNull();
|
||||
assertThat(config.getContext().isTlsVerify()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void configWithAuthIsRead() throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", pathToResource("with-auth/config.json"));
|
||||
DockerConfigurationMetadata metadata = DockerConfigurationMetadata.from(this.environment::get);
|
||||
DockerConfig configuration = metadata.getConfiguration();
|
||||
assertThat(configuration.getCredsStore()).isEqualTo("desktop");
|
||||
assertThat(configuration.getCredHelpers()).hasSize(3)
|
||||
.containsEntry("azurecr.io", "acr-env")
|
||||
.containsEntry("ecr.us-east-1.amazonaws.com", "ecr-login")
|
||||
.containsEntry("gcr.io", "gcr");
|
||||
assertThat(configuration.getAuths()).hasSize(3).hasEntrySatisfying("https://index.docker.io/v1/", (auth) -> {
|
||||
assertThat(auth.getUsername()).isEqualTo("username");
|
||||
assertThat(auth.getPassword()).isEqualTo("password");
|
||||
assertThat(auth.getEmail()).isEqualTo("test@gmail.com");
|
||||
}).hasEntrySatisfying("custom-registry.example.com", (auth) -> {
|
||||
assertThat(auth.getUsername()).isEqualTo("customUser");
|
||||
assertThat(auth.getPassword()).isEqualTo("customPass");
|
||||
assertThat(auth.getEmail()).isNull();
|
||||
}).hasEntrySatisfying("my-registry.example.com", (auth) -> {
|
||||
assertThat(auth.getUsername()).isEqualTo("user");
|
||||
assertThat(auth.getPassword()).isEqualTo("password");
|
||||
assertThat(auth.getEmail()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
private String pathToResource(String resource) throws URISyntaxException {
|
||||
URL url = getClass().getResource(resource);
|
||||
return Paths.get(url.toURI()).getParent().toAbsolutePath().toString();
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DockerCredentialHelper}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class DockerCredentialHelperTests {
|
||||
|
||||
@Test
|
||||
void shouldCreateDockerCredentialOfSuffix() {
|
||||
DockerCredentialHelper helper = DockerCredentialHelper.ofSuffix("desktop");
|
||||
assertThat(helper).hasFieldOrPropertyWithValue("name", "docker-credential-desktop");
|
||||
}
|
||||
|
||||
}
|
|
@ -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,6 +24,7 @@ import org.json.JSONException;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
|
@ -37,7 +38,7 @@ class DockerRegistryTokenAuthenticationTests extends AbstractJsonTests {
|
|||
@Test
|
||||
void createAuthHeaderReturnsEncodedHeader() throws IOException, JSONException {
|
||||
DockerRegistryTokenAuthentication auth = new DockerRegistryTokenAuthentication("tokenvalue");
|
||||
String header = auth.getAuthHeader();
|
||||
String header = auth.getAuthHeader(ImageReference.of("ubuntu:18.04"));
|
||||
String expectedJson = StreamUtils.copyToString(getContent("auth-token.json"), StandardCharsets.UTF_8);
|
||||
JSONAssert.assertEquals(expectedJson, new String(Base64.getUrlDecoder().decode(header)), true);
|
||||
}
|
||||
|
|
|
@ -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,6 +24,7 @@ import org.json.JSONException;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
|
@ -34,17 +35,21 @@ import org.springframework.util.StreamUtils;
|
|||
*/
|
||||
class DockerRegistryUserAuthenticationTests extends AbstractJsonTests {
|
||||
|
||||
private final ImageReference imageReference = ImageReference.of("ubuntu:18.04");
|
||||
|
||||
@Test
|
||||
void createMinimalAuthHeaderReturnsEncodedHeader() throws IOException, JSONException {
|
||||
DockerRegistryUserAuthentication auth = new DockerRegistryUserAuthentication("user", "secret",
|
||||
"https://docker.example.com", "docker@example.com");
|
||||
JSONAssert.assertEquals(jsonContent("auth-user-full.json"), decoded(auth.getAuthHeader()), true);
|
||||
JSONAssert.assertEquals(jsonContent("auth-user-full.json"), decoded(auth.getAuthHeader(this.imageReference)),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFullAuthHeaderReturnsEncodedHeader() throws IOException, JSONException {
|
||||
DockerRegistryUserAuthentication auth = new DockerRegistryUserAuthentication("user", "secret", null, null);
|
||||
JSONAssert.assertEquals(jsonContent("auth-user-minimal.json"), decoded(auth.getAuthHeader()), false);
|
||||
JSONAssert.assertEquals(jsonContent("auth-user-minimal.json"), decoded(auth.getAuthHeader(this.imageReference)),
|
||||
false);
|
||||
}
|
||||
|
||||
private String jsonContent(String s) throws IOException {
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
#!/bin/sh
|
||||
|
||||
read -r registryUrl
|
||||
|
||||
if [ "$registryUrl" = "user.example.com" ]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"ServerURL": "${registryUrl}",
|
||||
"Username": "username",
|
||||
"Secret": "secret"
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$registryUrl" = "token.example.com" ]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"ServerURL": "${registryUrl}",
|
||||
"Username": "<token>",
|
||||
"Secret": "secret"
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$registryUrl" = "url.missing.example.com" ]; then
|
||||
echo "no credentials server URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$registryUrl" = "username.missing.example.com" ]; then
|
||||
echo "no credentials username" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$registryUrl" = "credentials.missing.example.com" ]; then
|
||||
echo "credentials not found in native keychain" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Unknown error" >&2
|
||||
exit 1
|
|
@ -0,0 +1,39 @@
|
|||
@echo off
|
||||
|
||||
set /p registryUrl=
|
||||
|
||||
if "%registryUrl%" == "user.example.com" (
|
||||
echo {
|
||||
echo "ServerURL": "%registryUrl%",
|
||||
echo "Username": "username",
|
||||
echo "Secret": "secret"
|
||||
echo }
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
if "%registryUrl%" == "token.example.com" (
|
||||
echo {
|
||||
echo "ServerURL": "%registryUrl%",
|
||||
echo "Username": "<token>",
|
||||
echo "Secret": "secret"
|
||||
echo }
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
if "%registryUrl%" == "url.missing.example.com" (
|
||||
echo no credentials server URL >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%registryUrl%" == "username.missing.example.com" (
|
||||
echo no credentials username >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%registryUrl%" == "credentials.missing.example.com" (
|
||||
echo credentials not found in native keychain >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Unknown error >&2
|
||||
exit /b 1
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"auths": {
|
||||
"https://index.docker.io/v1/": {
|
||||
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
|
||||
"email": "test@gmail.com"
|
||||
},
|
||||
"custom-registry.example.com": {
|
||||
"auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz"
|
||||
},
|
||||
"my-registry.example.com": {
|
||||
"username": "user",
|
||||
"password": "password"
|
||||
}
|
||||
},
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr",
|
||||
"ecr.us-east-1.amazonaws.com": "ecr-login",
|
||||
"azurecr.io": "acr-env"
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
@ -145,7 +145,7 @@ public abstract class DockerSpec {
|
|||
|
||||
private DockerConfiguration customizeBuilderAuthentication(DockerConfiguration dockerConfiguration) {
|
||||
if (this.builderRegistry == null || this.builderRegistry.hasEmptyAuth()) {
|
||||
return dockerConfiguration;
|
||||
return dockerConfiguration.withBuilderRegistryDefaultAuthentication();
|
||||
}
|
||||
if (this.builderRegistry.hasTokenAuth() && !this.builderRegistry.hasUserAuth()) {
|
||||
return dockerConfiguration.withBuilderRegistryTokenAuthentication(this.builderRegistry.getToken().get());
|
||||
|
@ -161,7 +161,7 @@ public abstract class DockerSpec {
|
|||
|
||||
private DockerConfiguration customizePublishAuthentication(DockerConfiguration dockerConfiguration) {
|
||||
if (this.publishRegistry == null || this.publishRegistry.hasEmptyAuth()) {
|
||||
return dockerConfiguration.withEmptyPublishRegistryAuthentication();
|
||||
return dockerConfiguration.withPublishRegistryDefaultAuthentication();
|
||||
}
|
||||
if (this.publishRegistry.hasTokenAuth() && !this.publishRegistry.hasUserAuth()) {
|
||||
return dockerConfiguration.withPublishRegistryTokenAuthentication(this.publishRegistry.getToken().get());
|
||||
|
|
|
@ -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,9 @@ 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.docker.type.ImageReference;
|
||||
import org.springframework.boot.gradle.junit.GradleProjectBuilder;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
@ -39,6 +41,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|||
*/
|
||||
class DockerSpecTests {
|
||||
|
||||
private final ImageReference imageReference = ImageReference.of("ubuntu:18.04");
|
||||
|
||||
private DockerSpec dockerSpec;
|
||||
|
||||
@BeforeEach
|
||||
|
@ -54,12 +58,10 @@ class DockerSpecTests {
|
|||
void asDockerConfigurationWithDefaults() {
|
||||
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
|
||||
assertThat(dockerConfiguration.getHost()).isNull();
|
||||
assertThat(dockerConfiguration.getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(dockerConfiguration.getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -74,12 +76,10 @@ class DockerSpecTests {
|
|||
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()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(dockerConfiguration.getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -92,12 +92,10 @@ class DockerSpecTests {
|
|||
assertThat(host.getCertificatePath()).isNull();
|
||||
assertThat(host.getContext()).isNull();
|
||||
assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse();
|
||||
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -110,12 +108,10 @@ class DockerSpecTests {
|
|||
assertThat(host.isSecure()).isFalse();
|
||||
assertThat(host.getCertificatePath()).isNull();
|
||||
assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse();
|
||||
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -136,12 +132,10 @@ class DockerSpecTests {
|
|||
assertThat(host.isSecure()).isFalse();
|
||||
assertThat(host.getCertificatePath()).isNull();
|
||||
assertThat(dockerConfiguration.isBindHostToBuilder()).isTrue();
|
||||
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -159,12 +153,12 @@ class DockerSpecTests {
|
|||
registry.getEmail().set("docker2@example.com");
|
||||
});
|
||||
DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration();
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader(this.imageReference)))
|
||||
.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.getPublishRegistryAuthentication().getAuthHeader(this.imageReference)))
|
||||
.contains("\"username\" : \"user2\"")
|
||||
.contains("\"password\" : \"secret2\"")
|
||||
.contains("\"email\" : \"docker2@example.com\"")
|
||||
|
@ -199,9 +193,9 @@ class DockerSpecTests {
|
|||
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()))
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader(this.imageReference)))
|
||||
.contains("\"identitytoken\" : \"token1\"");
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader(this.imageReference)))
|
||||
.contains("\"identitytoken\" : \"token2\"");
|
||||
}
|
||||
|
||||
|
@ -220,4 +214,10 @@ class DockerSpecTests {
|
|||
return new String(Base64.getDecoder().decode(value));
|
||||
}
|
||||
|
||||
private Class<?> getDockerRegistryConfigFileAuthenticationClass() {
|
||||
return ClassUtils.resolveClassName(
|
||||
"org.springframework.boot.buildpack.platform.docker.configuration.DefaultDockerRegistryAuthentication",
|
||||
getClass().getClassLoader());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -168,7 +168,7 @@ public class Docker {
|
|||
|
||||
private DockerConfiguration customizeBuilderAuthentication(DockerConfiguration dockerConfiguration) {
|
||||
if (this.builderRegistry == null || this.builderRegistry.isEmpty()) {
|
||||
return dockerConfiguration;
|
||||
return dockerConfiguration.withBuilderRegistryDefaultAuthentication();
|
||||
}
|
||||
if (this.builderRegistry.hasTokenAuth() && !this.builderRegistry.hasUserAuth()) {
|
||||
return dockerConfiguration.withBuilderRegistryTokenAuthentication(this.builderRegistry.getToken());
|
||||
|
@ -187,7 +187,7 @@ public class Docker {
|
|||
return dockerConfiguration;
|
||||
}
|
||||
if (this.publishRegistry == null || this.publishRegistry.isEmpty()) {
|
||||
return dockerConfiguration.withEmptyPublishRegistryAuthentication();
|
||||
return dockerConfiguration.withPublishRegistryDefaultAuthentication();
|
||||
}
|
||||
if (this.publishRegistry.hasTokenAuth() && !this.publishRegistry.hasUserAuth()) {
|
||||
return dockerConfiguration.withPublishRegistryTokenAuthentication(this.publishRegistry.getToken());
|
||||
|
|
|
@ -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.
|
||||
|
@ -22,6 +22,8 @@ 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.docker.type.ImageReference;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
@ -34,17 +36,17 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
|||
*/
|
||||
class DockerTests {
|
||||
|
||||
private final ImageReference imageReference = ImageReference.of("ubuntu:22.04");
|
||||
|
||||
@Test
|
||||
void asDockerConfigurationWithDefaults() {
|
||||
void asDockerConfigurationWithDefaults() throws ClassNotFoundException {
|
||||
Docker docker = new Docker();
|
||||
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
|
||||
assertThat(dockerConfiguration.getHost()).isNull();
|
||||
assertThat(dockerConfiguration.getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(dockerConfiguration.getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -60,12 +62,10 @@ class DockerTests {
|
|||
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()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(dockerConfiguration.getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -79,12 +79,10 @@ class DockerTests {
|
|||
assertThat(host.isSecure()).isFalse();
|
||||
assertThat(host.getCertificatePath()).isNull();
|
||||
assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse();
|
||||
assertThat(createDockerConfiguration(docker).getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(dockerConfiguration.getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -109,12 +107,10 @@ class DockerTests {
|
|||
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()))
|
||||
.contains("\"username\" : \"\"")
|
||||
.contains("\"password\" : \"\"")
|
||||
.contains("\"email\" : \"\"")
|
||||
.contains("\"serveraddress\" : \"\"");
|
||||
assertThat(createDockerConfiguration(docker).getBuilderRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
assertThat(dockerConfiguration.getPublishRegistryAuthentication())
|
||||
.isInstanceOf(getDockerRegistryConfigFileAuthenticationClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -125,12 +121,12 @@ class DockerTests {
|
|||
docker.setPublishRegistry(
|
||||
new Docker.DockerRegistry("user2", "secret2", "https://docker2.example.com", "docker2@example.com"));
|
||||
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader(this.imageReference)))
|
||||
.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.getPublishRegistryAuthentication().getAuthHeader(this.imageReference)))
|
||||
.contains("\"username\" : \"user2\"")
|
||||
.contains("\"password\" : \"secret2\"")
|
||||
.contains("\"email\" : \"docker2@example.com\"")
|
||||
|
@ -170,9 +166,9 @@ class DockerTests {
|
|||
docker.setBuilderRegistry(new Docker.DockerRegistry("token1"));
|
||||
docker.setPublishRegistry(new Docker.DockerRegistry("token2"));
|
||||
DockerConfiguration dockerConfiguration = createDockerConfiguration(docker);
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader(this.imageReference)))
|
||||
.contains("\"identitytoken\" : \"token1\"");
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader(this.imageReference)))
|
||||
.contains("\"identitytoken\" : \"token2\"");
|
||||
}
|
||||
|
||||
|
@ -205,6 +201,12 @@ class DockerTests {
|
|||
|
||||
}
|
||||
|
||||
private Class<?> getDockerRegistryConfigFileAuthenticationClass() {
|
||||
return ClassUtils.resolveClassName(
|
||||
"org.springframework.boot.buildpack.platform.docker.configuration.DefaultDockerRegistryAuthentication",
|
||||
getClass().getClassLoader());
|
||||
}
|
||||
|
||||
String decoded(String value) {
|
||||
return new String(Base64.getDecoder().decode(value));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue