locals { registry = try(var.environment.registry, null) credentials_ref = try(local.registry.credentials_ref, "") service_namespaces = { for name, _ in var.services : name => [for ns_key, ns in var.namespaces : ns_key if contains(try(ns.services, []), name)] } # 1. сервис перечислен в каталоге, но файла services/.yaml нет catalog_without_file = flatten([ for ns_key, ns in var.namespaces : [ for name in try(ns.services, []) : "namespace '${ns_key}': сервис '${name}' указан в services[], но файла services/${name}.yaml нет" if !contains(keys(var.services), name) ] ]) # 2. файл есть, но сервис не размещён ни в одном namespace file_without_placement = [ for name, placements in local.service_namespaces : "services/${name}.yaml: сервис не размещён ни в одном namespace" if length(placements) == 0 ] # 3. поле service: не совпадает с именем файла name_mismatch = [ for name, svc in var.services : "services/${name}.yaml: поле 'service: ${try(svc.service, "")}' не совпадает с именем файла '${name}'" if try(svc.service, "") != name ] # 4. два и более сервиса в одном namespace без secret_prefix - иначе секреты # разных сервисов молча перетрут друг друга по одинаковым именам missing_secret_prefix = [ for ns_key, ns in var.namespaces : "namespace '${ns_key}': сервисов ${length(try(ns.services, []))} (${join(", ", try(ns.services, []))}), но secret_prefix не задан" if length(try(ns.services, [])) > 1 && trimspace(try(ns.secret_prefix, "")) == "" ] # 5. image_pull_secret: true, а registry в окружении не объявлен pull_secret_without_registry = [ for ns_key, ns in var.namespaces : "namespace '${ns_key}': image_pull_secret true, но environment.registry не задан" if try(ns.image_pull_secret, true) && local.registry == null ] # 6. credentials_ref не соответствует формату vault://# bad_credentials_ref = ( local.registry != null && !can(regex("^vault://[^#]+#.+$", local.credentials_ref)) ) ? ["environment.registry.credentials_ref '${local.credentials_ref}' не соответствует формату vault://#"] : [] validation_errors = concat( local.catalog_without_file, local.file_without_placement, local.name_mismatch, local.missing_secret_prefix, local.pull_secret_without_registry, local.bad_credentials_ref, ) # file() на несуществующем пути - единственный способ уронить plan с текстом # ошибки в терраформе без ресурсов и без внешних зависимостей _fail = length(local.validation_errors) > 0 ? file("НАРУШЕН КОНТРАКТ СЕРВИСОВ: ${join(" | ", local.validation_errors)}") : "" }