serverless/lib/plugins/aws/lib/update-stack.js
Max Marze d35327ea36
feat: make direct deployments the default (#12659)
* feat: make direct deployments the default

* docs: update docs to specify that direct deployments are the default
2024-07-02 15:21:51 -04:00

199 lines
6.5 KiB
JavaScript

import getS3EndpointForRegion from '../utils/get-s3-endpoint-for-region.js'
import utils from '@serverlessinc/sf-core/src/utils.js'
import isChangeSetWithoutChanges from '../utils/is-change-set-without-changes.js'
const { log, progress } = utils
const NO_UPDATE_MESSAGE = 'No updates are to be performed.'
export default {
async createFallback() {
this.createLater = false
progress
.get('main')
.notice('Creating AWS 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 ||
this.serverless.service.provider.deploymentMethod === 'direct'
) {
const params = this.getCreateStackParams({
templateUrl,
})
monitorCfData = await this.provider.request(
'CloudFormation',
'createStack',
params,
)
} else {
// Change-set based deployment
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 ||
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 {
// Change-set based deployment
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 === '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 Promise.resolve(this).then(() => {
if (this.createLater) {
return Promise.resolve(this).then(() => this.createFallback())
}
return Promise.resolve(this).then(() => this.update())
})
},
}