Merge pull request #45291 from nosan

* pr/45291:
  Polish

Closes gh-45291
This commit is contained in:
Stéphane Nicoll 2025-04-29 12:07:13 +02:00
commit 05ee9b387d
6 changed files with 46 additions and 27 deletions

View File

@ -40,7 +40,7 @@ class Credential extends MappedObject {
private final String secret; private final String secret;
private String serverUrl; private final String serverUrl;
Credential(JsonNode node) { Credential(JsonNode node) {
super(node, MethodHandles.lookup()); super(node, MethodHandles.lookup());

View File

@ -39,7 +39,7 @@ class CredentialHelper {
private static final String USR_LOCAL_BIN = "/usr/local/bin/"; private static final String USR_LOCAL_BIN = "/usr/local/bin/";
Set<String> CREDENTIAL_NOT_FOUND_MESSAGES = Set.of("credentials not found in native keychain", private static final Set<String> CREDENTIAL_NOT_FOUND_MESSAGES = Set.of("credentials not found in native keychain",
"no credentials server URL", "no credentials username"); "no credentials server URL", "no credentials username");
private final String executable; private final String executable;
@ -73,12 +73,12 @@ class CredentialHelper {
} }
} }
private ProcessBuilder processBuilder(String string) { private ProcessBuilder processBuilder(String action) {
ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(true); ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(true);
if (Platform.isWindows()) { if (Platform.isWindows()) {
processBuilder.command("cmd", "/c"); processBuilder.command("cmd", "/c");
} }
processBuilder.command(this.executable, string); processBuilder.command(this.executable, action);
return processBuilder; return processBuilder;
} }
@ -90,14 +90,21 @@ class CredentialHelper {
if (!Platform.isMac()) { if (!Platform.isMac()) {
throw ex; throw ex;
} }
List<String> command = new ArrayList<>(processBuilder.command()); try {
command.set(0, USR_LOCAL_BIN + command.get(0)); List<String> command = new ArrayList<>(processBuilder.command());
return processBuilder.command(command).start(); command.set(0, USR_LOCAL_BIN + command.get(0));
return processBuilder.command(command).start();
}
catch (Exception suppressed) {
// Suppresses the exception and rethrows the original exception
ex.addSuppressed(suppressed);
throw ex;
}
} }
} }
private boolean isCredentialsNotFoundError(String message) { private static boolean isCredentialsNotFoundError(String message) {
return this.CREDENTIAL_NOT_FOUND_MESSAGES.contains(message.trim()); return CREDENTIAL_NOT_FOUND_MESSAGES.contains(message.trim());
} }
} }

View File

