Skip to content

Commit 2e354aa

Browse files
committed
Refactor ECS scheduled tasks for forms admin
Consolidate ECS scheduled tasks for Mailchimp and organisations sync into a single module.
1 parent 3b59216 commit 2e354aa

3 files changed

Lines changed: 47 additions & 225 deletions

File tree

infra/modules/forms-admin/mailchimp-sync.tf

Lines changed: 3 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,3 @@
1-
##
2-
# ECS
3-
##
4-
locals {
5-
# Take the exported task container definition
6-
# and override some parts of it, so that it doesn't fall out of sync
7-
mailchimp_sync_container_definitions = merge(
8-
module.ecs_service.task_container_definition,
9-
{
10-
name = "forms-admin_mailchimp_sync",
11-
command = ["rake", "mailchimp:synchronize_audiences"]
12-
logConfiguration = {
13-
logDriver = "awslogs",
14-
options = {
15-
awslogs-group = module.ecs_service.application_log_group_name,
16-
awslogs-region = "eu-west-2",
17-
awslogs-stream-prefix = "forms-admin-${var.env_name}-mailchimp-sync"
18-
}
19-
},
20-
}
21-
)
22-
}
23-
24-
resource "aws_ecs_task_definition" "mailchimp_cron_job" {
25-
count = var.enable_mailchimp_sync ? 1 : 0
26-
27-
family = "${var.env_name}_forms-admin_mailchimp_sync"
28-
container_definitions = jsonencode([local.mailchimp_sync_container_definitions])
29-
30-
execution_role_arn = module.ecs_service.task_definition.execution_role_arn
31-
task_role_arn = module.ecs_service.task_definition.task_role_arn
32-
requires_compatibilities = module.ecs_service.task_definition.requires_compatibilities
33-
cpu = module.ecs_service.task_definition.cpu
34-
memory = module.ecs_service.task_definition.memory
35-
network_mode = "awsvpc"
36-
37-
runtime_platform {
38-
operating_system_family = "LINUX"
39-
cpu_architecture = "ARM64"
40-
}
41-
42-
// As we deploy the forms-admin versions with codepipeline, terraform is not the source of truth for the task definition image. Therefore use `track_latest` to avoid drift.
43-
track_latest = true
44-
}
45-
46-
##
47-
# EventBridge
48-
##
49-
resource "aws_cloudwatch_event_rule" "sync_mailchimp_cron_job" {
50-
count = var.enable_mailchimp_sync ? 1 : 0
51-
52-
name = "${var.env_name}-forms-admin-mailchimp-sync-cron"
53-
description = "Trigger the forms-admin MailChimp synchronisation on a schedule"
54-
schedule_expression = "cron(30 10 * * ? *)" # 10:30AM daily. In office hours so that we can respond to failures
55-
}
56-
57-
resource "aws_cloudwatch_event_target" "ecs_mailchimp_sync_job" {
58-
count = var.enable_mailchimp_sync ? 1 : 0
59-
60-
arn = var.ecs_cluster_arn
61-
rule = aws_cloudwatch_event_rule.sync_mailchimp_cron_job[0].name
62-
role_arn = aws_iam_role.ecs_mailchimp_cron_scheduler[0].arn
63-
64-
ecs_target {
65-
# Construct ARN without revision number to always use the latest revision
66-
# Format: arn:aws:ecs:region:account:task-definition/family
67-
# This ensures the EventBridge rule always uses the latest revision
68-
# which is updated by the forms-admin deployment pipeline
69-
task_definition_arn = "arn:aws:ecs:eu-west-2:${data.aws_caller_identity.current.account_id}:task-definition/${aws_ecs_task_definition.mailchimp_cron_job[0].family}"
70-
launch_type = "FARGATE"
71-
platform_version = "1.4.0"
72-
73-
network_configuration {
74-
assign_public_ip = false
75-
security_groups = module.ecs_service.service.network_configuration[0].security_groups
76-
subnets = module.ecs_service.service.network_configuration[0].subnets
77-
}
78-
}
79-
80-
dead_letter_config {
81-
arn = var.eventbridge_dead_letter_queue_arn
82-
}
83-
}
84-
851
## Monitor for failure
862
resource "aws_cloudwatch_event_rule" "sync_mailchimp_cron_job_failed" {
873
name = "${var.env_name}-forms-admin-mailchimp-sync-failed"
@@ -98,8 +14,9 @@ resource "aws_cloudwatch_event_rule" "sync_mailchimp_cron_job_failed" {
9814

9915
detail = {
10016
lastStatus = ["STOPPED"]
17+
group = ["family:forms-admin-mailchimp-sync"]
10118
containers = {
102-
name = [local.mailchimp_sync_container_definitions.name]
19+
name = ["main"]
10320
exitCode = [{ "anything-but" : [0] }]
10421
}
10522
}
@@ -119,7 +36,7 @@ resource "aws_cloudwatch_event_target" "sync_mailchimp_cron_job_alert_message" {
11936
"description": "GOV.UK Forms has a scheduled ECS task to sync our Mailchimp mailing list with new users in the users database (only applied in production). When this task fails an email is sent to Zendesk.",
12037
"next-steps": {
12138
"1": "Navigate to Splunk: https://gds.splunkcloud.com/en-GB/app/gds-543-forms/search.",
122-
"2": "Search for index=gds_dsp_production_forms log_stream=forms-admin-production-mailchimp-sync/forms-admin_mailchimp_sync/*. Use the 'Today' date-time preset to find today's logs.",
39+
"2": "Search for index=gds_dsp_production_forms log_stream=forms-admin-production-mailchimp-sync/main/*. Use the 'Today' date-time preset to find today's logs.",
12340
"3": "Review logs for errors."
12441
}
12542
}
@@ -131,35 +48,6 @@ resource "aws_cloudwatch_event_target" "sync_mailchimp_cron_job_alert_message" {
13148
}
13249
}
13350

134-
##
135-
# IAM
136-
##
137-
resource "aws_iam_role" "ecs_mailchimp_cron_scheduler" {
138-
count = var.enable_mailchimp_sync ? 1 : 0
139-
140-
name = "${var.env_name}-forms-admin-mailchimp-ecs-cron-scheduler"
141-
142-
assume_role_policy = jsonencode({
143-
Version = "2012-10-17"
144-
Statement = [
145-
{
146-
Action = "sts:AssumeRole"
147-
Effect = "Allow"
148-
Principal = {
149-
Service = "events.amazonaws.com"
150-
}
151-
}
152-
]
153-
})
154-
}
155-
156-
resource "aws_iam_role_policy_attachment" "ecs_mailchimp_events_policy" {
157-
count = var.enable_mailchimp_sync ? 1 : 0
158-
159-
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceEventsRole"
160-
role = aws_iam_role.ecs_mailchimp_cron_scheduler[0].name
161-
}
162-
16351
moved {
16452
from = aws_cloudwatch_event_rule.sync_cron_job_failed
16553
to = aws_cloudwatch_event_rule.sync_mailchimp_cron_job_failed
Lines changed: 3 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,3 @@
1-
##
2-
# ECS
3-
##
4-
locals {
5-
# Take the exported task container definition
6-
# and override some parts of it, so that it doesn't fall out of sync
7-
organisations_sync_container_definitions = merge(
8-
module.ecs_service.task_container_definition,
9-
{
10-
name = "forms-admin_organisations_sync",
11-
command = ["rake", "organisations:fetch"]
12-
logConfiguration = {
13-
logDriver = "awslogs",
14-
options = {
15-
awslogs-group = module.ecs_service.application_log_group_name,
16-
awslogs-region = "eu-west-2",
17-
awslogs-stream-prefix = "forms-admin-${var.env_name}-organisations-sync"
18-
}
19-
},
20-
}
21-
)
22-
}
23-
24-
resource "aws_ecs_task_definition" "orgs_cron_job" {
25-
count = var.enable_organisations_sync ? 1 : 0
26-
27-
family = "${var.env_name}_forms-admin_organisations_sync"
28-
container_definitions = jsonencode([local.organisations_sync_container_definitions])
29-
30-
execution_role_arn = module.ecs_service.task_definition.execution_role_arn
31-
task_role_arn = module.ecs_service.task_definition.task_role_arn
32-
requires_compatibilities = module.ecs_service.task_definition.requires_compatibilities
33-
cpu = module.ecs_service.task_definition.cpu
34-
memory = module.ecs_service.task_definition.memory
35-
network_mode = "awsvpc"
36-
track_latest = true
37-
38-
runtime_platform {
39-
operating_system_family = "LINUX"
40-
cpu_architecture = "ARM64"
41-
}
42-
}
43-
44-
##
45-
# EventBridge
46-
##
47-
resource "aws_cloudwatch_event_rule" "sync_orgs_cron_job" {
48-
count = var.enable_organisations_sync ? 1 : 0
49-
50-
name = "${var.env_name}-forms-admin-orgs-sync-cron"
51-
description = "Trigger the forms-admin organisations synchronisation on a schedule"
52-
schedule_expression = "cron(30 11 ? * TUE *)" # 11:30AM every Tuesday. In office hours so that we can respond to failures
53-
}
54-
55-
resource "aws_cloudwatch_event_target" "ecs_org_sync_job" {
56-
count = var.enable_organisations_sync ? 1 : 0
57-
58-
arn = var.ecs_cluster_arn
59-
rule = aws_cloudwatch_event_rule.sync_orgs_cron_job[0].name
60-
role_arn = aws_iam_role.ecs_orgs_cron_scheduler[0].arn
61-
62-
ecs_target {
63-
task_definition_arn = "arn:aws:ecs:eu-west-2:${data.aws_caller_identity.current.account_id}:task-definition/${aws_ecs_task_definition.orgs_cron_job[0].family}"
64-
launch_type = "FARGATE"
65-
platform_version = "1.4.0"
66-
67-
network_configuration {
68-
assign_public_ip = false
69-
security_groups = module.ecs_service.service.network_configuration[0].security_groups
70-
subnets = module.ecs_service.service.network_configuration[0].subnets
71-
}
72-
}
73-
74-
dead_letter_config {
75-
arn = var.eventbridge_dead_letter_queue_arn
76-
}
77-
}
78-
791
## Monitor for failure
802
resource "aws_cloudwatch_event_rule" "sync_orgs_cron_job_failed" {
813
count = var.enable_organisations_sync ? 1 : 0
@@ -94,8 +16,9 @@ resource "aws_cloudwatch_event_rule" "sync_orgs_cron_job_failed" {
9416

9517
detail = {
9618
lastStatus = ["STOPPED"]
19+
group = ["family:forms-admin-organisations-sync"]
9720
containers = {
98-
name = [local.organisations_sync_container_definitions.name]
21+
name = ["main"]
9922
exitCode = [{ "anything-but" : [0] }]
10023
}
10124
}
@@ -117,7 +40,7 @@ resource "aws_cloudwatch_event_target" "sync_orgs_cron_job_alert_message" {
11740
"description": "GOV.UK Forms has a scheduled ECS task to sync our organisations from GOV.UK. When this task fails an email is sent to Zendesk.",
11841
"next-steps": {
11942
"1": "Navigate to Splunk: https://gds.splunkcloud.com/en-GB/app/gds-543-forms/search.",
120-
"2": "Search for index=gds_dsp_production_forms log_stream=forms-admin-production-organisations-sync/forms-admin_organisations_sync/*. Use the 'Today' date-time preset to find today's logs.",
43+
"2": "Search for index=gds_dsp_production_forms log_stream=forms-admin-production-organisations-sync/main/*. Use the 'Today' date-time preset to find today's logs.",
12144
"3": "Review logs for errors."
12245
}
12346
}
@@ -128,32 +51,3 @@ resource "aws_cloudwatch_event_target" "sync_orgs_cron_job_alert_message" {
12851
arn = var.eventbridge_dead_letter_queue_arn
12952
}
13053
}
131-
132-
##
133-
# IAM
134-
##
135-
resource "aws_iam_role" "ecs_orgs_cron_scheduler" {
136-
count = var.enable_organisations_sync ? 1 : 0
137-
138-
name = "${var.env_name}-forms-admin-orgs-ecs-cron-scheduler"
139-
140-
assume_role_policy = jsonencode({
141-
Version = "2012-10-17"
142-
Statement = [
143-
{
144-
Action = "sts:AssumeRole"
145-
Effect = "Allow"
146-
Principal = {
147-
Service = "events.amazonaws.com"
148-
}
149-
}
150-
]
151-
})
152-
}
153-
154-
resource "aws_iam_role_policy_attachment" "ecs_orgs_events_policy" {
155-
count = var.enable_organisations_sync ? 1 : 0
156-
157-
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceEventsRole"
158-
role = aws_iam_role.ecs_orgs_cron_scheduler[0].name
159-
}

infra/modules/forms-admin/scheduled-tasks.tf

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
locals {
2-
scheduled_tasks = {}
2+
scheduled_tasks = {
3+
organisations_sync = {
4+
schedule_expression = "cron(30 11 ? * TUE *)" # 11:30AM every Tuesday. In office hours so that we can respond to failures
5+
command = ["rake", "organisations:fetch"]
6+
}
7+
mailchimp_sync = {
8+
schedule_expression = "cron(30 10 * * ? *)" # 10:30AM daily. In office hours so that we can respond to failures
9+
command = ["rake", "mailchimp:synchronize_audiences"]
10+
}
11+
}
312
}
413

514
module "scheduled_tasks" {
@@ -22,3 +31,34 @@ module "scheduled_tasks" {
2231
network_security_groups = module.ecs_service.service.network_configuration[0].security_groups
2332
network_subnets = module.ecs_service.service.network_configuration[0].subnets
2433
}
34+
35+
moved {
36+
from = aws_ecs_task_definition.orgs_cron_job[0]
37+
to = module.scheduled_tasks["organisations_sync"].aws_ecs_task_definition.this
38+
}
39+
40+
moved {
41+
from = aws_cloudwatch_event_rule.sync_orgs_cron_job[0]
42+
to = module.scheduled_tasks["organisations_sync"].aws_cloudwatch_event_rule.this
43+
}
44+
45+
moved {
46+
from = aws_cloudwatch_event_target.ecs_org_sync_job[0]
47+
to = module.scheduled_tasks["organisations_sync"].aws_cloudwatch_event_target.this
48+
}
49+
50+
moved {
51+
from = aws_ecs_task_definition.mailchimp_cron_job[0]
52+
to = module.scheduled_tasks["mailchimp_sync"].aws_ecs_task_definition.this
53+
}
54+
55+
moved {
56+
from = aws_cloudwatch_event_rule.sync_mailchimp_cron_job[0]
57+
to = module.scheduled_tasks["mailchimp_sync"].aws_cloudwatch_event_rule.this
58+
}
59+
60+
moved {
61+
from = aws_cloudwatch_event_target.ecs_mailchimp_sync_job[0]
62+
to = module.scheduled_tasks["mailchimp_sync"].aws_cloudwatch_event_target.this
63+
}
64+

0 commit comments

Comments
 (0)