mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
135 lines
5.9 KiB
JavaScript
135 lines
5.9 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 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 = [];
|
|
|
|
let monitoredSince = null;
|
|
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) => {
|
|
const stackEvents = data.StackEvents;
|
|
|
|
// look through all the stack events and find the first relevant
|
|
// event which is a "Stack" event and has a CREATE, UPDATE or DELETE status
|
|
const firstRelevantEvent = stackEvents.find((event) => {
|
|
const isStack = 'AWS::CloudFormation::Stack';
|
|
const updateIsInProgress = 'UPDATE_IN_PROGRESS';
|
|
const createIsInProgress = 'CREATE_IN_PROGRESS';
|
|
const deleteIsInProgress = 'DELETE_IN_PROGRESS';
|
|
|
|
return event.ResourceType === isStack
|
|
&& (event.ResourceStatus === updateIsInProgress
|
|
|| event.ResourceStatus === createIsInProgress
|
|
|| event.ResourceStatus === deleteIsInProgress);
|
|
});
|
|
|
|
// set the date some time before the first found
|
|
// stack event of recently issued stack modification
|
|
if (firstRelevantEvent) {
|
|
const eventDate = new Date(firstRelevantEvent.Timestamp);
|
|
const updatedDate = eventDate.setSeconds(eventDate.getSeconds() - 5);
|
|
monitoredSince = new Date(updatedDate);
|
|
}
|
|
|
|
// Loop through stack events
|
|
stackEvents.reverse().forEach((event) => {
|
|
const eventInRange = (monitoredSince <= event.Timestamp);
|
|
const eventNotLogged = (loggedEvents.indexOf(event.EventId) === -1);
|
|
let eventStatus = event.ResourceStatus || null;
|
|
if (eventInRange && eventNotLogged) {
|
|
// Keep track of stack status
|
|
if (event.ResourceType === 'AWS::CloudFormation::Stack'
|
|
&& event.StackName === event.LogicalResourceId) {
|
|
stackStatus = eventStatus;
|
|
}
|
|
// Keep track of first failed event
|
|
if (eventStatus
|
|
&& eventStatus.endsWith('FAILED') && stackLatestError === null) {
|
|
stackLatestError = event;
|
|
}
|
|
// Log stack events
|
|
if (this.options.verbose) {
|
|
if (eventStatus && eventStatus.endsWith('FAILED')) {
|
|
eventStatus = chalk.red(eventStatus);
|
|
} else if (eventStatus && eventStatus.endsWith('PROGRESS')) {
|
|
eventStatus = chalk.yellow(eventStatus);
|
|
} else if (eventStatus && 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
|
|
&& 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);
|
|
});
|
|
});
|
|
},
|
|
};
|