carlin 1.19.11 → 1.19.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/cli.js +232 -0
  2. package/dist/config.js +10 -0
  3. package/dist/deploy/addDefaults.cloudFormation.js +138 -0
  4. package/dist/deploy/baseStack/command.js +9 -0
  5. package/dist/deploy/baseStack/config.js +30 -0
  6. package/dist/deploy/baseStack/deployBaseStack.js +59 -0
  7. package/dist/deploy/baseStack/getBaseStackResource.js +26 -0
  8. package/dist/deploy/baseStack/getBucket.template.js +44 -0
  9. package/dist/deploy/baseStack/getLambdaImageBuilder.template.js +186 -0
  10. package/dist/deploy/baseStack/getLambdaLayerBuilder.template.js +140 -0
  11. package/dist/deploy/baseStack/getVpc.template.js +169 -0
  12. package/dist/deploy/cicd/cicd.template.js +922 -0
  13. package/dist/deploy/cicd/command.js +27 -0
  14. package/dist/deploy/cicd/command.options.js +71 -0
  15. package/dist/deploy/cicd/config.js +8 -0
  16. package/dist/deploy/cicd/deployCicd.js +93 -0
  17. package/dist/deploy/cicd/ecsTaskReportCommand.js +51 -0
  18. package/dist/deploy/cicd/getCicdStackName.js +11 -0
  19. package/dist/deploy/cicd/getTriggerPipelineObjectKey.js +11 -0
  20. package/dist/deploy/cicd/lambdas/cicdApiV1.handler.js +124 -0
  21. package/dist/deploy/cicd/lambdas/ecsTaskReport.handler.js +126 -0
  22. package/dist/deploy/cicd/lambdas/executeTasks.js +67 -0
  23. package/dist/deploy/cicd/lambdas/getProcessEnvVariable.js +10 -0
  24. package/dist/deploy/cicd/lambdas/githubWebhooksApiV1.handler.js +146 -0
  25. package/dist/deploy/cicd/lambdas/imageUpdaterSchedule.handler.js +44 -0
  26. package/dist/deploy/cicd/lambdas/index.js +13 -0
  27. package/dist/deploy/cicd/lambdas/pipelines.handler.js +134 -0
  28. package/dist/deploy/cicd/lambdas/putApprovalResultManualTask.js +52 -0
  29. package/dist/deploy/cicd/lambdas/shConditionalCommands.js +30 -0
  30. package/dist/deploy/cicd/pipelines.js +76 -0
  31. package/dist/deploy/cicd/readSSHKey.js +10 -0
  32. package/dist/deploy/cloudFormation.core.js +300 -0
  33. package/dist/deploy/cloudFormation.js +182 -0
  34. package/dist/deploy/command.js +185 -0
  35. package/dist/deploy/lambda/buildLambdaSingleFile.js +30 -0
  36. package/dist/deploy/lambda/deployLambdaCode.js +41 -0
  37. package/dist/deploy/lambda/deployLambdaLayers.js +34 -0
  38. package/dist/deploy/lambda/uploadCodeToECR.js +53 -0
  39. package/dist/deploy/lambda/uploadCodeToS3.js +31 -0
  40. package/dist/deploy/lambdaLayer/command.js +48 -0
  41. package/dist/deploy/lambdaLayer/deployLambdaLayer.js +140 -0
  42. package/dist/deploy/readDockerfile.js +18 -0
  43. package/dist/deploy/s3.js +165 -0
  44. package/dist/deploy/stackName.js +78 -0
  45. package/dist/deploy/staticApp/command.js +79 -0
  46. package/dist/deploy/staticApp/deployStaticApp.js +64 -0
  47. package/dist/deploy/staticApp/findDefaultBuildFolder.js +27 -0
  48. package/dist/deploy/staticApp/getOriginShieldRegion.js +30 -0
  49. package/dist/deploy/staticApp/getStaticAppBucket.js +19 -0
  50. package/dist/deploy/staticApp/invalidateCloudFront.js +42 -0
  51. package/dist/deploy/staticApp/removeOldVersions.js +43 -0
  52. package/dist/deploy/staticApp/staticApp.template.js +303 -0
  53. package/dist/deploy/staticApp/uploadBuiltAppToS3.js +27 -0
  54. package/dist/deploy/utils.js +29 -0
  55. package/dist/generateEnv/generateEnv.js +46 -0
  56. package/dist/generateEnv/generateEnvCommand.js +9 -0
  57. package/dist/index.js +5 -0
  58. package/dist/utils/addGroupToOptions.js +11 -0
  59. package/dist/utils/cloudFormationTemplate.js +132 -0
  60. package/dist/utils/codeBuild.js +50 -0
  61. package/dist/utils/environmentVariables.js +11 -0
  62. package/dist/utils/exec.js +22 -0
  63. package/dist/utils/formatCode.js +12 -0
  64. package/dist/utils/getAwsAccountId.js +10 -0
  65. package/dist/utils/getCurrentBranch.js +33 -0
  66. package/dist/utils/getEnvironment.js +6 -0
  67. package/dist/utils/getIamPath.js +6 -0
  68. package/dist/utils/getProjectName.js +35 -0
  69. package/dist/utils/index.js +17 -0
  70. package/dist/utils/packageJson.js +25 -0
  71. package/dist/utils/readCloudFormationTemplate.js +33 -0
  72. package/dist/utils/readObjectFile.js +46 -0
  73. package/package.json +3 -3
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildLambdaSingleFile = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const esbuild = tslib_1.__importStar(require("esbuild"));
6
+ const builtin_modules_1 = tslib_1.__importDefault(require("builtin-modules"));
7
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
8
+ const path_1 = tslib_1.__importDefault(require("path"));
9
+ const logPrefix = 'lambda';
10
+ const outFolder = 'dist';
11
+ const outFile = 'index.js';
12
+ const buildLambdaSingleFile = async ({ lambdaExternals, lambdaInput, }) => {
13
+ npmlog_1.default.info(logPrefix, 'Building Lambda single file...');
14
+ const { errors } = esbuild.buildSync({
15
+ banner: {
16
+ js: '// Powered by carlin (https://ttoss.dev)',
17
+ },
18
+ bundle: true,
19
+ entryPoints: [path_1.default.resolve(process.cwd(), lambdaInput)],
20
+ external: ['aws-sdk', ...builtin_modules_1.default, ...lambdaExternals],
21
+ platform: 'node',
22
+ outfile: path_1.default.join(process.cwd(), outFolder, outFile),
23
+ target: 'node12',
24
+ treeShaking: true,
25
+ });
26
+ if (errors.length > 0) {
27
+ throw errors;
28
+ }
29
+ };
30
+ exports.buildLambdaSingleFile = buildLambdaSingleFile;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deployLambdaCode = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const buildLambdaSingleFile_1 = require("./buildLambdaSingleFile");
6
+ const deployLambdaLayers_1 = require("./deployLambdaLayers");
7
+ const uploadCodeToECR_1 = require("./uploadCodeToECR");
8
+ const uploadCodeToS3_1 = require("./uploadCodeToS3");
9
+ const fs_1 = tslib_1.__importDefault(require("fs"));
10
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
11
+ const logPrefix = 'lambda';
12
+ /**
13
+ * 1. Build Lambda code using esbuild. The build process will create a single
14
+ * file with all your code.
15
+ * 1. The build will ignore packages on `lambda-externals` option.
16
+ * 1. Zip the output file.
17
+ * 1. Upload the zipped code to base stack bucket.
18
+ * 1. Add the code bucket, key and version to the CloudFormation template as
19
+ * parameters.
20
+ */
21
+ const deployLambdaCode = async ({ lambdaDockerfile, lambdaExternals, lambdaImage, lambdaInput, stackName, }) => {
22
+ if (!fs_1.default.existsSync(lambdaInput)) {
23
+ return undefined;
24
+ }
25
+ npmlog_1.default.info(logPrefix, 'Deploying Lambda code...');
26
+ await (0, buildLambdaSingleFile_1.buildLambdaSingleFile)({ lambdaExternals, lambdaInput });
27
+ const { bucket, key, versionId } = await (0, uploadCodeToS3_1.uploadCodeToS3)({ stackName });
28
+ if (!lambdaImage) {
29
+ await (0, deployLambdaLayers_1.deployLambdaLayers)({ lambdaExternals });
30
+ return { bucket, key, versionId };
31
+ }
32
+ const { imageUri } = await (0, uploadCodeToECR_1.uploadCodeToECR)({
33
+ bucket,
34
+ key,
35
+ versionId,
36
+ lambdaDockerfile,
37
+ lambdaExternals,
38
+ });
39
+ return { imageUri };
40
+ };
41
+ exports.deployLambdaCode = deployLambdaCode;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deployLambdaLayers = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const deployLambdaLayer_1 = require("../lambdaLayer/deployLambdaLayer");
6
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
7
+ const path_1 = tslib_1.__importDefault(require("path"));
8
+ const logPrefix = 'lambda';
9
+ const deployLambdaLayers = async ({ lambdaExternals = [], }) => {
10
+ if (lambdaExternals.length === 0) {
11
+ return;
12
+ }
13
+ npmlog_1.default.info(logPrefix, `--lambda-externals [${lambdaExternals.join(', ')}] was found. Creating other layers...`);
14
+ const { dependencies = {} } = (() => {
15
+ try {
16
+ return require(path_1.default.resolve(process.cwd(), 'package.json')) || {};
17
+ }
18
+ catch (err) {
19
+ npmlog_1.default.error(logPrefix, 'Cannot read package.json. Error message: %j', err.message);
20
+ return {};
21
+ }
22
+ })();
23
+ const packages = lambdaExternals.map((lambdaExternal) => {
24
+ try {
25
+ const semver = dependencies[lambdaExternal].replace(/(~|\^)/g, '');
26
+ return `${lambdaExternal}@${semver}`;
27
+ }
28
+ catch {
29
+ throw new Error(`Cannot find ${lambdaExternal} on package.json dependencies.`);
30
+ }
31
+ });
32
+ await (0, deployLambdaLayer_1.deployLambdaLayer)({ packages, deployIfExists: false });
33
+ };
34
+ exports.deployLambdaLayers = deployLambdaLayers;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadCodeToECR = void 0;
4
+ const config_1 = require("../../config");
5
+ const aws_sdk_1 = require("aws-sdk");
6
+ const getBaseStackResource_1 = require("../baseStack/getBaseStackResource");
7
+ const utils_1 = require("../../utils");
8
+ const codeBuild = new aws_sdk_1.CodeBuild({ region: config_1.AWS_DEFAULT_REGION });
9
+ const uploadCodeToECR = async ({ bucket, key, lambdaExternals, lambdaDockerfile, }) => {
10
+ const TEMP = 1;
11
+ if (TEMP) {
12
+ throw new Error('uploadCodeToECR not finished yet.');
13
+ }
14
+ const lambdaBuilder = await (0, getBaseStackResource_1.getBaseStackResource)('BASE_STACK_LAMBDA_IMAGE_BUILDER_LOGICAL_NAME');
15
+ const defaultDockerfile = [
16
+ 'FROM public.ecr.aws/lambda/nodejs:14',
17
+ // eslint-disable-next-line no-template-curly-in-string
18
+ 'COPY . ${LAMBDA_TASK_ROOT}',
19
+ 'RUN npm install',
20
+ ].join('\n');
21
+ const { build } = await codeBuild
22
+ .startBuild({
23
+ environmentVariablesOverride: [
24
+ {
25
+ name: 'DOCKERFILE',
26
+ value: lambdaDockerfile || defaultDockerfile,
27
+ },
28
+ {
29
+ name: 'LAMBDA_EXTERNALS',
30
+ value: lambdaExternals.join(' '),
31
+ },
32
+ {
33
+ name: 'IMAGE_TAG',
34
+ value: (0, utils_1.getPackageVersion)(),
35
+ },
36
+ {
37
+ name: 'REPOSITORY_ECR_REPOSITORY',
38
+ value: 'testtesteste',
39
+ },
40
+ ],
41
+ projectName: lambdaBuilder,
42
+ sourceLocationOverride: `${bucket}/${key}`,
43
+ sourceTypeOverride: 'S3',
44
+ })
45
+ .promise();
46
+ if (!(build === null || build === void 0 ? void 0 : build.id)) {
47
+ throw new Error('Cannot start build.');
48
+ }
49
+ await (0, utils_1.waitCodeBuildFinish)({ buildId: build.id, name: 'lambda-builder' });
50
+ const imageUri = '178804353523.dkr.ecr.us-east-1.amazonaws.com/testtesteste:0.0.1';
51
+ return { imageUri };
52
+ };
53
+ exports.uploadCodeToECR = uploadCodeToECR;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadCodeToS3 = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const getBaseStackResource_1 = require("../baseStack/getBaseStackResource");
6
+ const s3_1 = require("../s3");
7
+ const adm_zip_1 = tslib_1.__importDefault(require("adm-zip"));
8
+ const fs_1 = tslib_1.__importDefault(require("fs"));
9
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
10
+ const path_1 = tslib_1.__importDefault(require("path"));
11
+ const logPrefix = 'lambda';
12
+ const outFolder = 'dist';
13
+ const outFile = 'index.js';
14
+ const uploadCodeToS3 = async ({ stackName }) => {
15
+ npmlog_1.default.info(logPrefix, `Uploading code to S3...`);
16
+ const zip = new adm_zip_1.default();
17
+ const code = fs_1.default.readFileSync(path_1.default.join(process.cwd(), outFolder, outFile));
18
+ zip.addFile('index.js', code);
19
+ if (!fs_1.default.existsSync('dist')) {
20
+ fs_1.default.mkdirSync('dist');
21
+ }
22
+ zip.writeZip('dist/lambda.zip');
23
+ const bucketName = await (0, getBaseStackResource_1.getBaseStackResource)('BASE_STACK_BUCKET_LOGICAL_NAME');
24
+ return (0, s3_1.uploadFileToS3)({
25
+ bucket: bucketName,
26
+ contentType: 'application/zip',
27
+ key: `lambdas/${stackName}/lambda.zip`,
28
+ file: zip.toBuffer(),
29
+ });
30
+ };
31
+ exports.uploadCodeToS3 = uploadCodeToS3;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deployLambdaLayerCommand = exports.options = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
6
+ const config_1 = require("../../config");
7
+ const utils_1 = require("../../utils");
8
+ const deployLambdaLayer_1 = require("./deployLambdaLayer");
9
+ const logPrefix = 'deploy-lambda-layer';
10
+ /**
11
+ * https://stackoverflow.com/a/64880672/8786986
12
+ */
13
+ const packageNameRegex = /@[~^]?([\dvx*]+(?:[-.](?:[\dx*]+|alpha|beta))*)/;
14
+ exports.options = {
15
+ packages: {
16
+ array: true,
17
+ describe: `NPM packages' names to be deployed as Lambda Layers. It must follow the format: ${packageNameRegex.toString()}.`,
18
+ required: true,
19
+ type: 'string',
20
+ },
21
+ };
22
+ exports.deployLambdaLayerCommand = {
23
+ command: 'lambda-layer',
24
+ describe: 'Deploy Lambda Layer.',
25
+ builder: (yargs) => yargs
26
+ .options((0, utils_1.addGroupToOptions)(exports.options, 'Deploy Lambda Layer Options'))
27
+ .check(({ packages }) => {
28
+ const invalidPackages = packages
29
+ .map((packageName) => {
30
+ return packageNameRegex.test(packageName) ? undefined : packageName;
31
+ })
32
+ .filter((packageName) => !!packageName);
33
+ if (invalidPackages.length > 0) {
34
+ throw new Error(`Some package names are invalid: ${invalidPackages.join(', ')}. The package must follow the pattern: ${packageNameRegex.toString()}.`);
35
+ }
36
+ else {
37
+ return true;
38
+ }
39
+ }),
40
+ handler: ({ destroy, ...rest }) => {
41
+ if (destroy) {
42
+ npmlog_1.default.info(logPrefix, `${config_1.NAME} doesn't destroy lambda layers.`);
43
+ }
44
+ else {
45
+ (0, deployLambdaLayer_1.deployLambdaLayer)(rest);
46
+ }
47
+ },
48
+ };
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deployLambdaLayer = exports.getPackageLambdaLayerStackName = exports.getLambdaLayerTemplate = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const utils_1 = require("../../utils");
6
+ const aws_sdk_1 = require("aws-sdk");
7
+ const config_1 = require("../../config");
8
+ const cloudFormation_core_1 = require("../cloudFormation.core");
9
+ const getBaseStackResource_1 = require("../baseStack/getBaseStackResource");
10
+ const utils_2 = require("../utils");
11
+ const change_case_1 = require("change-case");
12
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
13
+ const logPrefix = 'lambda-layer';
14
+ const createLambdaLayerZipFile = async ({ codeBuildProjectName, packageName, }) => {
15
+ var _a, _b;
16
+ npmlog_1.default.info(logPrefix, `Creating zip file for package ${packageName}...`);
17
+ const codeBuild = new aws_sdk_1.CodeBuild();
18
+ const { build } = await codeBuild
19
+ .startBuild({
20
+ environmentVariablesOverride: [
21
+ {
22
+ name: 'PACKAGE_NAME',
23
+ value: packageName,
24
+ },
25
+ ],
26
+ projectName: codeBuildProjectName,
27
+ })
28
+ .promise();
29
+ if (!(build === null || build === void 0 ? void 0 : build.id)) {
30
+ throw new Error('Cannot start build.');
31
+ }
32
+ const result = await (0, utils_1.waitCodeBuildFinish)({
33
+ buildId: build.id,
34
+ name: packageName,
35
+ });
36
+ if ((_a = result.artifacts) === null || _a === void 0 ? void 0 : _a.location) {
37
+ const location = result.artifacts.location.split('/');
38
+ const bucket = (_b = location.shift()) === null || _b === void 0 ? void 0 : _b.replace('arn:aws:s3:::', '');
39
+ if (!bucket) {
40
+ throw new Error('Cannot retrieve bucket name.');
41
+ }
42
+ const key = location.join('/');
43
+ return { bucket, key };
44
+ }
45
+ throw new Error(`Cannot get artifact location for package ${packageName}`);
46
+ };
47
+ /**
48
+ * The CloudFormation template created to deploy a Lambda Layer.
49
+ *
50
+ * - The Layer name is the same as the Stack name.
51
+ */
52
+ const getLambdaLayerTemplate = ({ bucket, key, packageName, }) => {
53
+ const description = packageName
54
+ /**
55
+ * Description has limit of 256.
56
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-layerversion.html#cfn-lambda-layerversion-description
57
+ */
58
+ .substring(0, 256);
59
+ return {
60
+ AWSTemplateFormatVersion: '2010-09-09',
61
+ Resources: {
62
+ LambdaLayer: {
63
+ Type: 'AWS::Lambda::LayerVersion',
64
+ Properties: {
65
+ CompatibleRuntimes: ['nodejs12.x', 'nodejs14.x'],
66
+ Content: {
67
+ S3Bucket: bucket,
68
+ S3Key: key,
69
+ },
70
+ Description: description,
71
+ LayerName: { Ref: 'AWS::StackName' },
72
+ },
73
+ },
74
+ },
75
+ Outputs: {
76
+ LambdaLayerVersion: {
77
+ Description: description,
78
+ Value: { Ref: 'LambdaLayer' },
79
+ Export: {
80
+ Name: { Ref: 'AWS::StackName' },
81
+ },
82
+ },
83
+ },
84
+ };
85
+ };
86
+ exports.getLambdaLayerTemplate = getLambdaLayerTemplate;
87
+ /**
88
+ * The stack name is given by `CarlinLambdaLayer` prefix and the package name with
89
+ * `@` and `/` removed and `.` replace by the word `dot`.
90
+ */
91
+ const getPackageLambdaLayerStackName = (packageName) => {
92
+ return (0, change_case_1.pascalCase)(`${config_1.NAME} LambdaLayer ${packageName.replace(/\./g, 'dot')}`).replace(/_/g, '');
93
+ };
94
+ exports.getPackageLambdaLayerStackName = getPackageLambdaLayerStackName;
95
+ const getPackagesThatAreNotDeployed = async ({ packages, }) => {
96
+ return (await Promise.all(packages.map(async (packageName) => {
97
+ const stackName = (0, exports.getPackageLambdaLayerStackName)(packageName);
98
+ return (await (0, cloudFormation_core_1.doesStackExist)({ stackName })) ? '' : packageName;
99
+ }))).filter((packageName) => !!packageName);
100
+ };
101
+ const deployLambdaLayer = async ({ packages, deployIfExists = true, }) => {
102
+ try {
103
+ const packagesToBeDeployed = deployIfExists
104
+ ? packages
105
+ : await getPackagesThatAreNotDeployed({ packages });
106
+ if (packagesToBeDeployed.length === 0) {
107
+ return;
108
+ }
109
+ const codeBuildProjectName = await (0, getBaseStackResource_1.getBaseStackResource)('BASE_STACK_LAMBDA_LAYER_BUILDER_LOGICAL_NAME');
110
+ if (!codeBuildProjectName) {
111
+ throw new Error("Cannot deploy lambda-layer because AWS CodeBuild project doesn't exist.");
112
+ }
113
+ const deployLambdaLayerSinglePackage = async (packageName) => {
114
+ try {
115
+ const { bucket, key } = await createLambdaLayerZipFile({
116
+ codeBuildProjectName,
117
+ packageName,
118
+ });
119
+ const lambdaLayerTemplate = (0, exports.getLambdaLayerTemplate)({
120
+ packageName,
121
+ bucket,
122
+ key,
123
+ });
124
+ await (0, cloudFormation_core_1.deploy)({
125
+ template: lambdaLayerTemplate,
126
+ terminationProtection: true,
127
+ params: { StackName: (0, exports.getPackageLambdaLayerStackName)(packageName) },
128
+ });
129
+ }
130
+ catch (error) {
131
+ (0, utils_2.handleDeployError)({ error, logPrefix });
132
+ }
133
+ };
134
+ await Promise.all(packagesToBeDeployed.map((packageName) => deployLambdaLayerSinglePackage(packageName)));
135
+ }
136
+ catch (error) {
137
+ (0, utils_2.handleDeployError)({ error, logPrefix });
138
+ }
139
+ };
140
+ exports.deployLambdaLayer = deployLambdaLayer;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readDockerfile = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ /**
8
+ * This method was created because fs.readFileSync cannot be mocked.
9
+ */
10
+ const readDockerfile = (dockerfilePath) => {
11
+ try {
12
+ return fs.readFileSync(path.join(process.cwd(), dockerfilePath), 'utf8');
13
+ }
14
+ catch {
15
+ return '';
16
+ }
17
+ };
18
+ exports.readDockerfile = readDockerfile;
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteS3Directory = exports.emptyS3Directory = exports.uploadDirectoryToS3 = exports.getAllFilesInsideADirectory = exports.uploadFileToS3 = exports.getBucketKeyUrl = exports.s3 = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /* eslint-disable no-restricted-syntax */
6
+ /* eslint-disable no-await-in-loop */
7
+ const aws_sdk_1 = require("aws-sdk");
8
+ const fs_1 = tslib_1.__importDefault(require("fs"));
9
+ const glob_1 = tslib_1.__importDefault(require("glob"));
10
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
11
+ const mime_types_1 = tslib_1.__importDefault(require("mime-types"));
12
+ const path_1 = tslib_1.__importDefault(require("path"));
13
+ const logPrefix = 's3';
14
+ exports.s3 = new aws_sdk_1.S3({ apiVersion: '2006-03-01' });
15
+ const getBucketKeyUrl = ({ bucket, key, }) => {
16
+ return `https://s3.amazonaws.com/${bucket}/${key}`;
17
+ };
18
+ exports.getBucketKeyUrl = getBucketKeyUrl;
19
+ const uploadFileToS3 = async ({ bucket, contentType, file, filePath, key, }) => {
20
+ if (!file && !filePath) {
21
+ throw new Error('file or filePath must be defined');
22
+ }
23
+ let params = {
24
+ Bucket: bucket,
25
+ Key: key.split(path_1.default.sep).join('/'),
26
+ };
27
+ if (file) {
28
+ params.ContentType = contentType;
29
+ params.Body = file;
30
+ }
31
+ else if (filePath) {
32
+ const readFile = await fs_1.default.promises.readFile(filePath);
33
+ params = {
34
+ ...params,
35
+ ContentType: contentType || mime_types_1.default.contentType(path_1.default.extname(filePath)) || undefined,
36
+ };
37
+ params.Body = Buffer.from(readFile);
38
+ }
39
+ const { Bucket, Key, VersionId } = (await exports.s3.upload(params).promise());
40
+ return {
41
+ bucket: Bucket,
42
+ key: Key,
43
+ versionId: VersionId,
44
+ url: (0, exports.getBucketKeyUrl)({ bucket: Bucket, key: Key }),
45
+ };
46
+ };
47
+ exports.uploadFileToS3 = uploadFileToS3;
48
+ /**
49
+ * Get all files inside $directory.
50
+ */
51
+ const getAllFilesInsideADirectory = async ({ directory, }) => {
52
+ const allFilesAndDirectories = await new Promise((resolve, reject) => {
53
+ (0, glob_1.default)(`${directory}/**/*`, (err, matches) => {
54
+ return err ? reject(err) : resolve(matches);
55
+ });
56
+ });
57
+ const allFiles = allFilesAndDirectories
58
+ /**
59
+ * Remove directories.
60
+ */
61
+ .filter((item) => fs_1.default.lstatSync(item).isFile());
62
+ return allFiles;
63
+ };
64
+ exports.getAllFilesInsideADirectory = getAllFilesInsideADirectory;
65
+ const uploadDirectoryToS3 = async ({ bucket, bucketKey = '', directory, }) => {
66
+ npmlog_1.default.info(logPrefix, `Uploading directory ${directory}/ to ${bucket}/${bucketKey}...`);
67
+ const allFiles = await (0, exports.getAllFilesInsideADirectory)({ directory });
68
+ /**
69
+ * If the folder has no files (the folder name may be wrong), thrown an
70
+ * error. Discovered at #16 https://github.com/ttoss/carlin/issues/16.
71
+ */
72
+ if (allFiles.length === 0) {
73
+ throw new Error(`Directory ${directory}/ has no files.`);
74
+ }
75
+ const GROUP_MAX_LENGTH = 63;
76
+ const numberOfGroups = Math.ceil(allFiles.length / GROUP_MAX_LENGTH);
77
+ /**
78
+ * Divide all files and create "numberOfGroups" groups of files whose max
79
+ * length is GROUP_MAX_LENGTH.
80
+ */
81
+ const aoaOfFiles = allFiles.reduce((acc, file, index) => {
82
+ const groupIndex = index % numberOfGroups;
83
+ if (!acc[groupIndex]) {
84
+ acc[groupIndex] = [];
85
+ }
86
+ acc[index % numberOfGroups].push(file);
87
+ return acc;
88
+ }, []);
89
+ for (const [index, groupOfFiles] of aoaOfFiles.entries()) {
90
+ npmlog_1.default.info(logPrefix, `Uploading group ${index + 1}/${aoaOfFiles.length}...`);
91
+ await Promise.all(groupOfFiles.map((file) => (0, exports.uploadFileToS3)({
92
+ bucket,
93
+ key: path_1.default.join(bucketKey, path_1.default.relative(directory, file)),
94
+ filePath: file,
95
+ })));
96
+ }
97
+ };
98
+ exports.uploadDirectoryToS3 = uploadDirectoryToS3;
99
+ const emptyS3Directory = async ({ bucket, directory = '', }) => {
100
+ npmlog_1.default.info(logPrefix, `${bucket}/${directory} will be empty`);
101
+ try {
102
+ const { Contents, IsTruncated } = await exports.s3
103
+ .listObjectsV2({
104
+ Bucket: bucket,
105
+ Prefix: directory,
106
+ })
107
+ .promise();
108
+ if (Contents && Contents.length > 0) {
109
+ /**
110
+ * Get object versions
111
+ */
112
+ const objectsPromises = Contents.filter(({ Key }) => !!Key).map(async ({ Key }) => {
113
+ const { Versions = [] } = await exports.s3
114
+ .listObjectVersions({
115
+ Bucket: bucket,
116
+ Prefix: Key,
117
+ })
118
+ .promise();
119
+ return {
120
+ Key: Key,
121
+ Versions: Versions.map(({ VersionId }) => VersionId || undefined),
122
+ };
123
+ });
124
+ const objects = await Promise.all(objectsPromises);
125
+ const objectsWithVersionsIds = objects.reduce((acc, { Key, Versions }) => {
126
+ const objectWithVersionsIds = Versions.map((VersionId) => ({
127
+ Key,
128
+ VersionId,
129
+ }));
130
+ return [...acc, ...objectWithVersionsIds];
131
+ }, []);
132
+ await exports.s3
133
+ .deleteObjects({
134
+ Bucket: bucket,
135
+ Delete: { Objects: objectsWithVersionsIds },
136
+ })
137
+ .promise();
138
+ }
139
+ /**
140
+ * Truncated is files that exists but weren't listed from S3 API.
141
+ */
142
+ if (IsTruncated) {
143
+ await (0, exports.emptyS3Directory)({ bucket, directory });
144
+ }
145
+ npmlog_1.default.info(logPrefix, `${bucket}/${directory} is empty.`);
146
+ }
147
+ catch (err) {
148
+ npmlog_1.default.error(logPrefix, `Cannot empty ${bucket}/${directory}.`);
149
+ throw err;
150
+ }
151
+ };
152
+ exports.emptyS3Directory = emptyS3Directory;
153
+ const deleteS3Directory = async ({ bucket, directory = '', }) => {
154
+ try {
155
+ npmlog_1.default.info(logPrefix, `${bucket}/${directory} is being deleted...`);
156
+ await (0, exports.emptyS3Directory)({ bucket, directory });
157
+ await exports.s3.deleteObject({ Bucket: bucket, Key: directory }).promise();
158
+ npmlog_1.default.info(logPrefix, `${bucket}/${directory} was deleted.`);
159
+ }
160
+ catch (error) {
161
+ npmlog_1.default.error(logPrefix, `Cannot delete ${bucket}/${directory}.`);
162
+ throw error;
163
+ }
164
+ };
165
+ exports.deleteS3Directory = deleteS3Directory;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStackName = exports.limitStackName = exports.STACK_NAME_MAX_LENGTH = exports.setPreDefinedStackName = void 0;
4
+ const change_case_1 = require("change-case");
5
+ const utils_1 = require("../utils");
6
+ /**
7
+ * Used by CLI set stack name when it is defined.
8
+ */
9
+ const setPreDefinedStackName = (stackName) => {
10
+ (0, utils_1.setEnvVar)('STACK_NAME', stackName);
11
+ };
12
+ exports.setPreDefinedStackName = setPreDefinedStackName;
13
+ /**
14
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html
15
+ */
16
+ exports.STACK_NAME_MAX_LENGTH = 128;
17
+ const limitStackName = (stackName) => `${stackName}`.substring(0, exports.STACK_NAME_MAX_LENGTH);
18
+ exports.limitStackName = limitStackName;
19
+ /**
20
+ * If stack name isn't previously defined, the name will be created accordingly
21
+ * with the following rules:
22
+ *
23
+ * 1. The name has to parts.
24
+ *
25
+ * 1. The first part is defined by the package.json name, if it is defined.
26
+ * Else, it'll be a random name starting with the string "Stack-", e.g. **Stack-96830**.
27
+ *
28
+ * 1. The second part will be defined by, whichever is defined first:
29
+ * 1. environment,
30
+ * 1. [branch name](https://carlin.ttoss.dev/docs/CLI#branchbranch_name) in param-case,
31
+ * 1. `undefined`.
32
+ *
33
+ * Example:
34
+ *
35
+ * | Case | Package Name | Environment | Branch Name | `--stack-name` | Stack Name |
36
+ * | ---- | ------------ | ----------- | ---------- | -------------- | ---------- |
37
+ * | #1 | @package/name | Production | main | MyStackName | **MyStackName** |
38
+ * | #2 | @package/name | Production | main | | **PackageName-Production** |
39
+ * | #3 | @package/name | | main | | **PackageName-main** |
40
+ * | #4 | @package/name | | | | **PackageName** |
41
+ * | #5 | | Production | main | | **Stack-96820-Production** |
42
+ * | #6 | | | main | | **Stack-96820-main** |
43
+ * | #7 | | | | | **Stack-96820** |
44
+ *
45
+ * CAUTION!!!
46
+ *
47
+ * This method is a BREAKING CHANGE for **carlin**, I hope we never have to
48
+ * change this algorithm, ever. Stack name is how we track the stacks on AWS.
49
+ * Suppose we change this algorithm. If we perform an update or destroy
50
+ * operation, **carlin** will create another stack or do nothing because the
51
+ * old stack won't be found due to stack name changing.
52
+ *
53
+ */
54
+ const getStackName = async () => {
55
+ if ((0, utils_1.getEnvVar)('STACK_NAME')) {
56
+ return (0, utils_1.getEnvVar)('STACK_NAME');
57
+ }
58
+ const [currentBranch, environment, packageName] = await Promise.all([
59
+ (0, utils_1.getCurrentBranch)(),
60
+ (0, utils_1.getEnvironment)(),
61
+ (0, utils_1.getPackageName)(),
62
+ ]);
63
+ const firstName = packageName
64
+ ? (0, change_case_1.pascalCase)(packageName)
65
+ : `Stack-${Math.round(Math.random() * 100000)}`;
66
+ const secondName = (() => {
67
+ if (environment) {
68
+ return environment;
69
+ }
70
+ if (currentBranch) {
71
+ return (0, change_case_1.paramCase)(currentBranch);
72
+ }
73
+ return undefined;
74
+ })();
75
+ const name = [firstName, secondName].filter((word) => !!word).join('-');
76
+ return (0, exports.limitStackName)(name);
77
+ };
78
+ exports.getStackName = getStackName;