feat(observatory): seal blocked run preparations

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 23:14:05 +03:00
parent 8d5aeb0533
commit 2a5763d3fb
12 changed files with 1935 additions and 25 deletions
@@ -43,9 +43,78 @@ def sha256_file(path: Path) -> str:
def git_revision() -> str:
result = subprocess.run(
["git", "rev-parse", "HEAD"], cwd=REPOSITORY_ROOT, check=True, capture_output=True, text=True
["git", "rev-parse", "HEAD"],
cwd=REPOSITORY_ROOT,
check=True,
capture_output=True,
text=True,
)
return result.stdout.strip()
revision = result.stdout.strip()
if re.fullmatch(r"[a-f0-9]{40}", revision) is None:
raise ArtifactBuildError("Git revision is not a full SHA-1")
return revision
def materialize_revision(revision: str, destination: Path) -> None:
"""Materialize only the declared release inputs from one exact Git commit."""
if re.fullmatch(r"[a-f0-9]{40}", revision) is None:
raise ArtifactBuildError("artifact revision is invalid")
if destination.exists():
raise ArtifactBuildError("revision destination already exists")
verified = subprocess.run(
["git", "rev-parse", "--verify", f"{revision}^{{commit}}"],
cwd=REPOSITORY_ROOT,
check=False,
capture_output=True,
text=True,
)
if verified.returncode != 0 or verified.stdout.strip() != revision:
raise ArtifactBuildError("artifact revision does not identify an existing commit")
archive_path = destination.parent / "source.tar"
archived = subprocess.run(
[
"git",
"archive",
"--format=tar",
"--output",
str(archive_path),
revision,
"--",
*(path.as_posix() for path in SOURCES),
],
cwd=REPOSITORY_ROOT,
check=False,
capture_output=True,
text=True,
)
if archived.returncode != 0:
raise ArtifactBuildError("declared release inputs are not present in artifact revision")
destination.mkdir()
root = destination.resolve()
with tarfile.open(archive_path, "r:") as archive:
members = archive.getmembers()
for member in members:
target = (destination / member.name).resolve()
if target != root and root not in target.parents:
raise ArtifactBuildError("Git archive contains an unsafe path")
if not member.isdir() and not member.isreg():
raise ArtifactBuildError("Git archive contains a non-regular release input")
for member in members:
target = destination / member.name
if member.isdir():
target.mkdir(parents=True, exist_ok=True)
continue
stream = archive.extractfile(member)
if stream is None:
raise ArtifactBuildError("Git archive release input cannot be read")
target.parent.mkdir(parents=True, exist_ok=True)
with stream, target.open("wb") as output:
while chunk := stream.read(1024 * 1024):
output.write(chunk)
def tar_info(path: Path, arcname: str) -> tarfile.TarInfo:
@@ -81,24 +150,34 @@ def write_archive(stage: Path, target: Path) -> None:
archive.addfile(info, io.BytesIO())
def build(patch_id: str, output_directory: Path, *, revision: str | None = None) -> dict[str, object]:
def build(
patch_id: str,
output_directory: Path,
*,
revision: str | None = None,
) -> dict[str, object]:
if PATCH_ID.fullmatch(patch_id) is None:
raise ArtifactBuildError("patch id is invalid")
sources = tuple(REPOSITORY_ROOT / source for source in SOURCES)
if any(path.is_symlink() or not path.is_file() for path in sources):
raise ArtifactBuildError("release input is not a regular file")
selected_revision = revision or git_revision()
if re.fullmatch(r"[a-f0-9]{40}", selected_revision) is None:
raise ArtifactBuildError("artifact revision is invalid")
with tempfile.TemporaryDirectory(prefix="mission-core-m49-tgs-full-") as directory:
stage = Path(directory)
snapshot = stage / "source"
materialize_revision(selected_revision, snapshot)
sources = tuple(snapshot / source for source in SOURCES)
if any(path.is_symlink() or not path.is_file() for path in sources):
raise ArtifactBuildError("release input is not a regular file")
payload = stage / "payload"
payload.mkdir()
files: dict[str, dict[str, object]] = {}
for source in sources:
destination = payload / source.name
destination.write_bytes(source.read_bytes())
files[destination.name] = {"bytes": destination.stat().st_size, "sha256": sha256_file(destination)}
files[destination.name] = {
"bytes": destination.stat().st_size,
"sha256": sha256_file(destination),
}
release = {
"schema_version": "missioncore.m49-tgs-full-shadow-worker-release/v1",
"patch_id": patch_id,
@@ -106,7 +185,9 @@ def build(patch_id: str, output_directory: Path, *, revision: str | None = None)
"worker_id": "worker-006",
"candidate_id": "travel-tgs-full-shadow",
"license": "GPL-3.0-or-later",
"source_pack_sha256": "0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944",
"source_pack_sha256": (
"0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944"
),
"images": {
"travel": "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f",
"parity": "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0",
@@ -121,10 +202,14 @@ def build(patch_id: str, output_directory: Path, *, revision: str | None = None)
"files": files,
}
release_path = payload / "release.json"
release_path.write_text(json.dumps(release, indent=2, sort_keys=True) + "\n", encoding="utf-8")
release_path.write_text(
json.dumps(release, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
payload_names = sorted((*files, release_path.name))
(stage / "manifest.env").write_text(
f"id={patch_id}\ncomponent=mission-core-worker\ntype=qualification-release\n", encoding="utf-8"
f"id={patch_id}\ncomponent=mission-core-worker\ntype=qualification-release\n",
encoding="utf-8",
)
(stage / "files.txt").write_text("\n".join(payload_names) + "\n", encoding="utf-8")
target = output_directory.resolve() / f"nodedc-{patch_id}.tgz"
@@ -142,10 +227,15 @@ def build(patch_id: str, output_directory: Path, *, revision: str | None = None)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("patch_id")
parser.add_argument("--output-directory", type=Path, default=REPOSITORY_ROOT / ".runtime/worker-artifacts")
parser.add_argument(
"--output-directory",
type=Path,
default=REPOSITORY_ROOT / ".runtime/worker-artifacts",
)
parser.add_argument("--revision")
arguments = parser.parse_args()
try:
result = build(arguments.patch_id, arguments.output_directory)
result = build(arguments.patch_id, arguments.output_directory, revision=arguments.revision)
except (ArtifactBuildError, OSError, subprocess.SubprocessError) as exc:
parser.error(str(exc))
print(json.dumps(result, indent=2, sort_keys=True))