Files
computer-playbook/cli/mirror/util.py
T

135 lines
3.4 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Iterable, Tuple
import yaml
from .model import ImageRef
DOCKER_HUB_PREFIXES = (
"docker.io/",
"registry-1.docker.io/",
"index.docker.io/",
)
def load_yaml(path: Path) -> dict:
with path.open("r", encoding="utf-8") as f:
obj = yaml.safe_load(f) or {}
return obj if isinstance(obj, dict) else {}
def is_docker_hub_image(image: str) -> bool:
"""
Docker Hub images are either:
- implicit: postgres, postgis/postgis
- explicit: docker.io/postgis/postgis
"""
image = image.strip()
if not image:
return False
first = image.split("/", 1)[0]
if first in ("docker.io", "registry-1.docker.io", "index.docker.io"):
return True
# looks like registry host
if "." in first or ":" in first:
return False
return True
def normalize_docker_hub(image: str) -> str:
"""
Remove docker hub registry prefix if present.
"""
image = image.strip()
for prefix in DOCKER_HUB_PREFIXES:
if image.startswith(prefix):
return image[len(prefix) :]
return image
def split_name_and_suffix(image: str) -> Tuple[str, str]:
"""
Split image into base name and suffix:
repo/name:tag -> (repo/name, :tag)
repo/name@sha.. -> (repo/name, @sha..)
repo/name -> (repo/name, "")
"""
image = image.strip()
if "@sha256:" in image:
base, digest = image.split("@", 1)
return base, f"@{digest}"
last_slash = image.rfind("/")
last_colon = image.rfind(":")
if last_colon > last_slash:
return image[:last_colon], image[last_colon:]
return image, ""
def docker_hub_source(image: str, version: str) -> str:
"""
Build canonical docker hub source ref for skopeo.
postgres + 16 -> library/postgres:16
postgis/postgis + 17 -> postgis/postgis:17
"""
image = normalize_docker_hub(image)
base, _suffix = split_name_and_suffix(image) # drop embedded tag/digest if present
if "/" not in base:
base = f"library/{base}"
return f"{base}:{version}"
def canonical_image_name(image: str) -> str:
"""
Canonical image name without registry and without tag/digest.
Keeps docker-hub implicit name as-is (without adding 'library/').
"""
image = normalize_docker_hub(image)
base, _suffix = split_name_and_suffix(image)
return base
def iter_role_images(repo_root: Path) -> Iterable[ImageRef]:
roles_dir = repo_root / "roles"
for config_file in roles_dir.glob("**/config/main.yml"):
role_name = config_file.parent.parent.name
data = load_yaml(config_file)
docker = data.get("compose", {})
services = docker.get("services", {})
if not isinstance(services, dict):
continue
for service_name, service in services.items():
if not isinstance(service, dict):
continue
image = (service.get("image") or "").strip()
version = (service.get("version") or "").strip()
if not image or not version:
continue
if not is_docker_hub_image(image):
continue
yield ImageRef(
role=role_name,
service=str(service_name),
name=canonical_image_name(image),
version=version,
source=docker_hub_source(image, version),
)