refactor: Use unified infrastructure.yaml config file

- Add infrastructure.yaml as single source of truth (like values.yaml in Helm)
- Make all modules universal (no hardcoded entity names)
- Use existing PostgreSQL cluster instead of creating new one
- Add yc-database module for working with existing PostgreSQL
- Add k8s-secret module with lifecycle.ignore_changes support
- Update terragrunt.hcl files to read from infrastructure.yaml
- Remove scripts, use native Terragrunt functions (yamldecode)
This commit is contained in:
kochetkov.s 2026-01-19 15:28:39 +03:00
parent 1bba8d0d53
commit be34bc617b
24 changed files with 529 additions and 497 deletions

174
README.md
View File

@ -2,129 +2,143 @@
Этот репозиторий содержит инфраструктуру как код (IaC) для проекта Pulse, развернутого в Yandex Cloud. Этот репозиторий содержит инфраструктуру как код (IaC) для проекта Pulse, развернутого в Yandex Cloud.
## Архитектура
Проект использует **единый конфигурационный файл** `infrastructure.yaml` (аналог `values.yaml` в Helm), который описывает всю инфраструктуру декларативно. Модули Terraform универсальны и не знают про конкретные сущности - они используются для создания ресурсов, описанных в конфиге.
## Структура репозитория ## Структура репозитория
``` ```
. .
├── modules/ # Переиспользуемые Terraform модули ├── infrastructure.yaml # Единый конфигурационный файл (аналог values.yaml)
│ ├── yc-s3/ # Модуль для создания S3 бакета ├── modules/ # Универсальные Terraform модули
│ ├── yc-postgresql/ # Модуль для создания PostgreSQL кластера │ ├── yc-s3/ # Модуль для создания S3 бакета
│ ├── k8s-namespace/ # Модуль для создания Kubernetes namespace │ ├── yc-database/ # Модуль для работы с существующим PostgreSQL
│ └── k8s-secrets/ # Модуль для создания Kubernetes секретов │ ├── k8s-namespace/ # Модуль для создания Kubernetes namespace
├── live/ # Живая инфраструктура │ └── k8s-secret/ # Модуль для создания Kubernetes секретов
│ ├── terragrunt.hcl # Глобальная конфигурация Terragrunt └── live/ # Живая инфраструктура
│ ├── stage/ # Окружение stage ├── terragrunt.hcl # Глобальная конфигурация Terragrunt
│ │ ├── env.hcl # Конфигурация окружения └── stage/ # Окружение stage
│ │ ├── namespace/ # Namespace pulse ├── env.hcl # Конфигурация окружения
│ │ ├── s3/ # S3 бакет pulse ├── namespace/ # Namespace из конфига
│ │ ├── postgresql/ # PostgreSQL база данных ├── s3/ # S3 бакет из конфига
│ │ └── secrets/ # Kubernetes секреты ├── database/ # База данных из конфига
│ ├── prod/ # Окружение production └── secrets/ # Секреты из конфига
│ └── preprod/ # Окружение preprod
├── scripts/ # Вспомогательные скрипты
│ └── generate-pipeline.sh # Генератор GitLab CI пайплайна
└── .gitlab-ci.yml # Основной GitLab CI конфигурация
``` ```
## Требования ## Конфигурационный файл
- Terraform >= 1.0 Все ресурсы описываются в `infrastructure.yaml`:
- Terragrunt >= 0.50.0
- Доступ к Yandex Cloud
- Доступ к Kubernetes кластеру
## Переменные окружения ```yaml
environments:
stage:
namespaces:
- name: pulse
labels: {}
annotations: {}
secrets:
- name: dockerhub
namespace: pulse
type: dockerconfigjson
# ...
buckets:
- name: pulse
acl: public-read
# ...
databases:
- cluster_id: pg-stage # ID существующего кластера
database:
name: pulse_db
user:
name: pulse
password_length: 32
```
Для работы с инфраструктурой необходимо установить следующие переменные окружения: Каждый `terragrunt.hcl` читает этот конфиг через `yamldecode(file(...))` и извлекает нужные параметры.
### Yandex Cloud ## Модули
- `YC_TOKEN` - токен для доступа к Yandex Cloud API
- `YC_CLOUD_ID` - ID облака
- `YC_FOLDER_ID` - ID каталога (или `YC_STAGE_FOLDER_ID`, `YC_PROD_FOLDER_ID`, `YC_PREPROD_FOLDER_ID`)
### Terraform State Все модули **универсальны** и не содержат хардкода про конкретные сущности:
- `TF_STATE_BUCKET` - имя S3 бакета для хранения состояния Terraform
- `TF_STATE_DYNAMODB_ENDPOINT` - endpoint YDB для блокировок состояния
- `TF_STATE_DYNAMODB_TABLE` - имя таблицы YDB для блокировок
### Kubernetes - **yc-s3** - создает S3 бакет с любыми параметрами из конфига
- `KUBECONFIG` - путь к конфигурации Kubernetes (по умолчанию `~/.kube/config`) - **yc-database** - работает с существующим PostgreSQL кластером, создает БД и пользователя
- `KUBE_CONTEXT` - контекст Kubernetes (опционально) - **k8s-namespace** - создает namespace с любыми labels/annotations
- **k8s-secret** - создает секреты с поддержкой `lifecycle.ignore_changes`
### Docker Registry ## Особенности
- `DOCKER_REGISTRY_URL` - URL Docker registry (по умолчанию `cr.yandex`)
- `DOCKER_REGISTRY_USERNAME` - имя пользователя Docker registry
- `DOCKER_REGISTRY_PASSWORD` - пароль или токен Docker registry
### Сеть ### Секреты с защитой от перезаписи
- `YC_NETWORK_ID` - ID сети для PostgreSQL
- `YC_SUBNET_ID` - ID подсети для PostgreSQL Секреты могут быть настроены с `lifecycle.ignore_changes: true`, чтобы не перезаписываться после создания:
```yaml
secrets:
- name: pulse-s3-secret
lifecycle:
ignore_changes: true
```
### Использование существующего PostgreSQL
Модуль `yc-database` работает с **существующим** кластером PostgreSQL в Yandex Cloud. Он только создает базу данных и пользователя, не создавая новый кластер.
### Зависимости между ресурсами
Terragrunt автоматически управляет зависимостями через блоки `dependency`. Например, секреты зависят от namespace, s3 и database, и получают их outputs.
## Использование ## Использование
### Локальная разработка ### Локальная разработка
1. Перейдите в директорию нужного компонента: 1. Отредактируйте `infrastructure.yaml` для добавления/изменения ресурсов
2. Перейдите в директорию компонента:
```bash ```bash
cd live/stage/namespace cd live/stage/namespace
``` ```
2. Инициализируйте Terragrunt: 3. Инициализируйте Terragrunt:
```bash ```bash
terragrunt init terragrunt init
``` ```
3. Просмотрите план изменений: 4. Просмотрите план:
```bash ```bash
terragrunt plan terragrunt plan
``` ```
4. Примените изменения: 5. Примените изменения:
```bash ```bash
terragrunt apply terragrunt apply
``` ```
### Развертывание всех компонентов ### Развертывание всех компонентов
Для развертывания всех компонентов окружения используйте:
```bash ```bash
cd live/stage cd live/stage
terragrunt run-all apply terragrunt run-all apply
``` ```
## Переменные окружения
- `YC_TOKEN` - токен Yandex Cloud
- `YC_CLOUD_ID` - ID облака
- `YC_FOLDER_ID` / `YC_STAGE_FOLDER_ID` - ID каталога
- `DOCKER_REGISTRY_USERNAME` / `DOCKER_REGISTRY_PASSWORD` - для секрета dockerhub
- `TF_STATE_BUCKET` - бакет для хранения state
- `TF_STATE_DYNAMODB_ENDPOINT` / `TF_STATE_DYNAMODB_TABLE` - для блокировок state
## GitLab CI ## GitLab CI
Проект использует динамическую генерацию GitLab CI пайплайнов через downstream pipelines. При каждом коммите: Проект использует динамическую генерацию GitLab CI пайплайнов через downstream pipelines. Для каждого компонента создаются джобы: validate, plan, apply.
1. Запускается джоба `generate-pipeline`, которая сканирует структуру `live/` и создает джобы для каждого компонента ## Принципы
2. Запускается downstream пайплайн с сгенерированными джобами
Для каждого компонента создаются три джобы: 1. **DRY** - вся конфигурация в одном файле `infrastructure.yaml`
- `validate-{env}-{component}` - валидация конфигурации 2. **Универсальность** - модули не знают про конкретные сущности
- `plan-{env}-{component}` - планирование изменений 3. **Декларативность** - описание желаемого состояния, а не шагов
- `apply-{env}-{component}` - применение изменений (manual для prod) 4. **Идемпотентность** - повторный запуск безопасен
5. **Изоляция** - каждый компонент имеет свой state
## Созданные ресурсы
### Stage окружение
- **Namespace**: `pulse` в Kubernetes
- **S3 бакет**: `pulse` с публичным доступом
- **PostgreSQL**: кластер `pulse-pg-stage` с базой `pulse_db` и пользователем `pulse`
- **Секреты Kubernetes**:
- `dockerhub` - доступ к Yandex Container Registry
- `pulse-s3-secret` - ключи доступа к S3
- `pulse-postgresql-secret` - параметры подключения к PostgreSQL
## Безопасность
⚠️ **Важно**: Все секреты (пароли, ключи доступа) хранятся в Terraform state в открытом виде. Убедитесь, что:
- Бэкенд для state зашифрован (AES-256)
- Доступ к state строго ограничен
- Используется блокировка состояния через YDB
## Лицензия
Внутренний проект компании.

64
infrastructure.yaml Normal file
View File

@ -0,0 +1,64 @@
# Единый конфигурационный файл инфраструктуры
# Аналог values.yaml в Helm
environments:
stage:
namespaces:
- name: pulse
labels:
environment: stage
project: pulse
annotations:
managed-by: terraform
secrets:
- name: dockerhub
namespace: pulse
type: dockerconfigjson
registry_url: cr.yandex
# username и password из переменных окружения
- name: pulse-s3-secret
namespace: pulse
type: opaque
keys:
access_key: "" # Заполнится из outputs модуля s3
secret_key: "" # Заполнится из outputs модуля s3
bucket: "" # Заполнится из outputs модуля s3
endpoint: https://storage.yandexcloud.net
lifecycle:
ignore_changes: true
- name: pulse-postgresql-secret
namespace: pulse
type: opaque
keys:
host: "" # Заполнится из outputs модуля database
port: "6432"
database: "" # Заполнится из outputs модуля database
user: "" # Заполнится из outputs модуля database
password: "" # Заполнится из outputs модуля database
lifecycle:
ignore_changes: true
buckets:
- name: pulse
acl: public-read
versioning:
enabled: false
cors:
enabled: true
allowed_headers: ["*"]
allowed_methods: ["GET", "PUT", "POST", "DELETE", "HEAD"]
allowed_origins: ["*"]
expose_headers: ["ETag"]
max_age_seconds: 3600
databases:
- cluster_id: pg-stage # ID существующего кластера PostgreSQL
database:
name: pulse_db
user:
name: pulse
password_length: 32
password_special: false

View File

@ -0,0 +1,37 @@
# Включение корневой конфигурации
include "root" {
path = find_in_parent_folders()
}
# Включение конфигурации окружения
include "env" {
path = find_in_parent_folders("env.hcl")
expose = true
merge_strategy = "deep"
}
# Чтение конфигурации инфраструктуры
locals {
infra_config = yamldecode(file("${get_parent_terragrunt_dir()}/../../infrastructure.yaml"))
env_config = local.infra_config.environments[local.environment]
db_config = local.env_config.databases[0] # Первая БД из списка
}
# Путь к модулю
terraform {
source = "${get_parent_terragrunt_dir()}/../../modules//yc-database"
}
# Входные переменные из конфига
inputs = {
cluster_id = local.db_config.cluster_id
database_name = local.db_config.database.name
user_name = local.db_config.user.name
password_length = try(local.db_config.user.password_length, 32)
password_special = try(local.db_config.user.password_special, false)
permissions = [
{
database_name = local.db_config.database.name
}
]
}

View File

@ -10,19 +10,21 @@ include "env" {
merge_strategy = "deep" merge_strategy = "deep"
} }
# Чтение конфигурации инфраструктуры
locals {
infra_config = yamldecode(file("${get_parent_terragrunt_dir()}/../../infrastructure.yaml"))
env_config = local.infra_config.environments[local.environment]
namespace = local.env_config.namespaces[0] # Первый namespace из списка
}
# Путь к модулю # Путь к модулю
terraform { terraform {
source = "${get_parent_terragrunt_dir()}/../../modules//k8s-namespace" source = "${get_parent_terragrunt_dir()}/../../modules//k8s-namespace"
} }
# Входные переменные # Входные переменные из конфига
inputs = { inputs = {
namespace_name = "pulse" namespace_name = local.namespace.name
labels = { labels = local.namespace.labels
environment = local.environment annotations = local.namespace.annotations
project = "pulse"
}
annotations = {
"managed-by" = "terraform"
}
} }

View File

@ -1,33 +0,0 @@
# Включение корневой конфигурации
include "root" {
path = find_in_parent_folders()
}
# Включение конфигурации окружения
include "env" {
path = find_in_parent_folders("env.hcl")
expose = true
merge_strategy = "deep"
}
# Путь к модулю
terraform {
source = "${get_parent_terragrunt_dir()}/../../modules//yc-postgresql"
}
# Входные переменные
inputs = {
cluster_name = "pulse-pg-${local.environment}"
folder_id = local.folder_id
network_id = get_env("YC_NETWORK_ID", "")
subnet_id = get_env("YC_SUBNET_ID", "")
zone = "ru-central1-a"
environment = "PRODUCTION"
postgresql_version = "15"
resource_preset_id = "s2.micro"
disk_type_id = "network-ssd"
disk_size = 10
database_name = "pulse_db"
database_user = "pulse"
deletion_protection = false
}

View File

@ -10,14 +10,23 @@ include "env" {
merge_strategy = "deep" merge_strategy = "deep"
} }
# Чтение конфигурации инфраструктуры
locals {
infra_config = yamldecode(file("${get_parent_terragrunt_dir()}/../../infrastructure.yaml"))
env_config = local.infra_config.environments[local.environment]
bucket = local.env_config.buckets[0] # Первый bucket из списка
}
# Путь к модулю # Путь к модулю
terraform { terraform {
source = "${get_parent_terragrunt_dir()}/../../modules//yc-s3" source = "${get_parent_terragrunt_dir()}/../../modules//yc-s3"
} }
# Входные переменные # Входные переменные из конфига
inputs = { inputs = {
bucket_name = "pulse" bucket_name = local.bucket.name
folder_id = local.folder_id folder_id = local.folder_id
versioning_enabled = false versioning_enabled = try(local.bucket.versioning.enabled, false)
cors_enabled = try(local.bucket.cors.enabled, false)
cors_config = try(local.bucket.cors, {})
} }

View File

@ -13,7 +13,6 @@ include "env" {
# Зависимости от других компонентов # Зависимости от других компонентов
dependency "namespace" { dependency "namespace" {
config_path = "../namespace" config_path = "../namespace"
mock_outputs = { mock_outputs = {
name = "pulse" name = "pulse"
} }
@ -22,7 +21,6 @@ dependency "namespace" {
dependency "s3" { dependency "s3" {
config_path = "../s3" config_path = "../s3"
mock_outputs = { mock_outputs = {
bucket_name = "pulse" bucket_name = "pulse"
access_key = "mock-access-key" access_key = "mock-access-key"
@ -31,42 +29,76 @@ dependency "s3" {
mock_outputs_allowed_terraform_commands = ["validate", "plan"] mock_outputs_allowed_terraform_commands = ["validate", "plan"]
} }
dependency "postgresql" { dependency "database" {
config_path = "../postgresql" config_path = "../database"
mock_outputs = { mock_outputs = {
host = "mock-host.example.com" host = "mock-host.example.com"
database_name = "pulse_db" database_name = "pulse_db"
database_user = "pulse" user_name = "pulse"
password = "mock-password" password = "mock-password"
} }
mock_outputs_allowed_terraform_commands = ["validate", "plan"] mock_outputs_allowed_terraform_commands = ["validate", "plan"]
} }
# Чтение конфигурации инфраструктуры
locals {
infra_config = yamldecode(file("${get_parent_terragrunt_dir()}/../../infrastructure.yaml"))
env_config = local.infra_config.environments[local.environment]
secrets = local.env_config.secrets
}
# Путь к модулю # Путь к модулю
terraform { terraform {
source = "${get_parent_terragrunt_dir()}/../../modules//k8s-secrets" source = "${get_parent_terragrunt_dir()}/../../modules//k8s-secret"
} }
# Входные переменные # Входные переменные из конфига с подстановкой значений из dependencies
inputs = { inputs = {
namespace = dependency.namespace.outputs.name secrets = [
# dockerhub секрет
# Docker Hub (Yandex Registry) credentials {
docker_registry_url = get_env("DOCKER_REGISTRY_URL", "cr.yandex") name = local.secrets[0].name
docker_registry_username = get_env("DOCKER_REGISTRY_USERNAME", "") namespace = dependency.namespace.outputs.name
docker_registry_password = get_env("DOCKER_REGISTRY_PASSWORD", "") secret_type = local.secrets[0].type
data = {
# S3 credentials from dependency ".dockerconfigjson" = jsonencode({
s3_access_key = dependency.s3.outputs.access_key auths = {
s3_secret_key = dependency.s3.outputs.secret_key "${local.secrets[0].registry_url}" = {
s3_bucket_name = dependency.s3.outputs.bucket_name username = get_env("DOCKER_REGISTRY_USERNAME", "")
s3_endpoint = "https://storage.yandexcloud.net" password = get_env("DOCKER_REGISTRY_PASSWORD", "")
auth = base64encode("${get_env("DOCKER_REGISTRY_USERNAME", "")}:${get_env("DOCKER_REGISTRY_PASSWORD", "")}")
# PostgreSQL credentials from dependency }
db_host = dependency.postgresql.outputs.host }
db_port = dependency.postgresql.outputs.port })
db_name = dependency.postgresql.outputs.database_name }
db_user = dependency.postgresql.outputs.database_user ignore_changes = false
db_password = dependency.postgresql.outputs.password },
# pulse-s3-secret
{
name = local.secrets[1].name
namespace = dependency.namespace.outputs.name
secret_type = local.secrets[1].type
data = {
access_key = base64encode(dependency.s3.outputs.access_key)
secret_key = base64encode(dependency.s3.outputs.secret_key)
bucket = base64encode(dependency.s3.outputs.bucket_name)
endpoint = base64encode(local.secrets[1].keys.endpoint)
}
ignore_changes = try(local.secrets[1].lifecycle.ignore_changes, false)
},
# pulse-postgresql-secret
{
name = local.secrets[2].name
namespace = dependency.namespace.outputs.name
secret_type = local.secrets[2].type
data = {
host = base64encode(dependency.database.outputs.host)
port = base64encode(local.secrets[2].keys.port)
database = base64encode(dependency.database.outputs.database_name)
user = base64encode(dependency.database.outputs.user_name)
password = base64encode(dependency.database.outputs.password)
}
ignore_changes = try(local.secrets[2].lifecycle.ignore_changes, false)
}
]
} }

View File

@ -0,0 +1,20 @@
# Универсальный модуль для создания Kubernetes секрета
# Не знает про конкретные сущности, работает с любыми данными
resource "kubernetes_secret" "this" {
metadata {
name = var.secret_name
namespace = var.namespace
labels = var.labels
annotations = var.annotations
}
type = var.secret_type
data = var.data
# Защита от перезаписи если указано в lifecycle
lifecycle {
ignore_changes = var.ignore_changes ? [data] : []
}
}

View File

@ -0,0 +1,9 @@
output "secret_name" {
description = "Имя созданного секрета"
value = kubernetes_secret.this.metadata[0].name
}
output "secret_id" {
description = "ID созданного секрета"
value = kubernetes_secret.this.id
}

View File

@ -0,0 +1,38 @@
variable "secret_name" {
description = "Имя секрета"
type = string
}
variable "namespace" {
description = "Kubernetes namespace"
type = string
}
variable "secret_type" {
description = "Тип секрета (Opaque, kubernetes.io/dockerconfigjson, etc.)"
type = string
default = "Opaque"
}
variable "data" {
description = "Данные секрета (map строк)"
type = map(string)
}
variable "labels" {
description = "Labels для секрета"
type = map(string)
default = {}
}
variable "annotations" {
description = "Annotations для секрета"
type = map(string)
default = {}
}
variable "ignore_changes" {
description = "Игнорировать изменения после создания (не перезаписывать)"
type = bool
default = false
}

View File

@ -1,56 +0,0 @@
# Секрет для доступа к Docker Hub (Yandex Registry)
resource "kubernetes_secret" "dockerhub" {
metadata {
name = "dockerhub"
namespace = var.namespace
}
type = "kubernetes.io/dockerconfigjson"
data = {
".dockerconfigjson" = jsonencode({
auths = {
"${var.docker_registry_url}" = {
username = var.docker_registry_username
password = var.docker_registry_password
auth = base64encode("${var.docker_registry_username}:${var.docker_registry_password}")
}
}
})
}
}
# Секрет для доступа к S3
resource "kubernetes_secret" "pulse_s3" {
metadata {
name = "pulse-s3-secret"
namespace = var.namespace
}
type = "Opaque"
data = {
access_key = base64encode(var.s3_access_key)
secret_key = base64encode(var.s3_secret_key)
bucket = base64encode(var.s3_bucket_name)
endpoint = base64encode(var.s3_endpoint)
}
}
# Секрет для доступа к PostgreSQL
resource "kubernetes_secret" "pulse_postgresql" {
metadata {
name = "pulse-postgresql-secret"
namespace = var.namespace
}
type = "Opaque"
data = {
host = base64encode(var.db_host)
port = base64encode(tostring(var.db_port))
database = base64encode(var.db_name)
user = base64encode(var.db_user)
password = base64encode(var.db_password)
}
}

View File

@ -1,14 +0,0 @@
output "dockerhub_secret_name" {
description = "Name of the dockerhub secret"
value = kubernetes_secret.dockerhub.metadata[0].name
}
output "s3_secret_name" {
description = "Name of the S3 secret"
value = kubernetes_secret.pulse_s3.metadata[0].name
}
output "postgresql_secret_name" {
description = "Name of the PostgreSQL secret"
value = kubernetes_secret.pulse_postgresql.metadata[0].name
}

View File

@ -1,71 +0,0 @@
variable "namespace" {
description = "Kubernetes namespace where secrets will be created"
type = string
}
variable "docker_registry_url" {
description = "Docker registry URL (e.g., cr.yandex)"
type = string
default = "cr.yandex"
}
variable "docker_registry_username" {
description = "Docker registry username"
type = string
}
variable "docker_registry_password" {
description = "Docker registry password or token"
type = string
sensitive = true
}
variable "s3_access_key" {
description = "S3 access key"
type = string
sensitive = true
}
variable "s3_secret_key" {
description = "S3 secret key"
type = string
sensitive = true
}
variable "s3_bucket_name" {
description = "S3 bucket name"
type = string
}
variable "s3_endpoint" {
description = "S3 endpoint URL"
type = string
default = "https://storage.yandexcloud.net"
}
variable "db_host" {
description = "PostgreSQL host"
type = string
}
variable "db_port" {
description = "PostgreSQL port"
type = number
default = 6432
}
variable "db_name" {
description = "PostgreSQL database name"
type = string
}
variable "db_user" {
description = "PostgreSQL user"
type = string
}
variable "db_password" {
description = "PostgreSQL password"
type = string
sensitive = true
}

View File

@ -0,0 +1,46 @@
# Модуль для работы с существующим PostgreSQL кластером
# Создает базу данных и пользователя в существующем кластере
# Генерация пароля для пользователя
resource "random_password" "user_password" {
length = var.password_length
special = var.password_special
upper = true
lower = true
numeric = true
}
# Получение информации о существующем кластере
data "yandex_mdb_postgresql_cluster" "existing" {
cluster_id = var.cluster_id
}
# Создание базы данных
resource "yandex_mdb_postgresql_database" "this" {
cluster_id = var.cluster_id
name = var.database_name
owner = yandex_mdb_postgresql_user.this.name
lc_collate = var.lc_collate
lc_type = var.lc_type
dynamic "extension" {
for_each = var.extensions
content {
name = extension.value
}
}
}
# Создание пользователя
resource "yandex_mdb_postgresql_user" "this" {
cluster_id = var.cluster_id
name = var.user_name
password = random_password.user_password.result
dynamic "permission" {
for_each = var.permissions
content {
database_name = permission.value.database_name
}
}
}

View File

@ -0,0 +1,35 @@
output "cluster_id" {
description = "ID кластера PostgreSQL"
value = var.cluster_id
}
output "cluster_fqdn" {
description = "FQDN кластера PostgreSQL"
value = data.yandex_mdb_postgresql_cluster.existing.host[0].fqdn
}
output "host" {
description = "Host адрес кластера"
value = data.yandex_mdb_postgresql_cluster.existing.host[0].fqdn
}
output "port" {
description = "Порт кластера"
value = 6432
}
output "database_name" {
description = "Имя базы данных"
value = yandex_mdb_postgresql_database.this.name
}
output "user_name" {
description = "Имя пользователя"
value = yandex_mdb_postgresql_user.this.name
}
output "password" {
description = "Пароль пользователя"
value = random_password.user_password.result
sensitive = true
}

View File

@ -0,0 +1,52 @@
variable "cluster_id" {
description = "ID существующего PostgreSQL кластера в Yandex Cloud"
type = string
}
variable "database_name" {
description = "Имя базы данных"
type = string
}
variable "user_name" {
description = "Имя пользователя"
type = string
}
variable "password_length" {
description = "Длина пароля"
type = number
default = 32
}
variable "password_special" {
description = "Использовать специальные символы в пароле"
type = bool
default = false
}
variable "lc_collate" {
description = "LC_COLLATE для базы данных"
type = string
default = "en_US.UTF-8"
}
variable "lc_type" {
description = "LC_TYPE для базы данных"
type = string
default = "en_US.UTF-8"
}
variable "extensions" {
description = "Список расширений PostgreSQL"
type = list(string)
default = []
}
variable "permissions" {
description = "Права доступа пользователя"
type = list(object({
database_name = string
}))
default = []
}

View File

@ -1,55 +0,0 @@
# Генерация пароля для PostgreSQL (32 символа без специальных)
resource "random_password" "pg_password" {
length = 32
special = false
upper = true
lower = true
numeric = true
}
# Создание кластера PostgreSQL
resource "yandex_mdb_postgresql_cluster" "pulse" {
name = var.cluster_name
description = "PostgreSQL cluster for pulse project"
environment = var.environment
network_id = var.network_id
folder_id = var.folder_id
config {
version = var.postgresql_version
resources {
resource_preset_id = var.resource_preset_id
disk_type_id = var.disk_type_id
disk_size = var.disk_size
}
postgresql_config = var.postgresql_config
}
database {
name = var.database_name
owner = var.database_user
}
user {
name = var.database_user
password = random_password.pg_password.result
permission {
database_name = var.database_name
}
}
host {
zone = var.zone
subnet_id = var.subnet_id
}
maintenance_window {
type = var.maintenance_window_type
day = var.maintenance_window_day
hour = var.maintenance_window_hour
}
deletion_protection = var.deletion_protection
}

View File

@ -1,35 +0,0 @@
output "cluster_id" {
description = "ID of the PostgreSQL cluster"
value = yandex_mdb_postgresql_cluster.pulse.id
}
output "cluster_fqdn" {
description = "FQDN of the PostgreSQL cluster"
value = yandex_mdb_postgresql_cluster.pulse.host[0].fqdn
}
output "host" {
description = "Host address of the PostgreSQL cluster"
value = yandex_mdb_postgresql_cluster.pulse.host[0].fqdn
}
output "database_name" {
description = "Name of the database"
value = var.database_name
}
output "database_user" {
description = "Name of the database user"
value = var.database_user
}
output "password" {
description = "Password for the database user"
value = random_password.pg_password.result
sensitive = true
}
output "port" {
description = "Port of the PostgreSQL cluster"
value = 6432
}

View File

@ -1,97 +0,0 @@
variable "cluster_name" {
description = "Name of the PostgreSQL cluster"
type = string
}
variable "folder_id" {
description = "Yandex Cloud folder ID"
type = string
}
variable "network_id" {
description = "Network ID for the PostgreSQL cluster"
type = string
}
variable "subnet_id" {
description = "Subnet ID for the PostgreSQL cluster"
type = string
}
variable "zone" {
description = "Availability zone"
type = string
default = "ru-central1-a"
}
variable "environment" {
description = "Environment (PRODUCTION, PRESTABLE)"
type = string
default = "PRODUCTION"
}
variable "postgresql_version" {
description = "PostgreSQL version"
type = string
default = "15"
}
variable "resource_preset_id" {
description = "Resource preset ID"
type = string
default = "s2.micro"
}
variable "disk_type_id" {
description = "Disk type ID"
type = string
default = "network-ssd"
}
variable "disk_size" {
description = "Disk size in GB"
type = number
default = 10
}
variable "database_name" {
description = "Name of the database"
type = string
default = "pulse_db"
}
variable "database_user" {
description = "Name of the database user"
type = string
default = "pulse"
}
variable "postgresql_config" {
description = "PostgreSQL configuration"
type = map(any)
default = {}
}
variable "maintenance_window_type" {
description = "Maintenance window type"
type = string
default = "WEEKLY"
}
variable "maintenance_window_day" {
description = "Maintenance window day"
type = string
default = "SAT"
}
variable "maintenance_window_hour" {
description = "Maintenance window hour"
type = number
default = 3
}
variable "deletion_protection" {
description = "Enable deletion protection"
type = bool
default = false
}

View File

@ -1,3 +1,6 @@
# Универсальный модуль для создания S3 бакета
# Не знает про конкретные сущности
# Создание сервисного аккаунта для S3 # Создание сервисного аккаунта для S3
resource "yandex_iam_service_account" "sa" { resource "yandex_iam_service_account" "sa" {
name = "${var.bucket_name}-sa" name = "${var.bucket_name}-sa"
@ -17,27 +20,31 @@ resource "yandex_iam_service_account_static_access_key" "sa_key" {
description = "Static access key for ${var.bucket_name} bucket" description = "Static access key for ${var.bucket_name} bucket"
} }
# Создание S3 бакета с публичным доступом # Создание S3 бакета
resource "yandex_storage_bucket" "pulse" { resource "yandex_storage_bucket" "this" {
bucket = var.bucket_name bucket = var.bucket_name
access_key = yandex_iam_service_account_static_access_key.sa_key.access_key access_key = yandex_iam_service_account_static_access_key.sa_key.access_key
secret_key = yandex_iam_service_account_static_access_key.sa_key.secret_key secret_key = yandex_iam_service_account_static_access_key.sa_key.secret_key
acl = var.acl
# Публичный доступ для чтения
acl = "public-read"
# Настройка CORS для внешнего доступа
cors_rule {
allowed_headers = ["*"]
allowed_methods = ["GET", "PUT", "POST", "DELETE", "HEAD"]
allowed_origins = ["*"]
expose_headers = ["ETag"]
max_age_seconds = 3600
}
# Версионирование объектов # Версионирование объектов
versioning { dynamic "versioning" {
enabled = var.versioning_enabled for_each = var.versioning_enabled ? [1] : []
content {
enabled = true
}
}
# Настройка CORS
dynamic "cors_rule" {
for_each = var.cors_enabled ? [1] : []
content {
allowed_headers = var.cors_config.allowed_headers
allowed_methods = var.cors_config.allowed_methods
allowed_origins = var.cors_config.allowed_origins
expose_headers = try(var.cors_config.expose_headers, [])
max_age_seconds = try(var.cors_config.max_age_seconds, 3600)
}
} }
depends_on = [yandex_resourcemanager_folder_iam_member.storage_editor] depends_on = [yandex_resourcemanager_folder_iam_member.storage_editor]

View File

@ -1,6 +1,6 @@
output "bucket_name" { output "bucket_name" {
description = "Name of the created bucket" description = "Name of the created bucket"
value = yandex_storage_bucket.pulse.bucket value = yandex_storage_bucket.this.bucket
} }
output "access_key" { output "access_key" {

View File

@ -8,8 +8,36 @@ variable "folder_id" {
type = string type = string
} }
variable "acl" {
description = "ACL для бакета"
type = string
default = "private"
}
variable "versioning_enabled" { variable "versioning_enabled" {
description = "Enable versioning for the bucket" description = "Enable versioning for the bucket"
type = bool type = bool
default = false default = false
} }
variable "cors_enabled" {
description = "Enable CORS for the bucket"
type = bool
default = false
}
variable "cors_config" {
description = "CORS configuration"
type = object({
allowed_headers = list(string)
allowed_methods = list(string)
allowed_origins = list(string)
expose_headers = optional(list(string))
max_age_seconds = optional(number)
})
default = {
allowed_headers = []
allowed_methods = []
allowed_origins = []
}
}