blumeops/mise-tasks/container-build-and-release
Erich Blume 7641018c6a Fix container build workflows to checkout dispatch ref
When manually dispatching a container build with --ref, the build job
now checks out the specified commit instead of the branch HEAD. This
allows building containers from feature branches before merging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:24:32 -08:00

142 lines
4.2 KiB
Text
Executable file

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = ["typer>=0.15.0", "httpx>=0.28.0"]
# ///
#MISE description="Trigger container build workflows via Forgejo API"
#USAGE arg "<container>" help="Container name (directory under containers/)"
#USAGE flag "--ref <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 workflows via Forgejo API dispatch.
Dispatches both Build Container and Build Container (Nix) workflows.
Each workflow checks for its build file and skips if not present.
"""
import subprocess
import sys
from pathlib import Path
import httpx
import typer
REGISTRY = "registry.ops.eblu.me"
FORGE_URL = "https://forge.ops.eblu.me"
FORGE_API = f"{FORGE_URL}/api/v1"
REPO = "eblume/blumeops"
FORGE_ACTIONS = f"{FORGE_URL}/{REPO}/actions"
WORKFLOWS = [
"build-container.yaml",
"build-container-nix.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")
short_sha = ref[:7]
image = f"blumeops/{container}"
# Show expected builds
builds = []
if has_dockerfile:
builds.append(f" dockerfile -> {REGISTRY}/{image}:v<version>-{short_sha}")
if has_nix:
builds.append(f" nix -> {REGISTRY}/{image}:v<version>-{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("[dry-run] Would dispatch workflows:")
for wf in WORKFLOWS:
typer.echo(f" - {wf}")
typer.echo()
typer.echo(f"Monitor builds at: {FORGE_ACTIONS}")
return
token = get_forge_token()
headers = {
"Authorization": f"token {token}",
"Content-Type": "application/json",
}
for wf in WORKFLOWS:
url = f"{FORGE_API}/repos/{REPO}/actions/workflows/{wf}/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 {wf}")
else:
typer.echo(f"Error dispatching {wf}: {resp.status_code} {resp.text}")
raise typer.Exit(1)
typer.echo()
typer.echo(f"Monitor builds at: {FORGE_ACTIONS}")
if __name__ == "__main__":
app()