@enspirit/emb
Version:
A replacement for our Makefile-for-monorepos
64 lines (63 loc) • 2.48 kB
JavaScript
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'
});
}
}