UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

226 lines • 9.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stringify = require("json-stringify-safe"); const _ = require("lodash"); const semver = require("semver"); const ApolloGraphClient_1 = require("../graph/ApolloGraphClient"); const HandlerResult_1 = require("../HandlerResult"); const NodeConfigSecretResolver_1 = require("../internal/env/NodeConfigSecretResolver"); const metadata_1 = require("../internal/metadata/metadata"); const metadataReading_1 = require("../internal/metadata/metadataReading"); const parameterPopulation_1 = require("../internal/parameterPopulation"); const string_1 = require("../internal/util/string"); const SmartParameters_1 = require("../SmartParameters"); const MetadataProcessor_1 = require("../spi/env/MetadataProcessor"); const constructionUtils_1 = require("../util/constructionUtils"); const logger_1 = require("../util/logger"); const AbstractAutomationServer_1 = require("./AbstractAutomationServer"); /** * Simple automation server that offers building style * configuration */ class BuildableAutomationServer extends AbstractAutomationServer_1.AbstractAutomationServer { constructor(opts) { super(); this.opts = opts; this.commandHandlers = []; this.eventHandlers = []; this.ingesters = []; this.secretResolver = new NodeConfigSecretResolver_1.NodeConfigSecretResolver(); this.metadataProcessor = new MetadataProcessor_1.PassThroughMetadataProcessor(); if (opts.secretResolver) { this.secretResolver = opts.secretResolver; } if (opts.metadataProcessor) { this.metadataProcessor = opts.metadataProcessor; } if (opts.endpoints && opts.endpoints.graphql && opts.workspaceIds && opts.workspaceIds.length > 0 && opts.apiKey) { this.graphClient = new ApolloGraphClient_1.ApolloGraphClient(`${opts.endpoints.graphql}/${opts.workspaceIds[0]}`, { Authorization: `Bearer ${opts.apiKey}` }); } } registerCommandHandler(chm) { const factory = constructionUtils_1.toFactory(chm); const instanceToInspect = factory(); if (instanceToInspect) { const md = metadataReading_1.metadataFromInstance(instanceToInspect); if (!md) { throw new Error(`Cannot get metadata from handler '${stringify(instanceToInspect)}'`); } this.commandHandlers.push({ metadata: this.metadataProcessor.process(md, this.opts), invoke: (i, ctx) => { const newHandler = factory(); const params = !!newHandler.freshParametersInstance ? newHandler.freshParametersInstance() : newHandler; return this.invokeCommandHandlerWithFreshParametersInstance(newHandler, md, params, i, ctx); }, }); } return this; } fromCommandHandler(hc) { const md = metadata_1.isCommandHandlerMetadata(hc) ? hc : metadataReading_1.metadataFromInstance(hc); this.commandHandlers.push({ metadata: this.metadataProcessor.process(md, this.opts), invoke: (i, ctx) => { const freshParams = !!hc.freshParametersInstance ? hc.freshParametersInstance() : hc; return this.invokeCommandHandlerWithFreshParametersInstance(hc, md, freshParams, i, ctx); }, }); return this; } registerEventHandler(maker) { const factory = constructionUtils_1.toFactory(maker); const instanceToInspect = factory(); if (instanceToInspect) { const md = metadataReading_1.metadataFromInstance(instanceToInspect); if (!md) { throw new Error(`Cannot get metadata from event handler '${stringify(instanceToInspect)}'`); } this.eventHandlers.push({ metadata: this.metadataProcessor.process(md, this.opts), invoke: (e, ctx) => this.invokeFreshEventHandlerInstance(factory(), md, e, ctx), }); } return this; } registerIngester(ingester) { this.ingesters.push(ingester); return this; } invokeCommandHandler(invocation, metadata, ctx) { const handler = this.commandHandlers.find(a => a.metadata.name === invocation.name); logger_1.logger.debug("Invoking command handler '%s'", metadata.name); return handler.invoke(invocation, ctx); } invokeEventHandler(e, metadata, ctx) { const handler = this.eventHandlers.find(a => a.metadata.name === metadata.name); logger_1.logger.debug("Invoking event handler '%s'", metadata.name); return handler.invoke(e, ctx); } /** * Populate handler parameters */ invokeCommandHandlerWithFreshParametersInstance(h, md, params, invocation, ctx) { parameterPopulation_1.populateParameters(params, md, invocation.args); parameterPopulation_1.populateValues(params, md, this.opts); this.populateMappedParameters(params, md, invocation); this.populateSecrets(params, md, invocation.secrets); const bindAndValidate = SmartParameters_1.isSmartParameters(params) ? Promise.resolve(params.bindAndValidate()) : Promise.resolve(); return bindAndValidate .then(vr => { if (SmartParameters_1.isValidationError(vr)) { return Promise.reject(`Validation failure invoking command handler '${md.name}': [${vr.message}]`); } const handlerResult = h.handle(this.enrichContext(ctx), params); if (!handlerResult) { return HandlerResult_1.SuccessPromise; } return handlerResult .then(result => { if (result) { return result; } else { return HandlerResult_1.SuccessPromise; } }); }); } invokeFreshEventHandlerInstance(h, metadata, e, ctx) { this.populateSecrets(h, metadata, e.secrets); parameterPopulation_1.populateValues(h, metadata, this.opts); const handlerResult = h.handle(e, this.enrichContext(ctx), h); if (!handlerResult) { return HandlerResult_1.SuccessPromise; } return (handlerResult) .then(result => { if (result) { return result; } else { return HandlerResult_1.SuccessPromise; } }); } enrichContext(ctx) { ctx.graphClient = ctx.graphClient || this.graphClient; return ctx; } populateMappedParameters(h, metadata, invocation) { // Resolve from the invocation, otherwise from our fallback class InvocationSecretResolver { constructor(mp) { this.mp = mp; } resolve(key) { const value = this.mp.find(a => a.name === key); if (value) { return String(value.value); } throw new Error(`Cannot resolve mapped parameter '${key}'`); } } // if the bot sends any of them, then only use those? // it does not fallback for each parameter; all or nothing. // this is probably by design ... is there a test/dev circumstance where // mappedParameters is not populated? const mrResolver = invocation.mappedParameters ? new InvocationSecretResolver(invocation.mappedParameters) : this.secretResolver; // logger.debug("Applying mapped parameters"); const mappedParameters = metadata.mapped_parameters || []; const invMps = invocation.mappedParameters || []; mappedParameters.forEach(mp => { if (invMps.some(im => im.name === mp.name) || mp.required) { _.update(h, mp.name, () => mrResolver.resolve(mp.name)); } }); } populateSecrets(h, metadata, invocationSecrets) { // Resolve from the invocation, otherwise from our fallback class InvocationSecretResolver { constructor(sec) { this.sec = sec; } resolve(key) { const value = this.sec.find(a => a.uri === key); if (value) { if (!!value.value) { return String(value.value); } else { return undefined; } } throw new Error(`Cannot resolve secret '${key}'`); } } const secretResolver = invocationSecrets ? new InvocationSecretResolver(invocationSecrets) : this.secretResolver; // logger.debug("Applying secrets"); const secrets = metadata.secrets || []; secrets.forEach(s => { _.update(h, s.name, () => secretResolver.resolve(s.uri)); }); } get automations() { const version = !!this.opts.version && this.opts.policy === "durable" ? `${semver.major(this.opts.version)}.0.0` : this.opts.version; return { name: this.opts.name, version: version || "0.0.0", policy: this.opts.policy, team_ids: this.opts.workspaceIds, groups: string_1.toStringArray(this.opts.groups), keywords: this.opts.keywords, commands: this.commandHandlers.map(e => e.metadata), events: this.eventHandlers.map(e => e.metadata), ingesters: this.ingesters, }; } } exports.BuildableAutomationServer = BuildableAutomationServer; //# sourceMappingURL=BuildableAutomationServer.js.map