infra/burst/state/main.tf (4313 bytes)
1 # The shared state of burst mode: Postgres and Redis on the web server, the 2 # file bucket (Cloudflare R2), and the key of the SSH tunnel that each burst 3 # worker uses to reach Postgres and Redis. Workers can come from any cloud; 4 # this module does not depend on one. 5 # 6 # Run it ON THE WEB SERVER, as root: the setup step changes this machine 7 # (see setup-web-server.sh). See ../README.md for when and how. 8 9 terraform { 10 required_version = ">= 1.9" 11 required_providers { 12 cloudflare = { 13 source = "cloudflare/cloudflare" 14 version = "~> 5.25" 15 } 16 random = { 17 source = "hashicorp/random" 18 version = "~> 3.6" 19 } 20 tls = { 21 source = "hashicorp/tls" 22 version = "~> 4.0" 23 } 24 } 25 # The state holds passwords and keys: /var/lib/subplz-burst, root only. 26 backend "local" {} 27 } 28 29 # Reads CLOUDFLARE_API_TOKEN. 30 provider "cloudflare" {} 31 32 resource "random_password" "postgres" { 33 length = 32 34 special = false 35 } 36 37 resource "random_password" "redis" { 38 length = 32 39 special = false 40 } 41 42 # One key for every worker: it can open the two tunnels and nothing else. 43 resource "tls_private_key" "tunnel" { 44 algorithm = "ED25519" 45 } 46 47 resource "cloudflare_r2_bucket" "files" { 48 account_id = var.cloudflare_account_id 49 name = var.bucket_name 50 location = var.bucket_location 51 } 52 53 # The permission groups "read objects" and "write objects" of R2, found by name 54 # unless var.r2_permission_group_ids gives them. 55 data "cloudflare_api_token_permission_groups_list" "r2_read" { 56 count = var.r2_permission_group_ids == null ? 1 : 0 57 name = "Workers%20R2%20Storage%20Bucket%20Item%20Read" 58 } 59 60 data "cloudflare_api_token_permission_groups_list" "r2_write" { 61 count = var.r2_permission_group_ids == null ? 1 : 0 62 name = "Workers%20R2%20Storage%20Bucket%20Item%20Write" 63 } 64 65 # The app and the workers get a key for this bucket only. R2 takes the token 66 # id as the S3 key id, and the SHA-256 of the token value as the S3 secret. 67 resource "cloudflare_api_token" "files" { 68 name = "${var.bucket_name}-app" 69 policies = [{ 70 effect = "allow" 71 permission_groups = [for id in local.r2_permission_groups : { id = id }] 72 resources = jsonencode({ 73 "com.cloudflare.edge.r2.bucket.${var.cloudflare_account_id}_default_${cloudflare_r2_bucket.files.name}" = "*" 74 }) 75 }] 76 } 77 78 # Postgres and Redis on 127.0.0.1, and the tunnel user. The script is 79 # idempotent: a second run changes nothing. 80 resource "terraform_data" "web_server" { 81 triggers_replace = [ 82 filesha256("${path.module}/setup-web-server.sh"), 83 sha256(random_password.postgres.result), 84 sha256(random_password.redis.result), 85 tls_private_key.tunnel.public_key_openssh, 86 ] 87 88 provisioner "local-exec" { 89 command = "bash ${path.module}/setup-web-server.sh" 90 environment = { 91 POSTGRES_PASSWORD = random_password.postgres.result 92 REDIS_PASSWORD = random_password.redis.result 93 TUNNEL_PUBLIC_KEY = tls_private_key.tunnel.public_key_openssh 94 } 95 } 96 } 97 98 locals { 99 r2_permission_groups = var.r2_permission_group_ids != null ? var.r2_permission_group_ids : concat( 100 [for d in data.cloudflare_api_token_permission_groups_list.r2_read : d.result[0].id], 101 [for d in data.cloudflare_api_token_permission_groups_list.r2_write : d.result[0].id], 102 ) 103 s3_endpoint = "https://${var.cloudflare_account_id}.r2.cloudflarestorage.com" 104 s3_key_id = cloudflare_api_token.files.id 105 s3_secret = sha256(cloudflare_api_token.files.value) 106 # Through the tunnel, a worker sees Postgres and Redis on its own 127.0.0.1, 107 # as the web server does. 108 database_url = "postgresql+psycopg://subplz:${random_password.postgres.result}@127.0.0.1:5432/subplz" 109 redis_url = "redis://:${random_password.redis.result}@127.0.0.1:6379/0" 110 known_hosts = ( 111 var.ssh_port == 22 112 ? "${var.web_server_ip} ${trimspace(file(var.host_key_file))}" 113 : "[${var.web_server_ip}]:${var.ssh_port} ${trimspace(file(var.host_key_file))}" 114 ) 115 # boto3 1.36 and later send checksums that some S3 services refuse. 116 storage_env = <<-EOT 117 AWS_ACCESS_KEY_ID=${local.s3_key_id} 118 AWS_SECRET_ACCESS_KEY=${local.s3_secret} 119 AWS_ENDPOINT_URL_S3=${local.s3_endpoint} 120 AWS_DEFAULT_REGION=auto 121 AWS_REQUEST_CHECKSUM_CALCULATION=when_required 122 AWS_RESPONSE_CHECKSUM_VALIDATION=when_required 123 EOT 124 }