Merge pull request #13 from manmohan659/feat/terraform-infra

feat(terraform): provision full AWS stack for samosaChaat
This commit is contained in:
Manmohan 2026-04-16 14:26:20 -04:00 committed by GitHub
commit 2be82fe731
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 2502 additions and 0 deletions

12
.gitignore vendored
View File

@ -20,3 +20,15 @@ models/
CLAUDE.md
wandb/
onnx_export/
# Terraform
**/.terraform/*
*.tfstate
*.tfstate.*
*.tfvars
crash.log
crash.*.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json

35
terraform/backend.tf Normal file
View File

@ -0,0 +1,35 @@
# Shared remote-state configuration.
#
# Each environment overrides the `key` via `terraform init -backend-config="key=..."`
# (the harness in environments/<env>/main.tf passes `terraform { backend "s3" {} }`
# with no inline values so the same bucket can host multiple state files).
#
# Bootstrap (run once, manually):
#
# aws s3api create-bucket \
# --bucket samosachaat-terraform-state \
# --region us-west-2 \
# --create-bucket-configuration LocationConstraint=us-west-2
# aws s3api put-bucket-versioning \
# --bucket samosachaat-terraform-state \
# --versioning-configuration Status=Enabled
# aws s3api put-bucket-encryption \
# --bucket samosachaat-terraform-state \
# --server-side-encryption-configuration \
# '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
# aws dynamodb create-table \
# --table-name samosachaat-terraform-locks \
# --attribute-definitions AttributeName=LockID,AttributeType=S \
# --key-schema AttributeName=LockID,KeyType=HASH \
# --billing-mode PAY_PER_REQUEST \
# --region us-west-2
terraform {
backend "s3" {
bucket = "samosachaat-terraform-state"
key = "global/placeholder.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "samosachaat-terraform-locks"
}
}

View File

@ -0,0 +1,125 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "5.100.0"
constraints = ">= 4.33.0, >= 5.0.0, >= 5.79.0, >= 5.92.0, >= 5.95.0, < 6.0.0"
hashes = [
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
]
}
provider "registry.terraform.io/hashicorp/cloudinit" {
version = "2.3.7"
constraints = ">= 2.0.0"
hashes = [
"h1:M9TpQxKAE/hyOwytdX9MUNZw30HoD/OXqYIug5fkqH8=",
"zh:06f1c54e919425c3139f8aeb8fcf9bceca7e560d48c9f0c1e3bb0a8ad9d9da1e",
"zh:0e1e4cf6fd98b019e764c28586a386dc136129fef50af8c7165a067e7e4a31d5",
"zh:1871f4337c7c57287d4d67396f633d224b8938708b772abfc664d1f80bd67edd",
"zh:2b9269d91b742a71b2248439d5e9824f0447e6d261bfb86a8a88528609b136d1",
"zh:3d8ae039af21426072c66d6a59a467d51f2d9189b8198616888c1b7fc42addc7",
"zh:3ef4e2db5bcf3e2d915921adced43929214e0946a6fb11793085d9a48995ae01",
"zh:42ae54381147437c83cbb8790cc68935d71b6357728a154109d3220b1beb4dc9",
"zh:4496b362605ae4cbc9ef7995d102351e2fe311897586ffc7a4a262ccca0c782a",
"zh:652a2401257a12706d32842f66dac05a735693abcb3e6517d6b5e2573729ba13",
"zh:7406c30806f5979eaed5f50c548eced2ea18ea121e01801d2f0d4d87a04f6a14",
"zh:7848429fd5a5bcf35f6fee8487df0fb64b09ec071330f3ff240c0343fe2a5224",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
]
}
provider "registry.terraform.io/hashicorp/null" {
version = "3.2.4"
constraints = ">= 3.0.0"
hashes = [
"h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=",
"zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43",
"zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a",
"zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991",
"zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f",
"zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e",
"zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615",
"zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442",
"zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5",
"zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f",
"zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.8.1"
constraints = ">= 3.1.0, >= 3.5.0"
hashes = [
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
"zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0",
"zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66",
"zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9",
"zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05",
"zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8",
"zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b",
"zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699",
]
}
provider "registry.terraform.io/hashicorp/time" {
version = "0.13.1"
constraints = ">= 0.9.0"
hashes = [
"h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=",
"zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74",
"zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f",
"zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a",
"zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328",
"zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8",
"zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b",
"zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0",
"zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d",
"zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75",
"zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5",
]
}
provider "registry.terraform.io/hashicorp/tls" {
version = "4.2.1"
constraints = ">= 3.0.0, >= 4.0.0"
hashes = [
"h1:akFNuHwvrtnYMBofieoeXhPJDhYZzJVu/Q/BgZK2fgg=",
"zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a",
"zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86",
"zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad",
"zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191",
"zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b",
"zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5",
"zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635",
"zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933",
"zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e",
"zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed",
"zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}

View File

@ -0,0 +1,107 @@
locals {
name_prefix = "samosachaat-${var.environment}"
cluster_name = "${local.name_prefix}-eks"
tags = {
Project = "samosachaat"
Environment = var.environment
}
}
module "vpc" {
source = "../../modules/vpc"
name = local.name_prefix
cluster_name = local.cluster_name
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
single_nat_gateway = true
tags = local.tags
}
module "eks" {
source = "../../modules/eks"
cluster_name = local.cluster_name
cluster_version = "1.29"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
node_instance_type = "t3.large"
node_min_size = 2
node_max_size = 4
node_desired_size = 2
tags = local.tags
}
module "ecr" {
source = "../../modules/ecr"
force_delete = true
tags = local.tags
}
module "iam" {
source = "../../modules/iam"
name_prefix = local.name_prefix
oidc_provider_arn = module.eks.oidc_provider_arn
oidc_provider_url = module.eks.oidc_provider_url
create_github_oidc = true
github_repositories = var.github_repositories
tags = local.tags
}
module "rds" {
source = "../../modules/rds"
identifier = "${local.name_prefix}-pg"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
eks_node_security_group_id = module.eks.node_security_group_id
instance_class = "db.t3.micro"
multi_az = false
skip_final_snapshot = true
deletion_protection = false
tags = local.tags
}
module "efs" {
source = "../../modules/efs"
name = "${local.name_prefix}-models"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
eks_node_security_group_id = module.eks.node_security_group_id
tags = local.tags
}
module "acm" {
source = "../../modules/acm"
domain_name = var.domain_name
subject_alternative_names = ["*.${var.domain_name}"]
# First apply: leave wait_for_validation = false so Route53 records can be
# created in the same plan. Flip to true on a follow-up apply if desired.
wait_for_validation = false
tags = local.tags
}
module "route53" {
source = "../../modules/route53"
domain_name = var.domain_name
subdomains = ["grafana"]
acm_validation_records = module.acm.validation_records
# alb_dns_name / alb_zone_id are populated after the AWS Load Balancer
# Controller provisions the Ingress. Re-apply with -var to wire them up.
alb_dns_name = ""
alb_zone_id = ""
}

View File

@ -0,0 +1,70 @@
output "vpc_id" {
description = "VPC identifier."
value = module.vpc.vpc_id
}
output "private_subnet_ids" {
description = "Private subnet identifiers."
value = module.vpc.private_subnet_ids
}
output "public_subnet_ids" {
description = "Public subnet identifiers."
value = module.vpc.public_subnet_ids
}
output "cluster_name" {
description = "EKS cluster name."
value = module.eks.cluster_name
}
output "cluster_endpoint" {
description = "EKS API endpoint."
value = module.eks.cluster_endpoint
}
output "cluster_oidc_provider_arn" {
description = "OIDC provider ARN for IRSA bindings."
value = module.eks.oidc_provider_arn
}
output "rds_endpoint" {
description = "RDS endpoint (host:port)."
value = module.rds.db_instance_endpoint
}
output "rds_password" {
description = "Generated RDS master password."
value = module.rds.db_password
sensitive = true
}
output "ecr_repository_urls" {
description = "ECR repository URLs by name."
value = module.ecr.repository_urls
}
output "efs_file_system_id" {
description = "EFS filesystem ID for model weights."
value = module.efs.file_system_id
}
output "acm_certificate_arn" {
description = "ACM cert ARN for the ALB Ingress."
value = module.acm.certificate_arn
}
output "route53_zone_id" {
description = "Route53 hosted zone ID."
value = module.route53.zone_id
}
output "alb_controller_role_arn" {
description = "IRSA role ARN for the AWS Load Balancer Controller."
value = module.iam.alb_controller_role_arn
}
output "github_actions_role_arn" {
description = "IAM role for GitHub Actions OIDC assumption."
value = module.iam.github_actions_role_arn
}

View File

@ -0,0 +1,23 @@
variable "region" {
description = "AWS region."
type = string
default = "us-west-2"
}
variable "environment" {
description = "Environment name (dev/uat/prod)."
type = string
default = "dev"
}
variable "domain_name" {
description = "Apex domain — must already have a Route53 hosted zone."
type = string
default = "samosachaat.art"
}
variable "github_repositories" {
description = "GitHub repos that may assume the CI role."
type = list(string)
default = ["manmohan659/nanochat"]
}

View File

@ -0,0 +1,38 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.5"
}
tls = {
source = "hashicorp/tls"
version = ">= 4.0"
}
}
backend "s3" {
bucket = "samosachaat-terraform-state"
key = "envs/dev/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "samosachaat-terraform-locks"
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Project = "samosachaat"
Environment = var.environment
ManagedBy = "terraform"
}
}
}

View File

@ -0,0 +1,125 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "5.100.0"
constraints = ">= 4.33.0, >= 5.0.0, >= 5.79.0, >= 5.92.0, >= 5.95.0, < 6.0.0"
hashes = [
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
]
}
provider "registry.terraform.io/hashicorp/cloudinit" {
version = "2.3.7"
constraints = ">= 2.0.0"
hashes = [
"h1:M9TpQxKAE/hyOwytdX9MUNZw30HoD/OXqYIug5fkqH8=",
"zh:06f1c54e919425c3139f8aeb8fcf9bceca7e560d48c9f0c1e3bb0a8ad9d9da1e",
"zh:0e1e4cf6fd98b019e764c28586a386dc136129fef50af8c7165a067e7e4a31d5",
"zh:1871f4337c7c57287d4d67396f633d224b8938708b772abfc664d1f80bd67edd",
"zh:2b9269d91b742a71b2248439d5e9824f0447e6d261bfb86a8a88528609b136d1",
"zh:3d8ae039af21426072c66d6a59a467d51f2d9189b8198616888c1b7fc42addc7",
"zh:3ef4e2db5bcf3e2d915921adced43929214e0946a6fb11793085d9a48995ae01",
"zh:42ae54381147437c83cbb8790cc68935d71b6357728a154109d3220b1beb4dc9",
"zh:4496b362605ae4cbc9ef7995d102351e2fe311897586ffc7a4a262ccca0c782a",
"zh:652a2401257a12706d32842f66dac05a735693abcb3e6517d6b5e2573729ba13",
"zh:7406c30806f5979eaed5f50c548eced2ea18ea121e01801d2f0d4d87a04f6a14",
"zh:7848429fd5a5bcf35f6fee8487df0fb64b09ec071330f3ff240c0343fe2a5224",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
]
}
provider "registry.terraform.io/hashicorp/null" {
version = "3.2.4"
constraints = ">= 3.0.0"
hashes = [
"h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=",
"zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43",
"zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a",
"zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991",
"zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f",
"zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e",
"zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615",
"zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442",
"zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5",
"zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f",
"zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.8.1"
constraints = ">= 3.1.0, >= 3.5.0"
hashes = [
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
"zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0",
"zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66",
"zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9",
"zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05",
"zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8",
"zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b",
"zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699",
]
}
provider "registry.terraform.io/hashicorp/time" {
version = "0.13.1"
constraints = ">= 0.9.0"
hashes = [
"h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=",
"zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74",
"zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f",
"zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a",
"zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328",
"zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8",
"zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b",
"zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0",
"zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d",
"zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75",
"zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5",
]
}
provider "registry.terraform.io/hashicorp/tls" {
version = "4.2.1"
constraints = ">= 3.0.0, >= 4.0.0"
hashes = [
"h1:akFNuHwvrtnYMBofieoeXhPJDhYZzJVu/Q/BgZK2fgg=",
"zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a",
"zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86",
"zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad",
"zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191",
"zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b",
"zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5",
"zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635",
"zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933",
"zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e",
"zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed",
"zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}

View File

@ -0,0 +1,104 @@
locals {
name_prefix = "samosachaat-${var.environment}"
cluster_name = "${local.name_prefix}-eks"
tags = {
Project = "samosachaat"
Environment = var.environment
}
}
module "vpc" {
source = "../../modules/vpc"
name = local.name_prefix
cluster_name = local.cluster_name
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
single_nat_gateway = false
tags = local.tags
}
module "eks" {
source = "../../modules/eks"
cluster_name = local.cluster_name
cluster_version = "1.29"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
node_instance_type = "t3.xlarge"
node_min_size = 3
node_max_size = 10
node_desired_size = 3
tags = local.tags
}
module "ecr" {
source = "../../modules/ecr"
force_delete = false
tags = local.tags
}
module "iam" {
source = "../../modules/iam"
name_prefix = local.name_prefix
oidc_provider_arn = module.eks.oidc_provider_arn
oidc_provider_url = module.eks.oidc_provider_url
# GitHub OIDC provider is created by the dev env (account-level resource).
create_github_oidc = false
github_repositories = var.github_repositories
tags = local.tags
}
module "rds" {
source = "../../modules/rds"
identifier = "${local.name_prefix}-pg"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
eks_node_security_group_id = module.eks.node_security_group_id
instance_class = "db.t3.medium"
multi_az = true
skip_final_snapshot = false
deletion_protection = true
tags = local.tags
}
module "efs" {
source = "../../modules/efs"
name = "${local.name_prefix}-models"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
eks_node_security_group_id = module.eks.node_security_group_id
tags = local.tags
}
module "acm" {
source = "../../modules/acm"
domain_name = var.domain_name
subject_alternative_names = ["*.${var.domain_name}"]
wait_for_validation = false
tags = local.tags
}
module "route53" {
source = "../../modules/route53"
domain_name = var.domain_name
subdomains = ["grafana"]
acm_validation_records = module.acm.validation_records
alb_dns_name = ""
alb_zone_id = ""
}

View File

@ -0,0 +1,70 @@
output "vpc_id" {
description = "VPC identifier."
value = module.vpc.vpc_id
}
output "private_subnet_ids" {
description = "Private subnet identifiers."
value = module.vpc.private_subnet_ids
}
output "public_subnet_ids" {
description = "Public subnet identifiers."
value = module.vpc.public_subnet_ids
}
output "cluster_name" {
description = "EKS cluster name."
value = module.eks.cluster_name
}
output "cluster_endpoint" {
description = "EKS API endpoint."
value = module.eks.cluster_endpoint
}
output "cluster_oidc_provider_arn" {
description = "OIDC provider ARN for IRSA bindings."
value = module.eks.oidc_provider_arn
}
output "rds_endpoint" {
description = "RDS endpoint (host:port)."
value = module.rds.db_instance_endpoint
}
output "rds_password" {
description = "Generated RDS master password."
value = module.rds.db_password
sensitive = true
}
output "ecr_repository_urls" {
description = "ECR repository URLs by name."
value = module.ecr.repository_urls
}
output "efs_file_system_id" {
description = "EFS filesystem ID for model weights."
value = module.efs.file_system_id
}
output "acm_certificate_arn" {
description = "ACM cert ARN for the ALB Ingress."
value = module.acm.certificate_arn
}
output "route53_zone_id" {
description = "Route53 hosted zone ID."
value = module.route53.zone_id
}
output "alb_controller_role_arn" {
description = "IRSA role ARN for the AWS Load Balancer Controller."
value = module.iam.alb_controller_role_arn
}
output "github_actions_role_arn" {
description = "IAM role for GitHub Actions OIDC assumption."
value = module.iam.github_actions_role_arn
}

View File

@ -0,0 +1,23 @@
variable "region" {
description = "AWS region."
type = string
default = "us-west-2"
}
variable "environment" {
description = "Environment name (dev/uat/prod)."
type = string
default = "prod"
}
variable "domain_name" {
description = "Apex domain — must already have a Route53 hosted zone."
type = string
default = "samosachaat.art"
}
variable "github_repositories" {
description = "GitHub repos that may assume the CI role."
type = list(string)
default = ["manmohan659/nanochat"]
}

View File

@ -0,0 +1,38 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.5"
}
tls = {
source = "hashicorp/tls"
version = ">= 4.0"
}
}
backend "s3" {
bucket = "samosachaat-terraform-state"
key = "envs/prod/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "samosachaat-terraform-locks"
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Project = "samosachaat"
Environment = var.environment
ManagedBy = "terraform"
}
}
}

View File

@ -0,0 +1,125 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "5.100.0"
constraints = ">= 4.33.0, >= 5.0.0, >= 5.79.0, >= 5.92.0, >= 5.95.0, < 6.0.0"
hashes = [
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
]
}
provider "registry.terraform.io/hashicorp/cloudinit" {
version = "2.3.7"
constraints = ">= 2.0.0"
hashes = [
"h1:M9TpQxKAE/hyOwytdX9MUNZw30HoD/OXqYIug5fkqH8=",
"zh:06f1c54e919425c3139f8aeb8fcf9bceca7e560d48c9f0c1e3bb0a8ad9d9da1e",
"zh:0e1e4cf6fd98b019e764c28586a386dc136129fef50af8c7165a067e7e4a31d5",
"zh:1871f4337c7c57287d4d67396f633d224b8938708b772abfc664d1f80bd67edd",
"zh:2b9269d91b742a71b2248439d5e9824f0447e6d261bfb86a8a88528609b136d1",
"zh:3d8ae039af21426072c66d6a59a467d51f2d9189b8198616888c1b7fc42addc7",
"zh:3ef4e2db5bcf3e2d915921adced43929214e0946a6fb11793085d9a48995ae01",
"zh:42ae54381147437c83cbb8790cc68935d71b6357728a154109d3220b1beb4dc9",
"zh:4496b362605ae4cbc9ef7995d102351e2fe311897586ffc7a4a262ccca0c782a",
"zh:652a2401257a12706d32842f66dac05a735693abcb3e6517d6b5e2573729ba13",
"zh:7406c30806f5979eaed5f50c548eced2ea18ea121e01801d2f0d4d87a04f6a14",
"zh:7848429fd5a5bcf35f6fee8487df0fb64b09ec071330f3ff240c0343fe2a5224",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
]
}
provider "registry.terraform.io/hashicorp/null" {
version = "3.2.4"
constraints = ">= 3.0.0"
hashes = [
"h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=",
"zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43",
"zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a",
"zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991",
"zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f",
"zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e",
"zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615",
"zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442",
"zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5",
"zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f",
"zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.8.1"
constraints = ">= 3.1.0, >= 3.5.0"
hashes = [
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
"zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0",
"zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66",
"zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9",
"zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05",
"zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8",
"zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b",
"zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699",
]
}
provider "registry.terraform.io/hashicorp/time" {
version = "0.13.1"
constraints = ">= 0.9.0"
hashes = [
"h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=",
"zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74",
"zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f",
"zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a",
"zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328",
"zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8",
"zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b",
"zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0",
"zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d",
"zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75",
"zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5",
]
}
provider "registry.terraform.io/hashicorp/tls" {
version = "4.2.1"
constraints = ">= 3.0.0, >= 4.0.0"
hashes = [
"h1:akFNuHwvrtnYMBofieoeXhPJDhYZzJVu/Q/BgZK2fgg=",
"zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a",
"zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86",
"zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad",
"zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191",
"zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b",
"zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5",
"zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635",
"zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933",
"zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e",
"zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed",
"zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}

View File

@ -0,0 +1,104 @@
locals {
name_prefix = "samosachaat-${var.environment}"
cluster_name = "${local.name_prefix}-eks"
tags = {
Project = "samosachaat"
Environment = var.environment
}
}
module "vpc" {
source = "../../modules/vpc"
name = local.name_prefix
cluster_name = local.cluster_name
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
single_nat_gateway = true
tags = local.tags
}
module "eks" {
source = "../../modules/eks"
cluster_name = local.cluster_name
cluster_version = "1.29"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
node_instance_type = "t3.large"
node_min_size = 2
node_max_size = 4
node_desired_size = 2
tags = local.tags
}
module "ecr" {
source = "../../modules/ecr"
force_delete = false
tags = local.tags
}
module "iam" {
source = "../../modules/iam"
name_prefix = local.name_prefix
oidc_provider_arn = module.eks.oidc_provider_arn
oidc_provider_url = module.eks.oidc_provider_url
# GitHub OIDC provider is created by the dev env (account-level resource).
create_github_oidc = false
github_repositories = var.github_repositories
tags = local.tags
}
module "rds" {
source = "../../modules/rds"
identifier = "${local.name_prefix}-pg"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
eks_node_security_group_id = module.eks.node_security_group_id
instance_class = "db.t3.micro"
multi_az = false
skip_final_snapshot = true
deletion_protection = false
tags = local.tags
}
module "efs" {
source = "../../modules/efs"
name = "${local.name_prefix}-models"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids
eks_node_security_group_id = module.eks.node_security_group_id
tags = local.tags
}
module "acm" {
source = "../../modules/acm"
domain_name = var.domain_name
subject_alternative_names = ["*.${var.domain_name}"]
wait_for_validation = false
tags = local.tags
}
module "route53" {
source = "../../modules/route53"
domain_name = var.domain_name
subdomains = ["grafana"]
acm_validation_records = module.acm.validation_records
alb_dns_name = ""
alb_zone_id = ""
}

View File

@ -0,0 +1,70 @@
output "vpc_id" {
description = "VPC identifier."
value = module.vpc.vpc_id
}
output "private_subnet_ids" {
description = "Private subnet identifiers."
value = module.vpc.private_subnet_ids
}
output "public_subnet_ids" {
description = "Public subnet identifiers."
value = module.vpc.public_subnet_ids
}
output "cluster_name" {
description = "EKS cluster name."
value = module.eks.cluster_name
}
output "cluster_endpoint" {
description = "EKS API endpoint."
value = module.eks.cluster_endpoint
}
output "cluster_oidc_provider_arn" {
description = "OIDC provider ARN for IRSA bindings."
value = module.eks.oidc_provider_arn
}
output "rds_endpoint" {
description = "RDS endpoint (host:port)."
value = module.rds.db_instance_endpoint
}
output "rds_password" {
description = "Generated RDS master password."
value = module.rds.db_password
sensitive = true
}
output "ecr_repository_urls" {
description = "ECR repository URLs by name."
value = module.ecr.repository_urls
}
output "efs_file_system_id" {
description = "EFS filesystem ID for model weights."
value = module.efs.file_system_id
}
output "acm_certificate_arn" {
description = "ACM cert ARN for the ALB Ingress."
value = module.acm.certificate_arn
}
output "route53_zone_id" {
description = "Route53 hosted zone ID."
value = module.route53.zone_id
}
output "alb_controller_role_arn" {
description = "IRSA role ARN for the AWS Load Balancer Controller."
value = module.iam.alb_controller_role_arn
}
output "github_actions_role_arn" {
description = "IAM role for GitHub Actions OIDC assumption."
value = module.iam.github_actions_role_arn
}

View File

@ -0,0 +1,23 @@
variable "region" {
description = "AWS region."
type = string
default = "us-west-2"
}
variable "environment" {
description = "Environment name (dev/uat/prod)."
type = string
default = "uat"
}
variable "domain_name" {
description = "Apex domain — must already have a Route53 hosted zone."
type = string
default = "samosachaat.art"
}
variable "github_repositories" {
description = "GitHub repos that may assume the CI role."
type = list(string)
default = ["manmohan659/nanochat"]
}

View File

@ -0,0 +1,38 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.5"
}
tls = {
source = "hashicorp/tls"
version = ">= 4.0"
}
}
backend "s3" {
bucket = "samosachaat-terraform-state"
key = "envs/uat/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "samosachaat-terraform-locks"
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Project = "samosachaat"
Environment = var.environment
ManagedBy = "terraform"
}
}
}

View File

@ -0,0 +1,29 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
resource "aws_acm_certificate" "this" {
domain_name = var.domain_name
subject_alternative_names = var.subject_alternative_names
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
tags = var.tags
}
# Route53 records are created in the route53 module from validation_records output.
resource "aws_acm_certificate_validation" "this" {
count = var.wait_for_validation ? 1 : 0
certificate_arn = aws_acm_certificate.this.arn
validation_record_fqdns = var.validation_record_fqdns
}

View File

@ -0,0 +1,29 @@
output "certificate_arn" {
description = "ARN of the issued certificate (use on the ALB Ingress annotation alb.ingress.kubernetes.io/certificate-arn)."
value = aws_acm_certificate.this.arn
}
output "domain_validation_options" {
description = "Raw validation options from AWS — used by the route53 module."
value = aws_acm_certificate.this.domain_validation_options
}
output "validation_records" {
description = "Map keyed by domain → { name, type, record } ready to plug into route53.acm_validation_records."
value = {
for dvo in aws_acm_certificate.this.domain_validation_options :
dvo.domain_name => {
name = dvo.resource_record_name
type = dvo.resource_record_type
record = dvo.resource_record_value
}
}
}
output "validation_record_fqdns" {
description = "List of FQDNs to feed into aws_acm_certificate_validation."
value = [
for dvo in aws_acm_certificate.this.domain_validation_options :
dvo.resource_record_name
]
}

View File

@ -0,0 +1,28 @@
variable "domain_name" {
description = "Primary domain on the certificate (e.g. samosachaat.art)."
type = string
}
variable "subject_alternative_names" {
description = "SAN list (e.g. [\"*.samosachaat.art\"])."
type = list(string)
default = []
}
variable "wait_for_validation" {
description = "Block apply until DNS validation succeeds. Disable on first apply if Route53 records are created in the same plan."
type = bool
default = true
}
variable "validation_record_fqdns" {
description = "FQDNs of the DNS validation records (passed in from the route53 module)."
type = list(string)
default = []
}
variable "tags" {
description = "Tags applied to every resource."
type = map(string)
default = {}
}

View File

@ -0,0 +1,50 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
resource "aws_ecr_repository" "this" {
for_each = toset(var.repository_names)
name = each.key
image_tag_mutability = "MUTABLE"
force_delete = var.force_delete
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "AES256"
}
tags = var.tags
}
resource "aws_ecr_lifecycle_policy" "keep_last_20" {
for_each = aws_ecr_repository.this
repository = each.value.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep only the last 20 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 20
}
action = {
type = "expire"
}
}
]
})
}

View File

@ -0,0 +1,14 @@
output "repository_urls" {
description = "Map of repository name → registry URL (used by CI/CD `docker push`)."
value = { for name, repo in aws_ecr_repository.this : name => repo.repository_url }
}
output "repository_arns" {
description = "Map of repository name → ARN."
value = { for name, repo in aws_ecr_repository.this : name => repo.arn }
}
output "registry_id" {
description = "Account ID hosting the registry (same for all repos)."
value = values(aws_ecr_repository.this)[0].registry_id
}

View File

@ -0,0 +1,22 @@
variable "repository_names" {
description = "ECR repositories to create."
type = list(string)
default = [
"samosachaat-frontend",
"samosachaat-auth",
"samosachaat-chat-api",
"samosachaat-inference",
]
}
variable "force_delete" {
description = "Allow Terraform to destroy repositories even if they contain images (true for dev only)."
type = bool
default = false
}
variable "tags" {
description = "Tags applied to every resource."
type = map(string)
default = {}
}

View File

@ -0,0 +1,73 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
resource "aws_security_group" "efs" {
name = "${var.name}-efs-sg"
description = "NFS from EKS nodes to model-weights EFS"
vpc_id = var.vpc_id
ingress {
description = "NFS from EKS nodes"
from_port = 2049
to_port = 2049
protocol = "tcp"
security_groups = [var.eks_node_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = var.tags
}
resource "aws_efs_file_system" "this" {
creation_token = var.name
encrypted = true
performance_mode = var.performance_mode
throughput_mode = var.throughput_mode
lifecycle_policy {
transition_to_ia = "AFTER_30_DAYS"
}
tags = merge(var.tags, { Name = var.name })
}
resource "aws_efs_mount_target" "this" {
for_each = toset(var.private_subnet_ids)
file_system_id = aws_efs_file_system.this.id
subnet_id = each.key
security_groups = [aws_security_group.efs.id]
}
# Access point used by inference pods (UID/GID match the container user).
resource "aws_efs_access_point" "model_weights" {
file_system_id = aws_efs_file_system.this.id
posix_user {
uid = 1000
gid = 1000
}
root_directory {
path = "/model-weights"
creation_info {
owner_uid = 1000
owner_gid = 1000
permissions = "0755"
}
}
tags = var.tags
}

View File

@ -0,0 +1,26 @@
output "file_system_id" {
description = "EFS filesystem ID — pass to the EFS CSI driver StorageClass."
value = aws_efs_file_system.this.id
}
output "file_system_arn" {
description = "Filesystem ARN."
value = aws_efs_file_system.this.arn
}
output "dns_name" {
description = "Mount DNS name."
value = "${aws_efs_file_system.this.id}.efs.${data.aws_region.current.name}.amazonaws.com"
}
output "access_point_id" {
description = "Access point for the model-weights directory."
value = aws_efs_access_point.model_weights.id
}
output "security_group_id" {
description = "EFS security group."
value = aws_security_group.efs.id
}
data "aws_region" "current" {}

View File

@ -0,0 +1,37 @@
variable "name" {
description = "Filesystem name (used in tags and the creation token)."
type = string
}
variable "vpc_id" {
description = "VPC the mount targets live in."
type = string
}
variable "private_subnet_ids" {
description = "Private subnets that get mount targets (one per AZ)."
type = list(string)
}
variable "eks_node_security_group_id" {
description = "Node SG allowed to mount the filesystem."
type = string
}
variable "performance_mode" {
description = "EFS performance mode."
type = string
default = "generalPurpose"
}
variable "throughput_mode" {
description = "EFS throughput mode."
type = string
default = "bursting"
}
variable "tags" {
description = "Tags applied to every resource."
type = map(string)
default = {}
}

View File

@ -0,0 +1,60 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
data "aws_ssm_parameter" "eks_ami_id" {
name = "/aws/service/eks/optimized-ami/${var.cluster_version}/amazon-linux-2/recommended/image_id"
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = var.cluster_name
cluster_version = var.cluster_version
cluster_endpoint_public_access = true
cluster_endpoint_private_access = true
enable_irsa = true
vpc_id = var.vpc_id
subnet_ids = var.private_subnet_ids
control_plane_subnet_ids = var.private_subnet_ids
cluster_addons = {
coredns = { most_recent = true }
kube-proxy = { most_recent = true }
vpc-cni = { most_recent = true }
aws-ebs-csi-driver = { most_recent = true }
aws-efs-csi-driver = { most_recent = true }
}
eks_managed_node_group_defaults = {
ami_id = data.aws_ssm_parameter.eks_ami_id.value
enable_bootstrap_user_data = true
}
eks_managed_node_groups = {
default = {
min_size = var.node_min_size
max_size = var.node_max_size
desired_size = var.node_desired_size
instance_types = [var.node_instance_type]
capacity_type = "ON_DEMAND"
labels = {
role = "general"
}
}
}
tags = var.tags
}

View File

@ -0,0 +1,34 @@
output "cluster_name" {
description = "EKS cluster name."
value = module.eks.cluster_name
}
output "cluster_endpoint" {
description = "EKS API server endpoint."
value = module.eks.cluster_endpoint
}
output "cluster_certificate_authority_data" {
description = "Base64-encoded cluster CA certificate."
value = module.eks.cluster_certificate_authority_data
}
output "cluster_security_group_id" {
description = "Cluster control-plane security group."
value = module.eks.cluster_security_group_id
}
output "node_security_group_id" {
description = "Security group attached to managed node group ENIs (used by RDS / EFS to allow inbound traffic from nodes)."
value = module.eks.node_security_group_id
}
output "oidc_provider_arn" {
description = "IRSA OIDC provider ARN."
value = module.eks.oidc_provider_arn
}
output "oidc_provider_url" {
description = "IRSA OIDC issuer URL (without https://)."
value = module.eks.oidc_provider
}

View File

@ -0,0 +1,50 @@
variable "cluster_name" {
description = "EKS cluster name."
type = string
}
variable "cluster_version" {
description = "Kubernetes version for the EKS control plane."
type = string
default = "1.29"
}
variable "vpc_id" {
description = "VPC the cluster lives in."
type = string
}
variable "private_subnet_ids" {
description = "Private subnets for nodes and control-plane ENIs."
type = list(string)
}
variable "node_instance_type" {
description = "EC2 instance type for the managed node group."
type = string
default = "t3.large"
}
variable "node_min_size" {
description = "Minimum nodes in the managed node group."
type = number
default = 2
}
variable "node_max_size" {
description = "Maximum nodes in the managed node group."
type = number
default = 4
}
variable "node_desired_size" {
description = "Desired nodes in the managed node group."
type = number
default = 2
}
variable "tags" {
description = "Tags applied to every resource."
type = map(string)
default = {}
}

View File

@ -0,0 +1,197 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
tls = {
source = "hashicorp/tls"
version = ">= 4.0"
}
}
}
data "aws_caller_identity" "current" {}
data "aws_partition" "current" {}
##############################################
# EKS managed-node-group instance role
##############################################
data "aws_iam_policy_document" "ec2_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
resource "aws_iam_role" "eks_node" {
name = "${var.name_prefix}-eks-node"
assume_role_policy = data.aws_iam_policy_document.ec2_assume.json
tags = var.tags
}
locals {
eks_node_managed_policies = {
worker = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKSWorkerNodePolicy"
cni = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKS_CNI_Policy"
ecr_pull = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
ebs_csi = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
efs_csi = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy"
ssm = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
}
resource "aws_iam_role_policy_attachment" "eks_node" {
for_each = local.eks_node_managed_policies
role = aws_iam_role.eks_node.name
policy_arn = each.value
}
resource "aws_iam_instance_profile" "eks_node" {
name = aws_iam_role.eks_node.name
role = aws_iam_role.eks_node.name
}
##############################################
# AWS Load Balancer Controller IRSA role
##############################################
data "aws_iam_policy_document" "alb_irsa_assume" {
count = var.oidc_provider_arn == "" ? 0 : 1
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [var.oidc_provider_arn]
}
condition {
test = "StringEquals"
variable = "${var.oidc_provider_url}:sub"
values = ["system:serviceaccount:kube-system:aws-load-balancer-controller"]
}
condition {
test = "StringEquals"
variable = "${var.oidc_provider_url}:aud"
values = ["sts.amazonaws.com"]
}
}
}
resource "aws_iam_role" "alb_controller" {
count = var.oidc_provider_arn == "" ? 0 : 1
name = "${var.name_prefix}-alb-controller"
assume_role_policy = data.aws_iam_policy_document.alb_irsa_assume[0].json
tags = var.tags
}
resource "aws_iam_policy" "alb_controller" {
count = var.oidc_provider_arn == "" ? 0 : 1
name = "${var.name_prefix}-alb-controller"
description = "Permissions required by the AWS Load Balancer Controller."
policy = file("${path.module}/policies/alb_controller.json")
}
resource "aws_iam_role_policy_attachment" "alb_controller" {
count = var.oidc_provider_arn == "" ? 0 : 1
role = aws_iam_role.alb_controller[0].name
policy_arn = aws_iam_policy.alb_controller[0].arn
}
##############################################
# GitHub Actions OIDC provider + CI/CD role
##############################################
data "tls_certificate" "github" {
count = var.create_github_oidc ? 1 : 0
url = "https://token.actions.githubusercontent.com"
}
resource "aws_iam_openid_connect_provider" "github" {
count = var.create_github_oidc ? 1 : 0
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = data.tls_certificate.github[0].certificates[*].sha1_fingerprint
tags = var.tags
}
data "aws_iam_policy_document" "github_assume" {
count = var.create_github_oidc ? 1 : 0
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github[0].arn]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = [for r in var.github_repositories : "repo:${r}:*"]
}
}
}
resource "aws_iam_role" "github_actions" {
count = var.create_github_oidc ? 1 : 0
name = "${var.name_prefix}-github-actions"
assume_role_policy = data.aws_iam_policy_document.github_assume[0].json
tags = var.tags
}
# Permissions the CI role needs to push images, update kubeconfig, and apply manifests.
data "aws_iam_policy_document" "github_actions" {
count = var.create_github_oidc ? 1 : 0
statement {
sid = "ECRAuth"
actions = [
"ecr:GetAuthorizationToken",
]
resources = ["*"]
}
statement {
sid = "ECRPushPull"
actions = [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:CompleteLayerUpload",
"ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart",
"ecr:DescribeRepositories",
"ecr:ListImages",
]
resources = ["arn:${data.aws_partition.current.partition}:ecr:*:${data.aws_caller_identity.current.account_id}:repository/samosachaat-*"]
}
statement {
sid = "EKSDescribe"
actions = ["eks:DescribeCluster", "eks:ListClusters"]
resources = ["*"]
}
}
resource "aws_iam_policy" "github_actions" {
count = var.create_github_oidc ? 1 : 0
name = "${var.name_prefix}-github-actions"
policy = data.aws_iam_policy_document.github_actions[0].json
}
resource "aws_iam_role_policy_attachment" "github_actions" {
count = var.create_github_oidc ? 1 : 0
role = aws_iam_role.github_actions[0].name
policy_arn = aws_iam_policy.github_actions[0].arn
}

View File

@ -0,0 +1,29 @@
output "eks_node_role_arn" {
description = "ARN of the EKS managed-node-group instance role."
value = aws_iam_role.eks_node.arn
}
output "eks_node_role_name" {
description = "Name of the EKS node role."
value = aws_iam_role.eks_node.name
}
output "eks_node_instance_profile_name" {
description = "Instance profile attached to EKS nodes."
value = aws_iam_instance_profile.eks_node.name
}
output "alb_controller_role_arn" {
description = "IAM role to bind to the aws-load-balancer-controller ServiceAccount via IRSA."
value = try(aws_iam_role.alb_controller[0].arn, "")
}
output "github_actions_role_arn" {
description = "Role to assume from GitHub Actions for CI/CD (empty if not enabled)."
value = try(aws_iam_role.github_actions[0].arn, "")
}
output "github_oidc_provider_arn" {
description = "GitHub OIDC provider ARN (empty if not enabled)."
value = try(aws_iam_openid_connect_provider.github[0].arn, "")
}

View File

@ -0,0 +1,219 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:CreateServiceLinkedRole"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"iam:AWSServiceName": "elasticloadbalancing.amazonaws.com"
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeAccountAttributes",
"ec2:DescribeAddresses",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeInternetGateways",
"ec2:DescribeVpcs",
"ec2:DescribeVpcPeeringConnections",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeTags",
"ec2:GetCoipPoolUsage",
"ec2:DescribeCoipPools",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:DescribeListeners",
"elasticloadbalancing:DescribeListenerCertificates",
"elasticloadbalancing:DescribeSSLPolicies",
"elasticloadbalancing:DescribeRules",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetGroupAttributes",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:DescribeTags"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"cognito-idp:DescribeUserPoolClient",
"acm:ListCertificates",
"acm:DescribeCertificate",
"iam:ListServerCertificates",
"iam:GetServerCertificate",
"waf-regional:GetWebACL",
"waf-regional:GetWebACLForResource",
"waf-regional:AssociateWebACL",
"waf-regional:DisassociateWebACL",
"wafv2:GetWebACL",
"wafv2:GetWebACLForResource",
"wafv2:AssociateWebACL",
"wafv2:DisassociateWebACL",
"shield:GetSubscriptionState",
"shield:DescribeProtection",
"shield:CreateProtection",
"shield:DeleteProtection"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ec2:AuthorizeSecurityGroupIngress",
"ec2:RevokeSecurityGroupIngress"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateSecurityGroup"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateTags"
],
"Resource": "arn:aws:ec2:*:*:security-group/*",
"Condition": {
"StringEquals": {
"ec2:CreateAction": "CreateSecurityGroup"
},
"Null": {
"aws:RequestTag/elbv2.k8s.aws/cluster": "false"
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateTags",
"ec2:DeleteTags"
],
"Resource": "arn:aws:ec2:*:*:security-group/*",
"Condition": {
"Null": {
"aws:RequestTag/elbv2.k8s.aws/cluster": "true",
"aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:AuthorizeSecurityGroupIngress",
"ec2:RevokeSecurityGroupIngress",
"ec2:DeleteSecurityGroup"
],
"Resource": "*",
"Condition": {
"Null": {
"aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
}
}
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:CreateLoadBalancer",
"elasticloadbalancing:CreateTargetGroup"
],
"Resource": "*",
"Condition": {
"Null": {
"aws:RequestTag/elbv2.k8s.aws/cluster": "false"
}
}
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:CreateListener",
"elasticloadbalancing:DeleteListener",
"elasticloadbalancing:CreateRule",
"elasticloadbalancing:DeleteRule"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:AddTags",
"elasticloadbalancing:RemoveTags"
],
"Resource": [
"arn:aws:elasticloadbalancing:*:*:targetgroup/*/*",
"arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*",
"arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*"
],
"Condition": {
"Null": {
"aws:RequestTag/elbv2.k8s.aws/cluster": "true",
"aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
}
}
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:AddTags",
"elasticloadbalancing:RemoveTags"
],
"Resource": [
"arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*",
"arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*",
"arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*",
"arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*"
]
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:ModifyLoadBalancerAttributes",
"elasticloadbalancing:SetIpAddressType",
"elasticloadbalancing:SetSecurityGroups",
"elasticloadbalancing:SetSubnets",
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:ModifyTargetGroup",
"elasticloadbalancing:ModifyTargetGroupAttributes",
"elasticloadbalancing:DeleteTargetGroup"
],
"Resource": "*",
"Condition": {
"Null": {
"aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
}
}
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:RegisterTargets",
"elasticloadbalancing:DeregisterTargets"
],
"Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*"
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:SetWebAcl",
"elasticloadbalancing:ModifyListener",
"elasticloadbalancing:AddListenerCertificates",
"elasticloadbalancing:RemoveListenerCertificates",
"elasticloadbalancing:ModifyRule"
],
"Resource": "*"
}
]
}

