runas-core
Version:
The adhesive orchestrator
218 lines (185 loc) • 6.77 kB
JavaScript
'use strict';
const path = require('path');
const _ = require('lodash');
const moment = require('moment');
const bus = require('./bus');
const config = require('./config');
const logger = require('./logger');
const fsUtils = require('./utils/fsUtils');
const Step = require('./step');
const constants = require('./utils/constants');
const isInside = function(key, values) {
const sep = ',';
if (values && values.indexOf(sep) >= 0) {
return values.split(sep).indexOf(key) >= 0;
} else {
return key === values;
}
};
/**
*
* Runs one step. Is the responsible to run all the stages of a step.
*
* @returns {{execute: execute}}
* @module stepper
*/
const stepper = function() {
let step;
const _setWorkingDir = function() {
const _config = config.get();
const dirName = path.join(_config.rootDir, step.params.workingDir);
logger.trace('#green', 'stepper', 'workingDir:', '#cyan', dirName);
if (fsUtils.exists(dirName)) {
process.chdir(dirName);
} else {
logger.warn('#yellow', 'workingDir:', '#cyan', dirName, '#yellow', 'doesn\'t exist!!!', 'go to execution directory instead ->', _config.rootDir);
step.params.workingDir = '';
process.chdir(_config.rootDir);
}
};
/**
* Execute step by step recursively and synchronized!
* @param n
* @param resolve
* @param reject
*/
const _synchro = function(n, resolve, reject) {
const _config = config.get();
const _report = function(content, status, last) {
const end = moment();
return {
status: status,
content: content || {},
order: n + 1
};
};
const _resolve = function(result) {
const isLast = n === _config.stages.length - 1;
step.report(_report(result, 0, isLast));
if (result && result.skip || isLast) {
logger.info('\n\n', '#magenta', 'Finished', '|', '#green', step.params.description, '-', '#duration', moment() - step.initStep, '\n');
bus.emit('step:end', { status: 0 });
resolve(step.outputs);
} else {
_synchro(n + 1, resolve, reject);
}
};
const _reject = function(err) {
if (!err.notBuilt) {
logger.error('#green', 'stepper', 'error on', '#cyan', _config.stages[n], '(', n, 'of', _config.stages.length, ')', '#red', 'ERROR', (err && err.error || err));
}
step.report(_report(err, 1, !err || !err.keep));
if (err && err.keep) {
_synchro(n + 1, resolve, reject);
} else {
logger.info('\n\n', '#magenta', 'Finished', '|', '#green', step.params.description, '-', '#duration', moment() - step.initStep, '\n');
bus.emit('step:end', { status: 0 });
reject(err);
}
};
if (n < _config.stages.length) {
logger.trace('#green', 'stepper', 'running', '#cyan', _config.stages[n], '(', n, 'of', _config.stages.length, ')');
_setWorkingDir();
if (!step.params.pstage || isInside(_config.stages[n], step.params.pstage)) {
step._doPlugins(_config.stages[n])
.then((result) => {
if (result && result.skip === true) {
return Promise.resolve(result);
}
const promise = step._do(_config.stages[n]);
if (promise) {
return promise;
} else {
return Promise.resolve({skipped: true});
}
})
.then(_resolve)
.catch((err) => _reject(err));
} else {
_resolve();
}
} else {
resolve();
}
};
const _splitNormals = function(normal) {
const filter = (value, key) => key !== 'params';
const cloner = context => _.merge(_.pickBy(_.cloneDeep(normal), filter), {context: context, params: normal.params[context]});
const normals = _.map(normal.context, cloner);
return normals;
};
const execNormal = function(normal, resolve, reject) {
bus.emit('step:start', normal);
const stepConfig = config.load(normal);
const order = normal.order ? normal.order : 0;
step = stepConfig.step ? new Step(stepConfig.step, stepConfig.plugins) : {};
if (step._do && step.requires !== constants.notBuild) {
step.initStep = moment();
step.order = order;
logger.info('\n\n', '#magenta', 'Starting', '|', '#green', step.params.description, '| [', '#bold', `${normal.context}::${normal.name}`, ']', '\n');
_synchro(0, resolve, reject);
} else {
const checkImpl = normal.checkImpl && step.requires !== constants.notBuild;
const message = `Step "${normal.name}" is not implemented for context "${normal.context}"`;
const status = checkImpl ? 2 : 0;
bus
.emit('stage:start', { name: 'load' })
.emit('stage:end', {
status: status,
order: 0,
content: { message: message }
})
.emit('step:end', { status: status, data: {} });
if (checkImpl) {
reject({error: message, keep: true});
} else {
if (step.requires === constants.notBuild) {
logger.info('Run of', '#bold', 'step', '#green', `"${normal.name}"`, 'is configured as', '#green', `'${constants.notBuild}'`, 'for context', '#cyan', `"${normal.context}"`);
} else {
logger.info('Run of', '#bold', 'step', '#green', `"${normal.name}"`, 'is', '#green', 'allowed', 'to be not implemented for context', '#cyan', `"${normal.context}"`);
}
resolve();
}
}
};
const syncContexts = function(normal, resolve, reject) {
const normals = _splitNormals(normal);
const outputs = {};
const syncContext = function(_normals, index, _resolve, _reject) {
const _normal = _normals[index];
const ok = function(output) {
outputs[_normal.context] = output || {};
syncContext(_normals, index + 1, _resolve, _reject);
};
const ko = function(err) {
if (err && err.keep) {
logger.warn('#green', 'stepper', 'Step error (flow continues)', '#yellow', 'ERROR', (err.error || err));
syncContext(_normals, index + 1, _resolve, _reject);
} else {
if (!err.notBuilt) {
logger.error('#green', 'stepper', 'Step error (flow stopped)', '#red', 'ERROR', (err.error || err));
}
_reject(err);
}
};
if (index < _normals.length) {
execNormal(_normal, ok, ko);
} else {
_resolve(outputs);
}
};
syncContext(normals, 0, resolve, reject);
};
/**
* Execute one step with conditions checks
*
* @param normal step name
*/
const execute = function(normal, resolve, reject) {
syncContexts(normal, resolve, reject);
};
return {
execute: execute
};
};
module.exports = stepper();