Skip to content
Merged
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
103 changes: 96 additions & 7 deletions test/e2e/live/gateway-guard-legacy-keepalive-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
import path from "node:path";
import { pathToFileURL } from "node:url";

import * as dockerRunNamespace from "../../../src/lib/adapters/docker/run.ts";
import type { DockerGpuPatchDeps } from "../../../src/lib/onboard/docker-gpu-patch-types.ts";
import * as startupCommandPatchNamespace from "../../../src/lib/onboard/docker-startup-command-patch.ts";
import { redactString } from "../fixtures/redaction.ts";

const LEGACY_KEEPALIVE_COMMAND = ["sleep", "infinity"] as const;
const MANAGED_IMAGE_ENTRYPOINT = ["/usr/local/bin/nemoclaw-start"] as const;
const MANAGED_IMAGE_COMMAND = ["/bin/bash"] as const;
const LEGACY_OPENSHELL_ENTRYPOINT = ["/opt/openshell/bin/openshell-sandbox"] as const;
const DEFAULT_RECREATE_TIMEOUT_SECS = 180;
const DOCKER_CONTAINER_ID_PATTERN = /^[0-9a-f]{64}$/i;
const startupCommandPatch = (
Expand All @@ -16,8 +21,13 @@ const startupCommandPatch = (
: startupCommandPatchNamespace
) as typeof import("../../../src/lib/onboard/docker-startup-command-patch.ts");
const { recreateOpenShellDockerSandboxWithStartupCommand } = startupCommandPatch;
const dockerRun = (
"default" in dockerRunNamespace ? dockerRunNamespace.default : dockerRunNamespace
) as typeof import("../../../src/lib/adapters/docker/run.ts");
const { dockerCapture: defaultDockerCapture } = dockerRun;

type StartupCommandRecreate = typeof recreateOpenShellDockerSandboxWithStartupCommand;
type DockerCapture = NonNullable<DockerGpuPatchDeps["dockerCapture"]>;

export type LegacyKeepaliveFixtureOptions = {
sandboxName: string;
Expand All @@ -27,32 +37,111 @@ export type LegacyKeepaliveFixtureOptions = {

export type LegacyKeepaliveFixtureDeps = {
recreate: StartupCommandRecreate;
dockerCapture: DockerCapture;
};

const defaultDeps: LegacyKeepaliveFixtureDeps = {
recreate: recreateOpenShellDockerSandboxWithStartupCommand,
dockerCapture: defaultDockerCapture,
};

function requireFixtureInput(condition: boolean, message: string): asserts condition {
if (!condition) throw new Error(message);
}

function hasExactTokens(value: unknown, expected: readonly string[]): boolean {
return (
Array.isArray(value) &&
value.length === expected.length &&
value.every((token, index) => token === expected[index])
);
}

export function rewriteManagedInspectForLegacyKeepalive(
output: string,
expectedContainerId: string,
): string {
let parsed: unknown;
try {
parsed = JSON.parse(output);
} catch {
throw new Error("legacy keepalive fixture could not parse Docker inspect output");
}
requireFixtureInput(
Array.isArray(parsed) && parsed.length === 1,
"legacy keepalive fixture requires one Docker inspect record",
);
const inspect = parsed[0];
requireFixtureInput(
typeof inspect === "object" && inspect !== null,
"legacy keepalive fixture requires a Docker inspect object",
);
const record = inspect as Record<string, unknown>;
requireFixtureInput(
record.Id === expectedContainerId,
"legacy keepalive fixture Docker inspect identity changed",
);
const config = record.Config;
requireFixtureInput(
typeof config === "object" && config !== null,
"legacy keepalive fixture requires Docker configuration",
);
const configRecord = config as Record<string, unknown>;
requireFixtureInput(
hasExactTokens(configRecord.Entrypoint, MANAGED_IMAGE_ENTRYPOINT) &&
hasExactTokens(configRecord.Cmd, MANAGED_IMAGE_COMMAND),
"legacy keepalive fixture requires the reviewed managed-image process contract",
);

// The replacement container runs the exact pre-0.0.99 OpenShell supervisor
// contract. The production recreation helper still rejects other shapes.
configRecord.Entrypoint = [...LEGACY_OPENSHELL_ENTRYPOINT];
configRecord.Cmd = [];
return JSON.stringify(parsed);
}

function legacyKeepaliveDockerCapture(
expectedContainerId: string,
capture: DockerCapture,
): DockerCapture {
return (args, options) => {
const output = capture(args, options);
if (
args.length === 4 &&
args[0] === "inspect" &&
args[1] === "--type" &&
args[2] === "container" &&
args[3] === expectedContainerId
) {
return rewriteManagedInspectForLegacyKeepalive(output, expectedContainerId);
}
return output;
};
}

export function createLegacyKeepaliveFixture(
options: LegacyKeepaliveFixtureOptions,
deps: LegacyKeepaliveFixtureDeps = defaultDeps,
deps: Partial<LegacyKeepaliveFixtureDeps> = defaultDeps,
): ReturnType<StartupCommandRecreate> {
requireFixtureInput(options.sandboxName.trim() !== "", "sandbox name is required");
requireFixtureInput(
DOCKER_CONTAINER_ID_PATTERN.test(options.expectedContainerId),
"expected container ID must be a full Docker container ID",
);

const result = deps.recreate({
sandboxName: options.sandboxName,
expectedOldContainerId: options.expectedContainerId,
openshellSandboxCommand: LEGACY_KEEPALIVE_COMMAND,
timeoutSecs: options.timeoutSecs ?? DEFAULT_RECREATE_TIMEOUT_SECS,
});
const recreate = deps.recreate ?? defaultDeps.recreate;
const dockerCapture = deps.dockerCapture ?? defaultDeps.dockerCapture;
const result = recreate(
{
sandboxName: options.sandboxName,
expectedOldContainerId: options.expectedContainerId,
openshellSandboxCommand: LEGACY_KEEPALIVE_COMMAND,
timeoutSecs: options.timeoutSecs ?? DEFAULT_RECREATE_TIMEOUT_SECS,
},
{
dockerCapture: legacyKeepaliveDockerCapture(options.expectedContainerId, dockerCapture),
},
);

requireFixtureInput(
result.oldContainerId === options.expectedContainerId,
Expand Down
103 changes: 96 additions & 7 deletions test/e2e/support/gateway-guard-legacy-keepalive-fixture.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ import { fileURLToPath } from "node:url";

import { describe, expect, it, vi } from "vitest";

import {
buildDockerGpuCloneRunArgs,
buildDockerGpuMode,
} from "../../../src/lib/onboard/docker-gpu-patch.ts";
import {
createLegacyKeepaliveFixture,
type LegacyKeepaliveFixtureDeps,
rewriteManagedInspectForLegacyKeepalive,
} from "../live/gateway-guard-legacy-keepalive-fixture.ts";

const OLD_CONTAINER_ID = "a".repeat(64);
Expand All @@ -34,26 +39,110 @@ function successfulResult() {
};
}

function managedImageInspect(
entrypoint: string[] = ["/usr/local/bin/nemoclaw-start"],
containerId = OLD_CONTAINER_ID,
command: string[] = ["/bin/bash"],
): string {
return JSON.stringify([
{
Id: containerId,
Image: `sha256:${"c".repeat(64)}`,
Name: "/openshell-e2e-2701",
Config: {
Image: "nemoclaw-managed:test",
Entrypoint: entrypoint,
Cmd: command,
Env: ["OPENSHELL_SANDBOX_COMMAND=env /usr/local/bin/nemoclaw-start"],
},
HostConfig: {},
},
]);
}

describe("gateway guard legacy keepalive fixture", () => {
it("recreates only the pinned sandbox container with the legacy startup command", () => {
const recreate = vi.fn(() => successfulResult());
it("recreates only the pinned sandbox container with the reviewed legacy supervisor contract (#9364)", () => {
const dockerCapture = vi.fn(() => managedImageInspect());
const recreate = vi.fn((_, deps: Parameters<LegacyKeepaliveFixtureDeps["recreate"]>[1]) => {
const rewritten = JSON.parse(
deps?.dockerCapture?.(["inspect", "--type", "container", OLD_CONTAINER_ID], {
ignoreError: true,
}) ?? "null",
);
expect(rewritten[0].Config).toMatchObject({
Entrypoint: ["/opt/openshell/bin/openshell-sandbox"],
Cmd: [],
});
return successfulResult();
});

const result = createLegacyKeepaliveFixture(
{
sandboxName: "e2e-2701",
expectedContainerId: OLD_CONTAINER_ID,
},
{ recreate },
{ recreate, dockerCapture },
);

expect(result.newContainerId).toBe(NEW_CONTAINER_ID);
expect(recreate).toHaveBeenCalledOnce();
expect(recreate).toHaveBeenCalledWith({
sandboxName: "e2e-2701",
expectedOldContainerId: OLD_CONTAINER_ID,
expect(recreate).toHaveBeenCalledWith(
{
sandboxName: "e2e-2701",
expectedOldContainerId: OLD_CONTAINER_ID,
openshellSandboxCommand: ["sleep", "infinity"],
timeoutSecs: 180,
},
{ dockerCapture: expect.any(Function) },
);
});

it("rejects an unreviewed managed-image entrypoint before legacy recreation (#9364)", () => {
expect(() =>
rewriteManagedInspectForLegacyKeepalive(
managedImageInspect(["/unreviewed/supervisor"]),
OLD_CONTAINER_ID,
),
).toThrow("requires the reviewed managed-image process contract");
});

it("rejects an unreviewed managed-image command before legacy recreation (#9364)", () => {
expect(() =>
rewriteManagedInspectForLegacyKeepalive(
managedImageInspect(["/usr/local/bin/nemoclaw-start"], OLD_CONTAINER_ID, ["/bin/sh"]),
OLD_CONTAINER_ID,
),
).toThrow("requires the reviewed managed-image process contract");
});

it("rejects Docker inspect output for a different container before legacy recreation (#9364)", () => {
expect(() =>
rewriteManagedInspectForLegacyKeepalive(
managedImageInspect(["/usr/local/bin/nemoclaw-start"], NEW_CONTAINER_ID),
OLD_CONTAINER_ID,
),
).toThrow("Docker inspect identity changed");
});

it("produces a clone contract accepted by production startup-command validation (#9364)", () => {
const rewritten = JSON.parse(
rewriteManagedInspectForLegacyKeepalive(managedImageInspect(), OLD_CONTAINER_ID),
);
const immutableImage = `sha256:${"c".repeat(64)}`;
const args = buildDockerGpuCloneRunArgs(rewritten[0], buildDockerGpuMode("startup-command"), {
image: immutableImage,
openshellSandboxCommand: ["sleep", "infinity"],
timeoutSecs: 180,
});

expect(args).toEqual(
expect.arrayContaining([
"--entrypoint",
"/opt/openshell/bin/openshell-sandbox",
"--env",
"OPENSHELL_SANDBOX_COMMAND=sleep infinity",
]),
);
expect(args.slice(args.indexOf(immutableImage))).toEqual([immutableImage]);
});

it.each([
Expand Down