UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

361 lines • 17 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 globals_1 = require("../../globals"); const HandlerResult_1 = require("../../HandlerResult"); const MessageClientSupport_1 = require("../../spi/message/MessageClientSupport"); const logger_1 = require("../../util/logger"); const disposable_1 = require("../invoker/disposable"); const namespace = require("../util/cls"); const string_1 = require("../util/string"); class AbstractRequestProcessor { constructor(automations, configuration, listeners = []) { this.automations = automations; this.configuration = configuration; this.listeners = listeners; } processCommand(command, // tslint:disable-next-line:no-empty callback = () => { }) { // setup context const ses = namespace.create(); const cls = this.setupNamespace(command, this.automations); ses.run(() => { namespace.set(cls); this.listeners.forEach(l => l.commandIncoming(command)); const np = namespace.get(); const ci = { name: command.command, args: command.parameters, mappedParameters: command.mapped_parameters, secrets: command.secrets, }; const ctx = { workspaceId: command.team.id, source: command.source, correlationId: command.correlation_id, invocationId: np ? np.invocationId : undefined, messageClient: undefined, context: cls, trigger: _.cloneDeep(command), configuration: this.configuration, }; ctx.graphClient = this.createGraphClient(command, ctx); ctx.messageClient = this.createAndWrapMessageClient(command, ctx); ctx.lifecycle = { registerDisposable: disposable_1.registerDisposable(ctx), dispose: disposable_1.dispose(ctx), }; this.listeners.forEach(l => l.contextCreated(ctx)); this.listeners.forEach(l => l.commandStarting(ci, ctx)); this.invokeCommand(ci, ctx, command, callback); }); } processEvent(event, // tslint:disable-next-line:no-empty callback = () => { }) { // setup context const ses = namespace.create(); const cls = this.setupNamespace(event, this.automations); ses.run(() => { namespace.set(cls); this.listeners.forEach(l => l.eventIncoming(event)); const np = namespace.get(); const ef = { data: event.data, extensions: { operationName: event.extensions.operationName, }, secrets: event.secrets, }; const ctx = { workspaceId: event.extensions.team_id, correlationId: event.extensions.correlation_id, invocationId: np ? np.invocationId : undefined, messageClient: undefined, context: cls, trigger: _.cloneDeep(event), configuration: this.configuration, }; ctx.graphClient = this.createGraphClient(event, ctx); ctx.messageClient = this.createAndWrapMessageClient(event, ctx); ctx.lifecycle = { registerDisposable: disposable_1.registerDisposable(ctx), dispose: disposable_1.dispose(ctx), }; this.listeners.forEach(l => l.contextCreated(ctx)); this.listeners.forEach(l => l.eventStarting(ef, ctx)); this.invokeEvent(ef, ctx, event, callback); }); } sendCommandStatus(success, code, request, ctx) { const source = _.cloneDeep(request.source); if (source && source.slack) { delete source.slack.user; } const response = { api_version: "1", correlation_id: request.correlation_id, team: request.team, command: request.command, source: request.source, destinations: [source], status: { code, reason: `${success ? "Successfully" : "Unsuccessfully"} invoked command` + ` ${request.command} of ${this.automations.automations.name}@${this.automations.automations.version}`, }, }; return this.sendStatusMessage(response, ctx); } sendEventStatus(success, request, event, ctx) { const response = { api_version: "1", correlation_id: event.extensions.correlation_id, team: { id: event.extensions.team_id, name: event.extensions.team_name, }, event: request.extensions.operationName, status: { code: success ? 0 : 1, reason: `${success ? "Successfully" : "Unsuccessfully"} invoked event subscription` + ` ${request.extensions.operationName} of ${this.automations.automations.name}@${this.automations.automations.version}`, }, }; return this.sendStatusMessage(response, ctx); } invokeCommand(ci, ctx, command, callback) { const finalize = (result) => { this.sendCommandStatus(result.code === 0, result.code, command, ctx) .catch(err => logger_1.logger.warn("Unable to send status for command '%s': %s", command.command, err.message)) .then(() => { callback(Promise.resolve(result)); logger_1.logger.debug(`Finished invocation of command '%s': %s`, command.command, stringify(result, possibleAxiosObjectReplacer)); this.clearNamespace(); }); }; logger_1.logger.debug("Incoming command invocation '%s'", stringify(command, string_1.replacer)); try { this.automations.invokeCommand(ci, ctx) .then(result => { if (!result || !result.hasOwnProperty("code")) { return Object.assign(Object.assign({}, defaultResult(ctx)), result); } else { return result; } }) .then(result => ctx.lifecycle ? ctx.lifecycle.dispose().then(() => result) : result) .then(result => { if (result.code === 0) { result = Object.assign(Object.assign({}, defaultResult(ctx)), result); this.listeners.map(l => () => l.commandSuccessful(ci, ctx, result)) .reduce((p, f) => p.then(f), Promise.resolve()) .then(() => finalize(result)); } else { result = Object.assign(Object.assign({}, defaultErrorResult(ctx)), result); this.listeners.map(l => () => l.commandFailed(ci, ctx, result)) .reduce((p, f) => p.then(f), Promise.resolve()) .then(() => finalize(result)); } }) .catch(err => { this.handleCommandError(err, command, ci, ctx, callback); }); } catch (err) { this.handleCommandError(err, command, ci, ctx, callback); } } invokeEvent(ef, ctx, event, callback) { const finalize = (results) => { let noncircularResults = results; try { JSON.stringify(noncircularResults); } catch (err) { logger_1.logger.error("Circular object returned from event handler: %s", stringify(results)); noncircularResults = results.map(r => ({ code: r.code, message: stringify(r.message) })); logger_1.logger.error("Substituting for circular object: %j", noncircularResults); } this.sendEventStatus(!noncircularResults.some(r => r.code !== 0), ef, event, ctx) .catch(err => logger_1.logger.warn("Unable to send status for event subscription'%s': %s", event.extensions.operationName, err.message)) .then(() => { callback(Promise.resolve(noncircularResults)); logger_1.logger.debug(`Finished invocation of event subscription '%s': %s`, event.extensions.operationName, stringify(noncircularResults, possibleAxiosObjectReplacer)); this.clearNamespace(); }); }; logger_1.logger.debug("Incoming event subscription '%s'", stringify(event, string_1.replacer)); try { this.automations.onEvent(ef, ctx) .then(result => { if (!result || result.length === 0) { return [defaultResult(ctx)]; } else { return result; } }) .then(result => ctx.lifecycle ? ctx.lifecycle.dispose().then(() => result) : result) .then(result => { if (!result.some(r => r.code !== 0)) { this.listeners.map(l => () => l.eventSuccessful(ef, ctx, result)) .reduce((p, f) => p.then(f), Promise.resolve()) .then(() => finalize(result)); } else { this.listeners.map(l => () => l.eventFailed(ef, ctx, result)) .reduce((p, f) => p.then(f), Promise.resolve()) .then(() => finalize(result)); } }) .catch(err => { this.handleEventError(err, event, ef, ctx, callback); }); } catch (err) { this.handleEventError(err, event, ef, ctx, callback); } } createAndWrapMessageClient(event, context) { return new MessageClientSupport_1.DefaultSlackMessageClient(new AutomationEventListenerEnabledMessageClient(context, this.createMessageClient(event, context), this.listeners), context.graphClient); } setupNamespace(request, automations, invocationId = string_1.guid(), ts = Date.now()) { return { correlationId: _.get(request, "correlation_id") || _.get(request, "extensions.correlation_id"), workspaceId: _.get(request, "team.id") || _.get(request, "extensions.team_id"), workspaceName: _.get(request, "team.name") || _.get(request, "extensions.team_name"), operation: _.get(request, "command") || _.get(request, "extensions.operationName"), name: automations.automations.name, version: automations.automations.version, invocationId, ts, }; } clearNamespace() { namespace.set({ correlationId: null, workspaceId: null, workspaceName: null, operation: null, name: null, version: null, invocationId: null, ts: null, }); } handleCommandError(err, command, ci, ctx, callback) { const result = Object.assign(Object.assign({}, defaultErrorResult(ctx)), HandlerResult_1.failure(err)); this.listeners.map(l => () => l.commandFailed(ci, ctx, err)) .reduce((p, f) => p.then(f), Promise.resolve()) .then(() => { return this.sendCommandStatus(false, result.code, command, ctx) .then(() => { if (callback) { callback(Promise.resolve(result)); } if (err instanceof Error) { logger_1.logger.error(`Failed invocation of command '%s': %s`, command.command, err.message); logger_1.logger.error(err.stack); } else { logger_1.logger.error(`Failed invocation of command '%s'`, command.command); } this.clearNamespace(); }) .catch(error => logger_1.logger.warn("Unable to send status for command: " + stringify(command))); }); } handleEventError(err, event, ef, ctx, callback) { const result = Object.assign(Object.assign({}, defaultErrorResult(ctx)), HandlerResult_1.failure(err)); this.listeners.map(l => () => l.eventFailed(ef, ctx, err)) .reduce((p, f) => p.then(f), Promise.resolve()) .then(() => { return this.sendEventStatus(false, ef, event, ctx) .then(() => { if (callback) { callback(Promise.resolve(result)); } if (err instanceof Error) { logger_1.logger.error(`Failed invocation of event subscription '%s': %s`, event.extensions.operationName, err.message); logger_1.logger.error(err.stack); } else { logger_1.logger.error(`Failed invocation of event subscription '%s'`, event.extensions.operationName); } this.clearNamespace(); }) .catch(error => logger_1.logger.warn("Unable to send status for event subscription: " + stringify(event))); }); } } exports.AbstractRequestProcessor = AbstractRequestProcessor; class AutomationEventListenerEnabledMessageClient { constructor(ctx, delegate, listeners = []) { this.ctx = ctx; this.delegate = delegate; this.listeners = listeners; } respond(msg, options) { return __awaiter(this, void 0, void 0, function* () { const newMsg = yield this.listeners.map(l => m => l.messageSending(m.message || msg, [], m.options || options, this.ctx)) .reduce((p, f) => p.then(f), Promise.resolve({ message: msg, destinations: [], options })); globals_1.eventStore().recordMessage(newMsg.options && newMsg.options.id ? newMsg.options.id : string_1.guid(), this.ctx.correlationId, newMsg.message); yield this.delegate.respond(newMsg.message, newMsg.options); return Promise.all(this.listeners.map(l => l.messageSent(newMsg.message, [], newMsg.options, this.ctx))); }); } send(msg, destinations, options) { return __awaiter(this, void 0, void 0, function* () { const newMsg = yield this.listeners.map(l => m => l.messageSending(m.message || msg, m.destinations || destinations, m.options || options, this.ctx)) .reduce((p, f) => p.then(f), Promise.resolve({ message: msg, destinations, options })); globals_1.eventStore().recordMessage(newMsg.options && newMsg.options.id ? newMsg.options.id : string_1.guid(), this.ctx.correlationId, newMsg.message); yield this.delegate.send(newMsg.message, newMsg.destinations, newMsg.options); return Promise.all(this.listeners.map(l => l.messageSent(newMsg.message, newMsg.destinations, newMsg.options, this.ctx))); }); } delete(destinations, options) { return __awaiter(this, void 0, void 0, function* () { return this.delegate.delete(destinations, options); }); } } function defaultResult(context) { const result = { code: 0, correlation_id: context.context.correlationId, invocation_id: context.context.invocationId, }; return result; } exports.defaultResult = defaultResult; function defaultErrorResult(context) { const result = Object.assign(Object.assign({}, defaultResult(context)), { code: 1, message: `Command '${context.context.operation}' failed` }); return result; } exports.defaultErrorResult = defaultErrorResult; function possibleAxiosObjectReplacer(key, value) { if ((key === "request" || key === "response") && !!value && stringify(value).length > 200) { return `<...elided because it might be a really long axios ${key}...>`; } else { return value; } } exports.possibleAxiosObjectReplacer = possibleAxiosObjectReplacer; //# sourceMappingURL=AbstractRequestProcessor.js.map