carlin 1.19.11 → 1.19.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +232 -0
- package/dist/config.js +10 -0
- package/dist/deploy/addDefaults.cloudFormation.js +138 -0
- package/dist/deploy/baseStack/command.js +9 -0
- package/dist/deploy/baseStack/config.js +30 -0
- package/dist/deploy/baseStack/deployBaseStack.js +59 -0
- package/dist/deploy/baseStack/getBaseStackResource.js +26 -0
- package/dist/deploy/baseStack/getBucket.template.js +44 -0
- package/dist/deploy/baseStack/getLambdaImageBuilder.template.js +186 -0
- package/dist/deploy/baseStack/getLambdaLayerBuilder.template.js +140 -0
- package/dist/deploy/baseStack/getVpc.template.js +169 -0
- package/dist/deploy/cicd/cicd.template.js +922 -0
- package/dist/deploy/cicd/command.js +27 -0
- package/dist/deploy/cicd/command.options.js +71 -0
- package/dist/deploy/cicd/config.js +8 -0
- package/dist/deploy/cicd/deployCicd.js +93 -0
- package/dist/deploy/cicd/ecsTaskReportCommand.js +51 -0
- package/dist/deploy/cicd/getCicdStackName.js +11 -0
- package/dist/deploy/cicd/getTriggerPipelineObjectKey.js +11 -0
- package/dist/deploy/cicd/lambdas/cicdApiV1.handler.js +124 -0
- package/dist/deploy/cicd/lambdas/ecsTaskReport.handler.js +126 -0
- package/dist/deploy/cicd/lambdas/executeTasks.js +67 -0
- package/dist/deploy/cicd/lambdas/getProcessEnvVariable.js +10 -0
- package/dist/deploy/cicd/lambdas/githubWebhooksApiV1.handler.js +146 -0
- package/dist/deploy/cicd/lambdas/imageUpdaterSchedule.handler.js +44 -0
- package/dist/deploy/cicd/lambdas/index.js +13 -0
- package/dist/deploy/cicd/lambdas/pipelines.handler.js +134 -0
- package/dist/deploy/cicd/lambdas/putApprovalResultManualTask.js +52 -0
- package/dist/deploy/cicd/lambdas/shConditionalCommands.js +30 -0
- package/dist/deploy/cicd/pipelines.js +76 -0
- package/dist/deploy/cicd/readSSHKey.js +10 -0
- package/dist/deploy/cloudFormation.core.js +300 -0
- package/dist/deploy/cloudFormation.js +182 -0
- package/dist/deploy/command.js +185 -0
- package/dist/deploy/lambda/buildLambdaSingleFile.js +30 -0
- package/dist/deploy/lambda/deployLambdaCode.js +41 -0
- package/dist/deploy/lambda/deployLambdaLayers.js +34 -0
- package/dist/deploy/lambda/uploadCodeToECR.js +53 -0
- package/dist/deploy/lambda/uploadCodeToS3.js +31 -0
- package/dist/deploy/lambdaLayer/command.js +48 -0
- package/dist/deploy/lambdaLayer/deployLambdaLayer.js +140 -0
- package/dist/deploy/readDockerfile.js +18 -0
- package/dist/deploy/s3.js +165 -0
- package/dist/deploy/stackName.js +78 -0
- package/dist/deploy/staticApp/command.js +79 -0
- package/dist/deploy/staticApp/deployStaticApp.js +64 -0
- package/dist/deploy/staticApp/findDefaultBuildFolder.js +27 -0
- package/dist/deploy/staticApp/getOriginShieldRegion.js +30 -0
- package/dist/deploy/staticApp/getStaticAppBucket.js +19 -0
- package/dist/deploy/staticApp/invalidateCloudFront.js +42 -0
- package/dist/deploy/staticApp/removeOldVersions.js +43 -0
- package/dist/deploy/staticApp/staticApp.template.js +303 -0
- package/dist/deploy/staticApp/uploadBuiltAppToS3.js +27 -0
- package/dist/deploy/utils.js +29 -0
- package/dist/generateEnv/generateEnv.js +46 -0
- package/dist/generateEnv/generateEnvCommand.js +9 -0
- package/dist/index.js +5 -0
- package/dist/utils/addGroupToOptions.js +11 -0
- package/dist/utils/cloudFormationTemplate.js +132 -0
- package/dist/utils/codeBuild.js +50 -0
- package/dist/utils/environmentVariables.js +11 -0
- package/dist/utils/exec.js +22 -0
- package/dist/utils/formatCode.js +12 -0
- package/dist/utils/getAwsAccountId.js +10 -0
- package/dist/utils/getCurrentBranch.js +33 -0
- package/dist/utils/getEnvironment.js +6 -0
- package/dist/utils/getIamPath.js +6 -0
- package/dist/utils/getProjectName.js +35 -0
- package/dist/utils/index.js +17 -0
- package/dist/utils/packageJson.js +25 -0
- package/dist/utils/readCloudFormationTemplate.js +33 -0
- package/dist/utils/readObjectFile.js +46 -0
- package/package.json +3 -3
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.destroy = exports.canDestroyStack = exports.deploy = exports.defaultTemplatePaths = exports.enableTerminationProtection = exports.updateStack = exports.createStack = exports.deleteStack = exports.printStackOutputsAfterDeploy = exports.getStackOutput = exports.describeStack = exports.describeStackEvents = exports.doesStackExist = exports.describeStackResource = exports.describeStacks = exports.cloudFormationV2 = exports.cloudFormation = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
const addDefaults_cloudFormation_1 = require("./addDefaults.cloudFormation");
|
|
10
|
+
const s3_1 = require("./s3");
|
|
11
|
+
const stackName_1 = require("./stackName");
|
|
12
|
+
const aws_sdk_1 = tslib_1.__importDefault(require("aws-sdk"));
|
|
13
|
+
const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
|
|
14
|
+
const logPrefix = 'cloudformation';
|
|
15
|
+
npmlog_1.default.addLevel('event', 10000, { fg: 'yellow' });
|
|
16
|
+
npmlog_1.default.addLevel('output', 10000, { fg: 'blue' });
|
|
17
|
+
/**
|
|
18
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html
|
|
19
|
+
*/
|
|
20
|
+
const TEMPLATE_BODY_MAX_SIZE = 51200;
|
|
21
|
+
const isTemplateBodyGreaterThanMaxSize = (template) => Buffer.byteLength(JSON.stringify(template), 'utf8') >= TEMPLATE_BODY_MAX_SIZE;
|
|
22
|
+
/**
|
|
23
|
+
* Update CloudFormation template to base stack bucket.
|
|
24
|
+
* @param input.stackName: CloudFormation stack name.
|
|
25
|
+
* @param input.template: CloudFormation template.
|
|
26
|
+
*/
|
|
27
|
+
const uploadTemplateToBaseStackBucket = async ({ stackName, template, }) => {
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.info({ stackName, template });
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.log('uploadTemplateToBaseStackBucket needs to be implemented.');
|
|
32
|
+
throw new Error();
|
|
33
|
+
// const key = getPepeBucketTemplateKey({ stackName });
|
|
34
|
+
// return uploadFileToS3({
|
|
35
|
+
// bucket: await getPepeBucketName(),
|
|
36
|
+
// contentType: 'application/json',
|
|
37
|
+
// file: Buffer.from(JSON.stringify(template, null, 2)),
|
|
38
|
+
// key,
|
|
39
|
+
// });
|
|
40
|
+
};
|
|
41
|
+
const cloudFormation = () => {
|
|
42
|
+
return new client_cloudformation_1.CloudFormationClient({
|
|
43
|
+
apiVersion: '2010-05-15',
|
|
44
|
+
region: (0, utils_1.getEnvVar)('REGION'),
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
exports.cloudFormation = cloudFormation;
|
|
48
|
+
const cloudFormationV2 = () => {
|
|
49
|
+
return new aws_sdk_1.default.CloudFormation({ apiVersion: '2010-05-15' });
|
|
50
|
+
};
|
|
51
|
+
exports.cloudFormationV2 = cloudFormationV2;
|
|
52
|
+
const describeStacks = async ({ stackName, } = {}) => {
|
|
53
|
+
const { Stacks } = await (0, exports.cloudFormation)().send(new client_cloudformation_1.DescribeStacksCommand({ StackName: stackName }));
|
|
54
|
+
return Stacks;
|
|
55
|
+
};
|
|
56
|
+
exports.describeStacks = describeStacks;
|
|
57
|
+
const describeStackResource = async (input) => {
|
|
58
|
+
return (0, exports.cloudFormation)().send(new client_cloudformation_1.DescribeStackResourceCommand(input));
|
|
59
|
+
};
|
|
60
|
+
exports.describeStackResource = describeStackResource;
|
|
61
|
+
const doesStackExist = async ({ stackName }) => {
|
|
62
|
+
npmlog_1.default.info(logPrefix, `Checking if stack ${stackName} already exists...`);
|
|
63
|
+
try {
|
|
64
|
+
await (0, exports.describeStacks)({ stackName });
|
|
65
|
+
npmlog_1.default.info(logPrefix, `Stack ${stackName} already exists.`);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.Code === 'ValidationError') {
|
|
70
|
+
npmlog_1.default.info(logPrefix, `Stack ${stackName} does not exist.`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
exports.doesStackExist = doesStackExist;
|
|
77
|
+
const describeStackEvents = async ({ stackName, }) => {
|
|
78
|
+
npmlog_1.default.error(logPrefix, 'Stack events:');
|
|
79
|
+
const { StackEvents } = await (0, exports.cloudFormation)().send(new client_cloudformation_1.DescribeStackEventsCommand({ StackName: stackName }));
|
|
80
|
+
const events = (StackEvents || [])
|
|
81
|
+
.filter(({ Timestamp }) => Date.now() - Number(Timestamp) < 10 * 60 * 1000)
|
|
82
|
+
.filter(({ ResourceStatusReason }) => ResourceStatusReason)
|
|
83
|
+
/**
|
|
84
|
+
* Show newer events last.
|
|
85
|
+
*/
|
|
86
|
+
.reverse();
|
|
87
|
+
events.forEach(({ LogicalResourceId, ResourceStatusReason }) => npmlog_1.default.event(LogicalResourceId, ResourceStatusReason));
|
|
88
|
+
return events;
|
|
89
|
+
};
|
|
90
|
+
exports.describeStackEvents = describeStackEvents;
|
|
91
|
+
const describeStack = async ({ stackName }) => {
|
|
92
|
+
const stacks = await (0, exports.describeStacks)({ stackName });
|
|
93
|
+
if (!stacks) {
|
|
94
|
+
throw new Error(`Stack ${stackName} not found and cannot be described.`);
|
|
95
|
+
}
|
|
96
|
+
return stacks[0];
|
|
97
|
+
};
|
|
98
|
+
exports.describeStack = describeStack;
|
|
99
|
+
const getStackOutput = async ({ stackName, outputKey, }) => {
|
|
100
|
+
const { Outputs = [] } = await (0, exports.describeStack)({ stackName });
|
|
101
|
+
const output = Outputs === null || Outputs === void 0 ? void 0 : Outputs.find(({ OutputKey }) => OutputKey === outputKey);
|
|
102
|
+
if (!output) {
|
|
103
|
+
throw new Error(`Output ${outputKey} doesn't exist on ${stackName} stack`);
|
|
104
|
+
}
|
|
105
|
+
return output;
|
|
106
|
+
};
|
|
107
|
+
exports.getStackOutput = getStackOutput;
|
|
108
|
+
const saveEnvironmentOutput = async ({ outputs, }) => {
|
|
109
|
+
const stackName = await (0, stackName_1.getStackName)();
|
|
110
|
+
const envFile = { stackName, outputs };
|
|
111
|
+
const dotCarlinFolderPath = path.join(process.cwd(), '.carlin');
|
|
112
|
+
if (!fs.existsSync(dotCarlinFolderPath)) {
|
|
113
|
+
await fs.promises.mkdir(dotCarlinFolderPath);
|
|
114
|
+
}
|
|
115
|
+
const filePath = path.join(dotCarlinFolderPath, `${stackName}.json`);
|
|
116
|
+
await fs.promises.writeFile(filePath, JSON.stringify(envFile, null, 2));
|
|
117
|
+
};
|
|
118
|
+
const printStackOutputsAfterDeploy = async ({ stackName, }) => {
|
|
119
|
+
const { EnableTerminationProtection, StackName, Outputs = [], } = await (0, exports.describeStack)({ stackName });
|
|
120
|
+
await saveEnvironmentOutput({ outputs: Outputs });
|
|
121
|
+
npmlog_1.default.output('Describe Stack');
|
|
122
|
+
npmlog_1.default.output('StackName', StackName);
|
|
123
|
+
npmlog_1.default.output('EnableTerminationProtection', EnableTerminationProtection);
|
|
124
|
+
Outputs.forEach(({ OutputKey, OutputValue, Description, ExportName }) => {
|
|
125
|
+
npmlog_1.default.output(`${OutputKey}`, [
|
|
126
|
+
'',
|
|
127
|
+
`OutputKey: ${OutputKey}`,
|
|
128
|
+
`OutputValue: ${OutputValue}`,
|
|
129
|
+
`Description: ${Description}`,
|
|
130
|
+
`ExportName: ${ExportName}`,
|
|
131
|
+
'',
|
|
132
|
+
].join('\n'));
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
exports.printStackOutputsAfterDeploy = printStackOutputsAfterDeploy;
|
|
136
|
+
const deleteStack = async ({ stackName }) => {
|
|
137
|
+
npmlog_1.default.info(logPrefix, `Deleting stack ${stackName}...`);
|
|
138
|
+
await (0, exports.cloudFormation)().send(new client_cloudformation_1.DeleteStackCommand({ StackName: stackName }));
|
|
139
|
+
try {
|
|
140
|
+
await (0, exports.cloudFormationV2)()
|
|
141
|
+
.waitFor('stackDeleteComplete', { StackName: stackName })
|
|
142
|
+
.promise();
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
npmlog_1.default.error(logPrefix, `An error occurred when deleting stack ${stackName}.`);
|
|
146
|
+
await (0, exports.describeStackEvents)({ stackName });
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
npmlog_1.default.info(logPrefix, `Stack ${stackName} deleted.`);
|
|
150
|
+
};
|
|
151
|
+
exports.deleteStack = deleteStack;
|
|
152
|
+
const createStack = async ({ params, }) => {
|
|
153
|
+
const { StackName: stackName } = params;
|
|
154
|
+
npmlog_1.default.info(logPrefix, `Creating stack ${stackName}...`);
|
|
155
|
+
await (0, exports.cloudFormation)().send(new client_cloudformation_1.CreateStackCommand(params));
|
|
156
|
+
try {
|
|
157
|
+
await (0, exports.cloudFormationV2)()
|
|
158
|
+
.waitFor('stackCreateComplete', { StackName: stackName })
|
|
159
|
+
.promise();
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
npmlog_1.default.error(logPrefix, `An error occurred when creating stack ${stackName}.`);
|
|
163
|
+
await (0, exports.describeStackEvents)({ stackName });
|
|
164
|
+
await (0, exports.deleteStack)({ stackName });
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
npmlog_1.default.info(logPrefix, `Stack ${stackName} was created.`);
|
|
168
|
+
};
|
|
169
|
+
exports.createStack = createStack;
|
|
170
|
+
const updateStack = async ({ params, }) => {
|
|
171
|
+
const { StackName: stackName } = params;
|
|
172
|
+
npmlog_1.default.info(logPrefix, `Updating stack ${stackName}...`);
|
|
173
|
+
try {
|
|
174
|
+
await (0, exports.cloudFormation)().send(new client_cloudformation_1.UpdateStackCommand(params));
|
|
175
|
+
await (0, exports.cloudFormationV2)()
|
|
176
|
+
.waitFor('stackUpdateComplete', { StackName: stackName })
|
|
177
|
+
.promise();
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error.message === 'No updates are to be performed.') {
|
|
181
|
+
npmlog_1.default.info(logPrefix, error.message);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
npmlog_1.default.error(logPrefix, 'An error occurred when updating stack.');
|
|
185
|
+
await (0, exports.describeStackEvents)({ stackName });
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
npmlog_1.default.info(logPrefix, `Stack ${stackName} was updated.`);
|
|
189
|
+
};
|
|
190
|
+
exports.updateStack = updateStack;
|
|
191
|
+
const enableTerminationProtection = async ({ stackName, }) => {
|
|
192
|
+
npmlog_1.default.info(logPrefix, `Enabling termination protection...`);
|
|
193
|
+
try {
|
|
194
|
+
await (0, exports.cloudFormation)().send(new client_cloudformation_1.UpdateTerminationProtectionCommand({
|
|
195
|
+
EnableTerminationProtection: true,
|
|
196
|
+
StackName: stackName,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
npmlog_1.default.error(logPrefix, 'An error occurred when enabling termination protection');
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
exports.enableTerminationProtection = enableTerminationProtection;
|
|
205
|
+
exports.defaultTemplatePaths = ['ts', 'js', 'yaml', 'yml', 'json'].map((extension) => `src/cloudformation.${extension}`);
|
|
206
|
+
/**
|
|
207
|
+
* 1. Add defaults to CloudFormation template and parameters.
|
|
208
|
+
* 1. Check is CloudFormation template body is greater than max size limit.
|
|
209
|
+
* 1. If is greater, upload to S3 base stack.
|
|
210
|
+
* 1. If stack exists, update the stack, else create a new stack.
|
|
211
|
+
* 1. If `terminationProtection` option is true or `environment` is defined,
|
|
212
|
+
* then stack termination protection will be enabled.
|
|
213
|
+
*/
|
|
214
|
+
const deploy = async ({ terminationProtection = false, ...paramsAndTemplate }) => {
|
|
215
|
+
const { params, template } = await (0, addDefaults_cloudFormation_1.addDefaults)(paramsAndTemplate);
|
|
216
|
+
const stackName = params.StackName;
|
|
217
|
+
delete params.TemplateBody;
|
|
218
|
+
delete params.TemplateURL;
|
|
219
|
+
if (isTemplateBodyGreaterThanMaxSize(template)) {
|
|
220
|
+
const { url } = await uploadTemplateToBaseStackBucket({
|
|
221
|
+
stackName,
|
|
222
|
+
template,
|
|
223
|
+
});
|
|
224
|
+
params.TemplateURL = url;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
params.TemplateBody = JSON.stringify(template);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* CAPABILITY_AUTO_EXPAND allows serverless transform.
|
|
231
|
+
*/
|
|
232
|
+
params.Capabilities = [
|
|
233
|
+
'CAPABILITY_AUTO_EXPAND',
|
|
234
|
+
'CAPABILITY_IAM',
|
|
235
|
+
'CAPABILITY_NAMED_IAM',
|
|
236
|
+
];
|
|
237
|
+
if (await (0, exports.doesStackExist)({ stackName })) {
|
|
238
|
+
await (0, exports.updateStack)({ params });
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
await (0, exports.createStack)({ params });
|
|
242
|
+
}
|
|
243
|
+
if (terminationProtection || !!(0, utils_1.getEnvironment)()) {
|
|
244
|
+
await (0, exports.enableTerminationProtection)({ stackName });
|
|
245
|
+
}
|
|
246
|
+
await (0, exports.printStackOutputsAfterDeploy)({ stackName });
|
|
247
|
+
return (0, exports.describeStack)({ stackName });
|
|
248
|
+
};
|
|
249
|
+
exports.deploy = deploy;
|
|
250
|
+
const canDestroyStack = async ({ stackName }) => {
|
|
251
|
+
const { EnableTerminationProtection } = await (0, exports.describeStack)({ stackName });
|
|
252
|
+
if (EnableTerminationProtection) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
};
|
|
257
|
+
exports.canDestroyStack = canDestroyStack;
|
|
258
|
+
const emptyStackBuckets = async ({ stackName }) => {
|
|
259
|
+
const buckets = [];
|
|
260
|
+
await (async function getBuckets({ nextToken }) {
|
|
261
|
+
const { NextToken, StackResourceSummaries } = await (0, exports.cloudFormation)().send(new client_cloudformation_1.ListStackResourcesCommand({
|
|
262
|
+
StackName: stackName,
|
|
263
|
+
NextToken: nextToken,
|
|
264
|
+
}));
|
|
265
|
+
if (NextToken) {
|
|
266
|
+
await getBuckets({ nextToken: NextToken });
|
|
267
|
+
}
|
|
268
|
+
(StackResourceSummaries || []).forEach(({ ResourceType, PhysicalResourceId }) => {
|
|
269
|
+
if (ResourceType === 'AWS::S3::Bucket' && PhysicalResourceId) {
|
|
270
|
+
buckets.push(PhysicalResourceId);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
})({});
|
|
274
|
+
return Promise.all(buckets.map((bucket) => (0, s3_1.emptyS3Directory)({ bucket })));
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* 1. Check if `environment` is defined. If defined, return. It doesn't destroy
|
|
278
|
+
* stacks with defined `environment`.
|
|
279
|
+
* 1. Check if termination protection is disabled.
|
|
280
|
+
* 1. If the stack deployed buckets, empty all buckets.
|
|
281
|
+
* 1. Delete the stack.
|
|
282
|
+
*/
|
|
283
|
+
const destroy = async ({ stackName }) => {
|
|
284
|
+
const environment = (0, utils_1.getEnvironment)();
|
|
285
|
+
if (environment) {
|
|
286
|
+
npmlog_1.default.info(logPrefix, `Cannot destroy stack when environment (${environment}) is defined.`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!(await (0, exports.doesStackExist)({ stackName }))) {
|
|
290
|
+
npmlog_1.default.info(logPrefix, `Stack ${stackName} doesn't exist.`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!(await (0, exports.canDestroyStack)({ stackName }))) {
|
|
294
|
+
const message = `Stack ${stackName} cannot be destroyed while TerminationProtection is enabled.`;
|
|
295
|
+
throw new Error(message);
|
|
296
|
+
}
|
|
297
|
+
await emptyStackBuckets({ stackName });
|
|
298
|
+
await (0, exports.deleteStack)({ stackName });
|
|
299
|
+
};
|
|
300
|
+
exports.destroy = destroy;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.destroyCloudFormation = exports.deployCloudFormation = exports.defaultTemplatePaths = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const cloudFormation_core_1 = require("./cloudFormation.core");
|
|
7
|
+
const deployLambdaCode_1 = require("./lambda/deployLambdaCode");
|
|
8
|
+
const s3_1 = require("./s3");
|
|
9
|
+
const stackName_1 = require("./stackName");
|
|
10
|
+
const utils_2 = require("./utils");
|
|
11
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
12
|
+
const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
|
|
13
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
14
|
+
const logPrefix = 'cloudformation';
|
|
15
|
+
npmlog_1.default.addLevel('event', 10000, { fg: 'yellow' });
|
|
16
|
+
npmlog_1.default.addLevel('output', 10000, { fg: 'blue' });
|
|
17
|
+
exports.defaultTemplatePaths = ['ts', 'js', 'yaml', 'yml', 'json'].map((extension) => `./src/cloudformation.${extension}`);
|
|
18
|
+
const findAndReadCloudFormationTemplate = ({ templatePath: defaultTemplatePath, }) => {
|
|
19
|
+
const templatePath = defaultTemplatePath ||
|
|
20
|
+
exports.defaultTemplatePaths
|
|
21
|
+
/**
|
|
22
|
+
* Iterate over extensions. If the template of the current extension is
|
|
23
|
+
* found, we save it on the accumulator and return it every time until
|
|
24
|
+
* the loop ends.
|
|
25
|
+
*/
|
|
26
|
+
.reduce((acc, cur) => {
|
|
27
|
+
if (acc) {
|
|
28
|
+
return acc;
|
|
29
|
+
}
|
|
30
|
+
return fs_1.default.existsSync(path_1.default.resolve(process.cwd(), cur)) ? cur : acc;
|
|
31
|
+
}, '');
|
|
32
|
+
if (!templatePath) {
|
|
33
|
+
throw new Error('Cannot find a CloudFormation template.');
|
|
34
|
+
}
|
|
35
|
+
const extension = templatePath === null || templatePath === void 0 ? void 0 : templatePath.split('.').pop();
|
|
36
|
+
const fullPath = path_1.default.resolve(process.cwd(), templatePath);
|
|
37
|
+
/**
|
|
38
|
+
* We need to read Yaml first because CloudFormation specific tags aren't
|
|
39
|
+
* recognized when parsing a simple Yaml file. I.e., a possible error:
|
|
40
|
+
* "Error message: "unknown tag !<!Ref> at line 21, column 34:\n"
|
|
41
|
+
*/
|
|
42
|
+
if (['yaml', 'yml'].includes(extension)) {
|
|
43
|
+
return (0, utils_1.readCloudFormationYamlTemplate)({ templatePath });
|
|
44
|
+
}
|
|
45
|
+
return (0, utils_1.readObjectFile)({ path: fullPath });
|
|
46
|
+
};
|
|
47
|
+
const deployCloudFormation = async ({ lambdaDockerfile, lambdaInput, lambdaImage, lambdaExternals = [], parameters, template, templatePath, }) => {
|
|
48
|
+
try {
|
|
49
|
+
const { stackName } = await (0, utils_2.handleDeployInitialization)({ logPrefix });
|
|
50
|
+
const cloudFormationTemplate = (() => {
|
|
51
|
+
if (template) {
|
|
52
|
+
return { ...template };
|
|
53
|
+
}
|
|
54
|
+
return findAndReadCloudFormationTemplate({ templatePath });
|
|
55
|
+
})();
|
|
56
|
+
await (0, cloudFormation_core_1.cloudFormationV2)()
|
|
57
|
+
.validateTemplate({
|
|
58
|
+
TemplateBody: JSON.stringify(cloudFormationTemplate, null, 2),
|
|
59
|
+
})
|
|
60
|
+
.promise();
|
|
61
|
+
const params = {
|
|
62
|
+
StackName: stackName,
|
|
63
|
+
Parameters: parameters || [],
|
|
64
|
+
};
|
|
65
|
+
const deployCloudFormationDeployLambdaCode = async () => {
|
|
66
|
+
const response = await (0, deployLambdaCode_1.deployLambdaCode)({
|
|
67
|
+
lambdaDockerfile,
|
|
68
|
+
lambdaExternals,
|
|
69
|
+
lambdaInput,
|
|
70
|
+
lambdaImage,
|
|
71
|
+
stackName,
|
|
72
|
+
});
|
|
73
|
+
if (response) {
|
|
74
|
+
const { bucket, key, versionId, imageUri } = response;
|
|
75
|
+
if (imageUri) {
|
|
76
|
+
cloudFormationTemplate.Parameters = {
|
|
77
|
+
LambdaImageUri: { Type: 'String' },
|
|
78
|
+
...cloudFormationTemplate.Parameters,
|
|
79
|
+
};
|
|
80
|
+
params.Parameters.push({
|
|
81
|
+
ParameterKey: 'LambdaImageUri',
|
|
82
|
+
ParameterValue: imageUri,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else if (bucket && key && versionId) {
|
|
86
|
+
/**
|
|
87
|
+
* Add Parameters to CloudFormation template.
|
|
88
|
+
*/
|
|
89
|
+
cloudFormationTemplate.Parameters = {
|
|
90
|
+
LambdaS3Bucket: { Type: 'String' },
|
|
91
|
+
LambdaS3Key: { Type: 'String' },
|
|
92
|
+
LambdaS3ObjectVersion: { Type: 'String' },
|
|
93
|
+
...cloudFormationTemplate.Parameters,
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Add S3Bucket and S3Key to params.
|
|
97
|
+
*/
|
|
98
|
+
params.Parameters.push({
|
|
99
|
+
ParameterKey: 'LambdaS3Bucket',
|
|
100
|
+
ParameterValue: bucket,
|
|
101
|
+
}, {
|
|
102
|
+
ParameterKey: 'LambdaS3Key',
|
|
103
|
+
ParameterValue: key,
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* Used by CloudFormation AWS::Lambda::Function
|
|
107
|
+
* @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html}
|
|
108
|
+
* and by CloudFormation AWS::Serverless::Function
|
|
109
|
+
* @see {@link https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-functioncode.html}
|
|
110
|
+
*/
|
|
111
|
+
{
|
|
112
|
+
ParameterKey: 'LambdaS3ObjectVersion',
|
|
113
|
+
ParameterValue: versionId,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
await deployCloudFormationDeployLambdaCode();
|
|
119
|
+
const output = await (0, cloudFormation_core_1.deploy)({
|
|
120
|
+
params,
|
|
121
|
+
template: cloudFormationTemplate,
|
|
122
|
+
});
|
|
123
|
+
return output;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return (0, utils_2.handleDeployError)({ error, logPrefix });
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
exports.deployCloudFormation = deployCloudFormation;
|
|
130
|
+
const emptyStackBuckets = async ({ stackName }) => {
|
|
131
|
+
const buckets = [];
|
|
132
|
+
await (async function getBuckets({ nextToken }) {
|
|
133
|
+
const { NextToken, StackResourceSummaries } = await (0, cloudFormation_core_1.cloudFormationV2)()
|
|
134
|
+
.listStackResources({ StackName: stackName, NextToken: nextToken })
|
|
135
|
+
.promise();
|
|
136
|
+
if (NextToken) {
|
|
137
|
+
await getBuckets({ nextToken: NextToken });
|
|
138
|
+
}
|
|
139
|
+
(StackResourceSummaries || []).forEach(({ ResourceType, PhysicalResourceId }) => {
|
|
140
|
+
if (ResourceType === 'AWS::S3::Bucket' && PhysicalResourceId) {
|
|
141
|
+
buckets.push(PhysicalResourceId);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
})({});
|
|
145
|
+
return Promise.all(buckets.map((bucket) => (0, s3_1.emptyS3Directory)({ bucket })));
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* 1. Check if `environment` is defined. If defined, return. It doesn't destroy
|
|
149
|
+
* stacks with defined `environment`.
|
|
150
|
+
* 1. Check if termination protection is disabled.
|
|
151
|
+
* 1. If the stack deployed buckets, empty all buckets.
|
|
152
|
+
* 1. Delete the stack.
|
|
153
|
+
*/
|
|
154
|
+
const destroy = async ({ stackName }) => {
|
|
155
|
+
const environment = (0, utils_1.getEnvironment)();
|
|
156
|
+
if (environment) {
|
|
157
|
+
npmlog_1.default.info(logPrefix, `Cannot destroy stack when environment (${environment}) is defined.`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (!(await (0, cloudFormation_core_1.doesStackExist)({ stackName }))) {
|
|
161
|
+
npmlog_1.default.info(logPrefix, `Stack ${stackName} doesn't exist.`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!(await (0, cloudFormation_core_1.canDestroyStack)({ stackName }))) {
|
|
165
|
+
const message = `Stack ${stackName} cannot be destroyed while TerminationProtection is enabled.`;
|
|
166
|
+
throw new Error(message);
|
|
167
|
+
}
|
|
168
|
+
await emptyStackBuckets({ stackName });
|
|
169
|
+
await (0, cloudFormation_core_1.deleteStack)({ stackName });
|
|
170
|
+
};
|
|
171
|
+
const destroyCloudFormation = async ({ stackName: defaultStackName, } = {}) => {
|
|
172
|
+
try {
|
|
173
|
+
npmlog_1.default.info(logPrefix, 'CAUTION! Starting CloudFormation destroy...');
|
|
174
|
+
const stackName = defaultStackName || (await (0, stackName_1.getStackName)());
|
|
175
|
+
npmlog_1.default.info(logPrefix, `stackName: ${stackName}`);
|
|
176
|
+
await destroy({ stackName });
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
(0, utils_2.handleDeployError)({ error, logPrefix });
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
exports.destroyCloudFormation = destroyCloudFormation;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deployCommand = exports.examples = exports.options = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const command_1 = require("./baseStack/command");
|
|
7
|
+
const command_2 = require("./cicd/command");
|
|
8
|
+
const cloudFormation_1 = require("./cloudFormation");
|
|
9
|
+
const command_3 = require("./lambdaLayer/command");
|
|
10
|
+
const command_4 = require("./staticApp/command");
|
|
11
|
+
const stackName_1 = require("./stackName");
|
|
12
|
+
const cloudFormation_core_1 = require("./cloudFormation.core");
|
|
13
|
+
const readDockerfile_1 = require("./readDockerfile");
|
|
14
|
+
const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
|
|
15
|
+
const logPrefix = 'deploy';
|
|
16
|
+
const checkAwsAccountId = async (awsAccountId) => {
|
|
17
|
+
try {
|
|
18
|
+
const currentAwsAccountId = await (0, utils_1.getAwsAccountId)();
|
|
19
|
+
if (String(awsAccountId) !== String(currentAwsAccountId)) {
|
|
20
|
+
throw new Error(`AWS account id does not match. Current is "${currentAwsAccountId}" but the defined in configuration files is "${awsAccountId}".`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
npmlog_1.default.error(logPrefix, error.message);
|
|
25
|
+
process.exit();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const describeDeployCommand = {
|
|
29
|
+
command: 'describe',
|
|
30
|
+
describe: 'Print the outputs of the deployment.',
|
|
31
|
+
handler: async ({ stackName }) => {
|
|
32
|
+
try {
|
|
33
|
+
const newStackName = stackName || (await (0, stackName_1.getStackName)());
|
|
34
|
+
await (0, cloudFormation_core_1.printStackOutputsAfterDeploy)({ stackName: newStackName });
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
npmlog_1.default.info(logPrefix, 'Cannot describe stack. Message: %s', error.message);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
exports.options = {
|
|
42
|
+
'aws-account-id': {
|
|
43
|
+
description: 'AWS account id associated with the deployment.',
|
|
44
|
+
type: 'string',
|
|
45
|
+
},
|
|
46
|
+
destroy: {
|
|
47
|
+
default: false,
|
|
48
|
+
description: 'Destroy the deployment. You cannot destroy a deploy with "environment" is defined.',
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
},
|
|
51
|
+
'lambda-dockerfile': {
|
|
52
|
+
coerce: (arg) => (0, readDockerfile_1.readDockerfile)(arg),
|
|
53
|
+
default: 'Dockerfile',
|
|
54
|
+
describe: 'Instructions to create the Lambda image.',
|
|
55
|
+
type: 'string',
|
|
56
|
+
},
|
|
57
|
+
'lambda-image': {
|
|
58
|
+
default: false,
|
|
59
|
+
describe: 'A Lambda image will be created instead using S3.',
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
},
|
|
62
|
+
'lambda-externals': {
|
|
63
|
+
default: [],
|
|
64
|
+
describe: 'Lambda external packages.',
|
|
65
|
+
type: 'array',
|
|
66
|
+
},
|
|
67
|
+
'lambda-input': {
|
|
68
|
+
default: 'src/lambda.ts',
|
|
69
|
+
describe: 'Lambda input file. This file export all handlers used by the Lambda Functions.',
|
|
70
|
+
type: 'string',
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* This option has the format:
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* Array<{
|
|
77
|
+
* ParameterKey: string,
|
|
78
|
+
* ParameterValue: string,
|
|
79
|
+
* UsePreviousValue: true | false,
|
|
80
|
+
* ResolvedValue: string
|
|
81
|
+
* }>
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
parameters: {
|
|
85
|
+
alias: 'p',
|
|
86
|
+
default: [],
|
|
87
|
+
describe: 'A list of parameters that will be passed to CloudFormation Parameters when deploying. The format is the same as parameters from cloudformation create-stack CLI command.',
|
|
88
|
+
type: 'array',
|
|
89
|
+
},
|
|
90
|
+
'skip-deploy': {
|
|
91
|
+
alias: 'skip',
|
|
92
|
+
default: false,
|
|
93
|
+
describe: 'Skip deploy.',
|
|
94
|
+
type: 'boolean',
|
|
95
|
+
},
|
|
96
|
+
'stack-name': {
|
|
97
|
+
describe: 'CloudFormation Stack name.',
|
|
98
|
+
type: 'string',
|
|
99
|
+
},
|
|
100
|
+
'template-path': {
|
|
101
|
+
alias: 't',
|
|
102
|
+
type: 'string',
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
exports.examples = [
|
|
106
|
+
[
|
|
107
|
+
'carlin deploy -t src/cloudformation.template1.yml',
|
|
108
|
+
'Change the CloudFormation template path.',
|
|
109
|
+
],
|
|
110
|
+
['carlin deploy -e Production', 'Set environment.'],
|
|
111
|
+
[
|
|
112
|
+
'carlin deploy --lambda-input src/lambda/index.ts --lambda-externals momentjs',
|
|
113
|
+
"Lambda exists. Don't bundle momentjs.",
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
'carlin deploy --destroy --stack-name StackToBeDeleted',
|
|
117
|
+
'Destroy a specific stack.',
|
|
118
|
+
],
|
|
119
|
+
];
|
|
120
|
+
exports.deployCommand = {
|
|
121
|
+
command: 'deploy [deploy]',
|
|
122
|
+
describe: 'Deploy cloud resources.',
|
|
123
|
+
builder: (yargsBuilder) => {
|
|
124
|
+
yargsBuilder
|
|
125
|
+
.example(exports.examples)
|
|
126
|
+
.options((0, utils_1.addGroupToOptions)(exports.options, 'Deploy Options'))
|
|
127
|
+
/**
|
|
128
|
+
* Set stack name.
|
|
129
|
+
*/
|
|
130
|
+
.middleware(({ stackName }) => {
|
|
131
|
+
if (stackName)
|
|
132
|
+
(0, stackName_1.setPreDefinedStackName)(stackName);
|
|
133
|
+
})
|
|
134
|
+
/**
|
|
135
|
+
* Set lambdaImage if lambdaDockerfile exists.
|
|
136
|
+
*/
|
|
137
|
+
.middleware((argv) => {
|
|
138
|
+
if (argv.lambdaDockerfile) {
|
|
139
|
+
// eslint-disable-next-line no-param-reassign
|
|
140
|
+
argv.lambdaImage = true;
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
/**
|
|
144
|
+
* Check AWS account id.
|
|
145
|
+
*/
|
|
146
|
+
.middleware(async ({ environments, environment, awsAccountId: defaultAwsAccountId, }) => {
|
|
147
|
+
const envAwsAccountId = (() => {
|
|
148
|
+
return environments && environment && environments[environment]
|
|
149
|
+
? environments[environment].awsAccountId
|
|
150
|
+
: undefined;
|
|
151
|
+
})();
|
|
152
|
+
if (defaultAwsAccountId || envAwsAccountId) {
|
|
153
|
+
await checkAwsAccountId(defaultAwsAccountId || envAwsAccountId);
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
.middleware(({ skipDeploy }) => {
|
|
157
|
+
if (skipDeploy) {
|
|
158
|
+
npmlog_1.default.warn(logPrefix, "Skip deploy flag is true, then the deploy command wasn't executed.");
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
const commands = [
|
|
163
|
+
command_3.deployLambdaLayerCommand,
|
|
164
|
+
describeDeployCommand,
|
|
165
|
+
command_1.deployBaseStackCommand,
|
|
166
|
+
command_4.deployStaticAppCommand,
|
|
167
|
+
command_2.deployCicdCommand,
|
|
168
|
+
];
|
|
169
|
+
yargsBuilder.positional('deploy', {
|
|
170
|
+
choices: commands.map(({ command }) => command),
|
|
171
|
+
describe: 'Type of deployment.',
|
|
172
|
+
type: 'string',
|
|
173
|
+
});
|
|
174
|
+
commands.forEach((command) => yargsBuilder.command(command));
|
|
175
|
+
return yargsBuilder;
|
|
176
|
+
},
|
|
177
|
+
handler: ({ destroy, ...rest }) => {
|
|
178
|
+
if (destroy) {
|
|
179
|
+
(0, cloudFormation_1.destroyCloudFormation)();
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
(0, cloudFormation_1.deployCloudFormation)(rest);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
};
|