#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.12" # dependencies = ["typer>=0.24.0", "httpx>=0.28.1"] # /// #MISE description="Trigger container build workflows via Forgejo API" #USAGE arg "" help="Container name (directory under containers/)" #USAGE flag "--ref " help="Commit SHA or branch to build (defaults to current HEAD)" #USAGE flag "--dry-run" help="Show what would be done without triggering" """Trigger container build workflow via Forgejo API dispatch. Dispatches the unified build-container workflow, which handles both Dockerfile and Nix builds in a single workflow. """ import subprocess import sys from pathlib import Path import httpx import typer REGISTRY = "registry.ops.eblu.me" FORGE_URL = "https://forge.eblu.me" FORGE_API = f"{FORGE_URL}/api/v1" REPO = "eblume/blumeops" FORGE_ACTIONS = f"{FORGE_URL}/{REPO}/actions" WORKFLOW = "build-container.yaml" app = typer.Typer(add_completion=False) def git(*args: str) -> str: result = subprocess.run( ["git", *args], capture_output=True, text=True, check=True ) return result.stdout.strip() def get_forge_token() -> str: result = subprocess.run( ["op", "read", "op://blumeops/w3663ffnvkewbftncqxtcpeavy/api-token"], capture_output=True, text=True, check=True, ) return result.stdout.strip() def list_containers() -> None: typer.echo("Available containers:") for d in sorted(Path("containers").iterdir()): if not d.is_dir(): continue types = [] if (d / "Dockerfile").exists(): types.append("dockerfile") if (d / "default.nix").exists(): types.append("nix") if types: typer.echo(f" - {d.name} ({', '.join(types)})") @app.command() def main( container: str = typer.Argument(help="Container name (directory under containers/)"), ref: str = typer.Option("", "--ref", help="Commit SHA to build (defaults to current HEAD)"), dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be done without triggering"), ) -> None: """Trigger container build workflows via Forgejo API dispatch.""" container_dir = Path("containers") / container has_dockerfile = (container_dir / "Dockerfile").exists() has_nix = (container_dir / "default.nix").exists() if not has_dockerfile and not has_nix: typer.echo(f"Error: No Dockerfile or default.nix found in '{container_dir}'") typer.echo() list_containers() raise typer.Exit(1) if not ref: ref = git("rev-parse", "HEAD") else: # Resolve short SHAs or branch names to full SHA ref = git("rev-parse", ref) short_sha = ref[:7] image = f"blumeops/{container}" # Show expected builds builds = [] if has_dockerfile: builds.append(f" dockerfile -> {REGISTRY}/{image}:v-{short_sha}") if has_nix: builds.append(f" nix -> {REGISTRY}/{image}:v-{short_sha}-nix") if dry_run: typer.echo("[dry-run mode]") typer.echo(f"Container: {container}") typer.echo(f"Commit: {ref} ({short_sha})") typer.echo(f"Expected builds:") for b in builds: typer.echo(b) typer.echo() if dry_run: typer.echo(f"[dry-run] Would dispatch {WORKFLOW}") typer.echo() typer.echo(f"Monitor builds at: {FORGE_ACTIONS}") return token = get_forge_token() headers = { "Authorization": f"token {token}", "Content-Type": "application/json", } url = f"{FORGE_API}/repos/{REPO}/actions/workflows/{WORKFLOW}/dispatches" payload = { "ref": "main", "inputs": { "container": container, "ref": ref, }, } resp = httpx.post(url, json=payload, headers=headers, timeout=30) if resp.status_code == 204: typer.echo(f"Dispatched {WORKFLOW}") else: typer.echo(f"Error dispatching {WORKFLOW}: {resp.status_code} {resp.text}") raise typer.Exit(1) typer.echo() typer.echo(f"Monitor builds at: {FORGE_ACTIONS}") if __name__ == "__main__": app()