Skip to content

Commit 1d750a5

Browse files
authored
Merge pull request ceph#64642 from ShwetaBhosale1/fix_issue_72206_Allow_registry_credentials_to_define_multiple_container_registries
mgr/cephadm: Allow registry credentials to define multiple container registries Reviewed-by: Adam King <[email protected]>
2 parents cb24ff7 + 57deafb commit 1d750a5

File tree

7 files changed

+82
-30
lines changed

7 files changed

+82
-30
lines changed

doc/man/8/cephadm.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,23 @@ Can also use a JSON file containing the login info formatted as::
438438
"password":"REGISTRY_PASSWORD"
439439
}
440440

441+
For multiple registry logins, refer to the format below::
442+
443+
{
444+
"registry_credentials": [
445+
{
446+
"url": "REGISTRY_URL1",
447+
"username": "REGISTRY_USERNAME1",
448+
"password": "REGISTRY_PASSWORD1"
449+
},
450+
{
451+
"url": "REGISTRY_URL2",
452+
"username": "REGISTRY_USERNAME2",
453+
"password": "REGISTRY_PASSWORD2"
454+
}
455+
]
456+
}
457+
441458
and turn it in with command::
442459

443460
cephadm registry-login --registry-json [JSON FILE]

src/cephadm/cephadm.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2863,19 +2863,45 @@ def command_registry_login(ctx: CephadmContext) -> int:
28632863
if ctx.registry_json:
28642864
logger.info('Pulling custom registry login info from %s.' % ctx.registry_json)
28652865
d = get_parm(ctx.registry_json)
2866-
if d.get('url') and d.get('username') and d.get('password'):
2867-
ctx.registry_url = d.get('url')
2868-
ctx.registry_username = d.get('username')
2869-
ctx.registry_password = d.get('password')
2870-
registry_login(ctx, ctx.registry_url, ctx.registry_username, ctx.registry_password)
2871-
else:
2872-
raise Error('json provided for custom registry login did not include all necessary fields. '
2873-
'Please setup json file as\n'
2874-
'{\n'
2875-
' "url": "REGISTRY_URL",\n'
2876-
' "username": "REGISTRY_USERNAME",\n'
2877-
' "password": "REGISTRY_PASSWORD"\n'
2878-
'}\n')
2866+
# to support multiple container registries, the command will now accept a list
2867+
# of dictionaries. For backward compatibility, it will first check for the presence
2868+
# of the registry_credentials key. If the key is not found, it will fall back to
2869+
# parsing the old JSON format.
2870+
example_multi_registry = {
2871+
'registry_credentials': [
2872+
{
2873+
'url': 'REGISTRY_URL1',
2874+
'username': 'REGISTRY_USERNAME1',
2875+
'password': 'REGISTRY_PASSWORD1'
2876+
},
2877+
{
2878+
'url': 'REGISTRY_URL2',
2879+
'username': 'REGISTRY_USERNAME2',
2880+
'password': 'REGISTRY_PASSWORD2'
2881+
}
2882+
]
2883+
}
2884+
registry_creds = d.get('registry_credentials')
2885+
if not registry_creds:
2886+
registry_creds = [d]
2887+
for d in registry_creds:
2888+
if d.get('url') and d.get('username') and d.get('password'):
2889+
ctx.registry_url = d.get('url')
2890+
ctx.registry_username = d.get('username')
2891+
ctx.registry_password = d.get('password')
2892+
registry_login(ctx, ctx.registry_url, ctx.registry_username, ctx.registry_password)
2893+
else:
2894+
raise Error(
2895+
'json provided for custom registry login did not include all necessary fields. '
2896+
'Please setup json file as\n'
2897+
'{\n'
2898+
' "url": "REGISTRY_URL",\n'
2899+
' "username": "REGISTRY_USERNAME",\n'
2900+
' "password": "REGISTRY_PASSWORD"\n'
2901+
'}\n'
2902+
'or as below for multiple registry login\n'
2903+
f'{json.dumps(example_multi_registry, indent=4)}'
2904+
)
28792905
elif ctx.registry_url and ctx.registry_username and ctx.registry_password:
28802906
registry_login(ctx, ctx.registry_url, ctx.registry_username, ctx.registry_password)
28812907
else:

src/cephadm/cephadmlib/context_getters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def _get_config_json(option: str) -> Dict[str, Any]:
4545
return js
4646

4747

48-
def get_parm(option: str) -> Dict[str, str]:
48+
def get_parm(option: str) -> Dict[str, Any]:
4949
js = _get_config_json(option)
5050
# custom_config_files is a special field that may be in the config
5151
# dict. It is used for mounting custom config files into daemon's containers

