Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions build_system/builder/gate/debproof.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,25 @@ def _start(self, runtime: VmDeviceRuntime) -> None:
interval=self._install.systemd_ready_interval_seconds,
sleep=self._sleep,
)
self._claim_paths()

def _claim_paths(self) -> None:
"""Create and own the writable staging layout before staging writes to it.

The sealed image dockerignores `cache/`, so the only `/src/cache` in
this container is the mountpoint chain Docker materialized as root for
the read-only package mount. On a host whose uid is not the guest's,
staging as the guest then dies at `mkdir cache/target/tests` -- the
exact shape `InstallContainer._claim_paths` already repairs for the
install gate, applied here to the proof container it never covered.
"""
guest = self._install.guest_user.name
owned = self._install.layout.owned_paths(self._install.mount)
self._docker.exec(self._proof.container, ["mkdir", "-p", *owned])
self._docker.exec(self._proof.container, ["chown", "-R", f"{guest}:{guest}", *owned])
parents = self._install.layout.owned_parent_paths(self._install.mount)
self._docker.exec(self._proof.container, ["chown", f"{guest}:{guest}", *parents])

def _prepare_handoff(self, package: str, version: str) -> None:
"""Author the exact local graph before the package's postinst runs."""
layout = self._install.layout
Expand Down Expand Up @@ -262,6 +281,11 @@ def _verify_release(self, expected: str) -> None:
f"{guest.home}/{self._install.capsem_home}",
"--manifest-url",
manifest,
# The handoff hydrated the bytes; the baked URL is what the
# postinst preserves in manifest-metadata for a preverified
# payload. The verifier models exactly this split.
"--metadata-manifest-url",
self.manifest_url,
"--channel",
self.channel,
"--package-version",
Expand Down
23 changes: 19 additions & 4 deletions build_system/builder/gate/releasegraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

from __future__ import annotations

from pathlib import PurePosixPath

from . import config as gate_config
from .docker import Docker
from .errors import GateError
Expand Down Expand Up @@ -101,7 +103,19 @@ def record(manifest: str) -> None:
manifest = author_binary_graph(assets_manifest, build=build, record=record)
self.build_site(dist=out_dir)
self.check_channel(admin, channel=channel, dist=out_dir, manifest=manifest)
self.hand_off(manifest)
self.hand_off(manifest, channel=channel)

def _authored_graph(self, *, channel: str, out_dir: str) -> str:
"""Where `assets channel build --channel <channel>` writes its graph.

`capsem-admin` lays the distribution out as
`<out_dir>/assets/<channel>/<manifest>`. The configured
`graph_manifest` names that location for the install gate's `local`
channel; any other channel -- the package proof's `stable`, say --
replaces the channel segment, not the shape.
"""
name = PurePosixPath(self._config.graph_manifest).name
return f"{out_dir}/assets/{channel}/{name}"

def extract_admin(self, package: str) -> str:
"""Unpack the package without installing it, and return its admin binary.
Expand Down Expand Up @@ -206,7 +220,7 @@ def build_channel(
user=self._config.guest_user.name,
cwd=self._mount,
)
return f"{out_dir}/{self._config.graph_manifest}"
return self._authored_graph(channel=channel, out_dir=out_dir)

def build_site(self, *, dist: str) -> None:
"""Render the release site over the generated distribution."""
Expand Down Expand Up @@ -241,7 +255,7 @@ def check_channel(self, admin: str, *, channel: str, dist: str, manifest: str) -

# -- the handoff -------------------------------------------------------

def hand_off(self, manifest: str) -> None:
def hand_off(self, manifest: str, *, channel: str) -> None:
"""Point the package's postinst at the graph just authored.

Refuses two mistakes the installer cannot report. A target that does
Expand All @@ -251,8 +265,9 @@ def hand_off(self, manifest: str) -> None:
an install that looks fine and carries the wrong manifest.
"""
absolute = manifest if manifest.startswith("/") else f"{self._mount}/{manifest}"
authored = f"/assets/{channel}/{PurePosixPath(self._config.graph_manifest).name}"
if absolute.endswith(f"/{self._config.legacy_projection}") and not absolute.endswith(
f"/{self._config.graph_manifest}"
authored
):
raise GateError(
f"the install handoff must select the authoritative release graph, "
Expand Down
4 changes: 2 additions & 2 deletions build_system/tests/gate/test_gate_debproof.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def test_exact_package_graph_is_checked_and_handed_off_before_dpkg(
handoff = transcript.index("install-manifest-request.sh write")
install = transcript.index("dpkg -i")
assert extract < first_build < record < second_build < check < handoff < install
authoritative = f"{CONFIG.install.layout.channel}/{CONFIG.install.graph_manifest}"
authoritative = f"{CONFIG.install.layout.channel}/assets/nightly/manifest.json"
record_command = runner.matching(r"assets channel record-binary")[0]
assert f"--manifest-path {authoritative}" in record_command
assert f"--source-commit {SOURCE_COMMIT}" in transcript
Expand All @@ -215,7 +215,7 @@ def test_read_only_content_is_staged_before_record_binary_mutates_the_generated_
assert f":{CONFIG.install.proof_assets_mount}:ro" in started
assert f":{CONFIG.install.proof_config_mount}:ro" in started
record = runner.matching(r"assets channel record-binary")[0]
authoritative = f"{CONFIG.install.layout.channel}/{CONFIG.install.graph_manifest}"
authoritative = f"{CONFIG.install.layout.channel}/assets/nightly/manifest.json"
assert f"--manifest-path {authoritative}" in record
assert (
f"--manifest-path {CONFIG.install.proof_assets_mount}/{CONFIG.install.manifest_name}"
Expand Down
4 changes: 2 additions & 2 deletions build_system/tests/gate/test_gate_install_ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ def test_the_legacy_runtime_projection_is_refused(tmp_path: Path) -> None:
graph = ReleaseGraph(Docker(runner), CONFIG, source_commit=SOURCE_COMMIT)

with pytest.raises(GateError, match="not the legacy runtime projection"):
graph.hand_off(f"{LAYOUT.assets}/manifest.json")
graph.hand_off(f"{LAYOUT.assets}/manifest.json", channel=INSTALL.channel)

assert not runner.ran(r"install-manifest-request"), (
"the refusal must happen before anything is written"
Expand All @@ -430,7 +430,7 @@ def test_a_handoff_target_that_does_not_exist_is_refused(tmp_path: Path) -> None
graph = ReleaseGraph(Docker(runner), CONFIG, source_commit=SOURCE_COMMIT)

with pytest.raises(GateError, match="would find no request"):
graph.hand_off(AUTHORITATIVE)
graph.hand_off(AUTHORITATIVE, channel=INSTALL.channel)


def test_clearing_a_handoff_that_was_never_written_does_nothing(tmp_path: Path) -> None:
Expand Down
Loading