Add registry-backed module package releases
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Configure and verify protected GovOPlaN package-release boundaries."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from gitea_common import (
|
||||
GiteaClient,
|
||||
GiteaError,
|
||||
RepoTarget,
|
||||
load_dotenv,
|
||||
org_path,
|
||||
quote_path,
|
||||
repo_path,
|
||||
require_token,
|
||||
)
|
||||
|
||||
|
||||
META_ROOT = Path(__file__).resolve().parents[2]
|
||||
REQUIRED_SECRETS = {"GOVOPLAN_PACKAGE_USERNAME", "GOVOPLAN_PACKAGE_TOKEN"}
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--url", default="https://git.add-ideas.de")
|
||||
parser.add_argument("--owner", default="GovOPlaN")
|
||||
parser.add_argument("--team", default="Owners")
|
||||
parser.add_argument("--pattern", default="v*")
|
||||
parser.add_argument("--env-file", type=Path)
|
||||
parser.add_argument("--apply", action="store_true")
|
||||
return parser
|
||||
|
||||
|
||||
def package_repositories() -> tuple[str, ...]:
|
||||
inventory = json.loads((META_ROOT / "repositories.json").read_text(encoding="utf-8"))
|
||||
values = ["govoplan"]
|
||||
for item in inventory["repositories"]:
|
||||
name = str(item["name"])
|
||||
repository = META_ROOT.parent / str(item["path"])
|
||||
if name.startswith("govoplan-") and (repository / "pyproject.toml").is_file():
|
||||
values.append(name)
|
||||
return tuple(sorted(set(values)))
|
||||
|
||||
|
||||
def configure(
|
||||
client: GiteaClient,
|
||||
*,
|
||||
owner: str,
|
||||
team: str,
|
||||
pattern: str,
|
||||
apply: bool,
|
||||
) -> tuple[str, ...]:
|
||||
missing: list[str] = []
|
||||
expected = {
|
||||
"name_pattern": pattern,
|
||||
"whitelist_teams": [team],
|
||||
"whitelist_usernames": [],
|
||||
}
|
||||
for repository in package_repositories():
|
||||
path = repo_path(owner, repository, "/tag_protections")
|
||||
protections = client.request_json("GET", path)
|
||||
matching = [
|
||||
item
|
||||
for item in protections
|
||||
if isinstance(item, dict) and item.get("name_pattern") == pattern
|
||||
]
|
||||
if len(matching) == 1 and _matches(matching[0], expected):
|
||||
print(f"protected {repository}:{pattern}")
|
||||
continue
|
||||
missing.append(repository)
|
||||
if not apply:
|
||||
print(f"would protect {repository}:{pattern}")
|
||||
continue
|
||||
if len(matching) == 1:
|
||||
protection_id = matching[0].get("id")
|
||||
client.request_json(
|
||||
"PATCH",
|
||||
f"{path}/{quote_path(str(protection_id))}",
|
||||
body=expected,
|
||||
)
|
||||
print(f"updated {repository}:{pattern}")
|
||||
elif not matching:
|
||||
client.request_json("POST", path, body=expected)
|
||||
print(f"created {repository}:{pattern}")
|
||||
else:
|
||||
raise GiteaError(f"{repository} has duplicate {pattern!r} tag protections")
|
||||
return tuple(missing)
|
||||
|
||||
|
||||
def _matches(value: dict[str, object], expected: dict[str, object]) -> bool:
|
||||
return (
|
||||
value.get("name_pattern") == expected["name_pattern"]
|
||||
and sorted(value.get("whitelist_teams") or []) == expected["whitelist_teams"]
|
||||
and sorted(value.get("whitelist_usernames") or []) == expected["whitelist_usernames"]
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = build_parser().parse_args()
|
||||
try:
|
||||
load_dotenv(args.env_file)
|
||||
token = require_token()
|
||||
target = RepoTarget(base_url=args.url, owner=args.owner, repo="govoplan")
|
||||
with GiteaClient(target, token) as client:
|
||||
mismatches = configure(
|
||||
client,
|
||||
owner=args.owner,
|
||||
team=args.team,
|
||||
pattern=args.pattern,
|
||||
apply=args.apply,
|
||||
)
|
||||
secrets = client.request_json(
|
||||
"GET", org_path(args.owner, "/actions/secrets"), query={"limit": 50}
|
||||
)
|
||||
names = {
|
||||
str(item.get("name") or "")
|
||||
for item in secrets
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
missing_secrets = sorted(REQUIRED_SECRETS - names)
|
||||
if missing_secrets:
|
||||
print(
|
||||
"Missing organization Actions secrets: " + ", ".join(missing_secrets),
|
||||
file=sys.stderr,
|
||||
)
|
||||
unresolved = (bool(mismatches) and not args.apply) or bool(missing_secrets)
|
||||
if unresolved:
|
||||
return 1
|
||||
print("Package release protection and credential names are configured.")
|
||||
return 0
|
||||
except (GiteaError, OSError, ValueError, json.JSONDecodeError) as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user