Repository: docker/docker-py

Issue:
Support updated credential helpers syntax.
https://github.com/docker/compose/issues/4885


Discussion:

Resolving patch:
diff --git a/docker/auth.py b/docker/auth.py
--- a/docker/auth.py
+++ b/docker/auth.py
@@ -70,6 +70,15 @@ def split_repo_name(repo_name):
     return tuple(parts)
 
 
+def get_credential_store(authconfig, registry):
+    if not registry or registry == INDEX_NAME:
+        registry = 'https://index.docker.io/v1/'
+
+    return authconfig.get('credHelpers', {}).get(registry) or authconfig.get(
+        'credsStore'
+    )
+
+
 def resolve_authconfig(authconfig, registry=None):
     """
     Returns the authentication data from the given auth configuration for a
@@ -77,13 +86,17 @@ def resolve_authconfig(authconfig, registry=None):
     with full URLs are stripped down to hostnames before checking for a match.
     Returns None if no match was found.
     """
-    if 'credsStore' in authconfig:
-        log.debug(
-            'Using credentials store "{0}"'.format(authconfig['credsStore'])
-        )
-        return _resolve_authconfig_credstore(
-            authconfig, registry, authconfig['credsStore']
-        )
+
+    if 'credHelpers' in authconfig or 'credsStore' in authconfig:
+        store_name = get_credential_store(authconfig, registry)
+        if store_name is not None:
+            log.debug(
+                'Using credentials store "{0}"'.format(store_name)
+            )
+            return _resolve_authconfig_credstore(
+                authconfig, registry, store_name
+            )
+
     # Default to the public index server
     registry = resolve_index_name(registry) if registry else INDEX_NAME
     log.debug("Looking for auth entry for {0}".format(repr(registry)))
@@ -274,6 +287,9 @@ def load_config(config_path=None):
             if data.get('credsStore'):
                 log.debug("Found 'credsStore' section")
                 res.update({'credsStore': data['credsStore']})
+            if data.get('credHelpers'):
+                log.debug("Found 'credHelpers' section")
+                res.update({'credHelpers': data['credHelpers']})
             if res:
                 return res
             else:


Test patch:
diff --git a/tests/unit/auth_test.py b/tests/unit/auth_test.py
--- a/tests/unit/auth_test.py
+++ b/tests/unit/auth_test.py
@@ -272,6 +272,57 @@ def test_resolve_registry_and_auth_unauthenticated_registry(self):
         )
 
 
+class CredStoreTest(unittest.TestCase):
+    def test_get_credential_store(self):
+        auth_config = {
+            'credHelpers': {
+                'registry1.io': 'truesecret',
+                'registry2.io': 'powerlock'
+            },
+            'credsStore': 'blackbox',
+        }
+
+        assert auth.get_credential_store(
+            auth_config, 'registry1.io'
+        ) == 'truesecret'
+        assert auth.get_credential_store(
+            auth_config, 'registry2.io'
+        ) == 'powerlock'
+        assert auth.get_credential_store(
+            auth_config, 'registry3.io'
+        ) == 'blackbox'
+
+    def test_get_credential_store_no_default(self):
+        auth_config = {
+            'credHelpers': {
+                'registry1.io': 'truesecret',
+                'registry2.io': 'powerlock'
+            },
+        }
+        assert auth.get_credential_store(
+            auth_config, 'registry2.io'
+        ) == 'powerlock'
+        assert auth.get_credential_store(
+            auth_config, 'registry3.io'
+        ) is None
+
+    def test_get_credential_store_default_index(self):
+        auth_config = {
+            'credHelpers': {
+                'https://index.docker.io/v1/': 'powerlock'
+            },
+            'credsStore': 'truesecret'
+        }
+
+        assert auth.get_credential_store(auth_config, None) == 'powerlock'
+        assert auth.get_credential_store(
+            auth_config, 'docker.io'
+        ) == 'powerlock'
+        assert auth.get_credential_store(
+            auth_config, 'images.io'
+        ) == 'truesecret'
+
+
 class FindConfigFileTest(unittest.TestCase):
     def tmpdir(self, name):
         tmpdir = ensuretemp(name)