carlin 1.31.11 → 1.31.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/index.js +4477 -4
- package/package.json +10 -10
- package/dist/cli.js +0 -246
- package/dist/config.js +0 -11
- package/dist/deploy/addDefaults.cloudformation.js +0 -151
- package/dist/deploy/baseStack/command.js +0 -9
- package/dist/deploy/baseStack/config.js +0 -30
- package/dist/deploy/baseStack/deployBaseStack.js +0 -62
- package/dist/deploy/baseStack/getBaseStackResource.js +0 -27
- package/dist/deploy/baseStack/getBucketTemplate.js +0 -46
- package/dist/deploy/baseStack/getLambdaImageBuilderTemplate.js +0 -188
- package/dist/deploy/baseStack/getLambdaLayerBuilderTemplate.js +0 -142
- package/dist/deploy/baseStack/getVpcTemplate.js +0 -169
- package/dist/deploy/cicd/cicd.template.js +0 -938
- package/dist/deploy/cicd/command.js +0 -31
- package/dist/deploy/cicd/command.options.js +0 -79
- package/dist/deploy/cicd/config.js +0 -8
- package/dist/deploy/cicd/deployCicd.js +0 -121
- package/dist/deploy/cicd/ecsTaskReportCommand.js +0 -55
- package/dist/deploy/cicd/getCicdStackName.js +0 -11
- package/dist/deploy/cicd/getTriggerPipelineObjectKey.js +0 -11
- package/dist/deploy/cicd/lambdas/cicdApiV1.handler.js +0 -124
- package/dist/deploy/cicd/lambdas/ecsTaskReport.handler.js +0 -126
- package/dist/deploy/cicd/lambdas/executeTasks.js +0 -67
- package/dist/deploy/cicd/lambdas/getProcessEnvVariable.js +0 -10
- package/dist/deploy/cicd/lambdas/githubWebhooksApiV1.handler.js +0 -148
- package/dist/deploy/cicd/lambdas/imageUpdaterSchedule.handler.js +0 -44
- package/dist/deploy/cicd/lambdas/index.js +0 -13
- package/dist/deploy/cicd/lambdas/pipelines.handler.js +0 -160
- package/dist/deploy/cicd/lambdas/putApprovalResultManualTask.js +0 -51
- package/dist/deploy/cicd/lambdas/shConditionalCommands.js +0 -30
- package/dist/deploy/cicd/pipelines.js +0 -86
- package/dist/deploy/cicd/readSSHKey.js +0 -34
- package/dist/deploy/cloudformation.core.js +0 -379
- package/dist/deploy/cloudformation.js +0 -189
- package/dist/deploy/command.js +0 -205
- package/dist/deploy/lambda/buildLambdaSingleFile.js +0 -67
- package/dist/deploy/lambda/deployLambdaCode.js +0 -43
- package/dist/deploy/lambda/deployLambdaLayers.js +0 -36
- package/dist/deploy/lambda/uploadCodeToECR.js +0 -53
- package/dist/deploy/lambda/uploadCodeToS3.js +0 -33
- package/dist/deploy/lambdaLayer/command.js +0 -50
- package/dist/deploy/lambdaLayer/deployLambdaLayer.js +0 -139
- package/dist/deploy/lambdaLayer/getPackageLambdaLayerStackName.js +0 -21
- package/dist/deploy/readDockerfile.js +0 -40
- package/dist/deploy/s3.js +0 -210
- package/dist/deploy/stackName.js +0 -85
- package/dist/deploy/staticApp/command.js +0 -86
- package/dist/deploy/staticApp/deployStaticApp.js +0 -65
- package/dist/deploy/staticApp/findDefaultBuildFolder.js +0 -44
- package/dist/deploy/staticApp/getStaticAppBucket.js +0 -19
- package/dist/deploy/staticApp/invalidateCloudFront.js +0 -44
- package/dist/deploy/staticApp/removeOldVersions.js +0 -56
- package/dist/deploy/staticApp/staticApp.template.js +0 -371
- package/dist/deploy/staticApp/uploadBuiltAppToS3.js +0 -28
- package/dist/deploy/utils.js +0 -31
- package/dist/deploy/vercel/command.js +0 -31
- package/dist/deploy/vercel/deployVercel.js +0 -59
- package/dist/generateEnv/generateEnv.js +0 -64
- package/dist/generateEnv/generateEnvCommand.js +0 -29
- package/dist/utils/addGroupToOptions.js +0 -11
- package/dist/utils/cloudFormationTemplate.js +0 -142
- package/dist/utils/codeBuild.js +0 -52
- package/dist/utils/environmentVariables.js +0 -16
- package/dist/utils/exec.js +0 -26
- package/dist/utils/formatCode.js +0 -34
- package/dist/utils/getAwsAccountId.js +0 -10
- package/dist/utils/getCurrentBranch.js +0 -35
- package/dist/utils/getEnvironment.js +0 -8
- package/dist/utils/getIamPath.js +0 -6
- package/dist/utils/getProjectName.js +0 -35
- package/dist/utils/index.js +0 -31
- package/dist/utils/packageJson.js +0 -32
- package/dist/utils/spawn.js +0 -34
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4477 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
(
|
|
1
|
+
"use strict"; function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/config.ts
|
|
10
|
+
var NAME = "carlin";
|
|
11
|
+
var AWS_DEFAULT_REGION = "us-east-1";
|
|
12
|
+
var CLOUDFRONT_REGION = "us-east-1";
|
|
13
|
+
var NODE_RUNTIME = "nodejs20.x";
|
|
14
|
+
|
|
15
|
+
// src/utils/addGroupToOptions.ts
|
|
16
|
+
var addGroupToOptions = (options9, group) => {
|
|
17
|
+
Object.values(options9).forEach((option) => {
|
|
18
|
+
option.group = group;
|
|
19
|
+
});
|
|
20
|
+
return options9;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/utils/codeBuild.ts
|
|
24
|
+
var _awssdk = require('aws-sdk'); var _awssdk2 = _interopRequireDefault(_awssdk);
|
|
25
|
+
var _npmlog = require('npmlog'); var _npmlog2 = _interopRequireDefault(_npmlog);
|
|
26
|
+
var logPrefix = "codebuild";
|
|
27
|
+
var WAIT_TIME = 10 * 1e3;
|
|
28
|
+
var waitCodeBuildFinish = async ({
|
|
29
|
+
buildId,
|
|
30
|
+
name
|
|
31
|
+
}) => {
|
|
32
|
+
const codeBuild2 = new (0, _awssdk.CodeBuild)();
|
|
33
|
+
let result;
|
|
34
|
+
const checkIfBuildIsFinished = async () => {
|
|
35
|
+
const { builds } = await codeBuild2.batchGetBuilds({ ids: [buildId] }).promise();
|
|
36
|
+
return new Promise((resolve5, reject) => {
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
const executedBuild = _optionalChain([builds, 'optionalAccess', _ => _.find, 'call', _2 => _2(({ id }) => id === buildId)]);
|
|
39
|
+
_npmlog2.default.info(
|
|
40
|
+
logPrefix,
|
|
41
|
+
`Build status of ${name || buildId}: ${_optionalChain([executedBuild, 'optionalAccess', _3 => _3.buildStatus])}`
|
|
42
|
+
);
|
|
43
|
+
if (executedBuild && executedBuild.currentPhase === "COMPLETED") {
|
|
44
|
+
if (executedBuild.buildStatus === "SUCCEEDED") {
|
|
45
|
+
resolve5(executedBuild);
|
|
46
|
+
} else if (["FAILED", "FAILURE"].includes(executedBuild.buildStatus || "")) {
|
|
47
|
+
reject(new Error(`Cannot execute build ${buildId}.`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
resolve5(void 0);
|
|
51
|
+
}, WAIT_TIME);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
while (!result) {
|
|
55
|
+
result = await checkIfBuildIsFinished();
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
var startCodeBuildBuild = async ({
|
|
60
|
+
projectName
|
|
61
|
+
}) => {
|
|
62
|
+
const codeBuild2 = new (0, _awssdk.CodeBuild)();
|
|
63
|
+
const { build } = await codeBuild2.startBuild({ projectName }).promise();
|
|
64
|
+
if (!build) {
|
|
65
|
+
throw new Error(`Cannot start ${projectName} build`);
|
|
66
|
+
}
|
|
67
|
+
return build;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/utils/cloudFormationTemplate.ts
|
|
71
|
+
var _jsyaml = require('js-yaml'); var _jsyaml2 = _interopRequireDefault(_jsyaml);
|
|
72
|
+
|
|
73
|
+
// src/utils/environmentVariables.ts
|
|
74
|
+
var cache = /* @__PURE__ */ new Map();
|
|
75
|
+
var getEnvVar = (key) => {
|
|
76
|
+
return cache.has(key) && cache.get(key) ? cache.get(key) : void 0;
|
|
77
|
+
};
|
|
78
|
+
var setEnvVar = (key, value) => {
|
|
79
|
+
if (!value) {
|
|
80
|
+
return cache.delete(key);
|
|
81
|
+
}
|
|
82
|
+
return cache.set(key, value);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/utils/exec.ts
|
|
86
|
+
|
|
87
|
+
_npmlog2.default.heading = "exec";
|
|
88
|
+
|
|
89
|
+
// src/utils/formatCode.ts
|
|
90
|
+
var _uglifyjs = require('uglify-js'); var UglifyJS = _interopRequireWildcard(_uglifyjs);
|
|
91
|
+
var _prettier = require('prettier'); var prettier = _interopRequireWildcard(_prettier);
|
|
92
|
+
|
|
93
|
+
// src/utils/getAwsAccountId.ts
|
|
94
|
+
|
|
95
|
+
var getAwsAccountId = async () => {
|
|
96
|
+
const sts = new (0, _awssdk.STS)();
|
|
97
|
+
const { Account } = await sts.getCallerIdentity().promise();
|
|
98
|
+
return Account;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/utils/getCurrentBranch.ts
|
|
102
|
+
var _simplegit = require('simple-git'); var _simplegit2 = _interopRequireDefault(_simplegit);
|
|
103
|
+
var BRANCH_UNDEFINED = "";
|
|
104
|
+
var getCurrentBranch = async () => {
|
|
105
|
+
try {
|
|
106
|
+
if (getEnvVar("BRANCH")) {
|
|
107
|
+
return getEnvVar("BRANCH");
|
|
108
|
+
}
|
|
109
|
+
const { current } = await _simplegit2.default.call(void 0, ).branch();
|
|
110
|
+
return current || BRANCH_UNDEFINED;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return BRANCH_UNDEFINED;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/utils/getEnvironment.ts
|
|
117
|
+
var getEnvironment = () => {
|
|
118
|
+
return getEnvVar("ENVIRONMENT");
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/utils/getIamPath.ts
|
|
122
|
+
var getIamPath = () => `/${NAME}/`;
|
|
123
|
+
|
|
124
|
+
// src/utils/packageJson.ts
|
|
125
|
+
var _findupsync = require('findup-sync'); var _findupsync2 = _interopRequireDefault(_findupsync);
|
|
126
|
+
var _fs = require('fs'); var fs3 = _interopRequireWildcard(_fs); var fs6 = _interopRequireWildcard(_fs); var fs7 = _interopRequireWildcard(_fs); var fs10 = _interopRequireWildcard(_fs); var fs8 = _interopRequireWildcard(_fs); var fs11 = _interopRequireWildcard(_fs); var fs12 = _interopRequireWildcard(_fs);
|
|
127
|
+
var readPackageJson = () => {
|
|
128
|
+
const packageJsonDir = _findupsync2.default.call(void 0, "package.json");
|
|
129
|
+
if (!packageJsonDir) {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
return JSON.parse(fs3.default.readFileSync(packageJsonDir).toString());
|
|
133
|
+
};
|
|
134
|
+
var getPackageJsonProperty = ({ property }) => {
|
|
135
|
+
try {
|
|
136
|
+
return readPackageJson()[property];
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var getPackageName = () => {
|
|
142
|
+
return getPackageJsonProperty({ property: "name" });
|
|
143
|
+
};
|
|
144
|
+
var getPackageVersion = () => {
|
|
145
|
+
return getPackageJsonProperty({ property: "version" });
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/utils/getProjectName.ts
|
|
149
|
+
var _changecase = require('change-case');
|
|
150
|
+
var getProjectName = () => {
|
|
151
|
+
if (getEnvVar("PROJECT")) {
|
|
152
|
+
return getEnvVar("PROJECT");
|
|
153
|
+
}
|
|
154
|
+
const name = getPackageName();
|
|
155
|
+
if (!name) {
|
|
156
|
+
return "";
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
return _changecase.pascalCase.call(void 0, name.split(/[@/]/)[1]);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
return _changecase.pascalCase.call(void 0, name);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/utils/spawn.ts
|
|
166
|
+
var _process = require('process');
|
|
167
|
+
var _child_process = require('child_process'); var _child_process2 = _interopRequireDefault(_child_process);
|
|
168
|
+
|
|
169
|
+
_npmlog2.default.heading = "exec";
|
|
170
|
+
var spawn = (cmd) => {
|
|
171
|
+
return new Promise((resolve5, reject) => {
|
|
172
|
+
const [cmdName, ...cmdArgs] = cmd.split(" ");
|
|
173
|
+
const child = _child_process2.default.spawn(cmdName, cmdArgs);
|
|
174
|
+
child.stdout.on("data", (data) => {
|
|
175
|
+
_process.stdout.write(data);
|
|
176
|
+
});
|
|
177
|
+
child.stderr.on("data", (data) => {
|
|
178
|
+
_process.stdout.write(data);
|
|
179
|
+
});
|
|
180
|
+
child.on("error", (error) => {
|
|
181
|
+
reject(error);
|
|
182
|
+
});
|
|
183
|
+
child.on("close", (code) => {
|
|
184
|
+
if (code === 0) {
|
|
185
|
+
resolve5({});
|
|
186
|
+
} else {
|
|
187
|
+
reject(code);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/cli.ts
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
// src/deploy/baseStack/config.ts
|
|
197
|
+
|
|
198
|
+
var pascalCaseName = _changecase.pascalCase.call(void 0, NAME);
|
|
199
|
+
var BASE_STACK_NAME = `${pascalCaseName}BaseStack`;
|
|
200
|
+
var BASE_STACK_BUCKET_TEMPLATES_FOLDER = "cloudformation-templates";
|
|
201
|
+
var BASE_STACK_BUCKET_LOGICAL_NAME = `${pascalCaseName}Bucket`;
|
|
202
|
+
var BASE_STACK_BUCKET_NAME_EXPORTED_NAME = `${pascalCaseName}BucketNameExportedName`;
|
|
203
|
+
var BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME = `${pascalCaseName}LambdaImageBuilder`;
|
|
204
|
+
var BASE_STACK_LAMBDA_IMAGE_BUILDER_EXPORTED_NAME = `${pascalCaseName}LambdaImageBuilderExportedName`;
|
|
205
|
+
var BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME = `${pascalCaseName}LambdaLayerBuilder`;
|
|
206
|
+
var BASE_STACK_VPC_ID_EXPORTED_NAME = `${pascalCaseName}VPCIDExportedName`;
|
|
207
|
+
var BASE_STACK_VPC_DEFAULT_SECURITY_GROUP_EXPORTED_NAME = `${pascalCaseName}DefaultSecurityGroupExportedName`;
|
|
208
|
+
var BASE_STACK_VPC_PUBLIC_SUBNET_0_EXPORTED_NAME = `${pascalCaseName}VPCPublicSubnet0ExportedName`;
|
|
209
|
+
var BASE_STACK_VPC_PUBLIC_SUBNET_1_EXPORTED_NAME = `${pascalCaseName}VPCPublicSubnet1ExportedName`;
|
|
210
|
+
var BASE_STACK_VPC_PUBLIC_SUBNET_2_EXPORTED_NAME = `${pascalCaseName}VPCPublicSubnet2ExportedName`;
|
|
211
|
+
|
|
212
|
+
// src/deploy/cloudformation.core.ts
|
|
213
|
+
|
|
214
|
+
var _path = require('path'); var path2 = _interopRequireWildcard(_path); var path6 = _interopRequireWildcard(_path); var path8 = _interopRequireWildcard(_path); var path7 = _interopRequireWildcard(_path); var path9 = _interopRequireWildcard(_path); var path10 = _interopRequireWildcard(_path);
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
var _clientcloudformation = require('@aws-sdk/client-cloudformation');
|
|
227
|
+
|
|
228
|
+
// src/deploy/addDefaults.cloudformation.ts
|
|
229
|
+
var addDefaultsParametersAndTagsToParams = async (params) => {
|
|
230
|
+
const branchName = await getCurrentBranch();
|
|
231
|
+
const environment = await getEnvironment();
|
|
232
|
+
const packageName = await getPackageName();
|
|
233
|
+
const packageVersion = await getPackageVersion();
|
|
234
|
+
const projectName = await getProjectName();
|
|
235
|
+
const tagValuePattern = /[^a-zA-Z0-9_.:/=+\-@]/g;
|
|
236
|
+
return {
|
|
237
|
+
...params,
|
|
238
|
+
Parameters: [
|
|
239
|
+
...params.Parameters || [],
|
|
240
|
+
...environment ? [{ ParameterKey: "Environment", ParameterValue: environment }] : [],
|
|
241
|
+
{ ParameterKey: "Project", ParameterValue: projectName }
|
|
242
|
+
],
|
|
243
|
+
Tags: [
|
|
244
|
+
...params.Tags || [],
|
|
245
|
+
{ Key: "Branch", Value: branchName },
|
|
246
|
+
...environment ? [{ Key: "Environment", Value: environment }] : [],
|
|
247
|
+
{ Key: "Package", Value: packageName },
|
|
248
|
+
{ Key: "Project", Value: projectName },
|
|
249
|
+
{ Key: "Version", Value: packageVersion }
|
|
250
|
+
].filter(({ Value }) => {
|
|
251
|
+
return !!Value;
|
|
252
|
+
}).map(({ Key, Value }) => {
|
|
253
|
+
return {
|
|
254
|
+
Key,
|
|
255
|
+
Value: Value.replace(tagValuePattern, "")
|
|
256
|
+
};
|
|
257
|
+
})
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
var addDefaultParametersToTemplate = async (template) => {
|
|
261
|
+
const [environment, projectName] = await Promise.all([
|
|
262
|
+
getEnvironment(),
|
|
263
|
+
getProjectName()
|
|
264
|
+
]);
|
|
265
|
+
const newParameters = {
|
|
266
|
+
Project: { Default: projectName, Type: "String" }
|
|
267
|
+
};
|
|
268
|
+
if (environment) {
|
|
269
|
+
newParameters.Environment = { Default: environment, Type: "String" };
|
|
270
|
+
}
|
|
271
|
+
template.Parameters = { ...newParameters, ...template.Parameters };
|
|
272
|
+
};
|
|
273
|
+
var addLogGroupToResources = (template) => {
|
|
274
|
+
const { Resources } = template;
|
|
275
|
+
const resourcesEntries = Object.entries(Resources);
|
|
276
|
+
resourcesEntries.forEach(([key, resource]) => {
|
|
277
|
+
if (["AWS::Lambda::Function", "AWS::Serverless::Function"].includes(
|
|
278
|
+
resource.Type
|
|
279
|
+
)) {
|
|
280
|
+
const logGroup = resourcesEntries.find(([, resource2]) => {
|
|
281
|
+
const logGroupNameStr = JSON.stringify(
|
|
282
|
+
_optionalChain([resource2, 'access', _4 => _4.Properties, 'optionalAccess', _5 => _5.LogGroupName, 'optionalAccess', _6 => _6["Fn::Join"]]) || ""
|
|
283
|
+
);
|
|
284
|
+
return logGroupNameStr.includes(key);
|
|
285
|
+
});
|
|
286
|
+
if (!logGroup) {
|
|
287
|
+
Resources[`${key}LogsLogGroup`] = {
|
|
288
|
+
Type: "AWS::Logs::LogGroup",
|
|
289
|
+
DeletionPolicy: "Delete",
|
|
290
|
+
Properties: {
|
|
291
|
+
LogGroupName: { "Fn::Join": ["/", ["/aws/lambda", { Ref: key }]] }
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
var addEnvironmentsToLambdaResources = async (template) => {
|
|
299
|
+
const environment = getEnvironment();
|
|
300
|
+
const { Resources } = template;
|
|
301
|
+
const resourcesEntries = Object.entries(Resources);
|
|
302
|
+
resourcesEntries.forEach(([, resource]) => {
|
|
303
|
+
if (resource.Type === "AWS::Lambda::Function") {
|
|
304
|
+
const { Properties } = resource;
|
|
305
|
+
if ((Properties.Description || "").includes("Lambda@Edge")) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (!environment) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (!Properties.Environment) {
|
|
312
|
+
Properties.Environment = {};
|
|
313
|
+
}
|
|
314
|
+
if (!Properties.Environment.Variables) {
|
|
315
|
+
Properties.Environment.Variables = {};
|
|
316
|
+
}
|
|
317
|
+
Properties.Environment.Variables.ENVIRONMENT = environment;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
var CRITICAL_RESOURCES_TYPES = [
|
|
322
|
+
"AWS::Cognito::UserPool",
|
|
323
|
+
"AWS::DynamoDB::Table"
|
|
324
|
+
];
|
|
325
|
+
var addRetainToCriticalResources = async (template) => {
|
|
326
|
+
const environment = getEnvironment();
|
|
327
|
+
Object.entries(template.Resources).forEach(([, resource]) => {
|
|
328
|
+
if (CRITICAL_RESOURCES_TYPES.includes(resource.Type)) {
|
|
329
|
+
if (!resource.DeletionPolicy && environment) {
|
|
330
|
+
resource.DeletionPolicy = "Retain";
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
var addAppSyncApiOutputs = async (template) => {
|
|
336
|
+
Object.entries(template.Resources).forEach(([key, resource]) => {
|
|
337
|
+
if (resource.Type === "AWS::AppSync::GraphQLApi") {
|
|
338
|
+
template.Outputs = {
|
|
339
|
+
[key]: {
|
|
340
|
+
Description: `Automatically added by ${NAME}`,
|
|
341
|
+
Value: { "Fn::GetAtt": [key, "GraphQLUrl"] },
|
|
342
|
+
Export: {
|
|
343
|
+
Name: {
|
|
344
|
+
"Fn::Join": [":", [{ Ref: "AWS::StackName" }, "GraphQLApiUrl"]]
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
...template.Outputs
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
var addDefaults = async ({
|
|
354
|
+
params,
|
|
355
|
+
template
|
|
356
|
+
}) => {
|
|
357
|
+
const newTemplate = JSON.parse(JSON.stringify(template));
|
|
358
|
+
await addDefaultParametersToTemplate(newTemplate);
|
|
359
|
+
await addLogGroupToResources(newTemplate);
|
|
360
|
+
await addEnvironmentsToLambdaResources(newTemplate);
|
|
361
|
+
await addAppSyncApiOutputs(newTemplate);
|
|
362
|
+
await addRetainToCriticalResources(newTemplate);
|
|
363
|
+
const response = {
|
|
364
|
+
params: await addDefaultsParametersAndTagsToParams(params),
|
|
365
|
+
template: newTemplate
|
|
366
|
+
};
|
|
367
|
+
return response;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/deploy/s3.ts
|
|
371
|
+
|
|
372
|
+
var _glob = require('glob');
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
var _mimetypes = require('mime-types'); var _mimetypes2 = _interopRequireDefault(_mimetypes);
|
|
376
|
+
|
|
377
|
+
var logPrefix2 = "s3";
|
|
378
|
+
var s3 = new (0, _awssdk.S3)({ apiVersion: "2006-03-01" });
|
|
379
|
+
var getBucketKeyUrl = ({
|
|
380
|
+
bucket,
|
|
381
|
+
key
|
|
382
|
+
}) => {
|
|
383
|
+
return `https://s3.amazonaws.com/${bucket}/${key}`;
|
|
384
|
+
};
|
|
385
|
+
var uploadFileToS3 = async ({
|
|
386
|
+
bucket,
|
|
387
|
+
contentType,
|
|
388
|
+
file,
|
|
389
|
+
filePath,
|
|
390
|
+
key
|
|
391
|
+
}) => {
|
|
392
|
+
if (!file && !filePath) {
|
|
393
|
+
throw new Error("file or filePath must be defined");
|
|
394
|
+
}
|
|
395
|
+
let params = {
|
|
396
|
+
Bucket: bucket,
|
|
397
|
+
Key: key.split(path2.default.sep).join("/")
|
|
398
|
+
};
|
|
399
|
+
if (file) {
|
|
400
|
+
params.ContentType = contentType;
|
|
401
|
+
params.Body = file;
|
|
402
|
+
} else if (filePath) {
|
|
403
|
+
const readFile = await fs3.default.promises.readFile(filePath);
|
|
404
|
+
params = {
|
|
405
|
+
...params,
|
|
406
|
+
ContentType: contentType || _mimetypes2.default.contentType(path2.default.extname(filePath)) || void 0
|
|
407
|
+
};
|
|
408
|
+
params.Body = Buffer.from(readFile);
|
|
409
|
+
}
|
|
410
|
+
const { Bucket, Key, VersionId } = await s3.upload(params).promise();
|
|
411
|
+
return {
|
|
412
|
+
bucket: Bucket,
|
|
413
|
+
key: Key,
|
|
414
|
+
versionId: VersionId,
|
|
415
|
+
url: getBucketKeyUrl({ bucket: Bucket, key: Key })
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
var getAllFilesInsideADirectory = async ({
|
|
419
|
+
directory
|
|
420
|
+
}) => {
|
|
421
|
+
const allFilesAndDirectories = await _glob.glob.call(void 0, `${directory}/**/*`);
|
|
422
|
+
const allFiles = allFilesAndDirectories.filter((item) => {
|
|
423
|
+
return fs3.default.lstatSync(item).isFile();
|
|
424
|
+
});
|
|
425
|
+
return allFiles;
|
|
426
|
+
};
|
|
427
|
+
var copyRoot404To404Index = async ({ bucket }) => {
|
|
428
|
+
try {
|
|
429
|
+
const root404Exists = await s3.headObject({
|
|
430
|
+
Bucket: bucket,
|
|
431
|
+
Key: "404.html"
|
|
432
|
+
}).promise().catch(() => {
|
|
433
|
+
return false;
|
|
434
|
+
});
|
|
435
|
+
if (root404Exists) {
|
|
436
|
+
await s3.copyObject({
|
|
437
|
+
Bucket: bucket,
|
|
438
|
+
CopySource: `${bucket}/404.html`,
|
|
439
|
+
Key: "404/index.html"
|
|
440
|
+
}).promise();
|
|
441
|
+
}
|
|
442
|
+
} catch (err) {
|
|
443
|
+
_npmlog2.default.error(logPrefix2, `Cannot copy 404.html to 404/index.html`);
|
|
444
|
+
throw err;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
var uploadDirectoryToS3 = async ({
|
|
448
|
+
bucket,
|
|
449
|
+
bucketKey = "",
|
|
450
|
+
directory
|
|
451
|
+
}) => {
|
|
452
|
+
_npmlog2.default.info(
|
|
453
|
+
logPrefix2,
|
|
454
|
+
`Uploading directory ${directory}/ to ${bucket}/${bucketKey}...`
|
|
455
|
+
);
|
|
456
|
+
const allFiles = await getAllFilesInsideADirectory({ directory });
|
|
457
|
+
if (allFiles.length === 0) {
|
|
458
|
+
throw new Error(`Directory ${directory}/ has no files.`);
|
|
459
|
+
}
|
|
460
|
+
const GROUP_MAX_LENGTH = 63;
|
|
461
|
+
const numberOfGroups = Math.ceil(allFiles.length / GROUP_MAX_LENGTH);
|
|
462
|
+
const aoaOfFiles = allFiles.reduce((acc, file, index) => {
|
|
463
|
+
const groupIndex = index % numberOfGroups;
|
|
464
|
+
if (!acc[groupIndex]) {
|
|
465
|
+
acc[groupIndex] = [];
|
|
466
|
+
}
|
|
467
|
+
acc[index % numberOfGroups].push(file);
|
|
468
|
+
return acc;
|
|
469
|
+
}, []);
|
|
470
|
+
for (const [index, groupOfFiles] of aoaOfFiles.entries()) {
|
|
471
|
+
_npmlog2.default.info(logPrefix2, `Uploading group ${index + 1}/${aoaOfFiles.length}...`);
|
|
472
|
+
await Promise.all(
|
|
473
|
+
groupOfFiles.map((file) => {
|
|
474
|
+
return uploadFileToS3({
|
|
475
|
+
bucket,
|
|
476
|
+
key: path2.default.join(bucketKey, path2.default.relative(directory, file)),
|
|
477
|
+
filePath: file
|
|
478
|
+
});
|
|
479
|
+
})
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
var emptyS3Directory = async ({
|
|
484
|
+
bucket,
|
|
485
|
+
directory = ""
|
|
486
|
+
}) => {
|
|
487
|
+
_npmlog2.default.info(logPrefix2, `${bucket}/${directory} will be empty`);
|
|
488
|
+
try {
|
|
489
|
+
const { Contents, IsTruncated } = await s3.listObjectsV2({
|
|
490
|
+
Bucket: bucket,
|
|
491
|
+
Prefix: directory
|
|
492
|
+
}).promise();
|
|
493
|
+
if (Contents && Contents.length > 0) {
|
|
494
|
+
const objectsPromises = Contents.filter(({ Key }) => {
|
|
495
|
+
return !!Key;
|
|
496
|
+
}).map(async ({ Key }) => {
|
|
497
|
+
const { Versions = [] } = await s3.listObjectVersions({
|
|
498
|
+
Bucket: bucket,
|
|
499
|
+
Prefix: Key
|
|
500
|
+
}).promise();
|
|
501
|
+
return {
|
|
502
|
+
Key,
|
|
503
|
+
Versions: Versions.map(({ VersionId }) => {
|
|
504
|
+
return VersionId || void 0;
|
|
505
|
+
})
|
|
506
|
+
};
|
|
507
|
+
});
|
|
508
|
+
const objects = await Promise.all(objectsPromises);
|
|
509
|
+
const objectsWithVersionsIds = objects.reduce((acc, { Key, Versions }) => {
|
|
510
|
+
const objectWithVersionsIds = Versions.map((VersionId) => {
|
|
511
|
+
return {
|
|
512
|
+
Key,
|
|
513
|
+
VersionId
|
|
514
|
+
};
|
|
515
|
+
});
|
|
516
|
+
return [...acc, ...objectWithVersionsIds];
|
|
517
|
+
}, []);
|
|
518
|
+
await s3.deleteObjects({
|
|
519
|
+
Bucket: bucket,
|
|
520
|
+
Delete: { Objects: objectsWithVersionsIds }
|
|
521
|
+
}).promise();
|
|
522
|
+
}
|
|
523
|
+
if (IsTruncated) {
|
|
524
|
+
await emptyS3Directory({ bucket, directory });
|
|
525
|
+
}
|
|
526
|
+
_npmlog2.default.info(logPrefix2, `${bucket}/${directory} is empty.`);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
_npmlog2.default.error(logPrefix2, `Cannot empty ${bucket}/${directory}.`);
|
|
529
|
+
throw err;
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
var deleteS3Directory = async ({
|
|
533
|
+
bucket,
|
|
534
|
+
directory = ""
|
|
535
|
+
}) => {
|
|
536
|
+
try {
|
|
537
|
+
_npmlog2.default.info(logPrefix2, `${bucket}/${directory} is being deleted...`);
|
|
538
|
+
await emptyS3Directory({ bucket, directory });
|
|
539
|
+
await s3.deleteObject({ Bucket: bucket, Key: directory }).promise();
|
|
540
|
+
_npmlog2.default.info(logPrefix2, `${bucket}/${directory} was deleted.`);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
_npmlog2.default.error(logPrefix2, `Cannot delete ${bucket}/${directory}.`);
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// src/deploy/baseStack/getBaseStackResource.ts
|
|
548
|
+
var getBaseStackOutput = async (outputKey) => {
|
|
549
|
+
const output = await getStackOutput({
|
|
550
|
+
stackName: BASE_STACK_NAME,
|
|
551
|
+
outputKey
|
|
552
|
+
});
|
|
553
|
+
return output.OutputValue;
|
|
554
|
+
};
|
|
555
|
+
var resourcesKeys = {
|
|
556
|
+
BASE_STACK_BUCKET_LOGICAL_NAME,
|
|
557
|
+
BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME,
|
|
558
|
+
BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME
|
|
559
|
+
};
|
|
560
|
+
var resources = {};
|
|
561
|
+
var getBaseStackResource = async (resource) => {
|
|
562
|
+
if (!resources[resource]) {
|
|
563
|
+
resources[resource] = await getBaseStackOutput(resourcesKeys[resource]);
|
|
564
|
+
}
|
|
565
|
+
return resources[resource];
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// src/deploy/cloudformation.core.ts
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
var logPrefix3 = "cloudformation";
|
|
572
|
+
_npmlog2.default.addLevel("event", 1e4, { fg: "yellow" });
|
|
573
|
+
_npmlog2.default.addLevel("output", 1e4, { fg: "blue" });
|
|
574
|
+
var TEMPLATE_BODY_MAX_SIZE = 51200;
|
|
575
|
+
var isTemplateBodyGreaterThanMaxSize = (template) => {
|
|
576
|
+
return Buffer.byteLength(JSON.stringify(template), "utf8") >= TEMPLATE_BODY_MAX_SIZE;
|
|
577
|
+
};
|
|
578
|
+
var uploadTemplateToBaseStackBucket = async ({
|
|
579
|
+
stackName,
|
|
580
|
+
template
|
|
581
|
+
}) => {
|
|
582
|
+
const bucketName = await getBaseStackResource(
|
|
583
|
+
"BASE_STACK_BUCKET_LOGICAL_NAME"
|
|
584
|
+
);
|
|
585
|
+
const { url } = await uploadFileToS3({
|
|
586
|
+
bucket: bucketName,
|
|
587
|
+
contentType: "application/json",
|
|
588
|
+
key: `${BASE_STACK_BUCKET_TEMPLATES_FOLDER}/${stackName}.json`,
|
|
589
|
+
file: Buffer.from(JSON.stringify(template, null, 2))
|
|
590
|
+
});
|
|
591
|
+
return { url };
|
|
592
|
+
};
|
|
593
|
+
var cloudFormationClients = {};
|
|
594
|
+
var cloudformation = () => {
|
|
595
|
+
const cloudFormationClientConfig = {
|
|
596
|
+
apiVersion: "2010-05-15",
|
|
597
|
+
region: getEnvVar("REGION")
|
|
598
|
+
};
|
|
599
|
+
const key = JSON.stringify(cloudFormationClientConfig);
|
|
600
|
+
if (!cloudFormationClients[key]) {
|
|
601
|
+
cloudFormationClients[key] = new (0, _clientcloudformation.CloudFormationClient)({
|
|
602
|
+
apiVersion: "2010-05-15",
|
|
603
|
+
region: getEnvVar("REGION")
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return cloudFormationClients[key];
|
|
607
|
+
};
|
|
608
|
+
var cloudFormationV2 = () => {
|
|
609
|
+
return new _awssdk2.default.CloudFormation({ apiVersion: "2010-05-15" });
|
|
610
|
+
};
|
|
611
|
+
var describeStacks = async ({
|
|
612
|
+
stackName
|
|
613
|
+
} = {}) => {
|
|
614
|
+
const { Stacks } = await cloudformation().send(
|
|
615
|
+
new (0, _clientcloudformation.DescribeStacksCommand)({ StackName: stackName })
|
|
616
|
+
);
|
|
617
|
+
return Stacks;
|
|
618
|
+
};
|
|
619
|
+
var describeStackResource = async (input) => {
|
|
620
|
+
return cloudformation().send(new (0, _clientcloudformation.DescribeStackResourceCommand)(input));
|
|
621
|
+
};
|
|
622
|
+
var doesStackExist = async ({ stackName }) => {
|
|
623
|
+
_npmlog2.default.info(logPrefix3, `Checking if stack ${stackName} already exists...`);
|
|
624
|
+
try {
|
|
625
|
+
await describeStacks({ stackName });
|
|
626
|
+
_npmlog2.default.info(logPrefix3, `Stack ${stackName} already exists.`);
|
|
627
|
+
return true;
|
|
628
|
+
} catch (error) {
|
|
629
|
+
if (error.Code === "ValidationError") {
|
|
630
|
+
_npmlog2.default.info(logPrefix3, `Stack ${stackName} does not exist.`);
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
throw error;
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
var describeStackEvents = async ({
|
|
637
|
+
stackName
|
|
638
|
+
}) => {
|
|
639
|
+
_npmlog2.default.error(logPrefix3, "Stack events:");
|
|
640
|
+
const { StackEvents } = await cloudformation().send(
|
|
641
|
+
new (0, _clientcloudformation.DescribeStackEventsCommand)({ StackName: stackName })
|
|
642
|
+
);
|
|
643
|
+
const events = (StackEvents || []).filter(({ Timestamp }) => {
|
|
644
|
+
return Date.now() - Number(Timestamp) < 10 * 60 * 1e3;
|
|
645
|
+
}).filter(({ ResourceStatusReason }) => {
|
|
646
|
+
return ResourceStatusReason;
|
|
647
|
+
}).reverse();
|
|
648
|
+
events.forEach(({ LogicalResourceId, ResourceStatusReason }) => {
|
|
649
|
+
return _npmlog2.default.event(LogicalResourceId, ResourceStatusReason);
|
|
650
|
+
});
|
|
651
|
+
return events;
|
|
652
|
+
};
|
|
653
|
+
var describeStack = async ({ stackName }) => {
|
|
654
|
+
const stacks = await describeStacks({ stackName });
|
|
655
|
+
if (!stacks) {
|
|
656
|
+
throw new Error(`Stack ${stackName} not found and cannot be described.`);
|
|
657
|
+
}
|
|
658
|
+
return stacks[0];
|
|
659
|
+
};
|
|
660
|
+
var getStackOutput = async ({
|
|
661
|
+
stackName,
|
|
662
|
+
outputKey
|
|
663
|
+
}) => {
|
|
664
|
+
const { Outputs = [] } = await describeStack({ stackName });
|
|
665
|
+
const output = _optionalChain([Outputs, 'optionalAccess', _7 => _7.find, 'call', _8 => _8(({ OutputKey }) => {
|
|
666
|
+
return OutputKey === outputKey;
|
|
667
|
+
})]);
|
|
668
|
+
if (!output) {
|
|
669
|
+
throw new Error(`Output ${outputKey} doesn't exist on ${stackName} stack`);
|
|
670
|
+
}
|
|
671
|
+
return output;
|
|
672
|
+
};
|
|
673
|
+
var saveEnvironmentOutput = async ({
|
|
674
|
+
outputs,
|
|
675
|
+
stackName
|
|
676
|
+
}) => {
|
|
677
|
+
const envFile = { stackName };
|
|
678
|
+
envFile.outputs = outputs.reduce((acc, output) => {
|
|
679
|
+
if (!output.OutputKey || !output) {
|
|
680
|
+
return acc;
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
...acc,
|
|
684
|
+
[output.OutputKey]: output
|
|
685
|
+
};
|
|
686
|
+
}, {});
|
|
687
|
+
const dotCarlinFolderPath = path2.join(process.cwd(), ".carlin");
|
|
688
|
+
if (!fs3.existsSync(dotCarlinFolderPath)) {
|
|
689
|
+
await fs3.promises.mkdir(dotCarlinFolderPath);
|
|
690
|
+
}
|
|
691
|
+
const filePath = path2.join(dotCarlinFolderPath, `${stackName}.json`);
|
|
692
|
+
await fs3.promises.writeFile(filePath, JSON.stringify(envFile, null, 2));
|
|
693
|
+
};
|
|
694
|
+
var printStackOutputsAfterDeploy = async ({
|
|
695
|
+
stackName
|
|
696
|
+
}) => {
|
|
697
|
+
const {
|
|
698
|
+
EnableTerminationProtection,
|
|
699
|
+
StackName,
|
|
700
|
+
Outputs = []
|
|
701
|
+
} = await describeStack({ stackName });
|
|
702
|
+
await saveEnvironmentOutput({ stackName, outputs: Outputs });
|
|
703
|
+
_npmlog2.default.output("Describe Stack");
|
|
704
|
+
_npmlog2.default.output("StackName", StackName);
|
|
705
|
+
_npmlog2.default.output("EnableTerminationProtection", EnableTerminationProtection);
|
|
706
|
+
Outputs.forEach(({ OutputKey, OutputValue, Description, ExportName }) => {
|
|
707
|
+
_npmlog2.default.output(
|
|
708
|
+
`${OutputKey}`,
|
|
709
|
+
[
|
|
710
|
+
"",
|
|
711
|
+
`OutputKey: ${OutputKey}`,
|
|
712
|
+
`OutputValue: ${OutputValue}`,
|
|
713
|
+
`Description: ${Description}`,
|
|
714
|
+
`ExportName: ${ExportName}`,
|
|
715
|
+
""
|
|
716
|
+
].join("\n")
|
|
717
|
+
);
|
|
718
|
+
});
|
|
719
|
+
};
|
|
720
|
+
var deleteStack = async ({ stackName }) => {
|
|
721
|
+
_npmlog2.default.info(logPrefix3, `Deleting stack ${stackName}...`);
|
|
722
|
+
await cloudformation().send(new (0, _clientcloudformation.DeleteStackCommand)({ StackName: stackName }));
|
|
723
|
+
try {
|
|
724
|
+
await cloudFormationV2().waitFor("stackDeleteComplete", { StackName: stackName }).promise();
|
|
725
|
+
} catch (err) {
|
|
726
|
+
_npmlog2.default.error(logPrefix3, `An error occurred when deleting stack ${stackName}.`);
|
|
727
|
+
await describeStackEvents({ stackName });
|
|
728
|
+
throw err;
|
|
729
|
+
}
|
|
730
|
+
_npmlog2.default.info(logPrefix3, `Stack ${stackName} deleted.`);
|
|
731
|
+
};
|
|
732
|
+
var createStack = async ({
|
|
733
|
+
params
|
|
734
|
+
}) => {
|
|
735
|
+
const { StackName: stackName = "" } = params;
|
|
736
|
+
_npmlog2.default.info(logPrefix3, `Creating stack ${stackName}...`);
|
|
737
|
+
await cloudformation().send(new (0, _clientcloudformation.CreateStackCommand)(params));
|
|
738
|
+
try {
|
|
739
|
+
await cloudFormationV2().waitFor("stackCreateComplete", { StackName: stackName }).promise();
|
|
740
|
+
} catch (err) {
|
|
741
|
+
_npmlog2.default.error(logPrefix3, `An error occurred when creating stack ${stackName}.`);
|
|
742
|
+
await describeStackEvents({ stackName });
|
|
743
|
+
await deleteStack({ stackName });
|
|
744
|
+
throw err;
|
|
745
|
+
}
|
|
746
|
+
_npmlog2.default.info(logPrefix3, `Stack ${stackName} was created.`);
|
|
747
|
+
};
|
|
748
|
+
var updateStack = async ({
|
|
749
|
+
params
|
|
750
|
+
}) => {
|
|
751
|
+
const { StackName: stackName = "" } = params;
|
|
752
|
+
_npmlog2.default.info(logPrefix3, `Updating stack ${stackName}...`);
|
|
753
|
+
try {
|
|
754
|
+
await cloudformation().send(new (0, _clientcloudformation.UpdateStackCommand)(params));
|
|
755
|
+
await cloudFormationV2().waitFor("stackUpdateComplete", { StackName: stackName }).promise();
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (error.message === "No updates are to be performed.") {
|
|
758
|
+
_npmlog2.default.info(logPrefix3, error.message);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
_npmlog2.default.error(logPrefix3, "An error occurred when updating stack.");
|
|
762
|
+
await describeStackEvents({ stackName });
|
|
763
|
+
throw error;
|
|
764
|
+
}
|
|
765
|
+
_npmlog2.default.info(logPrefix3, `Stack ${stackName} was updated.`);
|
|
766
|
+
};
|
|
767
|
+
var enableTerminationProtection = async ({
|
|
768
|
+
stackName
|
|
769
|
+
}) => {
|
|
770
|
+
_npmlog2.default.info(logPrefix3, `Enabling termination protection...`);
|
|
771
|
+
try {
|
|
772
|
+
await cloudformation().send(
|
|
773
|
+
new (0, _clientcloudformation.UpdateTerminationProtectionCommand)({
|
|
774
|
+
EnableTerminationProtection: true,
|
|
775
|
+
StackName: stackName
|
|
776
|
+
})
|
|
777
|
+
);
|
|
778
|
+
} catch (err) {
|
|
779
|
+
_npmlog2.default.error(
|
|
780
|
+
logPrefix3,
|
|
781
|
+
"An error occurred when enabling termination protection"
|
|
782
|
+
);
|
|
783
|
+
throw err;
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
var defaultTemplatePaths = ["ts", "js", "yaml", "yml", "json"].map(
|
|
787
|
+
(extension) => {
|
|
788
|
+
return `src/cloudformation.${extension}`;
|
|
789
|
+
}
|
|
790
|
+
);
|
|
791
|
+
var deploy = async ({
|
|
792
|
+
terminationProtection = false,
|
|
793
|
+
...paramsAndTemplate
|
|
794
|
+
}) => {
|
|
795
|
+
const { params, template } = await addDefaults(paramsAndTemplate);
|
|
796
|
+
const stackName = params.StackName;
|
|
797
|
+
if (!stackName) {
|
|
798
|
+
throw new Error("StackName is required");
|
|
799
|
+
}
|
|
800
|
+
delete params.TemplateBody;
|
|
801
|
+
delete params.TemplateURL;
|
|
802
|
+
if (isTemplateBodyGreaterThanMaxSize(template)) {
|
|
803
|
+
const { url } = await uploadTemplateToBaseStackBucket({
|
|
804
|
+
stackName,
|
|
805
|
+
template
|
|
806
|
+
});
|
|
807
|
+
params.TemplateURL = url;
|
|
808
|
+
} else {
|
|
809
|
+
params.TemplateBody = JSON.stringify(template);
|
|
810
|
+
}
|
|
811
|
+
params.Capabilities = [
|
|
812
|
+
"CAPABILITY_AUTO_EXPAND",
|
|
813
|
+
"CAPABILITY_IAM",
|
|
814
|
+
"CAPABILITY_NAMED_IAM"
|
|
815
|
+
];
|
|
816
|
+
if (await doesStackExist({ stackName })) {
|
|
817
|
+
await updateStack({ params });
|
|
818
|
+
} else {
|
|
819
|
+
await createStack({ params });
|
|
820
|
+
}
|
|
821
|
+
if (terminationProtection || !!getEnvironment()) {
|
|
822
|
+
await enableTerminationProtection({ stackName });
|
|
823
|
+
}
|
|
824
|
+
await printStackOutputsAfterDeploy({ stackName });
|
|
825
|
+
return describeStack({ stackName });
|
|
826
|
+
};
|
|
827
|
+
var canDestroyStack = async ({ stackName }) => {
|
|
828
|
+
const { EnableTerminationProtection } = await describeStack({ stackName });
|
|
829
|
+
if (EnableTerminationProtection) {
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
return true;
|
|
833
|
+
};
|
|
834
|
+
var validateTemplate = async ({
|
|
835
|
+
stackName,
|
|
836
|
+
template
|
|
837
|
+
}) => {
|
|
838
|
+
const validateTemplateCommandInput = {};
|
|
839
|
+
if (isTemplateBodyGreaterThanMaxSize(template)) {
|
|
840
|
+
const { url } = await uploadTemplateToBaseStackBucket({
|
|
841
|
+
stackName,
|
|
842
|
+
template
|
|
843
|
+
});
|
|
844
|
+
validateTemplateCommandInput.TemplateURL = url;
|
|
845
|
+
} else {
|
|
846
|
+
validateTemplateCommandInput.TemplateBody = JSON.stringify(template);
|
|
847
|
+
}
|
|
848
|
+
await cloudformation().send(
|
|
849
|
+
new (0, _clientcloudformation.ValidateTemplateCommand)(validateTemplateCommandInput)
|
|
850
|
+
);
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/deploy/baseStack/getBucketTemplate.ts
|
|
854
|
+
var getBucketTemplate = () => {
|
|
855
|
+
return {
|
|
856
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
857
|
+
Resources: {
|
|
858
|
+
[BASE_STACK_BUCKET_LOGICAL_NAME]: {
|
|
859
|
+
Type: "AWS::S3::Bucket",
|
|
860
|
+
DeletionPolicy: "Retain",
|
|
861
|
+
Properties: {
|
|
862
|
+
LifecycleConfiguration: {
|
|
863
|
+
Rules: [
|
|
864
|
+
{
|
|
865
|
+
ExpirationInDays: 1,
|
|
866
|
+
Prefix: BASE_STACK_BUCKET_TEMPLATES_FOLDER,
|
|
867
|
+
Status: "Enabled"
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
NoncurrentVersionExpirationInDays: 3,
|
|
871
|
+
Status: "Enabled"
|
|
872
|
+
}
|
|
873
|
+
]
|
|
874
|
+
},
|
|
875
|
+
/**
|
|
876
|
+
* This is necessary because if we update Lambda code without change
|
|
877
|
+
* CloudFormation template, the Lambda will not be updated.
|
|
878
|
+
*/
|
|
879
|
+
VersioningConfiguration: {
|
|
880
|
+
Status: "Enabled"
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
},
|
|
885
|
+
Outputs: {
|
|
886
|
+
[BASE_STACK_BUCKET_LOGICAL_NAME]: {
|
|
887
|
+
Value: { Ref: BASE_STACK_BUCKET_LOGICAL_NAME },
|
|
888
|
+
Export: {
|
|
889
|
+
Name: BASE_STACK_BUCKET_NAME_EXPORTED_NAME
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
// src/deploy/baseStack/getLambdaImageBuilderTemplate.ts
|
|
897
|
+
|
|
898
|
+
var getLambdaImageBuilderTemplate = () => {
|
|
899
|
+
const CODE_BUILD_PROJECT_LOGS_LOGICAL_ID2 = "CodeBuildProjectLogsLogGroup";
|
|
900
|
+
const CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID2 = "ImageCodeBuildProjectIAMRole";
|
|
901
|
+
return {
|
|
902
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
903
|
+
Resources: {
|
|
904
|
+
[CODE_BUILD_PROJECT_LOGS_LOGICAL_ID2]: {
|
|
905
|
+
Type: "AWS::Logs::LogGroup",
|
|
906
|
+
DeletionPolicy: "Delete",
|
|
907
|
+
Properties: {}
|
|
908
|
+
},
|
|
909
|
+
[CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID2]: {
|
|
910
|
+
Type: "AWS::IAM::Role",
|
|
911
|
+
Properties: {
|
|
912
|
+
AssumeRolePolicyDocument: {
|
|
913
|
+
Version: "2012-10-17",
|
|
914
|
+
Statement: [
|
|
915
|
+
{
|
|
916
|
+
Effect: "Allow",
|
|
917
|
+
Principal: {
|
|
918
|
+
Service: "codebuild.amazonaws.com"
|
|
919
|
+
},
|
|
920
|
+
Action: "sts:AssumeRole"
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
},
|
|
924
|
+
Path: getIamPath(),
|
|
925
|
+
Policies: [
|
|
926
|
+
{
|
|
927
|
+
PolicyName: `${CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID2}Policy`,
|
|
928
|
+
PolicyDocument: {
|
|
929
|
+
Version: "2012-10-17",
|
|
930
|
+
Statement: [
|
|
931
|
+
{
|
|
932
|
+
Effect: "Allow",
|
|
933
|
+
Action: ["logs:CreateLogStream", "logs:PutLogEvents"],
|
|
934
|
+
Resource: "*"
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
Effect: "Allow",
|
|
938
|
+
Action: ["ecr:GetAuthorizationToken"],
|
|
939
|
+
Resource: "*"
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
Effect: "Allow",
|
|
943
|
+
Action: [
|
|
944
|
+
"ecr:BatchCheckLayerAvailability",
|
|
945
|
+
"ecr:CompleteLayerUpload",
|
|
946
|
+
"ecr:InitiateLayerUpload",
|
|
947
|
+
"ecr:PutImage",
|
|
948
|
+
"ecr:UploadLayerPart"
|
|
949
|
+
],
|
|
950
|
+
Resource: "*"
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
Effect: "Allow",
|
|
954
|
+
Action: "s3:GetObject",
|
|
955
|
+
Resource: [
|
|
956
|
+
{
|
|
957
|
+
"Fn::Sub": [
|
|
958
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
959
|
+
"arn:aws:s3:::${BucketName}/*",
|
|
960
|
+
{
|
|
961
|
+
BucketName: {
|
|
962
|
+
Ref: BASE_STACK_BUCKET_LOGICAL_NAME
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
]
|
|
966
|
+
}
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
]
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
]
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
[BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME]: {
|
|
976
|
+
Type: "AWS::CodeBuild::Project",
|
|
977
|
+
Properties: {
|
|
978
|
+
Artifacts: {
|
|
979
|
+
Type: "NO_ARTIFACTS"
|
|
980
|
+
},
|
|
981
|
+
Cache: {
|
|
982
|
+
Location: "LOCAL",
|
|
983
|
+
Modes: ["LOCAL_DOCKER_LAYER_CACHE"],
|
|
984
|
+
Type: "LOCAL"
|
|
985
|
+
},
|
|
986
|
+
Description: "Create Lambda image.",
|
|
987
|
+
Environment: {
|
|
988
|
+
ComputeType: "BUILD_GENERAL1_SMALL",
|
|
989
|
+
EnvironmentVariables: [
|
|
990
|
+
{
|
|
991
|
+
Name: "AWS_ACCOUNT_ID",
|
|
992
|
+
Value: { Ref: "AWS::AccountId" }
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
Name: "AWS_REGION",
|
|
996
|
+
Value: { Ref: "AWS::Region" }
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
Name: "IMAGE_TAG",
|
|
1000
|
+
Value: "latest"
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
Name: "LAMBDA_EXTERNALS",
|
|
1004
|
+
Value: ""
|
|
1005
|
+
}
|
|
1006
|
+
],
|
|
1007
|
+
Image: "aws/codebuild/standard:3.0",
|
|
1008
|
+
ImagePullCredentialsType: "CODEBUILD",
|
|
1009
|
+
PrivilegedMode: true,
|
|
1010
|
+
Type: "LINUX_CONTAINER"
|
|
1011
|
+
},
|
|
1012
|
+
LogsConfig: {
|
|
1013
|
+
CloudWatchLogs: {
|
|
1014
|
+
Status: "ENABLED",
|
|
1015
|
+
GroupName: { Ref: CODE_BUILD_PROJECT_LOGS_LOGICAL_ID2 }
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
ServiceRole: {
|
|
1019
|
+
"Fn::GetAtt": [CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID2, "Arn"]
|
|
1020
|
+
},
|
|
1021
|
+
Source: {
|
|
1022
|
+
BuildSpec: _jsyaml2.default.dump({
|
|
1023
|
+
version: "0.2",
|
|
1024
|
+
phases: {
|
|
1025
|
+
install: {
|
|
1026
|
+
commands: [
|
|
1027
|
+
"echo install started on `date`",
|
|
1028
|
+
"npm init -y",
|
|
1029
|
+
/**
|
|
1030
|
+
* https://stackoverflow.com/a/51433146/8786986
|
|
1031
|
+
*/
|
|
1032
|
+
"npm install --save --package-lock-only --no-package-lock $LAMBDA_EXTERNALS",
|
|
1033
|
+
"ls"
|
|
1034
|
+
]
|
|
1035
|
+
},
|
|
1036
|
+
pre_build: {
|
|
1037
|
+
commands: [
|
|
1038
|
+
"echo pre_build started on `date`",
|
|
1039
|
+
"$(aws ecr get-login --no-include-email --region $AWS_REGION)"
|
|
1040
|
+
]
|
|
1041
|
+
},
|
|
1042
|
+
build: {
|
|
1043
|
+
commands: [
|
|
1044
|
+
"echo build started on `date`",
|
|
1045
|
+
"echo Building the repository image...",
|
|
1046
|
+
'echo "$DOCKERFILE" > Dockerfile',
|
|
1047
|
+
"docker build -t $REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile .",
|
|
1048
|
+
"docker tag $REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG"
|
|
1049
|
+
]
|
|
1050
|
+
},
|
|
1051
|
+
post_build: {
|
|
1052
|
+
commands: [
|
|
1053
|
+
"echo post_build completed on `date`",
|
|
1054
|
+
"echo Pushing the repository image...",
|
|
1055
|
+
"docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG"
|
|
1056
|
+
]
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}),
|
|
1060
|
+
Type: "NO_SOURCE"
|
|
1061
|
+
},
|
|
1062
|
+
TimeoutInMinutes: 60
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
Outputs: {
|
|
1067
|
+
[BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME]: {
|
|
1068
|
+
Value: { Ref: BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME },
|
|
1069
|
+
Export: {
|
|
1070
|
+
Name: BASE_STACK_LAMBDA_IMAGE_BUILDER_EXPORTED_NAME
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
// src/deploy/baseStack/getLambdaLayerBuilderTemplate.ts
|
|
1078
|
+
var CODE_BUILD_PROJECT_LOGS_GROUP_LOGICAL_ID = `${BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME}LogsLogGroup`;
|
|
1079
|
+
var CODE_BUILD_PROJECT_IAM_ROLE_LOGICAL_ID = `${BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME}Role`;
|
|
1080
|
+
var getBuildSpec = () => {
|
|
1081
|
+
return `
|
|
1082
|
+
version: 0.2
|
|
1083
|
+
phases:
|
|
1084
|
+
install:
|
|
1085
|
+
runtime-versions:
|
|
1086
|
+
nodejs: 12
|
|
1087
|
+
commands:
|
|
1088
|
+
- npm i --no-bin-links --no-optional --no-package-lock --no-save --no-shrinkwrap $PACKAGE_NAME
|
|
1089
|
+
- mkdir nodejs
|
|
1090
|
+
- mv node_modules nodejs/node_modules
|
|
1091
|
+
artifacts:
|
|
1092
|
+
files:
|
|
1093
|
+
- nodejs/**/*
|
|
1094
|
+
name: $PACKAGE_NAME.zip
|
|
1095
|
+
`.trim();
|
|
1096
|
+
};
|
|
1097
|
+
var getLambdaLayerBuilderTemplate = () => {
|
|
1098
|
+
return {
|
|
1099
|
+
Resources: {
|
|
1100
|
+
[CODE_BUILD_PROJECT_IAM_ROLE_LOGICAL_ID]: {
|
|
1101
|
+
Type: "AWS::IAM::Role",
|
|
1102
|
+
Properties: {
|
|
1103
|
+
AssumeRolePolicyDocument: {
|
|
1104
|
+
Version: "2012-10-17",
|
|
1105
|
+
Statement: [
|
|
1106
|
+
{
|
|
1107
|
+
Effect: "Allow",
|
|
1108
|
+
Principal: {
|
|
1109
|
+
Service: ["codebuild.amazonaws.com"]
|
|
1110
|
+
},
|
|
1111
|
+
Action: ["sts:AssumeRole"]
|
|
1112
|
+
}
|
|
1113
|
+
]
|
|
1114
|
+
},
|
|
1115
|
+
Path: getIamPath(),
|
|
1116
|
+
Policies: [
|
|
1117
|
+
{
|
|
1118
|
+
PolicyName: `${CODE_BUILD_PROJECT_IAM_ROLE_LOGICAL_ID}Policy`,
|
|
1119
|
+
PolicyDocument: {
|
|
1120
|
+
Version: "2012-10-17",
|
|
1121
|
+
Statement: [
|
|
1122
|
+
{
|
|
1123
|
+
Effect: "Allow",
|
|
1124
|
+
Action: ["logs:CreateLogStream", "logs:PutLogEvents"],
|
|
1125
|
+
Resource: "*"
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
Effect: "Allow",
|
|
1129
|
+
Action: ["s3:*"],
|
|
1130
|
+
Resource: [
|
|
1131
|
+
{
|
|
1132
|
+
"Fn::Sub": [
|
|
1133
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
1134
|
+
"arn:aws:s3:::${BucketName}",
|
|
1135
|
+
{
|
|
1136
|
+
BucketName: {
|
|
1137
|
+
Ref: BASE_STACK_BUCKET_LOGICAL_NAME
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
]
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
"Fn::Sub": [
|
|
1144
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
1145
|
+
"arn:aws:s3:::${BucketName}/*",
|
|
1146
|
+
{
|
|
1147
|
+
BucketName: {
|
|
1148
|
+
Ref: BASE_STACK_BUCKET_LOGICAL_NAME
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
]
|
|
1152
|
+
}
|
|
1153
|
+
]
|
|
1154
|
+
}
|
|
1155
|
+
]
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
]
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
[CODE_BUILD_PROJECT_LOGS_GROUP_LOGICAL_ID]: {
|
|
1162
|
+
Type: "AWS::Logs::LogGroup",
|
|
1163
|
+
DeletionPolicy: "Delete",
|
|
1164
|
+
Properties: {}
|
|
1165
|
+
},
|
|
1166
|
+
[BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME]: {
|
|
1167
|
+
Type: "AWS::CodeBuild::Project",
|
|
1168
|
+
Properties: {
|
|
1169
|
+
Artifacts: {
|
|
1170
|
+
Location: { Ref: BASE_STACK_BUCKET_LOGICAL_NAME },
|
|
1171
|
+
NamespaceType: "NONE",
|
|
1172
|
+
OverrideArtifactName: true,
|
|
1173
|
+
Packaging: "ZIP",
|
|
1174
|
+
Path: "lambda-layers/packages",
|
|
1175
|
+
Type: "S3"
|
|
1176
|
+
},
|
|
1177
|
+
Environment: {
|
|
1178
|
+
ComputeType: "BUILD_GENERAL1_SMALL",
|
|
1179
|
+
Image: "aws/codebuild/standard:3.0",
|
|
1180
|
+
Type: "LINUX_CONTAINER"
|
|
1181
|
+
},
|
|
1182
|
+
LogsConfig: {
|
|
1183
|
+
CloudWatchLogs: {
|
|
1184
|
+
GroupName: {
|
|
1185
|
+
Ref: `${CODE_BUILD_PROJECT_LOGS_GROUP_LOGICAL_ID}`
|
|
1186
|
+
},
|
|
1187
|
+
Status: "ENABLED"
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
ServiceRole: {
|
|
1191
|
+
"Fn::GetAtt": `${CODE_BUILD_PROJECT_IAM_ROLE_LOGICAL_ID}.Arn`
|
|
1192
|
+
},
|
|
1193
|
+
Source: {
|
|
1194
|
+
BuildSpec: getBuildSpec(),
|
|
1195
|
+
Type: "NO_SOURCE"
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
},
|
|
1200
|
+
Outputs: {
|
|
1201
|
+
[BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME]: {
|
|
1202
|
+
Value: { Ref: BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME }
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
// src/deploy/baseStack/getVpcTemplate.ts
|
|
1209
|
+
|
|
1210
|
+
var getVpcTemplate = () => {
|
|
1211
|
+
const vpcName = `${_changecase.pascalCase.call(void 0, NAME)}VPC`;
|
|
1212
|
+
const EC2_INTERNET_GATEWAY_LOGICAL_ID = "EC2InternetGateway";
|
|
1213
|
+
const EC2_ROUTE_TABLE_LOGICAL_ID = "EC2RouteTable";
|
|
1214
|
+
const EC2_VPC_LOGICAL_ID = "EC2VCP";
|
|
1215
|
+
const template = {
|
|
1216
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
1217
|
+
Mappings: {
|
|
1218
|
+
CidrMappings: {
|
|
1219
|
+
VPC: {
|
|
1220
|
+
CIDR: "10.0.0.0/16"
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
Resources: {
|
|
1225
|
+
[EC2_VPC_LOGICAL_ID]: {
|
|
1226
|
+
Type: "AWS::EC2::VPC",
|
|
1227
|
+
Properties: {
|
|
1228
|
+
CidrBlock: {
|
|
1229
|
+
"Fn::FindInMap": ["CidrMappings", "VPC", "CIDR"]
|
|
1230
|
+
},
|
|
1231
|
+
EnableDnsHostnames: true,
|
|
1232
|
+
EnableDnsSupport: true,
|
|
1233
|
+
Tags: [
|
|
1234
|
+
{
|
|
1235
|
+
Key: "Name",
|
|
1236
|
+
Value: vpcName
|
|
1237
|
+
}
|
|
1238
|
+
]
|
|
1239
|
+
}
|
|
1240
|
+
},
|
|
1241
|
+
[EC2_INTERNET_GATEWAY_LOGICAL_ID]: {
|
|
1242
|
+
Type: "AWS::EC2::InternetGateway",
|
|
1243
|
+
Properties: {}
|
|
1244
|
+
},
|
|
1245
|
+
EC2VPCGatewayAttachment: {
|
|
1246
|
+
Type: "AWS::EC2::VPCGatewayAttachment",
|
|
1247
|
+
Properties: {
|
|
1248
|
+
InternetGatewayId: {
|
|
1249
|
+
Ref: EC2_INTERNET_GATEWAY_LOGICAL_ID
|
|
1250
|
+
},
|
|
1251
|
+
VpcId: {
|
|
1252
|
+
Ref: EC2_VPC_LOGICAL_ID
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
[EC2_ROUTE_TABLE_LOGICAL_ID]: {
|
|
1257
|
+
Type: "AWS::EC2::RouteTable",
|
|
1258
|
+
Properties: {
|
|
1259
|
+
Tags: [
|
|
1260
|
+
{
|
|
1261
|
+
Key: "Name",
|
|
1262
|
+
Value: {
|
|
1263
|
+
"Fn::Join": [" ", [vpcName, "-", EC2_ROUTE_TABLE_LOGICAL_ID]]
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
],
|
|
1267
|
+
VpcId: {
|
|
1268
|
+
Ref: EC2_VPC_LOGICAL_ID
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
},
|
|
1272
|
+
EC2Route: {
|
|
1273
|
+
Type: "AWS::EC2::Route",
|
|
1274
|
+
Properties: {
|
|
1275
|
+
DestinationCidrBlock: "0.0.0.0/0",
|
|
1276
|
+
GatewayId: {
|
|
1277
|
+
Ref: EC2_INTERNET_GATEWAY_LOGICAL_ID
|
|
1278
|
+
},
|
|
1279
|
+
RouteTableId: {
|
|
1280
|
+
Ref: EC2_ROUTE_TABLE_LOGICAL_ID
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
},
|
|
1285
|
+
Outputs: {
|
|
1286
|
+
VPCId: {
|
|
1287
|
+
Value: {
|
|
1288
|
+
Ref: EC2_VPC_LOGICAL_ID
|
|
1289
|
+
},
|
|
1290
|
+
Export: {
|
|
1291
|
+
Name: BASE_STACK_VPC_ID_EXPORTED_NAME
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
VPCDefaultSecurityGroup: {
|
|
1295
|
+
Value: {
|
|
1296
|
+
"Fn::GetAtt": [EC2_VPC_LOGICAL_ID, "DefaultSecurityGroup"]
|
|
1297
|
+
},
|
|
1298
|
+
Export: {
|
|
1299
|
+
Name: BASE_STACK_VPC_DEFAULT_SECURITY_GROUP_EXPORTED_NAME
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
[
|
|
1305
|
+
BASE_STACK_VPC_PUBLIC_SUBNET_0_EXPORTED_NAME,
|
|
1306
|
+
BASE_STACK_VPC_PUBLIC_SUBNET_1_EXPORTED_NAME,
|
|
1307
|
+
BASE_STACK_VPC_PUBLIC_SUBNET_2_EXPORTED_NAME
|
|
1308
|
+
].forEach((publicSubnetExportedName, index) => {
|
|
1309
|
+
const publicSubnetLogicalId = `PublicSubnet${index}EC2Subnet`;
|
|
1310
|
+
const publicSubnetCidrMappings = `PublicSubnet${index}`;
|
|
1311
|
+
template.Mappings.CidrMappings[publicSubnetCidrMappings] = {
|
|
1312
|
+
CIDR: `10.0.${index}.0/24`
|
|
1313
|
+
};
|
|
1314
|
+
template.Resources[publicSubnetLogicalId] = {
|
|
1315
|
+
Type: "AWS::EC2::Subnet",
|
|
1316
|
+
Properties: {
|
|
1317
|
+
AvailabilityZone: {
|
|
1318
|
+
"Fn::Select": [
|
|
1319
|
+
index,
|
|
1320
|
+
{
|
|
1321
|
+
"Fn::GetAZs": {
|
|
1322
|
+
Ref: "AWS::Region"
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
]
|
|
1326
|
+
},
|
|
1327
|
+
CidrBlock: {
|
|
1328
|
+
"Fn::FindInMap": ["CidrMappings", publicSubnetCidrMappings, "CIDR"]
|
|
1329
|
+
},
|
|
1330
|
+
MapPublicIpOnLaunch: true,
|
|
1331
|
+
Tags: [
|
|
1332
|
+
{
|
|
1333
|
+
Key: "Name",
|
|
1334
|
+
Value: {
|
|
1335
|
+
"Fn::Join": [
|
|
1336
|
+
" ",
|
|
1337
|
+
[EC2_VPC_LOGICAL_ID, "-", publicSubnetLogicalId]
|
|
1338
|
+
]
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
],
|
|
1342
|
+
VpcId: {
|
|
1343
|
+
Ref: EC2_VPC_LOGICAL_ID
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
template.Resources[`PublicSubnet${index}EC2SubnetRouteTableAssociation`] = {
|
|
1348
|
+
Type: "AWS::EC2::SubnetRouteTableAssociation",
|
|
1349
|
+
Properties: {
|
|
1350
|
+
RouteTableId: {
|
|
1351
|
+
Ref: EC2_ROUTE_TABLE_LOGICAL_ID
|
|
1352
|
+
},
|
|
1353
|
+
SubnetId: {
|
|
1354
|
+
Ref: publicSubnetLogicalId
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
if (!template.Outputs) {
|
|
1359
|
+
template.Outputs = {};
|
|
1360
|
+
}
|
|
1361
|
+
template.Outputs[publicSubnetLogicalId] = {
|
|
1362
|
+
Value: {
|
|
1363
|
+
Ref: publicSubnetLogicalId
|
|
1364
|
+
},
|
|
1365
|
+
Export: {
|
|
1366
|
+
Name: publicSubnetExportedName
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
});
|
|
1370
|
+
return template;
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
// src/deploy/stackName.ts
|
|
1374
|
+
|
|
1375
|
+
var setPreDefinedStackName = (stackName) => {
|
|
1376
|
+
setEnvVar("STACK_NAME", stackName);
|
|
1377
|
+
};
|
|
1378
|
+
var STACK_NAME_MAX_LENGTH = 100;
|
|
1379
|
+
var limitStackName = (stackName) => {
|
|
1380
|
+
return `${stackName}`.substring(0, STACK_NAME_MAX_LENGTH);
|
|
1381
|
+
};
|
|
1382
|
+
var getStackName = async () => {
|
|
1383
|
+
if (getEnvVar("STACK_NAME")) {
|
|
1384
|
+
return getEnvVar("STACK_NAME");
|
|
1385
|
+
}
|
|
1386
|
+
const [currentBranch, environment, packageName] = await Promise.all([
|
|
1387
|
+
getCurrentBranch(),
|
|
1388
|
+
getEnvironment(),
|
|
1389
|
+
getPackageName()
|
|
1390
|
+
]);
|
|
1391
|
+
const firstName = packageName ? _changecase.pascalCase.call(void 0, packageName) : `Stack-${Math.round(Math.random() * 1e5)}`;
|
|
1392
|
+
const secondName = (() => {
|
|
1393
|
+
if (environment) {
|
|
1394
|
+
return environment;
|
|
1395
|
+
}
|
|
1396
|
+
if (currentBranch) {
|
|
1397
|
+
return _changecase.paramCase.call(void 0, currentBranch);
|
|
1398
|
+
}
|
|
1399
|
+
return void 0;
|
|
1400
|
+
})();
|
|
1401
|
+
const name = [firstName, secondName].filter((word) => {
|
|
1402
|
+
return !!word;
|
|
1403
|
+
}).join("-");
|
|
1404
|
+
return limitStackName(name);
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
// src/deploy/utils.ts
|
|
1408
|
+
|
|
1409
|
+
var deployErrorLogs = ({
|
|
1410
|
+
error,
|
|
1411
|
+
logPrefix: logPrefix22
|
|
1412
|
+
}) => {
|
|
1413
|
+
_npmlog2.default.error(logPrefix22, `An error occurred. Cannot deploy ${logPrefix22}.`);
|
|
1414
|
+
_npmlog2.default.error(logPrefix22, "Error message: %j", _optionalChain([error, 'optionalAccess', _9 => _9.message]));
|
|
1415
|
+
};
|
|
1416
|
+
var handleDeployError = ({
|
|
1417
|
+
error,
|
|
1418
|
+
logPrefix: logPrefix22
|
|
1419
|
+
}) => {
|
|
1420
|
+
deployErrorLogs({ error, logPrefix: logPrefix22 });
|
|
1421
|
+
process.exit(1);
|
|
1422
|
+
};
|
|
1423
|
+
var handleDeployInitialization = async ({
|
|
1424
|
+
logPrefix: logPrefix22,
|
|
1425
|
+
stackName: preDefinedStackName
|
|
1426
|
+
}) => {
|
|
1427
|
+
_npmlog2.default.info(logPrefix22, `Starting deploy ${logPrefix22}...`);
|
|
1428
|
+
if (preDefinedStackName) {
|
|
1429
|
+
setPreDefinedStackName(preDefinedStackName);
|
|
1430
|
+
}
|
|
1431
|
+
const stackName = await getStackName();
|
|
1432
|
+
_npmlog2.default.info(logPrefix22, `stackName: ${stackName}`);
|
|
1433
|
+
return { stackName };
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
// src/deploy/baseStack/deployBaseStack.ts
|
|
1437
|
+
var _deepmerge = require('deepmerge'); var _deepmerge2 = _interopRequireDefault(_deepmerge);
|
|
1438
|
+
var logPrefix4 = "base-stack";
|
|
1439
|
+
var baseStackTemplate = _deepmerge2.default.all([
|
|
1440
|
+
getBucketTemplate(),
|
|
1441
|
+
getLambdaImageBuilderTemplate(),
|
|
1442
|
+
getLambdaLayerBuilderTemplate(),
|
|
1443
|
+
getVpcTemplate()
|
|
1444
|
+
]);
|
|
1445
|
+
var deployBaseStack = async () => {
|
|
1446
|
+
try {
|
|
1447
|
+
const { stackName } = await handleDeployInitialization({
|
|
1448
|
+
logPrefix: logPrefix4,
|
|
1449
|
+
stackName: BASE_STACK_NAME
|
|
1450
|
+
});
|
|
1451
|
+
await deploy({
|
|
1452
|
+
template: baseStackTemplate,
|
|
1453
|
+
params: { StackName: stackName },
|
|
1454
|
+
terminationProtection: true
|
|
1455
|
+
});
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
handleDeployError({ error, logPrefix: logPrefix4 });
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
|
|
1461
|
+
// src/deploy/baseStack/command.ts
|
|
1462
|
+
var deployBaseStackCommand = {
|
|
1463
|
+
command: "base-stack",
|
|
1464
|
+
describe: "Create base resources.",
|
|
1465
|
+
handler: deployBaseStack
|
|
1466
|
+
};
|
|
1467
|
+
|
|
1468
|
+
// src/deploy/cicd/deployCicd.ts
|
|
1469
|
+
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
// src/deploy/cicd/command.options.ts
|
|
1473
|
+
|
|
1474
|
+
var _helpers = require('yargs/helpers');
|
|
1475
|
+
|
|
1476
|
+
// src/deploy/cicd/config.ts
|
|
1477
|
+
var ECS_TASK_DEFAULT_CPU = "2048";
|
|
1478
|
+
var ECS_TASK_DEFAULT_MEMORY = "4096";
|
|
1479
|
+
var PIPELINE_ECS_TASK_EXECUTION_STAGE_NAME = `PipelineRunECSTasksStage`;
|
|
1480
|
+
var PIPELINE_ECS_TASK_EXECUTION_MANUAL_APPROVAL_ACTION_NAME = `PipelineRunECSTasksApproval`;
|
|
1481
|
+
|
|
1482
|
+
// src/deploy/cicd/pipelines.ts
|
|
1483
|
+
var pipelines = ["pr", "main", "tag"];
|
|
1484
|
+
|
|
1485
|
+
// src/deploy/cicd/command.options.ts
|
|
1486
|
+
var _yargs = require('yargs'); var _yargs2 = _interopRequireDefault(_yargs);
|
|
1487
|
+
var options = {
|
|
1488
|
+
cpu: {
|
|
1489
|
+
type: "string"
|
|
1490
|
+
},
|
|
1491
|
+
memory: {
|
|
1492
|
+
type: "string"
|
|
1493
|
+
},
|
|
1494
|
+
pipelines: {
|
|
1495
|
+
choices: pipelines,
|
|
1496
|
+
coerce: (values) => {
|
|
1497
|
+
return values.map((value) => {
|
|
1498
|
+
return _changecase.camelCase.call(void 0, value);
|
|
1499
|
+
});
|
|
1500
|
+
},
|
|
1501
|
+
default: [],
|
|
1502
|
+
description: "Pipelines that will be implemented with the CICD stack.",
|
|
1503
|
+
type: "array"
|
|
1504
|
+
},
|
|
1505
|
+
"update-repository": {
|
|
1506
|
+
alias: ["ur"],
|
|
1507
|
+
description: "Determine if the repository image will be updated.",
|
|
1508
|
+
default: true,
|
|
1509
|
+
type: "boolean"
|
|
1510
|
+
},
|
|
1511
|
+
"ssh-key": {
|
|
1512
|
+
demandOption: true,
|
|
1513
|
+
type: "string"
|
|
1514
|
+
},
|
|
1515
|
+
"ssh-url": {
|
|
1516
|
+
demandOption: true,
|
|
1517
|
+
type: "string"
|
|
1518
|
+
},
|
|
1519
|
+
"slack-webhook-url": {
|
|
1520
|
+
type: "string"
|
|
1521
|
+
},
|
|
1522
|
+
/**
|
|
1523
|
+
* This option has the format:
|
|
1524
|
+
*
|
|
1525
|
+
* ```ts
|
|
1526
|
+
* Array<{
|
|
1527
|
+
* name: string,
|
|
1528
|
+
* value: string,
|
|
1529
|
+
* }>
|
|
1530
|
+
* ```
|
|
1531
|
+
*/
|
|
1532
|
+
"task-environment": {
|
|
1533
|
+
alias: ["te"],
|
|
1534
|
+
default: [],
|
|
1535
|
+
describe: "A list of environment variables that will be passed to the ECS container task.",
|
|
1536
|
+
type: "array"
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
var getCicdConfig = () => {
|
|
1540
|
+
const { parsed } = _yargs2.default.call(void 0, _helpers.hideBin.call(void 0, process.argv)).config();
|
|
1541
|
+
if (!parsed) {
|
|
1542
|
+
return false;
|
|
1543
|
+
}
|
|
1544
|
+
const { argv } = parsed;
|
|
1545
|
+
const config = Object.keys(options).reduce((acc, key) => {
|
|
1546
|
+
const value = argv[key];
|
|
1547
|
+
if (value) {
|
|
1548
|
+
acc[key] = value;
|
|
1549
|
+
}
|
|
1550
|
+
return acc;
|
|
1551
|
+
}, {});
|
|
1552
|
+
return config;
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
// src/deploy/cicd/getTriggerPipelineObjectKey.ts
|
|
1556
|
+
var getTriggerPipelinesObjectKey = ({
|
|
1557
|
+
prefix,
|
|
1558
|
+
pipeline
|
|
1559
|
+
}) => {
|
|
1560
|
+
return `${prefix}/${pipeline}.zip`;
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
// src/deploy/cicd/cicd.template.ts
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
var API_LOGICAL_ID = "ApiV1ServerlessApi";
|
|
1567
|
+
var CODE_BUILD_PROJECT_LOGS_LOGICAL_ID = "RepositoryImageCodeBuildProjectLogsLogGroup";
|
|
1568
|
+
var CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID = "RepositoryImageCodeBuildProjectIAMRole";
|
|
1569
|
+
var ECR_REPOSITORY_LOGICAL_ID = "RepositoryECRRepository";
|
|
1570
|
+
var FUNCTION_IAM_ROLE_LOGICAL_ID = "ApiV1ServerlessFunctionIAMRole";
|
|
1571
|
+
var ECS_TASK_REPORT_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID = "EcsTaskReportHandler";
|
|
1572
|
+
var PROCESS_ENV_REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME = "REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME";
|
|
1573
|
+
var REPOSITORY_ECS_TASK_CONTAINER_NAME = "RepositoryECSTaskContainerName";
|
|
1574
|
+
var REPOSITORY_ECS_TASK_DEFINITION_LOGICAL_ID = "RepositoryECSTaskDefinition";
|
|
1575
|
+
var REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID = "RepositoryImageCodeBuildProject";
|
|
1576
|
+
var REPOSITORY_TASKS_ECS_CLUSTER_LOGICAL_ID = "RepositoryTasksECSCluster";
|
|
1577
|
+
var REPOSITORY_TASKS_ECS_CLUSTER_LOGS_LOG_GROUP_LOGICAL_ID = "RepositoryTasksECSClusterLogsLogGroup";
|
|
1578
|
+
var REPOSITORY_TASKS_ECS_TASK_DEFINITION_EXECUTION_ROLE_LOGICAL_ID = "RepositoryTasksECSTaskDefinitionExecutionRoleIAMRole";
|
|
1579
|
+
var REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID = "RepositoryTasksECSTaskDefinitionTaskRoleIAMRole";
|
|
1580
|
+
var PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID = "PipelinesArtifactStoreS3Bucket";
|
|
1581
|
+
var PIPELINES_ROLE_LOGICAL_ID = "PipelinesMainIAMRole";
|
|
1582
|
+
var PIPELINES_MAIN_LOGICAL_ID = "PipelinesMainCodePipeline";
|
|
1583
|
+
var PIPELINES_TAG_LOGICAL_ID = "PipelinesTagCodePipeline";
|
|
1584
|
+
var PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID = "PipelinesHandlerLambdaFunction";
|
|
1585
|
+
var IMAGE_UPDATER_SCHEDULE_SERVERLESS_FUNCTION_LOGICAL_ID = "ImageUpdaterScheduleServerlessFunction";
|
|
1586
|
+
var getRepositoryImageBuilder = () => {
|
|
1587
|
+
const nodeRuntimeNumber = NODE_RUNTIME.replace("nodejs", "").replace(
|
|
1588
|
+
".x",
|
|
1589
|
+
""
|
|
1590
|
+
);
|
|
1591
|
+
return {
|
|
1592
|
+
Type: "AWS::CodeBuild::Project",
|
|
1593
|
+
Properties: {
|
|
1594
|
+
Artifacts: {
|
|
1595
|
+
Type: "NO_ARTIFACTS"
|
|
1596
|
+
},
|
|
1597
|
+
Cache: {
|
|
1598
|
+
Location: "LOCAL",
|
|
1599
|
+
Modes: ["LOCAL_DOCKER_LAYER_CACHE"],
|
|
1600
|
+
Type: "LOCAL"
|
|
1601
|
+
},
|
|
1602
|
+
Description: "Create repository image.",
|
|
1603
|
+
Environment: {
|
|
1604
|
+
ComputeType: "BUILD_GENERAL1_SMALL",
|
|
1605
|
+
EnvironmentVariables: [
|
|
1606
|
+
{
|
|
1607
|
+
Name: "AWS_ACCOUNT_ID",
|
|
1608
|
+
Value: { Ref: "AWS::AccountId" }
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
Name: "AWS_REGION",
|
|
1612
|
+
Value: { Ref: "AWS::Region" }
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
Name: "DOCKERFILE",
|
|
1616
|
+
Value: {
|
|
1617
|
+
"Fn::Sub": [
|
|
1618
|
+
"FROM public.ecr.aws/ubuntu/ubuntu:20.04_stable",
|
|
1619
|
+
// https://stackoverflow.com/a/59693182/8786986
|
|
1620
|
+
"ENV DEBIAN_FRONTEND noninteractive",
|
|
1621
|
+
// Make sure apt is up to date
|
|
1622
|
+
"RUN apt-get update --fix-missing",
|
|
1623
|
+
"RUN apt-get install -y curl",
|
|
1624
|
+
"RUN apt-get install -y git",
|
|
1625
|
+
"RUN apt-get install -y jq",
|
|
1626
|
+
// Install Node.js
|
|
1627
|
+
`RUN curl -fsSL https://deb.nodesource.com/setup_${nodeRuntimeNumber}.x | bash -`,
|
|
1628
|
+
"RUN apt-get install -y nodejs",
|
|
1629
|
+
// Clean cache
|
|
1630
|
+
"RUN apt-get clean",
|
|
1631
|
+
// Install Yarn
|
|
1632
|
+
"RUN npm install -g yarn",
|
|
1633
|
+
// Install carlin CLI
|
|
1634
|
+
"RUN yarn global add carlin",
|
|
1635
|
+
// Configure git
|
|
1636
|
+
"RUN git config --global user.name carlin",
|
|
1637
|
+
"RUN git config --global user.email carlin@ttoss.dev",
|
|
1638
|
+
"RUN mkdir /root/.ssh/",
|
|
1639
|
+
"COPY ./id_rsa /root/.ssh/id_rsa",
|
|
1640
|
+
"RUN chmod 600 /root/.ssh/id_rsa",
|
|
1641
|
+
// Make sure your domain is accepted
|
|
1642
|
+
"RUN touch /root/.ssh/known_hosts",
|
|
1643
|
+
"RUN ssh-keyscan github.com >> /root/.ssh/known_hosts",
|
|
1644
|
+
// Copy repository
|
|
1645
|
+
"COPY . /home",
|
|
1646
|
+
// Go to repository directory
|
|
1647
|
+
"WORKDIR /home/repository",
|
|
1648
|
+
// Set Yarn cache
|
|
1649
|
+
"RUN mkdir -p /home/yarn-cache",
|
|
1650
|
+
"RUN yarn config set cache-folder /home/yarn-cache",
|
|
1651
|
+
"RUN yarn install",
|
|
1652
|
+
// Used in case of yarn.lock is modified.
|
|
1653
|
+
"RUN git checkout -- yarn.lock"
|
|
1654
|
+
].join("\n")
|
|
1655
|
+
}
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
Name: "IMAGE_TAG",
|
|
1659
|
+
Value: "latest"
|
|
1660
|
+
},
|
|
1661
|
+
{
|
|
1662
|
+
Name: "REPOSITORY_ECR_REPOSITORY",
|
|
1663
|
+
Value: { Ref: ECR_REPOSITORY_LOGICAL_ID }
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
Name: "SSH_KEY",
|
|
1667
|
+
Value: { Ref: "SSHKey" }
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
Name: "SSH_URL",
|
|
1671
|
+
Value: { Ref: "SSHUrl" }
|
|
1672
|
+
}
|
|
1673
|
+
],
|
|
1674
|
+
Image: "aws/codebuild/standard:3.0",
|
|
1675
|
+
ImagePullCredentialsType: "CODEBUILD",
|
|
1676
|
+
/**
|
|
1677
|
+
* Enables running the Docker daemon inside a Docker container. Set to
|
|
1678
|
+
* true only if the build project is used to build Docker images.
|
|
1679
|
+
* Otherwise, a build that attempts to interact with the Docker daemon
|
|
1680
|
+
* fails. The default setting is false."
|
|
1681
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-environment.html#cfn-codebuild-project-environment-privilegedmode
|
|
1682
|
+
*/
|
|
1683
|
+
PrivilegedMode: true,
|
|
1684
|
+
Type: "LINUX_CONTAINER"
|
|
1685
|
+
},
|
|
1686
|
+
LogsConfig: {
|
|
1687
|
+
CloudWatchLogs: {
|
|
1688
|
+
Status: "ENABLED",
|
|
1689
|
+
GroupName: { Ref: CODE_BUILD_PROJECT_LOGS_LOGICAL_ID }
|
|
1690
|
+
}
|
|
1691
|
+
},
|
|
1692
|
+
ServiceRole: {
|
|
1693
|
+
"Fn::GetAtt": [CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID, "Arn"]
|
|
1694
|
+
},
|
|
1695
|
+
Source: {
|
|
1696
|
+
BuildSpec: _jsyaml2.default.dump({
|
|
1697
|
+
version: "0.2",
|
|
1698
|
+
phases: {
|
|
1699
|
+
install: {
|
|
1700
|
+
commands: [
|
|
1701
|
+
"echo install started on `date`",
|
|
1702
|
+
`echo "$SSH_KEY" > ~/.ssh/id_rsa`,
|
|
1703
|
+
"chmod 600 ~/.ssh/id_rsa",
|
|
1704
|
+
"rm -rf repository",
|
|
1705
|
+
"git clone $SSH_URL repository",
|
|
1706
|
+
"cd repository",
|
|
1707
|
+
"ls"
|
|
1708
|
+
]
|
|
1709
|
+
},
|
|
1710
|
+
pre_build: {
|
|
1711
|
+
commands: ["echo pre_build started on `date`"]
|
|
1712
|
+
},
|
|
1713
|
+
build: {
|
|
1714
|
+
commands: [
|
|
1715
|
+
"echo build started on `date`",
|
|
1716
|
+
"$(aws ecr get-login --no-include-email --region $AWS_REGION)",
|
|
1717
|
+
"echo Building the repository image...",
|
|
1718
|
+
"cd ../",
|
|
1719
|
+
"cp ~/.ssh/id_rsa .",
|
|
1720
|
+
'echo "$DOCKERFILE" > Dockerfile',
|
|
1721
|
+
"cat Dockerfile",
|
|
1722
|
+
"docker build -t $REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile .",
|
|
1723
|
+
"docker tag $REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG",
|
|
1724
|
+
"echo Pushing the repository image...",
|
|
1725
|
+
"docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_ECR_REPOSITORY:$IMAGE_TAG"
|
|
1726
|
+
]
|
|
1727
|
+
},
|
|
1728
|
+
post_build: {
|
|
1729
|
+
commands: ["echo post_build completed on `date`"]
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}),
|
|
1733
|
+
Type: "NO_SOURCE"
|
|
1734
|
+
},
|
|
1735
|
+
TimeoutInMinutes: 15
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
};
|
|
1739
|
+
var triggerPipelinesObjectKeyPrefix = [
|
|
1740
|
+
"cicd",
|
|
1741
|
+
"pipelines",
|
|
1742
|
+
"triggers",
|
|
1743
|
+
getProjectName()
|
|
1744
|
+
].join("/");
|
|
1745
|
+
var getCicdTemplate = ({
|
|
1746
|
+
pipelines: pipelines2 = [],
|
|
1747
|
+
cpu = ECS_TASK_DEFAULT_CPU,
|
|
1748
|
+
memory = ECS_TASK_DEFAULT_MEMORY,
|
|
1749
|
+
s3: s32,
|
|
1750
|
+
slackWebhookUrl,
|
|
1751
|
+
taskEnvironment = []
|
|
1752
|
+
}) => {
|
|
1753
|
+
const resources2 = {};
|
|
1754
|
+
const executeEcsTaskVariables = {
|
|
1755
|
+
ECS_CLUSTER_ARN: {
|
|
1756
|
+
"Fn::GetAtt": [REPOSITORY_TASKS_ECS_CLUSTER_LOGICAL_ID, "Arn"]
|
|
1757
|
+
},
|
|
1758
|
+
ECS_CONTAINER_NAME: REPOSITORY_ECS_TASK_CONTAINER_NAME,
|
|
1759
|
+
ECS_TASK_DEFINITION: {
|
|
1760
|
+
Ref: REPOSITORY_ECS_TASK_DEFINITION_LOGICAL_ID
|
|
1761
|
+
},
|
|
1762
|
+
VPC_SECURITY_GROUP: {
|
|
1763
|
+
"Fn::ImportValue": BASE_STACK_VPC_DEFAULT_SECURITY_GROUP_EXPORTED_NAME
|
|
1764
|
+
},
|
|
1765
|
+
VPC_PUBLIC_SUBNET_0: {
|
|
1766
|
+
"Fn::ImportValue": BASE_STACK_VPC_PUBLIC_SUBNET_0_EXPORTED_NAME
|
|
1767
|
+
},
|
|
1768
|
+
VPC_PUBLIC_SUBNET_1: {
|
|
1769
|
+
"Fn::ImportValue": BASE_STACK_VPC_PUBLIC_SUBNET_1_EXPORTED_NAME
|
|
1770
|
+
},
|
|
1771
|
+
VPC_PUBLIC_SUBNET_2: {
|
|
1772
|
+
"Fn::ImportValue": BASE_STACK_VPC_PUBLIC_SUBNET_2_EXPORTED_NAME
|
|
1773
|
+
},
|
|
1774
|
+
ECS_TASK_REPORT_HANDLER_NAME: {
|
|
1775
|
+
Ref: ECS_TASK_REPORT_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID
|
|
1776
|
+
}
|
|
1777
|
+
};
|
|
1778
|
+
const getEcrRepositoryResource = () => {
|
|
1779
|
+
return {
|
|
1780
|
+
Type: "AWS::ECR::Repository",
|
|
1781
|
+
Properties: {
|
|
1782
|
+
LifecyclePolicy: {
|
|
1783
|
+
LifecyclePolicyText: JSON.stringify(
|
|
1784
|
+
{
|
|
1785
|
+
rules: [
|
|
1786
|
+
{
|
|
1787
|
+
rulePriority: 1,
|
|
1788
|
+
description: "Only keep the latest image",
|
|
1789
|
+
selection: {
|
|
1790
|
+
tagStatus: "any",
|
|
1791
|
+
countType: "imageCountMoreThan",
|
|
1792
|
+
countNumber: 1
|
|
1793
|
+
},
|
|
1794
|
+
action: {
|
|
1795
|
+
type: "expire"
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
]
|
|
1799
|
+
},
|
|
1800
|
+
null,
|
|
1801
|
+
2
|
|
1802
|
+
)
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
};
|
|
1807
|
+
resources2[ECR_REPOSITORY_LOGICAL_ID] = getEcrRepositoryResource();
|
|
1808
|
+
const commonFunctionProperties = {
|
|
1809
|
+
CodeUri: {
|
|
1810
|
+
Bucket: s32.bucket,
|
|
1811
|
+
Key: s32.key,
|
|
1812
|
+
Version: s32.versionId
|
|
1813
|
+
},
|
|
1814
|
+
Role: {
|
|
1815
|
+
"Fn::GetAtt": [FUNCTION_IAM_ROLE_LOGICAL_ID, "Arn"]
|
|
1816
|
+
},
|
|
1817
|
+
Runtime: NODE_RUNTIME,
|
|
1818
|
+
Timeout: 60
|
|
1819
|
+
};
|
|
1820
|
+
(() => {
|
|
1821
|
+
resources2[CODE_BUILD_PROJECT_LOGS_LOGICAL_ID] = {
|
|
1822
|
+
Type: "AWS::Logs::LogGroup",
|
|
1823
|
+
DeletionPolicy: "Delete",
|
|
1824
|
+
Properties: {}
|
|
1825
|
+
};
|
|
1826
|
+
resources2[CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID] = {
|
|
1827
|
+
Type: "AWS::IAM::Role",
|
|
1828
|
+
Properties: {
|
|
1829
|
+
AssumeRolePolicyDocument: {
|
|
1830
|
+
Version: "2012-10-17",
|
|
1831
|
+
Statement: [
|
|
1832
|
+
{
|
|
1833
|
+
Effect: "Allow",
|
|
1834
|
+
Principal: {
|
|
1835
|
+
Service: "codebuild.amazonaws.com"
|
|
1836
|
+
},
|
|
1837
|
+
Action: "sts:AssumeRole"
|
|
1838
|
+
}
|
|
1839
|
+
]
|
|
1840
|
+
},
|
|
1841
|
+
Path: getIamPath(),
|
|
1842
|
+
Policies: [
|
|
1843
|
+
{
|
|
1844
|
+
PolicyName: `${CODE_BUILD_PROJECT_SERVICE_ROLE_LOGICAL_ID}Policy`,
|
|
1845
|
+
PolicyDocument: {
|
|
1846
|
+
Version: "2012-10-17",
|
|
1847
|
+
Statement: [
|
|
1848
|
+
{
|
|
1849
|
+
Effect: "Allow",
|
|
1850
|
+
Action: ["logs:CreateLogStream", "logs:PutLogEvents"],
|
|
1851
|
+
Resource: "*"
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
Effect: "Allow",
|
|
1855
|
+
Action: ["ecr:GetAuthorizationToken"],
|
|
1856
|
+
Resource: "*"
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
Effect: "Allow",
|
|
1860
|
+
Action: [
|
|
1861
|
+
"ecr:BatchCheckLayerAvailability",
|
|
1862
|
+
"ecr:CompleteLayerUpload",
|
|
1863
|
+
"ecr:InitiateLayerUpload",
|
|
1864
|
+
"ecr:PutImage",
|
|
1865
|
+
"ecr:UploadLayerPart"
|
|
1866
|
+
],
|
|
1867
|
+
Resource: {
|
|
1868
|
+
"Fn::GetAtt": [ECR_REPOSITORY_LOGICAL_ID, "Arn"]
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
]
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
]
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
resources2[REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID] = getRepositoryImageBuilder();
|
|
1878
|
+
const cicdConfig = {
|
|
1879
|
+
...getCicdConfig(),
|
|
1880
|
+
"ssh-key": "/root/.ssh/id_rsa",
|
|
1881
|
+
environment: getEnvironment()
|
|
1882
|
+
};
|
|
1883
|
+
resources2[IMAGE_UPDATER_SCHEDULE_SERVERLESS_FUNCTION_LOGICAL_ID] = {
|
|
1884
|
+
Type: "AWS::Serverless::Function",
|
|
1885
|
+
Properties: {
|
|
1886
|
+
...commonFunctionProperties,
|
|
1887
|
+
Events: {
|
|
1888
|
+
Schedule: {
|
|
1889
|
+
Type: "Schedule",
|
|
1890
|
+
Properties: {
|
|
1891
|
+
Schedule: "rate(7 days)"
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
},
|
|
1895
|
+
Environment: {
|
|
1896
|
+
Variables: {
|
|
1897
|
+
[PROCESS_ENV_REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME]: {
|
|
1898
|
+
Ref: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID
|
|
1899
|
+
},
|
|
1900
|
+
CICD_CONFIG: JSON.stringify(cicdConfig),
|
|
1901
|
+
...executeEcsTaskVariables
|
|
1902
|
+
}
|
|
1903
|
+
},
|
|
1904
|
+
Handler: "index.imageUpdaterScheduleHandler"
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
})();
|
|
1908
|
+
const createApiResources = () => {
|
|
1909
|
+
resources2[API_LOGICAL_ID] = {
|
|
1910
|
+
Type: "AWS::Serverless::Api",
|
|
1911
|
+
Properties: {
|
|
1912
|
+
Auth: {
|
|
1913
|
+
ApiKeyRequired: false
|
|
1914
|
+
},
|
|
1915
|
+
StageName: "v1"
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
resources2[FUNCTION_IAM_ROLE_LOGICAL_ID] = {
|
|
1919
|
+
Type: "AWS::IAM::Role",
|
|
1920
|
+
Properties: {
|
|
1921
|
+
AssumeRolePolicyDocument: {
|
|
1922
|
+
Version: "2012-10-17",
|
|
1923
|
+
Statement: [
|
|
1924
|
+
{
|
|
1925
|
+
Effect: "Allow",
|
|
1926
|
+
Principal: {
|
|
1927
|
+
Service: "lambda.amazonaws.com"
|
|
1928
|
+
},
|
|
1929
|
+
Action: ["sts:AssumeRole"]
|
|
1930
|
+
}
|
|
1931
|
+
]
|
|
1932
|
+
},
|
|
1933
|
+
ManagedPolicyArns: [
|
|
1934
|
+
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
1935
|
+
],
|
|
1936
|
+
Path: getIamPath(),
|
|
1937
|
+
Policies: [
|
|
1938
|
+
{
|
|
1939
|
+
PolicyName: `${FUNCTION_IAM_ROLE_LOGICAL_ID}Policy`,
|
|
1940
|
+
PolicyDocument: {
|
|
1941
|
+
Version: "2012-10-17",
|
|
1942
|
+
Statement: [
|
|
1943
|
+
{
|
|
1944
|
+
Effect: "Allow",
|
|
1945
|
+
Action: ["codebuild:StartBuild"],
|
|
1946
|
+
Resource: {
|
|
1947
|
+
"Fn::GetAtt": [
|
|
1948
|
+
REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID,
|
|
1949
|
+
"Arn"
|
|
1950
|
+
]
|
|
1951
|
+
}
|
|
1952
|
+
},
|
|
1953
|
+
{
|
|
1954
|
+
Effect: "Allow",
|
|
1955
|
+
Action: ["iam:PassRole"],
|
|
1956
|
+
Resource: [
|
|
1957
|
+
{
|
|
1958
|
+
"Fn::GetAtt": [
|
|
1959
|
+
REPOSITORY_TASKS_ECS_TASK_DEFINITION_EXECUTION_ROLE_LOGICAL_ID,
|
|
1960
|
+
"Arn"
|
|
1961
|
+
]
|
|
1962
|
+
},
|
|
1963
|
+
{
|
|
1964
|
+
"Fn::GetAtt": [
|
|
1965
|
+
REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID,
|
|
1966
|
+
"Arn"
|
|
1967
|
+
]
|
|
1968
|
+
}
|
|
1969
|
+
]
|
|
1970
|
+
},
|
|
1971
|
+
{
|
|
1972
|
+
Effect: "Allow",
|
|
1973
|
+
Action: ["ecs:DescribeTasks"],
|
|
1974
|
+
Resource: "*"
|
|
1975
|
+
},
|
|
1976
|
+
{
|
|
1977
|
+
Effect: "Allow",
|
|
1978
|
+
Action: ["ecs:RunTask"],
|
|
1979
|
+
Resource: [
|
|
1980
|
+
{
|
|
1981
|
+
Ref: REPOSITORY_ECS_TASK_DEFINITION_LOGICAL_ID
|
|
1982
|
+
}
|
|
1983
|
+
]
|
|
1984
|
+
},
|
|
1985
|
+
{
|
|
1986
|
+
Action: [
|
|
1987
|
+
"codepipeline:PutApprovalResult",
|
|
1988
|
+
"codepipeline:GetJobDetails",
|
|
1989
|
+
"codepipeline:GetPipelineState",
|
|
1990
|
+
"codepipeline:PutJobSuccessResult",
|
|
1991
|
+
"codepipeline:PutJobFailureResult"
|
|
1992
|
+
],
|
|
1993
|
+
Effect: "Allow",
|
|
1994
|
+
Resource: "*"
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
Action: "s3:*",
|
|
1998
|
+
Effect: "Allow",
|
|
1999
|
+
Resource: {
|
|
2000
|
+
"Fn::Sub": [
|
|
2001
|
+
`arn:aws:s3:::\${BucketName}/${triggerPipelinesObjectKeyPrefix}*`,
|
|
2002
|
+
{
|
|
2003
|
+
BucketName: {
|
|
2004
|
+
"Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
]
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
]
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
]
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
resources2[ECS_TASK_REPORT_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID] = {
|
|
2017
|
+
Type: "AWS::Serverless::Function",
|
|
2018
|
+
Properties: {
|
|
2019
|
+
...commonFunctionProperties,
|
|
2020
|
+
Environment: {
|
|
2021
|
+
Variables: {
|
|
2022
|
+
ECS_TASK_LOGS_LOG_GROUP: {
|
|
2023
|
+
Ref: REPOSITORY_TASKS_ECS_CLUSTER_LOGS_LOG_GROUP_LOGICAL_ID
|
|
2024
|
+
},
|
|
2025
|
+
ECS_TASK_CONTAINER_NAME: REPOSITORY_ECS_TASK_CONTAINER_NAME,
|
|
2026
|
+
SLACK_WEBHOOK_URL: slackWebhookUrl
|
|
2027
|
+
}
|
|
2028
|
+
},
|
|
2029
|
+
Handler: "index.ecsTaskReportHandler"
|
|
2030
|
+
}
|
|
2031
|
+
};
|
|
2032
|
+
resources2.CicdApiV1ServerlessFunction = {
|
|
2033
|
+
Type: "AWS::Serverless::Function",
|
|
2034
|
+
Properties: {
|
|
2035
|
+
...commonFunctionProperties,
|
|
2036
|
+
Events: {
|
|
2037
|
+
ApiEvent: {
|
|
2038
|
+
Type: "Api",
|
|
2039
|
+
Properties: {
|
|
2040
|
+
Method: "POST",
|
|
2041
|
+
Path: "/cicd",
|
|
2042
|
+
RestApiId: { Ref: API_LOGICAL_ID }
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
},
|
|
2046
|
+
Environment: {
|
|
2047
|
+
Variables: {
|
|
2048
|
+
[PROCESS_ENV_REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME]: {
|
|
2049
|
+
Ref: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID
|
|
2050
|
+
},
|
|
2051
|
+
...executeEcsTaskVariables
|
|
2052
|
+
}
|
|
2053
|
+
},
|
|
2054
|
+
Handler: "index.cicdApiV1Handler"
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
resources2.GitHubWebhooksApiV1ServerlessFunction = {
|
|
2058
|
+
Type: "AWS::Serverless::Function",
|
|
2059
|
+
Properties: {
|
|
2060
|
+
...commonFunctionProperties,
|
|
2061
|
+
Events: {
|
|
2062
|
+
ApiEvent: {
|
|
2063
|
+
Type: "Api",
|
|
2064
|
+
Properties: {
|
|
2065
|
+
Method: "POST",
|
|
2066
|
+
Path: "/github/webhooks",
|
|
2067
|
+
RestApiId: { Ref: API_LOGICAL_ID }
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
},
|
|
2071
|
+
Environment: {
|
|
2072
|
+
Variables: {
|
|
2073
|
+
BASE_STACK_BUCKET_NAME: {
|
|
2074
|
+
"Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
|
|
2075
|
+
},
|
|
2076
|
+
TRIGGER_PIPELINES_OBJECT_KEY_PREFIX: triggerPipelinesObjectKeyPrefix,
|
|
2077
|
+
PIPELINES_JSON: JSON.stringify(pipelines2),
|
|
2078
|
+
...executeEcsTaskVariables
|
|
2079
|
+
}
|
|
2080
|
+
},
|
|
2081
|
+
Handler: "index.githubWebhooksApiV1Handler"
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
};
|
|
2085
|
+
createApiResources();
|
|
2086
|
+
(() => {
|
|
2087
|
+
resources2[REPOSITORY_TASKS_ECS_CLUSTER_LOGICAL_ID] = {
|
|
2088
|
+
Type: "AWS::ECS::Cluster",
|
|
2089
|
+
Properties: {}
|
|
2090
|
+
};
|
|
2091
|
+
resources2[REPOSITORY_TASKS_ECS_CLUSTER_LOGS_LOG_GROUP_LOGICAL_ID] = {
|
|
2092
|
+
Type: "AWS::Logs::LogGroup",
|
|
2093
|
+
DeletionPolicy: "Delete",
|
|
2094
|
+
Properties: {}
|
|
2095
|
+
};
|
|
2096
|
+
resources2[REPOSITORY_TASKS_ECS_TASK_DEFINITION_EXECUTION_ROLE_LOGICAL_ID] = {
|
|
2097
|
+
Type: "AWS::IAM::Role",
|
|
2098
|
+
Properties: {
|
|
2099
|
+
AssumeRolePolicyDocument: {
|
|
2100
|
+
Version: "2012-10-17",
|
|
2101
|
+
Statement: [
|
|
2102
|
+
{
|
|
2103
|
+
Effect: "Allow",
|
|
2104
|
+
Principal: {
|
|
2105
|
+
Service: "ecs-tasks.amazonaws.com"
|
|
2106
|
+
},
|
|
2107
|
+
Action: "sts:AssumeRole"
|
|
2108
|
+
}
|
|
2109
|
+
]
|
|
2110
|
+
},
|
|
2111
|
+
ManagedPolicyArns: [
|
|
2112
|
+
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
|
|
2113
|
+
],
|
|
2114
|
+
Path: getIamPath()
|
|
2115
|
+
}
|
|
2116
|
+
};
|
|
2117
|
+
resources2[REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID] = {
|
|
2118
|
+
Type: "AWS::IAM::Role",
|
|
2119
|
+
Properties: {
|
|
2120
|
+
AssumeRolePolicyDocument: {
|
|
2121
|
+
Version: "2012-10-17",
|
|
2122
|
+
Statement: [
|
|
2123
|
+
{
|
|
2124
|
+
Effect: "Allow",
|
|
2125
|
+
Principal: {
|
|
2126
|
+
Service: "ecs-tasks.amazonaws.com"
|
|
2127
|
+
},
|
|
2128
|
+
Action: "sts:AssumeRole"
|
|
2129
|
+
}
|
|
2130
|
+
]
|
|
2131
|
+
},
|
|
2132
|
+
ManagedPolicyArns: [
|
|
2133
|
+
"arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
|
|
2134
|
+
],
|
|
2135
|
+
Path: getIamPath(),
|
|
2136
|
+
/**
|
|
2137
|
+
* TODO: improve the policies rules.
|
|
2138
|
+
*/
|
|
2139
|
+
Policies: [
|
|
2140
|
+
{
|
|
2141
|
+
PolicyName: `${REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID}Policy`,
|
|
2142
|
+
PolicyDocument: {
|
|
2143
|
+
Version: "2012-10-17",
|
|
2144
|
+
Statement: [
|
|
2145
|
+
{
|
|
2146
|
+
Effect: "Allow",
|
|
2147
|
+
Action: ["*"],
|
|
2148
|
+
Resource: "*"
|
|
2149
|
+
}
|
|
2150
|
+
]
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
]
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
resources2[REPOSITORY_ECS_TASK_DEFINITION_LOGICAL_ID] = {
|
|
2157
|
+
Type: "AWS::ECS::TaskDefinition",
|
|
2158
|
+
Properties: {
|
|
2159
|
+
ContainerDefinitions: [
|
|
2160
|
+
{
|
|
2161
|
+
Environment: [
|
|
2162
|
+
{
|
|
2163
|
+
/**
|
|
2164
|
+
* https://docs.aws.amazon.com/AmazonECS/latest/developerguide/container-metadata.html#enable-metadata
|
|
2165
|
+
*/
|
|
2166
|
+
Name: "ECS_ENABLE_CONTAINER_METADATA",
|
|
2167
|
+
Value: "true"
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
Name: "CI",
|
|
2171
|
+
Value: "true"
|
|
2172
|
+
},
|
|
2173
|
+
...taskEnvironment.map((te) => {
|
|
2174
|
+
return {
|
|
2175
|
+
Name: te.name,
|
|
2176
|
+
Value: te.value
|
|
2177
|
+
};
|
|
2178
|
+
})
|
|
2179
|
+
],
|
|
2180
|
+
Image: {
|
|
2181
|
+
"Fn::Sub": [
|
|
2182
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
2183
|
+
"${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RepositoryECR}:latest",
|
|
2184
|
+
{
|
|
2185
|
+
RepositoryECR: { Ref: ECR_REPOSITORY_LOGICAL_ID }
|
|
2186
|
+
}
|
|
2187
|
+
]
|
|
2188
|
+
},
|
|
2189
|
+
LogConfiguration: {
|
|
2190
|
+
LogDriver: "awslogs",
|
|
2191
|
+
Options: {
|
|
2192
|
+
"awslogs-group": {
|
|
2193
|
+
Ref: REPOSITORY_TASKS_ECS_CLUSTER_LOGS_LOG_GROUP_LOGICAL_ID
|
|
2194
|
+
},
|
|
2195
|
+
"awslogs-region": { Ref: "AWS::Region" },
|
|
2196
|
+
"awslogs-stream-prefix": "ecs"
|
|
2197
|
+
}
|
|
2198
|
+
},
|
|
2199
|
+
Name: REPOSITORY_ECS_TASK_CONTAINER_NAME
|
|
2200
|
+
}
|
|
2201
|
+
],
|
|
2202
|
+
Cpu: cpu,
|
|
2203
|
+
ExecutionRoleArn: {
|
|
2204
|
+
"Fn::GetAtt": [
|
|
2205
|
+
REPOSITORY_TASKS_ECS_TASK_DEFINITION_EXECUTION_ROLE_LOGICAL_ID,
|
|
2206
|
+
"Arn"
|
|
2207
|
+
]
|
|
2208
|
+
},
|
|
2209
|
+
Memory: memory,
|
|
2210
|
+
NetworkMode: "awsvpc",
|
|
2211
|
+
RequiresCompatibilities: ["FARGATE"],
|
|
2212
|
+
TaskRoleArn: {
|
|
2213
|
+
"Fn::GetAtt": [
|
|
2214
|
+
REPOSITORY_TASKS_ECS_TASK_DEFINITION_TASK_ROLE_LOGICAL_ID,
|
|
2215
|
+
"Arn"
|
|
2216
|
+
]
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
};
|
|
2220
|
+
})();
|
|
2221
|
+
if (pipelines2.includes("main") || pipelines2.includes("tag")) {
|
|
2222
|
+
resources2[PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID] = {
|
|
2223
|
+
Type: "AWS::S3::Bucket",
|
|
2224
|
+
Properties: {
|
|
2225
|
+
LifecycleConfiguration: {
|
|
2226
|
+
Rules: [
|
|
2227
|
+
{
|
|
2228
|
+
/**
|
|
2229
|
+
* We won't use the artifacts forever.
|
|
2230
|
+
*/
|
|
2231
|
+
ExpirationInDays: 7,
|
|
2232
|
+
Status: "Enabled"
|
|
2233
|
+
}
|
|
2234
|
+
]
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
resources2[PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID] = {
|
|
2239
|
+
Type: "AWS::Lambda::Function",
|
|
2240
|
+
Properties: {
|
|
2241
|
+
Code: {
|
|
2242
|
+
S3Bucket: s32.bucket,
|
|
2243
|
+
S3Key: s32.key,
|
|
2244
|
+
S3ObjectVersion: s32.versionId
|
|
2245
|
+
},
|
|
2246
|
+
Environment: {
|
|
2247
|
+
Variables: {
|
|
2248
|
+
...executeEcsTaskVariables
|
|
2249
|
+
}
|
|
2250
|
+
},
|
|
2251
|
+
Handler: "index.pipelinesHandler",
|
|
2252
|
+
MemorySize: 128,
|
|
2253
|
+
Role: {
|
|
2254
|
+
"Fn::GetAtt": [FUNCTION_IAM_ROLE_LOGICAL_ID, "Arn"]
|
|
2255
|
+
},
|
|
2256
|
+
Runtime: NODE_RUNTIME,
|
|
2257
|
+
Timeout: 60
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
resources2[PIPELINES_ROLE_LOGICAL_ID] = {
|
|
2261
|
+
Type: "AWS::IAM::Role",
|
|
2262
|
+
Properties: {
|
|
2263
|
+
AssumeRolePolicyDocument: {
|
|
2264
|
+
Version: "2012-10-17",
|
|
2265
|
+
Statement: [
|
|
2266
|
+
{
|
|
2267
|
+
Effect: "Allow",
|
|
2268
|
+
Principal: {
|
|
2269
|
+
Service: "codepipeline.amazonaws.com"
|
|
2270
|
+
},
|
|
2271
|
+
Action: "sts:AssumeRole"
|
|
2272
|
+
}
|
|
2273
|
+
]
|
|
2274
|
+
},
|
|
2275
|
+
ManagedPolicyArns: [],
|
|
2276
|
+
Path: getIamPath(),
|
|
2277
|
+
Policies: [
|
|
2278
|
+
{
|
|
2279
|
+
PolicyName: `${PIPELINES_ROLE_LOGICAL_ID}Policy`,
|
|
2280
|
+
PolicyDocument: {
|
|
2281
|
+
Version: "2012-10-17",
|
|
2282
|
+
Statement: [
|
|
2283
|
+
{
|
|
2284
|
+
Effect: "Allow",
|
|
2285
|
+
Action: "lambda:InvokeFunction",
|
|
2286
|
+
Resource: [
|
|
2287
|
+
{
|
|
2288
|
+
"Fn::GetAtt": [
|
|
2289
|
+
PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID,
|
|
2290
|
+
"Arn"
|
|
2291
|
+
]
|
|
2292
|
+
}
|
|
2293
|
+
]
|
|
2294
|
+
},
|
|
2295
|
+
{
|
|
2296
|
+
Effect: "Allow",
|
|
2297
|
+
Action: "s3:*",
|
|
2298
|
+
Resource: [
|
|
2299
|
+
{
|
|
2300
|
+
"Fn::GetAtt": [
|
|
2301
|
+
PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID,
|
|
2302
|
+
"Arn"
|
|
2303
|
+
]
|
|
2304
|
+
},
|
|
2305
|
+
{
|
|
2306
|
+
"Fn::Sub": `arn:aws:s3:::\${${PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID}}/*`
|
|
2307
|
+
}
|
|
2308
|
+
]
|
|
2309
|
+
},
|
|
2310
|
+
{
|
|
2311
|
+
Effect: "Allow",
|
|
2312
|
+
Action: "s3:*",
|
|
2313
|
+
Resource: {
|
|
2314
|
+
"Fn::Sub": [
|
|
2315
|
+
`arn:aws:s3:::\${BucketName}/${triggerPipelinesObjectKeyPrefix}*`,
|
|
2316
|
+
{
|
|
2317
|
+
BucketName: {
|
|
2318
|
+
"Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
]
|
|
2322
|
+
}
|
|
2323
|
+
},
|
|
2324
|
+
{
|
|
2325
|
+
Effect: "Allow",
|
|
2326
|
+
Action: ["s3:Get*", "s3:List*"],
|
|
2327
|
+
Resource: {
|
|
2328
|
+
"Fn::Sub": [
|
|
2329
|
+
`arn:aws:s3:::\${BucketName}`,
|
|
2330
|
+
{
|
|
2331
|
+
BucketName: {
|
|
2332
|
+
"Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
]
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
]
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
]
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
const getCodePipelinePipeline = (pipeline) => {
|
|
2345
|
+
const pipelinePascalCase = _changecase.pascalCase.call(void 0, pipeline);
|
|
2346
|
+
const pipelineS3SourceOutputName = `Pipeline${pipelinePascalCase}S3SourceOutput`;
|
|
2347
|
+
return {
|
|
2348
|
+
Type: "AWS::CodePipeline::Pipeline",
|
|
2349
|
+
Properties: {
|
|
2350
|
+
ArtifactStore: {
|
|
2351
|
+
Location: { Ref: PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID },
|
|
2352
|
+
Type: "S3"
|
|
2353
|
+
},
|
|
2354
|
+
RestartExecutionOnUpdate: false,
|
|
2355
|
+
RoleArn: {
|
|
2356
|
+
"Fn::GetAtt": [PIPELINES_ROLE_LOGICAL_ID, "Arn"]
|
|
2357
|
+
},
|
|
2358
|
+
Stages: [
|
|
2359
|
+
{
|
|
2360
|
+
Actions: [
|
|
2361
|
+
{
|
|
2362
|
+
ActionTypeId: {
|
|
2363
|
+
Category: "Source",
|
|
2364
|
+
Owner: "AWS",
|
|
2365
|
+
Provider: "S3",
|
|
2366
|
+
Version: 1
|
|
2367
|
+
},
|
|
2368
|
+
Configuration: {
|
|
2369
|
+
S3Bucket: {
|
|
2370
|
+
"Fn::ImportValue": BASE_STACK_BUCKET_NAME_EXPORTED_NAME
|
|
2371
|
+
},
|
|
2372
|
+
S3ObjectKey: getTriggerPipelinesObjectKey({
|
|
2373
|
+
prefix: triggerPipelinesObjectKeyPrefix,
|
|
2374
|
+
pipeline
|
|
2375
|
+
})
|
|
2376
|
+
},
|
|
2377
|
+
Name: `Pipeline${pipelinePascalCase}S3SourceAction`,
|
|
2378
|
+
OutputArtifacts: [
|
|
2379
|
+
{
|
|
2380
|
+
Name: pipelineS3SourceOutputName
|
|
2381
|
+
}
|
|
2382
|
+
]
|
|
2383
|
+
}
|
|
2384
|
+
],
|
|
2385
|
+
Name: `Pipeline${pipelinePascalCase}S3SourceStage`
|
|
2386
|
+
},
|
|
2387
|
+
{
|
|
2388
|
+
Actions: [
|
|
2389
|
+
{
|
|
2390
|
+
ActionTypeId: {
|
|
2391
|
+
Category: "Invoke",
|
|
2392
|
+
Owner: "AWS",
|
|
2393
|
+
Provider: "Lambda",
|
|
2394
|
+
Version: 1
|
|
2395
|
+
},
|
|
2396
|
+
Configuration: {
|
|
2397
|
+
FunctionName: {
|
|
2398
|
+
Ref: PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID
|
|
2399
|
+
},
|
|
2400
|
+
UserParameters: /* @__PURE__ */ (() => {
|
|
2401
|
+
return pipeline;
|
|
2402
|
+
})()
|
|
2403
|
+
},
|
|
2404
|
+
InputArtifacts: [
|
|
2405
|
+
{
|
|
2406
|
+
Name: pipelineS3SourceOutputName
|
|
2407
|
+
}
|
|
2408
|
+
],
|
|
2409
|
+
Name: `Pipeline${pipelinePascalCase}RunECSTasksAction`
|
|
2410
|
+
},
|
|
2411
|
+
{
|
|
2412
|
+
ActionTypeId: {
|
|
2413
|
+
Category: "Approval",
|
|
2414
|
+
Owner: "AWS",
|
|
2415
|
+
Provider: "Manual",
|
|
2416
|
+
Version: 1
|
|
2417
|
+
},
|
|
2418
|
+
Name: PIPELINE_ECS_TASK_EXECUTION_MANUAL_APPROVAL_ACTION_NAME
|
|
2419
|
+
}
|
|
2420
|
+
],
|
|
2421
|
+
Name: PIPELINE_ECS_TASK_EXECUTION_STAGE_NAME
|
|
2422
|
+
}
|
|
2423
|
+
]
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
};
|
|
2427
|
+
if (pipelines2.includes("main")) {
|
|
2428
|
+
resources2[PIPELINES_MAIN_LOGICAL_ID] = getCodePipelinePipeline("main");
|
|
2429
|
+
}
|
|
2430
|
+
if (pipelines2.includes("tag")) {
|
|
2431
|
+
resources2[PIPELINES_TAG_LOGICAL_ID] = getCodePipelinePipeline("tag");
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
return {
|
|
2435
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
2436
|
+
Transform: "AWS::Serverless-2016-10-31",
|
|
2437
|
+
Resources: resources2,
|
|
2438
|
+
Parameters: {
|
|
2439
|
+
SSHKey: {
|
|
2440
|
+
NoEcho: true,
|
|
2441
|
+
Type: "String"
|
|
2442
|
+
},
|
|
2443
|
+
SSHUrl: {
|
|
2444
|
+
Type: "String"
|
|
2445
|
+
}
|
|
2446
|
+
},
|
|
2447
|
+
Outputs: {
|
|
2448
|
+
[REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID]: {
|
|
2449
|
+
Value: { Ref: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID }
|
|
2450
|
+
},
|
|
2451
|
+
ApiV1Endpoint: {
|
|
2452
|
+
Description: "CICD API v1 stage endpoint.",
|
|
2453
|
+
Value: {
|
|
2454
|
+
"Fn::Sub": `https://\${${API_LOGICAL_ID}}.execute-api.\${AWS::Region}.amazonaws.com/v1/`
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2459
|
+
};
|
|
2460
|
+
|
|
2461
|
+
// src/deploy/lambda/buildLambdaSingleFile.ts
|
|
2462
|
+
var _esbuild = require('esbuild'); var esbuild = _interopRequireWildcard(_esbuild);
|
|
2463
|
+
var _builtinmodules = require('builtin-modules'); var _builtinmodules2 = _interopRequireDefault(_builtinmodules);
|
|
2464
|
+
|
|
2465
|
+
|
|
2466
|
+
var logPrefix5 = "lambda";
|
|
2467
|
+
var outFolder = "dist";
|
|
2468
|
+
var outFile = "index.js";
|
|
2469
|
+
var buildLambdaSingleFile = async ({
|
|
2470
|
+
lambdaExternals,
|
|
2471
|
+
lambdaInput
|
|
2472
|
+
}) => {
|
|
2473
|
+
_npmlog2.default.info(logPrefix5, "Building Lambda single file...");
|
|
2474
|
+
const { errors } = esbuild.buildSync({
|
|
2475
|
+
banner: {
|
|
2476
|
+
js: "// Powered by carlin (https://ttoss.dev/docs/carlin/)"
|
|
2477
|
+
},
|
|
2478
|
+
bundle: true,
|
|
2479
|
+
entryPoints: [path2.default.resolve(process.cwd(), lambdaInput)],
|
|
2480
|
+
external: [
|
|
2481
|
+
/**
|
|
2482
|
+
* Only AWS SDK v3 on Node.js 18.x or higher.
|
|
2483
|
+
* https://aws.amazon.com/blogs/compute/node-js-18-x-runtime-now-available-in-aws-lambda/
|
|
2484
|
+
*/
|
|
2485
|
+
"@aws-sdk/*",
|
|
2486
|
+
..._builtinmodules2.default,
|
|
2487
|
+
...lambdaExternals
|
|
2488
|
+
],
|
|
2489
|
+
/**
|
|
2490
|
+
* https://esbuild.github.io/api/#minify
|
|
2491
|
+
*/
|
|
2492
|
+
minifySyntax: true,
|
|
2493
|
+
platform: "node",
|
|
2494
|
+
outfile: path2.default.join(process.cwd(), outFolder, outFile),
|
|
2495
|
+
target: "node20",
|
|
2496
|
+
treeShaking: true
|
|
2497
|
+
});
|
|
2498
|
+
if (errors.length > 0) {
|
|
2499
|
+
throw errors;
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
|
|
2503
|
+
// src/deploy/lambdaLayer/deployLambdaLayer.ts
|
|
2504
|
+
|
|
2505
|
+
|
|
2506
|
+
// src/deploy/lambdaLayer/getPackageLambdaLayerStackName.ts
|
|
2507
|
+
|
|
2508
|
+
var lambdaLayerStackNamePrefix = `LambdaLayer`;
|
|
2509
|
+
var getPackageLambdaLayerStackName = (packageName) => {
|
|
2510
|
+
const [scopedName, version] = packageName.split("@").filter((part) => {
|
|
2511
|
+
return !!part;
|
|
2512
|
+
});
|
|
2513
|
+
return [
|
|
2514
|
+
lambdaLayerStackNamePrefix,
|
|
2515
|
+
_changecase.pascalCase.call(void 0, scopedName),
|
|
2516
|
+
version.replace(/[^0-9.]/g, "").replace(/\./g, "-")
|
|
2517
|
+
].join("-");
|
|
2518
|
+
};
|
|
2519
|
+
|
|
2520
|
+
// src/deploy/lambdaLayer/deployLambdaLayer.ts
|
|
2521
|
+
|
|
2522
|
+
var logPrefix6 = "lambda-layer";
|
|
2523
|
+
var createLambdaLayerZipFile = async ({
|
|
2524
|
+
codeBuildProjectName,
|
|
2525
|
+
packageName
|
|
2526
|
+
}) => {
|
|
2527
|
+
_npmlog2.default.info(logPrefix6, `Creating zip file for package ${packageName}...`);
|
|
2528
|
+
const codeBuild2 = new (0, _awssdk.CodeBuild)();
|
|
2529
|
+
const { build } = await codeBuild2.startBuild({
|
|
2530
|
+
environmentVariablesOverride: [
|
|
2531
|
+
{
|
|
2532
|
+
name: "PACKAGE_NAME",
|
|
2533
|
+
value: packageName
|
|
2534
|
+
}
|
|
2535
|
+
],
|
|
2536
|
+
projectName: codeBuildProjectName
|
|
2537
|
+
}).promise();
|
|
2538
|
+
if (!_optionalChain([build, 'optionalAccess', _10 => _10.id])) {
|
|
2539
|
+
throw new Error("Cannot start build.");
|
|
2540
|
+
}
|
|
2541
|
+
const result = await waitCodeBuildFinish({
|
|
2542
|
+
buildId: build.id,
|
|
2543
|
+
name: packageName
|
|
2544
|
+
});
|
|
2545
|
+
if (_optionalChain([result, 'access', _11 => _11.artifacts, 'optionalAccess', _12 => _12.location])) {
|
|
2546
|
+
const location = result.artifacts.location.split("/");
|
|
2547
|
+
const bucket = _optionalChain([location, 'access', _13 => _13.shift, 'call', _14 => _14(), 'optionalAccess', _15 => _15.replace, 'call', _16 => _16("arn:aws:s3:::", "")]);
|
|
2548
|
+
if (!bucket) {
|
|
2549
|
+
throw new Error("Cannot retrieve bucket name.");
|
|
2550
|
+
}
|
|
2551
|
+
const key = location.join("/");
|
|
2552
|
+
return { bucket, key };
|
|
2553
|
+
}
|
|
2554
|
+
throw new Error(`Cannot get artifact location for package ${packageName}`);
|
|
2555
|
+
};
|
|
2556
|
+
var getLambdaLayerTemplate = ({
|
|
2557
|
+
bucket,
|
|
2558
|
+
key,
|
|
2559
|
+
packageName
|
|
2560
|
+
}) => {
|
|
2561
|
+
const description = packageName.substring(0, 256);
|
|
2562
|
+
return {
|
|
2563
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
2564
|
+
Resources: {
|
|
2565
|
+
LambdaLayer: {
|
|
2566
|
+
Type: "AWS::Lambda::LayerVersion",
|
|
2567
|
+
Properties: {
|
|
2568
|
+
CompatibleRuntimes: [NODE_RUNTIME],
|
|
2569
|
+
Content: {
|
|
2570
|
+
S3Bucket: bucket,
|
|
2571
|
+
S3Key: key
|
|
2572
|
+
},
|
|
2573
|
+
Description: description,
|
|
2574
|
+
LayerName: { Ref: "AWS::StackName" }
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
},
|
|
2578
|
+
Outputs: {
|
|
2579
|
+
LambdaLayerVersion: {
|
|
2580
|
+
Description: description,
|
|
2581
|
+
Value: { Ref: "LambdaLayer" },
|
|
2582
|
+
Export: {
|
|
2583
|
+
Name: { Ref: "AWS::StackName" }
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
};
|
|
2589
|
+
var getPackagesThatAreNotDeployed = async ({
|
|
2590
|
+
packages
|
|
2591
|
+
}) => {
|
|
2592
|
+
return (await Promise.all(
|
|
2593
|
+
packages.map(async (packageName) => {
|
|
2594
|
+
const stackName = getPackageLambdaLayerStackName(packageName);
|
|
2595
|
+
return await doesStackExist({ stackName }) ? "" : packageName;
|
|
2596
|
+
})
|
|
2597
|
+
)).filter((packageName) => {
|
|
2598
|
+
return !!packageName;
|
|
2599
|
+
});
|
|
2600
|
+
};
|
|
2601
|
+
var deployLambdaLayer = async ({
|
|
2602
|
+
packages,
|
|
2603
|
+
deployIfExists = true
|
|
2604
|
+
}) => {
|
|
2605
|
+
try {
|
|
2606
|
+
const packagesToBeDeployed = deployIfExists ? packages : await getPackagesThatAreNotDeployed({ packages });
|
|
2607
|
+
if (packagesToBeDeployed.length === 0) {
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const codeBuildProjectName = await getBaseStackResource(
|
|
2611
|
+
"BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME"
|
|
2612
|
+
);
|
|
2613
|
+
if (!codeBuildProjectName) {
|
|
2614
|
+
throw new Error(
|
|
2615
|
+
"Cannot deploy lambda-layer because AWS CodeBuild project doesn't exist."
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
const deployLambdaLayerSinglePackage = async (packageName) => {
|
|
2619
|
+
try {
|
|
2620
|
+
const { bucket, key } = await createLambdaLayerZipFile({
|
|
2621
|
+
codeBuildProjectName,
|
|
2622
|
+
packageName
|
|
2623
|
+
});
|
|
2624
|
+
const lambdaLayerTemplate = getLambdaLayerTemplate({
|
|
2625
|
+
packageName,
|
|
2626
|
+
bucket,
|
|
2627
|
+
key
|
|
2628
|
+
});
|
|
2629
|
+
await deploy({
|
|
2630
|
+
template: lambdaLayerTemplate,
|
|
2631
|
+
terminationProtection: true,
|
|
2632
|
+
params: { StackName: getPackageLambdaLayerStackName(packageName) }
|
|
2633
|
+
});
|
|
2634
|
+
} catch (error) {
|
|
2635
|
+
handleDeployError({ error, logPrefix: logPrefix6 });
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
await Promise.all(
|
|
2639
|
+
packagesToBeDeployed.map((packageName) => {
|
|
2640
|
+
return deployLambdaLayerSinglePackage(packageName);
|
|
2641
|
+
})
|
|
2642
|
+
);
|
|
2643
|
+
} catch (error) {
|
|
2644
|
+
handleDeployError({ error, logPrefix: logPrefix6 });
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
|
|
2648
|
+
// src/deploy/lambda/deployLambdaLayers.ts
|
|
2649
|
+
|
|
2650
|
+
|
|
2651
|
+
var logPrefix7 = "lambda";
|
|
2652
|
+
var deployLambdaLayers = async ({
|
|
2653
|
+
lambdaExternals = []
|
|
2654
|
+
}) => {
|
|
2655
|
+
if (lambdaExternals.length === 0) {
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
_npmlog2.default.info(
|
|
2659
|
+
logPrefix7,
|
|
2660
|
+
`--lambda-externals [${lambdaExternals.join(
|
|
2661
|
+
", "
|
|
2662
|
+
)}] was found. Creating other layers...`
|
|
2663
|
+
);
|
|
2664
|
+
const { dependencies = {} } = (() => {
|
|
2665
|
+
try {
|
|
2666
|
+
return __require(path2.default.resolve(process.cwd(), "package.json")) || {};
|
|
2667
|
+
} catch (err) {
|
|
2668
|
+
_npmlog2.default.error(
|
|
2669
|
+
logPrefix7,
|
|
2670
|
+
"Cannot read package.json. Error message: %j",
|
|
2671
|
+
err.message
|
|
2672
|
+
);
|
|
2673
|
+
return {};
|
|
2674
|
+
}
|
|
2675
|
+
})();
|
|
2676
|
+
const packages = lambdaExternals.map((lambdaExternal) => {
|
|
2677
|
+
try {
|
|
2678
|
+
const semver2 = dependencies[lambdaExternal].replace(/(~|\^)/g, "");
|
|
2679
|
+
return `${lambdaExternal}@${semver2}`;
|
|
2680
|
+
} catch (e2) {
|
|
2681
|
+
throw new Error(
|
|
2682
|
+
`Cannot find ${lambdaExternal} on package.json dependencies.`
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
await deployLambdaLayer({ packages, deployIfExists: false });
|
|
2687
|
+
};
|
|
2688
|
+
|
|
2689
|
+
// src/deploy/lambda/uploadCodeToECR.ts
|
|
2690
|
+
|
|
2691
|
+
var codeBuild = new (0, _awssdk.CodeBuild)({ region: AWS_DEFAULT_REGION });
|
|
2692
|
+
var uploadCodeToECR = async ({
|
|
2693
|
+
bucket,
|
|
2694
|
+
key,
|
|
2695
|
+
lambdaExternals,
|
|
2696
|
+
lambdaDockerfile
|
|
2697
|
+
}) => {
|
|
2698
|
+
const TEMP = 1;
|
|
2699
|
+
if (TEMP) {
|
|
2700
|
+
throw new Error("uploadCodeToECR not finished yet.");
|
|
2701
|
+
}
|
|
2702
|
+
const lambdaBuilder = await getBaseStackResource(
|
|
2703
|
+
"BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME"
|
|
2704
|
+
);
|
|
2705
|
+
const defaultDockerfile = [
|
|
2706
|
+
"FROM public.ecr.aws/lambda/nodejs:14",
|
|
2707
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
2708
|
+
"COPY . ${LAMBDA_TASK_ROOT}",
|
|
2709
|
+
"RUN npm install"
|
|
2710
|
+
].join("\n");
|
|
2711
|
+
const { build } = await codeBuild.startBuild({
|
|
2712
|
+
environmentVariablesOverride: [
|
|
2713
|
+
{
|
|
2714
|
+
name: "DOCKERFILE",
|
|
2715
|
+
value: lambdaDockerfile || defaultDockerfile
|
|
2716
|
+
},
|
|
2717
|
+
{
|
|
2718
|
+
name: "LAMBDA_EXTERNALS",
|
|
2719
|
+
value: lambdaExternals.join(" ")
|
|
2720
|
+
},
|
|
2721
|
+
{
|
|
2722
|
+
name: "IMAGE_TAG",
|
|
2723
|
+
value: getPackageVersion()
|
|
2724
|
+
},
|
|
2725
|
+
{
|
|
2726
|
+
name: "REPOSITORY_ECR_REPOSITORY",
|
|
2727
|
+
value: "testtesteste"
|
|
2728
|
+
}
|
|
2729
|
+
],
|
|
2730
|
+
projectName: lambdaBuilder,
|
|
2731
|
+
sourceLocationOverride: `${bucket}/${key}`,
|
|
2732
|
+
sourceTypeOverride: "S3"
|
|
2733
|
+
}).promise();
|
|
2734
|
+
if (!_optionalChain([build, 'optionalAccess', _17 => _17.id])) {
|
|
2735
|
+
throw new Error("Cannot start build.");
|
|
2736
|
+
}
|
|
2737
|
+
await waitCodeBuildFinish({ buildId: build.id, name: "lambda-builder" });
|
|
2738
|
+
const imageUri = "178804353523.dkr.ecr.us-east-1.amazonaws.com/testtesteste:0.0.1";
|
|
2739
|
+
return { imageUri };
|
|
2740
|
+
};
|
|
2741
|
+
|
|
2742
|
+
// src/deploy/lambda/uploadCodeToS3.ts
|
|
2743
|
+
var _admzip = require('adm-zip'); var _admzip2 = _interopRequireDefault(_admzip);
|
|
2744
|
+
|
|
2745
|
+
|
|
2746
|
+
|
|
2747
|
+
var logPrefix8 = "lambda";
|
|
2748
|
+
var outFolder2 = "dist";
|
|
2749
|
+
var outFile2 = "index.js";
|
|
2750
|
+
var uploadCodeToS3 = async ({ stackName }) => {
|
|
2751
|
+
_npmlog2.default.info(logPrefix8, `Uploading code to S3...`);
|
|
2752
|
+
const zip = new (0, _admzip2.default)();
|
|
2753
|
+
const code = fs3.default.readFileSync(path2.default.join(process.cwd(), outFolder2, outFile2));
|
|
2754
|
+
zip.addFile("index.js", code);
|
|
2755
|
+
if (!fs3.default.existsSync("dist")) {
|
|
2756
|
+
fs3.default.mkdirSync("dist");
|
|
2757
|
+
}
|
|
2758
|
+
zip.writeZip("dist/lambda.zip");
|
|
2759
|
+
const bucketName = await getBaseStackResource(
|
|
2760
|
+
"BASE_STACK_BUCKET_LOGICAL_NAME"
|
|
2761
|
+
);
|
|
2762
|
+
return uploadFileToS3({
|
|
2763
|
+
bucket: bucketName,
|
|
2764
|
+
contentType: "application/zip",
|
|
2765
|
+
key: `lambdas/${stackName}/lambda.zip`,
|
|
2766
|
+
file: zip.toBuffer()
|
|
2767
|
+
});
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2770
|
+
// src/deploy/lambda/deployLambdaCode.ts
|
|
2771
|
+
|
|
2772
|
+
|
|
2773
|
+
var logPrefix9 = "lambda";
|
|
2774
|
+
var deployLambdaCode = async ({
|
|
2775
|
+
lambdaDockerfile,
|
|
2776
|
+
lambdaExternals,
|
|
2777
|
+
lambdaImage,
|
|
2778
|
+
lambdaInput,
|
|
2779
|
+
stackName
|
|
2780
|
+
}) => {
|
|
2781
|
+
if (!fs3.default.existsSync(lambdaInput)) {
|
|
2782
|
+
return void 0;
|
|
2783
|
+
}
|
|
2784
|
+
_npmlog2.default.info(logPrefix9, "Deploying Lambda code...");
|
|
2785
|
+
await buildLambdaSingleFile({ lambdaExternals, lambdaInput });
|
|
2786
|
+
const { bucket, key, versionId } = await uploadCodeToS3({ stackName });
|
|
2787
|
+
if (!lambdaImage) {
|
|
2788
|
+
await deployLambdaLayers({ lambdaExternals });
|
|
2789
|
+
return { bucket, key, versionId };
|
|
2790
|
+
}
|
|
2791
|
+
const { imageUri } = await uploadCodeToECR({
|
|
2792
|
+
bucket,
|
|
2793
|
+
key,
|
|
2794
|
+
versionId,
|
|
2795
|
+
lambdaDockerfile,
|
|
2796
|
+
lambdaExternals
|
|
2797
|
+
});
|
|
2798
|
+
return { imageUri };
|
|
2799
|
+
};
|
|
2800
|
+
|
|
2801
|
+
// src/deploy/cicd/getCicdStackName.ts
|
|
2802
|
+
|
|
2803
|
+
var getCicdStackName = () => {
|
|
2804
|
+
const project = getProjectName();
|
|
2805
|
+
return _changecase.pascalCase.call(void 0, [NAME, "Cicd", project].join(" "));
|
|
2806
|
+
};
|
|
2807
|
+
|
|
2808
|
+
// src/deploy/cicd/deployCicd.ts
|
|
2809
|
+
|
|
2810
|
+
var logPrefix10 = "cicd";
|
|
2811
|
+
var getLambdaInput = (extension) => {
|
|
2812
|
+
return path6.resolve(__dirname, `lambdas/index.${extension}`);
|
|
2813
|
+
};
|
|
2814
|
+
var deployCicdLambdas = async ({ stackName }) => {
|
|
2815
|
+
const lambdaInput = (() => {
|
|
2816
|
+
if (fs6.existsSync(getLambdaInput("js"))) {
|
|
2817
|
+
return getLambdaInput("js");
|
|
2818
|
+
}
|
|
2819
|
+
if (fs6.existsSync(getLambdaInput("ts"))) {
|
|
2820
|
+
return getLambdaInput("ts");
|
|
2821
|
+
}
|
|
2822
|
+
throw new Error("Cannot read CICD lambdas file.");
|
|
2823
|
+
})();
|
|
2824
|
+
const s32 = await deployLambdaCode({
|
|
2825
|
+
lambdaInput,
|
|
2826
|
+
lambdaExternals: [],
|
|
2827
|
+
/**
|
|
2828
|
+
* Needs stackName to define the S3 key.
|
|
2829
|
+
*/
|
|
2830
|
+
stackName
|
|
2831
|
+
});
|
|
2832
|
+
if (!s32 || !s32.bucket) {
|
|
2833
|
+
throw new Error(
|
|
2834
|
+
"Cannot retrieve bucket in which Lambda code was deployed."
|
|
2835
|
+
);
|
|
2836
|
+
}
|
|
2837
|
+
return s32;
|
|
2838
|
+
};
|
|
2839
|
+
var waitRepositoryImageUpdate = async ({
|
|
2840
|
+
stackName
|
|
2841
|
+
}) => {
|
|
2842
|
+
_npmlog2.default.info(logPrefix10, "Starting repository image update...");
|
|
2843
|
+
const { OutputValue: projectName } = await getStackOutput({
|
|
2844
|
+
stackName,
|
|
2845
|
+
outputKey: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID
|
|
2846
|
+
});
|
|
2847
|
+
if (!projectName) {
|
|
2848
|
+
throw new Error(`Cannot retrieve repository image CodeBuild project name.`);
|
|
2849
|
+
}
|
|
2850
|
+
const build = await startCodeBuildBuild({ projectName });
|
|
2851
|
+
if (build.id) {
|
|
2852
|
+
await waitCodeBuildFinish({ buildId: build.id, name: stackName });
|
|
2853
|
+
}
|
|
2854
|
+
};
|
|
2855
|
+
var deployCicd = async ({
|
|
2856
|
+
cpu,
|
|
2857
|
+
memory,
|
|
2858
|
+
pipelines: pipelines2,
|
|
2859
|
+
updateRepository,
|
|
2860
|
+
slackWebhookUrl,
|
|
2861
|
+
sshKey,
|
|
2862
|
+
sshUrl,
|
|
2863
|
+
taskEnvironment
|
|
2864
|
+
}) => {
|
|
2865
|
+
try {
|
|
2866
|
+
const { stackName } = await handleDeployInitialization({
|
|
2867
|
+
logPrefix: logPrefix10,
|
|
2868
|
+
stackName: getCicdStackName()
|
|
2869
|
+
});
|
|
2870
|
+
await deploy({
|
|
2871
|
+
template: getCicdTemplate({
|
|
2872
|
+
cpu,
|
|
2873
|
+
memory,
|
|
2874
|
+
pipelines: pipelines2,
|
|
2875
|
+
s3: await deployCicdLambdas({ stackName }),
|
|
2876
|
+
slackWebhookUrl,
|
|
2877
|
+
taskEnvironment
|
|
2878
|
+
}),
|
|
2879
|
+
params: {
|
|
2880
|
+
StackName: stackName,
|
|
2881
|
+
Parameters: [
|
|
2882
|
+
{ ParameterKey: "SSHUrl", ParameterValue: sshUrl },
|
|
2883
|
+
{ ParameterKey: "SSHKey", ParameterValue: sshKey }
|
|
2884
|
+
]
|
|
2885
|
+
},
|
|
2886
|
+
terminationProtection: true
|
|
2887
|
+
});
|
|
2888
|
+
if (updateRepository) {
|
|
2889
|
+
await waitRepositoryImageUpdate({ stackName });
|
|
2890
|
+
}
|
|
2891
|
+
} catch (error) {
|
|
2892
|
+
handleDeployError({ error, logPrefix: logPrefix10 });
|
|
2893
|
+
}
|
|
2894
|
+
};
|
|
2895
|
+
|
|
2896
|
+
// src/deploy/cicd/readSSHKey.ts
|
|
2897
|
+
|
|
2898
|
+
var readSSHKey = (dir) => {
|
|
2899
|
+
return fs7.readFileSync(dir, "utf-8");
|
|
2900
|
+
};
|
|
2901
|
+
|
|
2902
|
+
// src/deploy/cicd/command.ts
|
|
2903
|
+
|
|
2904
|
+
var logPrefix11 = "deploy-cicd";
|
|
2905
|
+
var deployCicdCommand = {
|
|
2906
|
+
command: "cicd",
|
|
2907
|
+
describe: "Deploy CICD.",
|
|
2908
|
+
builder: (yargs3) => {
|
|
2909
|
+
return yargs3.options(addGroupToOptions(options, "Deploy CICD Options"));
|
|
2910
|
+
},
|
|
2911
|
+
handler: ({ destroy: destroy2, ...rest }) => {
|
|
2912
|
+
if (destroy2) {
|
|
2913
|
+
_npmlog2.default.info(logPrefix11, `${NAME} doesn't destroy CICD stack.`);
|
|
2914
|
+
} else {
|
|
2915
|
+
deployCicd({
|
|
2916
|
+
...rest,
|
|
2917
|
+
sshKey: readSSHKey(rest["ssh-key"])
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
// ../cloudformation/src/findAndReadCloudFormationTemplate.ts
|
|
2924
|
+
|
|
2925
|
+
|
|
2926
|
+
|
|
2927
|
+
// ../cloudformation/src/readCloudFormationYamlTemplate.ts
|
|
2928
|
+
|
|
2929
|
+
|
|
2930
|
+
|
|
2931
|
+
// ../cloudformation/src/cloudFormationYamlTemplate.ts
|
|
2932
|
+
|
|
2933
|
+
var cloudFormationTypes = [
|
|
2934
|
+
{
|
|
2935
|
+
tag: "!Equals",
|
|
2936
|
+
options: {
|
|
2937
|
+
kind: "sequence",
|
|
2938
|
+
construct: (data) => {
|
|
2939
|
+
return { "Fn::Equals": data };
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
},
|
|
2943
|
+
{
|
|
2944
|
+
tag: "!FindInMap",
|
|
2945
|
+
options: {
|
|
2946
|
+
kind: "sequence",
|
|
2947
|
+
construct: (data) => {
|
|
2948
|
+
return { "Fn::FindInMap": data };
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
},
|
|
2952
|
+
{
|
|
2953
|
+
tag: "!GetAtt",
|
|
2954
|
+
options: {
|
|
2955
|
+
kind: "scalar",
|
|
2956
|
+
construct: (data) => {
|
|
2957
|
+
return { "Fn::GetAtt": data.split(".") };
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
},
|
|
2961
|
+
{
|
|
2962
|
+
tag: "!GetAtt",
|
|
2963
|
+
options: {
|
|
2964
|
+
kind: "sequence",
|
|
2965
|
+
construct: (data) => {
|
|
2966
|
+
return { "Fn::GetAtt": data };
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
},
|
|
2970
|
+
{
|
|
2971
|
+
tag: "!If",
|
|
2972
|
+
options: {
|
|
2973
|
+
kind: "sequence",
|
|
2974
|
+
construct: (data) => {
|
|
2975
|
+
return { "Fn::If": data };
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
},
|
|
2979
|
+
{
|
|
2980
|
+
tag: "!ImportValue",
|
|
2981
|
+
options: {
|
|
2982
|
+
kind: "scalar",
|
|
2983
|
+
construct: (data) => {
|
|
2984
|
+
return { "Fn::ImportValue": data };
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
},
|
|
2988
|
+
{
|
|
2989
|
+
tag: "!Join",
|
|
2990
|
+
options: {
|
|
2991
|
+
kind: "sequence",
|
|
2992
|
+
construct: (data) => {
|
|
2993
|
+
return { "Fn::Join": data };
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
tag: "!Not",
|
|
2999
|
+
options: {
|
|
3000
|
+
kind: "sequence",
|
|
3001
|
+
construct: (data) => {
|
|
3002
|
+
return { "Fn::Not": data };
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
},
|
|
3006
|
+
{
|
|
3007
|
+
tag: "!Ref",
|
|
3008
|
+
options: {
|
|
3009
|
+
kind: "scalar",
|
|
3010
|
+
construct: (data) => {
|
|
3011
|
+
return { Ref: data };
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
},
|
|
3015
|
+
{
|
|
3016
|
+
tag: "!Sub",
|
|
3017
|
+
options: {
|
|
3018
|
+
kind: "scalar",
|
|
3019
|
+
construct: (data) => {
|
|
3020
|
+
return { "Fn::Sub": data };
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
},
|
|
3024
|
+
{
|
|
3025
|
+
tag: "!Sub",
|
|
3026
|
+
options: {
|
|
3027
|
+
kind: "sequence",
|
|
3028
|
+
construct: (data) => {
|
|
3029
|
+
return { "Fn::Sub": data };
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
];
|
|
3034
|
+
var getYamlTypes = (tagAndTypeArr) => {
|
|
3035
|
+
return tagAndTypeArr.map(({ tag, options: options9 }) => {
|
|
3036
|
+
return new _jsyaml2.default.Type(tag, options9);
|
|
3037
|
+
});
|
|
3038
|
+
};
|
|
3039
|
+
var getSchema = (tagAndTypeArr = []) => {
|
|
3040
|
+
return _jsyaml2.default.DEFAULT_SCHEMA.extend(
|
|
3041
|
+
getYamlTypes([...tagAndTypeArr, ...cloudFormationTypes])
|
|
3042
|
+
);
|
|
3043
|
+
};
|
|
3044
|
+
var loadCloudFormationTemplate = (template, tagAndTypeArr = []) => {
|
|
3045
|
+
return _jsyaml2.default.load(template, { schema: getSchema(tagAndTypeArr) });
|
|
3046
|
+
};
|
|
3047
|
+
|
|
3048
|
+
// ../cloudformation/src/readCloudFormationYamlTemplate.ts
|
|
3049
|
+
var getTypes = () => {
|
|
3050
|
+
return [
|
|
3051
|
+
{
|
|
3052
|
+
tag: `!SubString`,
|
|
3053
|
+
options: {
|
|
3054
|
+
kind: "scalar",
|
|
3055
|
+
construct: (filePath) => {
|
|
3056
|
+
return fs8.readFileSync(path7.resolve(process.cwd(), filePath)).toString();
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
];
|
|
3061
|
+
};
|
|
3062
|
+
var readCloudFormationYamlTemplate = ({
|
|
3063
|
+
templatePath
|
|
3064
|
+
}) => {
|
|
3065
|
+
const template = fs8.readFileSync(templatePath).toString();
|
|
3066
|
+
const parsed = loadCloudFormationTemplate(template, getTypes());
|
|
3067
|
+
if (!parsed || typeof parsed === "string") {
|
|
3068
|
+
throw new Error("Cannot parse CloudFormation template.");
|
|
3069
|
+
}
|
|
3070
|
+
return parsed;
|
|
3071
|
+
};
|
|
3072
|
+
|
|
3073
|
+
// ../cloudformation/src/readObjectFile.ts
|
|
3074
|
+
|
|
3075
|
+
|
|
3076
|
+
var readYaml = ({ path: path12 }) => {
|
|
3077
|
+
const template = fs3.default.readFileSync(path12, "utf8") || JSON.stringify({});
|
|
3078
|
+
return _jsyaml2.default.load(template);
|
|
3079
|
+
};
|
|
3080
|
+
var readObjectFile = ({ path: path12 }) => {
|
|
3081
|
+
if (!fs3.default.existsSync(path12)) {
|
|
3082
|
+
return {};
|
|
3083
|
+
}
|
|
3084
|
+
const extension = path12.split(".").pop();
|
|
3085
|
+
if (extension === "ts") {
|
|
3086
|
+
__require("ts-node").register({
|
|
3087
|
+
compilerOptions: { moduleResolution: "node", module: "commonjs" },
|
|
3088
|
+
moduleTypes: {
|
|
3089
|
+
"carlin.*": "cjs"
|
|
3090
|
+
},
|
|
3091
|
+
transpileOnly: true
|
|
3092
|
+
});
|
|
3093
|
+
const tsObj = __require(path12);
|
|
3094
|
+
const obj = tsObj.default || tsObj;
|
|
3095
|
+
return typeof obj === "function" ? obj() : obj;
|
|
3096
|
+
}
|
|
3097
|
+
if (extension === "js") {
|
|
3098
|
+
const obj = __require(path12);
|
|
3099
|
+
return typeof obj === "function" ? obj() : obj;
|
|
3100
|
+
}
|
|
3101
|
+
if (extension === "json") {
|
|
3102
|
+
return __require(path12);
|
|
3103
|
+
}
|
|
3104
|
+
if (extension === "yml" || extension === "yaml") {
|
|
3105
|
+
return readYaml({ path: path12 });
|
|
3106
|
+
}
|
|
3107
|
+
return {};
|
|
3108
|
+
};
|
|
3109
|
+
|
|
3110
|
+
// ../cloudformation/src/findAndReadCloudFormationTemplate.ts
|
|
3111
|
+
var defaultTemplatePaths2 = ["ts", "js", "yaml", "yml", "json"].map(
|
|
3112
|
+
(extension) => {
|
|
3113
|
+
return `./src/cloudformation.${extension}`;
|
|
3114
|
+
}
|
|
3115
|
+
);
|
|
3116
|
+
var findAndReadCloudFormationTemplate = ({
|
|
3117
|
+
templatePath: defaultTemplatePath
|
|
3118
|
+
}) => {
|
|
3119
|
+
const templatePath = defaultTemplatePath || defaultTemplatePaths2.reduce((acc, cur) => {
|
|
3120
|
+
if (acc) {
|
|
3121
|
+
return acc;
|
|
3122
|
+
}
|
|
3123
|
+
return fs10.existsSync(path8.resolve(process.cwd(), cur)) ? cur : acc;
|
|
3124
|
+
}, "");
|
|
3125
|
+
if (!templatePath) {
|
|
3126
|
+
throw new Error("Cannot find a CloudFormation template.");
|
|
3127
|
+
}
|
|
3128
|
+
const extension = _optionalChain([templatePath, 'optionalAccess', _18 => _18.split, 'call', _19 => _19("."), 'access', _20 => _20.pop, 'call', _21 => _21()]);
|
|
3129
|
+
const fullPath = path8.resolve(process.cwd(), templatePath);
|
|
3130
|
+
if (["yaml", "yml"].includes(extension)) {
|
|
3131
|
+
return readCloudFormationYamlTemplate({ templatePath });
|
|
3132
|
+
}
|
|
3133
|
+
return readObjectFile({ path: fullPath });
|
|
3134
|
+
};
|
|
3135
|
+
|
|
3136
|
+
// src/deploy/cloudformation.ts
|
|
3137
|
+
|
|
3138
|
+
var logPrefix12 = "cloudformation";
|
|
3139
|
+
_npmlog2.default.addLevel("event", 1e4, { fg: "yellow" });
|
|
3140
|
+
_npmlog2.default.addLevel("output", 1e4, { fg: "blue" });
|
|
3141
|
+
var defaultTemplatePaths3 = ["ts", "js", "yaml", "yml", "json"].map(
|
|
3142
|
+
(extension) => {
|
|
3143
|
+
return `./src/cloudformation.${extension}`;
|
|
3144
|
+
}
|
|
3145
|
+
);
|
|
3146
|
+
var deployCloudFormation = async ({
|
|
3147
|
+
lambdaDockerfile,
|
|
3148
|
+
lambdaInput,
|
|
3149
|
+
lambdaImage,
|
|
3150
|
+
lambdaExternals = [],
|
|
3151
|
+
parameters,
|
|
3152
|
+
template,
|
|
3153
|
+
templatePath
|
|
3154
|
+
}) => {
|
|
3155
|
+
try {
|
|
3156
|
+
const { stackName } = await handleDeployInitialization({ logPrefix: logPrefix12 });
|
|
3157
|
+
const cloudFormationTemplate = (() => {
|
|
3158
|
+
if (template) {
|
|
3159
|
+
return { ...template };
|
|
3160
|
+
}
|
|
3161
|
+
return findAndReadCloudFormationTemplate({ templatePath });
|
|
3162
|
+
})();
|
|
3163
|
+
_optionalChain([parameters, 'optionalAccess', _22 => _22.forEach, 'call', _23 => _23((parameter) => {
|
|
3164
|
+
if (_optionalChain([cloudFormationTemplate, 'access', _24 => _24.Parameters, 'optionalAccess', _25 => _25[parameter.key]])) {
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
if (!cloudFormationTemplate.Parameters) {
|
|
3168
|
+
cloudFormationTemplate.Parameters = {};
|
|
3169
|
+
}
|
|
3170
|
+
const type = (() => {
|
|
3171
|
+
if (typeof parameter.value === "string") {
|
|
3172
|
+
return "String";
|
|
3173
|
+
}
|
|
3174
|
+
if (typeof parameter.value === "number") {
|
|
3175
|
+
return "Number";
|
|
3176
|
+
}
|
|
3177
|
+
throw new Error(
|
|
3178
|
+
`Parameter assertion failed. Parameter ${parameter.key} value ${parameter.value} is not mapped.`
|
|
3179
|
+
);
|
|
3180
|
+
})();
|
|
3181
|
+
cloudFormationTemplate.Parameters[parameter.key] = {
|
|
3182
|
+
Type: type
|
|
3183
|
+
};
|
|
3184
|
+
})]);
|
|
3185
|
+
await validateTemplate({ stackName, template: cloudFormationTemplate });
|
|
3186
|
+
const params = {
|
|
3187
|
+
StackName: stackName,
|
|
3188
|
+
Parameters: _optionalChain([parameters, 'optionalAccess', _26 => _26.map, 'call', _27 => _27((parameter) => {
|
|
3189
|
+
return {
|
|
3190
|
+
ParameterKey: parameter.key,
|
|
3191
|
+
ParameterValue: parameter.value,
|
|
3192
|
+
UsePreviousValue: parameter.usePreviousValue,
|
|
3193
|
+
ResolvedValue: parameter.resolvedValue
|
|
3194
|
+
};
|
|
3195
|
+
})]) || []
|
|
3196
|
+
};
|
|
3197
|
+
const deployCloudFormationDeployLambdaCode = async () => {
|
|
3198
|
+
const response = await deployLambdaCode({
|
|
3199
|
+
lambdaDockerfile,
|
|
3200
|
+
lambdaExternals,
|
|
3201
|
+
lambdaInput,
|
|
3202
|
+
lambdaImage,
|
|
3203
|
+
stackName
|
|
3204
|
+
});
|
|
3205
|
+
if (response) {
|
|
3206
|
+
const { bucket, key, versionId, imageUri } = response;
|
|
3207
|
+
if (imageUri) {
|
|
3208
|
+
cloudFormationTemplate.Parameters = {
|
|
3209
|
+
LambdaImageUri: { Type: "String" },
|
|
3210
|
+
...cloudFormationTemplate.Parameters
|
|
3211
|
+
};
|
|
3212
|
+
params.Parameters.push({
|
|
3213
|
+
ParameterKey: "LambdaImageUri",
|
|
3214
|
+
ParameterValue: imageUri
|
|
3215
|
+
});
|
|
3216
|
+
} else if (bucket && key && versionId) {
|
|
3217
|
+
cloudFormationTemplate.Parameters = {
|
|
3218
|
+
LambdaS3Bucket: { Type: "String" },
|
|
3219
|
+
LambdaS3Key: { Type: "String" },
|
|
3220
|
+
LambdaS3ObjectVersion: { Type: "String" },
|
|
3221
|
+
...cloudFormationTemplate.Parameters
|
|
3222
|
+
};
|
|
3223
|
+
params.Parameters.push(
|
|
3224
|
+
{
|
|
3225
|
+
ParameterKey: "LambdaS3Bucket",
|
|
3226
|
+
ParameterValue: bucket
|
|
3227
|
+
},
|
|
3228
|
+
{
|
|
3229
|
+
ParameterKey: "LambdaS3Key",
|
|
3230
|
+
ParameterValue: key
|
|
3231
|
+
},
|
|
3232
|
+
/**
|
|
3233
|
+
* Used by CloudFormation AWS::Lambda::Function
|
|
3234
|
+
* @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html}
|
|
3235
|
+
* and by CloudFormation AWS::Serverless::Function
|
|
3236
|
+
* @see {@link https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-functioncode.html}
|
|
3237
|
+
*/
|
|
3238
|
+
{
|
|
3239
|
+
ParameterKey: "LambdaS3ObjectVersion",
|
|
3240
|
+
ParameterValue: versionId
|
|
3241
|
+
}
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
await deployCloudFormationDeployLambdaCode();
|
|
3247
|
+
const output = await deploy({
|
|
3248
|
+
params,
|
|
3249
|
+
template: cloudFormationTemplate
|
|
3250
|
+
});
|
|
3251
|
+
return output;
|
|
3252
|
+
} catch (error) {
|
|
3253
|
+
return handleDeployError({ error, logPrefix: logPrefix12 });
|
|
3254
|
+
}
|
|
3255
|
+
};
|
|
3256
|
+
var emptyStackBuckets = async ({ stackName }) => {
|
|
3257
|
+
const buckets = [];
|
|
3258
|
+
await (async ({ nextToken }) => {
|
|
3259
|
+
const {
|
|
3260
|
+
// NextToken,
|
|
3261
|
+
StackResourceSummaries
|
|
3262
|
+
} = await cloudFormationV2().listStackResources({ StackName: stackName, NextToken: nextToken }).promise();
|
|
3263
|
+
(StackResourceSummaries || []).forEach(
|
|
3264
|
+
({ ResourceType, PhysicalResourceId }) => {
|
|
3265
|
+
if (ResourceType === "AWS::S3::Bucket" && PhysicalResourceId) {
|
|
3266
|
+
buckets.push(PhysicalResourceId);
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
);
|
|
3270
|
+
})({});
|
|
3271
|
+
return Promise.all(
|
|
3272
|
+
buckets.map((bucket) => {
|
|
3273
|
+
return emptyS3Directory({ bucket });
|
|
3274
|
+
})
|
|
3275
|
+
);
|
|
3276
|
+
};
|
|
3277
|
+
var destroy = async ({ stackName }) => {
|
|
3278
|
+
const environment = getEnvironment();
|
|
3279
|
+
if (environment) {
|
|
3280
|
+
_npmlog2.default.info(
|
|
3281
|
+
logPrefix12,
|
|
3282
|
+
`Cannot destroy stack when environment (${environment}) is defined.`
|
|
3283
|
+
);
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
if (!await doesStackExist({ stackName })) {
|
|
3287
|
+
_npmlog2.default.info(logPrefix12, `Stack ${stackName} doesn't exist.`);
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
if (!await canDestroyStack({ stackName })) {
|
|
3291
|
+
const message = `Stack ${stackName} cannot be destroyed while TerminationProtection is enabled.`;
|
|
3292
|
+
throw new Error(message);
|
|
3293
|
+
}
|
|
3294
|
+
await emptyStackBuckets({ stackName });
|
|
3295
|
+
await deleteStack({ stackName });
|
|
3296
|
+
};
|
|
3297
|
+
var destroyCloudFormation = async ({
|
|
3298
|
+
stackName: defaultStackName
|
|
3299
|
+
} = {}) => {
|
|
3300
|
+
try {
|
|
3301
|
+
_npmlog2.default.info(logPrefix12, "CAUTION! Starting CloudFormation destroy...");
|
|
3302
|
+
const stackName = defaultStackName || await getStackName();
|
|
3303
|
+
_npmlog2.default.info(logPrefix12, `stackName: ${stackName}`);
|
|
3304
|
+
await destroy({ stackName });
|
|
3305
|
+
} catch (error) {
|
|
3306
|
+
handleDeployError({ error, logPrefix: logPrefix12 });
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
|
|
3310
|
+
// src/deploy/lambdaLayer/command.ts
|
|
3311
|
+
|
|
3312
|
+
var logPrefix13 = "deploy-lambda-layer";
|
|
3313
|
+
var packageNameRegex = /@[~^]?([\dvx*]+(?:[-.](?:[\dx*]+|alpha|beta))*)/;
|
|
3314
|
+
var options2 = {
|
|
3315
|
+
packages: {
|
|
3316
|
+
array: true,
|
|
3317
|
+
describe: `NPM packages' names to be deployed as Lambda Layers. It must follow the format: ${packageNameRegex.toString()}.`,
|
|
3318
|
+
required: true,
|
|
3319
|
+
type: "string"
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
var deployLambdaLayerCommand = {
|
|
3323
|
+
command: "lambda-layer",
|
|
3324
|
+
describe: "Deploy Lambda Layer.",
|
|
3325
|
+
builder: (yargs3) => yargs3.options(addGroupToOptions(options2, "Deploy Lambda Layer Options")).check(({ packages }) => {
|
|
3326
|
+
const invalidPackages = packages.map((packageName) => {
|
|
3327
|
+
return packageNameRegex.test(packageName) ? void 0 : packageName;
|
|
3328
|
+
}).filter((packageName) => !!packageName);
|
|
3329
|
+
if (invalidPackages.length > 0) {
|
|
3330
|
+
throw new Error(
|
|
3331
|
+
`Some package names are invalid: ${invalidPackages.join(
|
|
3332
|
+
", "
|
|
3333
|
+
)}. The package must follow the pattern: ${packageNameRegex.toString()}.`
|
|
3334
|
+
);
|
|
3335
|
+
} else {
|
|
3336
|
+
return true;
|
|
3337
|
+
}
|
|
3338
|
+
}),
|
|
3339
|
+
handler: ({ destroy: destroy2, ...rest }) => {
|
|
3340
|
+
if (destroy2) {
|
|
3341
|
+
_npmlog2.default.info(logPrefix13, `${NAME} doesn't destroy lambda layers.`);
|
|
3342
|
+
} else {
|
|
3343
|
+
deployLambdaLayer(rest);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
};
|
|
3347
|
+
|
|
3348
|
+
// src/deploy/staticApp/findDefaultBuildFolder.ts
|
|
3349
|
+
var defaultBuildFolders = [
|
|
3350
|
+
/**
|
|
3351
|
+
* Create React App default build folder
|
|
3352
|
+
*/
|
|
3353
|
+
"build",
|
|
3354
|
+
/**
|
|
3355
|
+
* Next.js default output folder
|
|
3356
|
+
*/
|
|
3357
|
+
"out",
|
|
3358
|
+
/**
|
|
3359
|
+
* Storybook default output folder
|
|
3360
|
+
*/
|
|
3361
|
+
"storybook-static",
|
|
3362
|
+
/**
|
|
3363
|
+
* Vite.js default build folder
|
|
3364
|
+
*/
|
|
3365
|
+
"dist"
|
|
3366
|
+
];
|
|
3367
|
+
var findDefaultBuildFolder = async () => {
|
|
3368
|
+
const validFolders = await Promise.all(
|
|
3369
|
+
defaultBuildFolders.map(async (directory) => {
|
|
3370
|
+
const allFiles = await getAllFilesInsideADirectory({
|
|
3371
|
+
directory
|
|
3372
|
+
});
|
|
3373
|
+
return { directory, isValid: allFiles.length !== 0 };
|
|
3374
|
+
})
|
|
3375
|
+
);
|
|
3376
|
+
const validFolder = validFolders.reduce((acc, cur) => {
|
|
3377
|
+
if (cur.isValid) {
|
|
3378
|
+
return cur.directory;
|
|
3379
|
+
}
|
|
3380
|
+
return acc;
|
|
3381
|
+
}, "");
|
|
3382
|
+
return validFolder;
|
|
3383
|
+
};
|
|
3384
|
+
|
|
3385
|
+
// src/deploy/staticApp/getStaticAppBucket.ts
|
|
3386
|
+
var STATIC_APP_BUCKET_LOGICAL_ID = "StaticBucket";
|
|
3387
|
+
var getStaticAppBucket = async ({
|
|
3388
|
+
stackName
|
|
3389
|
+
}) => {
|
|
3390
|
+
const params = {
|
|
3391
|
+
LogicalResourceId: STATIC_APP_BUCKET_LOGICAL_ID,
|
|
3392
|
+
StackName: stackName
|
|
3393
|
+
};
|
|
3394
|
+
try {
|
|
3395
|
+
const { StackResourceDetail } = await describeStackResource(params);
|
|
3396
|
+
return _optionalChain([StackResourceDetail, 'optionalAccess', _28 => _28.PhysicalResourceId]);
|
|
3397
|
+
} catch (error) {
|
|
3398
|
+
return void 0;
|
|
3399
|
+
}
|
|
3400
|
+
};
|
|
3401
|
+
|
|
3402
|
+
// src/deploy/staticApp/staticApp.template.ts
|
|
3403
|
+
var PACKAGE_VERSION = getPackageVersion();
|
|
3404
|
+
var STATIC_APP_BUCKET_LOGICAL_ID2 = "StaticBucket";
|
|
3405
|
+
var CLOUDFRONT_DISTRIBUTION_LOGICAL_ID = "CloudFrontDistribution";
|
|
3406
|
+
var CLOUDFRONT_ORIGIN_ACCESS_CONTROL_LOGICAL_ID = "OriginAccessControl";
|
|
3407
|
+
var ROUTE_53_RECORD_SET_GROUP_LOGICAL_ID = "Route53RecordSetGroup";
|
|
3408
|
+
var ERROR_DOCUMENT = "404/index.html";
|
|
3409
|
+
var CACHE_POLICY_ID = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad";
|
|
3410
|
+
var ORIGIN_REQUEST_POLICY_ID = "88a5eaf4-2fd4-4709-b370-b4c650ea3fcf";
|
|
3411
|
+
var ORIGIN_RESPONSE_POLICY_ID = "eaab4381-ed33-4a86-88ca-d9558dc6cd63";
|
|
3412
|
+
var getBucketStaticWebsiteTemplate = ({
|
|
3413
|
+
spa
|
|
3414
|
+
}) => {
|
|
3415
|
+
return {
|
|
3416
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
3417
|
+
Resources: {
|
|
3418
|
+
[STATIC_APP_BUCKET_LOGICAL_ID2]: {
|
|
3419
|
+
Type: "AWS::S3::Bucket",
|
|
3420
|
+
Properties: {
|
|
3421
|
+
CorsConfiguration: {
|
|
3422
|
+
CorsRules: [
|
|
3423
|
+
{
|
|
3424
|
+
AllowedHeaders: ["*"],
|
|
3425
|
+
AllowedMethods: ["GET"],
|
|
3426
|
+
AllowedOrigins: ["*"],
|
|
3427
|
+
Id: "OpenCors",
|
|
3428
|
+
MaxAge: 600
|
|
3429
|
+
}
|
|
3430
|
+
]
|
|
3431
|
+
},
|
|
3432
|
+
PublicAccessBlockConfiguration: {
|
|
3433
|
+
BlockPublicPolicy: false
|
|
3434
|
+
},
|
|
3435
|
+
WebsiteConfiguration: {
|
|
3436
|
+
IndexDocument: `index.html`,
|
|
3437
|
+
ErrorDocument: spa ? "index.html" : ERROR_DOCUMENT
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
},
|
|
3441
|
+
[`${STATIC_APP_BUCKET_LOGICAL_ID2}S3BucketPolicy`]: {
|
|
3442
|
+
Type: "AWS::S3::BucketPolicy",
|
|
3443
|
+
Properties: {
|
|
3444
|
+
Bucket: { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
|
|
3445
|
+
PolicyDocument: {
|
|
3446
|
+
Statement: [
|
|
3447
|
+
{
|
|
3448
|
+
Action: ["s3:GetObject"],
|
|
3449
|
+
Effect: "Allow",
|
|
3450
|
+
Principal: "*",
|
|
3451
|
+
Resource: {
|
|
3452
|
+
"Fn::Join": [
|
|
3453
|
+
"",
|
|
3454
|
+
[
|
|
3455
|
+
"arn:aws:s3:::",
|
|
3456
|
+
{ Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
|
|
3457
|
+
"/*"
|
|
3458
|
+
]
|
|
3459
|
+
]
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
]
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
},
|
|
3467
|
+
Outputs: {
|
|
3468
|
+
BucketWebsiteURL: {
|
|
3469
|
+
Description: "Bucket static app website URL",
|
|
3470
|
+
Value: {
|
|
3471
|
+
"Fn::GetAtt": [STATIC_APP_BUCKET_LOGICAL_ID2, "WebsiteURL"]
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
};
|
|
3476
|
+
};
|
|
3477
|
+
var getCloudFrontTemplate = ({
|
|
3478
|
+
acm,
|
|
3479
|
+
aliases = [],
|
|
3480
|
+
spa,
|
|
3481
|
+
hostedZoneName
|
|
3482
|
+
}) => {
|
|
3483
|
+
const template = {
|
|
3484
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
3485
|
+
Resources: {
|
|
3486
|
+
[STATIC_APP_BUCKET_LOGICAL_ID2]: {
|
|
3487
|
+
Type: "AWS::S3::Bucket",
|
|
3488
|
+
Properties: {
|
|
3489
|
+
PublicAccessBlockConfiguration: {
|
|
3490
|
+
BlockPublicPolicy: false
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
},
|
|
3494
|
+
[`${STATIC_APP_BUCKET_LOGICAL_ID2}S3BucketPolicy`]: {
|
|
3495
|
+
Type: "AWS::S3::BucketPolicy",
|
|
3496
|
+
Properties: {
|
|
3497
|
+
Bucket: { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
|
|
3498
|
+
PolicyDocument: {
|
|
3499
|
+
Statement: [
|
|
3500
|
+
/**
|
|
3501
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
|
|
3502
|
+
*/
|
|
3503
|
+
{
|
|
3504
|
+
Sid: "AllowCloudFrontServicePrincipalReadOnly",
|
|
3505
|
+
Effect: "Allow",
|
|
3506
|
+
Principal: {
|
|
3507
|
+
Service: "cloudfront.amazonaws.com"
|
|
3508
|
+
},
|
|
3509
|
+
Action: "s3:GetObject",
|
|
3510
|
+
Resource: {
|
|
3511
|
+
"Fn::Join": [
|
|
3512
|
+
"",
|
|
3513
|
+
[
|
|
3514
|
+
"arn:aws:s3:::",
|
|
3515
|
+
{ Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
|
|
3516
|
+
"/*"
|
|
3517
|
+
]
|
|
3518
|
+
]
|
|
3519
|
+
},
|
|
3520
|
+
Condition: {
|
|
3521
|
+
StringEquals: {
|
|
3522
|
+
"AWS:SourceArn": (
|
|
3523
|
+
// 'arn:aws:cloudfront::<AWS account ID>:distribution/<CloudFront distribution ID>',
|
|
3524
|
+
{
|
|
3525
|
+
"Fn::Join": [
|
|
3526
|
+
"",
|
|
3527
|
+
[
|
|
3528
|
+
"arn:aws:cloudfront::",
|
|
3529
|
+
{ Ref: "AWS::AccountId" },
|
|
3530
|
+
":distribution/",
|
|
3531
|
+
{ Ref: CLOUDFRONT_DISTRIBUTION_LOGICAL_ID }
|
|
3532
|
+
]
|
|
3533
|
+
]
|
|
3534
|
+
}
|
|
3535
|
+
)
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
]
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
};
|
|
3545
|
+
const cloudFrontResources = {
|
|
3546
|
+
[CLOUDFRONT_DISTRIBUTION_LOGICAL_ID]: {
|
|
3547
|
+
Type: "AWS::CloudFront::Distribution",
|
|
3548
|
+
Properties: {
|
|
3549
|
+
DistributionConfig: {
|
|
3550
|
+
Comment: {
|
|
3551
|
+
"Fn::Sub": [
|
|
3552
|
+
"CloudFront Distribution for ${Project} project.",
|
|
3553
|
+
{ Project: { Ref: "Project" } }
|
|
3554
|
+
]
|
|
3555
|
+
},
|
|
3556
|
+
CustomErrorResponses: [403, 404].map((errorCode) => {
|
|
3557
|
+
if (spa) {
|
|
3558
|
+
return {
|
|
3559
|
+
ErrorCachingMinTTL: 60 * 60 * 24,
|
|
3560
|
+
ErrorCode: errorCode,
|
|
3561
|
+
ResponseCode: 200,
|
|
3562
|
+
ResponsePagePath: "/index.html"
|
|
3563
|
+
};
|
|
3564
|
+
}
|
|
3565
|
+
return {
|
|
3566
|
+
ErrorCachingMinTTL: 0,
|
|
3567
|
+
ErrorCode: errorCode,
|
|
3568
|
+
ResponseCode: 404,
|
|
3569
|
+
ResponsePagePath: "/" + ERROR_DOCUMENT
|
|
3570
|
+
};
|
|
3571
|
+
}),
|
|
3572
|
+
DefaultCacheBehavior: {
|
|
3573
|
+
AllowedMethods: ["GET", "HEAD", "OPTIONS"],
|
|
3574
|
+
Compress: true,
|
|
3575
|
+
CachedMethods: ["GET", "HEAD", "OPTIONS"],
|
|
3576
|
+
/**
|
|
3577
|
+
* Caching OPTIONS. Related to OriginRequestPolicyId property.
|
|
3578
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-cors
|
|
3579
|
+
*/
|
|
3580
|
+
OriginRequestPolicyId: ORIGIN_REQUEST_POLICY_ID,
|
|
3581
|
+
/**
|
|
3582
|
+
* CachePolicyId property:
|
|
3583
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-cachepolicyid
|
|
3584
|
+
*/
|
|
3585
|
+
CachePolicyId: CACHE_POLICY_ID,
|
|
3586
|
+
ResponseHeadersPolicyId: ORIGIN_RESPONSE_POLICY_ID,
|
|
3587
|
+
TargetOriginId: { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
|
|
3588
|
+
ViewerProtocolPolicy: "redirect-to-https"
|
|
3589
|
+
},
|
|
3590
|
+
DefaultRootObject: "index.html",
|
|
3591
|
+
Enabled: true,
|
|
3592
|
+
HttpVersion: "http2",
|
|
3593
|
+
Origins: [
|
|
3594
|
+
{
|
|
3595
|
+
DomainName: {
|
|
3596
|
+
"Fn::GetAtt": [STATIC_APP_BUCKET_LOGICAL_ID2, "DomainName"]
|
|
3597
|
+
},
|
|
3598
|
+
Id: { Ref: STATIC_APP_BUCKET_LOGICAL_ID2 },
|
|
3599
|
+
OriginAccessControlId: {
|
|
3600
|
+
"Fn::GetAtt": [
|
|
3601
|
+
CLOUDFRONT_ORIGIN_ACCESS_CONTROL_LOGICAL_ID,
|
|
3602
|
+
"Id"
|
|
3603
|
+
]
|
|
3604
|
+
},
|
|
3605
|
+
/**
|
|
3606
|
+
* Note: As of September 2022, an empty OriginAccessIdentity must be specified in S3OriginConfig.
|
|
3607
|
+
*/
|
|
3608
|
+
S3OriginConfig: {
|
|
3609
|
+
OriginAccessIdentity: ""
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
]
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
},
|
|
3616
|
+
[CLOUDFRONT_ORIGIN_ACCESS_CONTROL_LOGICAL_ID]: {
|
|
3617
|
+
Type: "AWS::CloudFront::OriginAccessControl",
|
|
3618
|
+
Properties: {
|
|
3619
|
+
OriginAccessControlConfig: {
|
|
3620
|
+
Description: {
|
|
3621
|
+
"Fn::Sub": [
|
|
3622
|
+
"Default Origin Access Control for ${Project} project.",
|
|
3623
|
+
{ Project: { Ref: "Project" } }
|
|
3624
|
+
]
|
|
3625
|
+
},
|
|
3626
|
+
Name: {
|
|
3627
|
+
Ref: "AWS::StackName"
|
|
3628
|
+
},
|
|
3629
|
+
OriginAccessControlOriginType: "s3",
|
|
3630
|
+
SigningBehavior: "always",
|
|
3631
|
+
SigningProtocol: "sigv4"
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
if (acm) {
|
|
3637
|
+
const acmRegex = /^arn:aws:acm:[-a-z0-9]+:\d{12}:certificate\/[-a-z0-9]+$/;
|
|
3638
|
+
const acmCertificateArn = acmRegex.test(acm) ? acm : {
|
|
3639
|
+
"Fn::ImportValue": acm
|
|
3640
|
+
};
|
|
3641
|
+
cloudFrontResources.CloudFrontDistribution.Properties.DistributionConfig = {
|
|
3642
|
+
...cloudFrontResources.CloudFrontDistribution.Properties.DistributionConfig,
|
|
3643
|
+
Aliases: aliases || { Ref: "AWS::NoValue" },
|
|
3644
|
+
ViewerCertificate: {
|
|
3645
|
+
AcmCertificateArn: acmCertificateArn,
|
|
3646
|
+
/**
|
|
3647
|
+
* AWS CloudFront recommendation.
|
|
3648
|
+
*/
|
|
3649
|
+
MinimumProtocolVersion: "TLSv1.2_2021",
|
|
3650
|
+
SslSupportMethod: "sni-only"
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
if (hostedZoneName && aliases) {
|
|
3655
|
+
const recordSets = aliases.map((alias) => {
|
|
3656
|
+
if (alias === hostedZoneName) {
|
|
3657
|
+
return {
|
|
3658
|
+
AliasTarget: {
|
|
3659
|
+
DNSName: {
|
|
3660
|
+
"Fn::GetAtt": `${CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`
|
|
3661
|
+
},
|
|
3662
|
+
/**
|
|
3663
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
|
|
3664
|
+
*/
|
|
3665
|
+
HostedZoneId: "Z2FDTNDATAQYW2"
|
|
3666
|
+
},
|
|
3667
|
+
Name: alias,
|
|
3668
|
+
Type: "A"
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
return {
|
|
3672
|
+
Name: alias,
|
|
3673
|
+
ResourceRecords: [
|
|
3674
|
+
{
|
|
3675
|
+
"Fn::GetAtt": `${CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`
|
|
3676
|
+
}
|
|
3677
|
+
],
|
|
3678
|
+
TTL: 60,
|
|
3679
|
+
Type: "CNAME"
|
|
3680
|
+
};
|
|
3681
|
+
});
|
|
3682
|
+
const route53RecordSetGroupResources = {
|
|
3683
|
+
[ROUTE_53_RECORD_SET_GROUP_LOGICAL_ID]: {
|
|
3684
|
+
Type: "AWS::Route53::RecordSetGroup",
|
|
3685
|
+
DependsOn: [CLOUDFRONT_DISTRIBUTION_LOGICAL_ID],
|
|
3686
|
+
Properties: {
|
|
3687
|
+
// https://forums.aws.amazon.com/thread.jspa?threadID=103919
|
|
3688
|
+
HostedZoneName: `${hostedZoneName}${hostedZoneName.endsWith(".") ? "" : "."}`,
|
|
3689
|
+
RecordSets: recordSets
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
};
|
|
3693
|
+
template.Resources = {
|
|
3694
|
+
...template.Resources,
|
|
3695
|
+
...route53RecordSetGroupResources
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
template.Resources = { ...template.Resources, ...cloudFrontResources };
|
|
3699
|
+
const aliasesOutput = (aliases || []).reduce(
|
|
3700
|
+
(acc, alias, index) => {
|
|
3701
|
+
return {
|
|
3702
|
+
...acc,
|
|
3703
|
+
[`Alias${index}URL`]: {
|
|
3704
|
+
Value: `https://${alias}`
|
|
3705
|
+
}
|
|
3706
|
+
};
|
|
3707
|
+
},
|
|
3708
|
+
{}
|
|
3709
|
+
);
|
|
3710
|
+
template.Outputs = {
|
|
3711
|
+
...template.Outputs,
|
|
3712
|
+
...aliasesOutput,
|
|
3713
|
+
CloudFrontURL: {
|
|
3714
|
+
Value: {
|
|
3715
|
+
"Fn::Join": [
|
|
3716
|
+
"",
|
|
3717
|
+
[
|
|
3718
|
+
"https://",
|
|
3719
|
+
{
|
|
3720
|
+
"Fn::GetAtt": `${CLOUDFRONT_DISTRIBUTION_LOGICAL_ID}.DomainName`
|
|
3721
|
+
}
|
|
3722
|
+
]
|
|
3723
|
+
]
|
|
3724
|
+
}
|
|
3725
|
+
},
|
|
3726
|
+
CloudFrontDistributionId: {
|
|
3727
|
+
Value: {
|
|
3728
|
+
Ref: CLOUDFRONT_DISTRIBUTION_LOGICAL_ID
|
|
3729
|
+
}
|
|
3730
|
+
},
|
|
3731
|
+
CurrentVersion: {
|
|
3732
|
+
Value: PACKAGE_VERSION
|
|
3733
|
+
}
|
|
3734
|
+
};
|
|
3735
|
+
return template;
|
|
3736
|
+
};
|
|
3737
|
+
var getStaticAppTemplate = ({
|
|
3738
|
+
acm,
|
|
3739
|
+
aliases,
|
|
3740
|
+
cloudfront,
|
|
3741
|
+
spa,
|
|
3742
|
+
hostedZoneName,
|
|
3743
|
+
region
|
|
3744
|
+
}) => {
|
|
3745
|
+
if (cloudfront) {
|
|
3746
|
+
return getCloudFrontTemplate({
|
|
3747
|
+
acm,
|
|
3748
|
+
aliases,
|
|
3749
|
+
cloudfront,
|
|
3750
|
+
spa,
|
|
3751
|
+
hostedZoneName,
|
|
3752
|
+
region
|
|
3753
|
+
});
|
|
3754
|
+
}
|
|
3755
|
+
return getBucketStaticWebsiteTemplate({ spa });
|
|
3756
|
+
};
|
|
3757
|
+
|
|
3758
|
+
// src/deploy/staticApp/invalidateCloudFront.ts
|
|
3759
|
+
|
|
3760
|
+
|
|
3761
|
+
var CLOUDFRONT_DISTRIBUTION_ID = "CloudFrontDistributionId";
|
|
3762
|
+
var logPrefix14 = "static-app";
|
|
3763
|
+
var invalidateCloudFront = async ({
|
|
3764
|
+
outputs
|
|
3765
|
+
}) => {
|
|
3766
|
+
_npmlog2.default.info(logPrefix14, "Invalidating CloudFront...");
|
|
3767
|
+
if (!outputs) {
|
|
3768
|
+
_npmlog2.default.info(logPrefix14, "Invalidation: outputs do not exist.");
|
|
3769
|
+
return;
|
|
3770
|
+
}
|
|
3771
|
+
const cloudFrontDistributionIDOutput = outputs.find(
|
|
3772
|
+
(output) => output.OutputKey === CLOUDFRONT_DISTRIBUTION_ID
|
|
3773
|
+
);
|
|
3774
|
+
if (_optionalChain([cloudFrontDistributionIDOutput, 'optionalAccess', _29 => _29.OutputValue])) {
|
|
3775
|
+
const distributionId = cloudFrontDistributionIDOutput.OutputValue;
|
|
3776
|
+
const params = {
|
|
3777
|
+
DistributionId: distributionId,
|
|
3778
|
+
InvalidationBatch: {
|
|
3779
|
+
CallerReference: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3780
|
+
Paths: {
|
|
3781
|
+
Items: ["/*"],
|
|
3782
|
+
Quantity: 1
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
};
|
|
3786
|
+
const cloudFront = new (0, _awssdk.CloudFront)();
|
|
3787
|
+
try {
|
|
3788
|
+
await cloudFront.createInvalidation(params).promise();
|
|
3789
|
+
_npmlog2.default.info(
|
|
3790
|
+
logPrefix14,
|
|
3791
|
+
`CloudFront Distribution ID ${distributionId} invalidated with success.`
|
|
3792
|
+
);
|
|
3793
|
+
} catch (err) {
|
|
3794
|
+
_npmlog2.default.error(
|
|
3795
|
+
logPrefix14,
|
|
3796
|
+
`Error while trying to invalidate CloudFront distribution ${distributionId}.`
|
|
3797
|
+
);
|
|
3798
|
+
_npmlog2.default.error(logPrefix14, err);
|
|
3799
|
+
}
|
|
3800
|
+
} else {
|
|
3801
|
+
_npmlog2.default.info(
|
|
3802
|
+
logPrefix14,
|
|
3803
|
+
`Cannot invalidate because distribution does not exist.`
|
|
3804
|
+
);
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
|
|
3808
|
+
// src/deploy/staticApp/removeOldVersions.ts
|
|
3809
|
+
|
|
3810
|
+
var _semver = require('semver'); var _semver2 = _interopRequireDefault(_semver);
|
|
3811
|
+
var logPrefix15 = "static-app";
|
|
3812
|
+
var removeOldVersions = async ({ bucket }) => {
|
|
3813
|
+
try {
|
|
3814
|
+
_npmlog2.default.info(logPrefix15, "Removing old versions...");
|
|
3815
|
+
const { CommonPrefixes = [] } = await s3.listObjectsV2({ Bucket: bucket, Delimiter: "/" }).promise();
|
|
3816
|
+
const versions = _optionalChain([CommonPrefixes, 'optionalAccess', _30 => _30.map, 'call', _31 => _31(({ Prefix }) => {
|
|
3817
|
+
return _optionalChain([Prefix, 'optionalAccess', _32 => _32.replace, 'call', _33 => _33("/", "")]);
|
|
3818
|
+
}), 'access', _34 => _34.filter, 'call', _35 => _35((version) => {
|
|
3819
|
+
return _semver2.default.valid(version);
|
|
3820
|
+
}), 'access', _36 => _36.sort, 'call', _37 => _37((a, b) => {
|
|
3821
|
+
return _semver2.default.gt(a, b) ? -1 : 1;
|
|
3822
|
+
})]);
|
|
3823
|
+
versions.shift();
|
|
3824
|
+
versions.shift();
|
|
3825
|
+
versions.shift();
|
|
3826
|
+
await Promise.all(
|
|
3827
|
+
versions.map((version) => {
|
|
3828
|
+
return deleteS3Directory({ bucket, directory: `${version}` });
|
|
3829
|
+
})
|
|
3830
|
+
);
|
|
3831
|
+
} catch (error) {
|
|
3832
|
+
_npmlog2.default.info(
|
|
3833
|
+
logPrefix15,
|
|
3834
|
+
`Cannot remove older versions from "${bucket}" bucket.`
|
|
3835
|
+
);
|
|
3836
|
+
}
|
|
3837
|
+
};
|
|
3838
|
+
|
|
3839
|
+
// src/deploy/staticApp/uploadBuiltAppToS3.ts
|
|
3840
|
+
var uploadBuiltAppToS3 = async ({
|
|
3841
|
+
buildFolder: directory,
|
|
3842
|
+
bucket
|
|
3843
|
+
}) => {
|
|
3844
|
+
if (directory) {
|
|
3845
|
+
const files = await getAllFilesInsideADirectory({ directory });
|
|
3846
|
+
if (files.length > 0) {
|
|
3847
|
+
await emptyS3Directory({ bucket });
|
|
3848
|
+
}
|
|
3849
|
+
await uploadDirectoryToS3({ bucket, directory });
|
|
3850
|
+
return;
|
|
3851
|
+
}
|
|
3852
|
+
const defaultDirectory = await findDefaultBuildFolder();
|
|
3853
|
+
if (defaultDirectory) {
|
|
3854
|
+
await emptyS3Directory({ bucket });
|
|
3855
|
+
await uploadDirectoryToS3({ bucket, directory: defaultDirectory });
|
|
3856
|
+
await copyRoot404To404Index({ bucket });
|
|
3857
|
+
return;
|
|
3858
|
+
}
|
|
3859
|
+
throw new Error(
|
|
3860
|
+
`build-folder option wasn't provided and files weren't found in ${defaultBuildFolders.join(
|
|
3861
|
+
", "
|
|
3862
|
+
)} directories.`
|
|
3863
|
+
);
|
|
3864
|
+
};
|
|
3865
|
+
|
|
3866
|
+
// src/deploy/staticApp/deployStaticApp.ts
|
|
3867
|
+
var logPrefix16 = "static-app";
|
|
3868
|
+
var deployStaticApp = async ({
|
|
3869
|
+
acm,
|
|
3870
|
+
aliases,
|
|
3871
|
+
buildFolder,
|
|
3872
|
+
cloudfront,
|
|
3873
|
+
spa,
|
|
3874
|
+
hostedZoneName,
|
|
3875
|
+
region,
|
|
3876
|
+
skipUpload
|
|
3877
|
+
}) => {
|
|
3878
|
+
try {
|
|
3879
|
+
const { stackName } = await handleDeployInitialization({ logPrefix: logPrefix16 });
|
|
3880
|
+
const params = { StackName: stackName };
|
|
3881
|
+
const template = getStaticAppTemplate({
|
|
3882
|
+
acm,
|
|
3883
|
+
aliases,
|
|
3884
|
+
cloudfront,
|
|
3885
|
+
spa,
|
|
3886
|
+
hostedZoneName,
|
|
3887
|
+
region
|
|
3888
|
+
});
|
|
3889
|
+
const bucket = await getStaticAppBucket({ stackName });
|
|
3890
|
+
if (bucket) {
|
|
3891
|
+
if (!skipUpload) {
|
|
3892
|
+
await uploadBuiltAppToS3({ buildFolder, bucket, cloudfront });
|
|
3893
|
+
}
|
|
3894
|
+
const { Outputs } = await deploy({ params, template });
|
|
3895
|
+
await invalidateCloudFront({ outputs: Outputs });
|
|
3896
|
+
if (!skipUpload) {
|
|
3897
|
+
await removeOldVersions({ bucket });
|
|
3898
|
+
}
|
|
3899
|
+
} else {
|
|
3900
|
+
await deploy({ params, template });
|
|
3901
|
+
const newBucket = await getStaticAppBucket({ stackName });
|
|
3902
|
+
if (!newBucket) {
|
|
3903
|
+
throw new Error(`Cannot find bucket at ${stackName}.`);
|
|
3904
|
+
}
|
|
3905
|
+
await uploadBuiltAppToS3({ buildFolder, bucket: newBucket, cloudfront });
|
|
3906
|
+
}
|
|
3907
|
+
} catch (error) {
|
|
3908
|
+
handleDeployError({ error, logPrefix: logPrefix16 });
|
|
3909
|
+
}
|
|
3910
|
+
};
|
|
3911
|
+
|
|
3912
|
+
// src/deploy/staticApp/command.ts
|
|
3913
|
+
|
|
3914
|
+
var options3 = {
|
|
3915
|
+
acm: {
|
|
3916
|
+
describe: "The ARN of the certificate or the name of the exported variable whose value is the ARN of the certificate that will be associated to CloudFront.",
|
|
3917
|
+
type: "string"
|
|
3918
|
+
},
|
|
3919
|
+
aliases: {
|
|
3920
|
+
describe: "The aliases that will be associated with the CloudFront. See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html",
|
|
3921
|
+
implies: ["acm"],
|
|
3922
|
+
type: "array"
|
|
3923
|
+
},
|
|
3924
|
+
"build-folder": {
|
|
3925
|
+
describe: `The folder that will be uploaded. If not provided, it'll search for the folders "${defaultBuildFolders.join(
|
|
3926
|
+
", "
|
|
3927
|
+
)}."`,
|
|
3928
|
+
type: "string"
|
|
3929
|
+
},
|
|
3930
|
+
cloudfront: {
|
|
3931
|
+
default: false,
|
|
3932
|
+
describe: "A CloudFront resource is created along with S3 if this option is `true`.",
|
|
3933
|
+
require: false,
|
|
3934
|
+
type: "boolean"
|
|
3935
|
+
},
|
|
3936
|
+
"hosted-zone-name": {
|
|
3937
|
+
required: false,
|
|
3938
|
+
describe: `Is the name of a Route 53 hosted zone. If this value is provided, ${NAME} creates the subdomains defined on \`--aliases\` option. E.g. if you have a hosted zone named "sub.domain.com", the value provided may be "sub.domain.com".`,
|
|
3939
|
+
type: "string"
|
|
3940
|
+
},
|
|
3941
|
+
/**
|
|
3942
|
+
* CloudFront triggers can be only in US East (N. Virginia) Region.
|
|
3943
|
+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-cloudfront-triggers
|
|
3944
|
+
*/
|
|
3945
|
+
region: {
|
|
3946
|
+
coerce: () => {
|
|
3947
|
+
return CLOUDFRONT_REGION;
|
|
3948
|
+
},
|
|
3949
|
+
default: CLOUDFRONT_REGION,
|
|
3950
|
+
hidden: true,
|
|
3951
|
+
type: "string"
|
|
3952
|
+
},
|
|
3953
|
+
"skip-upload": {
|
|
3954
|
+
default: false,
|
|
3955
|
+
describe: "Skip files upload to S3. Useful when wanting update only CloudFormation.",
|
|
3956
|
+
type: "boolean"
|
|
3957
|
+
},
|
|
3958
|
+
spa: {
|
|
3959
|
+
default: false,
|
|
3960
|
+
describe: "This option enables CloudFront to serve a single page application (SPA).",
|
|
3961
|
+
require: false,
|
|
3962
|
+
type: "boolean"
|
|
3963
|
+
}
|
|
3964
|
+
};
|
|
3965
|
+
var deployStaticAppCommand = {
|
|
3966
|
+
command: "static-app",
|
|
3967
|
+
describe: "Deploy static app.",
|
|
3968
|
+
builder: (yargs3) => {
|
|
3969
|
+
return yargs3.options(addGroupToOptions(options3, "Deploy Static App Options")).middleware(() => {
|
|
3970
|
+
_awssdk2.default.config.region = CLOUDFRONT_REGION;
|
|
3971
|
+
});
|
|
3972
|
+
},
|
|
3973
|
+
handler: ({ destroy: destroy2, ...rest }) => {
|
|
3974
|
+
if (destroy2) {
|
|
3975
|
+
destroyCloudFormation();
|
|
3976
|
+
} else {
|
|
3977
|
+
deployStaticApp(rest);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
};
|
|
3981
|
+
|
|
3982
|
+
// src/deploy/vercel/deployVercel.ts
|
|
3983
|
+
|
|
3984
|
+
var logPrefix17 = "deploy vercel";
|
|
3985
|
+
var makeCommand = (cmds) => {
|
|
3986
|
+
return cmds.filter((cmd) => {
|
|
3987
|
+
return cmd !== void 0 && cmd !== null && cmd !== "";
|
|
3988
|
+
}).join(" ");
|
|
3989
|
+
};
|
|
3990
|
+
var deployVercel = async ({ token }) => {
|
|
3991
|
+
try {
|
|
3992
|
+
_npmlog2.default.info(logPrefix17, "Deploying on Vercel...");
|
|
3993
|
+
const environment = getEnvironment();
|
|
3994
|
+
const finalToken = token || process.env.VERCEL_TOKEN;
|
|
3995
|
+
if (!finalToken) {
|
|
3996
|
+
throw new Error("Missing Vercel token");
|
|
3997
|
+
}
|
|
3998
|
+
const cmdToken = finalToken ? `--token=${finalToken}` : "";
|
|
3999
|
+
const cmdProdFlag = environment === "Production" ? "--prod" : "";
|
|
4000
|
+
const cmdEnvironment = `--environment=${environment === "Production" ? "production" : "preview"}`;
|
|
4001
|
+
const pullCmd = makeCommand([
|
|
4002
|
+
"vercel",
|
|
4003
|
+
"pull",
|
|
4004
|
+
"--yes",
|
|
4005
|
+
cmdEnvironment,
|
|
4006
|
+
cmdToken
|
|
4007
|
+
]);
|
|
4008
|
+
await spawn(pullCmd);
|
|
4009
|
+
const buildCdm = makeCommand(["vercel", "build", cmdProdFlag, cmdToken]);
|
|
4010
|
+
await spawn(buildCdm);
|
|
4011
|
+
const deployCmd = makeCommand([
|
|
4012
|
+
"vercel",
|
|
4013
|
+
"deploy",
|
|
4014
|
+
"--prebuilt",
|
|
4015
|
+
cmdProdFlag,
|
|
4016
|
+
cmdToken
|
|
4017
|
+
]);
|
|
4018
|
+
await spawn(deployCmd);
|
|
4019
|
+
} catch (error) {
|
|
4020
|
+
handleDeployError({ error, logPrefix: logPrefix17 });
|
|
4021
|
+
}
|
|
4022
|
+
};
|
|
4023
|
+
|
|
4024
|
+
// src/deploy/vercel/command.ts
|
|
4025
|
+
|
|
4026
|
+
var logPrefix18 = "deploy vercel";
|
|
4027
|
+
var options4 = {
|
|
4028
|
+
token: {
|
|
4029
|
+
describe: "Vercel authorization token.",
|
|
4030
|
+
type: "string"
|
|
4031
|
+
}
|
|
4032
|
+
};
|
|
4033
|
+
var deployVercelCommand = {
|
|
4034
|
+
command: "vercel",
|
|
4035
|
+
describe: "Deploy on Vercel.",
|
|
4036
|
+
builder: (yargs3) => {
|
|
4037
|
+
return yargs3.options(
|
|
4038
|
+
addGroupToOptions(options4, "Deploy on Vercel Options")
|
|
4039
|
+
);
|
|
4040
|
+
},
|
|
4041
|
+
handler: ({ destroy: destroy2, ...rest }) => {
|
|
4042
|
+
if (destroy2) {
|
|
4043
|
+
_npmlog2.default.info(logPrefix18, "Destroy Vercel deployment not implemented yet.");
|
|
4044
|
+
} else {
|
|
4045
|
+
deployVercel(rest);
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
};
|
|
4049
|
+
|
|
4050
|
+
// src/deploy/readDockerfile.ts
|
|
4051
|
+
|
|
4052
|
+
|
|
4053
|
+
var readDockerfile = (dockerfilePath) => {
|
|
4054
|
+
try {
|
|
4055
|
+
return fs11.readFileSync(path9.join(process.cwd(), dockerfilePath), "utf8");
|
|
4056
|
+
} catch (e3) {
|
|
4057
|
+
return "";
|
|
4058
|
+
}
|
|
4059
|
+
};
|
|
4060
|
+
|
|
4061
|
+
// src/deploy/command.ts
|
|
4062
|
+
|
|
4063
|
+
var logPrefix19 = "deploy";
|
|
4064
|
+
var checkAwsAccountId = async (awsAccountId) => {
|
|
4065
|
+
try {
|
|
4066
|
+
const currentAwsAccountId = await getAwsAccountId();
|
|
4067
|
+
if (String(awsAccountId) !== String(currentAwsAccountId)) {
|
|
4068
|
+
throw new Error(
|
|
4069
|
+
`AWS account id does not match. Current is "${currentAwsAccountId}" but the defined in configuration files is "${awsAccountId}".`
|
|
4070
|
+
);
|
|
4071
|
+
}
|
|
4072
|
+
} catch (error) {
|
|
4073
|
+
if (error.code === "CredentialsError") {
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
_npmlog2.default.error(logPrefix19, error.message);
|
|
4077
|
+
process.exit();
|
|
4078
|
+
}
|
|
4079
|
+
};
|
|
4080
|
+
var describeDeployCommand = {
|
|
4081
|
+
command: "describe",
|
|
4082
|
+
describe: "Print the outputs of the deployment.",
|
|
4083
|
+
handler: async ({ stackName }) => {
|
|
4084
|
+
try {
|
|
4085
|
+
const newStackName = stackName || await getStackName();
|
|
4086
|
+
await printStackOutputsAfterDeploy({ stackName: newStackName });
|
|
4087
|
+
} catch (error) {
|
|
4088
|
+
_npmlog2.default.info(logPrefix19, "Cannot describe stack. Message: %s", error.message);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
};
|
|
4092
|
+
var options5 = {
|
|
4093
|
+
"aws-account-id": {
|
|
4094
|
+
description: "AWS account id associated with the deployment.",
|
|
4095
|
+
type: "string"
|
|
4096
|
+
},
|
|
4097
|
+
destroy: {
|
|
4098
|
+
default: false,
|
|
4099
|
+
description: 'Destroy the deployment. You cannot destroy a deploy with "environment" is defined.',
|
|
4100
|
+
type: "boolean"
|
|
4101
|
+
},
|
|
4102
|
+
"lambda-dockerfile": {
|
|
4103
|
+
coerce: (arg) => {
|
|
4104
|
+
return readDockerfile(arg);
|
|
4105
|
+
},
|
|
4106
|
+
default: "Dockerfile",
|
|
4107
|
+
describe: "Instructions to create the Lambda image.",
|
|
4108
|
+
type: "string"
|
|
4109
|
+
},
|
|
4110
|
+
"lambda-image": {
|
|
4111
|
+
default: false,
|
|
4112
|
+
describe: "A Lambda image will be created instead using S3.",
|
|
4113
|
+
type: "boolean"
|
|
4114
|
+
},
|
|
4115
|
+
"lambda-externals": {
|
|
4116
|
+
default: [],
|
|
4117
|
+
describe: "Lambda external packages.",
|
|
4118
|
+
type: "array"
|
|
4119
|
+
},
|
|
4120
|
+
"lambda-input": {
|
|
4121
|
+
default: "src/lambda.ts",
|
|
4122
|
+
describe: "Lambda input file. This file export all handlers used by the Lambda Functions.",
|
|
4123
|
+
type: "string"
|
|
4124
|
+
},
|
|
4125
|
+
/**
|
|
4126
|
+
* This option has the format:
|
|
4127
|
+
*
|
|
4128
|
+
* ```ts
|
|
4129
|
+
* {
|
|
4130
|
+
* key: string,
|
|
4131
|
+
* value: string,
|
|
4132
|
+
* usePreviousValue: boolean,
|
|
4133
|
+
* resolvedValue: string
|
|
4134
|
+
* }[]
|
|
4135
|
+
* ```
|
|
4136
|
+
*/
|
|
4137
|
+
parameters: {
|
|
4138
|
+
alias: "p",
|
|
4139
|
+
default: [],
|
|
4140
|
+
describe: "A list of parameters that will be passed to CloudFormation Parameters when deploying."
|
|
4141
|
+
},
|
|
4142
|
+
"skip-deploy": {
|
|
4143
|
+
alias: "skip",
|
|
4144
|
+
default: false,
|
|
4145
|
+
describe: "Skip deploy.",
|
|
4146
|
+
type: "boolean"
|
|
4147
|
+
},
|
|
4148
|
+
"stack-name": {
|
|
4149
|
+
describe: "CloudFormation Stack name.",
|
|
4150
|
+
type: "string"
|
|
4151
|
+
},
|
|
4152
|
+
"template-path": {
|
|
4153
|
+
alias: "t",
|
|
4154
|
+
type: "string"
|
|
4155
|
+
}
|
|
4156
|
+
};
|
|
4157
|
+
var examples = [
|
|
4158
|
+
[
|
|
4159
|
+
"carlin deploy -t src/cloudformation.template1.yml",
|
|
4160
|
+
"Change the CloudFormation template path."
|
|
4161
|
+
],
|
|
4162
|
+
["carlin deploy -e Production", "Set environment."],
|
|
4163
|
+
[
|
|
4164
|
+
"carlin deploy --lambda-input src/lambda/index.ts --lambda-externals momentjs",
|
|
4165
|
+
"Lambda exists. Don't bundle momentjs."
|
|
4166
|
+
],
|
|
4167
|
+
[
|
|
4168
|
+
"carlin deploy --destroy --stack-name StackToBeDeleted",
|
|
4169
|
+
"Destroy a specific stack."
|
|
4170
|
+
]
|
|
4171
|
+
];
|
|
4172
|
+
var deployCommand = {
|
|
4173
|
+
command: "deploy [deploy]",
|
|
4174
|
+
describe: "Deploy cloud resources.",
|
|
4175
|
+
builder: (yargsBuilder) => {
|
|
4176
|
+
yargsBuilder.example(examples).options(addGroupToOptions(options5, "Deploy Options")).middleware(({ stackName }) => {
|
|
4177
|
+
if (stackName) {
|
|
4178
|
+
setPreDefinedStackName(stackName);
|
|
4179
|
+
}
|
|
4180
|
+
}).middleware((argv) => {
|
|
4181
|
+
if (argv.lambdaDockerfile) {
|
|
4182
|
+
Object.assign(argv, {
|
|
4183
|
+
lambdaImage: true
|
|
4184
|
+
});
|
|
4185
|
+
}
|
|
4186
|
+
}).middleware(
|
|
4187
|
+
async ({
|
|
4188
|
+
environments,
|
|
4189
|
+
environment,
|
|
4190
|
+
awsAccountId: defaultAwsAccountId
|
|
4191
|
+
}) => {
|
|
4192
|
+
const envAwsAccountId = (() => {
|
|
4193
|
+
return environments && environment && environments[environment] ? environments[environment].awsAccountId : void 0;
|
|
4194
|
+
})();
|
|
4195
|
+
if (envAwsAccountId) {
|
|
4196
|
+
await checkAwsAccountId(envAwsAccountId);
|
|
4197
|
+
}
|
|
4198
|
+
if (defaultAwsAccountId) {
|
|
4199
|
+
await checkAwsAccountId(defaultAwsAccountId);
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
).middleware(({ skipDeploy }) => {
|
|
4203
|
+
if (skipDeploy) {
|
|
4204
|
+
_npmlog2.default.warn(
|
|
4205
|
+
logPrefix19,
|
|
4206
|
+
"Skip deploy flag is true, then the deploy command wasn't executed."
|
|
4207
|
+
);
|
|
4208
|
+
process.exit(0);
|
|
4209
|
+
}
|
|
4210
|
+
});
|
|
4211
|
+
const commands = [
|
|
4212
|
+
deployLambdaLayerCommand,
|
|
4213
|
+
describeDeployCommand,
|
|
4214
|
+
deployBaseStackCommand,
|
|
4215
|
+
deployStaticAppCommand,
|
|
4216
|
+
deployCicdCommand,
|
|
4217
|
+
deployVercelCommand
|
|
4218
|
+
];
|
|
4219
|
+
yargsBuilder.positional("deploy", {
|
|
4220
|
+
choices: commands.map(({ command }) => {
|
|
4221
|
+
return command;
|
|
4222
|
+
}),
|
|
4223
|
+
describe: "Type of deployment.",
|
|
4224
|
+
type: "string"
|
|
4225
|
+
});
|
|
4226
|
+
commands.forEach((command) => {
|
|
4227
|
+
return yargsBuilder.command(command);
|
|
4228
|
+
});
|
|
4229
|
+
return yargsBuilder;
|
|
4230
|
+
},
|
|
4231
|
+
handler: ({ destroy: destroy2, ...rest }) => {
|
|
4232
|
+
if (destroy2) {
|
|
4233
|
+
destroyCloudFormation();
|
|
4234
|
+
} else {
|
|
4235
|
+
deployCloudFormation(rest);
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
};
|
|
4239
|
+
|
|
4240
|
+
// src/deploy/cicd/ecsTaskReportCommand.ts
|
|
4241
|
+
|
|
4242
|
+
|
|
4243
|
+
var logPrefix20 = "cicd-ecs-task-report";
|
|
4244
|
+
var sendEcsTaskReport = async ({ status }) => {
|
|
4245
|
+
if (!process.env.ECS_TASK_REPORT_HANDLER_NAME) {
|
|
4246
|
+
_npmlog2.default.info(logPrefix20, "ECS_TASK_REPORT_HANDLER_NAME not defined.");
|
|
4247
|
+
return;
|
|
4248
|
+
}
|
|
4249
|
+
const lambda = new _awssdk2.default.Lambda();
|
|
4250
|
+
const payload = { status };
|
|
4251
|
+
if (process.env.ECS_TASK_ARN) {
|
|
4252
|
+
payload.ecsTaskArn = process.env.ECS_TASK_ARN;
|
|
4253
|
+
}
|
|
4254
|
+
if (process.env.PIPELINE_NAME) {
|
|
4255
|
+
payload.pipelineName = process.env.PIPELINE_NAME;
|
|
4256
|
+
}
|
|
4257
|
+
await lambda.invokeAsync({
|
|
4258
|
+
FunctionName: process.env.ECS_TASK_REPORT_HANDLER_NAME,
|
|
4259
|
+
InvokeArgs: JSON.stringify(payload)
|
|
4260
|
+
}).promise();
|
|
4261
|
+
_npmlog2.default.info(logPrefix20, "Report sent.");
|
|
4262
|
+
};
|
|
4263
|
+
var options6 = {
|
|
4264
|
+
status: {
|
|
4265
|
+
choices: ["Approved", "Rejected", "MainTagFound"],
|
|
4266
|
+
demandOption: true,
|
|
4267
|
+
type: "string"
|
|
4268
|
+
}
|
|
4269
|
+
};
|
|
4270
|
+
var ecsTaskReportCommand = {
|
|
4271
|
+
command: "cicd-ecs-task-report",
|
|
4272
|
+
describe: false,
|
|
4273
|
+
builder: (yargs3) => {
|
|
4274
|
+
return yargs3.options(options6);
|
|
4275
|
+
},
|
|
4276
|
+
handler: async (args) => {
|
|
4277
|
+
return sendEcsTaskReport(args);
|
|
4278
|
+
}
|
|
4279
|
+
};
|
|
4280
|
+
|
|
4281
|
+
// src/generateEnv/generateEnv.ts
|
|
4282
|
+
|
|
4283
|
+
|
|
4284
|
+
|
|
4285
|
+
var logPrefix21 = "generate-env";
|
|
4286
|
+
var readEnvFile = async ({
|
|
4287
|
+
envFileName,
|
|
4288
|
+
envsPath
|
|
4289
|
+
}) => {
|
|
4290
|
+
try {
|
|
4291
|
+
const content = await fs12.promises.readFile(
|
|
4292
|
+
path10.resolve(process.cwd(), envsPath, envFileName),
|
|
4293
|
+
"utf8"
|
|
4294
|
+
);
|
|
4295
|
+
return content;
|
|
4296
|
+
} catch (e4) {
|
|
4297
|
+
return void 0;
|
|
4298
|
+
}
|
|
4299
|
+
};
|
|
4300
|
+
var writeEnvFile = async ({
|
|
4301
|
+
envFileName,
|
|
4302
|
+
content
|
|
4303
|
+
}) => {
|
|
4304
|
+
return fs12.promises.writeFile(
|
|
4305
|
+
path10.resolve(process.cwd(), envFileName),
|
|
4306
|
+
content
|
|
4307
|
+
);
|
|
4308
|
+
};
|
|
4309
|
+
var generateEnv = async ({
|
|
4310
|
+
defaultEnvironment,
|
|
4311
|
+
path: envsPath
|
|
4312
|
+
}) => {
|
|
4313
|
+
const environment = getEnvironment() || defaultEnvironment;
|
|
4314
|
+
const envFileName = `.env.${environment}`;
|
|
4315
|
+
const envFile = await readEnvFile({ envFileName, envsPath });
|
|
4316
|
+
if (!envFile) {
|
|
4317
|
+
_npmlog2.default.info(
|
|
4318
|
+
logPrefix21,
|
|
4319
|
+
"Env file %s doesn't exist. Skip generating env file.",
|
|
4320
|
+
envFileName
|
|
4321
|
+
);
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
await writeEnvFile({ content: envFile, envFileName: ".env" });
|
|
4325
|
+
_npmlog2.default.info(
|
|
4326
|
+
logPrefix21,
|
|
4327
|
+
"Generate env file %s from %s successfully.",
|
|
4328
|
+
".env",
|
|
4329
|
+
envFileName
|
|
4330
|
+
);
|
|
4331
|
+
};
|
|
4332
|
+
|
|
4333
|
+
// src/generateEnv/generateEnvCommand.ts
|
|
4334
|
+
var DEFAULT_ENVIRONMENT = "Staging";
|
|
4335
|
+
var options7 = {
|
|
4336
|
+
"default-environment": {
|
|
4337
|
+
alias: "d",
|
|
4338
|
+
type: "string",
|
|
4339
|
+
describe: "Default environment.",
|
|
4340
|
+
default: DEFAULT_ENVIRONMENT
|
|
4341
|
+
},
|
|
4342
|
+
path: {
|
|
4343
|
+
alias: "p",
|
|
4344
|
+
type: "string",
|
|
4345
|
+
describe: "Path to the directory where envs files are located.",
|
|
4346
|
+
default: "./"
|
|
4347
|
+
}
|
|
4348
|
+
};
|
|
4349
|
+
var generateEnvCommand = {
|
|
4350
|
+
command: ["generate-env", "ge", "env"],
|
|
4351
|
+
describe: "Generate environment files.",
|
|
4352
|
+
builder: (yargs3) => {
|
|
4353
|
+
return yargs3.options(options7);
|
|
4354
|
+
},
|
|
4355
|
+
handler: (args) => {
|
|
4356
|
+
return generateEnv(args);
|
|
4357
|
+
}
|
|
4358
|
+
};
|
|
4359
|
+
|
|
4360
|
+
// src/cli.ts
|
|
4361
|
+
|
|
4362
|
+
|
|
4363
|
+
var _deepequal = require('deep-equal'); var _deepequal2 = _interopRequireDefault(_deepequal);
|
|
4364
|
+
|
|
4365
|
+
|
|
4366
|
+
|
|
4367
|
+
|
|
4368
|
+
var coerceSetEnvVar = (env) => {
|
|
4369
|
+
return (value) => {
|
|
4370
|
+
setEnvVar(env, value);
|
|
4371
|
+
return value;
|
|
4372
|
+
};
|
|
4373
|
+
};
|
|
4374
|
+
var options8 = {
|
|
4375
|
+
branch: {
|
|
4376
|
+
coerce: coerceSetEnvVar("BRANCH"),
|
|
4377
|
+
require: false,
|
|
4378
|
+
type: "string"
|
|
4379
|
+
},
|
|
4380
|
+
config: {
|
|
4381
|
+
alias: "c",
|
|
4382
|
+
describe: "Path to config file. You can create a config file and set all options there. Valid extensions: .js, .json, .ts, .yml, or .yaml.",
|
|
4383
|
+
require: false,
|
|
4384
|
+
type: "string"
|
|
4385
|
+
},
|
|
4386
|
+
environment: {
|
|
4387
|
+
alias: ["e", "env"],
|
|
4388
|
+
coerce: coerceSetEnvVar("ENVIRONMENT"),
|
|
4389
|
+
type: "string"
|
|
4390
|
+
},
|
|
4391
|
+
environments: {},
|
|
4392
|
+
project: {
|
|
4393
|
+
coerce: coerceSetEnvVar("PROJECT"),
|
|
4394
|
+
require: false,
|
|
4395
|
+
type: "string"
|
|
4396
|
+
},
|
|
4397
|
+
region: {
|
|
4398
|
+
alias: "r",
|
|
4399
|
+
// coerce: coerceSetEnvVar('REGION'),
|
|
4400
|
+
default: AWS_DEFAULT_REGION,
|
|
4401
|
+
describe: "AWS region.",
|
|
4402
|
+
type: "string"
|
|
4403
|
+
}
|
|
4404
|
+
};
|
|
4405
|
+
var getPkgConfig = () => {
|
|
4406
|
+
return NAME;
|
|
4407
|
+
};
|
|
4408
|
+
var getEnv = () => {
|
|
4409
|
+
return _changecase.constantCase.call(void 0, NAME);
|
|
4410
|
+
};
|
|
4411
|
+
var cli = () => {
|
|
4412
|
+
let finalConfig;
|
|
4413
|
+
const getConfig = () => {
|
|
4414
|
+
const names = ["js", "yml", "yaml", "json", "ts"].map((ext) => {
|
|
4415
|
+
return `${NAME}.${ext}`;
|
|
4416
|
+
});
|
|
4417
|
+
const paths = [];
|
|
4418
|
+
let currentPath = process.cwd();
|
|
4419
|
+
let findUpPath;
|
|
4420
|
+
do {
|
|
4421
|
+
findUpPath = _findupsync2.default.call(void 0, names, { cwd: currentPath });
|
|
4422
|
+
if (findUpPath) {
|
|
4423
|
+
currentPath = path2.default.resolve(findUpPath, "../..");
|
|
4424
|
+
paths.push(findUpPath);
|
|
4425
|
+
}
|
|
4426
|
+
} while (findUpPath);
|
|
4427
|
+
const configs = paths.map((p) => {
|
|
4428
|
+
return readObjectFile({ path: p }) || {};
|
|
4429
|
+
});
|
|
4430
|
+
finalConfig = _deepmerge2.default.all(configs.reverse());
|
|
4431
|
+
return finalConfig;
|
|
4432
|
+
};
|
|
4433
|
+
return _yargs2.default.call(void 0, _helpers.hideBin.call(void 0, process.argv)).strictCommands().scriptName(NAME).env(getEnv()).options(addGroupToOptions(options8, "Common Options")).middleware((argv, { parsed }) => {
|
|
4434
|
+
const { environment, environments } = argv;
|
|
4435
|
+
if (environment && environments && environments[environment]) {
|
|
4436
|
+
Object.entries(environments[environment]).forEach(([key, value]) => {
|
|
4437
|
+
const isKeyFromCli = (() => {
|
|
4438
|
+
const paramCaseKey = _changecase.paramCase.call(void 0, key);
|
|
4439
|
+
if (_optionalChain([parsed, 'optionalAccess', _38 => _38.defaulted, 'optionalAccess', _39 => _39[paramCaseKey]])) {
|
|
4440
|
+
return false;
|
|
4441
|
+
}
|
|
4442
|
+
if (_deepequal2.default.call(void 0, argv[key], finalConfig[key])) {
|
|
4443
|
+
return false;
|
|
4444
|
+
}
|
|
4445
|
+
return true;
|
|
4446
|
+
})();
|
|
4447
|
+
if (!isKeyFromCli) {
|
|
4448
|
+
argv[key] = value;
|
|
4449
|
+
}
|
|
4450
|
+
});
|
|
4451
|
+
}
|
|
4452
|
+
}).middleware(({ environment }) => {
|
|
4453
|
+
if (!["string", "undefined"].includes(typeof environment)) {
|
|
4454
|
+
throw new Error(
|
|
4455
|
+
`environment type is invalid. The value: ${JSON.stringify(
|
|
4456
|
+
environment
|
|
4457
|
+
)}`
|
|
4458
|
+
);
|
|
4459
|
+
}
|
|
4460
|
+
}).middleware(({ region }) => {
|
|
4461
|
+
_awssdk2.default.config.region = region;
|
|
4462
|
+
setEnvVar("REGION", region);
|
|
4463
|
+
}).pkgConf(getPkgConfig()).config(getConfig()).config("config", (configPath) => {
|
|
4464
|
+
return readObjectFile({ path: configPath });
|
|
4465
|
+
}).command({
|
|
4466
|
+
command: "print-args",
|
|
4467
|
+
describe: false,
|
|
4468
|
+
handler: (argv) => {
|
|
4469
|
+
return console.log(JSON.stringify(argv, null, 2));
|
|
4470
|
+
}
|
|
4471
|
+
}).command(deployCommand).command(ecsTaskReportCommand).command(generateEnvCommand).epilogue(
|
|
4472
|
+
"For more information, read our docs at https://ttoss.dev/docs/carlin/"
|
|
4473
|
+
).help();
|
|
4474
|
+
};
|
|
4475
|
+
|
|
4476
|
+
// src/index.ts
|
|
4477
|
+
cli().parse();
|