Download src/agents/sandbox/docker.create.test-helpers.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/sandbox/docker.create.test-helpers.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/agents/sandbox/docker.create.test-helpers.ts
-
curl -L -o docker.create.test-helpers.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/sandbox/docker.create.test-helpers.ts
11.1 kB
| import fs from "node:fs"; | |
| import { afterEach, beforeAll, beforeEach, vi } from "vitest"; | |
| import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js"; | |
| import { computeSandboxConfigHash } from "./config-hash.js"; | |
| import { DOCKER_SANDBOX_ENGINE } from "./container-engine.js"; | |
| import type { SandboxConfig } from "./types.js"; | |
| type SpawnCall = { | |
| command: string; | |
| args: string[]; | |
| globalArgs: string[]; | |
| envFileContents?: string; | |
| }; | |
| const namespaceMocks = vi.hoisted(() => ({ | |
| resolveDockerSourceNamespace: vi | |
| .fn<typeof import("./docker-mount-source.js").resolveDockerSourceNamespace>() | |
| .mockResolvedValue(undefined), | |
| })); | |
| const spawnState = vi.hoisted(() => ({ | |
| calls: [] as SpawnCall[], | |
| containerExists: true, | |
| inspectRunning: true, | |
| inspectError: "", | |
| labelHash: "", | |
| mounts: "[]", | |
| tmpfs: null as Record<string, string> | null, | |
| podmanInfo: "true\tfalse\t\t5.0.0\n", | |
| podmanConnections: "[]\n", | |
| podmanMachines: "[]\n", | |
| })); | |
| const registryMocks = vi.hoisted(() => ({ | |
| readRegistryEntry: vi.fn(), | |
| removeRegistryEntry: vi.fn(), | |
| updateRegistry: vi.fn(), | |
| })); | |
| const runtimeMocks = vi.hoisted(() => ({ | |
| log: vi.fn(), | |
| })); | |
| function usePodmanMachine() { | |
| spawnState.podmanInfo = "true\ttrue\t\t5.0.0\n"; | |
| spawnState.podmanConnections = JSON.stringify([ | |
| { | |
| Name: "podman-machine-default", | |
| URI: "ssh://core@127.0.0.1:60000/run/user/501/podman/podman.sock", | |
| Identity: "/tmp/podman-machine-default", | |
| Default: true, | |
| }, | |
| ]); | |
| spawnState.podmanMachines = JSON.stringify([ | |
| { | |
| Name: "podman-machine-default", | |
| Running: true, | |
| IdentityPath: "/tmp/podman-machine-default", | |
| Port: 60000, | |
| RemoteUsername: "core", | |
| }, | |
| ]); | |
| } | |
| function createRegistryMock() { | |
| return { | |
| readRegistryEntry: registryMocks.readRegistryEntry, | |
| removeRegistryEntry: registryMocks.removeRegistryEntry, | |
| updateRegistry: registryMocks.updateRegistry, | |
| }; | |
| } | |
| vi.mock("./registry.js", createRegistryMock); | |
| function createRuntimeMock() { | |
| return { | |
| defaultRuntime: runtimeMocks, | |
| }; | |
| } | |
| vi.mock("../../runtime.js", createRuntimeMock); | |
| async function createNamespaceMock() { | |
| const actual = await vi.importActual<typeof import("./docker-mount-source.js")>( | |
| "./docker-mount-source.js", | |
| ); | |
| return { | |
| parseInspectedSandboxMounts: actual.parseInspectedSandboxMounts, | |
| translateSandboxMountSources: actual.translateSandboxMountSources, | |
| resolveDockerSourceNamespace: namespaceMocks.resolveDockerSourceNamespace, | |
| }; | |
| } | |
| vi.mock("./docker-mount-source.js", createNamespaceMock); | |
| async function spawnDockerProcess(commandAndArgs: string[]) { | |
| const [command = "", ...rawArgs] = commandAndArgs; | |
| const globalArgs: string[] = []; | |
| let args = rawArgs; | |
| if (command === "podman") { | |
| while (args[0] === "--url" || args[0] === "--identity") { | |
| globalArgs.push(...args.slice(0, 2)); | |
| args = args.slice(2); | |
| } | |
| } | |
| // The tests assert docker CLI arguments without requiring Docker; this mock | |
| // implements only the inspect/create/start/rm calls used by ensureSandboxContainer. | |
| const envFileIndex = args.indexOf("--env-file"); | |
| const envFile = envFileIndex === -1 ? undefined : args[envFileIndex + 1]; | |
| const call: SpawnCall = { command, args, globalArgs }; | |
| if (args[0] === "create" && envFile) { | |
| call.envFileContents = fs.readFileSync(envFile, "utf8"); | |
| } | |
| spawnState.calls.push(call); | |
| let code = 0; | |
| let stdout = ""; | |
| let stderr = ""; | |
| if (command !== "docker" && command !== "podman") { | |
| code = 1; | |
| stderr = `unexpected command: ${command}`; | |
| } else if (args[0] === "inspect" && args[1] === "-f" && args[2] === "{{.State.Running}}") { | |
| if (spawnState.inspectError) { | |
| code = 125; | |
| stderr = spawnState.inspectError; | |
| } else if (!spawnState.containerExists) { | |
| code = 1; | |
| stderr = "No such object"; | |
| } else { | |
| stdout = spawnState.inspectRunning ? "true\n" : "false\n"; | |
| } | |
| } else if ( | |
| args[0] === "inspect" && | |
| args[1] === "-f" && | |
| args[2]?.includes('index .Config.Labels "openclaw.configHash"') | |
| ) { | |
| if (!spawnState.containerExists) { | |
| code = 1; | |
| stderr = "No such object"; | |
| } else { | |
| stdout = `${spawnState.labelHash}\n`; | |
| } | |
| } else if ( | |
| args[0] === "inspect" && | |
| args[2] === '{"Mounts":{{json .Mounts}},"Tmpfs":{{json .HostConfig.Tmpfs}}}' | |
| ) { | |
| stdout = JSON.stringify({ Mounts: JSON.parse(spawnState.mounts), Tmpfs: spawnState.tmpfs }); | |
| } else if (command === "podman" && args[0] === "info") { | |
| stdout = spawnState.podmanInfo; | |
| } else if (command === "podman" && args[0] === "system") { | |
| stdout = spawnState.podmanConnections; | |
| } else if (command === "podman" && args[0] === "machine") { | |
| stdout = spawnState.podmanMachines; | |
| } else if (args[0] === "rm" && args[1] === "-f") { | |
| spawnState.containerExists = false; | |
| spawnState.inspectRunning = false; | |
| } else if (args[0] === "image" && args[1] === "inspect") { | |
| code = 0; | |
| } else if (args[0] === "create") { | |
| if (spawnState.containerExists) { | |
| code = 1; | |
| stderr = "container name is already in use"; | |
| } else { | |
| spawnState.containerExists = true; | |
| spawnState.inspectRunning = false; | |
| spawnState.labelHash = | |
| args | |
| .find((arg) => arg.startsWith("openclaw.configHash=")) | |
| ?.slice("openclaw.configHash=".length) ?? ""; | |
| } | |
| } else if (args[0] === "start") { | |
| spawnState.inspectRunning = true; | |
| } else if (args[0] === "exec") { | |
| code = 0; | |
| } else { | |
| code = 1; | |
| stderr = `unexpected docker args: ${args.join(" ")}`; | |
| } | |
| return { | |
| failed: code !== 0, | |
| isCanceled: false, | |
| exitCode: code, | |
| stdout: Buffer.from(stdout), | |
| stderr: Buffer.from(stderr), | |
| }; | |
| } | |
| async function createProcessMock() { | |
| return { | |
| ...(await vi.importActual<typeof import("../../process/exec.js")>("../../process/exec.js")), | |
| spawnCommand: spawnDockerProcess, | |
| }; | |
| } | |
| vi.mock("../../process/exec.js", createProcessMock); | |
| function createSandboxConfig( | |
| dns: string[], | |
| binds?: string[], | |
| workspaceAccess: "rw" | "ro" | "none" = "rw", | |
| env: Record<string, string> = { LANG: "C.UTF-8" }, | |
| ): SandboxConfig { | |
| return { | |
| mode: "all", | |
| backend: "docker", | |
| scope: "shared", | |
| workspaceAccess, | |
| workspaceRoot: "~/.openclaw/sandboxes", | |
| dockerTmpfsSource: "default", | |
| docker: { | |
| image: "openclaw-sandbox:test", | |
| containerPrefix: "oc-test-", | |
| workdir: "/workspace", | |
| readOnlyRoot: true, | |
| tmpfs: ["/tmp", "/var/tmp", "/run"], | |
| network: "none", | |
| capDrop: ["ALL"], | |
| env, | |
| dns, | |
| extraHosts: ["host.docker.internal:host-gateway"], | |
| binds: binds ?? ["/tmp/workspace:/workspace:rw"], | |
| dangerouslyAllowReservedContainerTargets: true, | |
| }, | |
| ssh: { | |
| command: "ssh", | |
| workspaceRoot: "/tmp/openclaw-sandboxes", | |
| strictHostKeyChecking: true, | |
| updateHostKeys: true, | |
| }, | |
| browser: { | |
| enabled: false, | |
| image: "openclaw-browser:test", | |
| containerPrefix: "oc-browser-", | |
| network: "openclaw-sandbox-browser", | |
| cdpPort: 9222, | |
| vncPort: 5900, | |
| noVncPort: 6080, | |
| headless: true, | |
| noVncEnabled: false, | |
| allowHostControl: false, | |
| autoStart: false, | |
| autoStartTimeoutMs: 5000, | |
| }, | |
| tools: { allow: [], deny: [] }, | |
| prune: { idleHours: 24, maxAgeDays: 7 }, | |
| }; | |
| } | |
| export function createSandboxContainerTestHarness() { | |
| // Keep loaded modules and temporary directories per suite. Shared mock modules | |
| // are reset by the hooks installed for that suite. | |
| let ensureSandboxContainer: typeof import("./docker.js").ensureSandboxContainer; | |
| let resolveDockerEnvPolicyEpoch: typeof import("./docker.js").resolveDockerEnvPolicyEpoch; | |
| let PODMAN_SANDBOX_ENGINE: typeof import("./docker.js").PODMAN_SANDBOX_ENGINE; | |
| let prepareSandboxMountPlan: typeof import("./mount-plan.js").prepareSandboxMountPlan; | |
| const tempDirs = useAutoCleanupTempDirTracker(afterEach); | |
| beforeAll(async () => { | |
| vi.resetModules(); | |
| vi.doMock("./registry.js", createRegistryMock); | |
| vi.doMock("../../runtime.js", createRuntimeMock); | |
| vi.doMock("./docker-mount-source.js", createNamespaceMock); | |
| vi.doMock("../../process/exec.js", createProcessMock); | |
| ({ prepareSandboxMountPlan } = await import("./mount-plan.js")); | |
| ({ ensureSandboxContainer, resolveDockerEnvPolicyEpoch, PODMAN_SANDBOX_ENGINE } = | |
| await import("./docker.js")); | |
| }); | |
| async function computeTestSandboxHash(input: Parameters<typeof computeSandboxConfigHash>[0]) { | |
| const plan = await prepareSandboxMountPlan({ | |
| engine: DOCKER_SANDBOX_ENGINE, | |
| workspaceDir: input.workspaceDir, | |
| agentWorkspaceDir: input.agentWorkspaceDir, | |
| workdir: input.docker.workdir, | |
| workspaceAccess: input.workspaceAccess, | |
| binds: input.docker.binds, | |
| }); | |
| return computeSandboxConfigHash({ ...input, managedMounts: plan.binds }); | |
| } | |
| async function ensureSandboxCreateCallForTest(params: { | |
| cfg: SandboxConfig; | |
| workspaceDir?: string; | |
| scopeKey?: string; | |
| engine?: import("./docker.js").SandboxContainerEngine; | |
| }): Promise<SpawnCall> { | |
| const workspaceDir = params.workspaceDir ?? "/tmp/workspace"; | |
| await ensureSandboxContainer({ | |
| scopeKey: params.scopeKey ?? "shared", | |
| workspaceDir, | |
| agentWorkspaceDir: workspaceDir, | |
| cfg: params.cfg, | |
| ...(params.engine ? { engine: params.engine } : {}), | |
| }); | |
| const createCall = spawnState.calls.find( | |
| (call) => call.command === (params.engine?.command ?? "docker") && call.args[0] === "create", | |
| ); | |
| if (!createCall) { | |
| throw new Error(`expected ${params.engine?.command ?? "docker"} create call`); | |
| } | |
| return createCall; | |
| } | |
| beforeEach(() => { | |
| spawnState.calls.length = 0; | |
| spawnState.containerExists = true; | |
| spawnState.inspectRunning = true; | |
| spawnState.inspectError = ""; | |
| spawnState.labelHash = ""; | |
| spawnState.mounts = "[]"; | |
| spawnState.tmpfs = null; | |
| namespaceMocks.resolveDockerSourceNamespace.mockResolvedValue(undefined); | |
| spawnState.podmanInfo = "true\tfalse\t\t5.0.0\n"; | |
| spawnState.podmanConnections = "[]\n"; | |
| spawnState.podmanMachines = "[]\n"; | |
| registryMocks.readRegistryEntry.mockClear(); | |
| registryMocks.removeRegistryEntry.mockClear(); | |
| registryMocks.removeRegistryEntry.mockResolvedValue(undefined); | |
| registryMocks.updateRegistry.mockClear(); | |
| registryMocks.updateRegistry.mockResolvedValue(undefined); | |
| runtimeMocks.log.mockClear(); | |
| }); | |
| return { | |
| resolveDockerSourceNamespace: namespaceMocks.resolveDockerSourceNamespace, | |
| spawnState, | |
| registryMocks, | |
| runtimeMocks, | |
| tempDirs, | |
| usePodmanMachine, | |
| createSandboxConfig, | |
| computeTestSandboxHash, | |
| ensureSandboxCreateCallForTest, | |
| get ensureSandboxContainer() { | |
| return ensureSandboxContainer; | |
| }, | |
| get resolveDockerEnvPolicyEpoch() { | |
| return resolveDockerEnvPolicyEpoch; | |
| }, | |
| get PODMAN_SANDBOX_ENGINE() { | |
| return PODMAN_SANDBOX_ENGINE; | |
| }, | |
| }; | |
| } | |