UNPKG

briareus

Version:

Briareus assists with Feature Branch deploys to ECS

161 lines (141 loc) 5.11 kB
'use strict' const _ = require('lodash'); const async = require('async'); const EventEmitter = require('events'); const uuidv4 = require('uuid/v4'); const jsonpatch = require('fast-json-patch'); const s = require("underscore.string"); const Actions = require('./actions'); const persistance = require('./persistance'); class Pipeline { constructor(ctx, data, actions) { this.ctx = ctx; this.events = new EventEmitter(); this.data = data; this.actions = actions; } toJSON() { this.data; } run(cb) { this.doActions(this.actions, cb); } get(path) { return jsonpatch.getValueByPointer(this.data, path); } save(cb) { this.ctx.log.info(`Saving Pipeline`); persistance.putItem(this.data, cb) } emit(event, data = {}) { let decoratedEvent = { id: uuidv4(), createdAt: Math.floor(new Date()), name: event, data }; this.ctx.log.info({ event: decoratedEvent }, `Pipeline Event`); this.data.events.push(decoratedEvent); } doActions(phases, cb) { let pipelineMetadata = {}; async.series([ (done) => this.startStep('pipeline', pipelineMetadata, { message: "Starting Pipeline" }, done), (done) => { async.eachSeries(phases, (phase, next) => { const phaseEventMetadata = { phase: _.pick(phase, ['id']) }; async.series([ (complete) => this.startStep('phase', phaseEventMetadata, { message: phase.description }, complete), (complete) => async.series(this.makeActions(phase.actions), complete), (complete) => this.completeStep('phase', phaseEventMetadata, { message: "Phase Complete" }, complete), ], next); }, done); }, (done) => this.completeStep('pipeline', pipelineMetadata, { message: "Pipeline Complete" }, done) ], cb); // this.startStep('pipeline', {}, ""); // async.eachSeries(phases, (phase, next) => { // const phaseEventMetadata = { phase: _.pick(phase, ['id', 'description']) }; // const phaseStartAt = Date.now(); // this.startStep('phase', phaseEventMetadata, ""); // async.series(this.makeActions(phase.actions), (err) => { // if (err) return next(err); // process.nextTick(() => { // this.completeStep('phase', phaseEventMetadata, "", next); // }) // }); // }, (err) => { // // Complete next tick so pipeline end events don't // // occur at the same time as the action events // process.nextTick(() => { // if (err) this.emit('pipeline:fail'); // else this.emit('pipeline:complete'); // this.save((err2) => cb(err)) // }); // }); } // start(type, event, data, cb) { // if (!cb) cb = _.noop; // _.merge(event, { [type]: { startAt: Date.now() } }); // _.merge(_.clone(event), data); // this.startStep(type) // } startStep(type, metadata, data, cb) { if (!cb) cb = _.noop; // Record start date on metadata object _.merge(metadata, { [type]: { startAt: Date.now() } }); let evt = _.merge(_.clone(metadata), { [type]: data }); this.emit(`${type}:start`, evt); this.save(_.ary(cb, 1)); } completeStep(type, metadata, data, cb) { if (!cb) cb = _.noop; let endAt = Date.now(); let evt = _.merge(_.clone(metadata), { [type]: data }); this.emit(`${type}:complete`, _.merge(evt, { [type]: { duration: Math.ceil((endAt - metadata[type].startAt) / 1000), endAt: endAt } })); this.save(_.ary(cb, 1)); } errorStep(type, metadata, data, cb) { if (!cb) cb = _.noop; let evt = _.merge(_.clone(metadata), { [type]: data }); this.emit(`${type}:error`, evt); this.save(_.ary(cb, 1)); } makeActions(actions) { return _.map(actions, (actionName) => { let action = Actions[actionName]; return (cb) => { const eventMetadata = { action: { id: actionName } }; async.waterfall([ (next) => this.startStep('action', eventMetadata, { message: action.waiting }, next), (next) => action(this, this.data, next), (patches, next) => { this.ctx.log.debug({ patches }, `Output patch for action:${actionName}`); this.data = jsonpatch.applyPatch(this.data, patches).newDocument; let doneMessage = action.done; if (_.isFunction(doneMessage)) doneMessage = doneMessage(this.data); this.completeStep('action', eventMetadata, { message: doneMessage }, next) }, // Set a small timeout between actions to help avoid dynamodb write race conditions // Only a best of. It doesn't really matter if this only works 90% of the time. // Writes should be going into a write queue (next) => setTimeout(next, 100) ], (err) => { if (!err) return cb(); this.data.errors.push(err); this.errorStep('action', eventMetadata, { message: err.message }, (err2) => cb(err)); }); } }); } } module.exports = Pipeline;