diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java index 8d1c27b6ff..5ca753ce94 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java @@ -72,6 +72,24 @@ private static class AuthTemplate implements JsonTemplate { @Nullable private String auth; } + /** + * Returns the first value matching the given key predicates (short-circuiting in the order of + * predicates). + */ + private static T findFirstInMapByKey(Map map, List> keyMatches) { + return keyMatches + .stream() + .map(keyMatch -> findFirstInMapByKey(map, keyMatch)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + /** Returns the first value matching the given key predicate. */ + private static T findFirstInMapByKey(Map map, Predicate keyMatch) { + return map.keySet().stream().filter(keyMatch).map(map::get).findFirst().orElse(null); + } + /** Maps from registry to its {@link AuthTemplate}. */ private final Map auths = new HashMap<>(); @@ -87,8 +105,8 @@ private static class AuthTemplate implements JsonTemplate { *
    *
  1. Exact registry name *
  2. https:// + registry name - *
  3. registry name + arbitrary suffix - *
  4. https:// + registry name + arbitrary suffix + *
  5. registry name + / + arbitrary suffix + *
  6. https:// + registry name + / arbitrary suffix *
* * @param registry the registry to get the authorization for @@ -97,48 +115,41 @@ private static class AuthTemplate implements JsonTemplate { */ @Nullable public String getAuthFor(String registry) { - Predicate exactMatch = registry::equals; - Predicate withHttps = ("https://" + registry)::equals; - Predicate startsWith = name -> name.startsWith(registry); - Predicate startsWithAndWithHttps = name -> name.startsWith("https://" + registry); - AuthTemplate authTemplate = - getAuthTemplate(Arrays.asList(exactMatch, withHttps, startsWith, startsWithAndWithHttps)); - + AuthTemplate authTemplate = findFirstInMapByKey(auths, getRegistryMatchersFor(registry)); return authTemplate != null ? authTemplate.auth : null; } - /** Returns the first {@link AuthTemplate} matching the given predicates (short-circuiting). */ - private AuthTemplate getAuthTemplate(List> registryMatches) { - return registryMatches - .stream() - .map(this::getAuthTemplate) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - } - - /** Returns {@link AuthTemplate} matching the given predicate. */ - private AuthTemplate getAuthTemplate(Predicate registryMatch) { - return auths.keySet().stream().filter(registryMatch).map(auths::get).findFirst().orElse(null); - } - /** + * Returns {@code credsStore} or {@code credHelpers} for the given {@code registry}. If there + * exists a matching registry entry in {@code auths}, returns {@code credStore}; otherwise, a + * matching entry in {@code credHelpers} is returned based on the following lookup order: + * + *
    + *
  1. Exact registry name + *
  2. https:// + registry name + *
  3. registry name + / + arbitrary suffix + *
  4. https:// + registry name + / + arbitrary suffix + *
+ * * @param registry the registry to get the credential helpers for * @return {@code credsStore} if {@code registry} is present in {@code auths}; otherwise, searches * {@code credHelpers}; otherwise, {@code null} if not found */ @Nullable public String getCredentialHelperFor(String registry) { - if (credsStore != null) { - // The registry could be prefixed with the HTTPS protocol. - if (auths.containsKey(registry) || auths.containsKey("https://" + registry)) { - return credsStore; - } - } - if (credHelpers.containsKey(registry)) { - return credHelpers.get(registry); + List> registryMatchers = getRegistryMatchersFor(registry); + if (credsStore != null && findFirstInMapByKey(auths, registryMatchers) != null) { + return credsStore; } - return null; + return findFirstInMapByKey(credHelpers, registryMatchers); + } + + private List> getRegistryMatchersFor(String registry) { + Predicate exactMatch = registry::equals; + Predicate withHttps = ("https://" + registry)::equals; + Predicate withSuffix = name -> name.startsWith(registry + "/"); + Predicate WithHttpsAndSuffix = name -> name.startsWith("https://" + registry + "/"); + return Arrays.asList(exactMatch, withHttps, withSuffix, WithHttpsAndSuffix); } @VisibleForTesting diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplateTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplateTest.java index 6dcf67113d..e627751bc2 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplateTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplateTest.java @@ -73,4 +73,71 @@ public void testGetAuthFor_orderOfMatchPreference() throws URISyntaxException, I "dull-registry: starting with name and with https", dockerConfig.getAuthFor("dull-registry")); } + + @Test + public void testGetAuthFor_correctSuffixMatching() throws URISyntaxException, IOException { + Path json = Paths.get(Resources.getResource("json/dockerconfig_extra_matches.json").toURI()); + + DockerConfigTemplate dockerConfig = + JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class); + + Assert.assertNull(dockerConfig.getAuthFor("example")); + } + + @Test + public void testGetCredentialHelperFor() throws URISyntaxException, IOException { + Path json = Paths.get(Resources.getResource("json/dockerconfig.json").toURI()); + + DockerConfigTemplate dockerConfig = + JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class); + + Assert.assertEquals( + "some credential store", dockerConfig.getCredentialHelperFor("just registry")); + } + + @Test + public void testGetCredentialHelperFor_withHttps() throws URISyntaxException, IOException { + Path json = Paths.get(Resources.getResource("json/dockerconfig.json").toURI()); + + DockerConfigTemplate dockerConfig = + JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class); + + Assert.assertEquals( + "some credential store", dockerConfig.getCredentialHelperFor("with.protocol")); + } + + @Test + public void testGetCredentialHelperFor_withSuffix() throws URISyntaxException, IOException { + Path json = Paths.get(Resources.getResource("json/dockerconfig.json").toURI()); + + DockerConfigTemplate dockerConfig = + JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class); + + Assert.assertEquals( + "some credential store", dockerConfig.getCredentialHelperFor("with.suffix")); + } + + @Test + public void testGetCredentialHelperFor_withProtocolAndSuffix() + throws URISyntaxException, IOException { + Path json = Paths.get(Resources.getResource("json/dockerconfig.json").toURI()); + + DockerConfigTemplate dockerConfig = + JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class); + + Assert.assertEquals( + "some credential store", dockerConfig.getCredentialHelperFor("with.protocol.and.suffix")); + } + + @Test + public void testGetCredentialHelperFor_correctSuffixMatching() + throws URISyntaxException, IOException { + Path json = Paths.get(Resources.getResource("json/dockerconfig.json").toURI()); + + DockerConfigTemplate dockerConfig = + JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class); + + Assert.assertNull(dockerConfig.getCredentialHelperFor("example")); + Assert.assertNull(dockerConfig.getCredentialHelperFor("another.example")); + } } diff --git a/jib-core/src/test/resources/json/dockerconfig.json b/jib-core/src/test/resources/json/dockerconfig.json index cc8c1d9093..ce7eed37a7 100644 --- a/jib-core/src/test/resources/json/dockerconfig.json +++ b/jib-core/src/test/resources/json/dockerconfig.json @@ -3,13 +3,26 @@ "some other registry":{"auth":"some other auth"}, "some registry":{"auth":"some auth","password":"ignored"}, "https://registry":{"auth":"token"}, + "just registry":{}, - "https://with.protocol":{} + "https://with.protocol":{}, + "with.suffix/suffix/":{}, + "https://with.protocol.and.suffix/v10/":{}, + + "example.com":{"auth":"should not match example"} }, "credsStore":"some credential store", "credHelpers":{ "another registry":"another credential helper", "some registry":"some credential helper", - "index.docker.io":"index.docker.io credential helper" + "index.docker.io":"index.docker.io credential helper", + + "just.registry.in.helpers":"credHelper for just.registry.in.helpers", + "https://with.protocol.in.helpers":"credHelper for https://with.protocol.in.helpers", + "with.suffix.in.helpers/v2/":"credHelper for with.suffix.in.helpers/v2/", + "https://with.protocol.and.suffix.in.helpers/suffix": + "credHelper for https://with.protocol.and.suffix.in.helpers/suffix", + + "another.example.com.in.helpers":"should not match example" } } \ No newline at end of file