View File

@ -0,0 +1,34 @@
variable "name_prefix" {
description = "Prefix for IAM resource names (e.g. samosachaat-dev)."
type = string
}
variable "oidc_provider_arn" {
description = "EKS OIDC provider ARN. Pass empty string to skip ALB controller role creation."
type = string
default = ""
}
variable "oidc_provider_url" {
description = "EKS OIDC issuer hostname (no scheme, e.g. oidc.eks.us-west-2.amazonaws.com/id/XXX)."
type = string
default = ""
}
variable "create_github_oidc" {
description = "Create the GitHub Actions OIDC provider + CI role. Set to true exactly once per AWS account."
type = bool
default = false
}
variable "github_repositories" {
description = "GitHub repositories allowed to assume the CI role (e.g. [\"manmohan659/nanochat\"])."
type = list(string)
default = []
}
variable "tags" {
description = "Tags applied to every resource."
type = map(string)
default = {}
}

View File

@ -0,0 +1,87 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.5"
}
}
}
resource "random_password" "db" {
length = 32
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "aws_security_group" "db" {
name = "${var.identifier}-rds-sg"
description = "PostgreSQL access for samosaChaat from EKS nodes only"
vpc_id = var.vpc_id
ingress {
description = "PostgreSQL from EKS nodes"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [var.eks_node_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = var.tags
}
module "db" {
source = "terraform-aws-modules/rds/aws"
version = "~> 6.0"
identifier = var.identifier
engine = "postgres"
engine_version = "15"
family = "postgres15"
major_engine_version = "15"
instance_class = var.instance_class
allocated_storage = var.allocated_storage
max_allocated_storage = var.max_allocated_storage
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = random_password.db.result
port = 5432
manage_master_user_password = false
multi_az = var.multi_az
db_subnet_group_name = null
subnet_ids = var.private_subnet_ids
create_db_subnet_group = true
vpc_security_group_ids = [aws_security_group.db.id]
publicly_accessible = false
backup_retention_period = 7
backup_window = "03:00-04:00"
maintenance_window = "Mon:04:00-Mon:05:00"
skip_final_snapshot = var.skip_final_snapshot
deletion_protection = var.deletion_protection
performance_insights_enabled = true
create_monitoring_role = true
monitoring_interval = 60
tags = var.tags
}

View File

@ -0,0 +1,36 @@
output "db_instance_endpoint" {
description = "Endpoint of the form host:port."
value = module.db.db_instance_endpoint
}
output "db_instance_address" {
description = "Hostname of the DB instance."
value = module.db.db_instance_address
}
output "db_instance_port" {
description = "Listening port."
value = module.db.db_instance_port
}
output "db_instance_name" {
description = "Initial database name."
value = module.db.db_instance_name
}
output "db_instance_username" {
description = "Master username."
value = module.db.db_instance_username
sensitive = true
}
output "db_password" {
description = "Generated master password (write to Secrets Manager / Parameter Store from your env config)."
value = random_password.db.result
sensitive = true
}
output "db_security_group_id" {
description = "Security group attached to the DB."
value = aws_security_group.db.id
}

View File

@ -0,0 +1,73 @@
variable "identifier" {
description = "DB instance identifier (also used as name prefix)."
type = string
}
variable "vpc_id" {
description = "VPC the database lives in."
type = string
}
variable "private_subnet_ids" {
description = "Private subnets for the DB subnet group (>= 2 AZs)."
type = list(string)
}
variable "eks_node_security_group_id" {
description = "Node SG that should be allowed inbound to PostgreSQL."
type = string
}
variable "instance_class" {
description = "RDS instance class (e.g. db.t3.micro for dev, db.t3.medium for prod)."
type = string
default = "db.t3.micro"
}
variable "db_name" {
description = "Initial database name."
type = string
default = "samosachaat"
}
variable "db_username" {
description = "Master username."
type = string
default = "samosachaat_admin"
}
variable "allocated_storage" {
description = "Initial storage (GB)."
type = number
default = 20
}
variable "max_allocated_storage" {
description = "Storage autoscaling cap (GB)."
type = number
default = 100
}
variable "multi_az" {
description = "Enable Multi-AZ (recommended for prod)."
type = bool
default = false
}
variable "skip_final_snapshot" {
description = "Skip the final snapshot when destroying (true for dev)."
type = bool
default = true
}
variable "deletion_protection" {
description = "Block accidental deletion (recommended for prod)."
type = bool
default = false
}
variable "tags" {
description = "Tags applied to every resource."
type = map(string)
default = {}
}

View File

@ -0,0 +1,58 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
# Use an existing hosted zone (created out-of-band when registering the domain).
data "aws_route53_zone" "this" {
name = var.domain_name
private_zone = false
}
# alb_dns_name / alb_zone_id come from the AWS Load Balancer Controller after the
# Ingress is created (look up via `kubectl get ingress` or a data source). Pass
# empty strings to skip A-record creation on the first apply, then re-apply.
resource "aws_route53_record" "apex" {
count = var.alb_dns_name == "" ? 0 : 1
zone_id = data.aws_route53_zone.this.zone_id
name = var.domain_name
type = "A"
alias {
name = var.alb_dns_name
zone_id = var.alb_zone_id
evaluate_target_health = true
}
}
resource "aws_route53_record" "subdomains" {
for_each = var.alb_dns_name == "" ? toset([]) : toset(var.subdomains)
zone_id = data.aws_route53_zone.this.zone_id
name = "${each.key}.${var.domain_name}"
type = "A"
alias {
name = var.alb_dns_name
zone_id = var.alb_zone_id
evaluate_target_health = true
}
}
# ACM DNS-validation CNAMEs. Pass the map exported by the ACM module.
resource "aws_route53_record" "acm_validation" {
for_each = var.acm_validation_records
zone_id = data.aws_route53_zone.this.zone_id
name = each.value.name
type = each.value.type
records = [each.value.record]
ttl = 60
allow_overwrite = true
}

View File

@ -0,0 +1,14 @@
output "zone_id" {
description = "Hosted zone ID for the apex domain."
value = data.aws_route53_zone.this.zone_id
}
output "name_servers" {
description = "Authoritative name servers (configure these at the registrar)."
value = data.aws_route53_zone.this.name_servers
}
output "apex_record_fqdn" {
description = "FQDN of the apex A record (empty until alb_dns_name is supplied)."
value = try(aws_route53_record.apex[0].fqdn, "")
}

View File

@ -0,0 +1,32 @@
variable "domain_name" {
description = "Apex domain (e.g. samosachaat.art). A hosted zone for this domain must already exist."
type = string
}
variable "subdomains" {
description = "Subdomains to alias to the ALB (e.g. [\"grafana\", \"api\"])."
type = list(string)
default = ["grafana"]
}
variable "alb_dns_name" {
description = "ALB DNS name from the AWS Load Balancer Controller. Empty string skips A-record creation (first-apply bootstrap)."
type = string
default = ""
}
variable "alb_zone_id" {
description = "ALB hosted-zone ID (region-specific)."
type = string
default = ""
}
variable "acm_validation_records" {
description = "Map keyed by domain → { name, type, record } — pass module.acm.validation_records here."
type = map(object({
name = string
type = string
record = string
}))
default = {}
}

View File

@ -0,0 +1,44 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
locals {
cluster_tag_key = "kubernetes.io/cluster/${var.cluster_name}"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.name}-vpc"
cidr = var.cidr
azs = var.azs
private_subnets = var.private_subnets
public_subnets = var.public_subnets
enable_nat_gateway = true
single_nat_gateway = var.single_nat_gateway
one_nat_gateway_per_az = !var.single_nat_gateway
enable_dns_hostnames = true
enable_dns_support = true
public_subnet_tags = {
"kubernetes.io/role/elb" = "1"
(local.cluster_tag_key) = "shared"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = "1"
(local.cluster_tag_key) = "shared"
}
tags = var.tags
}

View File

@ -0,0 +1,34 @@
output "vpc_id" {
description = "VPC identifier."
value = module.vpc.vpc_id
}
output "vpc_cidr_block" {
description = "Primary CIDR block of the VPC."
value = module.vpc.vpc_cidr_block
}
output "private_subnet_ids" {
description = "Private subnet identifiers (one per AZ)."
value = module.vpc.private_subnets
}
output "public_subnet_ids" {
description = "Public subnet identifiers (one per AZ)."
value = module.vpc.public_subnets
}
output "private_subnet_cidrs" {
description = "Private subnet CIDR blocks."
value = module.vpc.private_subnets_cidr_blocks
}
output "azs" {
description = "AZs in use."
value = module.vpc.azs
}
output "natgw_ids" {
description = "NAT gateway identifiers."
value = module.vpc.natgw_ids
}

View File

@ -0,0 +1,45 @@
variable "name" {
description = "Name prefix used for the VPC and related resources."
type = string
}
variable "cluster_name" {
description = "EKS cluster name used to tag subnets so the AWS Load Balancer Controller can discover them."
type = string
}
variable "cidr" {
description = "CIDR block for the VPC."
type = string
default = "10.0.0.0/16"
}
variable "azs" {
description = "Availability zones to spread subnets across."
type = list(string)
default = ["us-west-2a", "us-west-2b", "us-west-2c"]
}
variable "private_subnets" {
description = "Private subnet CIDRs (one per AZ, in matching order)."
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
variable "public_subnets" {
description = "Public subnet CIDRs (one per AZ, in matching order)."
type = list(string)
default = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
}
variable "single_nat_gateway" {
description = "When true, all private subnets route through a single NAT gateway (dev). When false, one NAT per AZ (prod)."
type = bool
default = true
}
variable "tags" {
description = "Tags applied to every resource."
type = map(string)
default = {}
}

18
terraform/versions.tf Normal file
View File

@ -0,0 +1,18 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.5"
}
tls = {
source = "hashicorp/tls"
version = ">= 4.0"
}
}
}