src/cephadm/tests/test_cephadm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ def test_registry_login(self, _logger, _get_parm, _call_throws):
603603
['registry-login', '--registry-json', 'sample-json'])
604604
with pytest.raises(Exception) as e:
605605
assert _cephadm.command_registry_login(ctx)
606-
assert str(e.value) == ("json provided for custom registry login did not include all necessary fields. "
606+
assert str(e.value).startswith("json provided for custom registry login did not include all necessary fields. "
607607
"Please setup json file as\n"
608608
"{\n"
609609
" \"url\": \"REGISTRY_URL\",\n"

src/pybind/mgr/cephadm/module.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,18 +1316,28 @@ def registry_login(self, url: Optional[str] = None, username: Optional[str] = No
13161316
return -errno.EINVAL, "", ("Invalid arguments. Please provide arguments <url> <username> <password> "
13171317
"or -i <login credentials json file>")
13181318
elif (url and username and password):
1319-
registry_json = {'url': url, 'username': username, 'password': password}
1319+
registry_json = {'registry_credentials': [{'url': url, 'username': username, 'password': password}]}
13201320
else:
13211321
assert isinstance(inbuf, str)
13221322
registry_json = json.loads(inbuf)
1323-
if "url" not in registry_json or "username" not in registry_json or "password" not in registry_json:
1324-
return -errno.EINVAL, "", ("json provided for custom registry login did not include all necessary fields. "
1325-
"Please setup json file as\n"
1326-
"{\n"
1327-
" \"url\": \"REGISTRY_URL\",\n"
1328-
" \"username\": \"REGISTRY_USERNAME\",\n"
1329-
" \"password\": \"REGISTRY_PASSWORD\"\n"
1330-
"}\n")
1323+
registry_creds = registry_json.get('registry_credentials')
1324+
if not registry_creds:
1325+
if isinstance(registry_json, dict) and all(
1326+
isinstance(k, str) and isinstance(v, str) for k, v in registry_json.items()
1327+
):
1328+
registry_creds = [registry_json] # type: ignore[list-item]
1329+
registry_json = {'registry_credentials': registry_creds}
1330+
else:
1331+
return -errno.EINVAL, "", "Invalid login credentials json file"
1332+
for d in registry_creds:
1333+
if "url" not in d or "username" not in d or "password" not in d:
1334+
return -errno.EINVAL, "", ("json provided for custom registry login did not include all necessary fields. "
1335+
"Please setup json file as\n"
1336+
"{\n"
1337+
" \"url\": \"REGISTRY_URL\",\n"
1338+
" \"username\": \"REGISTRY_USERNAME\",\n"
1339+
" \"password\": \"REGISTRY_PASSWORD\"\n"
1340+
"}\n")
13311341

13321342
# verify login info works by attempting login on random host
13331343
host = None

src/pybind/mgr/cephadm/serve.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,15 +1823,15 @@ async def _get_container_image_info(self, image_name: str) -> ContainerInspectIn
18231823
return r
18241824

18251825
# function responsible for logging single host into custom registry
1826-
async def _registry_login(self, host: str, registry_json: Dict[str, str]) -> Optional[str]:
1826+
async def _registry_login(self, host: str, registry_json: Dict[str, list[Dict[str, str]]]) -> Optional[str]:
18271827
self.log.debug(
1828-
f"Attempting to log host {host} into custom registry @ {registry_json['url']}")
1828+
f"Attempting to log host {host} into custom registries")
18291829
# want to pass info over stdin rather than through normal list of args
18301830
out, err, code = await self._run_cephadm(
18311831
host, 'mon', 'registry-login',
18321832
['--registry-json', '-'], stdin=json.dumps(registry_json), error_ok=True)
18331833
if code:
1834-
return f"Host {host} failed to login to {registry_json['url']} as {registry_json['username']} with given password"
1834+
return f"Host {host} failed to login to all registries"
18351835
return None
18361836

18371837
async def _deploy_cephadm_binary(self, host: str, addr: Optional[str] = None) -> None:

src/pybind/mgr/cephadm/tests/test_cephadm.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,8 +2240,7 @@ def test_client_keyrings_special_host_labels(self, cephadm_module):
22402240
@mock.patch("cephadm.serve.CephadmServe._run_cephadm")
22412241
def test_registry_login(self, _run_cephadm, cephadm_module: CephadmOrchestrator):
22422242
def check_registry_credentials(url, username, password):
2243-
assert json.loads(cephadm_module.get_store('registry_credentials')) == {
2244-
'url': url, 'username': username, 'password': password}
2243+
assert json.loads(cephadm_module.get_store('registry_credentials')) == {'registry_credentials': [{'url': url, 'username': username, 'password': password}]}
22452244

22462245
_run_cephadm.side_effect = async_side_effect(('{}', '', 0))
22472246
with with_host(cephadm_module, 'test'):
@@ -2280,7 +2279,7 @@ def check_registry_credentials(url, username, password):
22802279
# test bad login where args are valid but login command fails
22812280
_run_cephadm.side_effect = async_side_effect(('{}', 'error', 1))
22822281
code, out, err = cephadm_module.registry_login('fail-url', 'fail-user', 'fail-password')
2283-
assert err == 'Host test failed to login to fail-url as fail-user with given password'
2282+
assert err == 'Host test failed to login to all registries'
22842283
check_registry_credentials('json-url', 'json-user', 'json-pass')
22852284

22862285
@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm(json.dumps({

0 commit comments

Comments
 (0)