briareus
Version:
Briareus assists with Feature Branch deploys to ECS
161 lines (141 loc) • 5.11 kB
JavaScript
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;