Fix parse_repository_tag to handle images with both tag and digest

The parse_repository_tag() function was incorrectly parsing Docker image
references that contained both a tag and a digest (e.g., nginx:1.0@sha256:abc).

Previously, when splitting by '@' first, the tag would be included in the
repository name, resulting in incorrect parsing:
- Input: "nginx:1.0@sha256:abc123"
- Old output: ("nginx:1.0", "sha256:abc123")
- Expected: ("nginx", "1.0@sha256:abc123")

The fix now:
1. Checks for digest (@) separator first
2. Examines the part before the digest for a tag (:) separator
3. Combines tag and digest as "tag@digest" when both are present

Added test cases:
- test_index_image_tag_and_sha
- test_index_user_image_tag_and_sha
- test_private_reg_image_tag_and_sha
This commit is contained in:
Paul Berruti 2025-11-22 16:58:54 -08:00
parent a985e05482
commit 9e666af1ab
2 changed files with 33 additions and 1 deletions

View File

@ -240,9 +240,23 @@ def convert_service_networks(
def parse_repository_tag(repo_name: str) -> tuple[str, str | None]: def parse_repository_tag(repo_name: str) -> tuple[str, str | None]:
# Check for digest (@ separator) first
parts = repo_name.rsplit("@", 1) parts = repo_name.rsplit("@", 1)
if len(parts) == 2: if len(parts) == 2:
return tuple(parts) # type: ignore # We have a digest, but there might also be a tag before it
repo_and_tag = parts[0]
digest = parts[1]
# Check if there's a tag in the part before the digest
tag_parts = repo_and_tag.rsplit(":", 1)
if len(tag_parts) == 2 and "/" not in tag_parts[1]:
# We have both tag and digest: return repo and "tag@digest"
return tag_parts[0], f"{tag_parts[1]}@{digest}"
else:
# Only digest, no tag: return repo and digest
return repo_and_tag, digest
# No digest, check for tag only
parts = repo_name.rsplit(":", 1) parts = repo_name.rsplit(":", 1)
if len(parts) == 2 and "/" not in parts[1]: if len(parts) == 2 and "/" not in parts[1]:
return tuple(parts) # type: ignore return tuple(parts) # type: ignore

View File

@ -359,6 +359,24 @@ class ParseRepositoryTagTest(unittest.TestCase):
f"sha256:{self.sha}", f"sha256:{self.sha}",
) )
def test_index_image_tag_and_sha(self) -> None:
assert parse_repository_tag(f"root:tag@sha256:{self.sha}") == (
"root",
f"tag@sha256:{self.sha}",
)
def test_index_user_image_tag_and_sha(self) -> None:
assert parse_repository_tag(f"user/repo:tag@sha256:{self.sha}") == (
"user/repo",
f"tag@sha256:{self.sha}",
)
def test_private_reg_image_tag_and_sha(self) -> None:
assert parse_repository_tag(f"url:5000/repo:tag@sha256:{self.sha}") == (
"url:5000/repo",
f"tag@sha256:{self.sha}",
)
class ParseDeviceTest(unittest.TestCase): class ParseDeviceTest(unittest.TestCase):
def test_dict(self) -> None: def test_dict(self) -> None: