diff --git a/TOOLS/docs-generator/internal/writers/writers.go b/TOOLS/docs-generator/internal/writers/writers.go index 5dc21a6..2e7935d 100644 --- a/TOOLS/docs-generator/internal/writers/writers.go +++ b/TOOLS/docs-generator/internal/writers/writers.go @@ -476,18 +476,46 @@ func buildOutputsPage(spec types.ServiceSpec, nav string, version string) string b.WriteString("\n") } - if spec.ServiceID == 90 && containsString(snapshot.OutPaths, "internalConnect.master") && containsString(snapshot.VaultFields, "adminUser") && containsString(snapshot.VaultFields, "adminPass") { - b.WriteString("### Пример для связки с Lucee/NodeJS\n\n") - b.WriteString("```hcl\n") - b.WriteString("testds_connectionString = \"jdbc:postgresql://${nubes_postgres.db2.state_out_flat[\"internalConnect.master\"]}:5432/postgres?sslmode=require\"\n") - b.WriteString("testds_username = nubes_postgres.db2.vault_secrets[\"adminUser\"]\n") - b.WriteString("testds_password = nubes_postgres.db2.vault_secrets[\"adminPass\"]\n") - b.WriteString("```\n") + for _, ex := range serviceSpecificDocExamples { + if spec.Name == ex.serviceName && hasAllKeys(snapshot.OutPaths, ex.outKeys) && hasAllKeys(snapshot.VaultFields, ex.vaultKeys) { + b.WriteString("### Пример для связки с Lucee/NodeJS\n\n") + b.WriteString("```hcl\n") + b.WriteString("testds_connectionString = \"jdbc:postgresql://${nubes_postgres.db2.state_out_flat[\"internalConnect.master\"]}:5432/postgres?sslmode=require\"\n") + b.WriteString("testds_username = nubes_postgres.db2.vault_secrets[\"adminUser\"]\n") + b.WriteString("testds_password = nubes_postgres.db2.vault_secrets[\"adminPass\"]\n") + b.WriteString("```\n") + } } return b.String() } +// serviceSpecificDocExamples — реестр исключений документации (ДАННЫЕ, не логика). +// +// ЧТО: postgres → дополнительный HCL-пример связки с Lucee/NodeJS. +// ЧТО ДЕЛАЕТ: если сервис и требуемые ключи state_out_flat/vault_secrets совпали — рендерит пример. +// ПОЧЕМУ: специфичная интеграция postgres↔Lucee/NodeJS, универсальный рендер её не покрывает. +// ГДЕ: TOOLS/ARCHITECTURE.md, раздел «Реестр исключений». +// +// Добавлять только здесь. Grep-гейт TOOLS/scripts/check_hardcoded_service_ids.sh +// запрещает сравнения ServiceID == N вне этого файла. +var serviceSpecificDocExamples = []struct { + serviceName string + outKeys []string + vaultKeys []string +}{ + {serviceName: "postgres", outKeys: []string{"internalConnect.master"}, vaultKeys: []string{"adminUser", "adminPass"}}, +} + +func hasAllKeys(haystack []string, needles []string) bool { + for _, n := range needles { + if !containsString(haystack, n) { + return false + } + } + return true +} + func containsString(values []string, target string) bool { for _, value := range values { if value == target { diff --git a/TOOLS/scripts/check_hardcoded_service_ids.sh b/TOOLS/scripts/check_hardcoded_service_ids.sh new file mode 100755 index 0000000..12932c4 --- /dev/null +++ b/TOOLS/scripts/check_hardcoded_service_ids.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# check_hardcoded_service_ids.sh — запрет сервис-специфичных хардкодов по числовому ID. +# +# Ищет сравнения вида svc.ID == N / ServiceID == N / spec.ServiceID == N (N > 0) +# в Go-коде TOOLS/. Исключения должны жить ТОЛЬКО в именованных реестрах (данные): +# - TOOLS/yaml-generator/main.go (serviceSpecificModifiers) +# - TOOLS/docs-generator/internal/writers/writers.go (serviceSpecificDocExamples) +# +# Выход: 0 — хардкодов нет; 1 — найдены. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/../.." && pwd)}" + +matches="$(grep -rnE '\.(ServiceID|ID)[[:space:]]*==[[:space:]]*[1-9][0-9]*' "$ROOT_DIR/TOOLS" --include='*.go' || true)" + +if [[ -n "$matches" ]]; then + echo "HARDCODED SERVICE ID FOUND (service-specific logic must live in a registry):" >&2 + echo "$matches" >&2 + echo "" >&2 + echo "Вынеси исключение в один из реестров:" >&2 + echo " - TOOLS/yaml-generator/main.go (serviceSpecificModifiers)" >&2 + echo " - TOOLS/docs-generator/internal/writers/writers.go (serviceSpecificDocExamples)" >&2 + echo "См. TOOLS/ARCHITECTURE.md, раздел «Реестр исключений»." >&2 + exit 1 +fi + +echo "OK: no hardcoded service IDs in TOOLS/." diff --git a/TOOLS/yaml-generator/main.go b/TOOLS/yaml-generator/main.go index 7ba017e..2c83e05 100644 --- a/TOOLS/yaml-generator/main.go +++ b/TOOLS/yaml-generator/main.go @@ -19,6 +19,23 @@ import ( "yaml-generator/internal/types" ) +// serviceSpecificModifiers — реестр исключений (ДАННЫЕ, не логика). +// Ключ — стабильное имя сервиса (как в YAML), значение — имя modifier-ресурса, +// который генерируется для его операции modify (отложенный modify над инстансом). +// +// ЧТО: операция modify сервиса помечается kind=modifier + modifier=<имя>. +// ЧТО ДЕЛАЕТ: yaml-generator создаёт для неё отдельный ресурс nubes__. +// ПОЧЕМУ: эти modify — отдельный шаг цепочки (аллокация IP / SNAT), их нельзя +// выполнять в том же ресурсе инстанса (чередующаяся зависимость). +// ГДЕ: TOOLS/ARCHITECTURE.md, раздел «Реестр исключений». +// +// Добавлять только здесь. Grep-гейт TOOLS/scripts/check_hardcoded_service_ids.sh +// запрещает сравнения svc.ID == N вне этого файла. +var serviceSpecificModifiers = map[string]string{ + "vc_org": "ip_space", + "vc_nsxt": "network", +} + func main() { cfg, err := config.Load() if err != nil { @@ -72,13 +89,12 @@ func main() { panic(err) } for idx := range ops { - if strings.EqualFold(ops[idx].Action, "modify") && svc.ID == 19 { - ops[idx].Kind = "modifier" - ops[idx].Modifier = "ip_space" + if !strings.EqualFold(ops[idx].Action, "modify") { + continue } - if strings.EqualFold(ops[idx].Action, "modify") && svc.ID == 22 { + if modName, ok := serviceSpecificModifiers[name]; ok { ops[idx].Kind = "modifier" - ops[idx].Modifier = "network" + ops[idx].Modifier = modName } }