serverless/lib/plugins/aws/lib/monitorStack.js
2016-10-19 10:35:34 +02:00

111 lines
4.6 KiB
JavaScript

'use strict';
const BbPromise = require('bluebird');
const async = require('async');
const chalk = require('chalk');
module.exports = {
monitorStack(action, cfData, frequency) {
// Skip monitoring if a deployment should not be performed
if (this.options.noDeploy) return BbPromise.bind(this).then(BbPromise.resolve());
// Skip monitoring if stack was already created
if (cfData === 'alreadyCreated') return BbPromise.bind(this).then(BbPromise.resolve());
// Monitor stack creation/update/removal
const validStatuses = [
'CREATE_COMPLETE',
'UPDATE_COMPLETE',
'DELETE_COMPLETE',
];
const loggedEvents = [];
const monitoredSince = new Date();
monitoredSince.setSeconds(monitoredSince.getSeconds() - 5);
let stackStatus = null;
let stackLatestError = null;
this.serverless.cli.log(`Checking Stack ${action} progress…`);
return new BbPromise((resolve, reject) => {
async.whilst(
() => (validStatuses.indexOf(stackStatus) === -1),
(callback) => {
setTimeout(() => {
const params = {
StackName: cfData.StackId,
};
return this.provider.request('CloudFormation',
'describeStackEvents',
params,
this.options.stage,
this.options.region)
.then((data) => {
// Loop through stack events
data.StackEvents.reverse().forEach((event) => {
const eventInRange = (monitoredSince < event.Timestamp);
const eventNotLogged = (loggedEvents.indexOf(event.EventId) === -1);
let eventStatus = event.ResourceStatus;
if (eventInRange && eventNotLogged) {
// Keep track of stack status
if (event.ResourceType === 'AWS::CloudFormation::Stack') {
stackStatus = eventStatus;
}
// Keep track of first failed event
if (eventStatus.endsWith('FAILED') && stackLatestError === null) {
stackLatestError = event;
}
// Log stack events
if (this.options.verbose) {
if (eventStatus.endsWith('FAILED')) {
eventStatus = chalk.red(eventStatus);
} else if (eventStatus.endsWith('PROGRESS')) {
eventStatus = chalk.yellow(eventStatus);
} else if (eventStatus.endsWith('COMPLETE')) {
eventStatus = chalk.green(eventStatus);
}
let eventLog = `CloudFormation - ${eventStatus} - `;
eventLog += `${event.ResourceType} - `;
eventLog += `${event.LogicalResourceId}`;
this.serverless.cli.consoleLog(eventLog);
} else {
this.serverless.cli.printDot();
}
// Prepare for next monitoring action
loggedEvents.push(event.EventId);
}
});
// Handle stack create/update/delete failures
if ((stackLatestError && !this.options.verbose)
|| (stackStatus.endsWith('ROLLBACK_COMPLETE') && this.options.verbose)) {
this.serverless.cli.log('Deployment failed!');
let errorMessage = 'An error occurred while provisioning your stack: ';
errorMessage += `${stackLatestError.LogicalResourceId} - `;
errorMessage += `${stackLatestError.ResourceStatusReason}.`;
return reject(new this.serverless.classes.Error(errorMessage));
}
// Trigger next monitoring action
return callback();
})
.catch((e) => {
if (action === 'removal' && e.message.endsWith('does not exist')) {
// empty console.log for a prettier output
if (!this.options.verbose) this.serverless.cli.consoleLog('');
this.serverless.cli.log(`Stack ${action} finished…`);
resolve('DELETE_COMPLETE');
} else {
reject(new this.serverless.classes.Error(e.message));
}
});
}, frequency || 5000);
},
() => {
// empty console.log for a prettier output
if (!this.options.verbose) this.serverless.cli.consoleLog('');
this.serverless.cli.log(`Stack ${action} finished…`);
resolve(stackStatus);
});
});
},
};