UNPKG

@deep-foundation/deeplinks

Version:

[![npm](https://img.shields.io/npm/v/@deep-foundation/deeplinks.svg)](https://www.npmjs.com/package/@deep-foundation/deeplinks) [![Gitpod](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/deep-fo

233 lines 12.9 kB
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