@hasna/uptime 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,795 @@
1
+ terraform {
2
+ required_version = ">= 1.6.0"
3
+
4
+ required_providers {
5
+ aws = {
6
+ source = "hashicorp/aws"
7
+ version = "~> 5.0"
8
+ }
9
+ }
10
+ }
11
+
12
+ provider "aws" {
13
+ region = var.region
14
+ }
15
+
16
+ data "aws_caller_identity" "current" {}
17
+
18
+ locals {
19
+ prefix = "${var.service_name}-${var.stage}"
20
+ container_port = 3899
21
+ evidence_bucket = "hasna-${var.stage}-${var.service_name}-evidence"
22
+ efs_uid = 10001
23
+ efs_gid = 10001
24
+ hosted_sqlite_db_path = "/data/uptime/uptime.db"
25
+ efs_enabled_services = toset(["web"])
26
+ services = {
27
+ web = {
28
+ desired_count = lookup(var.desired_counts, "web", 0)
29
+ command = ["bun", "dist/cli/index.js", "serve", "--mode", "hosted", "--host", "0.0.0.0", "--port", tostring(local.container_port)]
30
+ secrets = { APP_ENV = var.app_env_secret_arn, HASNA_UPTIME_HOSTED_TOKEN = var.hosted_token_secret_arn }
31
+ }
32
+ scheduler = {
33
+ desired_count = lookup(var.desired_counts, "scheduler", 0)
34
+ command = ["bun", "dist/cli/index.js", "cloud", "plan"]
35
+ secrets = { APP_ENV = var.app_env_secret_arn }
36
+ }
37
+ "public-probe" = {
38
+ desired_count = lookup(var.desired_counts, "public-probe", 0)
39
+ command = ["bun", "dist/cli/index.js", "cloud", "plan"]
40
+ secrets = { PROBE_CONFIG = var.public_probe_secret_arn }
41
+ }
42
+ reporter = {
43
+ desired_count = lookup(var.desired_counts, "reporter", 0)
44
+ command = ["bun", "dist/cli/index.js", "cloud", "plan"]
45
+ secrets = { REPORTING_CONFIG = var.reporting_secret_arn }
46
+ }
47
+ migration = {
48
+ desired_count = lookup(var.desired_counts, "migration", 0)
49
+ command = ["bun", "dist/cli/index.js", "cloud", "plan"]
50
+ secrets = { APP_ENV = var.app_env_secret_arn }
51
+ }
52
+ }
53
+ tags = {
54
+ ManagedBy = "terraform"
55
+ Service = var.service_name
56
+ Stage = var.stage
57
+ Account = var.account_name
58
+ }
59
+ }
60
+
61
+ data "aws_vpc" "target" {
62
+ id = var.vpc_id
63
+ }
64
+
65
+ resource "aws_ecr_repository" "open_uptime" {
66
+ name = var.ecr_repository_name
67
+ image_tag_mutability = "IMMUTABLE"
68
+
69
+ image_scanning_configuration {
70
+ scan_on_push = true
71
+ }
72
+
73
+ encryption_configuration {
74
+ encryption_type = "AES256"
75
+ }
76
+
77
+ tags = local.tags
78
+ }
79
+
80
+ resource "aws_cloudwatch_log_group" "image_builder" {
81
+ name = "/aws/codebuild/${local.prefix}-image-builder"
82
+ retention_in_days = 14
83
+ kms_key_id = var.kms_key_arn
84
+ tags = local.tags
85
+ }
86
+
87
+ data "aws_iam_policy_document" "codebuild_assume_role" {
88
+ statement {
89
+ actions = ["sts:AssumeRole"]
90
+
91
+ principals {
92
+ type = "Service"
93
+ identifiers = ["codebuild.amazonaws.com"]
94
+ }
95
+ }
96
+ }
97
+
98
+ resource "aws_iam_role" "image_builder" {
99
+ name = "${local.prefix}-image-builder-role"
100
+ assume_role_policy = data.aws_iam_policy_document.codebuild_assume_role.json
101
+ tags = local.tags
102
+ }
103
+
104
+ data "aws_iam_policy_document" "image_builder" {
105
+ statement {
106
+ actions = ["ecr:GetAuthorizationToken"]
107
+ resources = ["*"]
108
+ }
109
+
110
+ statement {
111
+ actions = [
112
+ "ecr:BatchCheckLayerAvailability",
113
+ "ecr:CompleteLayerUpload",
114
+ "ecr:DescribeImages",
115
+ "ecr:DescribeRepositories",
116
+ "ecr:InitiateLayerUpload",
117
+ "ecr:PutImage",
118
+ "ecr:UploadLayerPart",
119
+ ]
120
+ resources = [aws_ecr_repository.open_uptime.arn]
121
+ }
122
+
123
+ statement {
124
+ actions = [
125
+ "logs:CreateLogStream",
126
+ "logs:PutLogEvents",
127
+ ]
128
+ resources = ["${aws_cloudwatch_log_group.image_builder.arn}:*"]
129
+ }
130
+ }
131
+
132
+ resource "aws_iam_role_policy" "image_builder" {
133
+ name = "${local.prefix}-image-builder-policy"
134
+ role = aws_iam_role.image_builder.id
135
+ policy = data.aws_iam_policy_document.image_builder.json
136
+ }
137
+
138
+ resource "aws_codebuild_project" "image_builder" {
139
+ name = "${local.prefix}-image-builder"
140
+ description = "Build published @hasna/uptime package into the Open Uptime ECR image"
141
+ service_role = aws_iam_role.image_builder.arn
142
+ tags = local.tags
143
+
144
+ artifacts {
145
+ type = "NO_ARTIFACTS"
146
+ }
147
+
148
+ environment {
149
+ compute_type = "BUILD_GENERAL1_SMALL"
150
+ image = "aws/codebuild/standard:7.0"
151
+ type = "LINUX_CONTAINER"
152
+ privileged_mode = true
153
+ }
154
+
155
+ logs_config {
156
+ cloudwatch_logs {
157
+ group_name = aws_cloudwatch_log_group.image_builder.name
158
+ status = "ENABLED"
159
+ }
160
+ }
161
+
162
+ source {
163
+ type = "NO_SOURCE"
164
+ buildspec = <<-YAML
165
+ version: 0.2
166
+ phases:
167
+ pre_build:
168
+ commands:
169
+ - aws --version
170
+ - aws ecr get-login-password --region ${var.region} | docker login --username AWS --password-stdin ${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.region}.amazonaws.com
171
+ build:
172
+ commands:
173
+ - npm pack @hasna/uptime@${var.runtime_package_version}
174
+ - mkdir package
175
+ - tar -xzf hasna-uptime-*.tgz -C package --strip-components=1
176
+ - cd package
177
+ - docker build -f Dockerfile.package -t ${aws_ecr_repository.open_uptime.repository_url}:${var.runtime_package_version} .
178
+ - docker push ${aws_ecr_repository.open_uptime.repository_url}:${var.runtime_package_version}
179
+ - IMAGE_DIGEST=$(aws ecr describe-images --region ${var.region} --repository-name ${aws_ecr_repository.open_uptime.name} --image-ids imageTag=${var.runtime_package_version} --query 'imageDetails[0].imageDigest' --output text)
180
+ - printf '%s@%s\n' '${aws_ecr_repository.open_uptime.repository_url}' "$IMAGE_DIGEST"
181
+ YAML
182
+ }
183
+
184
+ depends_on = [aws_iam_role_policy.image_builder]
185
+ }
186
+
187
+ resource "aws_s3_bucket" "evidence" {
188
+ bucket = local.evidence_bucket
189
+ tags = local.tags
190
+ }
191
+
192
+ resource "aws_s3_bucket_public_access_block" "evidence" {
193
+ bucket = aws_s3_bucket.evidence.id
194
+ block_public_acls = true
195
+ block_public_policy = true
196
+ ignore_public_acls = true
197
+ restrict_public_buckets = true
198
+ }
199
+
200
+ resource "aws_s3_bucket_versioning" "evidence" {
201
+ bucket = aws_s3_bucket.evidence.id
202
+
203
+ versioning_configuration {
204
+ status = "Enabled"
205
+ }
206
+ }
207
+
208
+ resource "aws_s3_bucket_server_side_encryption_configuration" "evidence" {
209
+ bucket = aws_s3_bucket.evidence.id
210
+
211
+ rule {
212
+ apply_server_side_encryption_by_default {
213
+ kms_master_key_id = var.kms_key_arn
214
+ sse_algorithm = "aws:kms"
215
+ }
216
+ }
217
+ }
218
+
219
+ resource "aws_s3_bucket_lifecycle_configuration" "evidence" {
220
+ bucket = aws_s3_bucket.evidence.id
221
+
222
+ rule {
223
+ id = "evidence-retention"
224
+ status = "Enabled"
225
+
226
+ filter {
227
+ prefix = ""
228
+ }
229
+
230
+ abort_incomplete_multipart_upload {
231
+ days_after_initiation = 7
232
+ }
233
+
234
+ noncurrent_version_expiration {
235
+ noncurrent_days = 30
236
+ }
237
+
238
+ expiration {
239
+ days = 365
240
+ }
241
+ }
242
+ }
243
+
244
+ data "aws_iam_policy_document" "evidence_bucket" {
245
+ statement {
246
+ sid = "DenyInsecureTransport"
247
+ effect = "Deny"
248
+ actions = ["s3:*"]
249
+ resources = [
250
+ aws_s3_bucket.evidence.arn,
251
+ "${aws_s3_bucket.evidence.arn}/*",
252
+ ]
253
+
254
+ principals {
255
+ type = "*"
256
+ identifiers = ["*"]
257
+ }
258
+
259
+ condition {
260
+ test = "Bool"
261
+ variable = "aws:SecureTransport"
262
+ values = ["false"]
263
+ }
264
+ }
265
+ }
266
+
267
+ resource "aws_s3_bucket_policy" "evidence" {
268
+ bucket = aws_s3_bucket.evidence.id
269
+ policy = data.aws_iam_policy_document.evidence_bucket.json
270
+ }
271
+
272
+ resource "aws_cloudwatch_log_group" "service" {
273
+ for_each = local.services
274
+ name = "/ecs/${local.prefix}-${each.key}"
275
+ retention_in_days = 30
276
+ kms_key_id = var.kms_key_arn
277
+ tags = local.tags
278
+ }
279
+
280
+ resource "aws_ecs_cluster" "open_uptime" {
281
+ name = local.prefix
282
+ tags = local.tags
283
+ }
284
+
285
+ resource "aws_security_group" "alb" {
286
+ name = "${local.prefix}-alb-sg"
287
+ description = "Open Uptime ALB ingress"
288
+ vpc_id = data.aws_vpc.target.id
289
+ tags = local.tags
290
+ }
291
+
292
+ resource "aws_security_group_rule" "alb_https_ingress" {
293
+ count = length(var.alb_ingress_cidr_blocks) > 0 ? 1 : 0
294
+ type = "ingress"
295
+ description = "HTTPS"
296
+ security_group_id = aws_security_group.alb.id
297
+ from_port = 443
298
+ to_port = 443
299
+ protocol = "tcp"
300
+ cidr_blocks = var.alb_ingress_cidr_blocks
301
+ }
302
+
303
+ resource "aws_security_group_rule" "alb_to_web" {
304
+ type = "egress"
305
+ description = "To Open Uptime web"
306
+ security_group_id = aws_security_group.alb.id
307
+ from_port = local.container_port
308
+ to_port = local.container_port
309
+ protocol = "tcp"
310
+ source_security_group_id = aws_security_group.web.id
311
+ }
312
+
313
+ resource "aws_security_group" "web" {
314
+ name = "${local.prefix}-web-sg"
315
+ description = "Open Uptime web tasks"
316
+ vpc_id = data.aws_vpc.target.id
317
+ tags = local.tags
318
+ }
319
+
320
+ resource "aws_security_group_rule" "web_from_alb" {
321
+ type = "ingress"
322
+ description = "From ALB"
323
+ security_group_id = aws_security_group.web.id
324
+ from_port = local.container_port
325
+ to_port = local.container_port
326
+ protocol = "tcp"
327
+ source_security_group_id = aws_security_group.alb.id
328
+ }
329
+
330
+ resource "aws_security_group_rule" "web_egress" {
331
+ type = "egress"
332
+ description = "Controlled egress to AWS endpoints and EFS"
333
+ security_group_id = aws_security_group.web.id
334
+ from_port = 0
335
+ to_port = 0
336
+ protocol = "-1"
337
+ cidr_blocks = [data.aws_vpc.target.cidr_block]
338
+ }
339
+
340
+ resource "aws_security_group" "worker" {
341
+ for_each = {
342
+ for key, value in local.services : key => value if key != "web"
343
+ }
344
+
345
+ name = "${local.prefix}-${each.key}-sg"
346
+ description = "Open Uptime ${each.key} tasks"
347
+ vpc_id = data.aws_vpc.target.id
348
+ tags = local.tags
349
+ }
350
+
351
+ resource "aws_security_group_rule" "worker_egress" {
352
+ for_each = aws_security_group.worker
353
+
354
+ type = "egress"
355
+ description = each.key == "public-probe" ? "Public probe egress for approved public targets" : "Controlled egress to AWS endpoints"
356
+ security_group_id = each.value.id
357
+ from_port = 0
358
+ to_port = 0
359
+ protocol = "-1"
360
+ cidr_blocks = each.key == "public-probe" ? ["0.0.0.0/0"] : [data.aws_vpc.target.cidr_block]
361
+ }
362
+
363
+ resource "aws_security_group" "efs" {
364
+ name = "${local.prefix}-efs-sg"
365
+ description = "Open Uptime EFS data store"
366
+ vpc_id = data.aws_vpc.target.id
367
+ tags = local.tags
368
+ }
369
+
370
+ resource "aws_security_group_rule" "efs_from_web" {
371
+ type = "ingress"
372
+ description = "Open Uptime web to EFS"
373
+ security_group_id = aws_security_group.efs.id
374
+ from_port = 2049
375
+ to_port = 2049
376
+ protocol = "tcp"
377
+ source_security_group_id = aws_security_group.web.id
378
+ }
379
+
380
+ resource "aws_efs_file_system" "data" {
381
+ creation_token = "${local.prefix}-data"
382
+ encrypted = true
383
+ kms_key_id = var.kms_key_arn
384
+ tags = merge(local.tags, { Name = "${local.prefix}-data" })
385
+
386
+ lifecycle_policy {
387
+ transition_to_ia = "AFTER_30_DAYS"
388
+ }
389
+ }
390
+
391
+ resource "aws_efs_backup_policy" "data" {
392
+ file_system_id = aws_efs_file_system.data.id
393
+
394
+ backup_policy {
395
+ status = "ENABLED"
396
+ }
397
+ }
398
+
399
+ resource "aws_efs_access_point" "uptime" {
400
+ file_system_id = aws_efs_file_system.data.id
401
+
402
+ posix_user {
403
+ uid = local.efs_uid
404
+ gid = local.efs_gid
405
+ }
406
+
407
+ root_directory {
408
+ path = "/uptime"
409
+
410
+ creation_info {
411
+ owner_uid = local.efs_uid
412
+ owner_gid = local.efs_gid
413
+ permissions = "0750"
414
+ }
415
+ }
416
+
417
+ tags = merge(local.tags, { Name = "${local.prefix}-uptime" })
418
+ }
419
+
420
+ resource "aws_efs_mount_target" "data" {
421
+ for_each = toset(var.private_subnet_ids)
422
+ file_system_id = aws_efs_file_system.data.id
423
+ subnet_id = each.value
424
+ security_groups = [aws_security_group.efs.id]
425
+ }
426
+
427
+ resource "aws_backup_vault" "data" {
428
+ name = "${local.prefix}-data"
429
+ kms_key_arn = var.kms_key_arn
430
+ tags = local.tags
431
+ }
432
+
433
+ resource "aws_backup_plan" "data" {
434
+ name = "${local.prefix}-data"
435
+
436
+ rule {
437
+ rule_name = "daily"
438
+ target_vault_name = aws_backup_vault.data.name
439
+ schedule = "cron(0 5 * * ? *)"
440
+
441
+ lifecycle {
442
+ delete_after = 35
443
+ }
444
+ }
445
+
446
+ tags = local.tags
447
+ }
448
+
449
+ data "aws_iam_policy_document" "backup_assume_role" {
450
+ statement {
451
+ actions = ["sts:AssumeRole"]
452
+
453
+ principals {
454
+ type = "Service"
455
+ identifiers = ["backup.amazonaws.com"]
456
+ }
457
+ }
458
+ }
459
+
460
+ resource "aws_iam_role" "backup" {
461
+ name = "${local.prefix}-backup-role"
462
+ assume_role_policy = data.aws_iam_policy_document.backup_assume_role.json
463
+ tags = local.tags
464
+ }
465
+
466
+ resource "aws_iam_role_policy_attachment" "backup" {
467
+ role = aws_iam_role.backup.name
468
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
469
+ }
470
+
471
+ resource "aws_iam_role_policy_attachment" "backup_restore" {
472
+ role = aws_iam_role.backup.name
473
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores"
474
+ }
475
+
476
+ resource "aws_backup_selection" "data" {
477
+ iam_role_arn = aws_iam_role.backup.arn
478
+ name = "${local.prefix}-data"
479
+ plan_id = aws_backup_plan.data.id
480
+ resources = [aws_efs_file_system.data.arn]
481
+ }
482
+
483
+ resource "aws_lb" "open_uptime" {
484
+ name = "${local.prefix}-alb"
485
+ internal = false
486
+ load_balancer_type = "application"
487
+ security_groups = [aws_security_group.alb.id]
488
+ subnets = var.public_subnet_ids
489
+ tags = local.tags
490
+ }
491
+
492
+ resource "aws_lb_target_group" "web" {
493
+ name = "${local.prefix}-web-tg"
494
+ port = local.container_port
495
+ protocol = "HTTP"
496
+ target_type = "ip"
497
+ vpc_id = data.aws_vpc.target.id
498
+ tags = local.tags
499
+
500
+ health_check {
501
+ path = "/health"
502
+ healthy_threshold = 2
503
+ unhealthy_threshold = 3
504
+ matcher = "200"
505
+ }
506
+ }
507
+
508
+ resource "aws_lb_listener" "https" {
509
+ load_balancer_arn = aws_lb.open_uptime.arn
510
+ port = 443
511
+ protocol = "HTTPS"
512
+ certificate_arn = var.certificate_arn
513
+
514
+ default_action {
515
+ type = "forward"
516
+ target_group_arn = aws_lb_target_group.web.arn
517
+ }
518
+ }
519
+
520
+ resource "aws_route53_record" "open_uptime" {
521
+ count = var.hosted_zone_id == null ? 0 : 1
522
+ zone_id = var.hosted_zone_id
523
+ name = var.hostname
524
+ type = "A"
525
+
526
+ alias {
527
+ name = aws_lb.open_uptime.dns_name
528
+ zone_id = aws_lb.open_uptime.zone_id
529
+ evaluate_target_health = true
530
+ }
531
+ }
532
+
533
+ data "aws_iam_policy_document" "ecs_assume_role" {
534
+ statement {
535
+ actions = ["sts:AssumeRole"]
536
+
537
+ principals {
538
+ type = "Service"
539
+ identifiers = ["ecs-tasks.amazonaws.com"]
540
+ }
541
+ }
542
+ }
543
+
544
+ resource "aws_iam_role" "execution" {
545
+ name = "${local.prefix}-execution-role"
546
+ assume_role_policy = data.aws_iam_policy_document.ecs_assume_role.json
547
+ tags = local.tags
548
+ }
549
+
550
+ resource "aws_iam_role_policy_attachment" "execution_managed" {
551
+ role = aws_iam_role.execution.name
552
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
553
+ }
554
+
555
+ data "aws_iam_policy_document" "execution_secrets" {
556
+ statement {
557
+ actions = [
558
+ "secretsmanager:GetSecretValue",
559
+ "ssm:GetParameter",
560
+ "ssm:GetParameters",
561
+ ]
562
+ resources = distinct(flatten([
563
+ for service in values(local.services) : values(service.secrets)
564
+ ]))
565
+ }
566
+
567
+ statement {
568
+ actions = ["kms:Decrypt"]
569
+ resources = [var.kms_key_arn]
570
+ }
571
+ }
572
+
573
+ resource "aws_iam_role_policy" "execution_secrets" {
574
+ name = "${local.prefix}-execution-secrets"
575
+ role = aws_iam_role.execution.id
576
+ policy = data.aws_iam_policy_document.execution_secrets.json
577
+ }
578
+
579
+ resource "aws_iam_role" "task" {
580
+ for_each = local.services
581
+ name = "${local.prefix}-${each.key}-task-role"
582
+ assume_role_policy = data.aws_iam_policy_document.ecs_assume_role.json
583
+ tags = local.tags
584
+ }
585
+
586
+ data "aws_iam_policy_document" "task" {
587
+ for_each = local.services
588
+
589
+ statement {
590
+ actions = [
591
+ "s3:GetObject",
592
+ "s3:PutObject",
593
+ "s3:AbortMultipartUpload",
594
+ ]
595
+ resources = ["${aws_s3_bucket.evidence.arn}/${each.key}/*"]
596
+ }
597
+
598
+ statement {
599
+ actions = ["kms:Decrypt", "kms:GenerateDataKey"]
600
+ resources = [var.kms_key_arn]
601
+ }
602
+
603
+ dynamic "statement" {
604
+ for_each = contains(local.efs_enabled_services, each.key) ? [1] : []
605
+
606
+ content {
607
+ actions = [
608
+ "elasticfilesystem:ClientMount",
609
+ "elasticfilesystem:ClientWrite",
610
+ ]
611
+ resources = [aws_efs_file_system.data.arn]
612
+
613
+ condition {
614
+ test = "StringEquals"
615
+ variable = "elasticfilesystem:AccessPointArn"
616
+ values = [aws_efs_access_point.uptime.arn]
617
+ }
618
+ }
619
+ }
620
+ }
621
+
622
+ resource "aws_iam_role_policy" "task" {
623
+ for_each = local.services
624
+ name = "${local.prefix}-${each.key}-task-policy"
625
+ role = aws_iam_role.task[each.key].id
626
+ policy = data.aws_iam_policy_document.task[each.key].json
627
+ }
628
+
629
+ resource "aws_ecs_task_definition" "service" {
630
+ for_each = local.services
631
+ family = "${local.prefix}-${each.key}"
632
+ network_mode = "awsvpc"
633
+ requires_compatibilities = ["FARGATE"]
634
+ cpu = "512"
635
+ memory = "1024"
636
+ execution_role_arn = aws_iam_role.execution.arn
637
+ task_role_arn = aws_iam_role.task[each.key].arn
638
+
639
+ dynamic "volume" {
640
+ for_each = contains(local.efs_enabled_services, each.key) ? [1] : []
641
+
642
+ content {
643
+ name = "uptime-data"
644
+
645
+ efs_volume_configuration {
646
+ file_system_id = aws_efs_file_system.data.id
647
+ transit_encryption = "ENABLED"
648
+
649
+ authorization_config {
650
+ access_point_id = aws_efs_access_point.uptime.id
651
+ iam = "ENABLED"
652
+ }
653
+ }
654
+ }
655
+ }
656
+
657
+ container_definitions = jsonencode([
658
+ {
659
+ name = each.key
660
+ image = var.container_image
661
+ essential = true
662
+ command = each.value.command
663
+ portMappings = each.key == "web" ? [{
664
+ containerPort = local.container_port
665
+ hostPort = local.container_port
666
+ protocol = "tcp"
667
+ }] : []
668
+ environment = concat([
669
+ { name = "HASNA_UPTIME_MODE", value = "hosted" },
670
+ { name = "HASNA_UPTIME_WORKSPACE_ID", value = var.workspace_id },
671
+ { name = "HASNA_UPTIME_COMPONENT", value = each.key },
672
+ { name = "HASNA_UPTIME_HOSTNAME", value = var.hostname },
673
+ ], contains(local.efs_enabled_services, each.key) ? [
674
+ { name = "HASNA_UPTIME_HOSTED_SQLITE_DB", value = local.hosted_sqlite_db_path },
675
+ ] : [])
676
+ mountPoints = contains(local.efs_enabled_services, each.key) ? [
677
+ {
678
+ sourceVolume = "uptime-data"
679
+ containerPath = "/data/uptime"
680
+ readOnly = false
681
+ }
682
+ ] : []
683
+ secrets = [
684
+ for name, value_from in each.value.secrets : {
685
+ name = name
686
+ valueFrom = value_from
687
+ }
688
+ ]
689
+ logConfiguration = {
690
+ logDriver = "awslogs"
691
+ options = {
692
+ awslogs-group = aws_cloudwatch_log_group.service[each.key].name
693
+ awslogs-region = var.region
694
+ awslogs-stream-prefix = "ecs"
695
+ }
696
+ }
697
+ }
698
+ ])
699
+
700
+ tags = local.tags
701
+ }
702
+
703
+ resource "aws_ecs_service" "web" {
704
+ name = "${local.prefix}-web"
705
+ cluster = aws_ecs_cluster.open_uptime.id
706
+ task_definition = aws_ecs_task_definition.service["web"].arn
707
+ desired_count = local.services.web.desired_count
708
+ launch_type = "FARGATE"
709
+ tags = local.tags
710
+
711
+ deployment_circuit_breaker {
712
+ enable = true
713
+ rollback = true
714
+ }
715
+
716
+ network_configuration {
717
+ subnets = var.private_subnet_ids
718
+ security_groups = [aws_security_group.web.id]
719
+ assign_public_ip = false
720
+ }
721
+
722
+ load_balancer {
723
+ target_group_arn = aws_lb_target_group.web.arn
724
+ container_name = "web"
725
+ container_port = local.container_port
726
+ }
727
+
728
+ depends_on = [aws_lb_listener.https, aws_efs_mount_target.data]
729
+ }
730
+
731
+ resource "aws_ecs_service" "worker" {
732
+ for_each = {
733
+ for key, value in local.services : key => value if key != "web" && key != "migration"
734
+ }
735
+
736
+ name = "${local.prefix}-${each.key}"
737
+ cluster = aws_ecs_cluster.open_uptime.id
738
+ task_definition = aws_ecs_task_definition.service[each.key].arn
739
+ desired_count = each.value.desired_count
740
+ launch_type = "FARGATE"
741
+ tags = local.tags
742
+
743
+ deployment_circuit_breaker {
744
+ enable = true
745
+ rollback = true
746
+ }
747
+
748
+ network_configuration {
749
+ subnets = var.private_subnet_ids
750
+ security_groups = [aws_security_group.worker[each.key].id]
751
+ assign_public_ip = false
752
+ }
753
+ }
754
+
755
+ resource "aws_cloudwatch_metric_alarm" "web_5xx" {
756
+ alarm_name = "${local.prefix}-web-5xx"
757
+ alarm_description = "Open Uptime web target group 5xx responses"
758
+ namespace = "AWS/ApplicationELB"
759
+ metric_name = "HTTPCode_Target_5XX_Count"
760
+ statistic = "Sum"
761
+ period = 60
762
+ evaluation_periods = 5
763
+ threshold = 5
764
+ comparison_operator = "GreaterThanOrEqualToThreshold"
765
+ treat_missing_data = "notBreaching"
766
+ alarm_actions = var.alarm_actions
767
+ ok_actions = var.alarm_actions
768
+ tags = local.tags
769
+
770
+ dimensions = {
771
+ LoadBalancer = aws_lb.open_uptime.arn_suffix
772
+ TargetGroup = aws_lb_target_group.web.arn_suffix
773
+ }
774
+ }
775
+
776
+ resource "aws_cloudwatch_metric_alarm" "web_unhealthy" {
777
+ alarm_name = "${local.prefix}-web-unhealthy"
778
+ alarm_description = "Open Uptime unhealthy web targets"
779
+ namespace = "AWS/ApplicationELB"
780
+ metric_name = "UnHealthyHostCount"
781
+ statistic = "Maximum"
782
+ period = 60
783
+ evaluation_periods = 3
784
+ threshold = 1
785
+ comparison_operator = "GreaterThanOrEqualToThreshold"
786
+ treat_missing_data = "notBreaching"
787
+ alarm_actions = var.alarm_actions
788
+ ok_actions = var.alarm_actions
789
+ tags = local.tags
790
+
791
+ dimensions = {
792
+ LoadBalancer = aws_lb.open_uptime.arn_suffix
793
+ TargetGroup = aws_lb_target_group.web.arn_suffix
794
+ }
795
+ }