@deep-foundation/deeplinks
Version:
[](https://www.npmjs.com/package/@deep-foundation/deeplinks) [](https://gitpod.io/#https://github.com/deep-fo
233 lines • 12.9 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { exec } from 'child_process';
import crypto from 'crypto';
import axios from 'axios';
import getPort from 'get-port';
import Debug from 'debug';
import util from 'util';
import { serializeError } from 'serialize-error';
const execAsync = util.promisify(exec);
const debug = Debug('deeplinks:container-controller');
const log = debug.extend('log');
const error = debug.extend('error');
const DOCKER = process.env.DOCKER || '0';
export const runnerControllerOptionsDefault = {
gql_docker_domain: 'deep-links',
network: 'network',
handlersHash: {},
};
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
export class ContainerController {
constructor(options) {
this.runContainerHash = {};
this.handlersHash = {};
this.network = (options === null || options === void 0 ? void 0 : options.network) || runnerControllerOptionsDefault.network;
this.gql_docker_domain = (options === null || options === void 0 ? void 0 : options.gql_docker_domain) || runnerControllerOptionsDefault.gql_docker_domain;
this.handlersHash = (options === null || options === void 0 ? void 0 : options.handlersHash) || runnerControllerOptionsDefault.handlersHash;
}
;
_runContainer(containerName, dockerPort, options) {
return __awaiter(this, void 0, void 0, function* () {
const { handler, forcePort, forceRestart, publish } = options;
const { network, gql_docker_domain } = this;
let done = false;
let count = 30;
let dockerRunResult;
while (!done) {
log('_runContainer count', { count });
if (count < 0)
return { error: 'timeout _runContainer' };
count--;
try {
const command = `docker pull ${handler}; docker run --add-host host.docker.internal:host-gateway --restart unless-stopped -e PORT=${dockerPort} -e GQL_URN=${gql_docker_domain}:3006/gql -e GQL_SSL=0 --name ${containerName} ${publish ? `-p ${dockerPort}:${dockerPort}` : `--expose ${dockerPort}`} --net deep-${network} -d ${handler}`;
log('_runContainer command', { command });
const dockerRunResultObject = yield execAsync(command);
log('_runContainer dockerRunResultObject', JSON.stringify(dockerRunResultObject, null, 2));
dockerRunResult = dockerRunResultObject === null || dockerRunResultObject === void 0 ? void 0 : dockerRunResultObject.stdout;
log('_runContainer dockerRunResultString', { dockerRunResult });
}
catch (e) {
dockerRunResult = e.stderr;
}
if (dockerRunResult.indexOf('port is already allocated') !== -1) {
error('port is already allocated');
if (forcePort)
return { error: 'port is already allocated' };
continue;
}
else if (dockerRunResult.indexOf('is already in use by container') !== -1) {
error('is already in use by container');
if (!forceRestart)
return { error: 'is already in use by container' };
yield this._dropContainer(containerName);
}
else {
done = true;
if (!publish && !+DOCKER)
throw new Error('Need proxy, dl issue https://github.com/deep-foundation/deeplinks/issues/45');
let host = publish ? +DOCKER ? 'host.docker.internal' : 'localhost' : +DOCKER ? containerName : '';
const container = { name: containerName, host, port: dockerPort, options };
log('_runContainer container', container);
try {
const waitResult = yield execAsync(`npx wait-on --timeout 5000 http-get://${host}:${dockerPort}/healthz`);
log('_runContainer npx done', { waitResult });
}
catch (e) {
const serializedError = serializeError(e);
error('_runContainer error', JSON.stringify(serializedError, null, 2));
return { error: 'wait-on healthz timeout _runContainer' };
}
return container;
}
}
});
}
newContainer(options) {
return __awaiter(this, void 0, void 0, function* () {
const { handler, forcePort, forceName } = options;
const { network, handlersHash, runContainerHash } = this;
const containerName = forceName || `deep-${crypto.createHash('md5').update(handler).digest("hex")}`;
log('newContainer options, network, handlersHash, containerName, forceName', { options, network, handlersHash, containerName, forceName });
let container = yield this.findContainer(containerName);
log('newContainer container', { container });
if (container)
return container;
let dockerPort = forcePort || (yield getPort());
if (runContainerHash[containerName] === undefined) {
runContainerHash[containerName] = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
let done = false;
let count = 10;
while (!done) {
log('newContainer while count', { count });
count--;
container = yield this._runContainer(containerName, dockerPort, options);
log('newContainer while container', { container });
if (container === null || container === void 0 ? void 0 : container.error) {
if (!forcePort)
dockerPort = yield getPort();
continue;
}
handlersHash[containerName] = container;
log('newContainer done true', { containerName, handlersHash, runContainerHash });
done = true;
}
resolve(undefined);
}));
}
yield runContainerHash[containerName];
return handlersHash[containerName];
});
}
findContainer(containerName) {
return __awaiter(this, void 0, void 0, function* () {
const { handlersHash, runContainerHash } = this;
yield runContainerHash[containerName];
return handlersHash[containerName];
});
}
_dropContainer(containerName) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
log('_dropContainer', { containerName });
return (_a = (yield execAsync(`docker stop ${containerName} && docker rm ${containerName}`))) === null || _a === void 0 ? void 0 : _a.stdout;
});
}
dropContainer(container) {
return __awaiter(this, void 0, void 0, function* () {
const { handlersHash, runContainerHash } = this;
yield runContainerHash[container.name];
if (!handlersHash[container.name])
return;
let dockerStopResult = yield this._dropContainer(container.name);
handlersHash[container.name] = undefined;
log('dropContainer dockerStopResult', { dockerStopResult });
});
}
_checkAndRestart(container) {
return __awaiter(this, void 0, void 0, function* () {
const { handlersHash, runContainerHash } = this;
try {
const healthz = `http://${container.host}:${container.port}/healthz`;
yield axios.get(healthz);
return { error: 'healthz ok' };
}
catch (e) {
error('heatlthz error code', e.code);
runContainerHash[container.name] = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
handlersHash[container.name] = undefined;
container.options.forcePort = container.port;
container.options.forceName = container.name;
const newContainer = yield this._runContainer(container.name, container.port, container.options);
handlersHash[container.name] = newContainer;
resolve(undefined);
}));
yield runContainerHash[container.name];
log('_checkAndRestart handlersHash[container.name]', handlersHash[container.name]);
return handlersHash[container.name];
}
});
}
initHandler(container) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const { runContainerHash } = this;
const { host, port } = container;
let initResult;
yield runContainerHash[container.name];
try {
const initRunner = `http://${host}:${port}/init`;
log('initHandler initRunner', { initRunner });
initResult = yield axios.post(initRunner);
log('initHandler initResult status', { status: initResult.status });
}
catch (e) {
error('init error code', e.code);
const checkResult = yield this._checkAndRestart(container);
if (checkResult === null || checkResult === void 0 ? void 0 : checkResult.error)
return Object.assign({}, checkResult);
yield this.initHandler(container);
return ({ error: e });
}
if ((_a = initResult === null || initResult === void 0 ? void 0 : initResult.data) === null || _a === void 0 ? void 0 : _a.error)
return { error: (_b = initResult === null || initResult === void 0 ? void 0 : initResult.data) === null || _b === void 0 ? void 0 : _b.error };
return {};
});
}
callHandler(options) {
var _a, _b, _c, _d, _e;
return __awaiter(this, void 0, void 0, function* () {
const { handlersHash, runContainerHash } = this;
const { container } = options;
yield runContainerHash[container.name];
try {
const callRunner = `http://${container.host}:${container.port}/call`;
log('callHandler', { callRunner, params: options });
const callResult = yield axios.post(callRunner, { params: options });
log('callHandler callResult status', { status: callResult.status });
if ((_a = callResult === null || callResult === void 0 ? void 0 : callResult.data) === null || _a === void 0 ? void 0 : _a.error)
return { error: (_b = callResult === null || callResult === void 0 ? void 0 : callResult.data) === null || _b === void 0 ? void 0 : _b.error };
if ((_c = callResult === null || callResult === void 0 ? void 0 : callResult.data) === null || _c === void 0 ? void 0 : _c.rejected)
return Promise.reject((_d = callResult === null || callResult === void 0 ? void 0 : callResult.data) === null || _d === void 0 ? void 0 : _d.rejected);
return (_e = callResult === null || callResult === void 0 ? void 0 : callResult.data) === null || _e === void 0 ? void 0 : _e.resolved;
}
catch (e) {
const serializedError = serializeError(e);
error('call error', JSON.stringify(serializedError, null, 2));
const checkResult = yield this._checkAndRestart(container);
if (checkResult === null || checkResult === void 0 ? void 0 : checkResult.error)
return Object.assign({}, checkResult);
yield this.callHandler(options);
return ({ error: e });
}
});
}
}
//# sourceMappingURL=container-controller.js.map