Cut forgejo package prune over to OpenBao lane
This commit is contained in:
parent
ba767d5edb
commit
12903e3bed
5 changed files with 240 additions and 46 deletions
|
|
@ -24,6 +24,9 @@ DEFAULT_OWNER = "coulomb"
|
|||
DEFAULT_TYPES = ("container", "pypi", "npm", "generic")
|
||||
DEFAULT_MAX_VERSIONS = 3
|
||||
DEFAULT_APPS_ROOT = Path.home() / "railiance-apps"
|
||||
DEFAULT_FORGEJO_ADMIN_BAO_PATH = "platform/workloads/forgejo/forgejo-admin"
|
||||
DEFAULT_FORGEJO_ADMIN_BAO_FIELD = "API_TOKEN"
|
||||
LEGACY_FORGEJO_TOKEN_FILE = Path("/tmp/forgejo-tegwick-api-token")
|
||||
FORGEJO_IMAGE_RE = re.compile(
|
||||
r"^forgejo\.coulomb\.social/(?:coulomb/)?(?P<name>[^:/]+)(?::(?P<tag>[^/\s]+))?$",
|
||||
re.IGNORECASE,
|
||||
|
|
@ -323,25 +326,97 @@ def build_delete_plans(
|
|||
return plans, errors
|
||||
|
||||
|
||||
def _read_token_file(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8").strip()
|
||||
|
||||
|
||||
def _truthy_env(name: str) -> bool:
|
||||
return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _load_token_from_file_env() -> str | None:
|
||||
for env_name in ("FORGEJO_TOKEN_FILE", "FORGEJO_ADMIN_TOKEN_FILE"):
|
||||
raw_path = os.environ.get(env_name, "").strip()
|
||||
if not raw_path:
|
||||
continue
|
||||
path = Path(raw_path).expanduser()
|
||||
if not path.is_file():
|
||||
raise SystemExit(f"ERROR: {env_name} points to a missing file: {path}")
|
||||
token = _read_token_file(path)
|
||||
if token:
|
||||
return token
|
||||
raise SystemExit(f"ERROR: {env_name} points to an empty file: {path}")
|
||||
return None
|
||||
|
||||
|
||||
def _load_token_from_openbao() -> tuple[str | None, str | None]:
|
||||
bao_bin = os.environ.get("FORGEJO_ADMIN_BAO_CLI", "bao").strip() or "bao"
|
||||
bao_path = (
|
||||
os.environ.get("FORGEJO_ADMIN_BAO_PATH", DEFAULT_FORGEJO_ADMIN_BAO_PATH).strip()
|
||||
or DEFAULT_FORGEJO_ADMIN_BAO_PATH
|
||||
)
|
||||
bao_field = (
|
||||
os.environ.get("FORGEJO_ADMIN_BAO_FIELD", DEFAULT_FORGEJO_ADMIN_BAO_FIELD).strip()
|
||||
or DEFAULT_FORGEJO_ADMIN_BAO_FIELD
|
||||
)
|
||||
if shutil.which(bao_bin) is None:
|
||||
return None, f"{bao_bin} CLI not found"
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[bao_bin, "kv", "get", f"-field={bao_field}", bao_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
detail = exc.stderr.strip() or exc.stdout.strip() or f"exit {exc.returncode}"
|
||||
return None, f"{bao_bin} kv get failed: {detail}"
|
||||
except OSError as exc:
|
||||
return None, f"{bao_bin} invocation failed: {exc}"
|
||||
token = result.stdout.strip()
|
||||
if not token:
|
||||
return None, f"{bao_bin} kv get returned an empty {bao_field} field"
|
||||
return token, None
|
||||
|
||||
|
||||
def _token_help_message(bao_error: str | None) -> str:
|
||||
lines = [
|
||||
"ERROR: Forgejo API token required (read:package + write:package).",
|
||||
" Primary: bao login -method=oidc -path=netkingdom role=forgejo-admin-workload-kv-read",
|
||||
f" Default lane: {DEFAULT_FORGEJO_ADMIN_BAO_PATH} field {DEFAULT_FORGEJO_ADMIN_BAO_FIELD}",
|
||||
" Override lane via FORGEJO_ADMIN_BAO_PATH / FORGEJO_ADMIN_BAO_FIELD if needed.",
|
||||
" Break-glass: set FORGEJO_TOKEN / FORGEJO_ADMIN_TOKEN, or set FORGEJO_TOKEN_FILE / FORGEJO_ADMIN_TOKEN_FILE.",
|
||||
" Legacy /tmp fallback stays opt-in only via FORGEJO_ALLOW_LEGACY_FILE_FALLBACK=1.",
|
||||
" See: railiance-platform/docs/forgejo-package-prune.md",
|
||||
]
|
||||
if bao_error:
|
||||
lines.insert(3, f" OpenBao lookup failed: {bao_error}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def load_token() -> str:
|
||||
for env_name in ("FORGEJO_TOKEN", "FORGEJO_ADMIN_TOKEN"):
|
||||
token = os.environ.get(env_name, "").strip()
|
||||
if token:
|
||||
return token
|
||||
token_file = os.environ.get(
|
||||
"FORGEJO_TOKEN_FILE",
|
||||
os.environ.get("FORGEJO_ADMIN_TOKEN_FILE", "/tmp/forgejo-tegwick-api-token"),
|
||||
)
|
||||
path = Path(token_file).expanduser()
|
||||
if path.is_file():
|
||||
return path.read_text(encoding="utf-8").strip()
|
||||
raise SystemExit(
|
||||
"ERROR: Forgejo API token required (read:package + write:package).\n"
|
||||
" export FORGEJO_TOKEN=<pat> # or FORGEJO_ADMIN_TOKEN\n"
|
||||
" # or save PAT to /tmp/forgejo-tegwick-api-token (chmod 600)\n"
|
||||
" Generate: https://forgejo.coulomb.social/user/settings/applications\n"
|
||||
" See: railiance-platform/docs/forgejo-package-prune.md"
|
||||
)
|
||||
token = _load_token_from_file_env()
|
||||
if token:
|
||||
return token
|
||||
token, bao_error = _load_token_from_openbao()
|
||||
if token:
|
||||
return token
|
||||
|
||||
if _truthy_env("FORGEJO_ALLOW_LEGACY_FILE_FALLBACK"):
|
||||
if LEGACY_FORGEJO_TOKEN_FILE.is_file():
|
||||
token = _read_token_file(LEGACY_FORGEJO_TOKEN_FILE)
|
||||
if token:
|
||||
return token
|
||||
suffix = f"legacy file {LEGACY_FORGEJO_TOKEN_FILE} is empty"
|
||||
else:
|
||||
suffix = f"legacy file {LEGACY_FORGEJO_TOKEN_FILE} is missing"
|
||||
bao_error = f"{bao_error}; {suffix}" if bao_error else suffix
|
||||
|
||||
raise SystemExit(_token_help_message(bao_error))
|
||||
|
||||
|
||||
def emit_summary(
|
||||
|
|
@ -519,4 +594,4 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
raise SystemExit(main())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue