@aws/nx-plugin 0.110.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 (67) hide show
  1. package/README.md +1 -0
  2. package/generators.json +6 -0
  3. package/package.json +1 -1
  4. package/src/connection/agent-serve-local.js +7 -8
  5. package/src/connection/agent-serve-local.js.map +1 -1
  6. package/src/connection/serve-local.js +7 -8
  7. package/src/connection/serve-local.js.map +1 -1
  8. package/src/infra/app/generator.js +2 -5
  9. package/src/infra/app/generator.js.map +1 -1
  10. package/src/preset/__snapshots__/generator.spec.ts.snap +2 -0
  11. package/src/py/fast-api/__snapshots__/generator.spec.ts.snap +2 -2
  12. package/src/py/fast-api/files/app/__name__/init.py.template +2 -2
  13. package/src/py/project/generator.js +17 -1
  14. package/src/py/project/generator.js.map +1 -1
  15. package/src/py/strands-agent/__snapshots__/generator.spec.ts.snap +1 -1
  16. package/src/py/strands-agent/a2a-connection/generator.js +4 -8
  17. package/src/py/strands-agent/a2a-connection/generator.js.map +1 -1
  18. package/src/py/strands-agent/files/http/init.py.template +2 -2
  19. package/src/py/strands-agent/mcp-connection/__snapshots__/generator.spec.ts.snap +8 -4
  20. package/src/py/strands-agent/mcp-connection/generator.js +4 -8
  21. package/src/py/strands-agent/mcp-connection/generator.js.map +1 -1
  22. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +4822 -0
  23. package/src/ts/rdb/files/Dockerfile.template +15 -0
  24. package/src/ts/rdb/files/prisma/models/example.prisma.template +5 -0
  25. package/src/ts/rdb/files/prisma/schema.prisma.template +9 -0
  26. package/src/ts/rdb/files/prisma.config.ts.template +24 -0
  27. package/src/ts/rdb/files/scripts/docker-pull.ts.template +15 -0
  28. package/src/ts/rdb/files/scripts/docker-start.ts.template +81 -0
  29. package/src/ts/rdb/files/scripts/wait-for-db.ts.template +55 -0
  30. package/src/ts/rdb/files/src/constants.ts.template +8 -0
  31. package/src/ts/rdb/files/src/create-db-user-handler.ts.template +128 -0
  32. package/src/ts/rdb/files/src/index.ts.template +2 -0
  33. package/src/ts/rdb/files/src/migration-handler.ts.template +54 -0
  34. package/src/ts/rdb/files/src/prisma.ts.template +104 -0
  35. package/src/ts/rdb/files/src/utils.ts.template +145 -0
  36. package/src/ts/rdb/generator.d.ts +10 -0
  37. package/src/ts/rdb/generator.js +206 -0
  38. package/src/ts/rdb/generator.js.map +1 -0
  39. package/src/ts/rdb/schema.d.ts +17 -0
  40. package/src/ts/rdb/schema.json +73 -0
  41. package/src/ts/strands-agent/a2a-connection/generator.js +4 -8
  42. package/src/ts/strands-agent/a2a-connection/generator.js.map +1 -1
  43. package/src/ts/strands-agent/mcp-connection/generator.js +4 -8
  44. package/src/ts/strands-agent/mcp-connection/generator.js.map +1 -1
  45. package/src/utils/agent-connection/files/py-core-mcp/agentcore_mcp_client.py.template +9 -3
  46. package/src/utils/agent-connection/files/py-core-runtime-config/runtime_config.py.template +3 -2
  47. package/src/utils/api-constructs/api-constructs.js +2 -10
  48. package/src/utils/api-constructs/api-constructs.js.map +1 -1
  49. package/src/utils/api-constructs/open-api-metadata.js +2 -7
  50. package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
  51. package/src/utils/function-constructs/function-constructs.js +2 -10
  52. package/src/utils/function-constructs/function-constructs.js.map +1 -1
  53. package/src/utils/nx.d.ts +14 -1
  54. package/src/utils/nx.js +27 -1
  55. package/src/utils/nx.js.map +1 -1
  56. package/src/utils/rdb-constructs/files/cdk/app/dbs/__nameKebabCase__.ts.template +43 -0
  57. package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +380 -0
  58. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +732 -0
  59. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +742 -0
  60. package/src/utils/rdb-constructs/rdb-constructs.d.ts +23 -0
  61. package/src/utils/rdb-constructs/rdb-constructs.js +59 -0
  62. package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -0
  63. package/src/utils/versions.d.ts +15 -3
  64. package/src/utils/versions.js +14 -2
  65. package/src/utils/versions.js.map +1 -1
  66. package/src/utils/website-constructs/website-constructs.js +2 -10
  67. package/src/utils/website-constructs/website-constructs.js.map +1 -1
