@cto.ai/ops
Version:
💻 CTO.ai - The CLI built for Teams 🚀
156 lines (155 loc) • 6.17 kB
JavaScript
;
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;