fix(worker): preserve transport patch in Docker snapshot layers

This commit is contained in:
DCCONSTRUCTIONS
2026-09-02 23:50:22 +03:00
parent 0041e9fadb
commit 43f1cdc862
2 changed files with 65 additions and 2 deletions
@@ -234,9 +234,15 @@ def build_transport(engine: Engine, name: str, parent: str, script_sha: str) ->
"name": name + "-claim-v3-layer", "name": name + "-claim-v3-layer",
} }
), ),
{"Image": "sha256:" + parent, "HostConfig": {"NetworkMode": "none"}}, {"Image": "sha256:" + parent, "Entrypoint": ["/bin/true"], "Cmd": [],
"HostConfig": {"NetworkMode": "none", "CapDrop": ["ALL"], "PidsLimit": 32,
"SecurityOpt": ["no-new-privileges"]}},
)["Id"] )["Id"]
try: try:
# Initialize the layer with a network-free CPU no-op, never the agent CMD.
engine.request("POST", f"/containers/{temporary}/start")
if engine.request("POST", f"/containers/{temporary}/wait")["StatusCode"] != 0:
raise RuntimeError("offline image-layer initialization failed")
archive = engine.request( archive = engine.request(
"GET", f"/containers/{temporary}/archive?" + urlencode({"path": SOURCE}), raw=True "GET", f"/containers/{temporary}/archive?" + urlencode({"path": SOURCE}), raw=True
) )
@@ -249,6 +255,9 @@ def build_transport(engine: Engine, name: str, parent: str, script_sha: str) ->
replacement = patch_source(original) replacement = patch_source(original)
member.name = Path(SOURCE).name member.name = Path(SOURCE).name
member.size = len(replacement) member.size = len(replacement)
# Same-length v2 -> v3 bytes must not retain the original file timestamp:
# metadata-based snapshotters can otherwise omit the changed content.
member.mtime = max(int(time.time()), int(member.mtime) + 1)
member.pax_headers = {} member.pax_headers = {}
data = io.BytesIO() data = io.BytesIO()
with tarfile.open(fileobj=data, mode="w") as output: with tarfile.open(fileobj=data, mode="w") as output:
@@ -266,7 +275,9 @@ def build_transport(engine: Engine, name: str, parent: str, script_sha: str) ->
changes = engine.request("GET", f"/containers/{temporary}/changes") changes = engine.request("GET", f"/containers/{temporary}/changes")
allowed = {str(path) for path in Path(SOURCE).parents} | {SOURCE} allowed = {str(path) for path in Path(SOURCE).parents} | {SOURCE}
if not changes or any(item["Kind"] != 0 or item["Path"] not in allowed for item in changes): if not changes or any(item["Kind"] != 0 or item["Path"] not in allowed for item in changes):
raise ValueError("transport layer changed files outside the reviewed source") raise ValueError(
f"transport layer changed files outside the reviewed source: {changes!r}"
)
original_image = engine.request("GET", f"/images/sha256:{parent}/json") original_image = engine.request("GET", f"/images/sha256:{parent}/json")
config = copy.deepcopy(original_image["Config"]) config = copy.deepcopy(original_image["Config"])
config.setdefault("Labels", {}).update( config.setdefault("Labels", {}).update(
@@ -2,7 +2,9 @@ from __future__ import annotations
import copy import copy
import importlib.util import importlib.util
import io
import json import json
import tarfile
from pathlib import Path from pathlib import Path
import pytest import pytest
@@ -127,3 +129,53 @@ def test_changed_plan_cannot_write_evidence_or_touch_containers(
with pytest.raises(ValueError, match="plan changed"): with pytest.raises(ValueError, match="plan changed"):
migration.apply(object(), "0" * 64, evidence) migration.apply(object(), "0" * 64, evidence)
assert not evidence.exists() assert not evidence.exists()
def test_image_layer_refreshes_mtime_and_retains_exact_parent(monkeypatch) -> None:
source = b'PROTOCOL = "missioncore.observatory-worker-claim-request/v2"\n'
monkeypatch.setattr(migration, "BEFORE_SHA", migration.sha(source))
archive = io.BytesIO()
with tarfile.open(fileobj=archive, mode="w") as output:
member = tarfile.TarInfo("worker_http_transport.py")
member.size = len(source)
member.mtime = 123
output.addfile(member, io.BytesIO(source))
class FakeEngine:
removed = False
def request(self, method, path, body=None, *, raw=False):
if path.startswith("/containers/create"):
assert body["Entrypoint"] == ["/bin/true"]
assert body["HostConfig"]["NetworkMode"] == "none"
return {"Id": "temporary"}
if path.endswith("/start"):
return None
if path.endswith("/wait"):
return {"StatusCode": 0}
if "/archive?" in path:
if method == "GET":
return archive.getvalue()
with tarfile.open(fileobj=io.BytesIO(body)) as uploaded:
patched = uploaded.getmembers()[0]
assert patched.mtime > 123
assert uploaded.extractfile(patched).read() == source.replace(
migration.OLD, migration.NEW
)
return None
if path.endswith("/changes"):
return [{"Kind": 0, "Path": migration.SOURCE}]
if path == "/images/sha256:parent/json":
return {"Config": {"Labels": {}}, "RootFS": {"Layers": ["base"]}}
if path.startswith("/commit?"):
assert body["Labels"][migration.PARENT_LABEL] == "parent"
return {"Id": "sha256:new"}
if path == "/images/sha256:new/json":
return {"RootFS": {"Layers": ["base", "transport-only"]}}
assert method == "DELETE" and path == "/containers/temporary"
self.removed = True
engine = FakeEngine()
result = migration.build_transport(engine, "ndc-test-agent", "parent", "script")
assert result["image"] == "sha256:new"
assert engine.removed