// 2026-04-01 — postgres.tf: Managed PostgreSQL инстанс, пользователь и база данных. // // Порядок создания: // 1. nubes_postgres — сам инстанс PostgreSQL // 2. nubes_postgres_user — пользователь; пароль автоматически попадает в vault_secrets // 3. nubes_postgres_database — база данных с owner = созданный пользователь // // Важно: vault_secrets["users"] появляется только ПОСЛЕ первого apply (нет пользователя — нет ключа). // try() в locals страхует от ошибки на первом прогоне. // ── Locals: credentials из vault ───────────────────────────────────────────── locals { # Карта username→{password, username} из vault_secrets, который Nubes заполняет после # создания пользователя. try() нужен для первого apply, когда ключа ещё нет. pg_creds_map = try( jsondecode(lookup(nubes_postgres.pg_test_instance.vault_secrets, "users", "{}")), {} ) pg_password = try(local.pg_creds_map[var.pg_username]["password"], "") # Адрес master-ноды (внутренний — для подключения из кластера). pg_host = nubes_postgres.pg_test_instance.state_out_flat["internalConnect.master"] pg_port = 5432 } // ── Инстанс PostgreSQL ──────────────────────────────────────────────────────── resource "nubes_postgres" "pg_test_instance" { resource_name = var.pg_resource_name s3_uid = var.s3_uid resource_realm = var.realm # Минимальные ресурсы — достаточно для тестирования. resource_instances = 1 resource_memory = 512 # MiB resource_c_p_u = 500 # millicores resource_disk = "1" # GiB app_version = "17" # json_parameters убран — при передаче пустого объекта API возвращает "Invalid JSON String". # Если нужны кастомные параметры PG — добавить после диагностики. # Pooler не нужен для тестов — упрощает топологию. enable_pg_pooler_master = false enable_pg_pooler_slave = false allow_no_s_s_l = false auto_scale = false auto_scale_percentage = 10 auto_scale_tech_window = 0 auto_scale_quota_gb = "1" # Внешний адрес не нужен — подключаемся изнутри кластера. need_external_address_master = false operation_timeout = "11m" # Позволяет импортировать уже существующий инстанс с тем же именем, не падая # с "already exists" — удобно при повторном apply после ручного создания. adopt_existing_on_create = true } // ── Пользователь ────────────────────────────────────────────────────────────── resource "nubes_postgres_user" "pg_test_user" { postgres_id = nubes_postgres.pg_test_instance.id username = var.pg_username role = var.pg_role # Не падать если пользователь с таким именем уже существует. adopt_existing_on_create = true } resource "nubes_postgres_user" "pg_test_user3" { postgres_id = nubes_postgres.pg_test_instance.id username = "u3" role = var.pg_role depends_on = [nubes_postgres_user.pg_test_user] # Не падать если пользователь с таким именем уже существует. adopt_existing_on_create = true } // ── База данных ─────────────────────────────────────────────────────────────── resource "nubes_postgres_database" "pg_test_db" { postgres_id = nubes_postgres.pg_test_instance.id db_name = var.pg_db_name db_owner = nubes_postgres_user.pg_test_user.username # Не падать если БД уже существует. adopt_existing_on_create = true # ВАЖНО: из-за ограничения API Nubes (ERR-PG-08: "Concurrent operations are not supported") # нужно явно ждать пользователя даже если он не выглядит dependency. # других ресурс на инстансе ещё обрабатывает операции. depends_on = [nubes_postgres_user.pg_test_user3] }