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,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deployCicdCommand = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const config_1 = require("../../config");
6
+ const utils_1 = require("../../utils");
7
+ const deployCicd_1 = require("./deployCicd");
8
+ const command_options_1 = require("./command.options");
9
+ const readSSHKey_1 = require("./readSSHKey");
10
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
11
+ const logPrefix = 'deploy-cicd';
12
+ exports.deployCicdCommand = {
13
+ command: 'cicd',
14
+ describe: 'Deploy CICD.',
15
+ builder: (yargs) => yargs.options((0, utils_1.addGroupToOptions)(command_options_1.options, 'Deploy CICD Options')),
16
+ handler: ({ destroy, ...rest }) => {
17
+ if (destroy) {
18
+ npmlog_1.default.info(logPrefix, `${config_1.NAME} doesn't destroy CICD stack.`);
19
+ }
20
+ else {
21
+ (0, deployCicd_1.deployCicd)({
22
+ ...rest,
23
+ sshKey: (0, readSSHKey_1.readSSHKey)(rest['ssh-key']),
24
+ });
25
+ }
26
+ },
27
+ };
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCicdConfig = exports.options = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const change_case_1 = require("change-case");
6
+ const pipelines_1 = require("./pipelines");
7
+ const yargs_1 = tslib_1.__importDefault(require("yargs"));
8
+ exports.options = {
9
+ cpu: {
10
+ type: 'string',
11
+ },
12
+ memory: {
13
+ type: 'string',
14
+ },
15
+ pipelines: {
16
+ choices: pipelines_1.pipelines,
17
+ coerce: (values) => values.map((value) => (0, change_case_1.camelCase)(value)),
18
+ default: [],
19
+ description: 'Pipelines that will be implemented with the CICD stack.',
20
+ type: 'array',
21
+ },
22
+ 'update-repository': {
23
+ alias: ['ur'],
24
+ description: 'Determine if the repository image will be updated.',
25
+ default: true,
26
+ type: 'boolean',
27
+ },
28
+ 'ssh-key': {
29
+ demandOption: true,
30
+ type: 'string',
31
+ },
32
+ 'ssh-url': {
33
+ demandOption: true,
34
+ type: 'string',
35
+ },
36
+ 'slack-webhook-url': {
37
+ type: 'string',
38
+ },
39
+ /**
40
+ * This option has the format:
41
+ *
42
+ * ```ts
43
+ * Array<{
44
+ * name: string,
45
+ * value: string,
46
+ * }>
47
+ * ```
48
+ */
49
+ 'task-environment': {
50
+ alias: ['te'],
51
+ default: [],
52
+ describe: 'A list of environment variables that will be passed to the ECS container task.',
53
+ type: 'array',
54
+ },
55
+ };
56
+ const getCicdConfig = () => {
57
+ const { parsed } = yargs_1.default.config();
58
+ if (!parsed) {
59
+ return false;
60
+ }
61
+ const { argv } = parsed;
62
+ const config = Object.keys(exports.options).reduce((acc, key) => {
63
+ const value = argv[key];
64
+ if (value) {
65
+ acc[key] = value;
66
+ }
67
+ return acc;
68
+ }, {});
69
+ return config;
70
+ };
71
+ exports.getCicdConfig = getCicdConfig;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CICD_FOLDER_NAME = exports.PIPELINE_ECS_TASK_EXECUTION_MANUAL_APPROVAL_ACTION_NAME = exports.PIPELINE_ECS_TASK_EXECUTION_STAGE_NAME = exports.ECS_TASK_DEFAULT_MEMORY = exports.ECS_TASK_DEFAULT_CPU = void 0;
4
+ exports.ECS_TASK_DEFAULT_CPU = '2048';
5
+ exports.ECS_TASK_DEFAULT_MEMORY = '4096';
6
+ exports.PIPELINE_ECS_TASK_EXECUTION_STAGE_NAME = `PipelineRunECSTasksStage`;
7
+ exports.PIPELINE_ECS_TASK_EXECUTION_MANUAL_APPROVAL_ACTION_NAME = `PipelineRunECSTasksApproval`;
8
+ exports.CICD_FOLDER_NAME = '.cicd';
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deployCicd = exports.getLambdaInput = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const cicd_template_1 = require("./cicd.template");
8
+ const cloudFormation_core_1 = require("../cloudFormation.core");
9
+ const deployLambdaCode_1 = require("../lambda/deployLambdaCode");
10
+ const getCicdStackName_1 = require("./getCicdStackName");
11
+ const utils_1 = require("../utils");
12
+ const utils_2 = require("../../utils");
13
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
14
+ const logPrefix = 'cicd';
15
+ const getLambdaInput = (extension) => path.resolve(__dirname, `lambdas/index.${extension}`);
16
+ exports.getLambdaInput = getLambdaInput;
17
+ const deployCicdLambdas = async ({ stackName }) => {
18
+ const lambdaInput = (() => {
19
+ /**
20
+ * This case happens when carlin command is executed when the package is
21
+ * built.
22
+ */
23
+ if (fs.existsSync((0, exports.getLambdaInput)('js'))) {
24
+ return (0, exports.getLambdaInput)('js');
25
+ }
26
+ /**
27
+ * The package isn't built.
28
+ */
29
+ if (fs.existsSync((0, exports.getLambdaInput)('ts'))) {
30
+ return (0, exports.getLambdaInput)('ts');
31
+ }
32
+ throw new Error('Cannot read CICD lambdas file.');
33
+ })();
34
+ const s3 = await (0, deployLambdaCode_1.deployLambdaCode)({
35
+ lambdaInput,
36
+ lambdaExternals: [],
37
+ /**
38
+ * Needs stackName to define the S3 key.
39
+ */
40
+ stackName,
41
+ });
42
+ if (!s3 || !s3.bucket) {
43
+ throw new Error('Cannot retrieve bucket in which Lambda code was deployed.');
44
+ }
45
+ return s3;
46
+ };
47
+ const waitRepositoryImageUpdate = async ({ stackName, }) => {
48
+ npmlog_1.default.info(logPrefix, 'Starting repository image update...');
49
+ const { OutputValue: projectName } = await (0, cloudFormation_core_1.getStackOutput)({
50
+ stackName,
51
+ outputKey: cicd_template_1.REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID,
52
+ });
53
+ if (!projectName) {
54
+ throw new Error(`Cannot retrieve repository image CodeBuild project name.`);
55
+ }
56
+ const build = await (0, utils_2.startCodeBuildBuild)({ projectName });
57
+ if (build.id) {
58
+ await (0, utils_2.waitCodeBuildFinish)({ buildId: build.id, name: stackName });
59
+ }
60
+ };
61
+ const deployCicd = async ({ cpu, memory, pipelines, updateRepository, slackWebhookUrl, sshKey, sshUrl, taskEnvironment, }) => {
62
+ try {
63
+ const { stackName } = await (0, utils_1.handleDeployInitialization)({
64
+ logPrefix,
65
+ stackName: (0, getCicdStackName_1.getCicdStackName)(),
66
+ });
67
+ await (0, cloudFormation_core_1.deploy)({
68
+ template: (0, cicd_template_1.getCicdTemplate)({
69
+ cpu,
70
+ memory,
71
+ pipelines,
72
+ s3: await deployCicdLambdas({ stackName }),
73
+ slackWebhookUrl,
74
+ taskEnvironment,
75
+ }),
76
+ params: {
77
+ StackName: stackName,
78
+ Parameters: [
79
+ { ParameterKey: 'SSHUrl', ParameterValue: sshUrl },
80
+ { ParameterKey: 'SSHKey', ParameterValue: sshKey },
81
+ ],
82
+ },
83
+ terminationProtection: true,
84
+ });
85
+ if (updateRepository) {
86
+ await waitRepositoryImageUpdate({ stackName });
87
+ }
88
+ }
89
+ catch (error) {
90
+ (0, utils_1.handleDeployError)({ error, logPrefix });
91
+ }
92
+ };
93
+ exports.deployCicd = deployCicd;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ecsTaskReportCommand = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const aws_sdk_1 = tslib_1.__importDefault(require("aws-sdk"));
6
+ const npmlog_1 = tslib_1.__importDefault(require("npmlog"));
7
+ const logPrefix = 'cicd-ecs-task-report';
8
+ /**
9
+ * This method create the payload to send to Lambda ECS task report handler.
10
+ *
11
+ * @param param.status execution status.
12
+ */
13
+ const sendEcsTaskReport = async ({ status }) => {
14
+ if (!process.env.ECS_TASK_REPORT_HANDLER_NAME) {
15
+ npmlog_1.default.info(logPrefix, 'ECS_TASK_REPORT_HANDLER_NAME not defined.');
16
+ return;
17
+ }
18
+ const lambda = new aws_sdk_1.default.Lambda();
19
+ const payload = { status };
20
+ if (process.env.ECS_TASK_ARN) {
21
+ payload.ecsTaskArn = process.env.ECS_TASK_ARN;
22
+ }
23
+ if (process.env.PIPELINE_NAME) {
24
+ payload.pipelineName = process.env.PIPELINE_NAME;
25
+ }
26
+ await lambda
27
+ .invokeAsync({
28
+ FunctionName: process.env.ECS_TASK_REPORT_HANDLER_NAME,
29
+ InvokeArgs: JSON.stringify(payload),
30
+ })
31
+ .promise();
32
+ npmlog_1.default.info(logPrefix, 'Report sent.');
33
+ };
34
+ const options = {
35
+ status: {
36
+ choices: ['Approved', 'Rejected', 'MainTagFound'],
37
+ demandOption: true,
38
+ type: 'string',
39
+ },
40
+ };
41
+ /**
42
+ * Used to send report to ECS Task Report Handler Lambda.
43
+ */
44
+ exports.ecsTaskReportCommand = {
45
+ command: 'cicd-ecs-task-report',
46
+ describe: false,
47
+ builder: (yargs) => yargs.options(options),
48
+ handler: async (args) => {
49
+ return sendEcsTaskReport(args);
50
+ },
51
+ };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCicdStackName = void 0;
4
+ const change_case_1 = require("change-case");
5
+ const config_1 = require("../../config");
6
+ const getProjectName_1 = require("../../utils/getProjectName");
7
+ const getCicdStackName = () => {
8
+ const project = (0, getProjectName_1.getProjectName)();
9
+ return (0, change_case_1.pascalCase)([config_1.NAME, 'Cicd', project].join(' '));
10
+ };
11
+ exports.getCicdStackName = getCicdStackName;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTriggerPipelinesObjectKey = void 0;
4
+ /**
5
+ * The file with this key inside the source S3 key of main and tag pipelines
6
+ * will trigger those pipelines.
7
+ */
8
+ const getTriggerPipelinesObjectKey = ({ prefix, pipeline, }) => {
9
+ return `${prefix}/${pipeline}.zip`;
10
+ };
11
+ exports.getTriggerPipelinesObjectKey = getTriggerPipelinesObjectKey;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cicdApiV1Handler = void 0;
4
+ const aws_sdk_1 = require("aws-sdk");
5
+ const executeTasks_1 = require("./executeTasks");
6
+ const getProcessEnvVariable_1 = require("./getProcessEnvVariable");
7
+ const codebuild = new aws_sdk_1.CodeBuild({ apiVersion: '2016-10-06' });
8
+ /**
9
+ * The CI/CD REST API is responsible to update the image of the repository and
10
+ * running tasks inside a container using [ECS Fargate](https://aws.amazon.com/fargate/).
11
+ * The API URL has the format of an [AWS API Gateway](https://aws.amazon.com/api-gateway/) URL:
12
+ *
13
+ * ```sh
14
+ * https://<api-id>.execute-api.<region>.amazonaws.com/v1
15
+ * ```
16
+ *
17
+ * It can be found on the "Outputs" tab if you access the CI/CD stack details
18
+ * from AWS Console or by the output `ApiV1Endpoint` that is printed after the
19
+ * stack creation.
20
+ *
21
+ * Such API has the `/cicd` endpoint, that is consumed by a POST method. This
22
+ * endpoint has two actions, defined by the `action` property, passed inside
23
+ * the body of the POST method.
24
+ *
25
+ * - **updateRepository**
26
+ *
27
+ * This action update the repository image on AWS ECR. This command is useful
28
+ * to you update your _node_modules_ folder or your [Yarn cache](https://classic.yarnpkg.com/en/docs/cli/cache/).
29
+ *
30
+ * Body interface:
31
+ *
32
+ * ```ts
33
+ * {
34
+ * action: "updateRepository";
35
+ * }
36
+ * ```
37
+ *
38
+ * - **executeTask**
39
+ *
40
+ * This action create an execution of your repository image using [ECS Fargate](https://aws.amazon.com/fargate/).
41
+ * The commands that will be executed is passed by the `commands` property,
42
+ * which receives an array of commands. You can also provide the following
43
+ * properties:
44
+ *
45
+ * - `cpu`: CPU value reserved for the task.
46
+ * - `memory`: memory value reserved for the task.
47
+ * - `taskEnvironment`: list of environment variables that can be accessed
48
+ * on task execution.
49
+ *
50
+ * Body interface:
51
+ *
52
+ * ```ts
53
+ * {
54
+ * action: "executeTask";
55
+ * commands: string[];
56
+ * cpu?: string;
57
+ * memory?: string;
58
+ * taskEnvironment?: Array<{ name: string; value: string }>;
59
+ * }
60
+ * ```
61
+ *
62
+ * Example:
63
+ *
64
+ * ```json
65
+ * {
66
+ * "action": "executeTask",
67
+ * "commands": [
68
+ * "git status",
69
+ * "git pull origin main",
70
+ * "yarn",
71
+ * "yarn test"
72
+ * ],
73
+ * "cpu": "1024",
74
+ * "memory": "2048",
75
+ * "taskEnvironment": [
76
+ * {
77
+ * "name": "CI",
78
+ * "value": "true"
79
+ * }
80
+ * ]
81
+ * }
82
+ * ```
83
+ *
84
+ */
85
+ const cicdApiV1Handler = async (event) => {
86
+ try {
87
+ const body = JSON.parse(event.body || JSON.stringify({}));
88
+ let response;
89
+ if (body.action === 'updateRepository') {
90
+ response = await codebuild
91
+ .startBuild({
92
+ projectName: (0, getProcessEnvVariable_1.getProcessEnvVariable)('PROCESS_ENV_REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME'),
93
+ })
94
+ .promise();
95
+ }
96
+ if (body.action === 'executeTask') {
97
+ const { commands = [], cpu, memory, taskEnvironment = [] } = body;
98
+ if (commands.length === 0) {
99
+ throw new Error('Commands were not provided.');
100
+ }
101
+ response = await (0, executeTasks_1.executeTasks)({ commands, cpu, memory, taskEnvironment });
102
+ }
103
+ if (response) {
104
+ return {
105
+ statusCode: 200,
106
+ headers: {
107
+ 'Content-Type': 'application/json',
108
+ },
109
+ body: JSON.stringify(response),
110
+ };
111
+ }
112
+ return {
113
+ statusCode: 403,
114
+ body: 'Execute access forbidden',
115
+ };
116
+ }
117
+ catch (error) {
118
+ return {
119
+ statusCode: 400,
120
+ body: error.message,
121
+ };
122
+ }
123
+ };
124
+ exports.cicdApiV1Handler = cicdApiV1Handler;
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ecsTaskReportHandler = exports.getEcsTaskTags = exports.getEcsTaskLogsUrl = void 0;
4
+ const aws_sdk_1 = require("aws-sdk");
5
+ const webhook_1 = require("@slack/webhook");
6
+ const putApprovalResultManualTask_1 = require("./putApprovalResultManualTask");
7
+ const ecs = new aws_sdk_1.ECS({ apiVersion: '2014-11-13' });
8
+ const getEcsTaskId = ({ ecsTaskArn }) => {
9
+ /**
10
+ * Arn has the following format:
11
+ * arn:aws:ecs:us-east-1:483684946879:task/CarlinCicdCarlinMonorepo-RepositoryTasksECSCluster-1J6saGT91hCr/6fcc78682de442ae89a0b7339ac7d981
12
+ *
13
+ * We want the "6fcc78682de442ae89a0b7339ac7d981" part.
14
+ */
15
+ const ecsTaskId = ecsTaskArn.split('/')[2];
16
+ return ecsTaskId;
17
+ };
18
+ const getEcsTaskCluster = ({ ecsTaskArn }) => {
19
+ /**
20
+ * Arn has the following format:
21
+ * arn:aws:ecs:us-east-1:483684946879:task/CarlinCicdCarlinMonorepo-RepositoryTasksECSCluster-1J6saGT91hCr/6fcc78682de442ae89a0b7339ac7d981
22
+ *
23
+ * We want the "CarlinCicdCarlinMonorepo-RepositoryTasksECSCluster-1J6saGT91hCr" part.
24
+ */
25
+ const ecsTaskCluster = ecsTaskArn.split('/')[1];
26
+ return ecsTaskCluster;
27
+ };
28
+ const getEcsTaskLogsUrl = ({ ecsTaskArn }) => {
29
+ if (!process.env.ECS_TASK_CONTAINER_NAME ||
30
+ !process.env.ECS_TASK_LOGS_LOG_GROUP) {
31
+ return undefined;
32
+ }
33
+ const ecsTaskId = getEcsTaskId({ ecsTaskArn });
34
+ const ecsTaskLogsUrl = new URL([
35
+ /**
36
+ * https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
37
+ */
38
+ `https://console.aws.amazon.com/cloudwatch/home?region=${process.env.AWS_REGION}#logsV2:log-groups`,
39
+ 'log-group',
40
+ process.env.ECS_TASK_LOGS_LOG_GROUP,
41
+ 'log-events',
42
+ `ecs/${process.env.ECS_TASK_CONTAINER_NAME}/${ecsTaskId}`.replace(/\//g, '%252F'),
43
+ ]
44
+ .join('/')
45
+ .replace(/"/g, ''));
46
+ return ecsTaskLogsUrl.href;
47
+ };
48
+ exports.getEcsTaskLogsUrl = getEcsTaskLogsUrl;
49
+ const getEcsTaskTags = async ({ ecsTaskArn, }) => {
50
+ try {
51
+ const cluster = getEcsTaskCluster({ ecsTaskArn });
52
+ const { tasks } = await ecs
53
+ .describeTasks({ cluster, include: ['TAGS'], tasks: [ecsTaskArn] })
54
+ .promise();
55
+ const task = tasks === null || tasks === void 0 ? void 0 : tasks[0];
56
+ if (!task) {
57
+ return undefined;
58
+ }
59
+ return task.tags;
60
+ }
61
+ catch (error) {
62
+ return undefined;
63
+ }
64
+ };
65
+ exports.getEcsTaskTags = getEcsTaskTags;
66
+ /**
67
+ * This method is invoked when an ECS task is executed and the success or
68
+ * failure commands calls `carlin cicd-ecs-task-report --status=<status>`.
69
+ */
70
+ const ecsTaskReportHandler = async ({ ecsTaskArn, status, pipelineName, }) => {
71
+ const logs = ecsTaskArn && (0, exports.getEcsTaskLogsUrl)({ ecsTaskArn });
72
+ const handleApprovalResult = async () => {
73
+ if (pipelineName) {
74
+ await (0, putApprovalResultManualTask_1.putApprovalResultManualTask)({
75
+ pipelineName,
76
+ result: {
77
+ status: status === 'MainTagFound' ? 'Approved' : status,
78
+ summary: JSON.stringify({ status, logs }),
79
+ },
80
+ });
81
+ }
82
+ };
83
+ const ecsTaskTags = ecsTaskArn && (await (0, exports.getEcsTaskTags)({ ecsTaskArn }));
84
+ const handleStackNotification = async () => {
85
+ /**
86
+ * Do not send a notification if the task was main pipeline with tag.
87
+ */
88
+ if (status === 'MainTagFound') {
89
+ return;
90
+ }
91
+ const url = process.env.SLACK_WEBHOOK_URL;
92
+ if (!url) {
93
+ return;
94
+ }
95
+ const webhook = new webhook_1.IncomingWebhook(url);
96
+ /**
97
+ * Block Kit Builder: https://app.slack.com/block-kit-builder/TJ79J0ZU3#%7B%22blocks%22:%5B%5D%7D
98
+ * Formatting: https://api.slack.com/reference/surfaces/formatting
99
+ */
100
+ await webhook.send({
101
+ blocks: [
102
+ {
103
+ type: 'section',
104
+ text: {
105
+ type: 'mrkdwn',
106
+ text: `<${logs}|Logs>`,
107
+ },
108
+ },
109
+ {
110
+ type: 'section',
111
+ text: {
112
+ type: 'mrkdwn',
113
+ text: `\`\`\`${JSON.stringify({
114
+ status,
115
+ pipelineName,
116
+ ecsTaskTags,
117
+ }, null, 2)}\`\`\``,
118
+ },
119
+ },
120
+ ],
121
+ });
122
+ };
123
+ await Promise.all([handleApprovalResult(), handleStackNotification()]);
124
+ return { statusCode: 200, body: 'ok' };
125
+ };
126
+ exports.ecsTaskReportHandler = ecsTaskReportHandler;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeTasks = void 0;
4
+ const aws_sdk_1 = require("aws-sdk");
5
+ const shConditionalCommands_1 = require("./shConditionalCommands");
6
+ const getProcessEnvVariable_1 = require("./getProcessEnvVariable");
7
+ const ecs = new aws_sdk_1.ECS({ apiVersion: '2014-11-13', maxRetries: 3 });
8
+ const executeTasks = async ({ commands = [], cpu, memory, taskEnvironment = [], tags = [], }) => {
9
+ const command = (0, shConditionalCommands_1.compileCommands)([
10
+ /**
11
+ * https://stackoverflow.com/questions/2853803/how-to-echo-shell-commands-as-they-are-executed/2853811
12
+ */
13
+ 'set -x',
14
+ /**
15
+ * https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v3.html
16
+ */
17
+ `export ECS_TASK_ARN=$(curl -X GET "\${ECS_CONTAINER_METADATA_URI}/task" | jq ".TaskARN")`,
18
+ ...commands,
19
+ ]);
20
+ const response = await ecs
21
+ .runTask({
22
+ taskDefinition: (0, getProcessEnvVariable_1.getProcessEnvVariable)('ECS_TASK_DEFINITION'),
23
+ cluster: (0, getProcessEnvVariable_1.getProcessEnvVariable)('ECS_CLUSTER_ARN'),
24
+ count: 1,
25
+ launchType: 'FARGATE',
26
+ networkConfiguration: {
27
+ awsvpcConfiguration: {
28
+ subnets: [
29
+ (0, getProcessEnvVariable_1.getProcessEnvVariable)('VPC_PUBLIC_SUBNET_0'),
30
+ (0, getProcessEnvVariable_1.getProcessEnvVariable)('VPC_PUBLIC_SUBNET_1'),
31
+ (0, getProcessEnvVariable_1.getProcessEnvVariable)('VPC_PUBLIC_SUBNET_2'),
32
+ ],
33
+ assignPublicIp: 'ENABLED',
34
+ securityGroups: [(0, getProcessEnvVariable_1.getProcessEnvVariable)('VPC_SECURITY_GROUP')],
35
+ },
36
+ },
37
+ overrides: {
38
+ containerOverrides: [
39
+ {
40
+ command: ['sh', '-cv', command],
41
+ name: (0, getProcessEnvVariable_1.getProcessEnvVariable)('ECS_CONTAINER_NAME'),
42
+ environment: [
43
+ {
44
+ name: 'CI',
45
+ value: 'true',
46
+ },
47
+ {
48
+ name: 'ECS_ENABLE_CONTAINER_METADATA',
49
+ value: 'true',
50
+ },
51
+ {
52
+ name: 'ECS_TASK_REPORT_HANDLER_NAME',
53
+ value: (0, getProcessEnvVariable_1.getProcessEnvVariable)('ECS_TASK_REPORT_HANDLER_NAME'),
54
+ },
55
+ ...taskEnvironment,
56
+ ],
57
+ },
58
+ ],
59
+ cpu,
60
+ memory,
61
+ },
62
+ tags,
63
+ })
64
+ .promise();
65
+ return response;
66
+ };
67
+ exports.executeTasks = executeTasks;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getProcessEnvVariable = void 0;
4
+ const getProcessEnvVariable = (env) => {
5
+ if (process.env[env]) {
6
+ return process.env[env];
7
+ }
8
+ throw new Error(`process.env.${env} is not defined.`);
9
+ };
10
+ exports.getProcessEnvVariable = getProcessEnvVariable;