Skip to content

Commit 0750337

Browse files
authored
Merge pull request #1885 from docker/improve_authconfig_genconfig_separation
Improve separation between auth_configs and general_configs
2 parents 75e2e8a + ccbde11 commit 0750337

File tree

12 files changed

+149
-111
lines changed

12 files changed

+149
-111
lines changed

docker/api/build.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,12 @@ def _set_auth_headers(self, headers):
300300
# Matches CLI behavior: https://github.com/docker/docker/blob/
301301
# 67b85f9d26f1b0b2b240f2d794748fac0f45243c/cliconfig/
302302
# credentials/native_store.go#L68-L83
303-
for registry in self._auth_configs.keys():
304-
if registry == 'credsStore' or registry == 'HttpHeaders':
305-
continue
303+
for registry in self._auth_configs.get('auths', {}).keys():
306304
auth_data[registry] = auth.resolve_authconfig(
307305
self._auth_configs, registry
308306
)
309307
else:
310-
auth_data = self._auth_configs.copy()
308+
auth_data = self._auth_configs.get('auths', {}).copy()
311309
# See https://github.com/docker/docker-py/issues/1683
312310
if auth.INDEX_NAME in auth_data:
313311
auth_data[auth.INDEX_URL] = auth_data[auth.INDEX_NAME]

