Skip to content

Commit

Permalink
Merge pull request serverless#4132 from medikoo/fix-delete-failed-dis…
Browse files Browse the repository at this point in the history
…covery

Fix end of failed deletion discovery in AWS stack monitor
  • Loading branch information
pmuens authored Aug 24, 2017
2 parents 21dd400 + dec2e14 commit 380a6c1
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 6 deletions.
7 changes: 4 additions & 3 deletions lib/plugins/aws/lib/monitorStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,13 @@ module.exports = {
// Handle stack create/update/delete failures
if ((stackLatestError && !this.options.verbose)
|| (stackStatus
&& stackStatus.endsWith('ROLLBACK_COMPLETE')
&& (stackStatus.endsWith('ROLLBACK_COMPLETE')
|| stackStatus === 'DELETE_FAILED')
&& this.options.verbose)) {
// empty console.log for a prettier output
if (!this.options.verbose) this.serverless.cli.consoleLog('');
this.serverless.cli.log('Deployment failed!');
let errorMessage = 'An error occurred while provisioning your stack: ';
this.serverless.cli.log('Operation failed!');
let errorMessage = 'An error occurred: ';
errorMessage += `${stackLatestError.LogicalResourceId} - `;
errorMessage += `${stackLatestError.ResourceStatusReason}.`;
return reject(new this.serverless.classes.Error(errorMessage));
Expand Down
168 changes: 165 additions & 3 deletions lib/plugins/aws/lib/monitorStack.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ describe('monitorStack', () => {
describeStackEventsStub.onCall(3).resolves(updateRollbackComplete);

return awsPlugin.monitorStack('update', cfDataMock, 10).catch((e) => {
let errorMessage = 'An error occurred while provisioning your stack: ';
let errorMessage = 'An error occurred: ';
errorMessage += 'mochaS3 - Bucket already exists.';
expect(e.name).to.be.equal('ServerlessError');
expect(e.message).to.be.equal(errorMessage);
Expand Down Expand Up @@ -628,7 +628,7 @@ describe('monitorStack', () => {
describeStackEventsStub.onCall(3).resolves(updateRollbackFailedEvent);

return awsPlugin.monitorStack('update', cfDataMock, 10).catch((e) => {
let errorMessage = 'An error occurred while provisioning your stack: ';
let errorMessage = 'An error occurred: ';
errorMessage += 'mochaS3 - Bucket already exists.';
expect(e.name).to.be.equal('ServerlessError');
expect(e.message).to.be.equal(errorMessage);
Expand All @@ -647,6 +647,168 @@ describe('monitorStack', () => {
});
});

it('should throw an error and exit immediately if stack status is DELETE_FAILED', () => {
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request');
const cfDataMock = {
StackId: 'new-service-dev',
};
const deleteStartEvent = {
StackEvents: [
{
EventId: '1a2b3c4d',
StackName: 'new-service-dev',
LogicalResourceId: 'new-service-dev',
ResourceType: 'AWS::CloudFormation::Stack',
Timestamp: new Date(),
ResourceStatus: 'DELETE_IN_PROGRESS',
},
],
};
const deleteItemEvent = {
StackEvents: [
{
EventId: '1e2f3g4h',
StackName: 'new-service-dev',
LogicalResourceId: 'mochaLambda',
ResourceType: 'AWS::Lambda::Function',
Timestamp: new Date(),
ResourceStatus: 'DELETE_IN_PROGRESS',
},
],
};
const deleteItemFailedEvent = {
StackEvents: [
{
EventId: '1i2j3k4l',
StackName: 'new-service-dev',
LogicalResourceId: 'mochaLambda',
ResourceType: 'AWS::Lambda::Function',
Timestamp: new Date(),
ResourceStatus: 'DELETE_FAILED',
ResourceStatusReason: 'You are not authorized to perform this operation',
},
],
};
const deleteFailedEvent = {
StackEvents: [
{
EventId: '1m2n3o4p',
StackName: 'new-service-dev',
LogicalResourceId: 'new-service-dev',
ResourceType: 'AWS::CloudFormation::Stack',
Timestamp: new Date(),
ResourceStatus: 'DELETE_FAILED',
},
],
};

describeStackEventsStub.onCall(0).resolves(deleteStartEvent);
describeStackEventsStub.onCall(1).resolves(deleteItemEvent);
describeStackEventsStub.onCall(2).resolves(deleteItemFailedEvent);
describeStackEventsStub.onCall(3).resolves(deleteFailedEvent);

return awsPlugin.monitorStack('removal', cfDataMock, 10).catch((e) => {
let errorMessage = 'An error occurred: ';
errorMessage += 'mochaLambda - You are not authorized to perform this operation.';
expect(e.name).to.be.equal('ServerlessError');
expect(e.message).to.be.equal(errorMessage);
// callCount is 2 because Serverless will immediately exits and shows the error
expect(describeStackEventsStub.callCount).to.be.equal(3);
expect(describeStackEventsStub.calledWithExactly(
'CloudFormation',
'describeStackEvents',
{
StackName: cfDataMock.StackId,
},
awsPlugin.options.stage,
awsPlugin.options.region
)).to.be.equal(true);
awsPlugin.provider.request.restore();
});
});

it('should throw an error if stack status is DELETE_FAILED and should output all ' +
'stack events information with the --verbose option', () => {
awsPlugin.options.verbose = true;
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request');
const cfDataMock = {
StackId: 'new-service-dev',
};
const deleteStartEvent = {
StackEvents: [
{
EventId: '1a2b3c4d',
StackName: 'new-service-dev',
LogicalResourceId: 'new-service-dev',
ResourceType: 'AWS::CloudFormation::Stack',
Timestamp: new Date(),
ResourceStatus: 'DELETE_IN_PROGRESS',
},
],
};
const deleteItemEvent = {
StackEvents: [
{
EventId: '1e2f3g4h',
StackName: 'new-service-dev',
LogicalResourceId: 'mochaLambda',
ResourceType: 'AWS::Lambda::Function',
Timestamp: new Date(),
ResourceStatus: 'DELETE_IN_PROGRESS',
},
],
};
const deleteItemFailedEvent = {
StackEvents: [
{
EventId: '1i2j3k4l',
StackName: 'new-service-dev',
LogicalResourceId: 'mochaLambda',
ResourceType: 'AWS::Lambda::Function',
Timestamp: new Date(),
ResourceStatus: 'DELETE_FAILED',
ResourceStatusReason: 'You are not authorized to perform this operation',
},
],
};
const deleteFailedEvent = {
StackEvents: [
{
EventId: '1m2n3o4p',
StackName: 'new-service-dev',
LogicalResourceId: 'new-service-dev',
ResourceType: 'AWS::CloudFormation::Stack',
Timestamp: new Date(),
ResourceStatus: 'DELETE_FAILED',
},
],
};

describeStackEventsStub.onCall(0).resolves(deleteStartEvent);
describeStackEventsStub.onCall(1).resolves(deleteItemEvent);
describeStackEventsStub.onCall(2).resolves(deleteItemFailedEvent);
describeStackEventsStub.onCall(3).resolves(deleteFailedEvent);

return awsPlugin.monitorStack('removal', cfDataMock, 10).catch((e) => {
let errorMessage = 'An error occurred: ';
errorMessage += 'mochaLambda - You are not authorized to perform this operation.';
expect(e.name).to.be.equal('ServerlessError');
expect(e.message).to.be.equal(errorMessage);
// callCount is 2 because Serverless will immediately exits and shows the error
expect(describeStackEventsStub.callCount).to.be.equal(4);
expect(describeStackEventsStub.calledWithExactly(
'CloudFormation',
'describeStackEvents',
{
StackName: cfDataMock.StackId,
},
awsPlugin.options.stage,
awsPlugin.options.region
)).to.be.equal(true);
awsPlugin.provider.request.restore();
});
});

it('should record an error and fail if status is UPDATE_ROLLBACK_IN_PROGRESS', () => {
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request');
const cfDataMock = {
Expand Down Expand Up @@ -692,7 +854,7 @@ describe('monitorStack', () => {
describeStackEventsStub.onCall(2).resolves(updateRollbackCompleteEvent);

return awsPlugin.monitorStack('update', cfDataMock, 10).catch((e) => {
let errorMessage = 'An error occurred while provisioning your stack: ';
let errorMessage = 'An error occurred: ';
errorMessage += 'mocha - Export is in use.';
expect(e.name).to.be.equal('ServerlessError');
expect(e.message).to.be.equal(errorMessage);
Expand Down

0 comments on commit 380a6c1

Please sign in to comment.