From b26c85ad66c2ceb66e0f7cc2c2004e4d99b68216 Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sat, 9 Aug 2025 14:03:01 -0600 Subject: [PATCH] Image guard (? --- .github/scripts/ensure_compose_image.py | 93 +++++++++++++++++++---- .github/workflows/compose-image-guard.yml | 2 +- 2 files changed, 80 insertions(+), 15 deletions(-) diff --git a/.github/scripts/ensure_compose_image.py b/.github/scripts/ensure_compose_image.py index cd15b88..00c1ee0 100755 --- a/.github/scripts/ensure_compose_image.py +++ b/.github/scripts/ensure_compose_image.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import sys +import subprocess from pathlib import Path +from typing import Tuple try: import yaml @@ -11,34 +13,97 @@ except Exception: EXPECTED_IMAGE = "cooldockerizer93/spotizerr" -def validate_compose_image(path: Path) -> int: - if not path.exists(): - sys.stderr.write(f"File not found: {path}\n") +def load_compose(path: Path): + with path.open("r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} + + +def save_compose(path: Path, data) -> None: + with path.open("w", encoding="utf-8") as f: + yaml.safe_dump(data, f, sort_keys=False) + + +def ensure_image_unversioned(data) -> Tuple[bool, str, str]: + """ + Returns (changed, old_image, new_image) + """ + services = (data or {}).get("services", {}) + svc = services.get("spotizerr", {}) + image = svc.get("image") + if image == EXPECTED_IMAGE: + return False, image, image + + # Normalize to expected image if it has a tag/digest or is different + svc["image"] = EXPECTED_IMAGE + services["spotizerr"] = svc + data["services"] = services + return True, image, EXPECTED_IMAGE + + +def git(*args: str) -> subprocess.CompletedProcess: + return subprocess.run(["git", *args], check=False, text=True, capture_output=True) + + +def autocommit(file_path: str) -> None: + # Configure git identity if missing + git("config", "user.name").stdout + if git("config", "user.name").stdout.strip() == "": + git("config", "user.name", "github-actions[bot]") + if git("config", "user.email").stdout.strip() == "": + git("config", "user.email", "github-actions[bot]@users.noreply.github.com") + # Stage and commit + git("add", file_path) + status = git("status", "--porcelain").stdout.strip() + if status: + msg = "chore: normalize docker-compose image to cooldockerizer93/spotizerr" + commit_res = git("commit", "-m", msg) + if commit_res.returncode != 0: + sys.stderr.write(f"Git commit failed: {commit_res.stderr}\n") + sys.exit(1) + push_res = git("push") + if push_res.returncode != 0: + sys.stderr.write(f"Git push failed: {push_res.stderr}\n") + sys.exit(1) + print("Pushed normalization commit") + else: + print("No changes to commit") + + +def main(argv: list[str]) -> int: + # Usage: ensure_compose_image.py [docker-compose.yaml] [--autocommit] + compose_path = ( + Path(argv[1]) + if len(argv) > 1 and not argv[1].startswith("-") + else Path("docker-compose.yaml") + ) + do_autocommit = any(arg == "--autocommit" for arg in argv[1:]) + + if not compose_path.exists(): + sys.stderr.write(f"File not found: {compose_path}\n") return 1 try: - with path.open("r", encoding="utf-8") as f: - data = yaml.safe_load(f) + data = load_compose(compose_path) except Exception as e: - sys.stderr.write(f"Failed to parse YAML from {path}: {e}\n") + sys.stderr.write(f"Failed to parse YAML from {compose_path}: {e}\n") return 1 - image = (data or {}).get("services", {}).get("spotizerr", {}).get("image") + changed, old_image, new_image = ensure_image_unversioned(data) - if image != EXPECTED_IMAGE: + if changed: + save_compose(compose_path, data) sys.stderr.write( - f"services.spotizerr.image must be '{EXPECTED_IMAGE}' (found '{image}')\n" + f"Normalized services.spotizerr.image from '{old_image}' to '{new_image}'\n" ) + # For pre-commit: exit non-zero to force user to re-stage + if do_autocommit: + autocommit(str(compose_path)) + return 0 return 1 print(f"OK: docker-compose image is '{EXPECTED_IMAGE}'") return 0 -def main(argv: list[str]) -> int: - compose_path = Path(argv[1]) if len(argv) > 1 else Path("docker-compose.yaml") - return validate_compose_image(compose_path) - - if __name__ == "__main__": sys.exit(main(sys.argv)) diff --git a/.github/workflows/compose-image-guard.yml b/.github/workflows/compose-image-guard.yml index a63f297..8a5d406 100644 --- a/.github/workflows/compose-image-guard.yml +++ b/.github/workflows/compose-image-guard.yml @@ -33,4 +33,4 @@ jobs: - name: Validate docker-compose image run: | - python .github/scripts/ensure_compose_image.py docker-compose.yaml \ No newline at end of file + python .github/scripts/ensure_compose_image.py docker-compose.yaml --autocommit \ No newline at end of file