# Единственное место загрузки контракта сервисов (environments//*.yaml). # # Файл называется config.hcl, а НЕ terragrunt.hcl, намеренно: и # scripts/run_all_stacks.sh, и scripts/generate-pipeline.sh ищут юниты строго по # имени terragrunt.hcl, поэтому этот файл юнитом не считается и сам по себе не # прогоняется. # # Стеки подключают его через read_terragrunt_config() и берут local.<...>. # Именно read_terragrunt_config, а не dependency: dependency не виден из блока # locals, и попытка протащить компилятор через него уже роняла пайплайн. # # Окружение считается переехавшим на новый формат, только если у него есть # environments//environment.yaml. Для остальных контуров всё ниже # вырождается в пустые списки, и concat на стороне стеков становится # тождественной операцией. locals { repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../..") infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml")) env_override = trimspace(get_env("INFRA_ENV", "")) env_from_file = try(trimspace(local.infra_config.current_environment), "") env_name = local.env_override != "" ? local.env_override : ( local.env_from_file != "" ? local.env_from_file : try(sort(keys(try(local.infra_config.environments, {})))[0], "") ) env_dir = "${local.repo_root}/environments/${local.env_name}" # try(), а не тернарник по fileexists: в HCL обе ветки тернарника обязаны # иметь совместимые типы, а пустой объект против объекта с backends их не # имеет - "Inconsistent conditional result types". environment = try(yamldecode(file("${local.env_dir}/environment.yaml")), {}) namespaces = try(yamldecode(file("${local.env_dir}/namespaces.yaml")).namespaces, {}) service_files = try(fileset(local.env_dir, "services/*.yaml"), []) services = { for f in local.service_files : trimsuffix(basename(f), ".yaml") => yamldecode(file("${local.env_dir}/${f}")) } registry = try(local.environment.registry, null) credentials_ref = try(local.registry.credentials_ref, "") # ── вывод сущностей из имени сервиса ─────────────────────────────────────── # vhosts пуст -> []; первый vhost основной, его секрет зовётся просто # rabbitmq, остальные получают суффикс - хвост после "-". service_vhosts = { for name, svc in local.services : name => length(try(svc.needs.rabbitmq.vhosts, [])) > 0 ? svc.needs.rabbitmq.vhosts : [name] if try(svc.needs.rabbitmq, null) != null } # сервис размещён во всех namespace, где он перечислен в services[] service_namespaces = { for name, _ in local.services : name => sort([for ns_key, ns in local.namespaces : ns_key if contains(try(ns.services, []), name)]) } # ── inputs для modules/k8s-namespace ─────────────────────────────────────── namespaces_input = [ for ns_key, ns in local.namespaces : { name = ns_key labels = try(ns.labels, {}) annotations = try(ns.annotations, {}) manage = try(ns.manage, true) image_pull_secret = try(ns.image_pull_secret, true) } ] # ── inputs для modules/rabbitmq ──────────────────────────────────────────── rabbitmq_vhosts = [ for v in sort(distinct(flatten([for _, vhosts in local.service_vhosts : vhosts]))) : { name = v } ] rabbitmq_users = [ for name, _ in local.service_vhosts : { name = name, password_length = 32, password_special = false } ] # владелец получает полные права в каждом своём vhost rabbitmq_permissions = flatten([ for name, vhosts in local.service_vhosts : [ for v in vhosts : { user = name, vhost = v, configure = ".*", write = ".*", read = ".*" } ] ]) # ── секреты в форме v2-контракта ─────────────────────────────────────────── # Отдаём именно записи с targets, а не плоский список: live/secrets вливает их # ДО собственного разбора v2, поэтому вся его машинерия (валидация ключей, # вывод type из source.kind, разворачивание targets) работает без изменений. secret_prefix = { for name, _ in local.services : name => join("", [ for ns_key in local.service_namespaces[name] : try(local.namespaces[ns_key].secret_prefix, "") == "service" ? "${name}-" : "" ]) } rabbitmq_secrets = flatten([ for name, vhosts in local.service_vhosts : [ for ns_key in local.service_namespaces[name] : [ for idx, v in vhosts : { id = "${name}-rabbitmq-${ns_key}-${v}" schema = "rabbitmq.v1" namespace = ns_key ownership = "managed" source = { kind = "rabbitmq_user" ref = { vhost = v, user = name } } targets = [{ kind = "kubernetes" namespace = ns_key name = "${try(local.namespaces[ns_key].secret_prefix, "") == "service" ? "${name}-" : ""}rabbitmq${idx == 0 ? "" : "-${trimprefix(v, "${name}-")}"}" }] } ] ] ]) regcred_secrets = local.credentials_ref == "" ? [] : [ for ns_key, ns in local.namespaces : { id = "${ns_key}-regcred" schema = "dockerconfigjson.v1" namespace = ns_key ownership = "referenced" source = { kind = "secret_ref" ref = local.credentials_ref } targets = [{ kind = "kubernetes", namespace = ns_key, name = "regcred" }] } if try(ns.image_pull_secret, true) ] secrets_v2 = concat(local.regcred_secrets, local.rabbitmq_secrets) }