@atomist/automation-client
Version:
Atomist API for software low-level client
226 lines • 9.86 kB
JavaScript
"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