* Execute a component in a Docker container * - Starts container with specified image and command * - Mounts a temp directory for structured output at /shipsec-output * - Components should write results to /shipsec-output/result.json * - Stdout/stderr are used purely for logging/progress * - Au
( runner: DockerRunnerConfig, params: I, context: ExecutionContext, )
| 91 | * - Automatically cleans up container and temp directory on exit |
| 92 | */ |
| 93 | async function runComponentInDocker<I, O>( |
| 94 | runner: DockerRunnerConfig, |
| 95 | params: I, |
| 96 | context: ExecutionContext, |
| 97 | ): Promise<O> { |
| 98 | const { image, command, entrypoint, env = {}, network = 'none', platform, containerName, volumes, timeoutSeconds = 300, detached } = runner; |
| 99 | |
| 100 | context.logger.info(`[Docker] Running ${image} with command: ${formatArgs(command)}`); |
| 101 | context.emitProgress(`Starting Docker container: ${image}`); |
| 102 | |
| 103 | // Create temp directory for output and input |
| 104 | const outputDir = await mkdtemp(join(tmpdir(), 'shipsec-run-')); |
| 105 | const hostOutputPath = join(outputDir, OUTPUT_FILENAME); |
| 106 | const hostInputPath = join(outputDir, 'input.json'); |
| 107 | |
| 108 | try { |
| 109 | // Write inputs to file instead of passing via env or stdin |
| 110 | await writeFile(hostInputPath, JSON.stringify(params)); |
| 111 | |
| 112 | const dockerArgs = [ |
| 113 | 'run', |
| 114 | '--rm', |
| 115 | '-i', |
| 116 | '--network', network, |
| 117 | '--label', `shipsec.runId=${context.runId}`, |
| 118 | '--label', `shipsec.nodeRef=${context.componentRef}`, |
| 119 | // Mount the directory containing both input and output |
| 120 | '-v', `${outputDir}:${CONTAINER_OUTPUT_PATH}`, |
| 121 | ]; |
| 122 | |
| 123 | if (containerName) { |
| 124 | dockerArgs.push('--name', containerName); |
| 125 | } |
| 126 | |
| 127 | if (platform && platform.trim().length > 0) { |
| 128 | dockerArgs.push('--platform', platform); |
| 129 | } |
| 130 | |
| 131 | if (Array.isArray(volumes)) { |
| 132 | for (const vol of volumes) { |
| 133 | if (!vol || !vol.source || !vol.target) continue; |
| 134 | const mode = vol.readOnly ? ':ro' : ''; |
| 135 | dockerArgs.push('-v', `${vol.source}:${vol.target}${mode}`); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | if (runner.ports) { |
| 140 | for (const [hostPort, containerPort] of Object.entries(runner.ports)) { |
| 141 | dockerArgs.push('-p', `${hostPort}:${containerPort}`); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | for (const [key, value] of Object.entries(env)) { |
| 146 | dockerArgs.push('-e', `${key}=${value}`); |
| 147 | } |
| 148 | |
| 149 | // Tell the container where to read input and write output |
| 150 | dockerArgs.push('-e', `SHIPSEC_INPUT_PATH=${CONTAINER_OUTPUT_PATH}/input.json`); |
no test coverage detected