@aws/nx-plugin 0.111.0 → 0.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +1 -0
  2. package/generators.json +6 -0
  3. package/package.json +1 -1
  4. package/src/preset/__snapshots__/generator.spec.ts.snap +2 -0
  5. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +4822 -0
  6. package/src/ts/rdb/files/Dockerfile.template +15 -0
  7. package/src/ts/rdb/files/prisma/models/example.prisma.template +5 -0
  8. package/src/ts/rdb/files/prisma/schema.prisma.template +9 -0
  9. package/src/ts/rdb/files/prisma.config.ts.template +24 -0
  10. package/src/ts/rdb/files/scripts/docker-pull.ts.template +15 -0
  11. package/src/ts/rdb/files/scripts/docker-start.ts.template +81 -0
  12. package/src/ts/rdb/files/scripts/wait-for-db.ts.template +55 -0
  13. package/src/ts/rdb/files/src/constants.ts.template +8 -0
  14. package/src/ts/rdb/files/src/create-db-user-handler.ts.template +128 -0
  15. package/src/ts/rdb/files/src/index.ts.template +2 -0
  16. package/src/ts/rdb/files/src/migration-handler.ts.template +54 -0
  17. package/src/ts/rdb/files/src/prisma.ts.template +104 -0
  18. package/src/ts/rdb/files/src/utils.ts.template +145 -0
  19. package/src/ts/rdb/generator.d.ts +10 -0
  20. package/src/ts/rdb/generator.js +206 -0
  21. package/src/ts/rdb/generator.js.map +1 -0
  22. package/src/ts/rdb/schema.d.ts +17 -0
  23. package/src/ts/rdb/schema.json +73 -0
  24. package/src/utils/rdb-constructs/files/cdk/app/dbs/__nameKebabCase__.ts.template +43 -0
  25. package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +380 -0
  26. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +732 -0
  27. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +742 -0
  28. package/src/utils/rdb-constructs/rdb-constructs.d.ts +23 -0
  29. package/src/utils/rdb-constructs/rdb-constructs.js +59 -0
  30. package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -0
  31. package/src/utils/versions.d.ts +12 -1
  32. package/src/utils/versions.js +11 -0
  33. package/src/utils/versions.js.map +1 -1
