UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

212 lines • 8.21 kB
"use strict"; 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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const stringify = require("json-stringify-safe"); const _ = require("lodash"); const AutomationEventListener_1 = require("../../../server/AutomationEventListener"); const MessageClientSupport_1 = require("../../../spi/message/MessageClientSupport"); const logger_1 = require("../../../util/logger"); const memory_1 = require("../../util/memory"); const poll_1 = require("../../util/poll"); const shutdown_1 = require("../../util/shutdown"); const string_1 = require("../../util/string"); const AbstractRequestProcessor_1 = require("../AbstractRequestProcessor"); const RequestProcessor_1 = require("../RequestProcessor"); const messages_1 = require("./messages"); /** * A RequestProcessor that is being run as Node.JS Cluster worker handling all the actual work. */ class ClusterWorkerRequestProcessor extends AbstractRequestProcessor_1.AbstractRequestProcessor { /* tslint:disable:variable-name */ constructor(_automations, _configuration, _listeners = []) { super(_automations, _configuration, [..._listeners, new ClusterWorkerAutomationEventListener()]); this._automations = _automations; this._configuration = _configuration; this._listeners = _listeners; this.shutdownInitiated = false; // workerSend is async, so we cannot call it in a constructor process.send({ type: "atomist:online", context: undefined }); process.on("message", msg => { if (msg.type === "atomist:registration") { this.setRegistration(msg.registration); } else if (msg.type === "atomist:command") { this.setRegistrationIfRequired(msg); this.processCommand(decorateContext(msg)); } else if (msg.type === "atomist:event") { this.setRegistrationIfRequired(msg); this.processEvent(decorateContext(msg)); } else if (msg.type === "atomist:gc") { memory_1.gc(); } else if (msg.type === "atomist:heapdump") { memory_1.heapDump(); } else if (msg.type === "atomist:mtrace") { memory_1.mtrace(); } else if (msg.type === "atomist:shutdown") { logger_1.logger.debug("Received shutdown message"); this.shutdownInitiated = true; // async-exit-hook ensures hooks are only run once, so // this is safe even if worker already received signal process.kill(process.pid); } }); shutdown_1.registerShutdownHook(() => __awaiter(this, void 0, void 0, function* () { if (this.shutdownInitiated) { return 0; } if (!shutdown_1.terminationGraceful(this._configuration)) { return 0; } const gracePeriod = shutdown_1.terminationGracePeriod(this._configuration); try { yield poll_1.poll(() => this.shutdownInitiated, gracePeriod * 2); } catch (e) { logger_1.logger.warn("Did not receive shutdown message from master within twice the grace period"); return 1; } return 0; }), 0, "wait for shutdown message"); } /* tslint:enable:variable-name */ setRegistration(registration) { logger_1.logger.debug("Receiving registration '%s'", stringify(registration)); this.registration = registration; this.graphClients = this._configuration.graphql.client.factory; } setRegistrationIfRequired(data) { if (!this.registration) { this.setRegistration(data.registration); } } sendShutdown(code, ctx) { return __awaiter(this, void 0, void 0, function* () { yield messages_1.workerSend({ type: "atomist:shutdown", data: code, context: ctx.context }); }); } sendStatusMessage(payload, ctx) { return messages_1.workerSend({ type: "atomist:status", context: ctx.context, data: payload, }); } createGraphClient(event, context) { return this.graphClients.create(RequestProcessor_1.workspaceId(event), this._configuration); } createMessageClient(event, context) { return new ClusterWorkerMessageClient(event, context); } setupNamespace(request, automations, invocationId = string_1.guid(), ts = Date.now()) { const context = request.__context; delete request.__context; return context; } } exports.ClusterWorkerRequestProcessor = ClusterWorkerRequestProcessor; class ClusterWorkerMessageClient extends MessageClientSupport_1.MessageClientSupport { constructor(event, ctx) { super(); this.event = event; this.ctx = ctx; } delete(destinations, options) { return __awaiter(this, void 0, void 0, function* () { return this.doSend(undefined, Array.isArray(destinations) ? destinations : [destinations], Object.assign(Object.assign({}, options), { delete: true })); }); } doSend(msg, destinations, options) { return messages_1.workerSend({ type: "atomist:message", context: this.ctx.context, data: { message: msg, destinations, options, }, }); } } class ClusterWorkerAutomationEventListener extends AutomationEventListener_1.AutomationEventListenerSupport { commandSuccessful(payload, ctx, result) { return messages_1.workerSend({ type: "atomist:command_success", event: payload, context: ctx.context, data: sanitize(result), }); } commandFailed(payload, ctx, err) { return messages_1.workerSend({ type: "atomist:command_failure", event: payload, context: ctx.context, data: sanitize(err), }); } eventSuccessful(payload, ctx, result) { return messages_1.workerSend({ type: "atomist:event_success", event: payload, context: ctx.context, data: sanitize(result), }); } eventFailed(payload, ctx, err) { return messages_1.workerSend({ type: "atomist:event_failure", event: payload, context: ctx.context, data: sanitize(err), }); } } /** * Start a new worker node * @param {AutomationServer} automations * @param {WebSocketClientOptions} options * @returns {RequestProcessor} */ function startWorker(automations, configuration, listeners = []) { const worker = new ClusterWorkerRequestProcessor(automations, configuration, listeners); return worker; } exports.startWorker = startWorker; function decorateContext(msg) { const event = msg.data; event.__context = msg.context; return event; } function sanitize(obj) { const newObj = {}; _.forEach(obj, (v, k) => { try { JSON.stringify(v); newObj[k] = v; } catch (e) { newObj[k] = "[circular]"; } }); if (!!obj.message) { newObj.message = obj.message; } if (!!obj.stack) { newObj.stack = obj.stack; } return newObj; } //# sourceMappingURL=ClusterWorkerRequestProcessor.js.map