docker/api/client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class APIClient(
8787
"""
8888

8989
__attrs__ = requests.Session.__attrs__ + ['_auth_configs',
90+
'_general_configs',
9091
'_version',
9192
'base_url',
9293
'timeout']
@@ -105,8 +106,10 @@ def __init__(self, base_url=None, version=None,
105106
self.timeout = timeout
106107
self.headers['User-Agent'] = user_agent
107108

108-
self._auth_configs = auth.load_config()
109109
self._general_configs = config.load_general_config()
110+
self._auth_configs = auth.load_config(
111+
config_dict=self._general_configs
112+
)
110113

111114
base_url = utils.parse_host(
112115
base_url, IS_WINDOWS_PLATFORM, tls=bool(tls)

docker/api/daemon.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ def login(self, username, password=None, email=None, registry=None,
144144

145145
response = self._post_json(self._url('/auth'), data=req_data)
146146
if response.status_code == 200:
147+
if 'auths' not in self._auth_configs:
148+
self._auth_configs['auths'] = {}
147149
self._auth_configs[registry or auth.INDEX_NAME] = req_data
148150
return self._result(response, json=True)
149151

docker/auth.py

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,12 @@ def resolve_authconfig(authconfig, registry=None):
9898
registry = resolve_index_name(registry) if registry else INDEX_NAME
9999
log.debug("Looking for auth entry for {0}".format(repr(registry)))
100100

101-
if registry in authconfig:
101+
authdict = authconfig.get('auths', {})
102+
if registry in authdict:
102103
log.debug("Found {0}".format(repr(registry)))
103-
return authconfig[registry]
104+
return authdict[registry]
104105

105-
for key, conf in six.iteritems(authconfig):
106+
for key, conf in six.iteritems(authdict):
106107
if resolve_index_name(key) == registry:
107108
log.debug("Found {0}".format(repr(key)))
108109
return conf
@@ -220,47 +221,53 @@ def parse_auth(entries, raise_on_error=False):
220221
return conf
221222

222223

223-
def load_config(config_path=None):
224+
def load_config(config_path=None, config_dict=None):
224225
"""
225226
Loads authentication data from a Docker configuration file in the given
226227
root directory or if config_path is passed use given path.
227228
Lookup priority:
228229
explicit config_path parameter > DOCKER_CONFIG environment variable >
229230
~/.docker/config.json > ~/.dockercfg
230231
"""
231-
config_file = config.find_config_file(config_path)
232232

233-
if not config_file:
234-
return {}
233+
if not config_dict:
234+
config_file = config.find_config_file(config_path)
235+
236+
if not config_file:
237+
return {}
238+
try:
239+
with open(config_file) as f:
240+
config_dict = json.load(f)
241+
except (IOError, KeyError, ValueError) as e:
242+
# Likely missing new Docker config file or it's in an
243+
# unknown format, continue to attempt to read old location
244+
# and format.
245+
log.debug(e)
246+
return _load_legacy_config(config_file)
247+
248+
res = {}
249+
if config_dict.get('auths'):
250+
log.debug("Found 'auths' section")
251+
res.update({
252+
'auths': parse_auth(config_dict.pop('auths'), raise_on_error=True)
253+
})
254+
if config_dict.get('credsStore'):
255+
log.debug("Found 'credsStore' section")
256+
res.update({'credsStore': config_dict.pop('credsStore')})
257+
if config_dict.get('credHelpers'):
258+
log.debug("Found 'credHelpers' section")
259+
res.update({'credHelpers': config_dict.pop('credHelpers')})
260+
if res:
261+
return res
262+
263+
log.debug(
264+
"Couldn't find auth-related section ; attempting to interpret"
265+
"as auth-only file"
266+
)
267+
return parse_auth(config_dict)
235268

236-
try:
237-
with open(config_file) as f:
238-
data = json.load(f)
239-
res = {}
240-
if data.get('auths'):
241-
log.debug("Found 'auths' section")
242-
res.update(parse_auth(data['auths'], raise_on_error=True))
243-
if data.get('HttpHeaders'):
244-
log.debug("Found 'HttpHeaders' section")
245-
res.update({'HttpHeaders': data['HttpHeaders']})
246-
if data.get('credsStore'):
247-
log.debug("Found 'credsStore' section")
248-
res.update({'credsStore': data['credsStore']})
249-
if data.get('credHelpers'):
250-
log.debug("Found 'credHelpers' section")
251-
res.update({'credHelpers': data['credHelpers']})
252-
if res:
253-
return res
254-
else:
255-
log.debug("Couldn't find 'auths' or 'HttpHeaders' sections")
256-
f.seek(0)
257-
return parse_auth(json.load(f))
258-
except (IOError, KeyError, ValueError) as e:
259-
# Likely missing new Docker config file or it's in an
260-
# unknown format, continue to attempt to read old location
261-
# and format.
262-
log.debug(e)
263269

270+
def _load_legacy_config(config_file):
264271
log.debug("Attempting to parse legacy auth file format")
265272
try:
266273
data = []

docker/utils/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ def load_general_config(config_path=None):
5757
try:
5858
with open(config_file) as f:
5959
return json.load(f)
60-
except Exception as e:
60+
except (IOError, ValueError) as e:
61+
# In the case of a legacy `.dockercfg` file, we won't
62+
# be able to load any JSON data.
6163
log.debug(e)
62-
pass
6364

6465
log.debug("All parsing attempts failed - returning empty config")
6566
return {}

docker/utils/decorators.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ def wrapper(self, *args, **kwargs):
3838

3939
def update_headers(f):
4040
def inner(self, *args, **kwargs):
41-
if 'HttpHeaders' in self._auth_configs:
41+
if 'HttpHeaders' in self._general_configs:
4242
if not kwargs.get('headers'):
43-
kwargs['headers'] = self._auth_configs['HttpHeaders']
43+
kwargs['headers'] = self._general_configs['HttpHeaders']
4444
else:
45-
kwargs['headers'].update(self._auth_configs['HttpHeaders'])
45+
kwargs['headers'].update(self._general_configs['HttpHeaders'])
4646
return f(self, *args, **kwargs)
4747
return inner

tests/integration/api_container_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def test_create_container_with_volumes_from(self):
161161
self.client.start(container3_id)
162162

163163
info = self.client.inspect_container(res2['Id'])
164-
self.assertCountEqual(info['HostConfig']['VolumesFrom'], vol_names)
164+
assert len(info['HostConfig']['VolumesFrom']) == len(vol_names)
165165

166166
def create_container_readonly_fs(self):
167167
ctnr = self.client.create_container(

tests/integration/base.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import docker
66
from docker.utils import kwargs_from_env
7-
import six
87

98
from .. import helpers
109

@@ -19,9 +18,6 @@ class BaseIntegrationTest(unittest.TestCase):
1918
"""
2019

2120
def setUp(self):
22-
if six.PY2:
23-
self.assertRegex = self.assertRegexpMatches
24-
self.assertCountEqual = self.assertItemsEqual
2521
self.tmp_imgs = []
2622
self.tmp_containers = []
2723
self.tmp_folders = []

tests/unit/api_build_test.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,12 @@ def test_build_container_custom_context_gzip(self):
7373

7474
def test_build_remote_with_registry_auth(self):
7575
self.client._auth_configs = {
76-
'https://example.com': {
77-
'user': 'example',
78-
'password': 'example',
79-
'email': '[email protected]'
76+
'auths': {
77+
'https://example.com': {
78+
'user': 'example',
79+
'password': 'example',
80+
'email': '[email protected]'
81+
}
8082
}
8183
}
8284

@@ -85,7 +87,10 @@ def test_build_remote_with_registry_auth(self):
8587
'forcerm': False,
8688
'remote': 'https://github.com/docker-library/mongo'}
8789
expected_headers = {
88-
'X-Registry-Config': auth.encode_header(self.client._auth_configs)}
90+
'X-Registry-Config': auth.encode_header(
91+
self.client._auth_configs['auths']
92+
)
93+
}
8994

9095
self.client.build(path='https://github.com/docker-library/mongo')
9196

@@ -118,32 +123,43 @@ def test_build_container_invalid_container_limits(self):
118123

119124
def test_set_auth_headers_with_empty_dict_and_auth_configs(self):
120125
self.client._auth_configs = {
121-
'https://example.com': {
122-
'user': 'example',
123-
'password': 'example',
124-
'email': '[email protected]'
126+
'auths': {
127+
'https://example.com': {
128+
'user': 'example',
129+
'password': 'example',
130+
'email': '[email protected]'
131+
}
125132
}
126133
}
127134

128135
headers = {}
129136
expected_headers = {
130-
'X-Registry-Config': auth.encode_header(self.client._auth_configs)}
137+
'X-Registry-Config': auth.encode_header(
138+
self.client._auth_configs['auths']
139+
)
140+
}
141+
131142
self.client._set_auth_headers(headers)
132143
assert headers == expected_headers
133144

