Harden catalog publication transaction
This commit is contained in:
@@ -334,6 +334,7 @@ class CatalogPublishResult:
|
||||
target_keyring_path: str
|
||||
branch: str | None
|
||||
tag_name: str | None
|
||||
remote: str
|
||||
validation_valid: bool
|
||||
validation_error: str | None = None
|
||||
validation_warnings: tuple[str, ...] = ()
|
||||
@@ -343,6 +344,9 @@ class CatalogPublishResult:
|
||||
target_keyring_hash_before: str | None = None
|
||||
catalog_changed: bool = False
|
||||
keyring_changed: bool = False
|
||||
publication_commit_sha: str | None = None
|
||||
publication_tag_object_sha: str | None = None
|
||||
publication_tag_commit_sha: str | None = None
|
||||
steps: tuple[CatalogPublishStep, ...] = ()
|
||||
notes: tuple[str, ...] = ()
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,10 @@ from pathlib import Path
|
||||
|
||||
from govoplan_release.module_directory import write_module_directory
|
||||
from govoplan_release.model import to_jsonable
|
||||
from govoplan_release.publisher import publish_catalog_candidate
|
||||
from govoplan_release.publisher import (
|
||||
publication_runtime_trust_issues,
|
||||
publish_catalog_candidate,
|
||||
)
|
||||
from govoplan_release.selective_catalog import build_selective_catalog_candidate
|
||||
from govoplan_release.workspace import DEFAULT_WORKSPACE_ROOT
|
||||
|
||||
@@ -18,11 +21,27 @@ def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
selective = subparsers.add_parser("selective", help="Build a signed selective channel catalog candidate.")
|
||||
selective.add_argument("--workspace-root", type=Path, default=DEFAULT_WORKSPACE_ROOT)
|
||||
selective = subparsers.add_parser(
|
||||
"selective", help="Build a signed selective channel catalog candidate."
|
||||
)
|
||||
selective.add_argument(
|
||||
"--workspace-root", type=Path, default=DEFAULT_WORKSPACE_ROOT
|
||||
)
|
||||
selective.add_argument("--channel", default="stable")
|
||||
selective.add_argument("--repo-version", action="append", default=[], metavar="REPO=VERSION", required=True)
|
||||
selective.add_argument("--catalog-signing-key", action="append", default=[], metavar="KEY_ID=PRIVATE_KEY", required=True)
|
||||
selective.add_argument(
|
||||
"--repo-version",
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="REPO=VERSION",
|
||||
required=True,
|
||||
)
|
||||
selective.add_argument(
|
||||
"--catalog-signing-key",
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="KEY_ID=PRIVATE_KEY",
|
||||
required=True,
|
||||
)
|
||||
selective.add_argument(
|
||||
"--python-artifact",
|
||||
action="append",
|
||||
@@ -56,41 +75,76 @@ def main() -> int:
|
||||
),
|
||||
)
|
||||
selective.add_argument("--public-base-url", default="https://govoplan.add-ideas.de")
|
||||
selective.add_argument("--repository-base", default="git+ssh://git@git.add-ideas.de/add-ideas")
|
||||
selective.add_argument("--source-remote", default="origin", help="Configured Git remote containing immutable source tags.")
|
||||
selective.add_argument(
|
||||
"--repository-base", default="git+ssh://git@git.add-ideas.de/add-ideas"
|
||||
)
|
||||
selective.add_argument(
|
||||
"--source-remote",
|
||||
default="origin",
|
||||
help="Configured Git remote containing immutable source tags.",
|
||||
)
|
||||
selective.add_argument("--expires-days", type=int, default=90)
|
||||
selective.add_argument("--sequence", type=int)
|
||||
selective.add_argument("--skip-public-check", action="store_true")
|
||||
selective.add_argument("--json", action="store_true", help="Print machine-readable summary JSON.")
|
||||
selective.add_argument(
|
||||
"--json", action="store_true", help="Print machine-readable summary JSON."
|
||||
)
|
||||
|
||||
publish = subparsers.add_parser("publish-candidate", help="Publish a reviewed catalog candidate into the website repo.")
|
||||
publish = subparsers.add_parser(
|
||||
"publish-candidate",
|
||||
help="Publish a reviewed catalog candidate into the website repo.",
|
||||
)
|
||||
publish.add_argument("--candidate-dir", type=Path, required=True)
|
||||
publish.add_argument("--workspace-root", type=Path, default=DEFAULT_WORKSPACE_ROOT)
|
||||
publish.add_argument("--web-root", type=Path)
|
||||
publish.add_argument("--channel", default="stable")
|
||||
publish.add_argument("--apply", action="store_true", help="Copy candidate files into the website repo. Without this, only preview.")
|
||||
publish.add_argument(
|
||||
"--apply",
|
||||
action="store_true",
|
||||
help="Copy candidate files into the website repo. Without this, only preview.",
|
||||
)
|
||||
publish.add_argument("--build-web", action="store_true")
|
||||
publish.add_argument("--commit", action="store_true")
|
||||
publish.add_argument("--tag", action="store_true", help="Create a website catalog publication tag. Implies --commit.")
|
||||
publish.add_argument("--push", action="store_true", help="Push the website branch and tag. Implies --commit.")
|
||||
publish.add_argument(
|
||||
"--tag",
|
||||
action="store_true",
|
||||
help="Create a website catalog publication tag. Implies --commit.",
|
||||
)
|
||||
publish.add_argument(
|
||||
"--push",
|
||||
action="store_true",
|
||||
help="Push the website branch and tag. Implies --commit.",
|
||||
)
|
||||
publish.add_argument("--remote", default="origin")
|
||||
publish.add_argument("--source-remote", default="origin", help="Configured Git remote containing catalog source tags.")
|
||||
publish.add_argument(
|
||||
"--source-remote",
|
||||
default="origin",
|
||||
help="Configured Git remote containing catalog source tags.",
|
||||
)
|
||||
publish.add_argument("--branch")
|
||||
publish.add_argument("--tag-name")
|
||||
publish.add_argument("--npm", default="npm")
|
||||
publish.add_argument("--allow-dirty-website", action="store_true")
|
||||
publish.add_argument("--json", action="store_true", help="Print machine-readable summary JSON.")
|
||||
publish.add_argument(
|
||||
"--json", action="store_true", help="Print machine-readable summary JSON."
|
||||
)
|
||||
|
||||
directory = subparsers.add_parser("module-directory", help="Generate browsable module-directory files from a catalog and keyring.")
|
||||
directory = subparsers.add_parser(
|
||||
"module-directory",
|
||||
help="Generate browsable module-directory files from a catalog and keyring.",
|
||||
)
|
||||
directory.add_argument("--catalog", type=Path, required=True)
|
||||
directory.add_argument("--keyring", type=Path, required=True)
|
||||
directory.add_argument("--output-dir", type=Path, required=True)
|
||||
directory.add_argument("--channel", default="stable")
|
||||
directory.add_argument("--public-base-url", default="https://govoplan.add-ideas.de")
|
||||
directory.add_argument("--json", action="store_true", help="Print machine-readable summary JSON.")
|
||||
directory.add_argument(
|
||||
"--json", action="store_true", help="Print machine-readable summary JSON."
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.command == "selective":
|
||||
require_release_runtime_trust()
|
||||
result = build_selective_catalog_candidate(
|
||||
repo_versions=parse_repo_versions(tuple(args.repo_version)),
|
||||
channel=args.channel,
|
||||
@@ -114,6 +168,8 @@ def main() -> int:
|
||||
print_text_summary(payload)
|
||||
return 0 if result.status == "ready" else 1
|
||||
if args.command == "publish-candidate":
|
||||
if args.apply:
|
||||
require_release_runtime_trust()
|
||||
result = publish_catalog_candidate(
|
||||
candidate_dir=args.candidate_dir,
|
||||
workspace_root=args.workspace_root,
|
||||
@@ -138,6 +194,7 @@ def main() -> int:
|
||||
print_publish_summary(payload)
|
||||
return 0 if result.status in {"ready", "applied", "published"} else 1
|
||||
if args.command == "module-directory":
|
||||
require_release_runtime_trust()
|
||||
catalog_payload = json.loads(args.catalog.read_text(encoding="utf-8"))
|
||||
keyring_payload = json.loads(args.keyring.read_text(encoding="utf-8"))
|
||||
files = write_module_directory(
|
||||
@@ -147,7 +204,11 @@ def main() -> int:
|
||||
channel=args.channel,
|
||||
public_base_url=args.public_base_url,
|
||||
)
|
||||
payload = {"status": "generated", "output_dir": str(args.output_dir), "files": [str(path) for path in files]}
|
||||
payload = {
|
||||
"status": "generated",
|
||||
"output_dir": str(args.output_dir),
|
||||
"files": [str(path) for path in files],
|
||||
}
|
||||
if args.json:
|
||||
print(json.dumps(payload, indent=2, sort_keys=True))
|
||||
else:
|
||||
@@ -162,6 +223,12 @@ def main() -> int:
|
||||
return 2
|
||||
|
||||
|
||||
def require_release_runtime_trust() -> None:
|
||||
issues = publication_runtime_trust_issues()
|
||||
if issues:
|
||||
raise SystemExit("Release tooling trust gate failed: " + "; ".join(issues))
|
||||
|
||||
|
||||
def parse_repo_versions(values: tuple[str, ...]) -> dict[str, str]:
|
||||
result: dict[str, str] = {}
|
||||
for value in values:
|
||||
@@ -185,7 +252,9 @@ def parse_repo_paths(values: tuple[str, ...]) -> dict[str, Path]:
|
||||
repo = repo.strip()
|
||||
path_text = path_text.strip()
|
||||
if not repo or not path_text or repo in result:
|
||||
raise SystemExit(f"--python-artifact must uniquely use REPO=/path/to/wheel: {value}")
|
||||
raise SystemExit(
|
||||
f"--python-artifact must uniquely use REPO=/path/to/wheel: {value}"
|
||||
)
|
||||
result[repo] = Path(path_text).expanduser()
|
||||
return result
|
||||
|
||||
|
||||
Reference in New Issue
Block a user