mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
1056 lines
35 KiB
JavaScript
1056 lines
35 KiB
JavaScript
'use strict'
|
|
|
|
const chai = require('chai')
|
|
const sinon = require('sinon')
|
|
const Serverless = require('../../../../../../lib/serverless')
|
|
const AwsProvider = require('../../../../../../lib/plugins/aws/provider')
|
|
const CLI = require('../../../../../../lib/classes/cli')
|
|
const monitorStack = require('../../../../../../lib/plugins/aws/lib/monitor-stack')
|
|
|
|
chai.use(require('chai-as-promised'))
|
|
|
|
const { expect } = chai
|
|
|
|
describe('monitorStack', () => {
|
|
const serverless = new Serverless({ commands: [], options: {} })
|
|
const awsPlugin = {}
|
|
|
|
beforeEach(() => {
|
|
const options = {
|
|
stage: 'dev',
|
|
region: 'us-east-1',
|
|
}
|
|
awsPlugin.serverless = serverless
|
|
awsPlugin.provider = new AwsProvider(serverless, options)
|
|
awsPlugin.serverless.cli = new CLI(serverless)
|
|
awsPlugin.options = options
|
|
|
|
Object.assign(awsPlugin, monitorStack)
|
|
})
|
|
|
|
describe('#monitorStack()', () => {
|
|
it('should skip monitoring if the stack was already created', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
|
|
return awsPlugin
|
|
.monitorStack('update', 'alreadyCreated', { frequency: 10 })
|
|
.then(() => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(0)
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should keep monitoring until CREATE_COMPLETE stack status', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateFinishedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(updateFinishedEvent)
|
|
|
|
return awsPlugin
|
|
.monitorStack('create', cfDataMock, { frequency: 10 })
|
|
.then((stackStatus) => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(2)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
expect(stackStatus).to.be.equal('CREATE_COMPLETE')
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should keep monitoring until UPDATE_COMPLETE stack status', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateFinishedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(updateFinishedEvent)
|
|
|
|
return awsPlugin
|
|
.monitorStack('update', cfDataMock, { frequency: 10 })
|
|
.then((stackStatus) => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(2)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
expect(stackStatus).to.be.equal('UPDATE_COMPLETE')
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should keep monitoring until DELETE_COMPLETE stack status', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateFinishedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(updateFinishedEvent)
|
|
|
|
return awsPlugin
|
|
.monitorStack('delete', cfDataMock, { frequency: 10 })
|
|
.then((stackStatus) => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(2)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
expect(stackStatus).to.be.equal('DELETE_COMPLETE')
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should not stop monitoring on CREATE_COMPLETE nested stack status', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const nestedStackEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4z',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'nested-stack-name',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
const updateFinishedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(nestedStackEvent)
|
|
describeStackEventsStub.onCall(2).resolves(updateFinishedEvent)
|
|
|
|
return awsPlugin
|
|
.monitorStack('create', cfDataMock, { frequency: 10 })
|
|
.then((stackStatus) => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(3)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
expect(stackStatus).to.be.equal('CREATE_COMPLETE')
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should not stop monitoring on UPDATE_COMPLETE nested stack status', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const nestedStackEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4z',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'nested-stack-name',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
const updateFinishedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(nestedStackEvent)
|
|
describeStackEventsStub.onCall(2).resolves(updateFinishedEvent)
|
|
|
|
return awsPlugin
|
|
.monitorStack('update', cfDataMock, { frequency: 10 })
|
|
.then((stackStatus) => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(3)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
expect(stackStatus).to.be.equal('UPDATE_COMPLETE')
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should not stop monitoring on DELETE_COMPLETE nested stack status', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const nestedStackEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4z',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'nested-stack-name',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
const updateFinishedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(nestedStackEvent)
|
|
describeStackEventsStub.onCall(2).resolves(updateFinishedEvent)
|
|
|
|
return awsPlugin
|
|
.monitorStack('delete', cfDataMock, { frequency: 10 })
|
|
.then((stackStatus) => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(3)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
expect(stackStatus).to.be.equal('DELETE_COMPLETE')
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should keep monitoring until DELETE_COMPLETE or stack not found catch', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const stackNotFoundError = {
|
|
message: 'Stack new-service-dev does not exist',
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).rejects(stackNotFoundError)
|
|
|
|
return awsPlugin
|
|
.monitorStack('delete', cfDataMock, { frequency: 10 })
|
|
.then((stackStatus) => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(2)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
expect(stackStatus).to.be.equal('DELETE_COMPLETE')
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('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 updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateFailedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'mochaS3',
|
|
ResourceType: 'AWS::S3::Bucket',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_FAILED',
|
|
ResourceStatusReason: 'Bucket already exists',
|
|
},
|
|
],
|
|
}
|
|
const updateRollbackEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1i2j3k4l',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_ROLLBACK_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateRollbackComplete = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1m2n3o4p',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'ROLLBACK_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(updateFailedEvent)
|
|
describeStackEventsStub.onCall(2).resolves(updateRollbackEvent)
|
|
describeStackEventsStub.onCall(3).resolves(updateRollbackComplete)
|
|
|
|
return awsPlugin
|
|
.monitorStack('update', cfDataMock, { frequency: 10 })
|
|
.catch((e) => {
|
|
let errorMessage = 'An error occurred: '
|
|
errorMessage += 'mochaS3 - Bucket already exists.'
|
|
if (e.name !== 'ServerlessError') throw e
|
|
expect(e.name).to.be.equal('ServerlessError')
|
|
expect(e.message).to.be.equal(errorMessage)
|
|
expect(describeStackEventsStub.callCount).to.be.equal(4)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should keep monitoring when 1st ResourceType is not "AWS::CloudFormation::Stack"', async () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const firstNoStackResourceTypeEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'somebucket',
|
|
ResourceType: 'AWS::S3::Bucket',
|
|
Timestamp: new Date(),
|
|
},
|
|
],
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateComplete = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1m2n3o4p',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
describeStackEventsStub.onCall(0).resolves(firstNoStackResourceTypeEvent)
|
|
describeStackEventsStub.onCall(1).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(2).resolves(updateComplete)
|
|
|
|
return awsPlugin
|
|
.monitorStack('update', cfDataMock, { frequency: 10 })
|
|
.then(() => {
|
|
expect(describeStackEventsStub.callCount).to.be.equal(3)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should catch describeStackEvents error if stack was not in deleting state', () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const failedDescribeStackEvents = {
|
|
message: 'Something went wrong.',
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).rejects(failedDescribeStackEvents)
|
|
|
|
return awsPlugin
|
|
.monitorStack('update', cfDataMock, { frequency: 10 })
|
|
.catch((e) => {
|
|
expect(e.message).to.be.equal('Something went wrong.')
|
|
expect(describeStackEventsStub.callCount).to.be.equal(1)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should throw an error and exit immediately if stack status is *_FAILED', () => {
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
LogicalResourceId: 'mocha',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateFailedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
LogicalResourceId: 'mochaS3',
|
|
ResourceType: 'S3::Bucket',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_FAILED',
|
|
ResourceStatusReason: 'Bucket already exists',
|
|
},
|
|
],
|
|
}
|
|
const updateRollbackEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1i2j3k4l',
|
|
LogicalResourceId: 'mocha',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_ROLLBACK_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateRollbackFailedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1m2n3o4p',
|
|
LogicalResourceId: 'mocha',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_ROLLBACK_FAILED',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(updateFailedEvent)
|
|
describeStackEventsStub.onCall(2).resolves(updateRollbackEvent)
|
|
describeStackEventsStub.onCall(3).resolves(updateRollbackFailedEvent)
|
|
|
|
return awsPlugin
|
|
.monitorStack('update', cfDataMock, { frequency: 10 })
|
|
.catch((e) => {
|
|
let errorMessage = 'An error occurred: '
|
|
errorMessage += 'mochaS3 - Bucket already exists.'
|
|
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(2)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
|
|
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('delete', cfDataMock, { frequency: 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,
|
|
},
|
|
),
|
|
).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('delete', cfDataMock, { frequency: 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,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
},
|
|
)
|
|
|
|
it(
|
|
'should throw an error if stack status is DELETE_COMPLETE and should output all ' +
|
|
'stack events information with the --verbose option',
|
|
async () => {
|
|
awsPlugin.options.verbose = true
|
|
const describeStackEventsStub = sinon.stub(
|
|
awsPlugin.provider,
|
|
'request',
|
|
)
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const createStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const createItemFailedEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1m2n3o4p',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_COMPLETE',
|
|
},
|
|
{
|
|
EventId: '1i2j3k4l',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'myBucket',
|
|
ResourceType: 'AWS::S3::Bucket',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_IN_PROGRESS',
|
|
},
|
|
{
|
|
EventId: '1a2b3c4e',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_IN_PROGRESS',
|
|
},
|
|
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'myBucket',
|
|
ResourceType: 'AWS::S3::Bucket',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_FAILED',
|
|
ResourceStatusReason: 'Invalid Property for X',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(createStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(createItemFailedEvent)
|
|
|
|
await expect(
|
|
awsPlugin.monitorStack('create', cfDataMock, { frequency: 10 }),
|
|
).to.eventually.be.rejectedWith('myBucket - Invalid Property for X.')
|
|
awsPlugin.provider.request.restore()
|
|
},
|
|
)
|
|
|
|
it('should resolve properly first stack event (when CREATE fails and is followed with DELETE)', async () => {
|
|
awsPlugin.options.verbose = true
|
|
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request')
|
|
const cfDataMock = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const createStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1m2n3o4p',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_COMPLETE',
|
|
},
|
|
{
|
|
EventId: '1i2j3k4l',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'myBucket',
|
|
ResourceType: 'AWS::S3::Bucket',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_IN_PROGRESS',
|
|
},
|
|
{
|
|
EventId: '1a2b3c4e',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'DELETE_IN_PROGRESS',
|
|
},
|
|
|
|
{
|
|
EventId: '1e2f3g4h',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'myBucket',
|
|
ResourceType: 'AWS::S3::Bucket',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_FAILED',
|
|
ResourceStatusReason: 'Invalid Property for X',
|
|
},
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
StackName: 'new-service-dev',
|
|
LogicalResourceId: 'new-service-dev',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'CREATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(createStartEvent)
|
|
|
|
await expect(
|
|
awsPlugin.monitorStack('create', cfDataMock, { frequency: 10 }),
|
|
).to.eventually.be.rejectedWith('myBucket - Invalid Property for X.')
|
|
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 = {
|
|
StackId: 'new-service-dev',
|
|
}
|
|
const updateStartEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1a2b3c4d',
|
|
LogicalResourceId: 'mocha',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateRollbackEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1i2j3k4l',
|
|
LogicalResourceId: 'mocha',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_ROLLBACK_IN_PROGRESS',
|
|
},
|
|
],
|
|
}
|
|
const updateRollbackCompleteEvent = {
|
|
StackEvents: [
|
|
{
|
|
EventId: '1m2n3o4p',
|
|
LogicalResourceId: 'mocha',
|
|
ResourceType: 'AWS::CloudFormation::Stack',
|
|
Timestamp: new Date(),
|
|
ResourceStatus: 'UPDATE_ROLLBACK_COMPLETE',
|
|
},
|
|
],
|
|
}
|
|
|
|
describeStackEventsStub.onCall(0).resolves(updateStartEvent)
|
|
describeStackEventsStub.onCall(1).resolves(updateRollbackEvent)
|
|
describeStackEventsStub.onCall(2).resolves(updateRollbackCompleteEvent)
|
|
|
|
return awsPlugin
|
|
.monitorStack('update', cfDataMock, { frequency: 10 })
|
|
.catch((e) => {
|
|
let errorMessage = 'An error occurred: '
|
|
errorMessage += 'mocha - UPDATE_ROLLBACK_IN_PROGRESS.'
|
|
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(2)
|
|
expect(
|
|
describeStackEventsStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackEvents',
|
|
{
|
|
StackName: cfDataMock.StackId,
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
awsPlugin.provider.request.restore()
|
|
})
|
|
})
|
|
})
|
|
})
|