diff --git a/src/main/java/land/oras/auth/RegistriesConf.java b/src/main/java/land/oras/auth/RegistriesConf.java index cb1fcf71..95929d37 100644 --- a/src/main/java/land/oras/auth/RegistriesConf.java +++ b/src/main/java/land/oras/auth/RegistriesConf.java @@ -89,16 +89,33 @@ public static RegistriesConf newConf(List configPaths) { /** * Create a new RegistriesConf instance by loading configuration from standard paths. + * If the {@code CONTAINERS_REGISTRIES_CONF} environment variable is set it is used exclusively. + * Otherwise, the user-local config (under {@code $HOME}) is tried first, then {@code /etc/containers/registries.conf}. + * * @return A new RegistriesConf instance. */ public static RegistriesConf newConf() { + String containersRegistriesConf = System.getenv("CONTAINERS_REGISTRIES_CONF"); + if (containersRegistriesConf != null) { + LOG.debug("Using registries config from CONTAINERS_REGISTRIES_CONF: {}", containersRegistriesConf); + return newConf(List.of(Path.of(containersRegistriesConf))); + } + return newConf(defaultConfPaths()); + } + + /** + * Returns the ordered list of registries.conf paths to search when {@code CONTAINERS_REGISTRIES_CONF} is not set. + * The user-local path (under {@code $HOME}) is tried first; the system-wide path is always included as fallback. + * + * @return list of candidate paths. + */ + private static List defaultConfPaths() { Path globalPath = Path.of("/etc/containers/registries.conf"); - List paths = List.of( - System.getenv("HOME") != null - ? Path.of(System.getenv("HOME"), ".config", "containers", "registries.conf") - : globalPath, - globalPath); - return newConf(paths); + String home = System.getenv("HOME"); + if (home != null) { + return List.of(Path.of(home, ".config", "containers", "registries.conf"), globalPath); + } + return List.of(globalPath); } /** diff --git a/src/test/java/land/oras/TestUtils.java b/src/test/java/land/oras/TestUtils.java index 1ef36e29..308cba94 100644 --- a/src/test/java/land/oras/TestUtils.java +++ b/src/test/java/land/oras/TestUtils.java @@ -76,6 +76,7 @@ public static synchronized void createRegistriesConfFile(Path homeDir, String co public static synchronized void withHome(Path homeDir, Runnable action) throws Exception { new EnvironmentVariables() .set("HOME", homeDir.toAbsolutePath().toString()) + .remove("CONTAINERS_REGISTRIES_CONF") .execute(() -> { try { action.run(); diff --git a/src/test/java/land/oras/auth/RegistriesConfTest.java b/src/test/java/land/oras/auth/RegistriesConfTest.java index 35c90a1b..d6ac4029 100644 --- a/src/test/java/land/oras/auth/RegistriesConfTest.java +++ b/src/test/java/land/oras/auth/RegistriesConfTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.nio.file.Files; import java.nio.file.Path; import land.oras.TestUtils; import org.junit.jupiter.api.BeforeAll; @@ -29,6 +30,7 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; /** * Test class of {@link RegistriesConf}. @@ -73,4 +75,65 @@ void shouldReadUnqualifiedSearchRegistriesFromHome() throws Exception { assertEquals("docker.io", conf.getUnqualifiedRegistries().get(0)); }); } + + @Test + void shouldFallBackToGlobalPathWhenHomeIsAbsent() throws Exception { + new EnvironmentVariables() + .remove("HOME") + .remove("CONTAINERS_REGISTRIES_CONF") + .execute(() -> { + RegistriesConf conf = RegistriesConf.newConf(); + assertNotNull(conf); + // /etc/containers/registries.conf does not exist in the test environment, + // so the result is an empty config — but the code path is exercised. + assertTrue(conf.getUnqualifiedRegistries().isEmpty()); + assertTrue(conf.getAliases().isEmpty()); + }); + } + + @Test + void shouldUseContainersRegistriesConfWhenSet(@TempDir Path customDir) throws Exception { + // language=toml + String customContent = + """ + unqualified-search-registries = ["quay.io"] + + [aliases] + "busybox"="quay.io/library/busybox" + """; + Path customFile = customDir.resolve("registries.conf"); + Files.writeString(customFile, customContent); + + new EnvironmentVariables() + .set("CONTAINERS_REGISTRIES_CONF", customFile.toAbsolutePath().toString()) + .execute(() -> { + RegistriesConf conf = RegistriesConf.newConf(); + assertNotNull(conf); + assertEquals(1, conf.getUnqualifiedRegistries().size()); + assertEquals("quay.io", conf.getUnqualifiedRegistries().get(0)); + assertTrue(conf.hasAlias("busybox")); + assertEquals("quay.io/library/busybox", conf.getAliases().get("busybox")); + }); + } + + @Test + void containersRegistriesConfTakesPrecedenceOverHome(@TempDir Path customDir) throws Exception { + // language=toml + String customContent = """ + unqualified-search-registries = ["custom.io"] + """; + Path customFile = customDir.resolve("registries.conf"); + Files.writeString(customFile, customContent); + + new EnvironmentVariables() + .set("CONTAINERS_REGISTRIES_CONF", customFile.toAbsolutePath().toString()) + .set("HOME", homeDir.toAbsolutePath().toString()) + .execute(() -> { + RegistriesConf conf = RegistriesConf.newConf(); + // Must reflect the custom file, not the HOME-based one (which has "docker.io") + assertEquals(1, conf.getUnqualifiedRegistries().size()); + assertEquals("custom.io", conf.getUnqualifiedRegistries().get(0)); + assertFalse(conf.hasAlias("alpine")); + }); + } }