Skip to content

Commit 57deafb

Browse files
mgr/cephadm: Allow registry credentials to define multiple container registries
Fixes: https://tracker.ceph.com/issues/72206 Signed-off-by: Shweta Bhosale <[email protected]>
1 parent 14757d7 commit 57deafb

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
@@ -2860,19 +2860,45 @@ def command_registry_login(ctx: CephadmContext) -> int:
28602860
if ctx.registry_json:
28612861
logger.info('Pulling custom registry login info from %s.' % ctx.registry_json)
28622862
d = get_parm(ctx.registry_json)
2863-
if d.get('url') and d.get('username') and d.get('password'):
2864-
ctx.registry_url = d.get('url')
2865-
ctx.registry_username = d.get('username')
2866-
ctx.registry_password = d.get('password')
2867-
registry_login(ctx, ctx.registry_url, ctx.registry_username, ctx.registry_password)
2868-
else:
2869-
raise Error('json provided for custom registry login did not include all necessary fields. '
2870-
'Please setup json file as\n'
2871-
'{\n'
2872-
' "url": "REGISTRY_URL",\n'
2873-
' "username": "REGISTRY_USERNAME",\n'
2874-
' "password": "REGISTRY_PASSWORD"\n'
2875-
'}\n')
2863+
# to support multiple container registries, the command will now accept a list
2864+
# of dictionaries. For backward compatibility, it will first check for the presence
2865+
# of the registry_credentials key. If the key is not found, it will fall back to
2866+
# parsing the old JSON format.
2867+
example_multi_registry = {
2868+
'registry_credentials': [
2869+
{
2870+
'url': 'REGISTRY_URL1',
2871+
'username': 'REGISTRY_USERNAME1',
2872+
'password': 'REGISTRY_PASSWORD1'
2873+
},
2874+
{
2875+
'url': 'REGISTRY_URL2',
2876+
'username': 'REGISTRY_USERNAME2',
2877+
'password': 'REGISTRY_PASSWORD2'
2878+
}
2879+
]
2880+
}
2881+
registry_creds = d.get('registry_credentials')
2882+
if not registry_creds:
2883+
registry_creds = [d]
2884+
for d in registry_creds:
2885+
if d.get('url') and d.get('username') and d.get('password'):
2886+
ctx.registry_url = d.get('url')
2887+
ctx.registry_username = d.get('username')
2888+
ctx.registry_password = d.get('password')
2889+
registry_login(ctx, ctx.registry_url, ctx.registry_username, ctx.registry_password)
2890+
else:
2891+
raise Error(
2892+
'json provided for custom registry login did not include all necessary fields. '
2893+
'Please setup json file as\n'
2894+
'{\n'
2895+
' "url": "REGISTRY_URL",\n'
2896+
' "username": "REGISTRY_USERNAME",\n'
2897+
' "password": "REGISTRY_PASSWORD"\n'
2898+
'}\n'
2899+
'or as below for multiple registry login\n'
2900+
f'{json.dumps(example_multi_registry, indent=4)}'
2901+
)
28762902
elif ctx.registry_url and ctx.registry_username and ctx.registry_password:
28772903
registry_login(ctx, ctx.registry_url, ctx.registry_username, ctx.registry_password)
28782904
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
@@ -1310,18 +1310,28 @@ def registry_login(self, url: Optional[str] = None, username: Optional[str] = No
13101310
return -errno.EINVAL, "", ("Invalid arguments. Please provide arguments <url> <username> <password> "
13111311
"or -i <login credentials json file>")
13121312
elif (url and username and password):
1313-
registry_json = {'url': url, 'username': username, 'password': password}
1313+
registry_json = {'registry_credentials': [{'url': url, 'username': username, 'password': password}]}
13141314
else:
13151315
assert isinstance(inbuf, str)
13161316
registry_json = json.loads(inbuf)
1317-
if "url" not in registry_json or "username" not in registry_json or "password" not in registry_json:
1318-
return -errno.EINVAL, "", ("json provided for custom registry login did not include all necessary fields. "
1319-
"Please setup json file as\n"
1320-
"{\n"
1321-
" \"url\": \"REGISTRY_URL\",\n"
1322-
" \"username\": \"REGISTRY_USERNAME\",\n"
1323-
" \"password\": \"REGISTRY_PASSWORD\"\n"
1324-
"}\n")
1317+
registry_creds = registry_json.get('registry_credentials')
1318+
if not registry_creds:
1319+
if isinstance(registry_json, dict) and all(
1320+
isinstance(k, str) and isinstance(v, str) for k, v in registry_json.items()
1321+
):
1322+
registry_creds = [registry_json] # type: ignore[list-item]
1323+
registry_json = {'registry_credentials': registry_creds}
1324+
else:
1325+
return -errno.EINVAL, "", "Invalid login credentials json file"
1326+
for d in registry_creds:
1327+
if "url" not in d or "username" not in d or "password" not in d:
1328+
return -errno.EINVAL, "", ("json provided for custom registry login did not include all necessary fields. "
1329+
"Please setup json file as\n"
1330+
"{\n"
1331+
" \"url\": \"REGISTRY_URL\",\n"
1332+
" \"username\": \"REGISTRY_USERNAME\",\n"
1333+
" \"password\": \"REGISTRY_PASSWORD\"\n"
1334+
"}\n")
13251335

13261336
# verify login info works by attempting login on random host
13271337
host = None

src/pybind/mgr/cephadm/serve.py

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

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

18291829
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)