serverless/lib/plugins/aws/lib/update-stack.js

160 lines
6.1 KiB
JavaScript

'use strict';
const BbPromise = require('bluebird');
const getS3EndpointForRegion = require('../utils/get-s3-endpoint-for-region');
const { log, progress } = require('@serverless/utils/log');
const isChangeSetWithoutChanges = require('../utils/is-change-set-without-changes');
const NO_UPDATE_MESSAGE = 'No updates are to be performed.';
module.exports = {
async createFallback() {
this.createLater = false;
progress.get('main').notice('Creating CloudFormation stack', { isMainEvent: true });
const stackName = this.provider.naming.getStackName();
const compiledTemplateFileName = this.provider.naming.getCompiledTemplateS3Suffix();
const s3Endpoint = getS3EndpointForRegion(this.provider.getRegion());
const templateUrl = `https://${s3Endpoint}/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`;
let monitorCfData;
if (this.serverless.service.provider.deploymentMethod === 'direct') {
const params = this.getCreateStackParams({
templateUrl,
});
monitorCfData = await this.provider.request('CloudFormation', 'createStack', params);
} else {
const changeSetName = this.provider.naming.getStackChangeSetName();
const createChangeSetParams = this.getCreateChangeSetParams({
changeSetType: 'CREATE',
templateUrl,
});
const executeChangeSetParams = this.getExecuteChangeSetParams();
// Create new change set
this.provider.didCreateService = true;
log.info('Creating new change set');
await this.provider.request('CloudFormation', 'createChangeSet', createChangeSetParams);
// Wait for changeset to be created
log.info('Waiting for new change set to be created');
const changeSetDescription = await this.waitForChangeSetCreation(changeSetName, stackName);
// Check if stack has changes
if (isChangeSetWithoutChanges(changeSetDescription)) {
// Cleanup changeset when it does not include any changes
log.info('Created change set does not include any changes, removing it');
await this.provider.request('CloudFormation', 'deleteChangeSet', {
StackName: stackName,
ChangeSetName: changeSetName,
});
this.serverless.service.provider.deploymentWithEmptyChangeSet = true;
return false;
}
log.info('Executing created change set');
await this.provider.request('CloudFormation', 'executeChangeSet', executeChangeSetParams);
monitorCfData = changeSetDescription;
}
await this.monitorStack('create', monitorCfData);
return true;
},
async update() {
const compiledTemplateFileName = this.provider.naming.getCompiledTemplateS3Suffix();
const s3Endpoint = getS3EndpointForRegion(this.provider.getRegion());
const templateUrl = `https://${s3Endpoint}/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`;
const stackName = this.provider.naming.getStackName();
let monitorCfData;
if (this.serverless.service.provider.deploymentMethod === 'direct') {
const params = this.getUpdateStackParams({ templateUrl });
try {
monitorCfData = await this.provider.request('CloudFormation', 'updateStack', params);
} catch (e) {
if (e.message.includes(NO_UPDATE_MESSAGE)) {
return false;
}
throw e;
}
} else {
const changeSetName = this.provider.naming.getStackChangeSetName();
const createChangeSetParams = this.getCreateChangeSetParams({
changeSetType: 'UPDATE',
templateUrl,
});
const executeChangeSetParams = this.getExecuteChangeSetParams();
// Ensure that previous change set has been removed
await this.provider.request('CloudFormation', 'deleteChangeSet', {
StackName: stackName,
ChangeSetName: changeSetName,
});
// Create new change set
log.info('Creating new change set');
await this.provider.request('CloudFormation', 'createChangeSet', createChangeSetParams);
// Wait for changeset to be created
log.info('Waiting for new change set to be created');
const changeSetDescription = await this.waitForChangeSetCreation(changeSetName, stackName);
// Check if stack has changes
if (isChangeSetWithoutChanges(changeSetDescription)) {
// Cleanup changeset when it does not include any changes
log.info('Created change set does not include any changes, removing it');
await this.provider.request('CloudFormation', 'deleteChangeSet', {
StackName: stackName,
ChangeSetName: changeSetName,
});
this.serverless.service.provider.deploymentWithEmptyChangeSet = true;
return false;
}
log.info('Executing created change set');
await this.provider.request('CloudFormation', 'executeChangeSet', executeChangeSetParams);
monitorCfData = changeSetDescription;
}
await this.monitorStack('update', monitorCfData);
// Policy must have at least one statement, otherwise no updates would be possible at all
// Stack policy must be set after change set has been executed,
// as it might reference resources newly added in that change set.
// Applied only for ChangeSet deployments which is a default method
if (
(!this.serverless.service.provider.deploymentMethod ||
this.serverless.service.provider.deploymentMethod === 'changesets') &&
this.serverless.service.provider.stackPolicy &&
Object.keys(this.serverless.service.provider.stackPolicy).length
) {
log.info('Setting stack policy');
const stackPolicyBody = JSON.stringify({
Statement: this.serverless.service.provider.stackPolicy,
});
await this.provider.request('CloudFormation', 'setStackPolicy', {
StackName: stackName,
StackPolicyBody: stackPolicyBody,
});
}
return true;
},
async updateStack() {
return BbPromise.bind(this).then(() => {
if (this.createLater) {
return BbPromise.bind(this).then(this.createFallback);
}
return BbPromise.bind(this).then(this.update);
});
},
};