Skip to content
Open
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
23 changes: 15 additions & 8 deletions src/sandbox/local-sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,21 +112,28 @@ export function createLocalSandbox(workspace: WorkspaceStore, opts: LocalSandbox
preflightDone = undefined;
throw new Error("SANDBOX_BACKEND=local requires a running Docker daemon (is Docker Desktop running?)");
}
const img = await dexec([
// Existence and label are separate probes: images built by buildx with
// attestations (the Docker 29 default, and what `qm sandbox publish`
// emits) inspect as an OCI index whose Config has no Labels key, which
// fails the fingerprint template even though the image is present and
// usable. A template failure must not be reported as a missing image.
const img = await dexec(["image", "inspect", "-f", "{{.Id}}", image]);
if (img.code !== 0) {
preflightDone = undefined;
throw new Error(`local sandbox image ${image} not found — ${BUILD_HINT}`);
}
const labeled = await dexec([
"image",
"inspect",
"-f",
`{{.Id}} {{if .Config.Labels}}{{index .Config.Labels "${FINGERPRINT_LABEL}"}}{{end}}`,
`{{if .Config.Labels}}{{index .Config.Labels "${FINGERPRINT_LABEL}"}}{{end}}`,
image,
]);
if (img.code !== 0) {
preflightDone = undefined;
throw new Error(`local sandbox image ${image} not found — ${BUILD_HINT}`);
}
const [imageId = "", labeled = ""] = img.stdout.trim().split(/\s+/);
const fingerprint = labeled.code === 0 ? labeled.stdout.trim() : "";
const [imageId = ""] = img.stdout.trim().split(/\s+/);
if (!staleWarned) {
const want = await computeSandboxImageFingerprint(opts.repoRoot ?? process.cwd());
if (want && labeled && labeled !== want) {
if (want && fingerprint && fingerprint !== want) {
staleWarned = true;
console.warn(`[local-sandbox] sandbox image ${image} is stale — ${BUILD_HINT}`);
}
Expand Down
22 changes: 22 additions & 0 deletions test/local-sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,28 @@ test("a missing sandbox image fails provision with the build hint", async () =>
await assert.rejects(sb.provision(rw(scopeId("personal", "U0"))), /not found — run `npm run sandbox:local:build`/);
});

test("attestation (OCI index) image whose label template fails is usable, not reported missing (#577)", async () => {
const fake = installFakeDocker(daemonPort);
fake.imageLabelsTemplateError = true;
const sb = makeSandbox(fake);
const scope = scopeId("personal", "U0");
// The regression's signature was a preflight rejection claiming the image is
// missing. The preflight must accept the image; environments where the later
// daemon-exec prep fails for unrelated reasons (local Windows runs) still
// prove the image was found — only the not-found rejection is a failure.
try {
const h = await sb.provision(rw(scope));
assert.equal(h.id, localContainerName(scope));
assert.equal(fake.runCount, 1);
} catch (error) {
assert.doesNotMatch(
String(error),
/not found — run `npm run sandbox:local:build`/,
"image was misreported as missing",
);
}
});

test("cold provision creates volume + container, run() execs over the daemon, bytes round-trip", async () => {
const fake = installFakeDocker(daemonPort);
const sb = makeSandbox(fake);
Expand Down
16 changes: 15 additions & 1 deletion test/support/fake-docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export interface FakeDocker {
imageMissing: boolean;
imageId: string;
imageFingerprint: string;
/** Mimics a buildx attestation image: `{{.Id}}` inspects fine, but the
* fingerprint template fails because the OCI index's Config has no Labels key. */
imageLabelsTemplateError: boolean;
}

export function installFakeDocker(daemonPort: number): FakeDocker {
Expand All @@ -31,6 +34,7 @@ export function installFakeDocker(daemonPort: number): FakeDocker {
runCount: 0,
daemonDown: false,
imageMissing: false,
imageLabelsTemplateError: false,
imageId: "sha256:image-v1",
imageFingerprint: "",
dockerExec: async (args) => exec(args),
Expand Down Expand Up @@ -61,7 +65,17 @@ export function installFakeDocker(daemonPort: number): FakeDocker {
return ok("Docker version fake");
case "image": {
if (self.imageMissing) return fail("Error: No such image");
return ok(`${self.imageId} ${self.imageFingerprint}`);
if (rest[0] === "inspect") {
const format = rest[rest.indexOf("-f") + 1] ?? "";
if (format === "{{.Id}}") return ok(self.imageId);
if (self.imageLabelsTemplateError) {
return fail(
'template parsing error: template: :1:20: executing "" at <.Config.Labels>: map has no entry for key "Labels"',
);
}
return ok(self.imageFingerprint);
}
return fail(`unknown image subcommand ${rest.join(" ")}`);
}
case "inspect": {
const name = rest[rest.length - 1]!;
Expand Down