diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java index 9ab0fef2019..8613ebfb425 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java @@ -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 credHelpers; + + private final Map 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 extractAuths() { + Map 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 extractCredHelpers() { + Map credHelpers = valueAt("/credHelpers", Map.class); + return (credHelpers != null) ? Map.copyOf(credHelpers) : Collections.emptyMap(); } String getCurrentContext() { return this.currentContext; } + String getCredsStore() { + return this.credsStore; + } + + Map getCredHelpers() { + return this.credHelpers; + } + + Map 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; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java index b47bbaa3d80..962b38443fa 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java @@ -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(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-auth/config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-auth/config.json new file mode 100644 index 00000000000..5ccd4b0bf21 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-auth/config.json @@ -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" + } +}