Files
computer-playbook/utils/docker/image/ref.py
T
kevinveenbirkenbachandClaude Opus 5.5 0ed50ae0a5 feat(sys-os): declare the workspace runtime images where the updater finds them
The runtime image pins lived in meta/distros.yml, which neither the docker
updater, the image mirror nor the vulnerability scan reads, so a pin would
only ever move by hand. The new role sys-os carries them in its
meta/services.yml under the distro id; utils/distros.py renders the
workspace matrix from there with the stdlib block reader, and
meta/distros.yml keeps the distro attributes only.

A new release now arrives as an updater pull request. follow: latest holds
debian, ubuntu and fedora to the release their latest tag points at, by
Docker Hub digest: Ubuntu moves between LTS releases only (26.10 shares the
digest of rolling, not latest) and Fedora skips rawhide (46 shares the
digest of rawhide, latest is 45). Arch stays rolling; CentOS stream tags
carry no number the updater can order.

dockerhub_repo and is_dockerhub move to utils/docker/image/ref.py beside
split_registry_and_name, so utils/update/follow.py can use them without an
import cycle and utils/update/docker.py stays under the size cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:55:32 +01:00

71 lines
1.9 KiB
Python

from __future__ import annotations
import re
from typing import Any
DOCKER_HUB_REGISTRIES = frozenset(
{"docker.io", "registry-1.docker.io", "index.docker.io"}
)
GHCR_REGISTRY = "ghcr.io"
MCR_REGISTRY = "mcr.microsoft.com"
IMAGE_NAME_RE = re.compile(
r"^"
r"("
r"(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)"
r"(?:\.(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?))*"
r"(?:\:[0-9]{1,5})?"
r"/"
r")?"
r"[a-z0-9]+(?:[._-][a-z0-9]+)*"
r"(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)*"
r"$"
)
def is_valid_image_name(image: Any) -> bool:
if not isinstance(image, str):
return False
image = image.strip()
if not image or " " in image or "@" in image:
return False
if image.rfind(":") > image.rfind("/"):
return False
return IMAGE_NAME_RE.fullmatch(image) is not None
def split_registry_and_name(image: str) -> tuple[str | None, str] | None:
"""Return (registry, repository_name) for a validated Docker image name."""
image = image.strip()
if not is_valid_image_name(image):
return None
first, sep, rest = image.partition("/")
if not sep:
return None, image
if "." in first or ":" in first or first == "localhost":
return first, rest
return None, image
def is_dockerhub(image: str) -> bool:
parsed = split_registry_and_name(image)
if parsed is None:
return False
registry, _name = parsed
return registry is None or registry in DOCKER_HUB_REGISTRIES
def dockerhub_repo(image: str) -> str:
parsed = split_registry_and_name(image)
if parsed is None:
raise ValueError(f"Invalid Docker image reference: {image!r}")
registry, name = parsed
if registry is not None and registry not in DOCKER_HUB_REGISTRIES:
raise ValueError(f"Image is not a Docker Hub reference: {image!r}")
return name if "/" in name else f"library/{name}"