mysystem-cli 1.0.0 → 1.0.1

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,69 @@
1
+ AWSTemplateFormatVersion: '2010-09-09'
2
+ Description: Bootstrap IAM Role for GitHub Actions OIDC connection to AWS (MySystem).
3
+
4
+ Parameters:
5
+ GitHubOrg:
6
+ Type: String
7
+ Description: Your GitHub Organization or Username (case-sensitive)
8
+ GitHubRepo:
9
+ Type: String
10
+ Description: Your GitHub Repository Name (case-sensitive)
11
+
12
+ Resources:
13
+ # OIDC Provider for GitHub (Created only if it doesn't exist, we assume it's created or we create it here)
14
+ # Note: Since there can only be one OIDC provider per account, we handle it. If it fails, users can use an existing one.
15
+ GithubOidcProvider:
16
+ Type: AWS::IAM::OIDCProvider
17
+ Properties:
18
+ Url: https://token.actions.githubusercontent.com
19
+ ClientIdList:
20
+ - sts.amazonaws.com
21
+ ThumbprintList:
22
+ - 6938fd4d98bab03faadb97b34396831e3780aea1 # GitHub Actions Thumbprint
23
+
24
+ # IAM Role that GitHub Actions will assume to deploy the app
25
+ MySystemDeployRole:
26
+ Type: AWS::IAM::Role
27
+ Properties:
28
+ RoleName: !Sub 'MySystemDeployRole-${GitHubRepo}'
29
+ Description: IAM Role assumed by GitHub Actions to deploy infrastructure and code.
30
+ AssumeRolePolicyDocument:
31
+ Version: '2012-10-17'
32
+ Statement:
33
+ - Effect: Allow
34
+ Principal:
35
+ Federated: !Ref GithubOidcProvider
36
+ Action: sts:AssumeRoleWithWebIdentity
37
+ Condition:
38
+ StringEquals:
39
+ token.actions.githubusercontent.com:aud: sts.amazonaws.com
40
+ StringLike:
41
+ token.actions.githubusercontent.com:sub: !Sub 'repo:${GitHubOrg}/${GitHubRepo}:*'
42
+ # Attach Policies to allow Terraform to provision VPC, ECS, RDS, ALB, ECR etc.
43
+ ManagedPolicyArns:
44
+ - arn:aws:iam::aws:policy/PowerUserAccess
45
+ Policies:
46
+ - PolicyName: MySystemAdditionalIAMPolicy
47
+ PolicyDocument:
48
+ Version: '2012-10-17'
49
+ Statement:
50
+ # PowerUserAccess does not allow managing IAM roles. Terraform needs to create ECS Task Execution Roles.
51
+ - Effect: Allow
52
+ Action:
53
+ - iam:CreateRole
54
+ - iam:DeleteRole
55
+ - iam:GetRole
56
+ - iam:PassRole
57
+ - iam:PutRolePolicy
58
+ - iam:DeleteRolePolicy
59
+ - iam:GetRolePolicy
60
+ - iam:AttachRolePolicy
61
+ - iam:DetachRolePolicy
62
+ Resource:
63
+ - !Sub 'arn:aws:iam::${AWS::AccountId}:role/*-ecs-execution-role'
64
+ - !Sub 'arn:aws:iam::${AWS::AccountId}:role/*-ecs-task-role'
65
+
66
+ Outputs:
67
+ RoleARN:
68
+ Description: The ARN of the IAM Role for GitHub Actions. Copy this to your GitHub Repository Secret `AWS_ROLE_ARN`.
69
+ Value: !GetAtt MySystemDeployRole.Arn
@@ -0,0 +1,40 @@
1
+ # AWS Cost Budget Alert
2
+ # Sends an email notification if the forecasted or actual AWS monthly spend exceeds the specified threshold.
3
+
4
+ variable "billing_email" {
5
+ type = string
6
+ description = "Email address to send AWS budget and billing alerts"
7
+ default = ""
8
+ }
9
+
10
+ variable "budget_limit" {
11
+ type = string
12
+ description = "Monthly budget limit in USD"
13
+ default = "20"
14
+ }
15
+
16
+ resource "aws_budgets_budget" "monthly_cost" {
17
+ count = var.billing_email != "" ? 1 : 0
18
+ name = "${var.app_name}-monthly-budget"
19
+ budget_type = "COST"
20
+ limit_amount = var.budget_limit
21
+ limit_unit = "USD"
22
+ time_period_start = "2026-01-01_00:00" # Arbitrary start date in the past/present
23
+ time_unit = "MONTHLY"
24
+
25
+ notification {
26
+ comparison_operator = "GREATER_THAN"
27
+ threshold = 80
28
+ threshold_type = "PERCENTAGE"
29
+ notification_type = "ACTUAL"
30
+ subscriber_email_addresses = [var.billing_email]
31
+ }
32
+
33
+ notification {
34
+ comparison_operator = "GREATER_THAN"
35
+ threshold = 100
36
+ threshold_type = "PERCENTAGE"
37
+ notification_type = "FORECASTED"
38
+ subscriber_email_addresses = [var.billing_email]
39
+ }
40
+ }
@@ -0,0 +1,46 @@
1
+ resource "random_password" "db_password" {
2
+ count = var.enable_database ? 1 : 0
3
+ length = 16
4
+ special = false
5
+ }
6
+
7
+ resource "aws_db_subnet_group" "main" {
8
+ count = var.enable_database ? 1 : 0
9
+ name = "${var.app_name}-db-subnet-group"
10
+ subnet_ids = aws_subnet.private[*].id
11
+
12
+ tags = {
13
+ Name = "${var.app_name}-db-subnet-group"
14
+ }
15
+ }
16
+
17
+ resource "aws_db_instance" "postgres" {
18
+ count = var.enable_database ? 1 : 0
19
+ identifier = "${var.app_name}-db"
20
+ allocated_storage = 20
21
+ max_allocated_storage = 100
22
+ engine = "postgres"
23
+ engine_version = "15"
24
+ instance_class = "db.t4g.micro" # cost-effective burstable Graviton instance
25
+ db_name = replace(var.app_name, "-", "_")
26
+ username = "mysystem_admin"
27
+ password = random_password.db_password[0].result
28
+ db_subnet_group_name = aws_db_subnet_group.main[0].name
29
+ vpc_security_group_ids = [aws_security_group.db[0].id]
30
+ skip_final_snapshot = true
31
+ publicly_accessible = false
32
+
33
+ tags = {
34
+ Name = "${var.app_name}-db-postgres"
35
+ }
36
+ }
37
+
38
+ # Save password in SSM Parameter Store so the developer can retrieve it if needed,
39
+ # and so ECS tasks can fetch it securely.
40
+ resource "aws_ssm_parameter" "db_url" {
41
+ count = var.enable_database ? 1 : 0
42
+ name = "/${var.app_name}/database_url"
43
+ type = "SecureString"
44
+ description = "Database URL for the application"
45
+ value = "postgresql://${aws_db_instance.postgres[0].username}:${random_password.db_password[0].result}@${var.enable_rds_proxy ? aws_db_proxy.postgres[0].endpoint : aws_db_instance.postgres[0].endpoint}/${aws_db_instance.postgres[0].db_name}"
46
+ }
@@ -0,0 +1,89 @@
1
+ # AWS ACM (SSL Certificate) and Custom Domain Routing
2
+ # Supports automated Route 53 setups or external DNS providers (GoDaddy, Cloudflare, Namecheap)
3
+
4
+ variable "enable_custom_domain" {
5
+ type = bool
6
+ description = "Enable custom domain and HTTPS SSL certificate"
7
+ default = false
8
+ }
9
+
10
+ variable "domain_name" {
11
+ type = string
12
+ description = "The custom domain name (e.g., app.example.com)"
13
+ default = ""
14
+ }
15
+
16
+ variable "dns_provider" {
17
+ type = string
18
+ description = "DNS provider for domain validation ('route53' or 'external')"
19
+ default = "external"
20
+ }
21
+
22
+ # 1. SSL/TLS Certificate via ACM
23
+ resource "aws_acm_certificate" "cert" {
24
+ count = var.enable_custom_domain ? 1 : 0
25
+ domain_name = var.domain_name
26
+ validation_method = "DNS"
27
+
28
+ lifecycle {
29
+ create_before_destroy = true
30
+ }
31
+
32
+ tags = {
33
+ Name = "${var.app_name}-cert"
34
+ }
35
+ }
36
+
37
+ # --- Route 53 DNS Configuration (Automated Setup) ---
38
+ data "aws_route53_zone" "primary" {
39
+ count = var.enable_custom_domain && var.dns_provider == "route53" ? 1 : 0
40
+ name = join(".", slice(split(".", var.domain_name), length(split(".", var.domain_name)) - 2, length(split(".", var.domain_name))))
41
+ private_zone = false
42
+ }
43
+
44
+ # Create DNS validation record in Route53
45
+ resource "aws_route53_record" "cert_validation" {
46
+ count = var.enable_custom_domain && var.dns_provider == "route53" ? 1 : 0
47
+ name = tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_name
48
+ type = tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_type
49
+ zone_id = data.aws_route53_zone.primary[0].zone_id
50
+ records = [tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_value]
51
+ ttl = 60
52
+ }
53
+
54
+ # Validate certificate in ACM (Wait for validation to complete)
55
+ resource "aws_acm_certificate_validation" "cert" {
56
+ count = var.enable_custom_domain && var.dns_provider == "route53" ? 1 : 0
57
+ certificate_arn = aws_acm_certificate.cert[0].arn
58
+ validation_record_fqdns = [aws_route53_record.cert_validation[0].fqdn]
59
+ }
60
+
61
+ # Create A record in Route53 pointing to the Load Balancer
62
+ resource "aws_route53_record" "app" {
63
+ count = var.enable_custom_domain && var.dns_provider == "route53" ? 1 : 0
64
+ zone_id = data.aws_route53_zone.primary[0].zone_id
65
+ name = var.domain_name
66
+ type = "A"
67
+
68
+ alias {
69
+ name = aws_lb.main.dns_name
70
+ zone_id = aws_lb.main.zone_id
71
+ evaluate_target_health = true
72
+ }
73
+ }
74
+
75
+ # --- Outputs for External DNS Setup (GoDaddy, Cloudflare, etc.) ---
76
+ output "dns_validation_name" {
77
+ value = var.enable_custom_domain ? tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_name : "None"
78
+ description = "CNAME Name to add to your DNS provider for SSL certificate validation"
79
+ }
80
+
81
+ output "dns_validation_value" {
82
+ value = var.enable_custom_domain ? tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_value : "None"
83
+ description = "CNAME Value/Alias to add to your DNS provider for SSL certificate validation"
84
+ }
85
+
86
+ output "app_cname_alias" {
87
+ value = aws_lb.main.dns_name
88
+ description = "Point your domain CNAME record to this target to route traffic to the app"
89
+ }
@@ -0,0 +1,110 @@
1
+ # --- Amazon ECR (Container Registry) ---
2
+ resource "aws_ecr_repository" "app" {
3
+ name = var.app_name
4
+ image_tag_mutability = "MUTABLE"
5
+ force_destroy = true
6
+
7
+ image_scanning_configuration {
8
+ scan_on_push = true
9
+ }
10
+ }
11
+
12
+ # --- ECS Cluster ---
13
+ resource "aws_ecs_cluster" "main" {
14
+ name = "${var.app_name}-cluster"
15
+ }
16
+
17
+ # --- CloudWatch Logs ---
18
+ resource "aws_cloudwatch_log_group" "ecs" {
19
+ name = "/ecs/${var.app_name}"
20
+ retention_in_days = 7
21
+ }
22
+
23
+ # --- ECS Task Definition ---
24
+ resource "aws_ecs_task_definition" "app" {
25
+ family = var.app_name
26
+ network_mode = "awsvpc"
27
+ requires_compatibilities = ["FARGATE"]
28
+ cpu = var.container_cpu
29
+ memory = var.container_memory
30
+ execution_role_arn = aws_iam_role.ecs_execution.arn
31
+ task_role_arn = aws_iam_role.ecs_task.arn
32
+
33
+ container_definitions = jsonencode([
34
+ {
35
+ name = var.app_name
36
+ # We use a placeholder image for the initial provision, so terraform succeeds.
37
+ # The CI/CD pipeline will overwrite this image during actual deployment.
38
+ image = "${aws_ecr_repository.app.repository_url}:latest"
39
+ essential = true
40
+ portMappings = [
41
+ {
42
+ containerPort = var.container_port
43
+ hostPort = var.container_port
44
+ }
45
+ ]
46
+ logConfiguration = {
47
+ logDriver = "awslogs"
48
+ options = {
49
+ "awslogs-group" = aws_cloudwatch_log_group.ecs.name
50
+ "awslogs-region" = var.aws_region
51
+ "awslogs-stream-prefix" = "ecs"
52
+ }
53
+ }
54
+ environment = concat(
55
+ [
56
+ {
57
+ name = "PORT"
58
+ value = tostring(var.container_port)
59
+ },
60
+ {
61
+ name = "NODE_ENV"
62
+ value = var.environment
63
+ }
64
+ ],
65
+ var.enable_redis ? [
66
+ {
67
+ name = "REDIS_URL"
68
+ value = "redis://${aws_elasticache_replication_group.redis[0].primary_endpoint_address}:6379"
69
+ }
70
+ ] : [],
71
+ var.sentry_dsn != "" ? [
72
+ {
73
+ name = "SENTRY_DSN"
74
+ value = var.sentry_dsn
75
+ }
76
+ ] : []
77
+ )
78
+ # Securely load database URL parameter if database is enabled
79
+ secrets = var.enable_database ? [
80
+ {
81
+ name = "DATABASE_URL"
82
+ valueFrom = aws_ssm_parameter.db_url[0].arn
83
+ }
84
+ ] : []
85
+ }
86
+ ])
87
+ }
88
+
89
+ # --- ECS Service ---
90
+ resource "aws_ecs_service" "main" {
91
+ name = var.app_name
92
+ cluster = aws_ecs_cluster.main.id
93
+ task_definition = aws_ecs_task_definition.app.arn
94
+ desired_count = 1
95
+ launch_type = "FARGATE"
96
+
97
+ network_configuration {
98
+ subnets = aws_subnet.public[*].id # Required for pulling ECR images without NAT Gateway
99
+ security_groups = [aws_security_group.ecs.id]
100
+ assign_public_ip = true
101
+ }
102
+
103
+ load_balancer {
104
+ target_group_arn = aws_lb_target_group.app.arn
105
+ container_name = var.app_name
106
+ container_port = var.container_port
107
+ }
108
+
109
+ depends_on = [aws_lb_listener.http]
110
+ }
@@ -0,0 +1,14 @@
1
+ output "ecr_repository_url" {
2
+ value = aws_ecr_repository.app.repository_url
3
+ description = "URL of the ECR repository"
4
+ }
5
+
6
+ output "application_url" {
7
+ value = "http://${aws_lb.main.dns_name}"
8
+ description = "Public URL of the Application Load Balancer"
9
+ }
10
+
11
+ output "database_endpoint" {
12
+ value = var.enable_database ? aws_db_instance.postgres[0].endpoint : "None"
13
+ description = "RDS database endpoint"
14
+ }
@@ -0,0 +1,21 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+ required_providers {
4
+ aws = {
5
+ source = "hashicorp/aws"
6
+ version = "~> 5.0"
7
+ }
8
+ }
9
+ }
10
+
11
+ provider "aws" {
12
+ region = var.aws_region
13
+
14
+ default_tags {
15
+ tags = {
16
+ Environment = var.environment
17
+ ManagedBy = "MySystem"
18
+ Project = var.app_name
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,157 @@
1
+ # AWS RDS Proxy (Managed PgBouncer Connection Pooler)
2
+ # Pools database connections to allow the application to scale to thousands of concurrent users
3
+ # without exhausting database connection limits or memory.
4
+
5
+ variable "enable_rds_proxy" {
6
+ type = bool
7
+ description = "Whether to provision an RDS Proxy connection pooler"
8
+ default = false
9
+ }
10
+
11
+ # 1. AWS Secrets Manager Secret to store RDS credentials (required for RDS Proxy)
12
+ resource "aws_secretsmanager_secret" "db_credentials" {
13
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
14
+ name = "${var.app_name}-db-credentials"
15
+ recovery_window_in_days = 0 # force deletion on destroy for clean cleanup
16
+ }
17
+
18
+ resource "aws_secretsmanager_secret_version" "db_credentials" {
19
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
20
+ secret_id = aws_secretsmanager_secret.db_credentials[0].id
21
+ secret_string = jsonencode({
22
+ username = aws_db_instance.postgres[0].username
23
+ password = random_password.db_password[0].result
24
+ engine = "postgres"
25
+ host = aws_db_instance.postgres[0].address
26
+ port = 5432
27
+ dbInstanceIdentifier = aws_db_instance.postgres[0].identifier
28
+ })
29
+ }
30
+
31
+ # 2. IAM Role for RDS Proxy to access Secrets Manager
32
+ resource "aws_iam_role" "rds_proxy" {
33
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
34
+ name = "${var.app_name}-rds-proxy-role"
35
+
36
+ assume_role_policy = jsonencode({
37
+ Version = "2012-10-17"
38
+ Statement = [
39
+ {
40
+ Action = "sts:AssumeRole"
41
+ Effect = "Allow"
42
+ Principal = {
43
+ Service = "rds.amazonaws.com"
44
+ }
45
+ }
46
+ ]
47
+ })
48
+ }
49
+
50
+ resource "aws_iam_policy" "rds_proxy" {
51
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
52
+ name = "${var.app_name}-rds-proxy-policy"
53
+ description = "Allows RDS Proxy to read secrets from Secrets Manager"
54
+
55
+ policy = jsonencode({
56
+ Version = "2012-10-17"
57
+ Statement = [
58
+ {
59
+ Action = [
60
+ "secretsmanager:GetSecretValue",
61
+ "secretsmanager:DescribeSecret"
62
+ ]
63
+ Effect = "Allow"
64
+ Resource = [aws_secretsmanager_secret.db_credentials[0].arn]
65
+ }
66
+ ]
67
+ })
68
+ }
69
+
70
+ resource "aws_iam_role_policy_attachment" "rds_proxy" {
71
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
72
+ role = aws_iam_role.rds_proxy[0].name
73
+ policy_arn = aws_iam_policy.rds_proxy[0].arn
74
+ }
75
+
76
+ # 3. RDS Proxy Security Group
77
+ resource "aws_security_group" "rds_proxy" {
78
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
79
+ name = "${var.app_name}-rds-proxy-sg"
80
+ description = "Security Group for RDS Proxy"
81
+ vpc_id = aws_vpc.main.id
82
+
83
+ # Allow ECS tasks to connect to the RDS Proxy
84
+ ingress {
85
+ description = "Allow DB traffic from ECS tasks"
86
+ from_port = 5432
87
+ to_port = 5432
88
+ protocol = "tcp"
89
+ security_groups = [aws_security_group.ecs.id]
90
+ }
91
+
92
+ egress {
93
+ from_port = 0
94
+ to_port = 0
95
+ protocol = "-1"
96
+ cidr_blocks = ["0.0.0.0/0"]
97
+ }
98
+
99
+ tags = {
100
+ Name = "${var.app_name}-rds-proxy-sg"
101
+ }
102
+ }
103
+
104
+ # Allow RDS database to accept connections from the RDS Proxy
105
+ resource "aws_security_group_rule" "db_allow_rds_proxy" {
106
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
107
+ type = "ingress"
108
+ from_port = 5432
109
+ to_port = 5432
110
+ protocol = "tcp"
111
+ security_group_id = aws_security_group.db[0].id
112
+ source_security_group_id = aws_security_group.rds_proxy[0].id
113
+ description = "Allow traffic from RDS Proxy"
114
+ }
115
+
116
+ # 4. RDS Proxy
117
+ resource "aws_db_proxy" "postgres" {
118
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
119
+ name = "${var.app_name}-db-proxy"
120
+ debug_logging = false
121
+ engine_family = "POSTGRESQL"
122
+ idle_client_timeout = 1800
123
+ require_tls = true
124
+ role_arn = aws_iam_role.rds_proxy[0].arn
125
+ vpc_security_group_ids = [aws_security_group.rds_proxy[0].id]
126
+ vpc_subnet_ids = aws_subnet.private[*].id
127
+
128
+ auth {
129
+ auth_scheme = "SECRETS"
130
+ description = "Database authentication via Secrets Manager"
131
+ iam_auth = "DISABLED"
132
+ secret_arn = aws_secretsmanager_secret.db_credentials[0].arn
133
+ }
134
+
135
+ tags = {
136
+ Name = "${var.app_name}-db-proxy"
137
+ }
138
+ }
139
+
140
+ # 5. Connect RDS Proxy to the database instance
141
+ resource "aws_db_proxy_default_target_group" "postgres" {
142
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
143
+ db_proxy_name = aws_db_proxy.postgres[0].name
144
+
145
+ connection_pool_config {
146
+ max_connections_percent = 100
147
+ max_idle_connections_percent = 50
148
+ connection_borrow_timeout = 120
149
+ }
150
+ }
151
+
152
+ resource "aws_db_proxy_target" "postgres" {
153
+ count = var.enable_database && var.enable_rds_proxy ? 1 : 0
154
+ db_proxy_name = aws_db_proxy.postgres[0].name
155
+ target_group_name = aws_db_proxy_default_target_group.postgres[0].name
156
+ db_instance_identifier = aws_db_instance.postgres[0].id
157
+ }
@@ -0,0 +1,69 @@
1
+ # Amazon ElastiCache Redis Cluster
2
+ # Provides high-performance sub-millisecond caching and session/queue management.
3
+
4
+ variable "enable_redis" {
5
+ type = bool
6
+ description = "Whether to provision an ElastiCache Redis cluster"
7
+ default = false
8
+ }
9
+
10
+ # Redis Security Group
11
+ resource "aws_security_group" "redis" {
12
+ count = var.enable_redis ? 1 : 0
13
+ name = "${var.app_name}-redis-sg"
14
+ description = "Access to Redis from ECS tasks only"
15
+ vpc_id = aws_vpc.main.id
16
+
17
+ ingress {
18
+ description = "Allow Redis access from ECS tasks"
19
+ from_port = 6379
20
+ to_port = 6379
21
+ protocol = "tcp"
22
+ security_groups = [aws_security_group.ecs.id]
23
+ }
24
+
25
+ egress {
26
+ from_port = 0
27
+ to_port = 0
28
+ protocol = "-1"
29
+ cidr_blocks = ["0.0.0.0/0"]
30
+ }
31
+
32
+ tags = {
33
+ Name = "${var.app_name}-redis-sg"
34
+ }
35
+ }
36
+
37
+ # Redis Subnet Group (places Redis in private subnets)
38
+ resource "aws_elasticache_subnet_group" "redis" {
39
+ count = var.enable_redis ? 1 : 0
40
+ name = "${var.app_name}-redis-subnet-group"
41
+ subnet_ids = aws_subnet.private[*].id
42
+ }
43
+
44
+ # Redis Replication Group (Cluster)
45
+ resource "aws_elasticache_replication_group" "redis" {
46
+ count = var.enable_redis ? 1 : 0
47
+ replication_group_id = "${var.app_name}-redis"
48
+ description = "Redis cluster for ${var.app_name}"
49
+ node_type = "cache.t4g.micro" # cost-effective Graviton node
50
+ num_cache_clusters = 1
51
+ parameter_group_name = "default.redis7"
52
+ port = 6379
53
+ subnet_group_name = aws_elasticache_subnet_group.redis[0].name
54
+ security_group_ids = [aws_security_group.redis[0].id]
55
+ automatic_failover_enabled = false # Disable for single node to save cost
56
+
57
+ tags = {
58
+ Name = "${var.app_name}-redis"
59
+ }
60
+ }
61
+
62
+ # Save Redis URL in SSM Parameter Store
63
+ resource "aws_ssm_parameter" "redis_url" {
64
+ count = var.enable_redis ? 1 : 0
65
+ name = "/${var.app_name}/redis_url"
66
+ type = "SecureString"
67
+ description = "Redis URL for the application"
68
+ value = "redis://${aws_elasticache_replication_group.redis[0].primary_endpoint_address}:6379"
69
+ }