diff --git a/README.md b/README.md index 3a87a8a..32db5b1 100644 --- a/README.md +++ b/README.md @@ -2,129 +2,143 @@ Этот репозиторий содержит инфраструктуру как код (IaC) для проекта Pulse, развернутого в Yandex Cloud. +## Архитектура + +Проект использует **единый конфигурационный файл** `infrastructure.yaml` (аналог `values.yaml` в Helm), который описывает всю инфраструктуру декларативно. Модули Terraform универсальны и не знают про конкретные сущности - они используются для создания ресурсов, описанных в конфиге. + ## Структура репозитория ``` . -├── modules/ # Переиспользуемые Terraform модули -│ ├── yc-s3/ # Модуль для создания S3 бакета -│ ├── yc-postgresql/ # Модуль для создания PostgreSQL кластера -│ ├── k8s-namespace/ # Модуль для создания Kubernetes namespace -│ └── k8s-secrets/ # Модуль для создания Kubernetes секретов -├── live/ # Живая инфраструктура -│ ├── terragrunt.hcl # Глобальная конфигурация Terragrunt -│ ├── stage/ # Окружение stage -│ │ ├── env.hcl # Конфигурация окружения -│ │ ├── namespace/ # Namespace pulse -│ │ ├── s3/ # S3 бакет pulse -│ │ ├── postgresql/ # PostgreSQL база данных -│ │ └── secrets/ # Kubernetes секреты -│ ├── prod/ # Окружение production -│ └── preprod/ # Окружение preprod -├── scripts/ # Вспомогательные скрипты -│ └── generate-pipeline.sh # Генератор GitLab CI пайплайна -└── .gitlab-ci.yml # Основной GitLab CI конфигурация +├── infrastructure.yaml # Единый конфигурационный файл (аналог values.yaml) +├── modules/ # Универсальные Terraform модули +│ ├── yc-s3/ # Модуль для создания S3 бакета +│ ├── yc-database/ # Модуль для работы с существующим PostgreSQL +│ ├── k8s-namespace/ # Модуль для создания Kubernetes namespace +│ └── k8s-secret/ # Модуль для создания Kubernetes секретов +└── live/ # Живая инфраструктура + ├── terragrunt.hcl # Глобальная конфигурация Terragrunt + └── stage/ # Окружение stage + ├── env.hcl # Конфигурация окружения + ├── namespace/ # Namespace из конфига + ├── s3/ # S3 бакет из конфига + ├── database/ # База данных из конфига + └── secrets/ # Секреты из конфига ``` -## Требования +## Конфигурационный файл -- Terraform >= 1.0 -- Terragrunt >= 0.50.0 -- Доступ к Yandex Cloud -- Доступ к Kubernetes кластеру +Все ресурсы описываются в `infrastructure.yaml`: -## Переменные окружения +```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 -- `KUBECONFIG` - путь к конфигурации Kubernetes (по умолчанию `~/.kube/config`) -- `KUBE_CONTEXT` - контекст Kubernetes (опционально) +- **yc-s3** - создает S3 бакет с любыми параметрами из конфига +- **yc-database** - работает с существующим PostgreSQL кластером, создает БД и пользователя +- **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 cd live/stage/namespace ``` -2. Инициализируйте Terragrunt: +3. Инициализируйте Terragrunt: ```bash terragrunt init ``` -3. Просмотрите план изменений: +4. Просмотрите план: ```bash terragrunt plan ``` -4. Примените изменения: +5. Примените изменения: ```bash terragrunt apply ``` ### Развертывание всех компонентов -Для развертывания всех компонентов окружения используйте: - ```bash cd live/stage 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 пайплайнов через downstream pipelines. При каждом коммите: +Проект использует динамическую генерацию GitLab CI пайплайнов через downstream pipelines. Для каждого компонента создаются джобы: validate, plan, apply. -1. Запускается джоба `generate-pipeline`, которая сканирует структуру `live/` и создает джобы для каждого компонента -2. Запускается downstream пайплайн с сгенерированными джобами +## Принципы -Для каждого компонента создаются три джобы: -- `validate-{env}-{component}` - валидация конфигурации -- `plan-{env}-{component}` - планирование изменений -- `apply-{env}-{component}` - применение изменений (manual для prod) - -## Созданные ресурсы - -### 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 - -## Лицензия - -Внутренний проект компании. +1. **DRY** - вся конфигурация в одном файле `infrastructure.yaml` +2. **Универсальность** - модули не знают про конкретные сущности +3. **Декларативность** - описание желаемого состояния, а не шагов +4. **Идемпотентность** - повторный запуск безопасен +5. **Изоляция** - каждый компонент имеет свой state diff --git a/infrastructure.yaml b/infrastructure.yaml new file mode 100644 index 0000000..43a9118 --- /dev/null +++ b/infrastructure.yaml @@ -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 diff --git a/live/stage/database/terragrunt.hcl b/live/stage/database/terragrunt.hcl new file mode 100644 index 0000000..6b3e943 --- /dev/null +++ b/live/stage/database/terragrunt.hcl @@ -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 + } + ] +} diff --git a/live/stage/namespace/terragrunt.hcl b/live/stage/namespace/terragrunt.hcl index 1b3cc1f..d5909ff 100644 --- a/live/stage/namespace/terragrunt.hcl +++ b/live/stage/namespace/terragrunt.hcl @@ -10,19 +10,21 @@ include "env" { 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 { source = "${get_parent_terragrunt_dir()}/../../modules//k8s-namespace" } -# Входные переменные +# Входные переменные из конфига inputs = { - namespace_name = "pulse" - labels = { - environment = local.environment - project = "pulse" - } - annotations = { - "managed-by" = "terraform" - } + namespace_name = local.namespace.name + labels = local.namespace.labels + annotations = local.namespace.annotations } diff --git a/live/stage/postgresql/terragrunt.hcl b/live/stage/postgresql/terragrunt.hcl deleted file mode 100644 index 77641ed..0000000 --- a/live/stage/postgresql/terragrunt.hcl +++ /dev/null @@ -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 -} diff --git a/live/stage/s3/terragrunt.hcl b/live/stage/s3/terragrunt.hcl index 6c272de..c83fcc7 100644 --- a/live/stage/s3/terragrunt.hcl +++ b/live/stage/s3/terragrunt.hcl @@ -10,14 +10,23 @@ include "env" { 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 { source = "${get_parent_terragrunt_dir()}/../../modules//yc-s3" } -# Входные переменные +# Входные переменные из конфига inputs = { - bucket_name = "pulse" + bucket_name = local.bucket.name 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, {}) } diff --git a/live/stage/secrets/terragrunt.hcl b/live/stage/secrets/terragrunt.hcl index 4897c6c..9dac919 100644 --- a/live/stage/secrets/terragrunt.hcl +++ b/live/stage/secrets/terragrunt.hcl @@ -13,7 +13,6 @@ include "env" { # Зависимости от других компонентов dependency "namespace" { config_path = "../namespace" - mock_outputs = { name = "pulse" } @@ -22,7 +21,6 @@ dependency "namespace" { dependency "s3" { config_path = "../s3" - mock_outputs = { bucket_name = "pulse" access_key = "mock-access-key" @@ -31,42 +29,76 @@ dependency "s3" { mock_outputs_allowed_terraform_commands = ["validate", "plan"] } -dependency "postgresql" { - config_path = "../postgresql" - +dependency "database" { + config_path = "../database" mock_outputs = { host = "mock-host.example.com" database_name = "pulse_db" - database_user = "pulse" + user_name = "pulse" password = "mock-password" } 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 { - source = "${get_parent_terragrunt_dir()}/../../modules//k8s-secrets" + source = "${get_parent_terragrunt_dir()}/../../modules//k8s-secret" } -# Входные переменные +# Входные переменные из конфига с подстановкой значений из dependencies inputs = { - namespace = dependency.namespace.outputs.name - - # Docker Hub (Yandex Registry) credentials - docker_registry_url = get_env("DOCKER_REGISTRY_URL", "cr.yandex") - docker_registry_username = get_env("DOCKER_REGISTRY_USERNAME", "") - docker_registry_password = get_env("DOCKER_REGISTRY_PASSWORD", "") - - # S3 credentials from dependency - s3_access_key = dependency.s3.outputs.access_key - s3_secret_key = dependency.s3.outputs.secret_key - s3_bucket_name = dependency.s3.outputs.bucket_name - s3_endpoint = "https://storage.yandexcloud.net" - - # 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 - db_password = dependency.postgresql.outputs.password + secrets = [ + # dockerhub секрет + { + name = local.secrets[0].name + namespace = dependency.namespace.outputs.name + secret_type = local.secrets[0].type + data = { + ".dockerconfigjson" = jsonencode({ + auths = { + "${local.secrets[0].registry_url}" = { + username = get_env("DOCKER_REGISTRY_USERNAME", "") + password = get_env("DOCKER_REGISTRY_PASSWORD", "") + auth = base64encode("${get_env("DOCKER_REGISTRY_USERNAME", "")}:${get_env("DOCKER_REGISTRY_PASSWORD", "")}") + } + } + }) + } + ignore_changes = false + }, + # 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) + } + ] } diff --git a/modules/k8s-secret/main.tf b/modules/k8s-secret/main.tf new file mode 100644 index 0000000..4eee411 --- /dev/null +++ b/modules/k8s-secret/main.tf @@ -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] : [] + } +} diff --git a/modules/k8s-secret/outputs.tf b/modules/k8s-secret/outputs.tf new file mode 100644 index 0000000..6649706 --- /dev/null +++ b/modules/k8s-secret/outputs.tf @@ -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 +} diff --git a/modules/k8s-secret/variables.tf b/modules/k8s-secret/variables.tf new file mode 100644 index 0000000..4df2ac6 --- /dev/null +++ b/modules/k8s-secret/variables.tf @@ -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 +} diff --git a/modules/k8s-secrets/versions.tf b/modules/k8s-secret/versions.tf similarity index 100% rename from modules/k8s-secrets/versions.tf rename to modules/k8s-secret/versions.tf diff --git a/modules/k8s-secrets/main.tf b/modules/k8s-secrets/main.tf deleted file mode 100644 index 921538c..0000000 --- a/modules/k8s-secrets/main.tf +++ /dev/null @@ -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) - } -} diff --git a/modules/k8s-secrets/outputs.tf b/modules/k8s-secrets/outputs.tf deleted file mode 100644 index 8234fd3..0000000 --- a/modules/k8s-secrets/outputs.tf +++ /dev/null @@ -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 -} diff --git a/modules/k8s-secrets/variables.tf b/modules/k8s-secrets/variables.tf deleted file mode 100644 index 78df7b2..0000000 --- a/modules/k8s-secrets/variables.tf +++ /dev/null @@ -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 -} diff --git a/modules/yc-database/main.tf b/modules/yc-database/main.tf new file mode 100644 index 0000000..822b2fe --- /dev/null +++ b/modules/yc-database/main.tf @@ -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 + } + } +} diff --git a/modules/yc-database/outputs.tf b/modules/yc-database/outputs.tf new file mode 100644 index 0000000..799a2ad --- /dev/null +++ b/modules/yc-database/outputs.tf @@ -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 +} diff --git a/modules/yc-database/variables.tf b/modules/yc-database/variables.tf new file mode 100644 index 0000000..b006a8f --- /dev/null +++ b/modules/yc-database/variables.tf @@ -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 = [] +} diff --git a/modules/yc-postgresql/versions.tf b/modules/yc-database/versions.tf similarity index 100% rename from modules/yc-postgresql/versions.tf rename to modules/yc-database/versions.tf diff --git a/modules/yc-postgresql/main.tf b/modules/yc-postgresql/main.tf deleted file mode 100644 index d6c97de..0000000 --- a/modules/yc-postgresql/main.tf +++ /dev/null @@ -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 -} diff --git a/modules/yc-postgresql/outputs.tf b/modules/yc-postgresql/outputs.tf deleted file mode 100644 index a6502b4..0000000 --- a/modules/yc-postgresql/outputs.tf +++ /dev/null @@ -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 -} diff --git a/modules/yc-postgresql/variables.tf b/modules/yc-postgresql/variables.tf deleted file mode 100644 index 49a6fa1..0000000 --- a/modules/yc-postgresql/variables.tf +++ /dev/null @@ -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 -} diff --git a/modules/yc-s3/main.tf b/modules/yc-s3/main.tf index 6ecc921..057618c 100644 --- a/modules/yc-s3/main.tf +++ b/modules/yc-s3/main.tf @@ -1,3 +1,6 @@ +# Универсальный модуль для создания S3 бакета +# Не знает про конкретные сущности + # Создание сервисного аккаунта для S3 resource "yandex_iam_service_account" "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" } -# Создание S3 бакета с публичным доступом -resource "yandex_storage_bucket" "pulse" { +# Создание S3 бакета +resource "yandex_storage_bucket" "this" { bucket = var.bucket_name 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 - - # Публичный доступ для чтения - acl = "public-read" - - # Настройка CORS для внешнего доступа - cors_rule { - allowed_headers = ["*"] - allowed_methods = ["GET", "PUT", "POST", "DELETE", "HEAD"] - allowed_origins = ["*"] - expose_headers = ["ETag"] - max_age_seconds = 3600 - } + acl = var.acl # Версионирование объектов - versioning { - enabled = var.versioning_enabled + dynamic "versioning" { + 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] diff --git a/modules/yc-s3/outputs.tf b/modules/yc-s3/outputs.tf index f882daa..2e4dd06 100644 --- a/modules/yc-s3/outputs.tf +++ b/modules/yc-s3/outputs.tf @@ -1,6 +1,6 @@ output "bucket_name" { description = "Name of the created bucket" - value = yandex_storage_bucket.pulse.bucket + value = yandex_storage_bucket.this.bucket } output "access_key" { diff --git a/modules/yc-s3/variables.tf b/modules/yc-s3/variables.tf index 03226d3..bdb2595 100644 --- a/modules/yc-s3/variables.tf +++ b/modules/yc-s3/variables.tf @@ -8,8 +8,36 @@ variable "folder_id" { type = string } +variable "acl" { + description = "ACL для бакета" + type = string + default = "private" +} + variable "versioning_enabled" { description = "Enable versioning for the bucket" type = bool 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 = [] + } +}