Files
govoplan/tools/release/release-plan.py
Albrecht Degering 05ae81d641
Some checks failed
Dependency Audit / dependency-audit (push) Failing after 16s
Security Audit / security-audit (push) Failing after 11s
intermediate commit
2026-07-14 13:32:09 +02:00

85 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Generate a dry-run selective GovOPlaN release plan."""
from __future__ import annotations
import argparse
from datetime import UTC, datetime
import json
from pathlib import Path
from govoplan_release import build_dashboard, build_selective_release_plan
from govoplan_release.model import to_jsonable
from govoplan_release.workspace import DEFAULT_WORKSPACE_ROOT, META_ROOT
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--workspace-root", type=Path, default=DEFAULT_WORKSPACE_ROOT)
parser.add_argument("--repo", action="append", default=[], help="Repository to include. May be repeated.")
parser.add_argument("--target-version", help="Default target version for selected repositories.")
parser.add_argument(
"--repo-version",
action="append",
default=[],
metavar="REPO=VERSION",
help="Per-repository target version. May be repeated.",
)
parser.add_argument("--channel", default="stable", help="Release channel to plan for. Defaults to stable.")
parser.add_argument("--online", action="store_true", help="Check published catalog/keyring.")
parser.add_argument("--remote-tags", action="store_true", help="Also check remote Git tags. This can be slow across many repositories.")
parser.add_argument("--include-migrations", action="store_true", help="Run migration audits before planning.")
parser.add_argument("--output", type=Path, help="Write JSON plan to this path.")
args = parser.parse_args()
dashboard = build_dashboard(
workspace_root=args.workspace_root,
target_version=args.target_version,
online=args.online,
check_remote_tags=args.remote_tags,
check_public_catalog=args.online,
include_migrations=args.include_migrations,
channel=args.channel,
)
plan = build_selective_release_plan(
dashboard,
selected_repos=tuple(args.repo),
target_version=args.target_version,
repo_versions=parse_repo_versions(tuple(args.repo_version)),
channel=args.channel,
)
payload = json.dumps(to_jsonable(plan), indent=2, sort_keys=True) + "\n"
if args.output:
output = args.output.expanduser()
if not output.is_absolute():
output = META_ROOT / output
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(payload, encoding="utf-8")
print(output)
else:
print(payload, end="")
return 1 if plan.status == "blocked" else 0
def parse_repo_versions(values: tuple[str, ...]) -> dict[str, str]:
result: dict[str, str] = {}
for value in values:
if "=" not in value:
raise SystemExit(f"--repo-version must use REPO=VERSION: {value}")
repo, version = value.split("=", 1)
repo = repo.strip()
version = version.strip()
if not repo or not version:
raise SystemExit(f"--repo-version must use REPO=VERSION: {value}")
result[repo] = version
return result
def default_plan_path(channel: str) -> Path:
stamp = datetime.now(tz=UTC).strftime("%Y%m%d-%H%M%S")
return META_ROOT / "runtime" / "release-plans" / f"{channel}-{stamp}.json"
if __name__ == "__main__":
raise SystemExit(main())