Files

103 lines
3.7 KiB
JavaScript

import Docker from 'dockerode';
import { resolve } from 'path';
export class DockerManager {
docker;
constructor() {
this.docker = new Docker();
}
async pullImage(image) {
const stream = await this.docker.pull(image);
return new Promise((resolve, reject) => {
this.docker.modem.followProgress(stream, (err) => {
if (err)
reject(err);
else
resolve();
});
});
}
async createContainer(image, options) {
const absWorkspace = resolve(options.workspace);
const container = await this.docker.createContainer({
Image: image,
Cmd: ['sleep', '3600'],
WorkingDir: options.workingDir || '/github/workspace',
HostConfig: {
Binds: [`${absWorkspace}:/github/workspace`],
AutoRemove: true,
},
Env: Object.entries(options.env).map(([k, v]) => `${k}=${v}`),
Entrypoint: ['sh', '-c', 'while true; do sleep 3600; done'],
});
await container.start();
return container.id;
}
async execInContainer(containerId, step, modifiedCommand) {
const container = this.docker.getContainer(containerId);
const cmd = modifiedCommand ?? step.run ?? 'true';
const shell = step.shell ?? 'bash';
const workingDir = step.workingDirectory ?? '/github/workspace';
// Handle 'uses' steps by converting to a comment
if (!modifiedCommand && step.uses) {
const usesCmd = `echo "[CISandbox] Action step: ${step.uses}" && echo "Note: 'uses' actions are not yet supported in CISandbox. Convert to 'run' or modify the step to debug."`;
return this.runShell(container, usesCmd, workingDir, shell, step.env);
}
return this.runShell(container, cmd, workingDir, shell, step.env);
}
async runShell(container, cmd, workingDir, shell, extraEnv) {
const exec = await container.exec({
Cmd: [shell, '-c', cmd],
WorkingDir: workingDir,
AttachStdout: true,
AttachStderr: true,
Env: extraEnv ? Object.entries(extraEnv).map(([k, v]) => `${k}=${v}`) : undefined,
});
const stream = await exec.start({ hijack: true, stdin: false });
let stdout = '';
let stderr = '';
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => {
// Docker multiplexed stream: first byte is stream type
const type = chunk[0];
const payload = chunk.slice(8).toString('utf-8');
if (type === 1)
stdout += payload;
else
stderr += payload;
});
stream.on('end', async () => {
try {
const inspect = await exec.inspect();
resolve({
exitCode: inspect.ExitCode ?? 0,
stdout: stdout.trimEnd(),
stderr: stderr.trimEnd(),
});
}
catch (err) {
reject(err);
}
});
stream.on('error', reject);
});
}
async stopContainer(containerId) {
try {
const container = this.docker.getContainer(containerId);
await container.stop({ t: 2 });
}
catch {
// Already stopped or doesn't exist
}
}
async imageExists(image) {
try {
await this.docker.getImage(image).inspect();
return true;
}
catch {
return false;
}
}
}
//# sourceMappingURL=docker.js.map