UNPKG

@cto.ai/ops

Version:

💻 CTO.ai - The CLI built for Teams 🚀

156 lines (155 loc) 6.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ContainerService = void 0; const tslib_1 = require("tslib"); const CustomErrors_1 = require("./../errors/CustomErrors"); const debug_1 = tslib_1.__importDefault(require("debug")); const detect_port_1 = tslib_1.__importDefault(require("detect-port")); const get_docker_1 = tslib_1.__importDefault(require("./../utils/get-docker")); const cli_sdk_1 = require("@cto.ai/cli-sdk"); const debug = (0, debug_1.default)('ops:ContainerService'); class ContainerService { constructor() { this.log = console.log; this.getPorts = (portMap) => { let localPorts = []; let dockerPorts = []; for (const portPair of portMap) { let portSplit = portPair.split(':'); if (portSplit[0] === '' && portSplit[1] === '') { throw new CustomErrors_1.MissingYamlPortError(); } localPorts.push(portSplit[0]); dockerPorts.push(portSplit[1]); } return [localPorts, dockerPorts]; }; this.hasDuplicates = (allPorts) => { return new Set(allPorts).size !== allPorts.length; }; this.checkLocalPorts = async (localPorts) => { let allocatedPorts = []; for (const localPort of localPorts) { // NOTE: the type annotations here are incorrect, the argument can also be a string //@ts-ignore const port = await (0, detect_port_1.default)(localPort); if (localPort !== port.toString()) { allocatedPorts.push(localPort); } } return allocatedPorts; }; this.validatePorts = async (portMap) => { if (portMap.length === 0 || portMap[0] === null) { throw new CustomErrors_1.MissingYamlPortError(); } const [localPorts, dockerPorts] = this.getPorts(portMap); if (this.hasDuplicates(localPorts)) { throw new CustomErrors_1.DuplicateYamlPortError(); } if (this.hasDuplicates(dockerPorts)) { throw new CustomErrors_1.DuplicateYamlPortError(); } const allocatedPorts = await this.checkLocalPorts(localPorts); if (allocatedPorts.length != 0) { throw new CustomErrors_1.AllocatedYamlPortError(allocatedPorts.join(', ')); } }; this.create = async (op, options) => { const docker = await (0, get_docker_1.default)(console, 'ContainerService'); this.log(`⚙️ Running ${cli_sdk_1.ux.colors.dim(op.name)}...`); if (op.port) { try { await this.validatePorts(op.port); } catch (err) { // validatePorts throws a user-friendly error message this.log(err.message); debug('%O', err); throw new Error('Error creating Docker container'); } } try { this.container = await docker.createContainer(options); return this.container; } catch (err) { debug('%O', err); throw new Error('Error creating Docker container'); } }; /** * Starts and runs the current container. * NOTE: will `process.exit` when the container wraps up! */ this.start = async (stream) => { if (!this.container) throw new Error('No docker container to start up'); try { await this.container.start(); this.resize(); process.stdout.on('resize', this.resize); const exitStatus = await this.container.wait(); this.handleExit(stream, false, exitStatus); } catch (err) { debug('%O', err); throw new Error(err); } }; this.handleStream = (stream) => { const CTRL_P = '\u0010'; const CTRL_Q = '\u0011'; let previousKey = ''; stream.pipe(process.stdout); const stdin = process.stdin; stdin.resume(); stdin.setEncoding('utf8'); stdin.setRawMode && stdin.setRawMode(true); stdin.pipe(stream); stdin.on('data', (key) => { // Detects it is detaching a running container if (previousKey === CTRL_P && key === CTRL_Q) { this.handleExit(stream, false, 0); } previousKey = key; }); }; // NOTE: This function (indirectly) calls `process.exit` this.handleExit = (stream, isRaw, exitStatus) => { if (!this.container) throw new Error('No docker container for removal'); const stdout = process.stdout; const stdin = process.stdin; try { stdout.removeListener('resize', this.resize); stdin.removeAllListeners(); stdin.setRawMode && stdin.setRawMode(isRaw); stdin.resume(); stream.end(); } catch (err) { debug('%O', err); throw new Error(err); } }; this.resize = () => { if (!this.container) throw new Error('No docker container for resize'); try { const dimensions = { h: process.stdout.rows, w: process.stderr.columns, }; if (dimensions.h !== 0 && dimensions.w !== 0) { this.container.resize(dimensions, () => { }); } } catch (err) { debug('%O', err); throw new Error(err); } }; } } exports.ContainerService = ContainerService;