diff --git a/docker/auth.py b/docker/auth.py index 96a6e3a65..e09609003 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 b2fedb32e..9c0aa01d3 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')