forked from serverless/serverless
/
ensure-valid-bucket-exists.js
156 lines (135 loc) · 5.51 KB
/
ensure-valid-bucket-exists.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
'use strict';
const ServerlessError = require('../../../../serverless-error');
const { log, progress } = require('@serverless/utils/log');
const jsyaml = require('js-yaml');
const mainProgress = progress.get('main');
module.exports = {
async ensureValidBucketExists() {
// Ensure to set bucket name if it can be resolved
// Result of this operation will determine how further validation will be performed
try {
await this.setBucketName();
} catch (err) {
// If there is a validation error with expected message, it means that logical resource for
// S3 bucket does not exist and we want to proceed with handling that situation
if (
err.providerError.code !== 'ValidationError' ||
!err.message.includes('does not exist for stack')
) {
throw err;
}
}
// Validate that custom deployment bucket exists and has proper location
if (this.serverless.service.provider.deploymentBucket) {
let result;
try {
result = await this.provider.request('S3', 'getBucketLocation', {
Bucket: this.bucketName,
});
} catch (err) {
throw new ServerlessError(
`Could not locate deployment bucket: "${this.bucketName}". Error: ${err.message}`,
'DEPLOYMENT_BUCKET_NOT_FOUND'
);
}
if (result.LocationConstraint === '') result.LocationConstraint = 'us-east-1';
if (result.LocationConstraint === 'EU') result.LocationConstraint = 'eu-west-1';
if (result.LocationConstraint !== this.provider.getRegion()) {
throw new ServerlessError(
'Deployment bucket is not in the same region as the lambda function',
'DEPLOYMENT_BUCKET_INVALID_REGION'
);
}
// If above is satisfied, then custom S3 bucket is valid
return;
}
// If bucket name is set, it means it's defined as a part of CloudFormation template (custom bucket case was handled by logic above)
if (this.bucketName) {
if (!(await this.checkIfBucketExists(this.bucketName))) {
// It means that bucket was removed manually but is still a part of the CloudFormation stack, we cannot manually fix it
throw new ServerlessError(
'Deployment bucket has been removed manually. Please recreate it or remove your service and attempt to deploy it again',
'DEPLOYMENT_BUCKET_REMOVED_MANUALLY'
);
}
return;
}
log.info(
'Deployment bucket not found. Updating stack to include deployment bucket definition.'
);
const stackName = this.provider.naming.getStackName();
const changeSetName = this.provider.naming.getStackChangeSetName();
// This is situation where the bucket is not defined in the template at all
// It covers the case where someone was using custom deployment bucket
// but removed that setting from the configuration
mainProgress.notice('Ensuring that deployment bucket exists', { isMainEvent: true });
const getTemplateResult = await this.provider.request('CloudFormation', 'getTemplate', {
StackName: stackName,
TemplateStage: 'Original',
});
let templateBody;
if (getTemplateResult.TemplateBody) {
try {
templateBody = JSON.parse(getTemplateResult.TemplateBody);
} catch (error) {
try {
templateBody = jsyaml.load(getTemplateResult.TemplateBody);
} catch (error2) {
throw new ServerlessError(
'Could not parse CloudFormation template',
'CLOUDFORMATION_TEMPLATE_PARSE_FAILED'
);
}
}
} else {
templateBody = {};
}
if (!templateBody.Resources) {
templateBody.Resources = {};
}
if (!templateBody.Outputs) {
templateBody.Outputs = {};
}
Object.assign(
templateBody.Resources,
this.serverless.service.provider.coreCloudFormationTemplate.Resources
);
Object.assign(
templateBody.Outputs,
this.serverless.service.provider.coreCloudFormationTemplate.Outputs
);
let monitorCfData;
if (this.serverless.service.provider.deploymentMethod === 'direct') {
const params = this.getUpdateStackParams({ templateBody });
monitorCfData = await this.provider.request('CloudFormation', 'updateStack', params);
} else {
const createChangeSetParams = this.getCreateChangeSetParams({
changeSetType: 'UPDATE',
templateBody,
});
const executeChangeSetParams = this.getExecuteChangeSetParams();
// Ensure that previous change set has been removed
await this.provider.request('CloudFormation', 'deleteChangeSet', {
StackName: stackName,
ChangeSetName: changeSetName,
});
log.info('Creating new change set.');
// Create new change set
const changeSet = await this.provider.request(
'CloudFormation',
'createChangeSet',
createChangeSetParams
);
// Wait for changeset to be created
log.info('Waiting for new change set to be created.');
await this.waitForChangeSetCreation(changeSetName, stackName);
// We are not checking if change set has any changes here because we already know that there was no deployment bucket
// that needs to be created as a part of change set
// If that would not be the case, that means we have a bug in the logic above
await this.provider.request('CloudFormation', 'executeChangeSet', executeChangeSetParams);
monitorCfData = changeSet;
}
await this.monitorStack('update', monitorCfData);
await this.setBucketName();
},
};