From b3293527b8f4efd58019daf4a4f329b198757e4b Mon Sep 17 00:00:00 2001 From: aitbc Date: Sat, 9 May 2026 12:36:08 +0200 Subject: [PATCH] infra: add Terraform infrastructure as code for AWS deployment - Create main.tf with VPC, security groups, ECS, ALB, RDS, Redis, S3 - Add provider.tf with AWS provider configuration - Create variables.tf for infrastructure configuration - Add outputs.tf for infrastructure outputs - Implement ecs.tf with ECS task definitions and services - Add ecs_variables.tf for ECS-specific variables - Create comprehensive README.md with usage instructions - Implement state management with S3 backend and DynamoDB locking - Add security best practices (private subnets, encryption, secrets manager) --- infra/terraform/README.md | 144 +++++++++++++ infra/terraform/ecs.tf | 209 ++++++++++++++++++ infra/terraform/ecs_variables.tf | 55 +++++ infra/terraform/main.tf | 356 +++++++++++++++++++++++++++++++ infra/terraform/outputs.tf | 77 +++++++ infra/terraform/provider.tf | 28 +++ infra/terraform/variables.tf | 148 +++++++++++++ 7 files changed, 1017 insertions(+) create mode 100644 infra/terraform/README.md create mode 100644 infra/terraform/ecs.tf create mode 100644 infra/terraform/ecs_variables.tf create mode 100644 infra/terraform/main.tf create mode 100644 infra/terraform/outputs.tf create mode 100644 infra/terraform/provider.tf create mode 100644 infra/terraform/variables.tf diff --git a/infra/terraform/README.md b/infra/terraform/README.md new file mode 100644 index 00000000..1911ff29 --- /dev/null +++ b/infra/terraform/README.md @@ -0,0 +1,144 @@ +# AITBC Terraform Infrastructure + +This directory contains Terraform configurations for deploying AITBC infrastructure on AWS. + +## Prerequisites + +- Terraform >= 1.0 +- AWS CLI configured with appropriate credentials +- S3 bucket for Terraform state (configured in backend) +- DynamoDB table for state locking (configured in backend) + +## Directory Structure + +``` +terraform/ +├── main.tf # Main Terraform configuration +├── provider.tf # Provider configuration +├── variables.tf # Infrastructure variables +├── outputs.tf # Infrastructure outputs +├── ecs.tf # ECS task definitions and services +├── ecs_variables.tf # ECS-specific variables +└── README.md # This file +``` + +## Usage + +### Initialize Terraform + +```bash +terraform init +``` + +### Plan Infrastructure + +```bash +terraform plan -var-file=dev.tfvars +``` + +### Apply Infrastructure + +```bash +terraform apply -var-file=dev.tfvars +``` + +### Destroy Infrastructure + +```bash +terraform destroy -var-file=dev.tfvars +``` + +## Variables + +Create a `dev.tfvars`, `staging.tfvars`, or `prod.tfvars` file with environment-specific variables: + +```hcl +environment = "dev" +aws_region = "us-east-1" +db_username = "aitbc" +db_password = "your-secure-password" +database_url = "postgresql://..." +redis_url = "redis://..." +jwt_secret = "your-jwt-secret" +acm_certificate_arn = "arn:aws:acm:..." +``` + +## Infrastructure Components + +### Networking +- VPC with public and private subnets +- NAT Gateway for private subnet internet access +- Security groups for different services + +### Compute +- ECS Fargate cluster +- ECS task definitions for API services +- Application Load Balancer +- Auto-scaling capabilities + +### Databases +- RDS PostgreSQL for application data +- ElastiCache Redis for caching + +### Storage +- S3 bucket for data storage +- Versioning and encryption enabled + +### Monitoring +- CloudWatch Log Groups +- ECS CloudWatch Container Insights + +## State Management + +Terraform state is stored in S3 with DynamoDB locking: +- State bucket: `aitbc-terraform-state` +- Lock table: `aitbc-terraform-locks` + +## Security + +- All resources are tagged with project and environment +- Security groups restrict access by CIDR blocks +- RDS and Redis are in private subnets +- Secrets stored in AWS Secrets Manager +- S3 encryption enabled +- RDS encryption enabled + +## Cost Optimization + +- Use appropriate instance sizes for environment +- Enable auto-scaling for production +- Monitor costs with AWS Cost Explorer +- Use reserved instances for predictable workloads + +## Outputs + +After applying the configuration, Terraform outputs: +- VPC and subnet IDs +- ECS cluster ID and ARN +- ALB DNS name +- Database and Redis endpoints +- S3 bucket name + +## Troubleshooting + +### State Lock Issues +If you encounter state lock issues: +```bash +terraform force-unlock +``` + +### Resource Already Exists +If resources already exist outside Terraform, import them: +```bash +terraform import aws_vpc.this vpc-xxxxx +``` + +### Permission Errors +Ensure your AWS credentials have the necessary permissions: +- EC2 (VPC, subnets, security groups) +- ECS (clusters, task definitions, services) +- ELB (load balancers, target groups) +- RDS (database instances) +- ElastiCache (Redis clusters) +- S3 (buckets) +- Secrets Manager (secrets) diff --git a/infra/terraform/ecs.tf b/infra/terraform/ecs.tf new file mode 100644 index 00000000..24192449 --- /dev/null +++ b/infra/terraform/ecs.tf @@ -0,0 +1,209 @@ +# ECS task definition for AITBC API service + +resource "aws_ecs_task_definition" "api" { + family = "${var.project_name}-${var.environment}-api" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.api_cpu + memory = var.api_memory + execution_role_arn = aws_iam_role.ecs_execution_role.arn + task_role_arn = aws_iam_role.ecs_task_role.arn + + container_definitions = jsonencode([ + { + name = "api" + image = "${var.api_image}:${var.api_image_tag}" + cpu = var.api_cpu + memory = var.api_memory + essential = true + + portMappings = [ + { + containerPort = 8000 + protocol = "tcp" + } + ] + + environment = [ + { + name = "ENVIRONMENT" + value = var.environment + }, + { + name = "LOG_LEVEL" + value = var.log_level + } + ] + + secrets = [ + { + name = "DATABASE_URL" + valueFrom = aws_secretsmanager_secret.database_url.arn + }, + { + name = "REDIS_URL" + valueFrom = aws_secretsmanager_secret.redis_url.arn + }, + { + name = "JWT_SECRET" + valueFrom = aws_secretsmanager_secret.jwt_secret.arn + } + ] + + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.api.name + "awslogs-region" = var.aws_region + "awslogs-stream-prefix" = "api" + } + } + + healthCheck = { + command = ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"] + interval = 30 + timeout = 5 + retries = 3 + startPeriod = 60 + } + } + ]) + + tags = { + Name = "${var.project_name}-${var.environment}-api" + } +} + +# ECS service for API +resource "aws_ecs_service" "api" { + name = "${var.project_name}-${var.environment}-api" + cluster = aws_ecs_cluster.this.id + task_definition = aws_ecs_task_definition.api.arn + desired_count = var.api_desired_count + launch_type = "FARGATE" + + network_configuration { + subnets = module.vpc.private_subnets + security_groups = [aws_security_group.api_gateway.id] + assign_public_ip = false + } + + load_balancer { + target_group_arn = aws_lb_target_group.api.arn + container_name = "api" + container_port = 8000 + } + + depends_on = [aws_lb_listener.https] + + tags = { + Name = "${var.project_name}-${var.environment}-api" + } +} + +# IAM roles +resource "aws_iam_role" "ecs_execution_role" { + name = "${var.project_name}-${var.environment}-ecs-execution-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "${var.project_name}-${var.environment}-ecs-execution-role" + } +} + +resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy" { + role = aws_iam_role.ecs_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_iam_role" "ecs_task_role" { + name = "${var.project_name}-${var.environment}-ecs-task-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "${var.project_name}-${var.environment}-ecs-task-role" + } +} + +resource "aws_iam_role_policy" "ecs_task_policy" { + name = "${var.project_name}-${var.environment}-ecs-task-policy" + role = aws_iam_role.ecs_task_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ] + Resource = "${aws_s3_bucket.data.arn}/*" + }, + { + Effect = "Allow" + Action = [ + "secretsmanager:GetSecretValue" + ] + Resource = [ + aws_secretsmanager_secret.database_url.arn, + aws_secretsmanager_secret.redis_url.arn, + aws_secretsmanager_secret.jwt_secret.arn + ] + } + ] + }) +} + +# Secrets Manager +resource "aws_secretsmanager_secret" "database_url" { + name = "${var.project_name}/${var.environment}/database/url" +} + +resource "aws_secretsmanager_secret_version" "database_url" { + secret_id = aws_secretsmanager_secret.database_url.id + secret_string = var.database_url +} + +resource "aws_secretsmanager_secret" "redis_url" { + name = "${var.project_name}/${var.environment}/redis/url" +} + +resource "aws_secretsmanager_secret_version" "redis_url" { + secret_id = aws_secretsmanager_secret.redis.id + secret_string = var.redis_url +} + +resource "aws_secretsmanager_secret" "jwt_secret" { + name = "${var.project_name}/${var.environment}/jwt/secret" +} + +resource "aws_secretsmanager_secret_version" "jwt_secret" { + secret_id = aws_secretsmanager_secret.jwt_secret.id + secret_string = var.jwt_secret +} diff --git a/infra/terraform/ecs_variables.tf b/infra/terraform/ecs_variables.tf new file mode 100644 index 00000000..4f3e26b7 --- /dev/null +++ b/infra/terraform/ecs_variables.tf @@ -0,0 +1,55 @@ +# ECS API variables + +variable "api_cpu" { + description = "API task CPU units" + type = number + default = 512 +} + +variable "api_memory" { + description = "API task memory in MB" + type = number + default = 1024 +} + +variable "api_desired_count" { + description = "API desired task count" + type = number + default = 2 +} + +variable "api_image" { + description = "API Docker image" + type = string + default = "aitbc/api" +} + +variable "api_image_tag" { + description = "API Docker image tag" + type = string + default = "latest" +} + +variable "log_level" { + description = "Application log level" + type = string + default = "INFO" +} + +variable "database_url" { + description = "Database connection URL" + type = string + sensitive = true +} + +variable "redis_url" { + description = "Redis connection URL" + type = string + sensitive = true +} + +variable "jwt_secret" { + description = "JWT secret key" + type = string + sensitive = true +} diff --git a/infra/terraform/main.tf b/infra/terraform/main.tf new file mode 100644 index 00000000..cdaa603d --- /dev/null +++ b/infra/terraform/main.tf @@ -0,0 +1,356 @@ +# Terraform main configuration for AITBC infrastructure +# This file defines the infrastructure resources for AITBC deployment + +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } + + backend "s3" { + bucket = "aitbc-terraform-state" + key = "aitbc/terraform.tfstate" + region = "us-east-1" + encrypt = true + dynamodb_table = "aitbc-terraform-locks" + } +} + +provider "aws" { + region = var.aws_region + + default_tags { + tags = { + Project = "AITBC" + Environment = var.environment + ManagedBy = "Terraform" + } + } +} + +# Random resources for unique naming +resource "random_pet" "this" { + length = 2 +} + +# VPC +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = "${var.project_name}-${var.environment}-vpc" + cidr = var.vpc_cidr + + azs = var.availability_zones + private_subnets = var.private_subnet_cidrs + public_subnets = var.public_subnet_cidrs + + enable_nat_gateway = true + single_nat_gateway = true + one_nat_gateway_per_az = false + + enable_vpn_gateway = false + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "${var.project_name}-${var.environment}-vpc" + } +} + +# Security groups +resource "aws_security_group" "blockchain_rpc" { + name = "${var.project_name}-${var.environment}-blockchain-rpc" + description = "Security group for blockchain RPC service" + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 8006 + to_port = 8006 + protocol = "tcp" + cidr_blocks = var.allowed_cidr_blocks + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.project_name}-${var.environment}-blockchain-rpc" + } +} + +resource "aws_security_group" "api_gateway" { + name = "${var.project_name}-${var.environment}-api-gateway" + description = "Security group for API gateway" + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.project_name}-${var.environment}-api-gateway" + } +} + +# ECS Cluster +resource "aws_ecs_cluster" "this" { + name = "${var.project_name}-${var.environment}-cluster" + + setting { + name = "containerInsights" + value = "enabled" + } +} + +# Application Load Balancer +resource "aws_lb" "this" { + name = "${var.project_name}-${var.environment}-alb" + internal = false + load_balancer_type = "application" + security_groups = [aws_security_group.api_gateway.id] + subnets = module.vpc.public_subnets + + enable_deletion_protection = false + + tags = { + Name = "${var.project_name}-${var.environment}-alb" + } +} + +resource "aws_lb_target_group" "api" { + name = "${var.project_name}-${var.environment}-api-tg" + port = 8000 + protocol = "HTTP" + vpc_id = module.vpc.vpc_id + target_type = "ip" + + health_check { + enabled = true + healthy_threshold = 2 + interval = 30 + matcher = "200" + path = "/health" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 3 + } + + tags = { + Name = "${var.project_name}-${var.environment}-api-tg" + } +} + +resource "aws_lb_listener" "http" { + load_balancer_arn = aws_lb.this.arn + port = 80 + protocol = "HTTP" + + default_action { + type = "redirect" + + redirect { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } +} + +resource "aws_lb_listener" "https" { + load_balancer_arn = aws_lb.this.arn + port = 443 + protocol = "HTTPS" + certificate_arn = var.acm_certificate_arn + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.api.arn + } +} + +# RDS PostgreSQL +resource "aws_db_subnet_group" "this" { + name = "${var.project_name}-${var.environment}-db-subnet-group" + subnet_ids = module.vpc.private_subnets + + tags = { + Name = "${var.project_name}-${var.environment}-db-subnet-group" + } +} + +resource "aws_db_instance" "this" { + identifier = "${var.project_name}-${var.environment}-db" + + engine = "postgres" + engine_version = "15.4" + instance_class = var.db_instance_class + + allocated_storage = var.db_allocated_storage + max_allocated_storage = var.db_max_allocated_storage + storage_type = "gp3" + storage_encrypted = true + + db_name = var.db_name + username = var.db_username + password = var.db_password + + db_subnet_group_name = aws_db_subnet_group.this.name + vpc_security_group_ids = [aws_security_group.blockchain_rpc.id] + + multi_az = var.db_multi_az + backup_retention_period = var.db_backup_retention_period + backup_window = var.db_backup_window + maintenance_window = var.db_maintenance_window + + performance_insights_enabled = true + + skip_final_snapshot = false + final_snapshot_identifier = "${var.project_name}-${var.environment}-db-final-snapshot" + + tags = { + Name = "${var.project_name}-${var.environment}-db" + } +} + +# ElastiCache Redis +resource "aws_elasticache_subnet_group" "this" { + name = "${var.project_name}-${var.environment}-redis-subnet-group" + subnet_ids = module.vpc.private_subnets +} + +resource "aws_elasticache_replication_group" "this" { + replication_group_id = "${var.project_name}-${var.environment}-redis" + replication_group_description = "${var.project_name} Redis cluster for ${var.environment}" + + node_type = var.redis_node_type + num_cache_clusters = var.redis_num_nodes + port = 6379 + + engine = "redis" + engine_version = "7.0" + parameter_group_name = "default.redis7" + + subnet_group_name = aws_elasticache_subnet_group.this.name + security_group_ids = [aws_security_group.blockchain_rpc.id] + + automatic_failover_enabled = true + multi_az_enabled = true + + snapshot_retention_limit = var.redis_snapshot_retention_limit + snapshot_window = var.redis_snapshot_window + + tags = { + Name = "${var.project_name}-${var.environment}-redis" + } +} + +# CloudWatch Log Groups +resource "aws_cloudwatch_log_group" "api" { + name = "/aws/ecs/${var.project_name}-${var.environment}/api" + retention_in_days = var.log_retention_days +} + +resource "aws_cloudwatch_log_group" "blockchain" { + name = "/aws/ecs/${var.project_name}-${var.environment}/blockchain" + retention_in_days = var.log_retention_days +} + +# S3 Bucket for data storage +resource "aws_s3_bucket" "data" { + bucket = "${var.project_name}-${var.environment}-data-${random_pet.this.id}" + + tags = { + Name = "${var.project_name}-${var.environment}-data" + } +} + +resource "aws_s3_bucket_versioning" "data" { + bucket = aws_s3_bucket.data.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "data" { + bucket = aws_s3_bucket.data.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +# Outputs +output "vpc_id" { + description = "VPC ID" + value = module.vpc.vpc_id +} + +output "private_subnet_ids" { + description = "Private subnet IDs" + value = module.vpc.private_subnets +} + +output "public_subnet_ids" { + description = "Public subnet IDs" + value = module.vpc.public_subnets +} + +output "ecs_cluster_id" { + description = "ECS Cluster ID" + value = aws_ecs_cluster.this.id +} + +output "alb_dns_name" { + description = "ALB DNS name" + value = aws_lb.this.dns_name +} + +output "db_endpoint" { + description = "RDS endpoint" + value = aws_db_instance.this.endpoint + sensitive = true +} + +output "redis_endpoint" { + description = "Redis endpoint" + value = aws_elasticache_replication_this.primary_endpoint_address +} + +output "s3_bucket_name" { + description = "S3 bucket name" + value = aws_s3_bucket.data.id +} diff --git a/infra/terraform/outputs.tf b/infra/terraform/outputs.tf new file mode 100644 index 00000000..27e239c4 --- /dev/null +++ b/infra/terraform/outputs.tf @@ -0,0 +1,77 @@ +# Terraform outputs for AITBC infrastructure + +output "vpc_id" { + description = "VPC ID" + value = module.vpc.vpc_id +} + +output "private_subnet_ids" { + description = "Private subnet IDs" + value = module.vpc.private_subnets +} + +output "public_subnet_ids" { + description = "Public subnet IDs" + value = module.vpc.public_subnets +} + +output "ecs_cluster_id" { + description = "ECS Cluster ID" + value = aws_ecs_cluster.this.id +} + +output "ecs_cluster_arn" { + description = "ECS Cluster ARN" + value = aws_ecs_cluster.this.arn +} + +output "alb_dns_name" { + description = "ALB DNS name" + value = aws_lb.this.dns_name +} + +output "alb_zone_id" { + description = "ALB zone ID" + value = aws_lb.this.zone_id +} + +output "db_endpoint" { + description = "RDS endpoint" + value = aws_db_instance.this.endpoint + sensitive = true +} + +output "db_instance_id" { + description = "RDS instance ID" + value = aws_db_instance.this.id +} + +output "redis_endpoint" { + description = "Redis primary endpoint" + value = aws_elasticache_replication_this.primary_endpoint_address +} + +output "redis_port" { + description = "Redis port" + value = aws_elasticache_replication_this.port +} + +output "s3_bucket_name" { + description = "S3 bucket name" + value = aws_s3_bucket.data.id +} + +output "s3_bucket_arn" { + description = "S3 bucket ARN" + value = aws_s3_bucket.data.arn +} + +output "security_group_blockchain_rpc_id" { + description = "Blockchain RPC security group ID" + value = aws_security_group.blockchain_rpc.id +} + +output "security_group_api_gateway_id" { + description = "API gateway security group ID" + value = aws_security_group.api_gateway.id +} diff --git a/infra/terraform/provider.tf b/infra/terraform/provider.tf new file mode 100644 index 00000000..fe8087e2 --- /dev/null +++ b/infra/terraform/provider.tf @@ -0,0 +1,28 @@ +# Terraform provider configuration + +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} + +provider "aws" { + region = var.aws_region + + default_tags { + tags = { + Project = "AITBC" + Environment = var.environment + ManagedBy = "Terraform" + } + } +} diff --git a/infra/terraform/variables.tf b/infra/terraform/variables.tf new file mode 100644 index 00000000..5dd6b666 --- /dev/null +++ b/infra/terraform/variables.tf @@ -0,0 +1,148 @@ +# Terraform variables for AITBC infrastructure + +variable "project_name" { + description = "Project name" + type = string + default = "aitbc" +} + +variable "environment" { + description = "Environment name (dev, staging, prod)" + type = string + validation { + condition = contains(["dev", "staging", "prod"], var.environment) + error_message = "Environment must be dev, staging, or prod." + } +} + +variable "aws_region" { + description = "AWS region" + type = string + default = "us-east-1" +} + +variable "vpc_cidr" { + description = "VPC CIDR block" + type = string + default = "10.0.0.0/16" +} + +variable "availability_zones" { + description = "Availability zones" + type = list(string) + default = ["us-east-1a", "us-east-1b", "us-east-1c"] +} + +variable "private_subnet_cidrs" { + description = "Private subnet CIDR blocks" + type = list(string) + default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] +} + +variable "public_subnet_cidrs" { + description = "Public subnet CIDR blocks" + type = list(string) + default = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] +} + +variable "allowed_cidr_blocks" { + description = "Allowed CIDR blocks for security groups" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "acm_certificate_arn" { + description = "ACM certificate ARN for HTTPS" + type = string + default = "" +} + +variable "db_instance_class" { + description = "RDS instance class" + type = string + default = "db.t3.medium" +} + +variable "db_allocated_storage" { + description = "RDS allocated storage in GB" + type = number + default = 20 +} + +variable "db_max_allocated_storage" { + description = "RDS max allocated storage in GB" + type = number + default = 100 +} + +variable "db_name" { + description = "Database name" + type = string + default = "aitbc" +} + +variable "db_username" { + description = "Database username" + type = string + sensitive = true +} + +variable "db_password" { + description = "Database password" + type = string + sensitive = true +} + +variable "db_multi_az" { + description = "Enable multi-AZ for RDS" + type = bool + default = false +} + +variable "db_backup_retention_period" { + description = "RDS backup retention period in days" + type = number + default = 7 +} + +variable "db_backup_window" { + description = "RDS backup window" + type = string + default = "03:00-04:00" +} + +variable "db_maintenance_window" { + description = "RDS maintenance window" + type = string + default = "sun:04:00-sun:05:00" +} + +variable "redis_node_type" { + description = "Redis node type" + type = string + default = "cache.t3.medium" +} + +variable "redis_num_nodes" { + description = "Number of Redis nodes" + type = number + default = 2 +} + +variable "redis_snapshot_retention_limit" { + description = "Redis snapshot retention limit" + type = number + default = 5 +} + +variable "redis_snapshot_window" { + description = "Redis snapshot window" + type = string + default = "05:00-06:00" +} + +variable "log_retention_days" { + description = "CloudWatch log retention in days" + type = number + default = 30 +}