@ -26,6 +26,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.Base64; import java.util.Base64;
import java.util.HexFormat; import java.util.HexFormat;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
@ -36,6 +37,7 @@ import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.boot.buildpack.platform.system.Environment; import org.springframework.boot.buildpack.platform.system.Environment;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.util.function.SingletonSupplier;
/** /**
* Docker configuration stored in metadata files managed by the Docker CLI. * Docker configuration stored in metadata files managed by the Docker CLI.
@ -63,7 +65,8 @@ final class DockerConfigurationMetadata {
private static final String CONTEXT_FILE_NAME = "meta.json"; private static final String CONTEXT_FILE_NAME = "meta.json";
private static volatile DockerConfigurationMetadata systemEnvironmentConfigurationMetadata; private static final Supplier<DockerConfigurationMetadata> systemEnvironmentConfigurationMetadata = SingletonSupplier
.of(() -> DockerConfigurationMetadata.create(Environment.SYSTEM));
private final String configLocation; private final String configLocation;
@ -90,20 +93,18 @@ final class DockerConfigurationMetadata {
} }
static DockerConfigurationMetadata from(Environment environment) { static DockerConfigurationMetadata from(Environment environment) {
DockerConfigurationMetadata dockerConfigurationMetadata = (environment == Environment.SYSTEM) if (environment == Environment.SYSTEM) {
? DockerConfigurationMetadata.systemEnvironmentConfigurationMetadata : null; return systemEnvironmentConfigurationMetadata.get();
if (dockerConfigurationMetadata != null) {
return dockerConfigurationMetadata;
} }
return create(environment);
}
private static DockerConfigurationMetadata create(Environment environment) {
String configLocation = environment.get(DOCKER_CONFIG); String configLocation = environment.get(DOCKER_CONFIG);
configLocation = (configLocation != null) ? configLocation : getUserHomeConfigLocation(); configLocation = (configLocation != null) ? configLocation : getUserHomeConfigLocation();
DockerConfig dockerConfig = createDockerConfig(configLocation); DockerConfig dockerConfig = createDockerConfig(configLocation);
DockerContext dockerContext = createDockerContext(configLocation, dockerConfig.getCurrentContext()); DockerContext dockerContext = createDockerContext(configLocation, dockerConfig.getCurrentContext());
dockerConfigurationMetadata = new DockerConfigurationMetadata(configLocation, dockerConfig, dockerContext); return new DockerConfigurationMetadata(configLocation, dockerConfig, dockerContext);
if (environment == Environment.SYSTEM) {
DockerConfigurationMetadata.systemEnvironmentConfigurationMetadata = dockerConfigurationMetadata;
}
return dockerConfigurationMetadata;
} }
private static String getUserHomeConfigLocation() { private static String getUserHomeConfigLocation() {

View File

@ -19,6 +19,7 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.util.Assert;
/** /**
* Docker registry authentication configuration. * Docker registry authentication configuration.
@ -83,7 +84,8 @@ public interface DockerRegistryAuthentication {
* Factory method that returns a new {@link DockerRegistryAuthentication} instance * Factory method that returns a new {@link DockerRegistryAuthentication} instance
* that uses the standard docker JSON config (including support for credential * that uses the standard docker JSON config (including support for credential
* helpers) to generate auth headers. * helpers) to generate auth headers.
* @param fallback the fallback authentication to use if no suitable config is found * @param fallback the fallback authentication to use if no suitable config is found,
* may be null {@code}
* @return a new {@link DockerRegistryAuthentication} instance * @return a new {@link DockerRegistryAuthentication} instance
* @since 3.5.0 * @since 3.5.0
* @see #configuration(DockerRegistryAuthentication, BiConsumer) * @see #configuration(DockerRegistryAuthentication, BiConsumer)
@ -96,15 +98,17 @@ public interface DockerRegistryAuthentication {
* Factory method that returns a new {@link DockerRegistryAuthentication} instance * Factory method that returns a new {@link DockerRegistryAuthentication} instance
* that uses the standard docker JSON config (including support for credential * that uses the standard docker JSON config (including support for credential
* helpers) to generate auth headers. * helpers) to generate auth headers.
* @param fallback the fallback authentication to use if no suitable config is found * @param fallback the fallback authentication to use if no suitable config is found,
* may be {@code null}
* @param credentialHelperExceptionHandler callback that should handle credential * @param credentialHelperExceptionHandler callback that should handle credential
* helper exceptions * helper exceptions, never {@code null}
* @return a new {@link DockerRegistryAuthentication} instance * @return a new {@link DockerRegistryAuthentication} instance
* @since 3.5.0 * @since 3.5.0
* @see #configuration(DockerRegistryAuthentication, BiConsumer) * @see #configuration(DockerRegistryAuthentication, BiConsumer)
*/ */
static DockerRegistryAuthentication configuration(DockerRegistryAuthentication fallback, static DockerRegistryAuthentication configuration(DockerRegistryAuthentication fallback,
BiConsumer<String, Exception> credentialHelperExceptionHandler) { BiConsumer<String, Exception> credentialHelperExceptionHandler) {
Assert.notNull(credentialHelperExceptionHandler, () -> "'credentialHelperExceptionHandler' must not be null");
return new DockerRegistryConfigAuthentication(fallback, credentialHelperExceptionHandler); return new DockerRegistryConfigAuthentication(fallback, credentialHelperExceptionHandler);
} }

View File

@ -89,10 +89,17 @@ class CredentialHelperTests {
} }
@Test @Test
void getWhenCommandDoesNotExistErrorThrowsException() { void getWhenExecutableDoesNotExistErrorThrowsException() {
String name = "docker-credential-%s".formatted(UUID.randomUUID().toString()); String executable = "docker-credential-%s".formatted(UUID.randomUUID().toString());
assertThatIOException().isThrownBy(() -> new CredentialHelper(name).get("invalid.example.com")) assertThatIOException().isThrownBy(() -> new CredentialHelper(executable).get("invalid.example.com"))
.withMessageContaining(name); .withMessageContaining(executable)
.satisfies((ex) -> {
if (Platform.isMac()) {
assertThat(ex.getMessage()).doesNotContain("/usr/local/bin/");
assertThat(ex.getSuppressed()).allSatisfy((suppressed) -> assertThat(suppressed)
.hasMessageContaining("/usr/local/bin/" + executable));
}
});
} }
} }

View File

@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Dmytro Nosan * @author Dmytro Nosan
*/ */
class CredentialsTests { class CredentialTests {
@Test @Test
@WithResource(name = "credentials.json", content = """ @WithResource(name = "credentials.json", content = """