UNPKG

@enspirit/emb

Version:

A replacement for our Makefile-for-monorepos

64 lines (63 loc) 2.48 kB
import * as z from 'zod'; import { AbstractOperation } from '../../../operations/index.js'; /** * https://docs.docker.com/reference/api/engine/version/v1.37/#tag/Exec/operation/ContainerExec */ const schema = z.object({ container: z.string().describe('ID or name of the container'), env: z .record(z.string(), z.string()) .optional() .describe('A list of environment variables in the form'), script: z.string().describe('Command to run, as a string'), tty: z.boolean().default(false).optional().describe('Allocate a pseudo-TTY'), workingDir: z .string() .optional() .describe('The working directory for the exec process inside the container'), }); export class ContainerExecOperation extends AbstractOperation { out; constructor(out) { super(schema); this.out = out; } async _run(input) { const container = await this.context.docker.getContainer(input.container); const envVars = Object.entries(input.env || {}).reduce((arr, [key, value]) => { return [...arr, `${key}=${value}`]; }, []); const options = { AttachStderr: true, AttachStdout: true, Cmd: ['bash', '-eu', '-o', 'pipefail', '-c', input.script], Env: envVars, Tty: input.tty, WorkingDir: input.workingDir, }; const exec = await container.exec(options); const stream = await exec.start({}); exec.modem.demuxStream(stream, this.out || process.stdout, this.out || process.stderr); await new Promise((resolve, reject) => { const onError = (err) => reject(err); const onEnd = async () => { exec.inspect((error, res) => { if (error) { return reject(error); } const code = res?.ExitCode ?? 0; if (code !== 0) { const msg = res?.ProcessConfig?.entrypoint ? `container exec failed (exit ${code})` : `command failed (exit ${code})`; return reject(new Error(msg)); } resolve(); }); }; stream.on('error', onError); stream.on('end', onEnd); stream.on('close', onEnd); // some engines emit 'close' not 'end' }); } }