134145
def test_set_auth_headers_with_dict_and_auth_configs(self):
135146
self.client._auth_configs = {
136-
'https://example.com': {
137-
'user': 'example',
138-
'password': 'example',
139-
'email': '[email protected]'
147+
'auths': {
148+
'https://example.com': {
149+
'user': 'example',
150+
'password': 'example',
151+
'email': '[email protected]'
152+
}
140153
}
141154
}
142155

143156
headers = {'foo': 'bar'}
144157
expected_headers = {
145-
'foo': 'bar',
146-
'X-Registry-Config': auth.encode_header(self.client._auth_configs)}
158+
'X-Registry-Config': auth.encode_header(
159+
self.client._auth_configs['auths']
160+
),
161+
'foo': 'bar'
162+
}
147163

148164
self.client._set_auth_headers(headers)
149165
assert headers == expected_headers

tests/unit/auth_test.py

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,13 @@ class ResolveAuthTest(unittest.TestCase):
106106
private_config = {'auth': encode_auth({'username': 'privateuser'})}
107107
legacy_config = {'auth': encode_auth({'username': 'legacyauth'})}
108108

109-
auth_config = auth.parse_auth({
110-
'https://index.docker.io/v1/': index_config,
111-
'my.registry.net': private_config,
112-
'http://legacy.registry.url/v1/': legacy_config,
113-
})
109+
auth_config = {
110+
'auths': auth.parse_auth({
111+
'https://index.docker.io/v1/': index_config,
112+
'my.registry.net': private_config,
113+
'http://legacy.registry.url/v1/': legacy_config,
114+
})
115+
}
114116

115117
def test_resolve_authconfig_hostname_only(self):
116118
assert auth.resolve_authconfig(
@@ -360,9 +362,8 @@ def test_load_config_custom_config_env_with_auths(self):
360362

361363
with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}):
362364
cfg = auth.load_config(None)
363-
assert registry in cfg
364-
assert cfg[registry] is not None
365-
cfg = cfg[registry]
365+
assert registry in cfg['auths']
366+
cfg = cfg['auths'][registry]
366367
assert cfg['username'] == 'sakuya'
367368
assert cfg['password'] == 'izayoi'
368369
assert cfg['email'] == '[email protected]'
@@ -390,38 +391,13 @@ def test_load_config_custom_config_env_utf8(self):
390391

391392
with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}):
392393
cfg = auth.load_config(None)
393-
assert registry in cfg
394-
assert cfg[registry] is not None
395-
cfg = cfg[registry]
394+
assert registry in cfg['auths']
395+
cfg = cfg['auths'][registry]
396396
assert cfg['username'] == b'sakuya\xc3\xa6'.decode('utf8')
397397
assert cfg['password'] == b'izayoi\xc3\xa6'.decode('utf8')
398398
assert cfg['email'] == '[email protected]'
399399
assert cfg.get('auth') is None
400400

401-
def test_load_config_custom_config_env_with_headers(self):
402-
folder = tempfile.mkdtemp()
403-
self.addCleanup(shutil.rmtree, folder)
404-
405-
dockercfg_path = os.path.join(folder, 'config.json')
406-
config = {
407-
'HttpHeaders': {
408-
'Name': 'Spike',
409-
'Surname': 'Spiegel'
410-
},
411-
}
412-
413-
with open(dockercfg_path, 'w') as f:
414-
json.dump(config, f)
415-
416-
with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}):
417-
cfg = auth.load_config(None)
418-
assert 'HttpHeaders' in cfg
419-
assert cfg['HttpHeaders'] is not None
420-
cfg = cfg['HttpHeaders']
421-
422-
assert cfg['Name'] == 'Spike'
423-
assert cfg['Surname'] == 'Spiegel'
424-
425401
def test_load_config_unknown_keys(self):
426402
folder = tempfile.mkdtemp()
427403
self.addCleanup(shutil.rmtree, folder)
@@ -448,7 +424,7 @@ def test_load_config_invalid_auth_dict(self):
448424
json.dump(config, f)
449425

450426
cfg = auth.load_config(dockercfg_path)
451-
assert cfg == {'scarlet.net': {}}
427+
assert cfg == {'auths': {'scarlet.net': {}}}
452428

453429
def test_load_config_identity_token(self):
454430
folder = tempfile.mkdtemp()
@@ -469,7 +445,7 @@ def test_load_config_identity_token(self):
469445
json.dump(config, f)
470446

471447
cfg = auth.load_config(dockercfg_path)
472-
assert registry in cfg
473-
cfg = cfg[registry]
448+
assert registry in cfg['auths']
449+
cfg = cfg['auths'][registry]
474450
assert 'IdentityToken' in cfg
475451
assert cfg['IdentityToken'] == token

0 commit comments

Comments
 (0)