From 4666e25bed2dbc757ab80eb9a3204c42fa8c6036 Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 16 May 2024 13:00:46 -0700 Subject: [PATCH 01/14] add options.autoscalingRole --- changelog.md | 23 ++++++++++++++++ lib/template.js | 71 ++++++++++++++++++++++++++----------------------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/changelog.md b/changelog.md index 1989f8dc..ded0ca24 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,26 @@ +### 9.1.0 + +- Merge ScalingLambdaRole and LambdaTotalMessagesRole into the primary WatchbotRole resource to reduce number of distinct IAM roles +- Add option `autoScalingRole` for using a predefined auto scaling role instead of creating one for the stack. This role should have the following permissions, with the resouce scope being as strict as desired: + +```JSON +{ + "Statement": [ + { + "Action": [ + "application-autoscaling:*", + "cloudwatch:DescribeAlarms", + "cloudwatch:PutMetricAlarm", + "ecs:UpdateService", + "ecs:DescribeServices" + ], + "Resource": "*", + "Effect": "Allow" + } + ] +} +``` + ### 9.0.1 - Bug fix: TotalMessagesLambda now working as expected; watchbot stacks now scaling down when no tasks in queue diff --git a/lib/template.js b/lib/template.js index b849c378..af02a453 100644 --- a/lib/template.js +++ b/lib/template.js @@ -38,7 +38,8 @@ module.exports = (options = {}) => { dashboard: true, fifo: false, fargatePublicIp: 'DISABLED', - structuredLogging: false + structuredLogging: false, + autoscalingRole: false, }, options ); @@ -433,41 +434,43 @@ module.exports = (options = {}) => { Resources[prefixed('Service')].Properties.PlacementStrategies = options.placementStrategies; - Resources[prefixed('ScalingRole')] = { - Type: 'AWS::IAM::Role', - Properties: { - AssumeRolePolicyDocument: { - Statement: [ + if (!options.autoscalingRole) { + Resources[prefixed('ScalingRole')] = { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Principal: { Service: ['application-autoscaling.amazonaws.com'] }, + Action: ['sts:AssumeRole'] + } + ] + }, + Path: '/', + Policies: [ { - Effect: 'Allow', - Principal: { Service: ['application-autoscaling.amazonaws.com'] }, - Action: ['sts:AssumeRole'] + PolicyName: 'watchbot-autoscaling', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: [ + 'application-autoscaling:*', + 'cloudwatch:DescribeAlarms', + 'cloudwatch:PutMetricAlarm', + 'ecs:UpdateService', + 'ecs:DescribeServices' + ], + Resource: '*' + } + ] + } } ] - }, - Path: '/', - Policies: [ - { - PolicyName: 'watchbot-autoscaling', - PolicyDocument: { - Statement: [ - { - Effect: 'Allow', - Action: [ - 'application-autoscaling:*', - 'cloudwatch:DescribeAlarms', - 'cloudwatch:PutMetricAlarm', - 'ecs:UpdateService', - 'ecs:DescribeServices' - ], - Resource: '*' - } - ] - } - } - ] - } - }; + } + }; + } Resources[prefixed('ScalingTarget')] = { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', @@ -482,7 +485,7 @@ module.exports = (options = {}) => { ]), MinCapacity: options.minSize, MaxCapacity: options.maxSize, - RoleARN: cf.getAtt(prefixed('ScalingRole'), 'Arn') + RoleARN: options.autoscalingRole || cf.getAtt(prefixed('ScalingRole'), 'Arn') } }; From 421277bc26a2f6ab8d25de3e6143ba6a8ef2581a Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 16 May 2024 13:11:23 -0700 Subject: [PATCH 02/14] combine lambda roles into primary watchbot role --- bin/dead-letter.js | 2 +- lib/template.js | 121 ++++++++++++++------------------------------- 2 files changed, 38 insertions(+), 85 deletions(-) diff --git a/bin/dead-letter.js b/bin/dead-letter.js index e77042c5..2caf1240 100755 --- a/bin/dead-letter.js +++ b/bin/dead-letter.js @@ -3,7 +3,7 @@ /* eslint-disable no-console */ -const { CloudFormation } = require('@aws-sdk/client-cloudformation') +const { CloudFormation } = require('@aws-sdk/client-cloudformation'); const { SQS } = require('@aws-sdk/client-sqs'); const inquirer = require('inquirer'); const stream = require('stream'); diff --git a/lib/template.js b/lib/template.js index af02a453..863ba440 100644 --- a/lib/template.js +++ b/lib/template.js @@ -39,7 +39,7 @@ module.exports = (options = {}) => { fifo: false, fargatePublicIp: 'DISABLED', structuredLogging: false, - autoscalingRole: false, + autoscalingRole: false }, options ); @@ -247,7 +247,7 @@ module.exports = (options = {}) => { Statement: [ { Effect: 'Allow', - Principal: { Service: ['ecs-tasks.amazonaws.com'] }, + Principal: { Service: ['ecs-tasks.amazonaws.com', 'lambda.amazonaws.com'] }, Action: ['sts:AssumeRole'] } ] @@ -286,6 +286,39 @@ module.exports = (options = {}) => { ) ] } + }, + // roles for log-based lambda scaling utility + { + PolicyName: cf.join([cf.stackName, '-lambda-scaling']), + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: [ + 'logs:*' + ], + Resource: cf.join([ + 'arn:', + cf.partition, + ':logs:*:*:*' + ]) + }, + { + Effect: 'Allow', + Action: [ + 'cloudwatch:PutMetricData' + ], + Resource: '*' + }, + { + Effect: 'Allow', + Action: [ + 'sqs:GetQueueAttributes' + ], + Resource: cf.getAtt(prefixed('Queue'), 'Arn') + } + ] + } } ] } @@ -715,44 +748,11 @@ module.exports = (options = {}) => { } }; - Resources[prefixed('LambdaScalingRole')] = { - Type: 'AWS::IAM::Role', - Properties: { - AssumeRolePolicyDocument: { - Statement: [ - { - Effect: 'Allow', - Principal: { Service: ['lambda.amazonaws.com'] }, - Action: ['sts:AssumeRole'] - } - ] - }, - Policies: [{ - PolicyName: 'CustomcfnScalingLambdaLogs', - PolicyDocument: { - Statement: [ - { - Effect: 'Allow', - Action: [ - 'logs:*' - ], - Resource: cf.join([ - 'arn:', - cf.partition, - ':logs:*:*:*' - ]) - } - ] - } - }] - } - }; - Resources[prefixed('ScalingLambda')] = { Type: 'AWS::Lambda::Function', Properties: { Handler: 'index.handler', - Role: cf.getAtt(prefixed('LambdaScalingRole'), 'Arn'), + Role: cf.getAtt(prefixed('Role'), 'Arn'), Code: { ZipFile: cf.sub(` const response = require('./cfn-response'); @@ -778,7 +778,7 @@ module.exports = (options = {}) => { Type: 'AWS::Lambda::Function', Properties: { Handler: 'index.handler', - Role: cf.getAtt(prefixed('LambdaTotalMessagesRole'), 'Arn'), + Role: cf.getAtt(prefixed('Role'), 'Arn'), Timeout: 60, Code: { ZipFile: cf.sub(` @@ -815,53 +815,6 @@ module.exports = (options = {}) => { } }; - Resources[prefixed('LambdaTotalMessagesRole')] = { - Type: 'AWS::IAM::Role', - Properties: { - AssumeRolePolicyDocument: { - Statement: [ - { - Effect: 'Allow', - Principal: { Service: ['lambda.amazonaws.com'] }, - Action: ['sts:AssumeRole'] - } - ] - }, - Policies: [{ - PolicyName: 'LambdaTotalMessagesMetric', - PolicyDocument: { - Statement: [ - { - Effect: 'Allow', - Action: [ - 'logs:*' - ], - Resource: cf.join([ - 'arn:', - cf.partition, - ':logs:*:*:*' - ]) - }, - { - Effect: 'Allow', - Action: [ - 'cloudwatch:PutMetricData' - ], - Resource: '*' - }, - { - Effect: 'Allow', - Action: [ - 'sqs:GetQueueAttributes' - ], - Resource: cf.getAtt(prefixed('Queue'), 'Arn') - } - ] - } - }] - } - }; - Resources[prefixed('TotalMessagesSchedule')] = { Type: 'AWS::Events::Rule', Properties: { From 23eb2077a9012280d6888ef7be7af8094548299b Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 16 May 2024 13:28:19 -0700 Subject: [PATCH 03/14] document new autoscalingRoleArn option --- docs/building-a-template.md | 23 +++++++++++++++++++++++ lib/template.js | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/building-a-template.md b/docs/building-a-template.md index b0faf87e..1615baa4 100644 --- a/docs/building-a-template.md +++ b/docs/building-a-template.md @@ -99,6 +99,7 @@ When creating your watchbot stacks with the `watchbot.template()` method, you no **placementConstraints** | ECS service [placement constraints](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-placementconstraint.html). This value is ignored for `capacity` values other than `'EC2'`. | Object[]/Ref | No | false **placementStrategies** | ECS service [placement strategies](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-placementstrategy.html). This value is ignored for `capacity` values other than `'EC2'`. | Object[]/Ref | No | false **structuredLogging** | Whether to emit logs in JSON format or not | Boolean | No | `false` +**autoscalingRoleArn** | A custom autoscaling role to use instead of building a distinct role for the stack | String/Ref | No | If not provided, an autoscaling role will be built with the permissions described in [Custom Autoscaling Role](#custom-autoscaling-role) ### writableFilesystem mode explained @@ -137,3 +138,25 @@ var outputs = { cloudfriend.merge(myTemplate, watcher, outputs); ``` + +### Custom Autoscaling Role + +You can provide a custom autoscaling role for your service. If you do not provide a custom role, a role with the following permissions will be created, which can only be assumed by the `application-autoscaling.amazonaws.com` principal. + +```JSON +{ + "Statement": [ + { + "Action": [ + "application-autoscaling:*", + "cloudwatch:DescribeAlarms", + "cloudwatch:PutMetricAlarm", + "ecs:UpdateService", + "ecs:DescribeServices" + ], + "Resource": "*", + "Effect": "Allow" + } + ] +} +``` \ No newline at end of file diff --git a/lib/template.js b/lib/template.js index 863ba440..d1d1acdb 100644 --- a/lib/template.js +++ b/lib/template.js @@ -39,7 +39,7 @@ module.exports = (options = {}) => { fifo: false, fargatePublicIp: 'DISABLED', structuredLogging: false, - autoscalingRole: false + autoscalingRoleArn: false }, options ); @@ -467,7 +467,7 @@ module.exports = (options = {}) => { Resources[prefixed('Service')].Properties.PlacementStrategies = options.placementStrategies; - if (!options.autoscalingRole) { + if (!options.autoscalingRoleArn) { Resources[prefixed('ScalingRole')] = { Type: 'AWS::IAM::Role', Properties: { From 8a5d5980a864011c52ef9d73ef818694cd33c573 Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 May 2024 09:43:37 -0700 Subject: [PATCH 04/14] 9.1.0-dev.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d679348d..86e2605a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mapbox/watchbot", - "version": "9.0.1", + "version": "9.1.0-dev.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@mapbox/watchbot", - "version": "9.0.1", + "version": "9.1.0-dev.1", "license": "BSD-2-Clause", "dependencies": { "@aws-sdk/client-cloudformation": "^3.414.0", diff --git a/package.json b/package.json index 5050bdcc..713f2a32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mapbox/watchbot", - "version": "9.0.1", + "version": "9.1.0-dev.1", "description": "", "main": "index.js", "engines": { From 71c062645a88d6112ecf01202fa3e47f05cedf0f Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 May 2024 10:10:53 -0700 Subject: [PATCH 05/14] pipline version 2 --- cloudformation/ecs-watchbot-generate-binaries.template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudformation/ecs-watchbot-generate-binaries.template.js b/cloudformation/ecs-watchbot-generate-binaries.template.js index 61a6bd5b..0315d7f3 100644 --- a/cloudformation/ecs-watchbot-generate-binaries.template.js +++ b/cloudformation/ecs-watchbot-generate-binaries.template.js @@ -247,7 +247,7 @@ const Resources = { ActionTypeId: { Category: 'Build', Owner: 'AWS', - Version: '1', + Version: '2', Provider: 'CodeBuild' }, InputArtifacts: [ @@ -262,7 +262,7 @@ const Resources = { ActionTypeId: { Category: 'Build', Owner: 'AWS', - Version: '1', + Version: '2', Provider: 'CodeBuild' }, InputArtifacts: [ From e6f41e9c7b902d3ad04efb95e9800a90cc3f263e Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 May 2024 10:13:33 -0700 Subject: [PATCH 06/14] just github v2 --- cloudformation/ecs-watchbot-generate-binaries.template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudformation/ecs-watchbot-generate-binaries.template.js b/cloudformation/ecs-watchbot-generate-binaries.template.js index 0315d7f3..90a053ae 100644 --- a/cloudformation/ecs-watchbot-generate-binaries.template.js +++ b/cloudformation/ecs-watchbot-generate-binaries.template.js @@ -247,7 +247,7 @@ const Resources = { ActionTypeId: { Category: 'Build', Owner: 'AWS', - Version: '2', + Version: '1', Provider: 'CodeBuild' }, InputArtifacts: [ From e78140fba4f54ecfd0d55d74b531a0b2721f0a1f Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 May 2024 10:14:55 -0700 Subject: [PATCH 07/14] actually v2 --- cloudformation/ecs-watchbot-generate-binaries.template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudformation/ecs-watchbot-generate-binaries.template.js b/cloudformation/ecs-watchbot-generate-binaries.template.js index 90a053ae..c2b63c30 100644 --- a/cloudformation/ecs-watchbot-generate-binaries.template.js +++ b/cloudformation/ecs-watchbot-generate-binaries.template.js @@ -223,7 +223,7 @@ const Resources = { ActionTypeId: { Category: 'Source', Owner: 'ThirdParty', - Version: '1', + Version: '2', Provider: 'GitHub' }, OutputArtifacts: [ @@ -262,7 +262,7 @@ const Resources = { ActionTypeId: { Category: 'Build', Owner: 'AWS', - Version: '2', + Version: '1', Provider: 'CodeBuild' }, InputArtifacts: [ From 4a712c9a1f27f08e6582739806eee9774fb48dc9 Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 May 2024 10:20:42 -0700 Subject: [PATCH 08/14] update branch source --- cloudformation/ecs-watchbot-generate-binaries.template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudformation/ecs-watchbot-generate-binaries.template.js b/cloudformation/ecs-watchbot-generate-binaries.template.js index c2b63c30..bf06a577 100644 --- a/cloudformation/ecs-watchbot-generate-binaries.template.js +++ b/cloudformation/ecs-watchbot-generate-binaries.template.js @@ -223,7 +223,7 @@ const Resources = { ActionTypeId: { Category: 'Source', Owner: 'ThirdParty', - Version: '2', + Version: '1', Provider: 'GitHub' }, OutputArtifacts: [ @@ -233,7 +233,7 @@ const Resources = { Owner: 'mapbox', Repo: 'ecs-watchbot', PollForSourceChanges: 'false', - Branch: 'master', + Branch: 'mapsam/iam-role-options', OAuthToken: '{{resolve:secretsmanager:code-pipeline-helper/access-token}}' } } From 7d7cdf855a7b96caddbb4cabf383ae6caa3887e2 Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 May 2024 10:42:02 -0700 Subject: [PATCH 09/14] 9.1.0-dev.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86e2605a..386ff92b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.1", + "version": "9.1.0-dev.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.1", + "version": "9.1.0-dev.2", "license": "BSD-2-Clause", "dependencies": { "@aws-sdk/client-cloudformation": "^3.414.0", diff --git a/package.json b/package.json index 713f2a32..eb9208e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.1", + "version": "9.1.0-dev.2", "description": "", "main": "index.js", "engines": { From f8bf252ea719acb2c8884f09230e03b3aabf9a17 Mon Sep 17 00:00:00 2001 From: mapsam Date: Tue, 21 May 2024 09:54:09 -0700 Subject: [PATCH 10/14] 9.1.0-dev.3 --- lib/template.js | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/template.js b/lib/template.js index d1d1acdb..c677b0dd 100644 --- a/lib/template.js +++ b/lib/template.js @@ -518,7 +518,7 @@ module.exports = (options = {}) => { ]), MinCapacity: options.minSize, MaxCapacity: options.maxSize, - RoleARN: options.autoscalingRole || cf.getAtt(prefixed('ScalingRole'), 'Arn') + RoleARN: options.autoscalingRoleArn || cf.getAtt(prefixed('ScalingRole'), 'Arn') } }; diff --git a/package-lock.json b/package-lock.json index 386ff92b..ea838cd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.2", + "version": "9.1.0-dev.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.2", + "version": "9.1.0-dev.3", "license": "BSD-2-Clause", "dependencies": { "@aws-sdk/client-cloudformation": "^3.414.0", diff --git a/package.json b/package.json index eb9208e8..198b41c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.2", + "version": "9.1.0-dev.3", "description": "", "main": "index.js", "engines": { From 202f25a78192d297c9af99682999de120c55e8f7 Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 23 May 2024 14:19:06 -0700 Subject: [PATCH 11/14] 9.x branch build --- cloudformation/ecs-watchbot-generate-binaries.template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudformation/ecs-watchbot-generate-binaries.template.js b/cloudformation/ecs-watchbot-generate-binaries.template.js index bf06a577..d1cd71ae 100644 --- a/cloudformation/ecs-watchbot-generate-binaries.template.js +++ b/cloudformation/ecs-watchbot-generate-binaries.template.js @@ -233,7 +233,7 @@ const Resources = { Owner: 'mapbox', Repo: 'ecs-watchbot', PollForSourceChanges: 'false', - Branch: 'mapsam/iam-role-options', + Branch: '9.x', OAuthToken: '{{resolve:secretsmanager:code-pipeline-helper/access-token}}' } } From 38f771f1bd5d29e1b98eedec3f4faeee85e2a02d Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 13 Jun 2024 08:32:31 -0700 Subject: [PATCH 12/14] 9.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea838cd9..7872c56b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.3", + "version": "9.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.3", + "version": "9.1.0", "license": "BSD-2-Clause", "dependencies": { "@aws-sdk/client-cloudformation": "^3.414.0", diff --git a/package.json b/package.json index 198b41c1..3a4918c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mapbox/watchbot", - "version": "9.1.0-dev.3", + "version": "9.1.0", "description": "", "main": "index.js", "engines": { From c8b2526c9cbf2cea30090e07a740febb81ef1f4c Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 13 Jun 2024 08:36:02 -0700 Subject: [PATCH 13/14] update snapshots --- test/__snapshots__/template.spec.js.snap | 1634 ++++++++-------------- 1 file changed, 614 insertions(+), 1020 deletions(-) diff --git a/test/__snapshots__/template.spec.js.snap b/test/__snapshots__/template.spec.js.snap index 6f27a5b6..b9c9c341 100644 --- a/test/__snapshots__/template.spec.js.snap +++ b/test/__snapshots__/template.spec.js.snap @@ -89,7 +89,7 @@ exports[`[template]: all-properties 1`] = ` }, }, "Metadata": { - "EcsWatchbotVersion": "9.0.1", + "EcsWatchbotVersion": "9.1.0", }, "Outputs": { "ClusterArn": { @@ -127,7 +127,7 @@ exports[`[template]: all-properties 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#memoryutilization", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", "AlarmName": { "Fn::Join": [ "-", @@ -287,119 +287,6 @@ exports[`[template]: all-properties 1`] = ` }, "Type": "AWS::SQS::Queue", }, - "SoupLambdaScalingRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - ], - }, - "PolicyName": "CustomcfnScalingLambdaLogs", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SoupLambdaTotalMessagesRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - { - "Action": [ - "cloudwatch:PutMetricData", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "sqs:GetQueueAttributes", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "SoupQueue", - "Arn", - ], - }, - }, - ], - }, - "PolicyName": "LambdaTotalMessagesMetric", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, "SoupLogGroup": { "Properties": { "LogGroupName": { @@ -625,7 +512,7 @@ exports[`[template]: all-properties 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#queuesize", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", "AlarmName": { "Fn::Join": [ "-", @@ -699,6 +586,7 @@ exports[`[template]: all-properties 1`] = ` "Principal": { "Service": [ "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", ], }, }, @@ -772,6 +660,60 @@ exports[`[template]: all-properties 1`] = ` ], }, }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "SoupQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, { "PolicyDocument": { "Statement": [ @@ -945,7 +887,7 @@ exports[`[template]: all-properties 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "SoupLambdaScalingRole", + "SoupRole", "Arn", ], }, @@ -1386,7 +1328,7 @@ exports[`[template]: all-properties 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "SoupLambdaTotalMessagesRole", + "SoupRole", "Arn", ], }, @@ -1457,7 +1399,7 @@ exports[`[template]: all-properties 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#workererrors", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", "AlarmName": { "Fn::Join": [ "-", @@ -1613,7 +1555,7 @@ exports[`[template]: all-properties-CPU 1`] = ` }, }, "Metadata": { - "EcsWatchbotVersion": "9.0.1", + "EcsWatchbotVersion": "9.1.0", }, "Outputs": { "ClusterArn": { @@ -1651,7 +1593,7 @@ exports[`[template]: all-properties-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#memoryutilization", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", "AlarmName": { "Fn::Join": [ "-", @@ -1811,172 +1753,59 @@ exports[`[template]: all-properties-CPU 1`] = ` }, "Type": "AWS::SQS::Queue", }, - "SoupLambdaScalingRole": { + "SoupLogGroup": { "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], + "LogGroupName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", }, - }, + { + "Ref": "AWS::Region", + }, + "soup", + ], ], }, - "Policies": [ + "RetentionInDays": 14, + }, + "Type": "AWS::Logs::LogGroup", + }, + "SoupMessageReceivesMetric": { + "Properties": { + "FilterPattern": "{ $.receives = * }", + "LogGroupName": { + "Ref": "SoupLogGroup", + }, + "MetricTransformations": [ { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], + "MetricName": { + "Fn::Join": [ + "", + [ + "SoupMessageReceives-", + { + "Ref": "AWS::StackName", }, - }, + ], ], }, - "PolicyName": "CustomcfnScalingLambdaLogs", + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": "$.receives", }, ], }, - "Type": "AWS::IAM::Role", + "Type": "AWS::Logs::MetricFilter", }, - "SoupLambdaTotalMessagesRole": { + "SoupMetricSchedulePermission": { "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - { - "Action": [ - "cloudwatch:PutMetricData", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "sqs:GetQueueAttributes", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "SoupQueue", - "Arn", - ], - }, - }, - ], - }, - "PolicyName": "LambdaTotalMessagesMetric", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SoupLogGroup": { - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "-", - [ - { - "Ref": "AWS::StackName", - }, - { - "Ref": "AWS::Region", - }, - "soup", - ], - ], - }, - "RetentionInDays": 14, - }, - "Type": "AWS::Logs::LogGroup", - }, - "SoupMessageReceivesMetric": { - "Properties": { - "FilterPattern": "{ $.receives = * }", - "LogGroupName": { - "Ref": "SoupLogGroup", - }, - "MetricTransformations": [ - { - "MetricName": { - "Fn::Join": [ - "", - [ - "SoupMessageReceives-", - { - "Ref": "AWS::StackName", - }, - ], - ], - }, - "MetricNamespace": "Mapbox/ecs-watchbot", - "MetricValue": "$.receives", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "SoupMetricSchedulePermission": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "SoupTotalMessagesLambda", - "Arn", + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SoupTotalMessagesLambda", + "Arn", ], }, "Principal": "events.amazonaws.com", @@ -2149,7 +1978,7 @@ exports[`[template]: all-properties-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#queuesize", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", "AlarmName": { "Fn::Join": [ "-", @@ -2223,6 +2052,7 @@ exports[`[template]: all-properties-CPU 1`] = ` "Principal": { "Service": [ "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", ], }, }, @@ -2296,6 +2126,60 @@ exports[`[template]: all-properties-CPU 1`] = ` ], }, }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "SoupQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, { "PolicyDocument": { "Statement": [ @@ -2469,7 +2353,7 @@ exports[`[template]: all-properties-CPU 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "SoupLambdaScalingRole", + "SoupRole", "Arn", ], }, @@ -2906,7 +2790,7 @@ exports[`[template]: all-properties-CPU 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "SoupLambdaTotalMessagesRole", + "SoupRole", "Arn", ], }, @@ -2977,7 +2861,7 @@ exports[`[template]: all-properties-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#workererrors", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", "AlarmName": { "Fn::Join": [ "-", @@ -3133,7 +3017,7 @@ exports[`[template]: all-properties-low-CPU 1`] = ` }, }, "Metadata": { - "EcsWatchbotVersion": "9.0.1", + "EcsWatchbotVersion": "9.1.0", }, "Outputs": { "ClusterArn": { @@ -3171,7 +3055,7 @@ exports[`[template]: all-properties-low-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#memoryutilization", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", "AlarmName": { "Fn::Join": [ "-", @@ -3331,188 +3215,75 @@ exports[`[template]: all-properties-low-CPU 1`] = ` }, "Type": "AWS::SQS::Queue", }, - "SoupLambdaScalingRole": { + "SoupLogGroup": { "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], + "LogGroupName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", }, - }, + { + "Ref": "AWS::Region", + }, + "soup", + ], ], }, - "Policies": [ + "RetentionInDays": 14, + }, + "Type": "AWS::Logs::LogGroup", + }, + "SoupMessageReceivesMetric": { + "Properties": { + "FilterPattern": "{ $.receives = * }", + "LogGroupName": { + "Ref": "SoupLogGroup", + }, + "MetricTransformations": [ { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], + "MetricName": { + "Fn::Join": [ + "", + [ + "SoupMessageReceives-", + { + "Ref": "AWS::StackName", }, - }, + ], ], }, - "PolicyName": "CustomcfnScalingLambdaLogs", + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": "$.receives", }, ], }, - "Type": "AWS::IAM::Role", + "Type": "AWS::Logs::MetricFilter", }, - "SoupLambdaTotalMessagesRole": { + "SoupMetricSchedulePermission": { "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SoupTotalMessagesLambda", + "Arn", ], }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - { - "Action": [ - "cloudwatch:PutMetricData", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "sqs:GetQueueAttributes", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "SoupQueue", - "Arn", - ], - }, - }, - ], - }, - "PolicyName": "LambdaTotalMessagesMetric", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SoupLogGroup": { - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "-", - [ - { - "Ref": "AWS::StackName", - }, - { - "Ref": "AWS::Region", - }, - "soup", - ], - ], - }, - "RetentionInDays": 14, - }, - "Type": "AWS::Logs::LogGroup", - }, - "SoupMessageReceivesMetric": { - "Properties": { - "FilterPattern": "{ $.receives = * }", - "LogGroupName": { - "Ref": "SoupLogGroup", - }, - "MetricTransformations": [ - { - "MetricName": { - "Fn::Join": [ - "", - [ - "SoupMessageReceives-", - { - "Ref": "AWS::StackName", - }, - ], - ], - }, - "MetricNamespace": "Mapbox/ecs-watchbot", - "MetricValue": "$.receives", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "SoupMetricSchedulePermission": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "SoupTotalMessagesLambda", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "SoupTotalMessagesSchedule", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "SoupNotificationTopic": { - "Description": "Subscribe to this topic to receive emails when tasks fail or retry", - "Properties": { - "Subscription": [ + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "SoupTotalMessagesSchedule", + "Arn", + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "SoupNotificationTopic": { + "Description": "Subscribe to this topic to receive emails when tasks fail or retry", + "Properties": { + "Subscription": [ { "Endpoint": "hello@mapbox.pagerduty.com", "Protocol": "email", @@ -3669,7 +3440,7 @@ exports[`[template]: all-properties-low-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#queuesize", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", "AlarmName": { "Fn::Join": [ "-", @@ -3743,6 +3514,7 @@ exports[`[template]: all-properties-low-CPU 1`] = ` "Principal": { "Service": [ "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", ], }, }, @@ -3816,6 +3588,60 @@ exports[`[template]: all-properties-low-CPU 1`] = ` ], }, }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "SoupQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, { "PolicyDocument": { "Statement": [ @@ -3989,7 +3815,7 @@ exports[`[template]: all-properties-low-CPU 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "SoupLambdaScalingRole", + "SoupRole", "Arn", ], }, @@ -4438,7 +4264,7 @@ exports[`[template]: all-properties-low-CPU 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "SoupLambdaTotalMessagesRole", + "SoupRole", "Arn", ], }, @@ -4509,7 +4335,7 @@ exports[`[template]: all-properties-low-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#workererrors", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", "AlarmName": { "Fn::Join": [ "-", @@ -4665,7 +4491,7 @@ exports[`[template]: all-properties-no-CPU 1`] = ` }, }, "Metadata": { - "EcsWatchbotVersion": "9.0.1", + "EcsWatchbotVersion": "9.1.0", }, "Outputs": { "ClusterArn": { @@ -4703,7 +4529,7 @@ exports[`[template]: all-properties-no-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#memoryutilization", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", "AlarmName": { "Fn::Join": [ "-", @@ -4863,119 +4689,6 @@ exports[`[template]: all-properties-no-CPU 1`] = ` }, "Type": "AWS::SQS::Queue", }, - "SoupLambdaScalingRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - ], - }, - "PolicyName": "CustomcfnScalingLambdaLogs", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SoupLambdaTotalMessagesRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - { - "Action": [ - "cloudwatch:PutMetricData", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "sqs:GetQueueAttributes", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "SoupQueue", - "Arn", - ], - }, - }, - ], - }, - "PolicyName": "LambdaTotalMessagesMetric", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, "SoupLogGroup": { "Properties": { "LogGroupName": { @@ -5201,7 +4914,7 @@ exports[`[template]: all-properties-no-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#queuesize", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", "AlarmName": { "Fn::Join": [ "-", @@ -5275,6 +4988,7 @@ exports[`[template]: all-properties-no-CPU 1`] = ` "Principal": { "Service": [ "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", ], }, }, @@ -5348,6 +5062,60 @@ exports[`[template]: all-properties-no-CPU 1`] = ` ], }, }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "SoupQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, { "PolicyDocument": { "Statement": [ @@ -5521,7 +5289,7 @@ exports[`[template]: all-properties-no-CPU 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "SoupLambdaScalingRole", + "SoupRole", "Arn", ], }, @@ -5970,7 +5738,7 @@ exports[`[template]: all-properties-no-CPU 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "SoupLambdaTotalMessagesRole", + "SoupRole", "Arn", ], }, @@ -6041,7 +5809,7 @@ exports[`[template]: all-properties-no-CPU 1`] = ` "Ref": "SoupNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#workererrors", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", "AlarmName": { "Fn::Join": [ "-", @@ -6197,7 +5965,7 @@ exports[`[template]: defaults 1`] = ` }, }, "Metadata": { - "EcsWatchbotVersion": "9.0.1", + "EcsWatchbotVersion": "9.1.0", }, "Outputs": { "ClusterArn": { @@ -6235,7 +6003,7 @@ exports[`[template]: defaults 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#memoryutilization", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", "AlarmName": { "Fn::Join": [ "-", @@ -6334,179 +6102,66 @@ exports[`[template]: defaults 1`] = ` "Type": "AWS::CloudWatch::Dashboard", }, "WatchbotDeadLetterAlarm": { - "Properties": { - "AlarmActions": [ - { - "Ref": "WatchbotNotificationTopic", - }, - ], - "AlarmDescription": "Provides notification when messages are visible in the dead letter queue", - "AlarmName": { - "Fn::Join": [ - "-", - [ - { - "Ref": "AWS::StackName", - }, - "Watchbot-dead-letter", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "Dimensions": [ - { - "Name": "QueueName", - "Value": { - "Fn::GetAtt": [ - "WatchbotDeadLetterQueue", - "QueueName", - ], - }, - }, - ], - "EvaluationPeriods": 1, - "MetricName": "ApproximateNumberOfMessagesVisible", - "Namespace": "AWS/SQS", - "Period": "60", - "Statistic": "Minimum", - "Threshold": 1, - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "WatchbotDeadLetterQueue": { - "Description": "List of messages that failed to process 14 times", - "Properties": { - "MessageRetentionPeriod": 1209600, - "QueueName": { - "Fn::Join": [ - "", - [ - { - "Ref": "AWS::StackName", - }, - "-", - "WatchbotDeadLetterQueue", - ], - ], - }, - }, - "Type": "AWS::SQS::Queue", - }, - "WatchbotLambdaScalingRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - ], - }, - "PolicyName": "CustomcfnScalingLambdaLogs", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "WatchbotLambdaTotalMessagesRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - { - "Action": [ - "cloudwatch:PutMetricData", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "sqs:GetQueueAttributes", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "WatchbotQueue", - "Arn", - ], - }, - }, + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "Provides notification when messages are visible in the dead letter queue", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-dead-letter", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotDeadLetterQueue", + "QueueName", ], }, - "PolicyName": "LambdaTotalMessagesMetric", }, ], + "EvaluationPeriods": 1, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": "60", + "Statistic": "Minimum", + "Threshold": 1, }, - "Type": "AWS::IAM::Role", + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotDeadLetterQueue": { + "Description": "List of messages that failed to process 14 times", + "Properties": { + "MessageRetentionPeriod": 1209600, + "QueueName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-", + "WatchbotDeadLetterQueue", + ], + ], + }, + }, + "Type": "AWS::SQS::Queue", }, "WatchbotLogGroup": { "Properties": { @@ -6659,7 +6314,7 @@ exports[`[template]: defaults 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#queuesize", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", "AlarmName": { "Fn::Join": [ "-", @@ -6733,6 +6388,7 @@ exports[`[template]: defaults 1`] = ` "Principal": { "Service": [ "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", ], }, }, @@ -6806,6 +6462,60 @@ exports[`[template]: defaults 1`] = ` ], }, }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, ], }, "Type": "AWS::IAM::Role", @@ -6957,7 +6667,7 @@ exports[`[template]: defaults 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "WatchbotLambdaScalingRole", + "WatchbotRole", "Arn", ], }, @@ -7384,7 +7094,7 @@ exports[`[template]: defaults 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "WatchbotLambdaTotalMessagesRole", + "WatchbotRole", "Arn", ], }, @@ -7455,7 +7165,7 @@ exports[`[template]: defaults 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#workererrors", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", "AlarmName": { "Fn::Join": [ "-", @@ -7735,7 +7445,7 @@ exports[`[template]: fifo 1`] = ` }, }, "Metadata": { - "EcsWatchbotVersion": "9.0.1", + "EcsWatchbotVersion": "9.1.0", }, "Outputs": { "ClusterArn": { @@ -7773,7 +7483,7 @@ exports[`[template]: fifo 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#memoryutilization", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", "AlarmName": { "Fn::Join": [ "-", @@ -7892,162 +7602,49 @@ exports[`[template]: fifo 1`] = ` }, ], ], - }, - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "Dimensions": [ - { - "Name": "QueueName", - "Value": { - "Fn::GetAtt": [ - "WatchbotDeadLetterQueue", - "QueueName", - ], - }, - }, - ], - "EvaluationPeriods": 1, - "MetricName": "ApproximateNumberOfMessagesVisible", - "Namespace": "AWS/SQS", - "Period": "60", - "Statistic": "Minimum", - "Threshold": 1, - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "WatchbotDeadLetterQueue": { - "Description": "List of messages that failed to process 14 times", - "Properties": { - "ContentBasedDeduplication": true, - "FifoQueue": true, - "MessageRetentionPeriod": 1209600, - "QueueName": { - "Fn::Join": [ - "", - [ - { - "Ref": "AWS::StackName", - }, - "-", - "WatchbotDeadLetterQueue", - ".fifo", - ], - ], - }, - }, - "Type": "AWS::SQS::Queue", - }, - "WatchbotLambdaScalingRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - ], - }, - "PolicyName": "CustomcfnScalingLambdaLogs", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "WatchbotLambdaTotalMessagesRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - { - "Action": [ - "cloudwatch:PutMetricData", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "sqs:GetQueueAttributes", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "WatchbotQueue", - "Arn", - ], - }, - }, + }, + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotDeadLetterQueue", + "QueueName", ], }, - "PolicyName": "LambdaTotalMessagesMetric", }, ], + "EvaluationPeriods": 1, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": "60", + "Statistic": "Minimum", + "Threshold": 1, }, - "Type": "AWS::IAM::Role", + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotDeadLetterQueue": { + "Description": "List of messages that failed to process 14 times", + "Properties": { + "ContentBasedDeduplication": true, + "FifoQueue": true, + "MessageRetentionPeriod": 1209600, + "QueueName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-", + "WatchbotDeadLetterQueue", + ".fifo", + ], + ], + }, + }, + "Type": "AWS::SQS::Queue", }, "WatchbotLogGroup": { "Properties": { @@ -8164,7 +7761,7 @@ exports[`[template]: fifo 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#queuesize", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", "AlarmName": { "Fn::Join": [ "-", @@ -8238,6 +7835,7 @@ exports[`[template]: fifo 1`] = ` "Principal": { "Service": [ "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", ], }, }, @@ -8304,6 +7902,60 @@ exports[`[template]: fifo 1`] = ` ], }, }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, ], }, "Type": "AWS::IAM::Role", @@ -8455,7 +8107,7 @@ exports[`[template]: fifo 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "WatchbotLambdaScalingRole", + "WatchbotRole", "Arn", ], }, @@ -8864,7 +8516,7 @@ exports[`[template]: fifo 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "WatchbotLambdaTotalMessagesRole", + "WatchbotRole", "Arn", ], }, @@ -8935,7 +8587,7 @@ exports[`[template]: fifo 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#workererrors", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", "AlarmName": { "Fn::Join": [ "-", @@ -9091,7 +8743,7 @@ exports[`[template]: fifoMaxSize 1`] = ` }, }, "Metadata": { - "EcsWatchbotVersion": "9.0.1", + "EcsWatchbotVersion": "9.1.0", }, "Outputs": { "ClusterArn": { @@ -9129,7 +8781,7 @@ exports[`[template]: fifoMaxSize 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#memoryutilization", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", "AlarmName": { "Fn::Join": [ "-", @@ -9292,119 +8944,6 @@ exports[`[template]: fifoMaxSize 1`] = ` }, "Type": "AWS::SQS::Queue", }, - "WatchbotLambdaScalingRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - ], - }, - "PolicyName": "CustomcfnScalingLambdaLogs", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "WatchbotLambdaTotalMessagesRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com", - ], - }, - }, - ], - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:*", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:*:*", - ], - ], - }, - }, - { - "Action": [ - "cloudwatch:PutMetricData", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "sqs:GetQueueAttributes", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "WatchbotQueue", - "Arn", - ], - }, - }, - ], - }, - "PolicyName": "LambdaTotalMessagesMetric", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, "WatchbotLogGroup": { "Properties": { "LogGroupName": { @@ -9520,7 +9059,7 @@ exports[`[template]: fifoMaxSize 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#queuesize", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", "AlarmName": { "Fn::Join": [ "-", @@ -9594,6 +9133,7 @@ exports[`[template]: fifoMaxSize 1`] = ` "Principal": { "Service": [ "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", ], }, }, @@ -9660,6 +9200,60 @@ exports[`[template]: fifoMaxSize 1`] = ` ], }, }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, ], }, "Type": "AWS::IAM::Role", @@ -9811,7 +9405,7 @@ exports[`[template]: fifoMaxSize 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "WatchbotLambdaScalingRole", + "WatchbotRole", "Arn", ], }, @@ -10220,7 +9814,7 @@ exports[`[template]: fifoMaxSize 1`] = ` "Handler": "index.handler", "Role": { "Fn::GetAtt": [ - "WatchbotLambdaTotalMessagesRole", + "WatchbotRole", "Arn", ], }, @@ -10291,7 +9885,7 @@ exports[`[template]: fifoMaxSize 1`] = ` "Ref": "WatchbotNotificationTopic", }, ], - "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.0.1/docs/alarms.md#workererrors", + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", "AlarmName": { "Fn::Join": [ "-", From e32bec64ff8a5bbe2f5f12c678c0f13911c21137 Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 13 Jun 2024 08:41:31 -0700 Subject: [PATCH 14/14] add some tests --- test/__snapshots__/template.spec.js.snap | 2622 ++++++++++++++++++++++ test/template.spec.js | 24 + 2 files changed, 2646 insertions(+) diff --git a/test/__snapshots__/template.spec.js.snap b/test/__snapshots__/template.spec.js.snap index b9c9c341..9b66a123 100644 --- a/test/__snapshots__/template.spec.js.snap +++ b/test/__snapshots__/template.spec.js.snap @@ -5876,6 +5876,2628 @@ exports[`[template]: all-properties-no-CPU 1`] = ` } `; +exports[`[template]: autoscalingRoleArn 1`] = ` +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Conditions": { + "InChina": { + "Fn::Equals": [ + { + "Fn::Select": [ + "0", + { + "Fn::Split": [ + "-", + { + "Ref": "AWS::Region", + }, + ], + }, + ], + }, + "cn", + ], + }, + "WatchbotCapacityIsEC2": { + "Fn::Equals": [ + "FARGATE", + "EC2", + ], + }, + "WatchbotCapacityIsFargate": { + "Fn::Equals": [ + "FARGATE", + "FARGATE", + ], + }, + "WatchbotCapacityIsFargateSpot": { + "Fn::Equals": [ + "FARGATE", + "FARGATE_SPOT", + ], + }, + "WatchbotCapacityIsNotEC2": { + "Fn::Not": [ + { + "Fn::Equals": [ + "FARGATE", + "EC2", + ], + }, + ], + }, + }, + "Mappings": { + "EcrRegion": { + "ap-northeast-1": { + "Region": "us-west-2", + }, + "ap-southeast-1": { + "Region": "us-west-2", + }, + "ap-southeast-2": { + "Region": "us-west-2", + }, + "cn-north-1": { + "Region": "cn-north-1", + }, + "cn-northwest-1": { + "Region": "cn-northwest-1", + }, + "eu-central-1": { + "Region": "eu-west-1", + }, + "eu-west-1": { + "Region": "eu-west-1", + }, + "us-east-1": { + "Region": "us-east-1", + }, + "us-east-2": { + "Region": "us-east-1", + }, + "us-west-1": { + "Region": "us-west-2", + }, + "us-west-2": { + "Region": "us-west-2", + }, + }, + }, + "Metadata": { + "EcsWatchbotVersion": "9.1.0", + }, + "Outputs": { + "ClusterArn": { + "Description": "Service cluster ARN", + "Value": "processing", + }, + "WatchbotDeadLetterQueueUrl": { + "Description": "The URL for the dead letter queue", + "Value": { + "Ref": "WatchbotDeadLetterQueue", + }, + }, + "WatchbotLogGroup": { + "Description": "The ARN of Watchbot's log group", + "Value": { + "Fn::GetAtt": [ + "WatchbotLogGroup", + "Arn", + ], + }, + }, + "WatchbotQueueUrl": { + "Description": "The URL for the primary work queue", + "Value": { + "Ref": "WatchbotQueue", + }, + }, + }, + "Parameters": {}, + "Resources": { + "WatchbotAlarmMemoryUtilization": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "WatchbotMemoryUtilization", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "ClusterName", + "Value": "processing", + }, + { + "Name": "ServiceName", + "Value": { + "Fn::GetAtt": [ + "WatchbotService", + "Name", + ], + }, + }, + ], + "EvaluationPeriods": 10, + "MetricName": "MemoryUtilization", + "Namespace": "AWS/ECS", + "Period": 60, + "Statistic": "Average", + "Threshold": 100, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotCustomScalingResource": { + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "WatchbotScalingLambda", + "Arn", + ], + }, + "maxSize": 1, + }, + "Type": "AWS::CloudFormation::CustomResource", + }, + "WatchbotDashboard": { + "Properties": { + "DashboardBody": { + "Fn::Sub": [ + "{"widgets":[{"type":"metric","x":0,"y":0,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Visible and NotVisible Messages","metrics":[["AWS/SQS","ApproximateNumberOfMessagesNotVisible","QueueName","\${WatchbotQueue}",{"period":60}],["AWS/SQS","ApproximateNumberOfMessagesVisible","QueueName","\${WatchbotQueue}",{"period":60}]],"stat":"Sum","region":"\${AWS::Region}","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":0,"y":12,"width":12,"height":6,"properties":{"metrics":[["AWS/SQS","ApproximateAgeOfOldestMessage","QueueName","\${WatchbotQueue}",{"stat":"Maximum"}],["...",{"stat":"p99"}],["..."],["...",{"stat":"p50"}]],"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Age of Oldest Message (sec)","stat":"Average","region":"\${AWS::Region}","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":0,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Worker Duration (msec)","metrics":[["Mapbox/ecs-watchbot","\${Prefix}WorkerDuration-\${AWS::StackName}",{"stat":"Maximum"}],["...",{"stat":"p99"}],["..."],["...",{"stat":"p50"}]],"region":"\${AWS::Region}","stat":"Average","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":0,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Response Duration (msec)","metrics":[["Mapbox/ecs-watchbot","\${Prefix}ResponseDuration-\${AWS::StackName}",{"stat":"Maximum"}],["...",{"stat":"p99"}],["..."],["...",{"stat":"p50"}]],"region":"\${AWS::Region}","stat":"Average","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":0,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Deleted messages","metrics":[["AWS/SQS","NumberOfMessagesDeleted","QueueName","\${WatchbotQueue}",{"period":60}]],"stat":"Sum","region":"\${AWS::Region}","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":12,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotService: Task Counts","metrics":[["ECS/ContainerInsights","RunningTaskCount","ClusterName","\${Cluster}","ServiceName","\${WatchbotService}",{"period":60}],[".","DesiredTaskCount",".",".",".",".",{"period":60}],[".","PendingTaskCount",".",".",".",".",{"period":60}]],"region":"\${AWS::Region}","period":60}},{"type":"metric","x":12,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"Concurrency vs Throughput","metrics":[["ECS/ContainerInsights","RunningTaskCount","ClusterName","\${Cluster}","ServiceName","\${WatchbotService}",{"period":60,"yAxis":"right"}],[".","DesiredTaskCount",".",".",".",".",{"period":60,"yAxis":"right"}],["AWS/SQS","ApproximateNumberOfMessagesVisible","QueueName","\${WatchbotQueue}",{"period":60,"stat":"Sum","yAxis":"left"}]],"region":"\${AWS::Region}","period":60,"yAxis":{"right":{"min":0}}}},{"type":"metric","x":0,"y":18,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotService: CPUUtilization, MemoryUtilization","metrics":[["AWS/ECS","CPUUtilization","ServiceName","\${WatchbotService}","ClusterName","\${Cluster}",{"period":60}],[".","MemoryUtilization",".",".",".",".",{"period":60}]],"region":"\${AWS::Region}","period":300}},{"type":"metric","x":12,"y":18,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotDeadLetterQueue: Visible and NotVisible Messages","metrics":[["AWS/SQS","ApproximateNumberOfMessagesNotVisible","QueueName","\${WatchbotDeadLetterQueue}",{"period":60}],["AWS/SQS","ApproximateNumberOfMessagesVisible","QueueName","\${WatchbotDeadLetterQueue}",{"period":60}]],"stat":"Sum","region":"\${AWS::Region}","period":60,"yAxis":{"left":{"min":0}}}}]}", + { + "Cluster": "processing", + "Prefix": "Watchbot", + "WatchbotDeadLetterQueue": { + "Fn::GetAtt": [ + "WatchbotDeadLetterQueue", + "QueueName", + ], + }, + "WatchbotQueue": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + "WatchbotService": { + "Fn::GetAtt": [ + "WatchbotService", + "Name", + ], + }, + }, + ], + }, + "DashboardName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + }, + "Type": "AWS::CloudWatch::Dashboard", + }, + "WatchbotDeadLetterAlarm": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "Provides notification when messages are visible in the dead letter queue", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-dead-letter", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotDeadLetterQueue", + "QueueName", + ], + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": "60", + "Statistic": "Minimum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotDeadLetterQueue": { + "Description": "List of messages that failed to process 14 times", + "Properties": { + "MessageRetentionPeriod": 1209600, + "QueueName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-", + "WatchbotDeadLetterQueue", + ], + ], + }, + }, + "Type": "AWS::SQS::Queue", + }, + "WatchbotLogGroup": { + "Properties": { + "LogGroupName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + { + "Ref": "AWS::Region", + }, + "watchbot", + ], + ], + }, + "RetentionInDays": 14, + }, + "Type": "AWS::Logs::LogGroup", + }, + "WatchbotMessageReceivesMetric": { + "Properties": { + "FilterPattern": "{ $.receives = * }", + "LogGroupName": { + "Ref": "WatchbotLogGroup", + }, + "MetricTransformations": [ + { + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotMessageReceives-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": "$.receives", + }, + ], + }, + "Type": "AWS::Logs::MetricFilter", + }, + "WatchbotMetricSchedulePermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "WatchbotTotalMessagesLambda", + "Arn", + ], + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "WatchbotTotalMessagesSchedule", + "Arn", + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "WatchbotNotificationTopic": { + "Description": "Subscribe to this topic to receive emails when tasks fail or retry", + "Properties": { + "Subscription": [ + { + "Endpoint": "hello@mapbox.pagerduty.com", + "Protocol": "email", + }, + ], + }, + "Type": "AWS::SNS::Topic", + }, + "WatchbotQueue": { + "Properties": { + "MessageRetentionPeriod": 1209600, + "QueueName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-", + "WatchbotQueue", + ], + ], + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "WatchbotDeadLetterQueue", + "Arn", + ], + }, + "maxReceiveCount": 10, + }, + "VisibilityTimeout": 180, + }, + "Type": "AWS::SQS::Queue", + }, + "WatchbotQueuePolicy": { + "Properties": { + "PolicyDocument": { + "Id": "WatchbotWatchbotQueue", + "Statement": [ + { + "Action": [ + "sqs:SendMessage", + ], + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "WatchbotTopic", + }, + }, + }, + "Effect": "Allow", + "Principal": { + "AWS": "*", + }, + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + "Sid": "SendSomeMessages", + }, + ], + "Version": "2008-10-17", + }, + "Queues": [ + { + "Ref": "WatchbotQueue", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "WatchbotQueueSizeAlarm": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-queue-size", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + }, + ], + "EvaluationPeriods": 24, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": "300", + "Statistic": "Average", + "Threshold": 40, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotResponseDurationMetric": { + "Properties": { + "FilterPattern": "{ $.response_duration = * }", + "LogGroupName": { + "Ref": "WatchbotLogGroup", + }, + "MetricTransformations": [ + { + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotResponseDuration-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": "$.response_duration", + }, + ], + }, + "Type": "AWS::Logs::MetricFilter", + }, + "WatchbotRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole", + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", + ], + }, + }, + ], + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:ChangeMessageVisibility", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotLogGroup", + "Arn", + ], + }, + }, + { + "Fn::If": [ + "InChina", + { + "Ref": "AWS::NoValue", + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::ImportValue": "cloudformation-kms-production", + }, + }, + ], + }, + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "WatchbotTopic", + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-default-worker", + ], + ], + }, + }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "WatchbotScaleDown": { + "Properties": { + "PolicyName": { + "Fn::Sub": "Watchbot\${AWS::StackName}-scale-down", + }, + "PolicyType": "StepScaling", + "ScalingTargetId": { + "Ref": "WatchbotScalingTarget", + }, + "StepScalingPolicyConfiguration": { + "AdjustmentType": "PercentChangeInCapacity", + "Cooldown": 300, + "MetricAggregationType": "Average", + "StepAdjustments": [ + { + "MetricIntervalUpperBound": 0, + "ScalingAdjustment": -100, + }, + ], + }, + }, + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + }, + "WatchbotScaleDownTrigger": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotScaleDown", + }, + ], + "AlarmDescription": "Scale down due to lack of in-flight messages in queue", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-scale-down", + ], + ], + }, + "ComparisonOperator": "LessThanThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "TotalMessages", + "Namespace": "Mapbox/ecs-watchbot", + "Period": 600, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotScaleUp": { + "Properties": { + "PolicyName": { + "Fn::Sub": "\${AWS::StackName}-scale-up", + }, + "PolicyType": "StepScaling", + "ScalingTargetId": { + "Ref": "WatchbotScalingTarget", + }, + "StepScalingPolicyConfiguration": { + "AdjustmentType": "ChangeInCapacity", + "Cooldown": 300, + "MetricAggregationType": "Average", + "StepAdjustments": [ + { + "MetricIntervalLowerBound": 0, + "ScalingAdjustment": { + "Fn::GetAtt": [ + "WatchbotCustomScalingResource", + "ScalingAdjustment", + ], + }, + }, + ], + }, + }, + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + }, + "WatchbotScaleUpTrigger": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotScaleUp", + }, + ], + "AlarmDescription": "Scale up due to visible messages in queue", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-scale-up", + ], + ], + }, + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 0, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotScalingLambda": { + "Properties": { + "Code": { + "ZipFile": { + "Fn::Sub": " + const response = require('./cfn-response'); + exports.handler = function(event,context){ + const result = Math.round(Math.max(Math.min(parseInt(event.ResourceProperties.maxSize) / 10, 100), 1)); + response.send(event, context, response.SUCCESS, { ScalingAdjustment: result }); + } + ", + }, + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "WatchbotRole", + "Arn", + ], + }, + "Runtime": "nodejs18.x", + }, + "Type": "AWS::Lambda::Function", + }, + "WatchbotScalingTarget": { + "Properties": { + "MaxCapacity": 1, + "MinCapacity": 0, + "ResourceId": { + "Fn::Join": [ + "", + [ + "service/", + "processing", + "/", + { + "Fn::GetAtt": [ + "WatchbotService", + "Name", + ], + }, + ], + ], + }, + "RoleARN": "arn:autoscaling:role/abcd", + "ScalableDimension": "ecs:service:DesiredCount", + "ServiceNamespace": "ecs", + }, + "Type": "AWS::ApplicationAutoScaling::ScalableTarget", + }, + "WatchbotService": { + "Properties": { + "CapacityProviderStrategy": { + "Fn::If": [ + "WatchbotCapacityIsFargateSpot", + [ + { + "CapacityProvider": "FARGATE_SPOT", + "Weight": 1, + }, + ], + { + "Ref": "AWS::NoValue", + }, + ], + }, + "Cluster": "processing", + "LaunchType": { + "Fn::If": [ + "WatchbotCapacityIsFargate", + "FARGATE", + { + "Ref": "AWS::NoValue", + }, + ], + }, + "NetworkConfiguration": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": undefined, + "Subnets": undefined, + }, + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "PropagateTags": "TASK_DEFINITION", + "TaskDefinition": { + "Ref": "WatchbotTask", + }, + }, + "Type": "AWS::ECS::Service", + }, + "WatchbotTask": { + "Properties": { + "ContainerDefinitions": [ + { + "Command": [ + "watchbot", + "listen", + "echo hello world", + ], + "Cpu": { + "Fn::If": [ + "WatchbotCapacityIsEC2", + { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + 256, + 128, + ], + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "Environment": [ + { + "Name": "WorkTopic", + "Value": { + "Ref": "WatchbotTopic", + }, + }, + { + "Name": "QueueUrl", + "Value": { + "Ref": "WatchbotQueue", + }, + }, + { + "Name": "LogGroup", + "Value": { + "Ref": "WatchbotLogGroup", + }, + }, + { + "Name": "writableFilesystem", + "Value": false, + }, + { + "Name": "maxJobDuration", + "Value": 0, + }, + { + "Name": "Volumes", + "Value": "/tmp", + }, + { + "Name": "Fifo", + "Value": "false", + }, + { + "Name": "structuredLogging", + "Value": "false", + }, + ], + "Image": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId", + }, + ".dkr.ecr.", + { + "Fn::FindInMap": [ + "EcrRegion", + { + "Ref": "AWS::Region", + }, + "Region", + ], + }, + ".", + { + "Ref": "AWS::URLSuffix", + }, + "/", + "example", + ":", + "1", + ], + ], + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "WatchbotLogGroup", + }, + "awslogs-region": { + "Ref": "AWS::Region", + }, + "awslogs-stream-prefix": "1", + }, + }, + "Memory": { + "Fn::If": [ + "WatchbotCapacityIsEC2", + { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + 512, + { + "Ref": "AWS::NoValue", + }, + ], + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "MemoryReservation": { + "Ref": "AWS::NoValue", + }, + "MountPoints": [ + { + "ContainerPath": "/tmp", + "SourceVolume": "tmp", + }, + ], + "Name": { + "Fn::Join": [ + "-", + [ + "Watchbot", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "Privileged": false, + "ReadonlyRootFilesystem": { + "Fn::If": [ + "WatchbotCapacityIsEC2", + true, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "Ulimits": [ + { + "HardLimit": 10240, + "Name": "nofile", + "SoftLimit": 10240, + }, + ], + }, + ], + "Cpu": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + 256, + 128, + ], + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "ExecutionRoleArn": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + { + "Fn::Sub": "arn:\${AWS::Partition}:iam::\${AWS::AccountId}:role/ecsTaskExecutionRole", + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "Family": "example", + "Memory": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + 512, + { + "Ref": "AWS::NoValue", + }, + ], + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "NetworkMode": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + "awsvpc", + { + "Ref": "AWS::NoValue", + }, + ], + }, + "RequiresCompatibilities": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + [ + "FARGATE", + ], + { + "Ref": "AWS::NoValue", + }, + ], + }, + "TaskRoleArn": { + "Ref": "WatchbotRole", + }, + "Volumes": [ + { + "Name": "tmp", + }, + ], + }, + "Type": "AWS::ECS::TaskDefinition", + }, + "WatchbotTopic": { + "Properties": { + "Subscription": [ + { + "Endpoint": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + "Protocol": "sqs", + }, + ], + }, + "Type": "AWS::SNS::Topic", + }, + "WatchbotTotalMessagesLambda": { + "Properties": { + "Code": { + "ZipFile": { + "Fn::Sub": [ + " + const { SQS } = require('@aws-sdk/client-sqs'); + const { CloudWatch } = require('@aws-sdk/client-cloudwatch'); + exports.handler = function(event, context, callback) { + const sqs = new SQS({ region: process.env.AWS_DEFAULT_REGION }); + const cw = new CloudWatch({ region: process.env.AWS_DEFAULT_REGION }); + + return sqs.getQueueAttributes({ + QueueUrl: '\${QueueUrl}', + AttributeNames: ['ApproximateNumberOfMessagesNotVisible', 'ApproximateNumberOfMessages'] + }) + .then((attrs) => { + return cw.putMetricData({ + Namespace: 'Mapbox/ecs-watchbot', + MetricData: [{ + MetricName: 'TotalMessages', + Dimensions: [{ Name: 'QueueName', Value: '\${QueueName}' }], + Value: Number(attrs.Attributes.ApproximateNumberOfMessagesNotVisible) + + Number(attrs.Attributes.ApproximateNumberOfMessages) + }] + }) + }) + .then((metric) => callback(null, metric)) + .catch((err) => callback(err)); + } + ", + { + "QueueName": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + "QueueUrl": { + "Ref": "WatchbotQueue", + }, + }, + ], + }, + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "WatchbotRole", + "Arn", + ], + }, + "Runtime": "nodejs18.x", + "Timeout": 60, + }, + "Type": "AWS::Lambda::Function", + }, + "WatchbotTotalMessagesSchedule": { + "Properties": { + "Description": "Update TotalMessages metric every minute", + "Name": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-total-messages", + ], + ], + }, + "ScheduleExpression": "cron(0/1 * * * ? *)", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "WatchbotTotalMessagesLambda", + "Arn", + ], + }, + "Id": "WatchbotTotalMessagesLambda", + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "WatchbotWorkerDurationMetric": { + "Properties": { + "FilterPattern": "{ $.duration = * }", + "LogGroupName": { + "Ref": "WatchbotLogGroup", + }, + "MetricTransformations": [ + { + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotWorkerDuration-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": "$.duration", + }, + ], + }, + "Type": "AWS::Logs::MetricFilter", + }, + "WatchbotWorkerErrorsAlarm": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-worker-errors", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanThreshold", + "EvaluationPeriods": 1, + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotWorkerErrors-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "Namespace": "Mapbox/ecs-watchbot", + "Period": "60", + "Statistic": "Sum", + "Threshold": 10, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotWorkerErrorsMetric": { + "Properties": { + "FilterPattern": ""[failure]"", + "LogGroupName": { + "Ref": "WatchbotLogGroup", + }, + "MetricTransformations": [ + { + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotWorkerErrors-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": 1, + }, + ], + }, + "Type": "AWS::Logs::MetricFilter", + }, + }, + "Rules": {}, + "Transform": undefined, +} +`; + +exports[`[template]: autoscalingRoleArnImport 1`] = ` +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Conditions": { + "InChina": { + "Fn::Equals": [ + { + "Fn::Select": [ + "0", + { + "Fn::Split": [ + "-", + { + "Ref": "AWS::Region", + }, + ], + }, + ], + }, + "cn", + ], + }, + "WatchbotCapacityIsEC2": { + "Fn::Equals": [ + "FARGATE", + "EC2", + ], + }, + "WatchbotCapacityIsFargate": { + "Fn::Equals": [ + "FARGATE", + "FARGATE", + ], + }, + "WatchbotCapacityIsFargateSpot": { + "Fn::Equals": [ + "FARGATE", + "FARGATE_SPOT", + ], + }, + "WatchbotCapacityIsNotEC2": { + "Fn::Not": [ + { + "Fn::Equals": [ + "FARGATE", + "EC2", + ], + }, + ], + }, + }, + "Mappings": { + "EcrRegion": { + "ap-northeast-1": { + "Region": "us-west-2", + }, + "ap-southeast-1": { + "Region": "us-west-2", + }, + "ap-southeast-2": { + "Region": "us-west-2", + }, + "cn-north-1": { + "Region": "cn-north-1", + }, + "cn-northwest-1": { + "Region": "cn-northwest-1", + }, + "eu-central-1": { + "Region": "eu-west-1", + }, + "eu-west-1": { + "Region": "eu-west-1", + }, + "us-east-1": { + "Region": "us-east-1", + }, + "us-east-2": { + "Region": "us-east-1", + }, + "us-west-1": { + "Region": "us-west-2", + }, + "us-west-2": { + "Region": "us-west-2", + }, + }, + }, + "Metadata": { + "EcsWatchbotVersion": "9.1.0", + }, + "Outputs": { + "ClusterArn": { + "Description": "Service cluster ARN", + "Value": "processing", + }, + "WatchbotDeadLetterQueueUrl": { + "Description": "The URL for the dead letter queue", + "Value": { + "Ref": "WatchbotDeadLetterQueue", + }, + }, + "WatchbotLogGroup": { + "Description": "The ARN of Watchbot's log group", + "Value": { + "Fn::GetAtt": [ + "WatchbotLogGroup", + "Arn", + ], + }, + }, + "WatchbotQueueUrl": { + "Description": "The URL for the primary work queue", + "Value": { + "Ref": "WatchbotQueue", + }, + }, + }, + "Parameters": {}, + "Resources": { + "WatchbotAlarmMemoryUtilization": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#memoryutilization", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "WatchbotMemoryUtilization", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "ClusterName", + "Value": "processing", + }, + { + "Name": "ServiceName", + "Value": { + "Fn::GetAtt": [ + "WatchbotService", + "Name", + ], + }, + }, + ], + "EvaluationPeriods": 10, + "MetricName": "MemoryUtilization", + "Namespace": "AWS/ECS", + "Period": 60, + "Statistic": "Average", + "Threshold": 100, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotCustomScalingResource": { + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "WatchbotScalingLambda", + "Arn", + ], + }, + "maxSize": 1, + }, + "Type": "AWS::CloudFormation::CustomResource", + }, + "WatchbotDashboard": { + "Properties": { + "DashboardBody": { + "Fn::Sub": [ + "{"widgets":[{"type":"metric","x":0,"y":0,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Visible and NotVisible Messages","metrics":[["AWS/SQS","ApproximateNumberOfMessagesNotVisible","QueueName","\${WatchbotQueue}",{"period":60}],["AWS/SQS","ApproximateNumberOfMessagesVisible","QueueName","\${WatchbotQueue}",{"period":60}]],"stat":"Sum","region":"\${AWS::Region}","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":0,"y":12,"width":12,"height":6,"properties":{"metrics":[["AWS/SQS","ApproximateAgeOfOldestMessage","QueueName","\${WatchbotQueue}",{"stat":"Maximum"}],["...",{"stat":"p99"}],["..."],["...",{"stat":"p50"}]],"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Age of Oldest Message (sec)","stat":"Average","region":"\${AWS::Region}","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":0,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Worker Duration (msec)","metrics":[["Mapbox/ecs-watchbot","\${Prefix}WorkerDuration-\${AWS::StackName}",{"stat":"Maximum"}],["...",{"stat":"p99"}],["..."],["...",{"stat":"p50"}]],"region":"\${AWS::Region}","stat":"Average","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":0,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Response Duration (msec)","metrics":[["Mapbox/ecs-watchbot","\${Prefix}ResponseDuration-\${AWS::StackName}",{"stat":"Maximum"}],["...",{"stat":"p99"}],["..."],["...",{"stat":"p50"}]],"region":"\${AWS::Region}","stat":"Average","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":0,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotQueue: Deleted messages","metrics":[["AWS/SQS","NumberOfMessagesDeleted","QueueName","\${WatchbotQueue}",{"period":60}]],"stat":"Sum","region":"\${AWS::Region}","period":60,"yAxis":{"left":{"min":0}}}},{"type":"metric","x":12,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotService: Task Counts","metrics":[["ECS/ContainerInsights","RunningTaskCount","ClusterName","\${Cluster}","ServiceName","\${WatchbotService}",{"period":60}],[".","DesiredTaskCount",".",".",".",".",{"period":60}],[".","PendingTaskCount",".",".",".",".",{"period":60}]],"region":"\${AWS::Region}","period":60}},{"type":"metric","x":12,"y":12,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"Concurrency vs Throughput","metrics":[["ECS/ContainerInsights","RunningTaskCount","ClusterName","\${Cluster}","ServiceName","\${WatchbotService}",{"period":60,"yAxis":"right"}],[".","DesiredTaskCount",".",".",".",".",{"period":60,"yAxis":"right"}],["AWS/SQS","ApproximateNumberOfMessagesVisible","QueueName","\${WatchbotQueue}",{"period":60,"stat":"Sum","yAxis":"left"}]],"region":"\${AWS::Region}","period":60,"yAxis":{"right":{"min":0}}}},{"type":"metric","x":0,"y":18,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotService: CPUUtilization, MemoryUtilization","metrics":[["AWS/ECS","CPUUtilization","ServiceName","\${WatchbotService}","ClusterName","\${Cluster}",{"period":60}],[".","MemoryUtilization",".",".",".",".",{"period":60}]],"region":"\${AWS::Region}","period":300}},{"type":"metric","x":12,"y":18,"width":12,"height":6,"properties":{"view":"timeSeries","stacked":false,"title":"WatchbotDeadLetterQueue: Visible and NotVisible Messages","metrics":[["AWS/SQS","ApproximateNumberOfMessagesNotVisible","QueueName","\${WatchbotDeadLetterQueue}",{"period":60}],["AWS/SQS","ApproximateNumberOfMessagesVisible","QueueName","\${WatchbotDeadLetterQueue}",{"period":60}]],"stat":"Sum","region":"\${AWS::Region}","period":60,"yAxis":{"left":{"min":0}}}}]}", + { + "Cluster": "processing", + "Prefix": "Watchbot", + "WatchbotDeadLetterQueue": { + "Fn::GetAtt": [ + "WatchbotDeadLetterQueue", + "QueueName", + ], + }, + "WatchbotQueue": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + "WatchbotService": { + "Fn::GetAtt": [ + "WatchbotService", + "Name", + ], + }, + }, + ], + }, + "DashboardName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + }, + "Type": "AWS::CloudWatch::Dashboard", + }, + "WatchbotDeadLetterAlarm": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "Provides notification when messages are visible in the dead letter queue", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-dead-letter", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotDeadLetterQueue", + "QueueName", + ], + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": "60", + "Statistic": "Minimum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotDeadLetterQueue": { + "Description": "List of messages that failed to process 14 times", + "Properties": { + "MessageRetentionPeriod": 1209600, + "QueueName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-", + "WatchbotDeadLetterQueue", + ], + ], + }, + }, + "Type": "AWS::SQS::Queue", + }, + "WatchbotLogGroup": { + "Properties": { + "LogGroupName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + { + "Ref": "AWS::Region", + }, + "watchbot", + ], + ], + }, + "RetentionInDays": 14, + }, + "Type": "AWS::Logs::LogGroup", + }, + "WatchbotMessageReceivesMetric": { + "Properties": { + "FilterPattern": "{ $.receives = * }", + "LogGroupName": { + "Ref": "WatchbotLogGroup", + }, + "MetricTransformations": [ + { + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotMessageReceives-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": "$.receives", + }, + ], + }, + "Type": "AWS::Logs::MetricFilter", + }, + "WatchbotMetricSchedulePermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "WatchbotTotalMessagesLambda", + "Arn", + ], + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "WatchbotTotalMessagesSchedule", + "Arn", + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "WatchbotNotificationTopic": { + "Description": "Subscribe to this topic to receive emails when tasks fail or retry", + "Properties": { + "Subscription": [ + { + "Endpoint": "hello@mapbox.pagerduty.com", + "Protocol": "email", + }, + ], + }, + "Type": "AWS::SNS::Topic", + }, + "WatchbotQueue": { + "Properties": { + "MessageRetentionPeriod": 1209600, + "QueueName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-", + "WatchbotQueue", + ], + ], + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "WatchbotDeadLetterQueue", + "Arn", + ], + }, + "maxReceiveCount": 10, + }, + "VisibilityTimeout": 180, + }, + "Type": "AWS::SQS::Queue", + }, + "WatchbotQueuePolicy": { + "Properties": { + "PolicyDocument": { + "Id": "WatchbotWatchbotQueue", + "Statement": [ + { + "Action": [ + "sqs:SendMessage", + ], + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "WatchbotTopic", + }, + }, + }, + "Effect": "Allow", + "Principal": { + "AWS": "*", + }, + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + "Sid": "SendSomeMessages", + }, + ], + "Version": "2008-10-17", + }, + "Queues": [ + { + "Ref": "WatchbotQueue", + }, + ], + }, + "Type": "AWS::SQS::QueuePolicy", + }, + "WatchbotQueueSizeAlarm": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#queuesize", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-queue-size", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + }, + ], + "EvaluationPeriods": 24, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": "300", + "Statistic": "Average", + "Threshold": 40, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotResponseDurationMetric": { + "Properties": { + "FilterPattern": "{ $.response_duration = * }", + "LogGroupName": { + "Ref": "WatchbotLogGroup", + }, + "MetricTransformations": [ + { + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotResponseDuration-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": "$.response_duration", + }, + ], + }, + "Type": "AWS::Logs::MetricFilter", + }, + "WatchbotRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole", + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "ecs-tasks.amazonaws.com", + "lambda.amazonaws.com", + ], + }, + }, + ], + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:ChangeMessageVisibility", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotLogGroup", + "Arn", + ], + }, + }, + { + "Fn::If": [ + "InChina", + { + "Ref": "AWS::NoValue", + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::ImportValue": "cloudformation-kms-production", + }, + }, + ], + }, + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "WatchbotTopic", + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-default-worker", + ], + ], + }, + }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":logs:*:*:*", + ], + ], + }, + }, + { + "Action": [ + "cloudwatch:PutMetricData", + ], + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + }, + ], + }, + "PolicyName": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName", + }, + "-lambda-scaling", + ], + ], + }, + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "WatchbotScaleDown": { + "Properties": { + "PolicyName": { + "Fn::Sub": "Watchbot\${AWS::StackName}-scale-down", + }, + "PolicyType": "StepScaling", + "ScalingTargetId": { + "Ref": "WatchbotScalingTarget", + }, + "StepScalingPolicyConfiguration": { + "AdjustmentType": "PercentChangeInCapacity", + "Cooldown": 300, + "MetricAggregationType": "Average", + "StepAdjustments": [ + { + "MetricIntervalUpperBound": 0, + "ScalingAdjustment": -100, + }, + ], + }, + }, + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + }, + "WatchbotScaleDownTrigger": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotScaleDown", + }, + ], + "AlarmDescription": "Scale down due to lack of in-flight messages in queue", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-scale-down", + ], + ], + }, + "ComparisonOperator": "LessThanThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "TotalMessages", + "Namespace": "Mapbox/ecs-watchbot", + "Period": 600, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotScaleUp": { + "Properties": { + "PolicyName": { + "Fn::Sub": "\${AWS::StackName}-scale-up", + }, + "PolicyType": "StepScaling", + "ScalingTargetId": { + "Ref": "WatchbotScalingTarget", + }, + "StepScalingPolicyConfiguration": { + "AdjustmentType": "ChangeInCapacity", + "Cooldown": 300, + "MetricAggregationType": "Average", + "StepAdjustments": [ + { + "MetricIntervalLowerBound": 0, + "ScalingAdjustment": { + "Fn::GetAtt": [ + "WatchbotCustomScalingResource", + "ScalingAdjustment", + ], + }, + }, + ], + }, + }, + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + }, + "WatchbotScaleUpTrigger": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotScaleUp", + }, + ], + "AlarmDescription": "Scale up due to visible messages in queue", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-scale-up", + ], + ], + }, + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "QueueName", + "Value": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ApproximateNumberOfMessagesVisible", + "Namespace": "AWS/SQS", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 0, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotScalingLambda": { + "Properties": { + "Code": { + "ZipFile": { + "Fn::Sub": " + const response = require('./cfn-response'); + exports.handler = function(event,context){ + const result = Math.round(Math.max(Math.min(parseInt(event.ResourceProperties.maxSize) / 10, 100), 1)); + response.send(event, context, response.SUCCESS, { ScalingAdjustment: result }); + } + ", + }, + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "WatchbotRole", + "Arn", + ], + }, + "Runtime": "nodejs18.x", + }, + "Type": "AWS::Lambda::Function", + }, + "WatchbotScalingTarget": { + "Properties": { + "MaxCapacity": 1, + "MinCapacity": 0, + "ResourceId": { + "Fn::Join": [ + "", + [ + "service/", + "processing", + "/", + { + "Fn::GetAtt": [ + "WatchbotService", + "Name", + ], + }, + ], + ], + }, + "RoleARN": { + "Fn::ImportValue": "my-role-arn", + }, + "ScalableDimension": "ecs:service:DesiredCount", + "ServiceNamespace": "ecs", + }, + "Type": "AWS::ApplicationAutoScaling::ScalableTarget", + }, + "WatchbotService": { + "Properties": { + "CapacityProviderStrategy": { + "Fn::If": [ + "WatchbotCapacityIsFargateSpot", + [ + { + "CapacityProvider": "FARGATE_SPOT", + "Weight": 1, + }, + ], + { + "Ref": "AWS::NoValue", + }, + ], + }, + "Cluster": "processing", + "LaunchType": { + "Fn::If": [ + "WatchbotCapacityIsFargate", + "FARGATE", + { + "Ref": "AWS::NoValue", + }, + ], + }, + "NetworkConfiguration": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": undefined, + "Subnets": undefined, + }, + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "PropagateTags": "TASK_DEFINITION", + "TaskDefinition": { + "Ref": "WatchbotTask", + }, + }, + "Type": "AWS::ECS::Service", + }, + "WatchbotTask": { + "Properties": { + "ContainerDefinitions": [ + { + "Command": [ + "watchbot", + "listen", + "echo hello world", + ], + "Cpu": { + "Fn::If": [ + "WatchbotCapacityIsEC2", + { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + 256, + 128, + ], + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "Environment": [ + { + "Name": "WorkTopic", + "Value": { + "Ref": "WatchbotTopic", + }, + }, + { + "Name": "QueueUrl", + "Value": { + "Ref": "WatchbotQueue", + }, + }, + { + "Name": "LogGroup", + "Value": { + "Ref": "WatchbotLogGroup", + }, + }, + { + "Name": "writableFilesystem", + "Value": false, + }, + { + "Name": "maxJobDuration", + "Value": 0, + }, + { + "Name": "Volumes", + "Value": "/tmp", + }, + { + "Name": "Fifo", + "Value": "false", + }, + { + "Name": "structuredLogging", + "Value": "false", + }, + ], + "Image": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId", + }, + ".dkr.ecr.", + { + "Fn::FindInMap": [ + "EcrRegion", + { + "Ref": "AWS::Region", + }, + "Region", + ], + }, + ".", + { + "Ref": "AWS::URLSuffix", + }, + "/", + "example", + ":", + "1", + ], + ], + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "WatchbotLogGroup", + }, + "awslogs-region": { + "Ref": "AWS::Region", + }, + "awslogs-stream-prefix": "1", + }, + }, + "Memory": { + "Fn::If": [ + "WatchbotCapacityIsEC2", + { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + 512, + { + "Ref": "AWS::NoValue", + }, + ], + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "MemoryReservation": { + "Ref": "AWS::NoValue", + }, + "MountPoints": [ + { + "ContainerPath": "/tmp", + "SourceVolume": "tmp", + }, + ], + "Name": { + "Fn::Join": [ + "-", + [ + "Watchbot", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "Privileged": false, + "ReadonlyRootFilesystem": { + "Fn::If": [ + "WatchbotCapacityIsEC2", + true, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "Ulimits": [ + { + "HardLimit": 10240, + "Name": "nofile", + "SoftLimit": 10240, + }, + ], + }, + ], + "Cpu": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + 256, + 128, + ], + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "ExecutionRoleArn": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + { + "Fn::Sub": "arn:\${AWS::Partition}:iam::\${AWS::AccountId}:role/ecsTaskExecutionRole", + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "Family": "example", + "Memory": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + 512, + { + "Ref": "AWS::NoValue", + }, + ], + }, + { + "Ref": "AWS::NoValue", + }, + ], + }, + "NetworkMode": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + "awsvpc", + { + "Ref": "AWS::NoValue", + }, + ], + }, + "RequiresCompatibilities": { + "Fn::If": [ + "WatchbotCapacityIsNotEC2", + [ + "FARGATE", + ], + { + "Ref": "AWS::NoValue", + }, + ], + }, + "TaskRoleArn": { + "Ref": "WatchbotRole", + }, + "Volumes": [ + { + "Name": "tmp", + }, + ], + }, + "Type": "AWS::ECS::TaskDefinition", + }, + "WatchbotTopic": { + "Properties": { + "Subscription": [ + { + "Endpoint": { + "Fn::GetAtt": [ + "WatchbotQueue", + "Arn", + ], + }, + "Protocol": "sqs", + }, + ], + }, + "Type": "AWS::SNS::Topic", + }, + "WatchbotTotalMessagesLambda": { + "Properties": { + "Code": { + "ZipFile": { + "Fn::Sub": [ + " + const { SQS } = require('@aws-sdk/client-sqs'); + const { CloudWatch } = require('@aws-sdk/client-cloudwatch'); + exports.handler = function(event, context, callback) { + const sqs = new SQS({ region: process.env.AWS_DEFAULT_REGION }); + const cw = new CloudWatch({ region: process.env.AWS_DEFAULT_REGION }); + + return sqs.getQueueAttributes({ + QueueUrl: '\${QueueUrl}', + AttributeNames: ['ApproximateNumberOfMessagesNotVisible', 'ApproximateNumberOfMessages'] + }) + .then((attrs) => { + return cw.putMetricData({ + Namespace: 'Mapbox/ecs-watchbot', + MetricData: [{ + MetricName: 'TotalMessages', + Dimensions: [{ Name: 'QueueName', Value: '\${QueueName}' }], + Value: Number(attrs.Attributes.ApproximateNumberOfMessagesNotVisible) + + Number(attrs.Attributes.ApproximateNumberOfMessages) + }] + }) + }) + .then((metric) => callback(null, metric)) + .catch((err) => callback(err)); + } + ", + { + "QueueName": { + "Fn::GetAtt": [ + "WatchbotQueue", + "QueueName", + ], + }, + "QueueUrl": { + "Ref": "WatchbotQueue", + }, + }, + ], + }, + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "WatchbotRole", + "Arn", + ], + }, + "Runtime": "nodejs18.x", + "Timeout": 60, + }, + "Type": "AWS::Lambda::Function", + }, + "WatchbotTotalMessagesSchedule": { + "Properties": { + "Description": "Update TotalMessages metric every minute", + "Name": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-total-messages", + ], + ], + }, + "ScheduleExpression": "cron(0/1 * * * ? *)", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "WatchbotTotalMessagesLambda", + "Arn", + ], + }, + "Id": "WatchbotTotalMessagesLambda", + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "WatchbotWorkerDurationMetric": { + "Properties": { + "FilterPattern": "{ $.duration = * }", + "LogGroupName": { + "Ref": "WatchbotLogGroup", + }, + "MetricTransformations": [ + { + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotWorkerDuration-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": "$.duration", + }, + ], + }, + "Type": "AWS::Logs::MetricFilter", + }, + "WatchbotWorkerErrorsAlarm": { + "Properties": { + "AlarmActions": [ + { + "Ref": "WatchbotNotificationTopic", + }, + ], + "AlarmDescription": "https://github.com/mapbox/ecs-watchbot/blob/v9.1.0/docs/alarms.md#workererrors", + "AlarmName": { + "Fn::Join": [ + "-", + [ + { + "Ref": "AWS::StackName", + }, + "Watchbot-worker-errors", + { + "Ref": "AWS::Region", + }, + ], + ], + }, + "ComparisonOperator": "GreaterThanThreshold", + "EvaluationPeriods": 1, + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotWorkerErrors-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "Namespace": "Mapbox/ecs-watchbot", + "Period": "60", + "Statistic": "Sum", + "Threshold": 10, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "WatchbotWorkerErrorsMetric": { + "Properties": { + "FilterPattern": ""[failure]"", + "LogGroupName": { + "Ref": "WatchbotLogGroup", + }, + "MetricTransformations": [ + { + "MetricName": { + "Fn::Join": [ + "", + [ + "WatchbotWorkerErrors-", + { + "Ref": "AWS::StackName", + }, + ], + ], + }, + "MetricNamespace": "Mapbox/ecs-watchbot", + "MetricValue": 1, + }, + ], + }, + "Type": "AWS::Logs::MetricFilter", + }, + }, + "Rules": {}, + "Transform": undefined, +} +`; + exports[`[template]: defaults 1`] = ` { "AWSTemplateFormatVersion": "2010-09-09", diff --git a/test/template.spec.js b/test/template.spec.js index ff093338..36bd777a 100644 --- a/test/template.spec.js +++ b/test/template.spec.js @@ -214,4 +214,28 @@ test('[template]', () => { })); expect(fifoMaxSize).toMatchSnapshot('fifoMaxSize'); + + const builtWithAutoscalingRole = cf.merge(template({ + service: 'example', + serviceVersion: '1', + command: 'echo hello world', + cluster: 'processing', + notificationEmail: 'hello@mapbox.pagerduty.com', + capacity: 'FARGATE', + autoscalingRoleArn: 'arn:autoscaling:role/abcd' + })); + + expect(builtWithAutoscalingRole).toMatchSnapshot('autoscalingRoleArn'); + + const builtWithAutoscalingRoleImport = cf.merge(template({ + service: 'example', + serviceVersion: '1', + command: 'echo hello world', + cluster: 'processing', + notificationEmail: 'hello@mapbox.pagerduty.com', + capacity: 'FARGATE', + autoscalingRoleArn: cf.importValue('my-role-arn') + })); + + expect(builtWithAutoscalingRoleImport).toMatchSnapshot('autoscalingRoleArnImport'); });