mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
135 lines
3.4 KiB
Python
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),
|
|
)
|