@@ -0,0 +1,732 @@
1
+ terraform {
2
+ required_version = ">= 1.0"
3
+
4
+ required_providers {
5
+ archive = {
6
+ source = "hashicorp/archive"
7
+ version = "~> 2.5"
8
+ }
9
+ aws = {
10
+ source = "hashicorp/aws"
11
+ version = "~> 6.33"
12
+ }
13
+ null = {
14
+ source = "hashicorp/null"
15
+ version = ">= 3.0"
16
+ }
17
+ random = {
18
+ source = "hashicorp/random"
19
+ version = ">= 3.0"
20
+ }
21
+ }
22
+ }
23
+
24
+ variable "vpc_id" {
25
+ description = "VPC where the Aurora cluster will be deployed."
26
+ type = string
27
+ }
28
+
29
+ variable "database_subnet_ids" {
30
+ description = "Subnet IDs for the Aurora cluster and RDS Proxy. Private isolated subnets are fine — no outbound egress required."
31
+ type = list(string)
32
+ }
33
+
34
+ variable "lambda_subnet_ids" {
35
+ description = "Subnet IDs for the create-db-user and migration Lambda functions. Must have outbound egress to AWS service endpoints (e.g. Secrets Manager) — use private subnets with a NAT gateway."
36
+ type = list(string)
37
+ }
38
+
39
+ variable "engine_version" {
40
+ description = "Aurora engine version."
41
+ type = string
42
+ default = null
43
+ }
44
+
45
+ variable "port" {
46
+ description = "Database port for the selected Aurora engine."
47
+ type = number
48
+ default = null
49
+ }
50
+
51
+ variable "serverless_min_capacity" {
52
+ description = "Minimum Aurora Serverless v2 ACUs."
53
+ type = number
54
+ default = 0.5
55
+ }
56
+
57
+ variable "serverless_max_capacity" {
58
+ description = "Maximum Aurora Serverless v2 ACUs."
59
+ type = number
60
+ default = 4
61
+ }
62
+
63
+ variable "instance_count" {
64
+ description = "Number of Aurora instances to create."
65
+ type = number
66
+ default = 1
67
+ }
68
+
69
+ variable "deletion_protection" {
70
+ description = "Whether deletion protection is enabled."
71
+ type = bool
72
+ default = true
73
+ }
74
+
75
+ variable "skip_final_snapshot" {
76
+ description = "Whether to skip the final snapshot on deletion."
77
+ type = bool
78
+ default = false
79
+ }
80
+
81
+ variable "enable_rds_proxy" {
82
+ description = "Whether to provision an RDS Proxy in front of the Aurora cluster."
83
+ type = bool
84
+ default = true
85
+ }
86
+
87
+ variable "enable_credential_rotation" {
88
+ description = "Whether to enable automatic credential rotation for the admin secret."
89
+ type = bool
90
+ default = true
91
+ }
92
+
93
+ variable "enable_cloudwatch_logs" {
94
+ description = "Whether to export Aurora engine logs to CloudWatch. PostgreSQL also enables verbose statement logging."
95
+ type = bool
96
+ default = false
97
+ }
98
+
99
+ variable "enable_performance_insights" {
100
+ description = "Whether to enable Performance Insights on Aurora cluster instances."
101
+ type = bool
102
+ default = true
103
+ }
104
+
105
+ variable "performance_insights_retention_period" {
106
+ description = "Retention period, in days, for Performance Insights data when enabled."
107
+ type = number
108
+ default = 7
109
+ }
110
+
111
+ variable "enable_backup" {
112
+ description = "Whether to provision an AWS Backup plan for the Aurora cluster."
113
+ type = bool
114
+ default = false
115
+ }
116
+
117
+ variable "tags" {
118
+ description = "Tags to apply to all resources."
119
+ type = map(string)
120
+ default = {}
121
+ }
122
+
123
+ variable "docker_image_tag" {
124
+ description = "Docker image tag for the migration handler. Defaults to the tag built by the Nx docker target."
125
+ type = string
126
+ default = "<%= dockerImageTag %>"
127
+ }
128
+
129
+ variable "asset_bucket_name" {
130
+ description = "Name of the shared asset S3 bucket used to stage the Lambda deployment zip. Instantiate the `core/asset-bucket` module once per deployment and pass its `bucket_name` output here."
131
+ type = string
132
+ }
133
+
134
+ data "aws_region" "current" {}
135
+ data "aws_caller_identity" "current" {}
136
+
137
+ resource "random_string" "suffix" {
138
+ length = 8
139
+ special = false
140
+ upper = false
141
+ }
142
+
143
+ locals {
144
+ create_db_user_bundle_path = "${path.module}/../../../../../../../<%- createDbUserBundleDir %>"
145
+ migration_function_name = "<%= nameKebabCase %>-migration-${random_string.suffix.result}"
146
+ create_db_user_function_name = "<%= nameKebabCase %>-create-db-user-${random_string.suffix.result}"
147
+ database_runtime_user = "db_${random_string.suffix.result}"
148
+ }
149
+
150
+ module "aurora" {
151
+ source = "../../../core/rdb/aurora"
152
+ #checkov:skip=CKV_AWS_139:Deletion protection is enabled but checkov is unable to detect it correctly
153
+ name = "<%= nameKebabCase %>"
154
+ vpc_id = var.vpc_id
155
+ subnet_ids = var.database_subnet_ids
156
+ lambda_subnet_ids = var.lambda_subnet_ids
157
+ engine = "<%= engine === 'mysql' ? 'aurora-mysql' : 'aurora-postgresql' %>"
158
+ engine_version = var.engine_version
159
+ database_name = "<%= databaseName %>"
160
+ admin_user = "<%= adminUser %>"
161
+ port = var.port
162
+ serverless_min_capacity = var.serverless_min_capacity
163
+ serverless_max_capacity = var.serverless_max_capacity
164
+ instance_count = var.instance_count
165
+ deletion_protection = var.deletion_protection
166
+ skip_final_snapshot = var.skip_final_snapshot
167
+ enable_rds_proxy = var.enable_rds_proxy
168
+ enable_credential_rotation = var.enable_credential_rotation
169
+ enable_cloudwatch_logs = var.enable_cloudwatch_logs
170
+ enable_performance_insights = var.enable_performance_insights
171
+ performance_insights_retention_period = var.performance_insights_retention_period
172
+ enable_backup = var.enable_backup
173
+ tags = var.tags
174
+ }
175
+
176
+ resource "aws_iam_role_policy" "proxy_db_user_connect" {
177
+ count = var.enable_rds_proxy ? 1 : 0
178
+
179
+ role = module.aurora.proxy_role_name
180
+
181
+ policy = jsonencode({
182
+ Version = "2012-10-17"
183
+ Statement = [
184
+ {
185
+ Effect = "Allow"
186
+ Action = ["rds-db:connect"]
187
+ Resource = [
188
+ "arn:aws:rds-db:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:dbuser:${module.aurora.cluster_resource_id}/${local.database_runtime_user}"
189
+ ]
190
+ }
191
+ ]
192
+ })
193
+ }
194
+
195
+ data "archive_file" "create_db_user_zip" {
196
+ type = "zip"
197
+ source_dir = local.create_db_user_bundle_path
198
+ output_path = "${path.module}/../../../../../../../dist/packages/common/terraform/dbs/<%= nameKebabCase %>/create-db-user.zip"
199
+ }
200
+
201
+ resource "aws_s3_object" "create_db_user_zip" {
202
+ bucket = var.asset_bucket_name
203
+ key = "dbs/<%= nameKebabCase %>/${data.archive_file.create_db_user_zip.output_sha256}.zip"
204
+ source = data.archive_file.create_db_user_zip.output_path
205
+ source_hash = data.archive_file.create_db_user_zip.output_base64sha256
206
+ etag = data.archive_file.create_db_user_zip.output_md5
207
+ }
208
+
209
+ module "add_rdb_to_runtime_config" {
210
+ source = "../../../core/runtime-config/entry"
211
+
212
+ namespace = "database"
213
+ key = "<%= nameClassName %>"
214
+ value = {
215
+ hostname = module.aurora.cluster_endpoint
216
+ port = module.aurora.cluster_port
217
+ database = module.aurora.database_name
218
+ adminUser = module.aurora.admin_user
219
+ dbUser = local.database_runtime_user
220
+ region = data.aws_region.current.region
221
+ }
222
+ }
223
+
224
+ resource "aws_ecr_repository" "migration_handler" {
225
+ #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
226
+ #checkov:skip=CKV_AWS_51:Mutable tags are intentional; the migration handler is always pushed as latest and redeployment is gated on docker_digest changes in null_resource.docker_publish
227
+ name = "<%= nameKebabCase %>-migration-${random_string.suffix.result}"
228
+ image_tag_mutability = "MUTABLE"
229
+ force_delete = true
230
+
231
+ image_scanning_configuration {
232
+ scan_on_push = true
233
+ }
234
+
235
+ tags = var.tags
236
+ }
237
+
238
+ resource "aws_ecr_repository_policy" "migration_handler" {
239
+ repository = aws_ecr_repository.migration_handler.name
240
+
241
+ policy = jsonencode({
242
+ Version = "2012-10-17"
243
+ Statement = [
244
+ {
245
+ Sid = "AllowPushPull"
246
+ Effect = "Allow"
247
+ Principal = {
248
+ AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
249
+ }
250
+ Action = [
251
+ "ecr:BatchCheckLayerAvailability",
252
+ "ecr:CompleteLayerUpload",
253
+ "ecr:GetDownloadUrlForLayer",
254
+ "ecr:InitiateLayerUpload",
255
+ "ecr:PutImage",
256
+ "ecr:UploadLayerPart"
257
+ ]
258
+ },
259
+ {
260
+ Sid = "AllowLambdaPull"
261
+ Effect = "Allow"
262
+ Principal = {
263
+ Service = "lambda.amazonaws.com"
264
+ }
265
+ Action = [
266
+ "ecr:BatchCheckLayerAvailability",
267
+ "ecr:BatchGetImage",
268
+ "ecr:GetDownloadUrlForLayer"
269
+ ]
270
+ }
271
+ ]
272
+ })
273
+ }
274
+
275
+ data "external" "docker_digest" {
276
+ program = ["sh", "-c", "echo '{\"digest\":\"'$(docker inspect ${var.docker_image_tag} --format '{{.Id}}')'\"}' "]
277
+ }
278
+
279
+ resource "null_resource" "docker_publish" {
280
+ triggers = {
281
+ docker_digest = data.external.docker_digest.result.digest
282
+ repository_url = aws_ecr_repository.migration_handler.repository_url
283
+ docker_image_tag = var.docker_image_tag
284
+ }
285
+
286
+ provisioner "local-exec" {
287
+ command = <<-EOT
288
+ aws ecr get-login-password --region ${data.aws_region.current.region} | docker login --username AWS --password-stdin ${self.triggers.repository_url}
289
+ docker tag ${self.triggers.docker_image_tag} ${self.triggers.repository_url}:latest
290
+ docker push ${self.triggers.repository_url}:latest
291
+ EOT
292
+ }
293
+
294
+ depends_on = [aws_ecr_repository_policy.migration_handler]
295
+ }
296
+
297
+ resource "aws_iam_role" "migration_handler" {
298
+ name = "<%= nameClassName %>MigrationHandlerRole-${random_string.suffix.result}"
299
+
300
+ assume_role_policy = jsonencode({
301
+ Version = "2012-10-17"
302
+ Statement = [
303
+ {
304
+ Effect = "Allow"
305
+ Principal = {
306
+ Service = "lambda.amazonaws.com"
307
+ }
308
+ Action = "sts:AssumeRole"
309
+ }
310
+ ]
311
+ })
312
+
313
+ tags = var.tags
314
+ }
315
+
316
+ resource "aws_iam_role_policy_attachment" "migration_handler_basic_execution" {
317
+ role = aws_iam_role.migration_handler.name
318
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
319
+ }
320
+
321
+ resource "aws_iam_role_policy_attachment" "migration_handler_vpc_access" {
322
+ role = aws_iam_role.migration_handler.name
323
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
324
+ }
325
+
326
+ resource "aws_iam_role_policy_attachment" "migration_handler_xray" {
327
+ role = aws_iam_role.migration_handler.name
328
+ policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
329
+ }
330
+
331
+ resource "aws_iam_role_policy" "migration_handler_access" {
332
+ role = aws_iam_role.migration_handler.name
333
+
334
+ policy = jsonencode({
335
+ Version = "2012-10-17"
336
+ Statement = [
337
+ <%_ if (engine === 'mysql') { _%>
338
+ {
339
+ Effect = "Allow"
340
+ Action = [
341
+ "secretsmanager:GetSecretValue"
342
+ ]
343
+ Resource = [module.aurora.secret_arn]
344
+ },
345
+ {
346
+ Effect = "Allow"
347
+ Action = [
348
+ "kms:Decrypt"
349
+ ]
350
+ Resource = [module.aurora.kms_key_arn]
351
+ }
352
+ <%_ } else { _%>
353
+ {
354
+ Effect = "Allow"
355
+ Action = [
356
+ "rds-db:connect"
357
+ ]
358
+ Resource = [
359
+ "arn:aws:rds-db:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:dbuser:${var.enable_rds_proxy ? module.aurora.proxy_resource_id : module.aurora.cluster_resource_id}/${local.database_runtime_user}"
360
+ ]
361
+ }
362
+ <%_ } _%>
363
+ ]
364
+ })
365
+ }
366
+
367
+ resource "aws_iam_role" "create_db_user" {
368
+ name = "<%= nameClassName %>CreateDbUserRole-${random_string.suffix.result}"
369
+
370
+ assume_role_policy = jsonencode({
371
+ Version = "2012-10-17"
372
+ Statement = [
373
+ {
374
+ Effect = "Allow"
375
+ Principal = {
376
+ Service = "lambda.amazonaws.com"
377
+ }
378
+ Action = "sts:AssumeRole"
379
+ }
380
+ ]
381
+ })
382
+
383
+ tags = var.tags
384
+ }
385
+
386
+ resource "aws_iam_role_policy_attachment" "create_db_user_basic_execution" {
387
+ role = aws_iam_role.create_db_user.name
388
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
389
+ }
390
+
391
+ resource "aws_iam_role_policy_attachment" "create_db_user_vpc_access" {
392
+ role = aws_iam_role.create_db_user.name
393
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
394
+ }
395
+
396
+ resource "aws_iam_role_policy_attachment" "create_db_user_xray" {
397
+ role = aws_iam_role.create_db_user.name
398
+ policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
399
+ }
400
+
401
+ resource "aws_iam_role_policy" "create_db_user_secret_access" {
402
+ role = aws_iam_role.create_db_user.name
403
+
404
+ policy = jsonencode({
405
+ Version = "2012-10-17"
406
+ Statement = [
407
+ {
408
+ Effect = "Allow"
409
+ Action = [
410
+ "secretsmanager:GetSecretValue"
411
+ ]
412
+ Resource = [module.aurora.secret_arn]
413
+ },
414
+ {
415
+ Effect = "Allow"
416
+ Action = [
417
+ "kms:Decrypt"
418
+ ]
419
+ Resource = [module.aurora.kms_key_arn]
420
+ }
421
+ ]
422
+ })
423
+ }
424
+
425
+ resource "aws_cloudwatch_log_group" "migration_handler" {
426
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
427
+ #checkov:skip=CKV_AWS_338:Log retention set to forever
428
+ #checkov:skip=CKV_AWS_66:Log retention set to forever
429
+ name = "/aws/lambda/${local.migration_function_name}"
430
+ tags = var.tags
431
+ }
432
+
433
+ resource "aws_cloudwatch_log_group" "create_db_user" {
434
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
435
+ #checkov:skip=CKV_AWS_338:Log retention set to forever
436
+ #checkov:skip=CKV_AWS_66:Log retention set to forever
437
+ name = "/aws/lambda/${local.create_db_user_function_name}"
438
+ tags = var.tags
439
+ }
440
+
441
+ resource "aws_security_group" "migration_handler" {
442
+ name_prefix = "<%= nameKebabCase %>-migration-"
443
+ description = "Security group for the migration Lambda function"
444
+ vpc_id = var.vpc_id
445
+ tags = var.tags
446
+ }
447
+
448
+ resource "aws_vpc_security_group_egress_rule" "migration_handler_to_database" {
449
+ <%_ if (engine === 'mysql') { _%>
450
+ security_group_id = aws_security_group.migration_handler.id
451
+ referenced_security_group_id = module.aurora.database_security_group_id
452
+ <%_ } else { _%>
453
+ security_group_id = aws_security_group.migration_handler.id
454
+ referenced_security_group_id = module.aurora.security_group_id
455
+ <%_ } _%>
456
+ from_port = module.aurora.cluster_port
457
+ to_port = module.aurora.cluster_port
458
+ ip_protocol = "tcp"
459
+ description = "Allow outbound traffic to Aurora on database port"
460
+ }
461
+
462
+ resource "aws_vpc_security_group_egress_rule" "migration_handler_https" {
463
+ security_group_id = aws_security_group.migration_handler.id
464
+ cidr_ipv4 = "0.0.0.0/0"
465
+ from_port = 443
466
+ to_port = 443
467
+ ip_protocol = "tcp"
468
+ description = "Allow outbound HTTPS to AWS service endpoints"
469
+ }
470
+
471
+ resource "aws_security_group" "create_db_user" {
472
+ name_prefix = "<%= nameKebabCase %>-create-db-user-"
473
+ description = "Security group for the create-db-user Lambda function"
474
+ vpc_id = var.vpc_id
475
+ tags = var.tags
476
+ }
477
+
478
+ resource "aws_vpc_security_group_egress_rule" "create_db_user_to_database" {
479
+ security_group_id = aws_security_group.create_db_user.id
480
+ referenced_security_group_id = module.aurora.database_security_group_id
481
+ from_port = module.aurora.cluster_port
482
+ to_port = module.aurora.cluster_port
483
+ ip_protocol = "tcp"
484
+ description = "Allow outbound traffic to Aurora on database port"
485
+ }
486
+
487
+ resource "aws_vpc_security_group_egress_rule" "create_db_user_https" {
488
+ security_group_id = aws_security_group.create_db_user.id
489
+ cidr_ipv4 = "0.0.0.0/0"
490
+ from_port = 443
491
+ to_port = 443
492
+ ip_protocol = "tcp"
493
+ description = "Allow outbound HTTPS to AWS service endpoints"
494
+ }
495
+
496
+ resource "aws_vpc_security_group_ingress_rule" "migration_handler_to_database" {
497
+ <%_ if (engine === 'mysql') { _%>
498
+ security_group_id = module.aurora.database_security_group_id
499
+ <%_ } else { _%>
500
+ security_group_id = module.aurora.security_group_id
501
+ <%_ } _%>
502
+ referenced_security_group_id = aws_security_group.migration_handler.id
503
+ from_port = module.aurora.cluster_port
504
+ to_port = module.aurora.cluster_port
505
+ ip_protocol = "tcp"
506
+ description = "Allow inbound traffic from migration Lambda on database port"
507
+ }
508
+
509
+ resource "aws_vpc_security_group_ingress_rule" "create_db_user_to_database" {
510
+ security_group_id = module.aurora.database_security_group_id
511
+ referenced_security_group_id = aws_security_group.create_db_user.id
512
+ from_port = module.aurora.cluster_port
513
+ to_port = module.aurora.cluster_port
514
+ ip_protocol = "tcp"
515
+ description = "Allow inbound traffic from create-db-user Lambda on database port"
516
+ }
517
+
518
+ resource "aws_lambda_function" "create_db_user" {
519
+ #checkov:skip=CKV_AWS_116:Dead Letter Queue not required for the create-db-user handler
520
+ #checkov:skip=CKV_AWS_115:Concurrent execution limit not required for the create-db-user handler
521
+ #checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
522
+ #checkov:skip=CKV_AWS_272:Code signing not configured as deployment packages are built and deployed within the same pipeline
523
+ s3_bucket = aws_s3_object.create_db_user_zip.bucket
524
+ s3_key = aws_s3_object.create_db_user_zip.key
525
+ s3_object_version = aws_s3_object.create_db_user_zip.version_id
526
+ function_name = local.create_db_user_function_name
527
+ role = aws_iam_role.create_db_user.arn
528
+ handler = "index.handler"
529
+ source_code_hash = data.archive_file.create_db_user_zip.output_base64sha256
530
+ runtime = "nodejs24.x"
531
+ timeout = 300
532
+ architectures = ["arm64"]
533
+
534
+ tracing_config {
535
+ mode = "Active"
536
+ }
537
+
538
+ vpc_config {
539
+ subnet_ids = var.lambda_subnet_ids
540
+ security_group_ids = [aws_security_group.create_db_user.id]
541
+ }
542
+
543
+ environment {
544
+ variables = {
545
+ DATABASE_SECRET_ARN = module.aurora.secret_arn
546
+ NODE_EXTRA_CA_CERTS = "/var/runtime/ca-cert.pem"
547
+ }
548
+ }
549
+
550
+ tags = var.tags
551
+
552
+ depends_on = [
553
+ aws_s3_object.create_db_user_zip,
554
+ aws_iam_role_policy_attachment.create_db_user_basic_execution,
555
+ aws_iam_role_policy_attachment.create_db_user_vpc_access,
556
+ aws_iam_role_policy_attachment.create_db_user_xray,
557
+ aws_iam_role_policy.create_db_user_secret_access,
558
+ aws_cloudwatch_log_group.create_db_user,
559
+ aws_vpc_security_group_ingress_rule.create_db_user_to_database
560
+ ]
561
+ }
562
+
563
+ resource "null_resource" "create_db_user_trigger" {
564
+ triggers = {
565
+ cluster_arn = module.aurora.cluster_arn
566
+ function_name = aws_lambda_function.create_db_user.function_name
567
+ db_user = local.database_runtime_user
568
+ bundle_hash = data.archive_file.create_db_user_zip.output_base64sha256
569
+ }
570
+
571
+ provisioner "local-exec" {
572
+ command = <<-EOT
573
+ output_file=$(mktemp)
574
+ response=$(aws lambda invoke \
575
+ --region ${data.aws_region.current.region} \
576
+ --function-name ${self.triggers.function_name} \
577
+ --cli-binary-format raw-in-base64-out \
578
+ --payload '{"RequestType":"Create","PhysicalResourceId":"db-user:${self.triggers.db_user}"}' \
579
+ "$output_file")
580
+ cat "$output_file"
581
+ echo "$response"
582
+ if echo "$response" | grep -q '"FunctionError"'; then
583
+ rm -f "$output_file"
584
+ exit 1
585
+ fi
586
+ rm -f "$output_file"
587
+ EOT
588
+ }
589
+
590
+ depends_on = [aws_lambda_function.create_db_user, module.aurora]
591
+ }
592
+
593
+ resource "aws_lambda_function" "migration_handler" {
594
+ #checkov:skip=CKV_AWS_116:Dead Letter Queue not required for the migration handler
595
+ #checkov:skip=CKV_AWS_115:Concurrent execution limit not required for the migration handler
596
+ #checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
597
+ #checkov:skip=CKV_AWS_272:Code signing does not apply to container image Lambda functions
598
+ package_type = "Image"
599
+ function_name = local.migration_function_name
600
+ role = aws_iam_role.migration_handler.arn
601
+ image_uri = "${aws_ecr_repository.migration_handler.repository_url}:latest"
602
+ memory_size = 1024
603
+ timeout = 300
604
+ architectures = ["arm64"]
605
+
606
+ tracing_config {
607
+ mode = "Active"
608
+ }
609
+
610
+ vpc_config {
611
+ subnet_ids = var.lambda_subnet_ids
612
+ security_group_ids = [aws_security_group.migration_handler.id]
613
+ }
614
+
615
+ environment {
616
+ variables = {
617
+ <%_ if (engine === 'mysql') { _%>
618
+ DATABASE_SECRET_ARN = module.aurora.secret_arn
619
+ <%_ } else { _%>
620
+ HOSTNAME = module.aurora.cluster_endpoint
621
+ DATABASE = module.aurora.database_name
622
+ PORT = tostring(module.aurora.cluster_port)
623
+ DBUSER = local.database_runtime_user
624
+ <%_ } _%>
625
+ }
626
+ }
627
+
628
+ tags = var.tags
629
+
630
+ depends_on = [
631
+ null_resource.docker_publish,
632
+ aws_iam_role_policy_attachment.migration_handler_basic_execution,
633
+ aws_iam_role_policy_attachment.migration_handler_vpc_access,
634
+ aws_iam_role_policy_attachment.migration_handler_xray,
635
+ aws_iam_role_policy.migration_handler_access,
636
+ aws_cloudwatch_log_group.migration_handler,
637
+ aws_vpc_security_group_ingress_rule.migration_handler_to_database
638
+ ]
639
+ }
640
+
641
+ resource "null_resource" "migration_trigger" {
642
+ triggers = {
643
+ docker_digest = data.external.docker_digest.result.digest
644
+ cluster_arn = module.aurora.cluster_arn
645
+ database_ready = module.aurora.database_ready
646
+ function_name = aws_lambda_function.migration_handler.function_name
647
+ <%_ if (engine !== 'mysql') { _%>
648
+ db_user = local.database_runtime_user
649
+ <%_ } _%>
650
+ }
651
+
652
+ provisioner "local-exec" {
653
+ command = <<-EOT
654
+ output_file=$(mktemp)
655
+ response=$(aws lambda invoke \
656
+ --region ${data.aws_region.current.region} \
657
+ --function-name ${self.triggers.function_name} \
658
+ --cli-binary-format raw-in-base64-out \
659
+ "$output_file")
660
+ cat "$output_file"
661
+ echo "$response"
662
+ if echo "$response" | grep -q '"FunctionError"'; then
663
+ rm -f "$output_file"
664
+ exit 1
665
+ fi
666
+ rm -f "$output_file"
667
+ EOT
668
+ }
669
+
670
+ depends_on = [
671
+ aws_lambda_function.migration_handler,
672
+ module.aurora,
673
+ null_resource.create_db_user_trigger,
674
+ aws_iam_role_policy.proxy_db_user_connect
675
+ ]
676
+ }
677
+
678
+ output "security_group_id" {
679
+ description = "Security group ID to allow inbound connections to the database (proxy SG if RDS Proxy is enabled, otherwise DB SG)."
680
+ value = module.aurora.security_group_id
681
+ }
682
+
683
+ output "database_security_group_id" {
684
+ description = "Security group ID of the Aurora cluster directly, used for direct DB access rules (e.g. when RDS Proxy is disabled)."
685
+ value = module.aurora.database_security_group_id
686
+ }
687
+
688
+ output "cluster_resource_id" {
689
+ description = "Resource ID of the Aurora cluster, used for IAM rds-db:connect policies."
690
+ value = module.aurora.cluster_resource_id
691
+ }
692
+
693
+ output "proxy_resource_id" {
694
+ description = "Resource identifier of the RDS Proxy (prx-XXXX), used for IAM rds-db:connect policies when connecting through the proxy. Null if RDS Proxy is disabled."
695
+ value = module.aurora.proxy_resource_id
696
+ }
697
+
698
+ output "kms_key_arn" {
699
+ description = "ARN of the KMS key protecting Aurora and the admin credentials secret, used to grant decrypt access alongside secret_arn."
700
+ value = module.aurora.kms_key_arn
701
+ }
702
+
703
+ output "cluster_arn" {
704
+ description = "ARN of the Aurora cluster."
705
+ value = module.aurora.cluster_arn
706
+ }
707
+
708
+ output "cluster_endpoint" {
709
+ description = "Writer endpoint of the Aurora cluster."
710
+ value = module.aurora.cluster_endpoint
711
+ }
712
+
713
+ output "reader_endpoint" {
714
+ description = "Reader endpoint of the Aurora cluster."
715
+ value = module.aurora.reader_endpoint
716
+ }
717
+
718
+ output "cluster_port" {
719
+ description = "Port exposed by the Aurora cluster."
720
+ value = module.aurora.cluster_port
721
+ }
722
+
723
+ output "secret_arn" {
724
+ description = "ARN of the generated admin credentials secret."
725
+ value = module.aurora.secret_arn
726
+ }
727
+
728
+ output "database_runtime_user" {
729
+ description = "Application database user created for IAM-authenticated access."
730
+ value = local.database_runtime_user
731
+ }
732
+