Image guard (?
This commit is contained in:
93
.github/scripts/ensure_compose_image.py
vendored
93
.github/scripts/ensure_compose_image.py
vendored
@@ -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))
|
||||
|
||||
2
.github/workflows/compose-image-guard.yml
vendored
2
.github/workflows/compose-image-guard.yml
vendored
@@ -33,4 +33,4 @@ jobs:
|
||||
|
||||
- name: Validate docker-compose image
|
||||
run: |
|
||||
python .github/scripts/ensure_compose_image.py docker-compose.yaml
|
||||
python .github/scripts/ensure_compose_image.py docker-compose.yaml --autocommit
|
||||
Reference in New Issue
Block a user