From cf23bd19ef78630676af28c3a7b24f36864d900e Mon Sep 17 00:00:00 2001 From: semx <7532921+semx@users.noreply.github.com> Date: Wed, 5 Aug 2026 22:33:56 +0400 Subject: [PATCH] Treat an uppercase first component as a registry host in split_repo_name The daemon's reference grammar (distribution/reference splitDockerDomain) treats the first component of an image reference as a domain when it contains an uppercase letter, since image path components are lowercase. docker-py only checked for '.', ':' or 'localhost', so a reference like MyRegistry/image was resolved as a Hub path and the wrong registry's auth config was selected. Align split_repo_name with the daemon. Signed-off-by: semx <7532921+semx@users.noreply.github.com> --- docker/auth.py | 11 +++++++++-- tests/unit/auth_test.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/docker/auth.py b/docker/auth.py index 96a6e3a656..e096090030 100644 --- a/docker/auth.py +++ b/docker/auth.py @@ -59,9 +59,16 @@ def get_config_header(client, registry): def split_repo_name(repo_name): parts = repo_name.split('/', 1) if len(parts) == 1 or ( - '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost' + '.' not in parts[0] and ':' not in parts[0] + and parts[0] != 'localhost' and parts[0] == parts[0].lower() ): - # This is a docker index repo (ex: username/foobar or ubuntu) + # This is a docker index repo (ex: username/foobar or ubuntu). + # The first component is only a registry when it looks like a host: + # it contains a '.' or ':', is 'localhost', or contains an uppercase + # letter (image path components are always lowercase). This mirrors + # the daemon's reference grammar (distribution/reference + # splitDockerDomain), so the auth registry matches what the daemon + # routes on. return INDEX_NAME, repo_name return tuple(parts) diff --git a/tests/unit/auth_test.py b/tests/unit/auth_test.py index b2fedb32e4..9c0aa01d3a 100644 --- a/tests/unit/auth_test.py +++ b/tests/unit/auth_test.py @@ -85,6 +85,20 @@ def test_resolve_repository_name_localhost_with_username(self): 'localhost', 'username/image' ) + def test_resolve_repository_name_uppercase_registry(self): + # A first component containing an uppercase letter is a registry host, + # not a Hub path: image path components are always lowercase, so the + # daemon's reference grammar treats it as a domain. Keep auth registry + # selection in sync with that. + assert auth.resolve_repository_name('MyRegistry/image') == ( + 'MyRegistry', 'image' + ) + + def test_resolve_repository_name_uppercase_registry_with_username(self): + assert auth.resolve_repository_name('MyRegistry/username/image') == ( + 'MyRegistry', 'username/image' + ) + def test_invalid_index_name(self): with pytest.raises(errors.InvalidRepository): auth.resolve_repository_name('-gecko.com/image')