Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/aio_fleet/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def verify_registry_tags(
failures: list[str] = []
for tag in tags:
failure = (
_verify_dockerhub_tag(tag)
_verify_dockerhub_tag(docker, tag, env=env)
if _is_dockerhub_tag(tag)
else _verify_with_docker_imagetools(docker, tag, env=env)
)
Expand Down Expand Up @@ -104,7 +104,17 @@ def _is_dockerhub_tag(tag: str) -> bool:
return first in {"docker.io", "index.docker.io"} or "." not in first


def _verify_dockerhub_tag(tag: str, *, attempts: int = 8) -> str | None:
def _verify_dockerhub_tag(
docker: str,
tag: str,
*,
env: Mapping[str, str] | None = None,
attempts: int = 8,
) -> str | None:
docker_failure = _verify_with_docker_imagetools(docker, tag, env=env)
if docker_failure is None:
return None

parsed = _dockerhub_tag_parts(tag)
if parsed is None:
return f"{tag}: unsupported Docker Hub tag format"
Expand Down
72 changes: 45 additions & 27 deletions tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,43 +186,40 @@ def test_component_registry_tags_use_configured_component_image(
assert "jsonbored/example-aio:latest" not in command # nosec B101


def test_dockerhub_verification_uses_tag_api(monkeypatch) -> None:
seen_urls: list[str] = []

class Response:
status = 200

def __enter__(self):
return self

def __exit__(self, *_args) -> None:
return None
def test_dockerhub_verification_uses_docker_imagetools_first(monkeypatch) -> None:
seen_commands: list[list[str]] = []
inspect_env = {"DOCKER_CONFIG": "/workspace/aio-fleet-docker"}
seen_envs: list[dict[str, str] | None] = []
monkeypatch.setattr(registry.shutil, "which", lambda _name: "docker")

def read(self) -> bytes:
return b"{}"
def fake_run(command: list[str], **kwargs):
seen_commands.append(command)
seen_envs.append(kwargs.get("env"))
return SimpleNamespace(returncode=0, stdout="", stderr="")

monkeypatch.setattr(registry.shutil, "which", lambda _name: "docker")
monkeypatch.setattr(registry.subprocess, "run", fake_run)
monkeypatch.setattr(
registry.subprocess,
"run",
registry.urllib.request,
"urlopen",
lambda *_args, **_kwargs: (_ for _ in ()).throw(
AssertionError("Docker Hub tags should not use docker inspect")
AssertionError("Docker Hub tags should use docker inspect before API")
),
)

def fake_urlopen(url: str, timeout: int):
seen_urls.append(url)
assert timeout == 20 # nosec B101
return Response()

monkeypatch.setattr(registry.urllib.request, "urlopen", fake_urlopen)

assert (
registry.verify_registry_tags(["jsonbored/sure-aio:latest"]) == []
registry.verify_registry_tags(["jsonbored/sure-aio:latest"], env=inspect_env)
== []
) # nosec B101
assert seen_urls == [ # nosec B101
"https://hub.docker.com/v2/repositories/jsonbored/sure-aio/tags/latest"
assert seen_commands == [ # nosec B101
[
"docker",
"buildx",
"imagetools",
"inspect",
"jsonbored/sure-aio:latest",
]
]
assert seen_envs == [inspect_env] # nosec B101


def test_dockerhub_verification_reports_malformed_json(monkeypatch) -> None:
Expand All @@ -239,6 +236,13 @@ def read(self) -> bytes:
return b"not json"

monkeypatch.setattr(registry.shutil, "which", lambda _name: "docker")
monkeypatch.setattr(
registry.subprocess,
"run",
lambda *_args, **_kwargs: SimpleNamespace(
returncode=1, stdout="", stderr="not indexed yet"
),
)
monkeypatch.setattr(
registry.urllib.request, "urlopen", lambda *_args, **_kwargs: Response()
)
Expand All @@ -255,6 +259,13 @@ def read(self) -> bytes:

def test_dockerhub_verification_reports_missing_tag(monkeypatch) -> None:
monkeypatch.setattr(registry.shutil, "which", lambda _name: "docker")
monkeypatch.setattr(
registry.subprocess,
"run",
lambda *_args, **_kwargs: SimpleNamespace(
returncode=1, stdout="", stderr="not found"
),
)
monkeypatch.setattr(registry.time, "sleep", lambda _seconds: None)

def fake_urlopen(url: str, timeout: int):
Expand Down Expand Up @@ -293,6 +304,13 @@ def fake_urlopen(url: str, timeout: int):
return Response()

monkeypatch.setattr(registry.shutil, "which", lambda _name: "docker")
monkeypatch.setattr(
registry.subprocess,
"run",
lambda *_args, **_kwargs: SimpleNamespace(
returncode=1, stdout="", stderr="not indexed yet"
),
)
monkeypatch.setattr(registry.urllib.request, "urlopen", fake_urlopen)
monkeypatch.setattr(registry.time, "sleep", sleeps.append)

Expand Down
Loading