@@ -0,0 +1,742 @@
1
+ # Core Aurora module
2
+ # This module creates an Aurora cluster and a generated admin secret.
3
+
4
+ terraform {
5
+ required_version = ">= 1.0"
6
+
7
+ required_providers {
8
+ aws = {
9
+ source = "hashicorp/aws"
10
+ version = "~> 6.33"
11
+ }
12
+ null = {
13
+ source = "hashicorp/null"
14
+ version = ">= 3.0"
15
+ }
16
+ random = {
17
+ source = "hashicorp/random"
18
+ version = ">= 3.0"
19
+ }
20
+ }
21
+ }
22
+
23
+ data "aws_caller_identity" "current" {}
24
+ data "aws_partition" "current" {}
25
+ data "aws_region" "current" {}
26
+
27
+ data "aws_iam_policy" "enhanced_monitoring" {
28
+ arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
29
+ }
30
+
31
+ variable "name" {
32
+ description = "Base name applied to Aurora resources."
33
+ type = string
34
+ }
35
+
36
+ variable "vpc_id" {
37
+ description = "VPC where the Aurora cluster will be deployed."
38
+ type = string
39
+ }
40
+
41
+ variable "subnet_ids" {
42
+ description = "Subnet IDs for the Aurora DB subnet group and RDS Proxy. These subnets do not need outbound egress — private isolated subnets are fine."
43
+ type = list(string)
44
+ }
45
+
46
+ variable "lambda_subnet_ids" {
47
+ description = "Subnet IDs for the credential-rotation Lambda. Must have outbound egress to AWS service endpoints (e.g. Secrets Manager) — use private subnets with a NAT gateway."
48
+ type = list(string)
49
+ }
50
+
51
+ variable "engine" {
52
+ description = "Aurora engine to use."
53
+ type = string
54
+ default = "aurora-postgresql"
55
+
56
+ validation {
57
+ condition = contains(["aurora-postgresql", "aurora-mysql"], var.engine)
58
+ error_message = "engine must be aurora-postgresql or aurora-mysql."
59
+ }
60
+ }
61
+
62
+ variable "engine_version" {
63
+ description = "Aurora engine version."
64
+ type = string
65
+ default = null
66
+ }
67
+
68
+ variable "database_name" {
69
+ description = "Initial database created in the cluster."
70
+ type = string
71
+ }
72
+
73
+ variable "admin_user" {
74
+ description = "Admin username stored in the generated Secrets Manager secret."
75
+ type = string
76
+ }
77
+
78
+ variable "port" {
79
+ description = "Database port for the selected Aurora engine."
80
+ type = number
81
+ default = null
82
+ }
83
+
84
+ variable "serverless_min_capacity" {
85
+ description = "Minimum Aurora Serverless v2 ACUs."
86
+ type = number
87
+ default = 0.5
88
+ }
89
+
90
+ variable "serverless_max_capacity" {
91
+ description = "Maximum Aurora Serverless v2 ACUs."
92
+ type = number
93
+ default = 4
94
+ }
95
+
96
+ variable "instance_count" {
97
+ description = "Number of Aurora instances to create."
98
+ type = number
99
+ default = 1
100
+ }
101
+
102
+ variable "deletion_protection" {
103
+ description = "Whether deletion protection is enabled."
104
+ type = bool
105
+ default = true
106
+ }
107
+
108
+ variable "skip_final_snapshot" {
109
+ description = "Whether to skip the final snapshot on deletion."
110
+ type = bool
111
+ default = false
112
+ }
113
+
114
+ variable "enable_rds_proxy" {
115
+ description = "Whether to provision an RDS Proxy in front of the Aurora cluster."
116
+ type = bool
117
+ default = true
118
+ }
119
+
120
+ variable "enable_credential_rotation" {
121
+ description = "Whether to enable automatic credential rotation for the admin secret."
122
+ type = bool
123
+ default = true
124
+ }
125
+
126
+ variable "enable_cloudwatch_logs" {
127
+ description = "Whether to export Aurora engine logs to CloudWatch. PostgreSQL also enables verbose statement logging."
128
+ type = bool
129
+ default = false
130
+ }
131
+
132
+ variable "enable_performance_insights" {
133
+ description = "Whether to enable Performance Insights on Aurora cluster instances."
134
+ type = bool
135
+ default = true
136
+ }
137
+
138
+ variable "performance_insights_retention_period" {
139
+ description = "Retention period, in days, for Performance Insights data when enabled."
140
+ type = number
141
+ default = 7
142
+ }
143
+
144
+ variable "enable_backup" {
145
+ description = "Whether to provision an AWS Backup plan for the Aurora cluster."
146
+ type = bool
147
+ default = false
148
+ }
149
+
150
+ variable "enable_key_rotation" {
151
+ description = "Whether to enable automatic key rotation on the KMS key used to encrypt the Aurora cluster and its credentials secret."
152
+ type = bool
153
+ default = true
154
+ }
155
+
156
+ variable "tags" {
157
+ description = "Tags to apply to all resources."
158
+ type = map(string)
159
+ default = {}
160
+ }
161
+
162
+ locals {
163
+ default_port = var.engine == "aurora-mysql" ? 3306 : 5432
164
+ default_engine_version = var.engine == "aurora-mysql" ? "8.0.mysql_aurora.3.12.0" : "17.7"
165
+ parameter_group_family = var.engine == "aurora-mysql" ? "aurora-mysql8.0" : "aurora-postgresql${split(".", coalesce(var.engine_version, local.default_engine_version))[0]}"
166
+ }
167
+
168
+ resource "aws_rds_cluster_parameter_group" "database" {
169
+ count = var.enable_cloudwatch_logs ? 1 : 0
170
+
171
+ name_prefix = "${var.name}-"
172
+ family = local.parameter_group_family
173
+ description = "Parameter group for ${var.name} Aurora cluster"
174
+
175
+ dynamic "parameter" {
176
+ for_each = var.engine == "aurora-postgresql" ? [1] : []
177
+ content {
178
+ name = "log_statement"
179
+ value = "all"
180
+ }
181
+ }
182
+
183
+ tags = var.tags
184
+ }
185
+
186
+ resource "aws_kms_key" "database" {
187
+ description = "KMS key for Aurora cluster ${var.name}"
188
+ enable_key_rotation = var.enable_key_rotation
189
+
190
+ policy = jsonencode({
191
+ Version = "2012-10-17"
192
+ Statement = [
193
+ {
194
+ Sid = "EnableRootAccess"
195
+ Effect = "Allow"
196
+ Principal = {
197
+ AWS = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"
198
+ }
199
+ Action = "kms:*"
200
+ Resource = "*"
201
+ },
202
+ {
203
+ Sid = "AllowRDSService"
204
+ Effect = "Allow"
205
+ Principal = {
206
+ Service = "rds.amazonaws.com"
207
+ }
208
+ Action = [
209
+ "kms:Encrypt",
210
+ "kms:Decrypt",
211
+ "kms:ReEncrypt*",
212
+ "kms:GenerateDataKey*",
213
+ "kms:DescribeKey",
214
+ "kms:CreateGrant"
215
+ ]
216
+ Resource = "*"
217
+ }
218
+ ]
219
+ })
220
+
221
+ tags = merge(var.tags, {
222
+ Name = "${var.name}-aurora"
223
+ })
224
+ }
225
+
226
+ resource "aws_iam_role" "enhanced_monitoring" {
227
+ name_prefix = "${var.name}-aurora-monitoring-"
228
+
229
+ assume_role_policy = jsonencode({
230
+ Version = "2012-10-17"
231
+ Statement = [
232
+ {
233
+ Effect = "Allow"
234
+ Principal = {
235
+ Service = "monitoring.rds.amazonaws.com"
236
+ }
237
+ Action = "sts:AssumeRole"
238
+ }
239
+ ]
240
+ })
241
+
242
+ tags = merge(var.tags, {
243
+ Name = "${var.name}-aurora-monitoring"
244
+ })
245
+ }
246
+
247
+ resource "aws_iam_role_policy_attachment" "enhanced_monitoring" {
248
+ role = aws_iam_role.enhanced_monitoring.name
249
+ policy_arn = data.aws_iam_policy.enhanced_monitoring.arn
250
+ }
251
+
252
+ resource "aws_security_group" "database" {
253
+ name_prefix = "${var.name}-aurora-"
254
+ description = "Security group for Aurora cluster ${var.name}"
255
+ vpc_id = var.vpc_id
256
+
257
+ tags = merge(var.tags, {
258
+ Name = "${var.name}-aurora"
259
+ })
260
+ }
261
+
262
+ resource "aws_db_subnet_group" "database" {
263
+ name = "${var.name}-aurora"
264
+ subnet_ids = var.subnet_ids
265
+
266
+ tags = merge(var.tags, {
267
+ Name = "${var.name}-aurora"
268
+ })
269
+ }
270
+
271
+ resource "random_password" "master_password" {
272
+ length = 32
273
+ special = true
274
+ override_special = "!#$%&*()-_=+[]{}<>:?"
275
+ }
276
+
277
+ resource "aws_secretsmanager_secret" "credentials" {
278
+ #checkov:skip=CKV2_AWS_57:Rotation is configured via aws_cloudformation_stack.credentials_rotation using the AWS::SecretsManager-2024-09-16 transform; Checkov cannot resolve rotation schedules created by CloudFormation transforms
279
+ name_prefix = "${var.name}-aurora-credentials-"
280
+ kms_key_id = aws_kms_key.database.arn
281
+
282
+ tags = merge(var.tags, {
283
+ Name = "${var.name}-aurora-credentials"
284
+ })
285
+ }
286
+
287
+ resource "aws_secretsmanager_secret_version" "credentials" {
288
+ secret_id = aws_secretsmanager_secret.credentials.id
289
+ secret_string = jsonencode({
290
+ engine = var.engine == "aurora-mysql" ? "mysql" : "postgres"
291
+ username = var.admin_user
292
+ password = random_password.master_password.result
293
+ host = aws_rds_cluster.database.endpoint
294
+ port = aws_rds_cluster.database.port
295
+ dbname = var.database_name
296
+ dbClusterIdentifier = aws_rds_cluster.database.cluster_identifier
297
+ })
298
+ }
299
+
300
+ resource "aws_rds_cluster" "database" {
301
+ #checkov:skip=CKV2_AWS_27:Query logging can be enabled with enable_cloudwatch_logs; this module defaults to CDK-equivalent behavior unless logging is requested
302
+ #checkov:skip=CKV_AWS_139:Deletion protection is enabled but checkov is unable to detect it correctly
303
+ cluster_identifier = "${var.name}-aurora"
304
+ engine = var.engine
305
+ engine_version = coalesce(var.engine_version, local.default_engine_version)
306
+ database_name = var.database_name
307
+ master_username = var.admin_user
308
+ master_password = random_password.master_password.result
309
+ db_subnet_group_name = aws_db_subnet_group.database.name
310
+ db_cluster_parameter_group_name = var.enable_cloudwatch_logs ? aws_rds_cluster_parameter_group.database[0].name : null
311
+ vpc_security_group_ids = [aws_security_group.database.id]
312
+ port = coalesce(var.port, local.default_port)
313
+ storage_encrypted = true
314
+ kms_key_id = aws_kms_key.database.arn
315
+ deletion_protection = var.deletion_protection
316
+ skip_final_snapshot = var.skip_final_snapshot
317
+ final_snapshot_identifier = var.skip_final_snapshot ? null : "${var.name}-aurora-final-snapshot"
318
+ iam_database_authentication_enabled = true
319
+ monitoring_interval = 5
320
+ monitoring_role_arn = aws_iam_role.enhanced_monitoring.arn
321
+ copy_tags_to_snapshot = true
322
+ enabled_cloudwatch_logs_exports = var.enable_cloudwatch_logs ? (var.engine == "aurora-mysql" ? ["audit", "error", "general", "slowquery"] : ["postgresql"]) : null
323
+
324
+ serverlessv2_scaling_configuration {
325
+ min_capacity = var.serverless_min_capacity
326
+ max_capacity = var.serverless_max_capacity
327
+ }
328
+
329
+ tags = merge(var.tags, {
330
+ Name = "${var.name}-aurora"
331
+ })
332
+ }
333
+
334
+ resource "aws_rds_cluster_instance" "database" {
335
+ count = var.instance_count
336
+
337
+ identifier = "${var.name}-aurora-${count.index + 1}"
338
+ cluster_identifier = aws_rds_cluster.database.id
339
+ instance_class = "db.serverless"
340
+ engine = aws_rds_cluster.database.engine
341
+ engine_version = aws_rds_cluster.database.engine_version
342
+ db_subnet_group_name = aws_db_subnet_group.database.name
343
+
344
+ auto_minor_version_upgrade = true
345
+ monitoring_interval = 5
346
+ monitoring_role_arn = aws_iam_role.enhanced_monitoring.arn
347
+ performance_insights_enabled = var.enable_performance_insights
348
+ performance_insights_kms_key_id = var.enable_performance_insights ? aws_kms_key.database.arn : null
349
+ performance_insights_retention_period = var.enable_performance_insights ? var.performance_insights_retention_period : null
350
+
351
+ tags = merge(var.tags, {
352
+ Name = "${var.name}-aurora-${count.index + 1}"
353
+ })
354
+ }
355
+
356
+ resource "aws_security_group" "rotation" {
357
+ #checkov:skip=CKV2_AWS_5:Security group is attached to the hosted rotation Lambda created by AWS::SecretsManager::RotationSchedule; Checkov cannot resolve resources created by the CloudFormation transform
358
+ count = var.enable_credential_rotation ? 1 : 0
359
+ name_prefix = "${var.name}-aurora-rotation-"
360
+ description = "Security group for Aurora secret rotation ${var.name}"
361
+ vpc_id = var.vpc_id
362
+
363
+ tags = merge(var.tags, {
364
+ Name = "${var.name}-aurora-rotation"
365
+ })
366
+ }
367
+
368
+ resource "aws_vpc_security_group_egress_rule" "rotation_to_database" {
369
+ count = var.enable_credential_rotation ? 1 : 0
370
+ security_group_id = aws_security_group.rotation[0].id
371
+ referenced_security_group_id = aws_security_group.database.id
372
+ from_port = coalesce(var.port, local.default_port)
373
+ to_port = coalesce(var.port, local.default_port)
374
+ ip_protocol = "tcp"
375
+ description = "Allow outbound database traffic from secret rotation Lambda"
376
+ }
377
+
378
+ resource "aws_vpc_security_group_ingress_rule" "rotation_to_database" {
379
+ count = var.enable_credential_rotation ? 1 : 0
380
+ security_group_id = aws_security_group.database.id
381
+ referenced_security_group_id = aws_security_group.rotation[0].id
382
+ from_port = coalesce(var.port, local.default_port)
383
+ to_port = coalesce(var.port, local.default_port)
384
+ ip_protocol = "tcp"
385
+ description = "Allow inbound database traffic from secret rotation Lambda"
386
+ }
387
+
388
+ resource "aws_cloudformation_stack" "credentials_rotation" {
389
+ #checkov:skip=CKV_AWS_124:SNS event notifications are not required for the credentials rotation stack
390
+ count = var.enable_credential_rotation ? 1 : 0
391
+ name = "${var.name}-aurora-credentials-rotation"
392
+
393
+ capabilities = ["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"]
394
+
395
+ template_body = jsonencode({
396
+ AWSTemplateFormatVersion = "2010-09-09"
397
+ Transform = "AWS::SecretsManager-2024-09-16"
398
+ Resources = {
399
+ CredentialsRotationSchedule = {
400
+ Type = "AWS::SecretsManager::RotationSchedule"
401
+ Properties = {
402
+ SecretId = aws_secretsmanager_secret.credentials.arn
403
+ HostedRotationLambda = {
404
+ RotationType = var.engine == "aurora-mysql" ? "MySQLSingleUser" : "PostgreSQLSingleUser"
405
+ RotationLambdaName = "${var.name}-aurora-credentials-rotation"
406
+ VpcSecurityGroupIds = aws_security_group.rotation[0].id
407
+ VpcSubnetIds = join(",", var.lambda_subnet_ids)
408
+ }
409
+ RotationRules = {
410
+ AutomaticallyAfterDays = 30
411
+ }
412
+ }
413
+ }
414
+ }
415
+ })
416
+
417
+ depends_on = [
418
+ aws_secretsmanager_secret_version.credentials,
419
+ aws_rds_cluster_instance.database,
420
+ aws_vpc_security_group_egress_rule.rotation_to_database,
421
+ aws_vpc_security_group_ingress_rule.rotation_to_database,
422
+ ]
423
+ }
424
+
425
+ resource "aws_iam_role" "proxy" {
426
+ count = var.enable_rds_proxy ? 1 : 0
427
+
428
+ name_prefix = "${var.name}-aurora-proxy-"
429
+
430
+ assume_role_policy = jsonencode({
431
+ Version = "2012-10-17"
432
+ Statement = [
433
+ {
434
+ Effect = "Allow"
435
+ Principal = {
436
+ Service = "rds.amazonaws.com"
437
+ }
438
+ Action = "sts:AssumeRole"
439
+ }
440
+ ]
441
+ })
442
+
443
+ tags = merge(var.tags, {
444
+ Name = "${var.name}-aurora-proxy"
445
+ })
446
+ }
447
+
448
+ resource "aws_iam_role_policy" "proxy_secret_access" {
449
+ count = var.enable_rds_proxy ? 1 : 0
450
+ role = aws_iam_role.proxy[0].name
451
+
452
+ policy = jsonencode({
453
+ Version = "2012-10-17"
454
+ Statement = [
455
+ {
456
+ Effect = "Allow"
457
+ Action = ["secretsmanager:GetSecretValue"]
458
+ Resource = [aws_secretsmanager_secret.credentials.arn]
459
+ },
460
+ {
461
+ Effect = "Allow"
462
+ Action = ["kms:Decrypt"]
463
+ Resource = [aws_kms_key.database.arn]
464
+ }
465
+ ]
466
+ })
467
+ }
468
+
469
+ resource "aws_security_group" "proxy" {
470
+ #checkov:skip=CKV2_AWS_5:Security group is attached to aws_db_proxy.aurora; Checkov cannot resolve count-conditional cross-resource references
471
+ count = var.enable_rds_proxy ? 1 : 0
472
+
473
+ name_prefix = "${var.name}-aurora-proxy-"
474
+ description = "Security group for Aurora proxy ${var.name}"
475
+ vpc_id = var.vpc_id
476
+
477
+ tags = merge(var.tags, {
478
+ Name = "${var.name}-aurora-proxy"
479
+ })
480
+ }
481
+
482
+ resource "aws_db_proxy" "aurora" {
483
+ count = var.enable_rds_proxy ? 1 : 0
484
+
485
+ name = "${var.name}-proxy"
486
+ default_auth_scheme = "IAM_AUTH"
487
+ engine_family = var.engine == "aurora-mysql" ? "MYSQL" : "POSTGRESQL"
488
+ role_arn = aws_iam_role.proxy[0].arn
489
+ vpc_subnet_ids = var.subnet_ids
490
+ vpc_security_group_ids = [aws_security_group.proxy[0].id]
491
+ require_tls = true
492
+
493
+ auth {
494
+ auth_scheme = "SECRETS"
495
+ iam_auth = "REQUIRED"
496
+ secret_arn = aws_secretsmanager_secret.credentials.arn
497
+ }
498
+
499
+ depends_on = [
500
+ aws_iam_role_policy.proxy_secret_access,
501
+ aws_secretsmanager_secret_version.credentials,
502
+ ]
503
+ }
504
+
505
+ resource "aws_db_proxy_default_target_group" "default" {
506
+ count = var.enable_rds_proxy ? 1 : 0
507
+
508
+ db_proxy_name = aws_db_proxy.aurora[0].name
509
+ }
510
+
511
+ resource "aws_db_proxy_target" "default" {
512
+ count = var.enable_rds_proxy ? 1 : 0
513
+
514
+ db_proxy_name = aws_db_proxy.aurora[0].name
515
+ target_group_name = aws_db_proxy_default_target_group.default[0].name
516
+ db_cluster_identifier = aws_rds_cluster.database.cluster_identifier
517
+ }
518
+
519
+ resource "null_resource" "proxy_target_ready" {
520
+ count = var.enable_rds_proxy ? 1 : 0
521
+
522
+ triggers = {
523
+ db_proxy_name = aws_db_proxy.aurora[0].name
524
+ target_group_name = aws_db_proxy_default_target_group.default[0].name
525
+ db_cluster_identifier = aws_rds_cluster.database.cluster_identifier
526
+ }
527
+
528
+ provisioner "local-exec" {
529
+ command = <<-EOT
530
+ start_time=$(date +%s)
531
+ deadline=$((start_time + 600))
532
+ while true; do
533
+ target_state=$(aws rds describe-db-proxy-targets \
534
+ --region ${data.aws_region.current.region} \
535
+ --db-proxy-name ${self.triggers.db_proxy_name} \
536
+ --target-group-name ${self.triggers.target_group_name} \
537
+ --query "Targets[?Type=='RDS_INSTANCE'] | [0].TargetHealth.State" \
538
+ --output text)
539
+
540
+ if [ "$target_state" = "AVAILABLE" ]; then
541
+ exit 0
542
+ fi
543
+
544
+ current_time=$(date +%s)
545
+ if [ "$current_time" -ge "$deadline" ]; then
546
+ echo "RDS Proxy target ${self.triggers.db_cluster_identifier} did not become AVAILABLE within 10 minutes. Last state: $target_state"
547
+ exit 1
548
+ fi
549
+
550
+ sleep 10
551
+ done
552
+ EOT
553
+ }
554
+
555
+ depends_on = [
556
+ aws_db_proxy_target.default,
557
+ aws_rds_cluster_instance.database,
558
+ aws_vpc_security_group_ingress_rule.proxy_to_database,
559
+ aws_vpc_security_group_egress_rule.proxy_to_database
560
+ ]
561
+ }
562
+
563
+ resource "null_resource" "cluster_ready" {
564
+ count = var.enable_rds_proxy ? 0 : 1
565
+
566
+ triggers = {
567
+ cluster_identifier = aws_rds_cluster.database.cluster_identifier
568
+ instance_ids = join(",", aws_rds_cluster_instance.database[*].id)
569
+ }
570
+
571
+ depends_on = [aws_rds_cluster_instance.database]
572
+ }
573
+
574
+ resource "aws_vpc_security_group_ingress_rule" "proxy_to_database" {
575
+ count = var.enable_rds_proxy ? 1 : 0
576
+
577
+ security_group_id = aws_security_group.database.id
578
+ referenced_security_group_id = aws_security_group.proxy[0].id
579
+ from_port = coalesce(var.port, local.default_port)
580
+ to_port = coalesce(var.port, local.default_port)
581
+ ip_protocol = "tcp"
582
+ description = "Allow inbound database traffic from RDS Proxy"
583
+ }
584
+
585
+ resource "aws_vpc_security_group_egress_rule" "proxy_to_database" {
586
+ count = var.enable_rds_proxy ? 1 : 0
587
+
588
+ security_group_id = aws_security_group.proxy[0].id
589
+ referenced_security_group_id = aws_security_group.database.id
590
+ from_port = coalesce(var.port, local.default_port)
591
+ to_port = coalesce(var.port, local.default_port)
592
+ ip_protocol = "tcp"
593
+ description = "Allow outbound database traffic from RDS Proxy to Aurora cluster"
594
+ }
595
+
596
+ data "aws_iam_policy" "backup" {
597
+ count = var.enable_backup ? 1 : 0
598
+
599
+ arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
600
+ }
601
+
602
+ resource "aws_iam_role" "backup" {
603
+ count = var.enable_backup ? 1 : 0
604
+
605
+ name_prefix = "${var.name}-backup-"
606
+
607
+ assume_role_policy = jsonencode({
608
+ Version = "2012-10-17"
609
+ Statement = [
610
+ {
611
+ Effect = "Allow"
612
+ Principal = { Service = "backup.amazonaws.com" }
613
+ Action = "sts:AssumeRole"
614
+ }
615
+ ]
616
+ })
617
+
618
+ tags = var.tags
619
+ }
620
+
621
+ resource "aws_iam_role_policy_attachment" "backup" {
622
+ count = var.enable_backup ? 1 : 0
623
+
624
+ role = aws_iam_role.backup[0].name
625
+ policy_arn = data.aws_iam_policy.backup[0].arn
626
+ }
627
+
628
+ resource "aws_backup_vault" "database" {
629
+ count = var.enable_backup ? 1 : 0
630
+
631
+ name = "${var.name}-aurora-backup"
632
+ kms_key_arn = aws_kms_key.database.arn
633
+ tags = var.tags
634
+ }
635
+
636
+ resource "aws_backup_plan" "database" {
637
+ count = var.enable_backup ? 1 : 0
638
+
639
+ name = "${var.name}-aurora-backup"
640
+
641
+ rule {
642
+ rule_name = "daily-backup"
643
+ target_vault_name = aws_backup_vault.database[0].name
644
+ schedule = "cron(0 5 ? * * *)"
645
+
646
+ lifecycle {
647
+ delete_after = 35
648
+ }
649
+ }
650
+
651
+ tags = var.tags
652
+ }
653
+
654
+ resource "aws_backup_selection" "database" {
655
+ count = var.enable_backup ? 1 : 0
656
+
657
+ name = "${var.name}-aurora"
658
+ plan_id = aws_backup_plan.database[0].id
659
+ iam_role_arn = aws_iam_role.backup[0].arn
660
+
661
+ resources = [aws_rds_cluster.database.arn]
662
+ }
663
+
664
+ output "cluster_arn" {
665
+ description = "ARN of the Aurora cluster."
666
+ value = aws_rds_cluster.database.arn
667
+ }
668
+
669
+ output "cluster_resource_id" {
670
+ description = "Resource ID of the Aurora cluster used for IAM database authentication."
671
+ value = aws_rds_cluster.database.cluster_resource_id
672
+ }
673
+
674
+ output "proxy_resource_id" {
675
+ 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."
676
+ value = var.enable_rds_proxy ? split(":", aws_db_proxy.aurora[0].arn)[6] : null
677
+ }
678
+
679
+ output "cluster_endpoint" {
680
+ description = "Writer endpoint of the Aurora cluster or proxy."
681
+ value = var.enable_rds_proxy ? aws_db_proxy.aurora[0].endpoint : aws_rds_cluster.database.endpoint
682
+ }
683
+
684
+ output "reader_endpoint" {
685
+ description = "Reader endpoint of the Aurora cluster."
686
+ value = aws_rds_cluster.database.reader_endpoint
687
+ }
688
+
689
+ output "cluster_port" {
690
+ description = "Port exposed by the Aurora cluster."
691
+ value = aws_rds_cluster.database.port
692
+ }
693
+
694
+ output "security_group_id" {
695
+ description = "Security group protecting the Aurora proxy or cluster."
696
+ value = var.enable_rds_proxy ? aws_security_group.proxy[0].id : aws_security_group.database.id
697
+ }
698
+
699
+ output "database_security_group_id" {
700
+ description = "Security group protecting the Aurora cluster directly."
701
+ value = aws_security_group.database.id
702
+ }
703
+
704
+ output "proxy_role_name" {
705
+ description = "Name of the IAM role used by the RDS Proxy, or null if proxy is disabled."
706
+ value = var.enable_rds_proxy ? aws_iam_role.proxy[0].name : null
707
+ }
708
+
709
+ output "secret_arn" {
710
+ description = "ARN of the generated admin credentials secret."
711
+ value = aws_secretsmanager_secret.credentials.arn
712
+ }
713
+
714
+ output "kms_key_arn" {
715
+ description = "ARN of the KMS key protecting Aurora and the generated secret."
716
+ value = aws_kms_key.database.arn
717
+ }
718
+
719
+ output "database_host" {
720
+ description = "Hostname of the Aurora cluster writer endpoint or proxy endpoint."
721
+ value = var.enable_rds_proxy ? aws_db_proxy.aurora[0].endpoint : aws_rds_cluster.database.endpoint
722
+ }
723
+
724
+ output "database_ready" {
725
+ description = "Dependency marker that is resolved after the selected database connection path is ready."
726
+ value = true
727
+
728
+ depends_on = [
729
+ null_resource.proxy_target_ready,
730
+ null_resource.cluster_ready
731
+ ]
732
+ }
733
+
734
+ output "database_name" {
735
+ description = "Initial database created in the cluster."
736
+ value = var.database_name
737
+ }
738
+
739
+ output "admin_user" {
740
+ description = "Admin username stored in the generated secret."
741
+ value = jsondecode(aws_secretsmanager_secret_version.credentials.secret_string).username
742
+ }