test terragrunt ci

This commit is contained in:
Work 2026-01-28 15:05:41 +03:00
parent 51dce6b708
commit d6300bfa80
6 changed files with 89 additions and 83 deletions

View File

@ -63,6 +63,22 @@ environments:
lifecycle: lifecycle:
ignore_changes: true ignore_changes: true
- name: testci-postgresql-secret
namespace: testci
type: database
dependencies:
cluster: "c9qa2coo5ukgcg93fldm"
db: testci_db
user: testci
lifecycle:
ignore_changes: true
custom_keys:
sslmode: "verify-full"
random_keys:
full-host:
length: 32
special: false
- name: session-secret - name: session-secret
namespace: pulse namespace: pulse

View File

@ -1,4 +1,3 @@
include "root" { include "root" {
path = find_in_parent_folders() path = find_in_parent_folders()
} }
@ -10,26 +9,20 @@ include "env" {
} }
locals { locals {
repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../../..") repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../../..")
infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml")) infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml"))
env_name = basename(dirname(get_terragrunt_dir())) env_name = basename(dirname(get_terragrunt_dir()))
env_config = local.infra_config.environments[local.env_name] env_config = local.infra_config.environments[local.env_name]
bucket = local.env_config.buckets[0] # Первый bucket из списка
} }
terraform { terraform {
source = "${get_terragrunt_dir()}/../../../modules//yc-s3" source = "${get_terragrunt_dir()}/../../../modules//yc-s3"
} }
inputs = { inputs = {
bucket_name = local.bucket.name buckets = local.env_config.buckets
folder_id = get_env("YC_STAGE_FOLDER_ID", get_env("YC_FOLDER_ID", "")) folder_id = get_env("YC_STAGE_FOLDER_ID", get_env("YC_FOLDER_ID", ""))
acl = try(local.bucket.acl, "private")
versioning_enabled = try(local.bucket.versioning.enabled, false)
cors_enabled = try(local.bucket.cors.enabled, false)
cors_config = try(local.bucket.cors, {})
yc_token = get_env("YC_TOKEN", "") yc_token = get_env("YC_TOKEN", "")
yc_service_account_key_file = get_env("YC_SERVICE_ACCOUNT_KEY_FILE", "") yc_service_account_key_file = get_env("YC_SERVICE_ACCOUNT_KEY_FILE", "")
yc_cloud_id = get_env("YC_CLOUD_ID", "") yc_cloud_id = get_env("YC_CLOUD_ID", "")

View File

@ -29,13 +29,11 @@ resource "yandex_mdb_postgresql_user" "this" {
password = random_password.user_password[each.key].result password = random_password.user_password[each.key].result
conn_limit = try(each.value.user.conn_limit, 10) conn_limit = try(each.value.user.conn_limit, 10)
# Permission на свою БД + дополнительные permissions # Permissions только на ДРУГИЕ существующие БД
# Yandex API требует явный permission даже для владельца # Permission на свою БД НЕ добавляем - owner получает доступ автоматически
# и БД ещё не существует на момент создания user
dynamic "permission" { dynamic "permission" {
for_each = distinct(concat( for_each = distinct(compact(try(each.value.user.permissions, [])))
[each.value.database.name],
try(each.value.user.permissions, [])
))
content { content {
database_name = permission.value database_name = permission.value
} }

View File

@ -1,35 +1,44 @@
# Универсальный модуль для создания S3 бакета # Универсальный модуль для создания S3 бакетов
# Не знает про конкретные сущности
# Создание сервисного аккаунта для S3 locals {
buckets_map = { for bucket in var.buckets : bucket.name => bucket }
}
# Создание сервисного аккаунта для каждого бакета
resource "yandex_iam_service_account" "sa" { resource "yandex_iam_service_account" "sa" {
name = "${var.bucket_name}-sa" for_each = local.buckets_map
description = "Service account for ${var.bucket_name} bucket"
name = "${each.key}-sa"
description = "Service account for ${each.key} bucket"
} }
# Назначение роли storage.editor сервисному аккаунту # Назначение роли storage.editor сервисному аккаунту
resource "yandex_resourcemanager_folder_iam_member" "storage_editor" { resource "yandex_resourcemanager_folder_iam_member" "storage_editor" {
for_each = local.buckets_map
folder_id = var.folder_id folder_id = var.folder_id
role = "storage.editor" role = "storage.editor"
member = "serviceAccount:${yandex_iam_service_account.sa.id}" member = "serviceAccount:${yandex_iam_service_account.sa[each.key].id}"
} }
# Создание статического ключа доступа # Создание статического ключа доступа
resource "yandex_iam_service_account_static_access_key" "sa_key" { resource "yandex_iam_service_account_static_access_key" "sa_key" {
service_account_id = yandex_iam_service_account.sa.id for_each = local.buckets_map
description = "Static access key for ${var.bucket_name} bucket"
service_account_id = yandex_iam_service_account.sa[each.key].id
description = "Static access key for ${each.key} bucket"
} }
# Создание S3 бакета # Создание S3 бакета
# Bucket создается от имени текущего пользователя (YC_TOKEN), не сервисного аккаунта
# Ключи сервисного аккаунта используются только для доступа к bucket после создания
resource "yandex_storage_bucket" "this" { resource "yandex_storage_bucket" "this" {
bucket = var.bucket_name for_each = local.buckets_map
acl = var.acl
bucket = each.key
acl = each.value.acl
# Версионирование объектов # Версионирование объектов
dynamic "versioning" { dynamic "versioning" {
for_each = var.versioning_enabled ? [1] : [] for_each = try(each.value.versioning.enabled, false) ? [1] : []
content { content {
enabled = true enabled = true
} }
@ -37,13 +46,13 @@ resource "yandex_storage_bucket" "this" {
# Настройка CORS # Настройка CORS
dynamic "cors_rule" { dynamic "cors_rule" {
for_each = var.cors_enabled ? [1] : [] for_each = try(each.value.cors.enabled, false) ? [1] : []
content { content {
allowed_headers = var.cors_config.allowed_headers allowed_headers = try(each.value.cors.allowed_headers, [])
allowed_methods = var.cors_config.allowed_methods allowed_methods = try(each.value.cors.allowed_methods, [])
allowed_origins = var.cors_config.allowed_origins allowed_origins = try(each.value.cors.allowed_origins, [])
expose_headers = try(var.cors_config.expose_headers, []) expose_headers = try(each.value.cors.expose_headers, [])
max_age_seconds = try(var.cors_config.max_age_seconds, 3600) max_age_seconds = try(each.value.cors.max_age_seconds, 3600)
} }
} }
} }

View File

@ -1,21 +1,31 @@
# Для обратной совместимости - первый бакет
output "bucket_name" { output "bucket_name" {
description = "Name of the created bucket" description = "Name of the first bucket (for backward compatibility)"
value = yandex_storage_bucket.this.bucket value = length(var.buckets) > 0 ? yandex_storage_bucket.this[var.buckets[0].name].bucket : ""
} }
output "access_key" { output "access_key" {
description = "Access key for S3 bucket" description = "Access key for the first bucket (for backward compatibility)"
value = yandex_iam_service_account_static_access_key.sa_key.access_key value = length(var.buckets) > 0 ? yandex_iam_service_account_static_access_key.sa_key[var.buckets[0].name].access_key : ""
sensitive = true sensitive = true
} }
output "secret_key" { output "secret_key" {
description = "Secret key for S3 bucket" description = "Secret key for the first bucket (for backward compatibility)"
value = yandex_iam_service_account_static_access_key.sa_key.secret_key value = length(var.buckets) > 0 ? yandex_iam_service_account_static_access_key.sa_key[var.buckets[0].name].secret_key : ""
sensitive = true sensitive = true
} }
output "service_account_id" { # Новые outputs - map всех бакетов
description = "ID of the service account" output "buckets" {
value = yandex_iam_service_account.sa.id description = "Map of all buckets (name => data)"
value = {
for name, bucket in yandex_storage_bucket.this : name => {
bucket_name = bucket.bucket
access_key = yandex_iam_service_account_static_access_key.sa_key[name].access_key
secret_key = yandex_iam_service_account_static_access_key.sa_key[name].secret_key
service_account_id = yandex_iam_service_account.sa[name].id
}
}
sensitive = true
} }

View File

@ -1,43 +1,23 @@
variable "bucket_name" { variable "buckets" {
description = "Name of the S3 bucket" description = "List of S3 buckets to create"
type = string type = list(object({
name = string
acl = optional(string, "private")
versioning = optional(object({
enabled = bool
}), { enabled = false })
cors = optional(object({
enabled = optional(bool, false)
allowed_headers = optional(list(string), [])
allowed_methods = optional(list(string), [])
allowed_origins = optional(list(string), [])
expose_headers = optional(list(string), [])
max_age_seconds = optional(number, 3600)
}), { enabled = false })
}))
} }
variable "folder_id" { variable "folder_id" {
description = "Yandex Cloud folder ID" description = "Yandex Cloud folder ID"
type = string 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 = []
}
}