diff --git a/TOOLS/docs-generator/internal/types/types.go b/TOOLS/docs-generator/internal/types/types.go
new file mode 100644
index 0000000..447119c
--- /dev/null
+++ b/TOOLS/docs-generator/internal/types/types.go
@@ -0,0 +1,71 @@
+// Package types — структуры данных для генератора документации.
+package types
+
+// ParamSpec — параметр операции.
+type ParamSpec struct {
+ ID int `yaml:"id"`
+ Code string `yaml:"code"`
+ DataType string `yaml:"data_type"`
+ Type string `yaml:"type"`
+ Required bool `yaml:"required"`
+ Default interface{} `yaml:"default"`
+ Descr string `yaml:"descr"`
+ Man string `yaml:"man"`
+ RefSvcID *int `yaml:"ref_svc_id"`
+ Func string `yaml:"func"`
+ MinValue *int `yaml:"minvalue"`
+ MaxValue *int `yaml:"maxvalue"`
+ Regex string `yaml:"regex"`
+ ValueList []string `yaml:"value_list"`
+ Unique string `yaml:"unique_scope"`
+ MaxLength *int `yaml:"maxlength"`
+ MinLength *int `yaml:"minlength"`
+}
+
+// OperationSpec — операция сервиса.
+type OperationSpec struct {
+ Name string `yaml:"name"`
+ ID int `yaml:"id"`
+ Kind string `yaml:"kind"`
+ Action string `yaml:"action"`
+ Subresource string `yaml:"subresource"`
+ Man string `yaml:"man"`
+ Params []ParamSpec `yaml:"params"`
+}
+
+// OutputParam — выходной параметр.
+type OutputParam struct {
+ Code string `yaml:"code"`
+ Type string `yaml:"type"`
+ Sensitive bool `yaml:"sensitive"`
+}
+
+// ServiceSpec — YAML-спек сервиса.
+type ServiceSpec struct {
+ Name string `yaml:"name"`
+ ServiceID int `yaml:"service_id"`
+ ServiceDisplayName string `yaml:"service_display_name"`
+ ServiceMan string `yaml:"service_man"`
+ Lifecycle struct {
+ SuspendOnDestroyDefault bool `yaml:"suspend_on_destroy_default"`
+ AdoptExistingOnCreateDefault bool `yaml:"adopt_existing_on_create_default"`
+ } `yaml:"lifecycle"`
+ Outputs struct {
+ Params []OutputParam `yaml:"params"`
+ } `yaml:"outputs"`
+ Operations []OperationSpec `yaml:"operations"`
+}
+
+// ServiceMeta — запись из services_list.txt.
+type ServiceMeta struct {
+ ID int
+ Name string
+}
+
+// CloudOutputSnapshot — снимок выходных полей из облака.
+type CloudOutputSnapshot struct {
+ ServiceID int `json:"serviceId"`
+ ServiceName string `json:"serviceName"`
+ OutPaths []string `json:"out_paths"`
+ VaultFields []string `json:"vault_fields"`
+}
diff --git a/TOOLS/docs-generator/internal/writers/writers.go b/TOOLS/docs-generator/internal/writers/writers.go
new file mode 100644
index 0000000..feaba90
--- /dev/null
+++ b/TOOLS/docs-generator/internal/writers/writers.go
@@ -0,0 +1,859 @@
+// Package writers — генерация Markdown-документации для ресурсов.
+package writers
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "html"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "docs-generator/internal/types"
+)
+
+// CloudOutputByServiceID глобальный кэш снепшотов облачных выходов.
+var CloudOutputByServiceID = map[int]types.CloudOutputSnapshot{}
+
+// ResourceDocs генерирует полный набор .md файлов для одного сервиса.
+func ResourceDocs(docsDir string, spec types.ServiceSpec, version string, apiEndpoint string) {
+ base := spec.Name
+ nav := fmt.Sprintf("[Manual](%s.md) | [Create params](%s_params_create.md) | [Modify params](%s_params_modify.md) | [Output params](%s_outputs.md) | [Operations](%s_ops.md) | [Example](%s_example.md)", base, base, base, base, base, base)
+
+ WriteFile(filepath.Join(docsDir, base+".md"), buildManualPage(spec, nav))
+ WriteFile(filepath.Join(docsDir, base+"_example.md"), buildExamplePage(spec, nav, version, apiEndpoint))
+ WriteFile(filepath.Join(docsDir, base+"_params_create.md"), buildCreateParamsPage(spec, nav))
+ WriteFile(filepath.Join(docsDir, base+"_params_modify.md"), buildModifyParamsPage(spec, nav))
+ WriteFile(filepath.Join(docsDir, base+"_outputs.md"), buildOutputsPage(spec, nav))
+ WriteFile(filepath.Join(docsDir, base+"_ops.md"), buildOpsPage(spec, nav, docsDir))
+ WriteFile(filepath.Join(docsDir, base+"_params.md"), buildParamsLandingPage(spec, nav))
+
+ for _, srName := range CollectSubresources(spec) {
+ srBase := fmt.Sprintf("%s_%s", base, Slug(srName))
+ srNav := fmt.Sprintf("[%s (основной)](%s.md) | [Operations](%s_ops.md) | [%s](%s.md) | [Example](%s_example.md)",
+ spec.ServiceDisplayName, base, base, Capitalize(srName), srBase, srBase)
+ WriteFile(filepath.Join(docsDir, srBase+".md"), buildSubresourcePage(spec, srName, srNav))
+ WriteFile(filepath.Join(docsDir, srBase+"_example.md"), buildSubresourceExamplePage(spec, srName, srNav, version, apiEndpoint))
+ }
+}
+
+// WriteFile пишет контент в файл.
+func WriteFile(path, content string) {
+ if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
+ fmt.Fprintf(os.Stderr, "ERROR: cannot write %s: %v\n", path, err)
+ os.Exit(1)
+ }
+}
+
+// IndexMD генерирует index.md.
+func IndexMD(docsDir string, specs []types.ServiceSpec) {
+ var b bytes.Buffer
+ b.WriteString("# Ресурсы провайдера\n\n")
+ b.WriteString("| ID | Ресурс | Описание |\n")
+ b.WriteString("|-----|--------|----------|\n")
+ for _, spec := range specs {
+ resName := "nubes_" + spec.Name
+ link := fmt.Sprintf("[%s](%s.md)", resName, spec.Name)
+ descr := strings.ReplaceAll(spec.ServiceDisplayName, "|", "\\|")
+ b.WriteString(fmt.Sprintf("| %d | %s | %s |\n", spec.ServiceID, link, descr))
+ }
+ outPath := filepath.Join(docsDir, "index.md")
+ if err := os.WriteFile(outPath, b.Bytes(), 0o644); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: failed to write index.md: %v\n", err)
+ }
+}
+
+// CollectSubresources возвращает уникальные subresource-имена.
+func CollectSubresources(spec types.ServiceSpec) []string {
+ seen := map[string]bool{}
+ var out []string
+ for _, op := range spec.Operations {
+ if op.Kind == "subresource" && op.Subresource != "" {
+ if !seen[op.Subresource] {
+ seen[op.Subresource] = true
+ out = append(out, op.Subresource)
+ }
+ }
+ }
+ return out
+}
+
+// FindParams находит параметры операции по action.
+func FindParams(ops []types.OperationSpec, action string) []types.ParamSpec {
+ for _, op := range ops {
+ if op.Kind == "instance" && op.Action == action {
+ return op.Params
+ }
+ }
+ return nil
+}
+
+// SplitParams делит параметры на обязательные и со значением по умолчанию.
+func SplitParams(params []types.ParamSpec) ([]types.ParamSpec, []types.ParamSpec) {
+ required := []types.ParamSpec{}
+ defaults := []types.ParamSpec{}
+ for _, p := range params {
+ if hasDefault(p.Default) {
+ defaults = append(defaults, p)
+ continue
+ }
+ required = append(required, p)
+ }
+ return required, defaults
+}
+
+// FindSubresourceParams находит параметры subresource-операции.
+func FindSubresourceParams(ops []types.OperationSpec, srName, action string) []types.ParamSpec {
+ for _, op := range ops {
+ if op.Kind == "subresource" && op.Subresource == srName && op.Action == action {
+ return op.Params
+ }
+ }
+ return nil
+}
+
+// ===== internal helpers =====
+
+func buildHeader(spec types.ServiceSpec, nav string) string {
+ name := spec.ServiceDisplayName
+ if name == "" {
+ name = spec.Name
+ }
+ return fmt.Sprintf("# Resource nubes_%s\n\nService ID: `%d`\n\nService Name: %s\n\n%s\n\n", spec.Name, spec.ServiceID, name, nav)
+}
+
+func buildManualPage(spec types.ServiceSpec, nav string) string {
+ var b strings.Builder
+ b.WriteString(buildHeader(spec, nav))
+ b.WriteString("## MAN\n\n")
+ man := strings.TrimSpace(spec.ServiceMan)
+ if man == "" {
+ man = "Manual not available."
+ }
+ b.WriteString("
\n")
+ b.WriteString(man)
+ b.WriteString("\n
\n")
+ return b.String()
+}
+
+func buildExamplePage(spec types.ServiceSpec, nav, version string, apiEndpoint string) string {
+ var b strings.Builder
+ b.WriteString(buildHeader(spec, nav))
+ b.WriteString(fmt.Sprintf("## Copy-ready manifest (`%s_main.tf`)\n\n", spec.Name))
+ b.WriteString("```hcl\n")
+ b.WriteString(exampleBlock(spec, version, apiEndpoint))
+ b.WriteString("```\n\n")
+ b.WriteString("## Outputs usage\n\n")
+ b.WriteString("```hcl\n")
+ b.WriteString("# Выходные параметры можно использовать как\n")
+ b.WriteString(fmt.Sprintf("nubes_%s.baza.state_params[\"имя_ключа\"]\n", spec.Name))
+ b.WriteString(fmt.Sprintf("nubes_%s.baza.state_out[\"имя_ключа\"]\n", spec.Name))
+ b.WriteString(fmt.Sprintf("nubes_%s.baza.state_params_flat[\"имя_ключа\"]\n", spec.Name))
+ b.WriteString(fmt.Sprintf("nubes_%s.baza.state_out_flat[\"имя_ключа\"]\n", spec.Name))
+ b.WriteString(fmt.Sprintf("nubes_%s.baza.vault_secrets[\"имя_ключа\"]\n", spec.Name))
+ b.WriteString(fmt.Sprintf("nubes_%s.baza.vault_url\n", spec.Name))
+ b.WriteString(fmt.Sprintf("nubes_%s.baza.vault_user_path\n", spec.Name))
+ b.WriteString(fmt.Sprintf("nubes_%s.baza.vault_fields\n", spec.Name))
+ b.WriteString("```\n")
+ return b.String()
+}
+
+func exampleBlock(spec types.ServiceSpec, version string, apiEndpoint string) string {
+ createParams := FindParams(spec.Operations, "create")
+ requiredParams, defaultParams := SplitParams(createParams)
+
+ var b strings.Builder
+ b.WriteString("terraform {\n")
+ b.WriteString(" required_providers {\n")
+ b.WriteString(" nubes = {\n")
+ b.WriteString(" source = \"terra.k8c.ru/nubes/nubes\"\n")
+ b.WriteString(fmt.Sprintf(" version = \"%s\"\n", version))
+ b.WriteString(" }\n")
+ b.WriteString(" }\n")
+ b.WriteString("}\n\n")
+ b.WriteString("# Токен доступа api_token к Nubes API — замените на реальный.\n")
+ b.WriteString("provider \"nubes\" {\n")
+ b.WriteString(" api_token = \"token_***\"\n")
+ b.WriteString(fmt.Sprintf(" api_endpoint = \"%s\"\n", apiEndpoint))
+ b.WriteString("}\n")
+ b.WriteString(fmt.Sprintf("resource \"nubes_%s\" \"baza\" {\n", spec.Name))
+
+ for _, p := range requiredParams {
+ b.WriteString(formatParamLine(p, " ", true))
+ }
+
+ if len(defaultParams) > 0 {
+ b.WriteString("\n # Параметры, имеющие значение по умолчанию, если не меняете - эти параметры не обязательно прописывать в манифесте\n")
+ for _, p := range defaultParams {
+ b.WriteString(formatParamLine(p, " ", false))
+ }
+ }
+
+ b.WriteString("}\n")
+ return b.String()
+}
+
+func buildCreateParamsPage(spec types.ServiceSpec, nav string) string {
+ var b strings.Builder
+ b.WriteString(buildHeader(spec, nav))
+ b.WriteString("## Create params\n\n")
+ createParams := FindParams(spec.Operations, "create")
+ requiredParams, defaultParams := SplitParams(createParams)
+
+ b.WriteString("**Обязательные параметры (вводимые пользователем)**\n\n")
+ b.WriteString(renderParamTable(requiredParams, true, true))
+
+ b.WriteString("\n**Параметры, имеющие значение по умолчанию, если не меняете - эти параметры не обязательно прописывать в манифесте**\n")
+ b.WriteString(renderParamTable(defaultParams, false, false))
+
+ lifecycle := spec.Lifecycle
+ if lifecycle.SuspendOnDestroyDefault || lifecycle.AdoptExistingOnCreateDefault {
+ b.WriteString("\n\n")
+ b.WriteString("Параметры поведения (кратко)
\n")
+ b.WriteString(fmt.Sprintf("По умолчанию: `suspend_on_destroy = %s`, `adopt_existing_on_create = %s`.
\n", formatBoolTitle(lifecycle.SuspendOnDestroyDefault), formatBoolTitle(lifecycle.AdoptExistingOnCreateDefault)))
+ b.WriteString("При `terraform destroy` или удалении ресурса из манифеста:
\n")
+ b.WriteString("- `suspend_on_destroy=true` — инстанс переводится в `Suspend` (не удаляется).
\n")
+ b.WriteString("- `suspend_on_destroy=false` — Terraform удаляет ресурс только из state.
\n")
+ b.WriteString("При `apply` флаг `adopt_existing_on_create` работает как авто-`import`:
\n")
+ b.WriteString("- `false` — если ресурс уже есть, будет ошибка.
\n")
+ b.WriteString("- `true` — Terraform может взять существующий инстанс под управление (`running` → adopt, `suspended` → resume+adopt при совпадении параметров).
\n")
+ b.WriteString("Важно: один инстанс должен быть только в одном state. Иначе получите конфликт управления.\n")
+ b.WriteString("
\n")
+ }
+
+ return b.String()
+}
+
+func buildModifyParamsPage(spec types.ServiceSpec, nav string) string {
+ var b strings.Builder
+ b.WriteString(buildHeader(spec, nav))
+ b.WriteString("## Modify params\n\n")
+ modifyParams := FindParams(spec.Operations, "modify")
+ b.WriteString(renderModifyTable(modifyParams))
+ return b.String()
+}
+
+func buildOutputsPage(spec types.ServiceSpec, nav string) string {
+ var b strings.Builder
+ b.WriteString(buildHeader(spec, nav))
+ b.WriteString("## Output params\n\n")
+ if len(spec.Outputs.Params) == 0 {
+ b.WriteString("None.\n")
+ return b.String()
+ }
+ for _, p := range spec.Outputs.Params {
+ desc := outputDescription(p.Code)
+ if desc != "" {
+ b.WriteString(fmt.Sprintf("- `%s` (%s) — %s\n", p.Code, p.Type, desc))
+ } else {
+ b.WriteString(fmt.Sprintf("- `%s` (%s)\n", p.Code, p.Type))
+ }
+ }
+
+ snapshot, ok := CloudOutputByServiceID[spec.ServiceID]
+ if !ok {
+ return b.String()
+ }
+
+ hasOut := len(snapshot.OutPaths) > 0
+ hasVault := len(snapshot.VaultFields) > 0
+ if !hasOut && !hasVault {
+ return b.String()
+ }
+
+ b.WriteString("\n## Реальные поля из облака (running/suspended snapshot)\n\n")
+ b.WriteString("Поля ниже получены из реальных инстансов этого сервиса в облаке.\n")
+ b.WriteString("Используйте их как готовые ключи для `state_out_flat` и `vault_secrets`.\n\n")
+
+ if hasOut {
+ b.WriteString("### `state_out_flat` ключи\n\n")
+ for _, key := range snapshot.OutPaths {
+ if strings.TrimSpace(key) == "" {
+ continue
+ }
+ b.WriteString(fmt.Sprintf("- `state_out_flat[\"%s\"]`\n", key))
+ }
+ b.WriteString("\n")
+ }
+
+ if hasVault {
+ b.WriteString("### `vault_secrets` ключи\n\n")
+ for _, key := range snapshot.VaultFields {
+ if strings.TrimSpace(key) == "" {
+ continue
+ }
+ b.WriteString(fmt.Sprintf("- `vault_secrets[\"%s\"]`\n", key))
+ }
+ 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")
+ }
+
+ return b.String()
+}
+
+func containsString(values []string, target string) bool {
+ for _, value := range values {
+ if value == target {
+ return true
+ }
+ }
+ return false
+}
+
+func buildOpsPage(spec types.ServiceSpec, nav, docsDir string) string {
+ var b strings.Builder
+ b.WriteString(buildHeader(spec, nav))
+ b.WriteString("## Operations\n\n")
+ for _, op := range spec.Operations {
+ name := op.Name
+ if name == "" {
+ name = op.Action
+ }
+ descr := strings.TrimSpace(stripHTML(op.Man))
+ if descr != "" {
+ descr = ": " + descr
+ }
+ if op.Kind == "subresource" {
+ descrStr := strings.TrimPrefix(descr, ": ")
+ if op.Subresource != "" {
+ subFile := fmt.Sprintf("%s_%s.md", spec.Name, Slug(op.Subresource))
+ b.WriteString(fmt.Sprintf("- `%s` — %s. См. [nubes_%s_%s](%s)\n", name, descrStr, spec.Name, Slug(op.Subresource), subFile))
+ } else {
+ if descrStr != "" {
+ b.WriteString(fmt.Sprintf("\n#### `%s` — %s\n\n", name, descrStr))
+ } else {
+ b.WriteString(fmt.Sprintf("\n#### `%s`\n\n", name))
+ }
+ if len(op.Params) > 0 {
+ b.WriteString(renderParamTable(op.Params, true, true))
+ b.WriteString("\n")
+ }
+ }
+ continue
+ }
+
+ switch op.Action {
+ case "create":
+ b.WriteString(fmt.Sprintf("- `%s` — Создание кластера. Параметры: [%s params](%s_params_create.md)\n", name, "Create", spec.Name))
+ case "modify":
+ b.WriteString(fmt.Sprintf("- `%s` — Изменение кластера. Параметры: [%s params](%s_params_modify.md)\n", name, "Modify", spec.Name))
+ default:
+ if descr != "" {
+ b.WriteString(fmt.Sprintf("- `%s` — %s\n", name, strings.TrimPrefix(descr, ": ")))
+ } else {
+ b.WriteString(fmt.Sprintf("- `%s`\n", name))
+ }
+ }
+ }
+ return b.String()
+}
+
+func buildParamsLandingPage(spec types.ServiceSpec, nav string) string {
+ var b strings.Builder
+ b.WriteString(buildHeader(spec, nav))
+ b.WriteString("## Parameters\n\n")
+ b.WriteString(fmt.Sprintf("- [Create params](%s_params_create.md)\n", spec.Name))
+ b.WriteString(fmt.Sprintf("- [Modify params](%s_params_modify.md)\n", spec.Name))
+ return b.String()
+}
+
+func buildSubresourcePage(spec types.ServiceSpec, srName string, nav string) string {
+ var b strings.Builder
+ srResourceName := fmt.Sprintf("nubes_%s_%s", spec.Name, Slug(srName))
+ b.WriteString(fmt.Sprintf("# Resource %s\n\n", srResourceName))
+ b.WriteString(fmt.Sprintf("Service: `nubes_%s` (ID: %d)\n\n", spec.Name, spec.ServiceID))
+ b.WriteString(nav + "\n\n")
+
+ createParams := FindSubresourceParams(spec.Operations, srName, "create")
+ b.WriteString("## Create params\n\n")
+ if len(createParams) > 0 {
+ req, def := SplitParams(createParams)
+ b.WriteString("**Обязательные параметры**\n\n")
+ b.WriteString(renderParamTable(req, true, true))
+ if len(def) > 0 {
+ b.WriteString("\n**Параметры со значением по умолчанию**\n\n")
+ b.WriteString(renderParamTable(def, false, false))
+ }
+ } else {
+ b.WriteString("None.\n")
+ }
+
+ modifyParams := FindSubresourceParams(spec.Operations, srName, "modify")
+ if len(modifyParams) > 0 {
+ b.WriteString("\n## Modify params\n\n")
+ b.WriteString(renderModifyTable(modifyParams))
+ }
+
+ deleteParams := FindSubresourceParams(spec.Operations, srName, "delete")
+ b.WriteString("\n## Delete params\n\n")
+ if len(deleteParams) > 0 {
+ b.WriteString(renderParamTable(deleteParams, true, true))
+ } else {
+ b.WriteString("None.\n")
+ }
+
+ return b.String()
+}
+
+func buildSubresourceExamplePage(spec types.ServiceSpec, srName string, nav string, version string, apiEndpoint string) string {
+ var b strings.Builder
+ srResourceName := fmt.Sprintf("nubes_%s_%s", spec.Name, Slug(srName))
+ b.WriteString(fmt.Sprintf("# Resource %s — Example\n\n", srResourceName))
+ b.WriteString(fmt.Sprintf("Service: `nubes_%s` (ID: %d)\n\n", spec.Name, spec.ServiceID))
+ b.WriteString(nav + "\n\n")
+ b.WriteString(fmt.Sprintf("## Copy-ready manifest (`%s_%s_main.tf`)\n\n", spec.Name, Slug(srName)))
+ b.WriteString("```hcl\n")
+
+ b.WriteString("terraform {\n")
+ b.WriteString(" required_providers {\n")
+ b.WriteString(" nubes = {\n")
+ b.WriteString(" source = \"terra.k8c.ru/nubes/nubes\"\n")
+ b.WriteString(fmt.Sprintf(" version = \"%s\"\n", version))
+ b.WriteString(" }\n")
+ b.WriteString(" }\n")
+ b.WriteString("}\n\n")
+
+ b.WriteString("provider \"nubes\" {\n")
+ b.WriteString(" api_token = \"token_***\"\n")
+ b.WriteString(fmt.Sprintf(" api_endpoint = \"%s\"\n", apiEndpoint))
+ b.WriteString("}\n\n")
+
+ b.WriteString(fmt.Sprintf("# Родительский ресурс — nubes_%s должен быть создан заранее\n", spec.Name))
+ b.WriteString(fmt.Sprintf("# resource \"nubes_%s\" \"baza\" { ... }\n\n", spec.Name))
+
+ b.WriteString(fmt.Sprintf("resource \"%s\" \"example\" {\n", srResourceName))
+ b.WriteString(fmt.Sprintf(" %s_id = nubes_%s.baza.id\n", spec.Name, spec.Name))
+
+ createParams := FindSubresourceParams(spec.Operations, srName, "create")
+ req, def := SplitParams(createParams)
+ for _, p := range req {
+ b.WriteString(formatParamLine(p, " ", true))
+ }
+ if len(def) > 0 {
+ b.WriteString("\n # Параметры со значением по умолчанию\n")
+ for _, p := range def {
+ b.WriteString(formatParamLine(p, " ", false))
+ }
+ }
+
+ b.WriteString("}\n")
+ b.WriteString("```\n")
+ return b.String()
+}
+
+// ===== formatting helpers =====
+
+func renderParamTable(params []types.ParamSpec, requiredTable, noDefault bool) string {
+ if len(params) == 0 {
+ return "None.\n"
+ }
+ var b strings.Builder
+ tableClass := "resource-table resource-table-compact"
+ if requiredTable {
+ tableClass += " resource-table-required"
+ }
+ b.WriteString(fmt.Sprintf("\n", tableClass))
+ if noDefault {
+ b.WriteString("| ID | Code | Type | Description | Constraints |
\n\n")
+ } else {
+ b.WriteString("| ID | Code | Type | Default | Description | Constraints |
\n\n")
+ }
+ for _, p := range params {
+ b.WriteString("")
+ b.WriteString(fmt.Sprintf("| %s | ", escapeText(formatID(p.ID))))
+ b.WriteString(fmt.Sprintf("%s | ", formatParamCode(p.Code)))
+ b.WriteString(fmt.Sprintf("%s | ", formatTypeCell(p)))
+ if !noDefault {
+ b.WriteString(fmt.Sprintf("%s | ", defaultCell(p.Default)))
+ }
+ b.WriteString(fmt.Sprintf("%s | ", escapeText(pickTextTable(p))))
+ b.WriteString(fmt.Sprintf("%s | ", escapeText(collectConstraints(p))))
+ b.WriteString("
\n")
+ }
+ b.WriteString("
\n")
+ return b.String()
+}
+
+func renderModifyTable(params []types.ParamSpec) string {
+ if len(params) == 0 {
+ return "None.\n"
+ }
+ var b strings.Builder
+ b.WriteString("\n")
+ b.WriteString("| ID | Code | Type | Description | Constraints |
\n\n")
+ for _, p := range params {
+ b.WriteString("")
+ b.WriteString(fmt.Sprintf("| %s | ", escapeText(formatID(p.ID))))
+ b.WriteString(fmt.Sprintf("%s | ", formatParamCode(p.Code)))
+ b.WriteString(fmt.Sprintf("%s | ", formatTypeCell(p)))
+ b.WriteString(fmt.Sprintf("%s | ", escapeText(pickTextTable(p))))
+ b.WriteString(fmt.Sprintf("%s | ", escapeText(collectConstraints(p))))
+ b.WriteString("
\n")
+ }
+ b.WriteString("
\n")
+ return b.String()
+}
+
+func formatParamLine(p types.ParamSpec, indent string, requiredOnly bool) string {
+ if requiredOnly && !p.Required {
+ return ""
+ }
+ value := sampleValue(p)
+ comment := strings.TrimSpace(stripHTML(p.Descr))
+ if comment == "" {
+ comment = strings.TrimSpace(stripHTML(p.Man))
+ }
+ paramCode := ToSnake(p.Code)
+ if comment != "" {
+ return fmt.Sprintf("%s%s = %s # %s\n", indent, paramCode, value, comment)
+ }
+ return fmt.Sprintf("%s%s = %s\n", indent, paramCode, value)
+}
+
+func sampleValue(p types.ParamSpec) string {
+ if hasDefault(p.Default) {
+ return formatLiteral(p.Default)
+ }
+ if len(p.ValueList) > 0 {
+ return formatLiteral(p.ValueList[0])
+ }
+ dtype := strings.ToLower(firstType(p))
+ switch {
+ case strings.Contains(dtype, "bool"):
+ return "false"
+ case strings.Contains(dtype, "int") || strings.Contains(dtype, "number"):
+ return "1"
+ case strings.Contains(dtype, "json"):
+ return "{}"
+ }
+ return "\"TODO\""
+}
+
+func formatLiteral(value interface{}) string {
+ switch v := value.(type) {
+ case bool:
+ if v {
+ return "true"
+ }
+ return "false"
+ case int:
+ return strconv.Itoa(v)
+ case int64:
+ return strconv.FormatInt(v, 10)
+ case float64:
+ return strconv.FormatFloat(v, 'f', -1, 64)
+ case string:
+ if strings.HasPrefix(v, "{") || strings.HasPrefix(v, "[") {
+ return v
+ }
+ if strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"") {
+ return v
+ }
+ if looksLikeNumber(v) || v == "true" || v == "false" {
+ return v
+ }
+ return fmt.Sprintf("\"%s\"", v)
+ default:
+ return fmt.Sprintf("\"%v\"", v)
+ }
+}
+
+func looksLikeNumber(value string) bool {
+ if value == "" {
+ return false
+ }
+ if value[0] == '-' {
+ value = value[1:]
+ }
+ for _, ch := range value {
+ if ch == '.' {
+ continue
+ }
+ if ch < '0' || ch > '9' {
+ return false
+ }
+ }
+ return true
+}
+
+func formatID(id int) string {
+ if id == 0 {
+ return ""
+ }
+ return strconv.Itoa(id)
+}
+
+func formatTypeCell(p types.ParamSpec) string {
+ dtype := firstType(p)
+ if dtype == "" {
+ return ""
+ }
+ return "" + escapeText(dtype) + ""
+}
+
+func firstType(p types.ParamSpec) string {
+ if p.DataType != "" {
+ return normalizeType(p.DataType)
+ }
+ return normalizeType(p.Type)
+}
+
+func normalizeType(dtype string) string {
+ cleaned := strings.ReplaceAll(dtype, ">", ">")
+ if idx := strings.Index(cleaned, ">"); idx >= 0 {
+ cleaned = cleaned[:idx]
+ }
+ return strings.TrimSpace(cleaned)
+}
+
+func defaultCell(value interface{}) string {
+ if !hasDefault(value) {
+ return ""
+ }
+ if s, ok := value.(string); ok {
+ return "" + escapeText(s) + ""
+ }
+ return "" + escapeText(formatLiteral(value)) + ""
+}
+
+func pickTextTable(p types.ParamSpec) string {
+ if p.Man != "" {
+ return p.Man
+ }
+ return p.Descr
+}
+
+func hasDefault(value interface{}) bool {
+ if value == nil {
+ return false
+ }
+ if s, ok := value.(string); ok {
+ return strings.TrimSpace(s) != ""
+ }
+ return true
+}
+
+func outputDescription(code string) string {
+ switch code {
+ case "state_params":
+ return "параметры, отправленные в API при создании/изменении"
+ case "state_out":
+ return "ответ API с результатами/выходными значениями"
+ case "state_params_flat":
+ return "параметры в плоском виде (ключи с путями)"
+ case "state_out_flat":
+ return "ответы в плоском виде (ключи с путями)"
+ case "vault_secrets":
+ return "секреты, записанные в Vault для ресурса"
+ case "vault_url":
+ return "адрес Vault для ресурса"
+ case "vault_user_path":
+ return "путь пользователя в Vault"
+ case "vault_fields":
+ return "список ключей доступных секретов"
+ default:
+ return ""
+ }
+}
+
+func formatBoolTitle(value bool) string {
+ if value {
+ return "True"
+ }
+ return "False"
+}
+
+func collectConstraints(p types.ParamSpec) string {
+ parts := []string{}
+ if p.MinValue != nil {
+ parts = append(parts, fmt.Sprintf("minvalue=%d", *p.MinValue))
+ }
+ if p.MaxValue != nil {
+ parts = append(parts, fmt.Sprintf("maxvalue=%d", *p.MaxValue))
+ }
+ if p.Regex != "" {
+ parts = append(parts, fmt.Sprintf("regex=%s", p.Regex))
+ }
+ if len(p.ValueList) > 0 {
+ parts = append(parts, fmt.Sprintf("value_list=%s", strings.Join(p.ValueList, ", ")))
+ }
+ if p.Func != "" {
+ parts = append(parts, fmt.Sprintf("func=%s", p.Func))
+ }
+ if p.RefSvcID != nil {
+ parts = append(parts, fmt.Sprintf("ref_svc_id=%d", *p.RefSvcID))
+ }
+ if p.Unique != "" {
+ parts = append(parts, fmt.Sprintf("unique_scope=%s", p.Unique))
+ }
+ if p.MaxLength != nil {
+ parts = append(parts, fmt.Sprintf("maxlength=%d", *p.MaxLength))
+ }
+ if p.MinLength != nil {
+ parts = append(parts, fmt.Sprintf("minlength=%d", *p.MinLength))
+ }
+ return strings.Join(parts, "; ")
+}
+
+func escapeText(value string) string {
+ if value == "" {
+ return ""
+ }
+ text := html.EscapeString(value)
+ text = strings.ReplaceAll(text, "<br/>", "
")
+ text = strings.ReplaceAll(text, "<br />", "
")
+ text = strings.ReplaceAll(text, "<br>", "
")
+ return text
+}
+
+func formatParamCode(code string) string {
+ return formatCode(ToSnake(code))
+}
+
+func formatCode(code string) string {
+ if code == "" {
+ return ""
+ }
+ if len(code) <= 40 {
+ return "" + code + ""
+ }
+ out := []string{}
+ prev := rune(0)
+ for _, ch := range code {
+ if ch == '_' {
+ out = append(out, "_")
+ prev = ch
+ continue
+ }
+ if prev != 0 && (unicodeIsLower(prev) || unicodeIsDigit(prev)) && unicodeIsUpper(ch) {
+ out = append(out, "")
+ }
+ out = append(out, string(ch))
+ prev = ch
+ }
+ return "" + strings.Join(out, "") + ""
+}
+
+// ToSnake конвертирует CamelCase → snake_case.
+func ToSnake(value string) string {
+ if value == "" {
+ return ""
+ }
+ var out []rune
+ lastUnderscore := false
+ prevLowerOrDigit := false
+ for _, r := range value {
+ if r >= 'A' && r <= 'Z' {
+ if prevLowerOrDigit && !lastUnderscore {
+ out = append(out, '_')
+ }
+ out = append(out, r+'a'-'A')
+ lastUnderscore = false
+ prevLowerOrDigit = false
+ continue
+ }
+ if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
+ out = append(out, r)
+ lastUnderscore = false
+ prevLowerOrDigit = true
+ continue
+ }
+ if !lastUnderscore && len(out) > 0 {
+ out = append(out, '_')
+ lastUnderscore = true
+ }
+ prevLowerOrDigit = false
+ }
+ return strings.Trim(string(out), "_")
+}
+
+func unicodeIsLower(r rune) bool { return r >= 'a' && r <= 'z' }
+func unicodeIsUpper(r rune) bool { return r >= 'A' && r <= 'Z' }
+func unicodeIsDigit(r rune) bool { return r >= '0' && r <= '9' }
+
+func stripHTML(value string) string {
+ if value == "" {
+ return ""
+ }
+ re := regexp.MustCompile("<[^>]+>")
+ cleaned := re.ReplaceAllString(value, " ")
+ cleaned = strings.Join(strings.Fields(cleaned), " ")
+ return html.UnescapeString(cleaned)
+}
+
+// Slug нормализует строку для использования в URL/имени файла.
+func Slug(value string) string {
+ out := []rune{}
+ for _, ch := range value {
+ if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' {
+ out = append(out, ch)
+ } else if ch >= 'A' && ch <= 'Z' {
+ out = append(out, ch+'a'-'A')
+ } else {
+ out = append(out, '_')
+ }
+ }
+ return strings.Trim(string(out), "_")
+}
+
+// Capitalize делает первую букву заглавной.
+func Capitalize(s string) string {
+ if s == "" {
+ return s
+ }
+ return strings.ToUpper(s[:1]) + s[1:]
+}
+
+// LoadCloudOutputSnapshot загружает снепшот облачных выходов.
+func LoadCloudOutputSnapshot(root string) map[int]types.CloudOutputSnapshot {
+ out := map[int]types.CloudOutputSnapshot{}
+ base := filepath.Join(root, "docs", "70_api")
+ entries, err := os.ReadDir(base)
+ if err != nil {
+ return out
+ }
+
+ latestDir := ""
+ for _, entry := range entries {
+ if !entry.IsDir() {
+ continue
+ }
+ name := entry.Name()
+ if strings.HasPrefix(name, "output_inventory_snapshot_") {
+ if name > latestDir {
+ latestDir = name
+ }
+ }
+ }
+ if latestDir == "" {
+ return out
+ }
+
+ path := filepath.Join(base, latestDir, "running_suspended_output_fields_for_docs.json")
+ b, err := os.ReadFile(path)
+ if err != nil {
+ return out
+ }
+
+ var items []types.CloudOutputSnapshot
+ if err := json.Unmarshal(b, &items); err != nil {
+ return out
+ }
+
+ for _, item := range items {
+ out[item.ServiceID] = item
+ }
+
+ return out
+}
diff --git a/TOOLS/docs-generator/main.go b/TOOLS/docs-generator/main.go
index 63fad11..c419712 100644
--- a/TOOLS/docs-generator/main.go
+++ b/TOOLS/docs-generator/main.go
@@ -1,11 +1,9 @@
+// docs-generator — генератор Markdown-документации для ресурсов провайдера.
package main
import (
- "bytes"
- "encoding/json"
"flag"
"fmt"
- "html"
"io/fs"
"os"
"path/filepath"
@@ -15,91 +13,29 @@ import (
"strings"
"gopkg.in/yaml.v3"
+
+ "docs-generator/internal/types"
+ "docs-generator/internal/writers"
)
-type ParamSpec struct {
- ID int `yaml:"id"`
- Code string `yaml:"code"`
- DataType string `yaml:"data_type"`
- Type string `yaml:"type"`
- Required bool `yaml:"required"`
- Default interface{} `yaml:"default"`
- Descr string `yaml:"descr"`
- Man string `yaml:"man"`
- RefSvcID *int `yaml:"ref_svc_id"`
- Func string `yaml:"func"`
- MinValue *int `yaml:"minvalue"`
- MaxValue *int `yaml:"maxvalue"`
- Regex string `yaml:"regex"`
- ValueList []string `yaml:"value_list"`
- Unique string `yaml:"unique_scope"`
- MaxLength *int `yaml:"maxlength"`
- MinLength *int `yaml:"minlength"`
-}
-
-type OperationSpec struct {
- Name string `yaml:"name"`
- ID int `yaml:"id"`
- Kind string `yaml:"kind"`
- Action string `yaml:"action"`
- Subresource string `yaml:"subresource"`
- Man string `yaml:"man"`
- Params []ParamSpec `yaml:"params"`
-}
-
-type OutputParam struct {
- Code string `yaml:"code"`
- Type string `yaml:"type"`
- Sensitive bool `yaml:"sensitive"`
-}
-
-type ServiceSpec struct {
- Name string `yaml:"name"`
- ServiceID int `yaml:"service_id"`
- ServiceDisplayName string `yaml:"service_display_name"`
- ServiceMan string `yaml:"service_man"`
- Lifecycle struct {
- SuspendOnDestroyDefault bool `yaml:"suspend_on_destroy_default"`
- AdoptExistingOnCreateDefault bool `yaml:"adopt_existing_on_create_default"`
- } `yaml:"lifecycle"`
- Outputs struct {
- Params []OutputParam `yaml:"params"`
- } `yaml:"outputs"`
- Operations []OperationSpec `yaml:"operations"`
-}
-
-type ServiceMeta struct {
- ID int
- Name string
-}
-
-type CloudOutputSnapshot struct {
- ServiceID int `json:"serviceId"`
- ServiceName string `json:"serviceName"`
- OutPaths []string `json:"out_paths"`
- VaultFields []string `json:"vault_fields"`
-}
-
-var cloudOutputByServiceID = map[int]CloudOutputSnapshot{}
-
func main() {
resourcesDirFlag := flag.String("resources", "", "Path to resources_yaml")
docsDirFlag := flag.String("docs", "", "Path to docs/30_registry/resources")
servicesListFlag := flag.String("services", "", "Path to devops/config/services_list.txt")
- excludeFlag := flag.String("exclude", "", "Comma-separated resource names to skip (e.g. clickhouse)")
+ excludeFlag := flag.String("exclude", "", "Comma-separated resource names to skip")
versionFlag := flag.String("version", "", "Provider version for example block")
apiEndpointFlag := flag.String("api-endpoint", "https://deck-api.ngcloud.ru/api/v1/index.cfm", "API endpoint for example block")
flag.Parse()
root := detectRoot()
- resourcesDir := pickPath(*resourcesDirFlag, filepath.Join(root, "universal_rebuild", "resources_yaml"))
+ resourcesDir := pickPath(*resourcesDirFlag, filepath.Join(root, "provider", "resources_yaml"))
docsDir := pickPath(*docsDirFlag, filepath.Join(root, "docs", "30_registry", "resources"))
servicesList := pickPath(*servicesListFlag, filepath.Join(root, "devops", "config", "services_list.txt"))
- cloudOutputByServiceID = loadCloudOutputSnapshot(root)
+ writers.CloudOutputByServiceID = writers.LoadCloudOutputSnapshot(root)
excludeSet := toSet(*excludeFlag)
version := *versionFlag
if version == "" {
- version = detectVersion(filepath.Join(root, "universal_rebuild", "main.go"))
+ version = detectVersion(filepath.Join(root, "provider", "main.go"))
}
apiEndpoint := *apiEndpointFlag
@@ -115,24 +51,23 @@ func main() {
os.Exit(1)
}
- var processedSpecs []ServiceSpec
+ var processedSpecs []types.ServiceSpec
for _, spec := range specs {
if excludeSet[spec.Name] {
continue
}
- writeResourceDocs(docsDir, spec, version, apiEndpoint)
+ writers.ResourceDocs(docsDir, spec, version, apiEndpoint)
processedSpecs = append(processedSpecs, spec)
}
- generateIndexMD(docsDir, processedSpecs)
+ writers.IndexMD(docsDir, processedSpecs)
}
func detectRoot() string {
cwd, err := os.Getwd()
if err != nil {
- fmt.Fprintf(os.Stderr, "ERROR: cannot get working directory: %v\n", err)
- os.Exit(1)
+ panic(err)
}
- if filepath.Base(cwd) == "universal_rebuild" {
+ if filepath.Base(cwd) == "provider" {
return filepath.Dir(cwd)
}
return cwd
@@ -157,48 +92,6 @@ func toSet(csv string) map[string]bool {
return out
}
-func loadCloudOutputSnapshot(root string) map[int]CloudOutputSnapshot {
- out := map[int]CloudOutputSnapshot{}
- base := filepath.Join(root, "docs", "70_api")
- entries, err := os.ReadDir(base)
- if err != nil {
- return out
- }
-
- latestDir := ""
- for _, entry := range entries {
- if !entry.IsDir() {
- continue
- }
- name := entry.Name()
- if strings.HasPrefix(name, "output_inventory_snapshot_") {
- if name > latestDir {
- latestDir = name
- }
- }
- }
- if latestDir == "" {
- return out
- }
-
- path := filepath.Join(base, latestDir, "running_suspended_output_fields_for_docs.json")
- b, err := os.ReadFile(path)
- if err != nil {
- return out
- }
-
- var items []CloudOutputSnapshot
- if err := json.Unmarshal(b, &items); err != nil {
- return out
- }
-
- for _, item := range items {
- out[item.ServiceID] = item
- }
-
- return out
-}
-
func detectVersion(mainPath string) string {
b, err := os.ReadFile(mainPath)
if err != nil {
@@ -212,13 +105,13 @@ func detectVersion(mainPath string) string {
return "2.x"
}
-func loadServicesList(path string) []ServiceMeta {
+func loadServicesList(path string) []types.ServiceMeta {
b, err := os.ReadFile(path)
if err != nil {
return nil
}
lines := strings.Split(string(b), "\n")
- out := []ServiceMeta{}
+ out := []types.ServiceMeta{}
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
@@ -233,14 +126,14 @@ func loadServicesList(path string) []ServiceMeta {
if err != nil {
continue
}
- out = append(out, ServiceMeta{ID: id, Name: parts[1]})
+ out = append(out, types.ServiceMeta{ID: id, Name: parts[1]})
}
return out
}
-func loadSpecs(dir string, ordered []ServiceMeta) []ServiceSpec {
- specsByID := map[int]ServiceSpec{}
- walkErr := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+func loadSpecs(dir string, ordered []types.ServiceMeta) []types.ServiceSpec {
+ specsByID := map[int]types.ServiceSpec{}
+ filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
@@ -251,20 +144,16 @@ func loadSpecs(dir string, ordered []ServiceMeta) []ServiceSpec {
if err != nil {
return err
}
- var spec ServiceSpec
+ var spec types.ServiceSpec
if err := yaml.Unmarshal(b, &spec); err != nil {
return err
}
specsByID[spec.ServiceID] = spec
return nil
})
- if walkErr != nil {
- fmt.Fprintf(os.Stderr, "ERROR: failed to load specs from %s: %v\n", dir, walkErr)
- os.Exit(1)
- }
if len(ordered) == 0 {
- specs := make([]ServiceSpec, 0, len(specsByID))
+ specs := make([]types.ServiceSpec, 0, len(specsByID))
for _, spec := range specsByID {
specs = append(specs, spec)
}
@@ -272,7 +161,7 @@ func loadSpecs(dir string, ordered []ServiceMeta) []ServiceSpec {
return specs
}
- specs := []ServiceSpec{}
+ specs := []types.ServiceSpec{}
for _, meta := range ordered {
spec, ok := specsByID[meta.ID]
if !ok {
@@ -282,818 +171,3 @@ func loadSpecs(dir string, ordered []ServiceMeta) []ServiceSpec {
}
return specs
}
-
-// collectSubresources возвращает уникальные subresource-имена сервиса.
-func collectSubresources(spec ServiceSpec) []string {
- seen := map[string]bool{}
- var out []string
- for _, op := range spec.Operations {
- if op.Kind == "subresource" && op.Subresource != "" {
- if !seen[op.Subresource] {
- seen[op.Subresource] = true
- out = append(out, op.Subresource)
- }
- }
- }
- return out
-}
-
-func writeResourceDocs(docsDir string, spec ServiceSpec, version string, apiEndpoint string) {
- base := spec.Name
- nav := fmt.Sprintf("[Manual](%s.md) | [Create params](%s_params_create.md) | [Modify params](%s_params_modify.md) | [Output params](%s_outputs.md) | [Operations](%s_ops.md) | [Example](%s_example.md)", base, base, base, base, base, base)
-
- writeFile(filepath.Join(docsDir, base+".md"), buildManualPage(spec, nav))
- writeFile(filepath.Join(docsDir, base+"_example.md"), buildExamplePage(spec, nav, version, apiEndpoint))
- writeFile(filepath.Join(docsDir, base+"_params_create.md"), buildCreateParamsPage(spec, nav))
- writeFile(filepath.Join(docsDir, base+"_params_modify.md"), buildModifyParamsPage(spec, nav))
- writeFile(filepath.Join(docsDir, base+"_outputs.md"), buildOutputsPage(spec, nav))
- writeFile(filepath.Join(docsDir, base+"_ops.md"), buildOpsPage(spec, nav, docsDir))
- writeFile(filepath.Join(docsDir, base+"_params.md"), buildParamsLandingPage(spec, nav))
-
- // Отдельные страницы для каждого subresource (nubes_{service}_{subresource})
- for _, srName := range collectSubresources(spec) {
- srBase := fmt.Sprintf("%s_%s", base, slug(srName))
- srNav := fmt.Sprintf("[%s (основной)](%s.md) | [Operations](%s_ops.md) | [%s](%s.md) | [Example](%s_example.md)",
- spec.ServiceDisplayName, base, base, capitalize(srName), srBase, srBase)
- writeFile(filepath.Join(docsDir, srBase+".md"), buildSubresourcePage(spec, srName, srNav))
- writeFile(filepath.Join(docsDir, srBase+"_example.md"), buildSubresourceExamplePage(spec, srName, srNav, version, apiEndpoint))
- }
-}
-
-func writeFile(path, content string) {
- if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
- fmt.Fprintf(os.Stderr, "ERROR: cannot write %s: %v\n", path, err)
- os.Exit(1)
- }
-}
-
-func buildHeader(spec ServiceSpec, nav string) string {
- name := spec.ServiceDisplayName
- if name == "" {
- name = spec.Name
- }
- return fmt.Sprintf("# Resource nubes_%s\n\nService ID: `%d`\n\nService Name: %s\n\n%s\n\n", spec.Name, spec.ServiceID, name, nav)
-}
-
-func buildManualPage(spec ServiceSpec, nav string) string {
- var b strings.Builder
- b.WriteString(buildHeader(spec, nav))
- b.WriteString("## MAN\n\n")
- man := strings.TrimSpace(spec.ServiceMan)
- if man == "" {
- man = "Manual not available."
- }
- b.WriteString("\n")
- b.WriteString(man)
- b.WriteString("\n
\n")
- return b.String()
-}
-
-func buildExamplePage(spec ServiceSpec, nav, version string, apiEndpoint string) string {
- var b strings.Builder
- b.WriteString(buildHeader(spec, nav))
- b.WriteString(fmt.Sprintf("## Copy-ready manifest (`%s_main.tf`)\n\n", spec.Name))
- b.WriteString("```hcl\n")
- b.WriteString(exampleBlock(spec, version, apiEndpoint))
- b.WriteString("```\n\n")
- b.WriteString("## Outputs usage\n\n")
- b.WriteString("```hcl\n")
- b.WriteString("# \u0412\u044b\u0445\u043e\u0434\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a\n")
- b.WriteString(fmt.Sprintf("nubes_%s.baza.state_params[\"\u0438\u043c\u044f_\u043a\u043b\u044e\u0447\u0430\"]\n", spec.Name))
- b.WriteString(fmt.Sprintf("nubes_%s.baza.state_out[\"\u0438\u043c\u044f_\u043a\u043b\u044e\u0447\u0430\"]\n", spec.Name))
- b.WriteString(fmt.Sprintf("nubes_%s.baza.state_params_flat[\"\u0438\u043c\u044f_\u043a\u043b\u044e\u0447\u0430\"]\n", spec.Name))
- b.WriteString(fmt.Sprintf("nubes_%s.baza.state_out_flat[\"\u0438\u043c\u044f_\u043a\u043b\u044e\u0447\u0430\"]\n", spec.Name))
- b.WriteString(fmt.Sprintf("nubes_%s.baza.vault_secrets[\"\u0438\u043c\u044f_\u043a\u043b\u044e\u0447\u0430\"]\n", spec.Name))
- b.WriteString(fmt.Sprintf("nubes_%s.baza.vault_url\n", spec.Name))
- b.WriteString(fmt.Sprintf("nubes_%s.baza.vault_user_path\n", spec.Name))
- b.WriteString(fmt.Sprintf("nubes_%s.baza.vault_fields\n", spec.Name))
- b.WriteString("```\n")
- return b.String()
-}
-
-func exampleBlock(spec ServiceSpec, version string, apiEndpoint string) string {
- createParams := findParams(spec.Operations, "create")
- requiredParams, defaultParams := splitParams(createParams)
-
- var b strings.Builder
- b.WriteString("terraform {\n")
- b.WriteString(" required_providers {\n")
- b.WriteString(" nubes = {\n")
- b.WriteString(" source = \"terra.k8c.ru/nubes/nubes\"\n")
- b.WriteString(fmt.Sprintf(" version = \"%s\"\n", version))
- b.WriteString(" }\n")
- b.WriteString(" }\n")
- b.WriteString("}\n\n")
- b.WriteString("# \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 api_token \u043a Nubes API \u2014 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u0435 \u043d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439.\n")
- b.WriteString("provider \"nubes\" {\n")
- b.WriteString(" api_token = \"token_***\"\n")
- b.WriteString(fmt.Sprintf(" api_endpoint = \"%s\"\n", apiEndpoint))
- b.WriteString("}\n")
- b.WriteString(fmt.Sprintf("resource \"nubes_%s\" \"baza\" {\n", spec.Name))
-
- for _, p := range requiredParams {
- b.WriteString(formatParamLine(p, " ", true))
- }
-
- if len(defaultParams) > 0 {
- b.WriteString("\n # \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u0438\u043c\u0435\u044e\u0449\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u0435\u0441\u043b\u0438 \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442\u0435 - \u044d\u0442\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0432 \u043c\u0430\u043d\u0438\u0444\u0435\u0441\u0442\u0435\n")
- for _, p := range defaultParams {
- b.WriteString(formatParamLine(p, " ", false))
- }
- }
-
- b.WriteString("}\n")
- return b.String()
-}
-
-func buildCreateParamsPage(spec ServiceSpec, nav string) string {
- var b strings.Builder
- b.WriteString(buildHeader(spec, nav))
- b.WriteString("## Create params\n\n")
- createParams := findParams(spec.Operations, "create")
- requiredParams, defaultParams := splitParams(createParams)
-
- b.WriteString("**\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b (\u0432\u0432\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c)**\n\n")
- b.WriteString(renderParamTable(requiredParams, true, true))
-
- b.WriteString("\n**\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u0438\u043c\u0435\u044e\u0449\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u0435\u0441\u043b\u0438 \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442\u0435 - \u044d\u0442\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0432 \u043c\u0430\u043d\u0438\u0444\u0435\u0441\u0442\u0435**\n")
- b.WriteString(renderParamTable(defaultParams, false, false))
-
- lifecycle := spec.Lifecycle
- if lifecycle.SuspendOnDestroyDefault || lifecycle.AdoptExistingOnCreateDefault {
- b.WriteString("\n\n")
- b.WriteString("\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f (\u043a\u0440\u0430\u0442\u043a\u043e)
\n")
- b.WriteString(fmt.Sprintf("\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `suspend_on_destroy = %s`, `adopt_existing_on_create = %s`.
\n", formatBoolTitle(lifecycle.SuspendOnDestroyDefault), formatBoolTitle(lifecycle.AdoptExistingOnCreateDefault)))
- b.WriteString("\u041f\u0440\u0438 `terraform destroy` \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0438 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 \u0438\u0437 \u043c\u0430\u043d\u0438\u0444\u0435\u0441\u0442\u0430:
\n")
- b.WriteString("- `suspend_on_destroy=true` \u2014 \u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0432 `Suspend` (\u043d\u0435 \u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f).
\n")
- b.WriteString("- `suspend_on_destroy=false` \u2014 Terraform \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u0440\u0435\u0441\u0443\u0440\u0441 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 state.
\n")
- b.WriteString("\u041f\u0440\u0438 `apply` \u0444\u043b\u0430\u0433 `adopt_existing_on_create` \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u0430\u0432\u0442\u043e-`import`:
\n")
- b.WriteString("- `false` \u2014 \u0435\u0441\u043b\u0438 \u0440\u0435\u0441\u0443\u0440\u0441 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c, \u0431\u0443\u0434\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0430.
\n")
- b.WriteString("- `true` \u2014 Terraform \u043c\u043e\u0436\u0435\u0442 \u0432\u0437\u044f\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u043f\u043e\u0434 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 (`running` \u2192 adopt, `suspended` \u2192 resume+adopt \u043f\u0440\u0438 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432).
\n")
- b.WriteString("\u0412\u0430\u0436\u043d\u043e: \u043e\u0434\u0438\u043d \u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043e\u0434\u043d\u043e\u043c state. \u0418\u043d\u0430\u0447\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f.\n")
- b.WriteString("
\n")
- }
-
- return b.String()
-}
-
-func buildModifyParamsPage(spec ServiceSpec, nav string) string {
- var b strings.Builder
- b.WriteString(buildHeader(spec, nav))
- b.WriteString("## Modify params\n\n")
- modifyParams := findParams(spec.Operations, "modify")
- b.WriteString(renderModifyTable(modifyParams))
- return b.String()
-}
-
-func buildOutputsPage(spec ServiceSpec, nav string) string {
- var b strings.Builder
- b.WriteString(buildHeader(spec, nav))
- b.WriteString("## Output params\n\n")
- if len(spec.Outputs.Params) == 0 {
- b.WriteString("None.\n")
- return b.String()
- }
- for _, p := range spec.Outputs.Params {
- desc := outputDescription(p.Code)
- if desc != "" {
- b.WriteString(fmt.Sprintf("- `%s` (%s) \u2014 %s\n", p.Code, p.Type, desc))
- } else {
- b.WriteString(fmt.Sprintf("- `%s` (%s)\n", p.Code, p.Type))
- }
- }
-
- snapshot, ok := cloudOutputByServiceID[spec.ServiceID]
- if !ok {
- return b.String()
- }
-
- hasOut := len(snapshot.OutPaths) > 0
- hasVault := len(snapshot.VaultFields) > 0
- if !hasOut && !hasVault {
- return b.String()
- }
-
- b.WriteString("\n## Реальные поля из облака (running/suspended snapshot)\n\n")
- b.WriteString("Поля ниже получены из реальных инстансов этого сервиса в облаке.\n")
- b.WriteString("Используйте их как готовые ключи для `state_out_flat` и `vault_secrets`.\n\n")
-
- if hasOut {
- b.WriteString("### `state_out_flat` ключи\n\n")
- for _, key := range snapshot.OutPaths {
- if strings.TrimSpace(key) == "" {
- continue
- }
- b.WriteString(fmt.Sprintf("- `state_out_flat[\"%s\"]`\n", key))
- }
- b.WriteString("\n")
- }
-
- if hasVault {
- b.WriteString("### `vault_secrets` ключи\n\n")
- for _, key := range snapshot.VaultFields {
- if strings.TrimSpace(key) == "" {
- continue
- }
- b.WriteString(fmt.Sprintf("- `vault_secrets[\"%s\"]`\n", key))
- }
- 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")
- }
-
- return b.String()
-}
-
-func containsString(values []string, target string) bool {
- for _, value := range values {
- if value == target {
- return true
- }
- }
- return false
-}
-
-func buildOpsPage(spec ServiceSpec, nav, docsDir string) string {
- var b strings.Builder
- b.WriteString(buildHeader(spec, nav))
- b.WriteString("## Operations\n\n")
- for _, op := range spec.Operations {
- name := op.Name
- if name == "" {
- name = op.Action
- }
- descr := strings.TrimSpace(stripHTML(op.Man))
- if descr != "" {
- descr = ": " + descr
- }
- // Subresource-операции проверяем ДО switch, чтобы create_user/create_database
- // не попадали в case "create" основного ресурса
- if op.Kind == "subresource" {
- descrStr := strings.TrimPrefix(descr, ": ")
- if op.Subresource != "" {
- // Ссылаемся на отдельную страницу subresource — она будет создана в writeResourceDocs
- subFile := fmt.Sprintf("%s_%s.md", spec.Name, slug(op.Subresource))
- b.WriteString(fmt.Sprintf("- `%s` \u2014 %s. \u0421\u043c. [nubes_%s_%s](%s)\n", name, descrStr, spec.Name, slug(op.Subresource), subFile))
- } else {
- // Нет subresource-имени — выводим инлайн
- if descrStr != "" {
- b.WriteString(fmt.Sprintf("\n#### `%s` \u2014 %s\n\n", name, descrStr))
- } else {
- b.WriteString(fmt.Sprintf("\n#### `%s`\n\n", name))
- }
- if len(op.Params) > 0 {
- b.WriteString(renderParamTable(op.Params, true, true))
- b.WriteString("\n")
- }
- }
- continue
- }
-
- switch op.Action {
- case "create":
- b.WriteString(fmt.Sprintf("- `%s` \u2014 \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430. \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b: [%s params](%s_params_create.md)\n", name, "Create", spec.Name))
- case "modify":
- b.WriteString(fmt.Sprintf("- `%s` \u2014 \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430. \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b: [%s params](%s_params_modify.md)\n", name, "Modify", spec.Name))
- default:
- if descr != "" {
- b.WriteString(fmt.Sprintf("- `%s` \u2014 %s\n", name, strings.TrimPrefix(descr, ": ")))
- } else {
- b.WriteString(fmt.Sprintf("- `%s`\n", name))
- }
- }
- }
- return b.String()
-}
-
-func buildParamsLandingPage(spec ServiceSpec, nav string) string {
- var b strings.Builder
- b.WriteString(buildHeader(spec, nav))
- b.WriteString("## Parameters\n\n")
- b.WriteString(fmt.Sprintf("- [Create params](%s_params_create.md)\n", spec.Name))
- b.WriteString(fmt.Sprintf("- [Modify params](%s_params_modify.md)\n", spec.Name))
- return b.String()
-}
-
-func findParams(ops []OperationSpec, action string) []ParamSpec {
- for _, op := range ops {
- if op.Kind == "instance" && op.Action == action {
- return op.Params
- }
- }
- return nil
-}
-
-func splitParams(params []ParamSpec) ([]ParamSpec, []ParamSpec) {
- required := []ParamSpec{}
- defaults := []ParamSpec{}
- for _, p := range params {
- if hasDefault(p.Default) {
- defaults = append(defaults, p)
- continue
- }
- required = append(required, p)
- }
- return required, defaults
-}
-
-func hasDefault(value interface{}) bool {
- if value == nil {
- return false
- }
- if s, ok := value.(string); ok {
- return strings.TrimSpace(s) != ""
- }
- return true
-}
-
-func renderParamTable(params []ParamSpec, requiredTable, noDefault bool) string {
- if len(params) == 0 {
- return "None.\n"
- }
- var b strings.Builder
- tableClass := "resource-table resource-table-compact"
- if requiredTable {
- tableClass += " resource-table-required"
- }
- b.WriteString(fmt.Sprintf("\n", tableClass))
- if noDefault {
- b.WriteString("| ID | Code | Type | Description | Constraints |
\n\n")
- } else {
- b.WriteString("| ID | Code | Type | Default | Description | Constraints |
\n\n")
- }
- for _, p := range params {
- b.WriteString("")
- b.WriteString(fmt.Sprintf("| %s | ", escapeText(formatID(p.ID))))
- b.WriteString(fmt.Sprintf("%s | ", formatParamCode(p.Code)))
- b.WriteString(fmt.Sprintf("%s | ", formatTypeCell(p)))
- if !noDefault {
- b.WriteString(fmt.Sprintf("%s | ", defaultCell(p.Default)))
- }
- b.WriteString(fmt.Sprintf("%s | ", escapeText(pickTextTable(p))))
- b.WriteString(fmt.Sprintf("%s | ", escapeText(collectConstraints(p))))
- b.WriteString("
\n")
- }
- b.WriteString("
\n")
- return b.String()
-}
-
-func renderModifyTable(params []ParamSpec) string {
- if len(params) == 0 {
- return "None.\n"
- }
- var b strings.Builder
- b.WriteString("\n")
- b.WriteString("| ID | Code | Type | Description | Constraints |
\n\n")
- for _, p := range params {
- b.WriteString("")
- b.WriteString(fmt.Sprintf("| %s | ", escapeText(formatID(p.ID))))
- b.WriteString(fmt.Sprintf("%s | ", formatParamCode(p.Code)))
- b.WriteString(fmt.Sprintf("%s | ", formatTypeCell(p)))
- b.WriteString(fmt.Sprintf("%s | ", escapeText(pickTextTable(p))))
- b.WriteString(fmt.Sprintf("%s | ", escapeText(collectConstraints(p))))
- b.WriteString("
\n")
- }
- b.WriteString("
\n")
- return b.String()
-}
-
-func formatParamLine(p ParamSpec, indent string, requiredOnly bool) string {
- if requiredOnly && !p.Required {
- return ""
- }
- value := sampleValue(p)
- comment := strings.TrimSpace(stripHTML(p.Descr))
- if comment == "" {
- comment = strings.TrimSpace(stripHTML(p.Man))
- }
- paramCode := toSnake(p.Code)
- if comment != "" {
- return fmt.Sprintf("%s%s = %s # %s\n", indent, paramCode, value, comment)
- }
- return fmt.Sprintf("%s%s = %s\n", indent, paramCode, value)
-}
-
-func sampleValue(p ParamSpec) string {
- if hasDefault(p.Default) {
- return formatLiteral(p.Default)
- }
- if len(p.ValueList) > 0 {
- return formatLiteral(p.ValueList[0])
- }
- dtype := strings.ToLower(firstType(p))
- switch {
- case strings.Contains(dtype, "bool"):
- return "false"
- case strings.Contains(dtype, "int") || strings.Contains(dtype, "number"):
- return "1"
- case strings.Contains(dtype, "json"):
- return "{}"
- }
- return "\"TODO\""
-}
-
-func formatLiteral(value interface{}) string {
- switch v := value.(type) {
- case bool:
- if v {
- return "true"
- }
- return "false"
- case int:
- return strconv.Itoa(v)
- case int64:
- return strconv.FormatInt(v, 10)
- case float64:
- return strconv.FormatFloat(v, 'f', -1, 64)
- case string:
- if strings.HasPrefix(v, "{") || strings.HasPrefix(v, "[") {
- return v
- }
- if strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"") {
- return v
- }
- if looksLikeNumber(v) || v == "true" || v == "false" {
- return v
- }
- return fmt.Sprintf("\"%s\"", v)
- default:
- return fmt.Sprintf("\"%v\"", v)
- }
-}
-
-func looksLikeNumber(value string) bool {
- if value == "" {
- return false
- }
- if value[0] == '-' {
- value = value[1:]
- }
- for _, ch := range value {
- if ch == '.' {
- continue
- }
- if ch < '0' || ch > '9' {
- return false
- }
- }
- return true
-}
-
-func formatID(id int) string {
- if id == 0 {
- return ""
- }
- return strconv.Itoa(id)
-}
-
-func formatTypeCell(p ParamSpec) string {
- dtype := firstType(p)
- if dtype == "" {
- return ""
- }
- return "" + escapeText(dtype) + ""
-}
-
-func firstType(p ParamSpec) string {
- if p.DataType != "" {
- return normalizeType(p.DataType)
- }
- return normalizeType(p.Type)
-}
-
-func normalizeType(dtype string) string {
- cleaned := strings.ReplaceAll(dtype, ">", ">")
- if idx := strings.Index(cleaned, ">"); idx >= 0 {
- cleaned = cleaned[:idx]
- }
- return strings.TrimSpace(cleaned)
-}
-
-func defaultCell(value interface{}) string {
- if !hasDefault(value) {
- return ""
- }
- if s, ok := value.(string); ok {
- return "" + escapeText(s) + ""
- }
- return "" + escapeText(formatLiteral(value)) + ""
-}
-
-func pickTextTable(p ParamSpec) string {
- if p.Man != "" {
- return p.Man
- }
- return p.Descr
-}
-
-func outputDescription(code string) string {
- switch code {
- case "state_params":
- return "\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0432 API \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438/\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438"
- case "state_out":
- return "\u043e\u0442\u0432\u0435\u0442 API \u0441 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u043c\u0438/\u0432\u044b\u0445\u043e\u0434\u043d\u044b\u043c\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438"
- case "state_params_flat":
- return "\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432 \u043f\u043b\u043e\u0441\u043a\u043e\u043c \u0432\u0438\u0434\u0435 (\u043a\u043b\u044e\u0447\u0438 \u0441 \u043f\u0443\u0442\u044f\u043c\u0438)"
- case "state_out_flat":
- return "\u043e\u0442\u0432\u0435\u0442\u044b \u0432 \u043f\u043b\u043e\u0441\u043a\u043e\u043c \u0432\u0438\u0434\u0435 (\u043a\u043b\u044e\u0447\u0438 \u0441 \u043f\u0443\u0442\u044f\u043c\u0438)"
- case "vault_secrets":
- return "\u0441\u0435\u043a\u0440\u0435\u0442\u044b, \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u0432 Vault \u0434\u043b\u044f \u0440\u0435\u0441\u0443\u0440\u0441\u0430"
- case "vault_url":
- return "\u0430\u0434\u0440\u0435\u0441 Vault \u0434\u043b\u044f \u0440\u0435\u0441\u0443\u0440\u0441\u0430"
- case "vault_user_path":
- return "\u043f\u0443\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 Vault"
- case "vault_fields":
- return "\u0441\u043f\u0438\u0441\u043e\u043a \u043a\u043b\u044e\u0447\u0435\u0439 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0441\u0435\u043a\u0440\u0435\u0442\u043e\u0432"
- default:
- return ""
- }
-}
-
-func formatBoolTitle(value bool) string {
- if value {
- return "True"
- }
- return "False"
-}
-
-func collectConstraints(p ParamSpec) string {
- parts := []string{}
- if p.MinValue != nil {
- parts = append(parts, fmt.Sprintf("minvalue=%d", *p.MinValue))
- }
- if p.MaxValue != nil {
- parts = append(parts, fmt.Sprintf("maxvalue=%d", *p.MaxValue))
- }
- if p.Regex != "" {
- parts = append(parts, fmt.Sprintf("regex=%s", p.Regex))
- }
- if len(p.ValueList) > 0 {
- parts = append(parts, fmt.Sprintf("value_list=%s", strings.Join(p.ValueList, ", ")))
- }
- if p.Func != "" {
- parts = append(parts, fmt.Sprintf("func=%s", p.Func))
- }
- if p.RefSvcID != nil {
- parts = append(parts, fmt.Sprintf("ref_svc_id=%d", *p.RefSvcID))
- }
- if p.Unique != "" {
- parts = append(parts, fmt.Sprintf("unique_scope=%s", p.Unique))
- }
- if p.MaxLength != nil {
- parts = append(parts, fmt.Sprintf("maxlength=%d", *p.MaxLength))
- }
- if p.MinLength != nil {
- parts = append(parts, fmt.Sprintf("minlength=%d", *p.MinLength))
- }
- return strings.Join(parts, "; ")
-}
-
-func escapeText(value string) string {
- if value == "" {
- return ""
- }
- text := html.EscapeString(value)
- text = strings.ReplaceAll(text, "<br/>", "
")
- text = strings.ReplaceAll(text, "<br />", "
")
- text = strings.ReplaceAll(text, "<br>", "
")
- return text
-}
-
-func formatCode(code string) string {
- if code == "" {
- return ""
- }
- if len(code) <= 40 {
- return "" + code + ""
- }
- out := []string{}
- prev := rune(0)
- for _, ch := range code {
- if ch == '_' {
- out = append(out, "_")
- prev = ch
- continue
- }
- if prev != 0 && (unicodeIsLower(prev) || unicodeIsDigit(prev)) && unicodeIsUpper(ch) {
- out = append(out, "")
- }
- out = append(out, string(ch))
- prev = ch
- }
- return "" + strings.Join(out, "") + ""
-}
-
-func formatParamCode(code string) string {
- return formatCode(toSnake(code))
-}
-
-func toSnake(value string) string {
- if value == "" {
- return ""
- }
- var out []rune
- lastUnderscore := false
- prevLowerOrDigit := false
- for _, r := range value {
- if r >= 'A' && r <= 'Z' {
- if prevLowerOrDigit && !lastUnderscore {
- out = append(out, '_')
- }
- out = append(out, r+'a'-'A')
- lastUnderscore = false
- prevLowerOrDigit = false
- continue
- }
- if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
- out = append(out, r)
- lastUnderscore = false
- prevLowerOrDigit = true
- continue
- }
- if !lastUnderscore && len(out) > 0 {
- out = append(out, '_')
- lastUnderscore = true
- }
- prevLowerOrDigit = false
- }
- return strings.Trim(string(out), "_")
-}
-
-func unicodeIsLower(r rune) bool { return r >= 'a' && r <= 'z' }
-func unicodeIsUpper(r rune) bool { return r >= 'A' && r <= 'Z' }
-func unicodeIsDigit(r rune) bool { return r >= '0' && r <= '9' }
-
-func stripHTML(value string) string {
- if value == "" {
- return ""
- }
- re := regexp.MustCompile("<[^>]+>")
- cleaned := re.ReplaceAllString(value, " ")
- cleaned = strings.Join(strings.Fields(cleaned), " ")
- return html.UnescapeString(cleaned)
-}
-
-func slug(value string) string {
- out := []rune{}
- for _, ch := range value {
- if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' {
- out = append(out, ch)
- } else if ch >= 'A' && ch <= 'Z' {
- out = append(out, ch+'a'-'A')
- } else {
- out = append(out, '_')
- }
- }
- return strings.Trim(string(out), "_")
-}
-
-func fileExists(path string) bool {
- _, err := os.Stat(path)
- return err == nil
-}
-
-func escapeCode(value string) string {
- buf := bytes.NewBuffer(nil)
- for _, line := range strings.Split(value, "\n") {
- buf.WriteString(html.EscapeString(line))
- buf.WriteString("\n")
- }
- return buf.String()
-}
-
-// generateIndexMD writes index.md with the list of all processed services.
-// The file is written to docsDir so each profile gets its own stand-specific index.
-func generateIndexMD(docsDir string, specs []ServiceSpec) {
- var b bytes.Buffer
- b.WriteString("# Ресурсы провайдера\n\n")
- b.WriteString("| ID | Ресурс | Описание |\n")
- b.WriteString("|-----|--------|----------|\n")
- for _, spec := range specs {
- resName := "nubes_" + spec.Name
- link := fmt.Sprintf("[%s](%s.md)", resName, spec.Name)
- descr := strings.ReplaceAll(spec.ServiceDisplayName, "|", "\\|")
- b.WriteString(fmt.Sprintf("| %d | %s | %s |\n", spec.ServiceID, link, descr))
- }
- outPath := filepath.Join(docsDir, "index.md")
- if err := os.WriteFile(outPath, b.Bytes(), 0o644); err != nil {
- fmt.Fprintf(os.Stderr, "Warning: failed to write index.md: %v\n", err)
- }
-}
-
-// capitalize делает первую букву строки заглавной.
-func capitalize(s string) string {
- if s == "" {
- return s
- }
- return strings.ToUpper(s[:1]) + s[1:]
-}
-
-// findSubresourceParams возвращает параметры операции subresource по названию и action.
-func findSubresourceParams(ops []OperationSpec, srName, action string) []ParamSpec {
- for _, op := range ops {
- if op.Kind == "subresource" && op.Subresource == srName && op.Action == action {
- return op.Params
- }
- }
- return nil
-}
-
-// buildSubresourcePage генерирует страницу документации для subresource (nubes_{service}_{subresource}).
-func buildSubresourcePage(spec ServiceSpec, srName string, nav string) string {
- var b strings.Builder
- srResourceName := fmt.Sprintf("nubes_%s_%s", spec.Name, slug(srName))
- b.WriteString(fmt.Sprintf("# Resource %s\n\n", srResourceName))
- b.WriteString(fmt.Sprintf("Service: `nubes_%s` (ID: %d)\n\n", spec.Name, spec.ServiceID))
- b.WriteString(nav + "\n\n")
-
- // Create params
- createParams := findSubresourceParams(spec.Operations, srName, "create")
- b.WriteString("## Create params\n\n")
- if len(createParams) > 0 {
- req, def := splitParams(createParams)
- b.WriteString("**Обязательные параметры**\n\n")
- b.WriteString(renderParamTable(req, true, true))
- if len(def) > 0 {
- b.WriteString("\n**Параметры со значением по умолчанию**\n\n")
- b.WriteString(renderParamTable(def, false, false))
- }
- } else {
- b.WriteString("None.\n")
- }
-
- // Modify params (если есть)
- modifyParams := findSubresourceParams(spec.Operations, srName, "modify")
- if len(modifyParams) > 0 {
- b.WriteString("\n## Modify params\n\n")
- b.WriteString(renderModifyTable(modifyParams))
- }
-
- // Delete params
- deleteParams := findSubresourceParams(spec.Operations, srName, "delete")
- b.WriteString("\n## Delete params\n\n")
- if len(deleteParams) > 0 {
- b.WriteString(renderParamTable(deleteParams, true, true))
- } else {
- b.WriteString("None.\n")
- }
-
- return b.String()
-}
-
-// buildSubresourceExamplePage генерирует страницу с HCL-примером для subresource.
-func buildSubresourceExamplePage(spec ServiceSpec, srName string, nav string, version string, apiEndpoint string) string {
- var b strings.Builder
- srResourceName := fmt.Sprintf("nubes_%s_%s", spec.Name, slug(srName))
- b.WriteString(fmt.Sprintf("# Resource %s — Example\n\n", srResourceName))
- b.WriteString(fmt.Sprintf("Service: `nubes_%s` (ID: %d)\n\n", spec.Name, spec.ServiceID))
- b.WriteString(nav + "\n\n")
- b.WriteString(fmt.Sprintf("## Copy-ready manifest (`%s_%s_main.tf`)\n\n", spec.Name, slug(srName)))
- b.WriteString("```hcl\n")
-
- // terraform block
- b.WriteString("terraform {\n")
- b.WriteString(" required_providers {\n")
- b.WriteString(" nubes = {\n")
- b.WriteString(" source = \"terra.k8c.ru/nubes/nubes\"\n")
- b.WriteString(fmt.Sprintf(" version = \"%s\"\n", version))
- b.WriteString(" }\n")
- b.WriteString(" }\n")
- b.WriteString("}\n\n")
-
- b.WriteString("provider \"nubes\" {\n")
- b.WriteString(" api_token = \"token_***\"\n")
- b.WriteString(fmt.Sprintf(" api_endpoint = \"%s\"\n", apiEndpoint))
- b.WriteString("}\n\n")
-
- // Основной ресурс (родитель) — ссылка
- b.WriteString(fmt.Sprintf("# Родительский ресурс — nubes_%s должен быть создан заранее\n", spec.Name))
- b.WriteString(fmt.Sprintf("# resource \"nubes_%s\" \"baza\" { ... }\n\n", spec.Name))
-
- // Subresource
- b.WriteString(fmt.Sprintf("resource \"%s\" \"example\" {\n", srResourceName))
- b.WriteString(fmt.Sprintf(" %s_id = nubes_%s.baza.id\n", spec.Name, spec.Name))
-
- createParams := findSubresourceParams(spec.Operations, srName, "create")
- req, def := splitParams(createParams)
- for _, p := range req {
- b.WriteString(formatParamLine(p, " ", true))
- }
- if len(def) > 0 {
- b.WriteString("\n # Параметры со значением по умолчанию\n")
- for _, p := range def {
- b.WriteString(formatParamLine(p, " ", false))
- }
- }
-
- b.WriteString("}\n")
- b.WriteString("```\n")
- return b.String()
-}
diff --git a/TOOLS/ops-generator/generate_ops_docs.go b/TOOLS/ops-generator/generate_ops_docs.go
deleted file mode 100644
index 81d00e3..0000000
--- a/TOOLS/ops-generator/generate_ops_docs.go
+++ /dev/null
@@ -1,289 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "io/fs"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "gopkg.in/yaml.v3"
-)
-
-// ops_docs_gen: builds per-service operations docs from resources_ops_yaml.
-//
-// Env:
-// - NUBES_OPS_YAML_DIR (default: /resources_ops_yaml)
-// - NUBES_OPS_DOCS_DIR (default: /../docs/30_registry/resources/operations)
-
-func main() {
- repoRoot, err := findRepoRoot()
- if err != nil {
- panic(err)
- }
-
- yamlDir := strings.TrimSpace(os.Getenv("NUBES_OPS_YAML_DIR"))
- if yamlDir == "" {
- yamlDir = filepath.Join(repoRoot, "resources_ops_yaml")
- }
-
- docsDir := strings.TrimSpace(os.Getenv("NUBES_OPS_DOCS_DIR"))
- if docsDir == "" {
- root := filepath.Dir(repoRoot)
- docsDir = filepath.Join(root, "docs", "30_registry", "resources", "operations")
- }
-
- if err := os.MkdirAll(docsDir, 0o755); err != nil {
- panic(err)
- }
-
- entries := []docEntry{}
-
- err = filepath.WalkDir(yamlDir, func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
- if d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
- return nil
- }
- b, err := os.ReadFile(path)
- if err != nil {
- return err
- }
- var spec ServiceOpsSpec
- if err := yaml.Unmarshal(b, &spec); err != nil {
- return err
- }
- if spec.ServiceID == 0 || spec.Name == "" {
- return nil
- }
-
- fileName := fmt.Sprintf("%d_%s.md", spec.ServiceID, sanitizeName(spec.Name))
- outPath := filepath.Join(docsDir, fileName)
- if err := writeServiceDoc(outPath, spec); err != nil {
- return err
- }
-
- entries = append(entries, docEntry{
- ServiceID: spec.ServiceID,
- Name: spec.Name,
- File: fileName,
- Title: serviceTitle(spec),
- OpCount: len(spec.Operations),
- })
- return nil
- })
- if err != nil {
- panic(err)
- }
-
- sort.Slice(entries, func(i, j int) bool { return entries[i].ServiceID < entries[j].ServiceID })
- if err := writeIndex(filepath.Join(docsDir, "index.md"), entries); err != nil {
- panic(err)
- }
-
- fmt.Printf("Generated %d operations docs in %s\n", len(entries), docsDir)
-}
-
-type docEntry struct {
- ServiceID int
- Name string
- File string
- Title string
- OpCount int
-}
-
-func writeServiceDoc(path string, spec ServiceOpsSpec) error {
- var b strings.Builder
- b.WriteString(fmt.Sprintf("# Operations for nubes_%s\n\n", spec.Name))
- b.WriteString(fmt.Sprintf("Service ID: `%d`\n\n", spec.ServiceID))
-
- if title := serviceTitle(spec); title != "" {
- b.WriteString(fmt.Sprintf("Service: %s\n\n", title))
- }
-
- if strings.TrimSpace(spec.ServiceMan) != "" {
- b.WriteString("## UI description\n\n")
- b.WriteString(spec.ServiceMan)
- b.WriteString("\n\n")
- }
-
- b.WriteString("## Operations\n\n")
- b.WriteString("Non-CRUD operations are typically executed via an action resource.\n")
- b.WriteString("Domain operations (for example create_user, create_database) are best modeled as separate resources.\n\n")
- if len(spec.Operations) == 0 {
- b.WriteString("No operations found.\n")
- return os.WriteFile(path, []byte(b.String()), 0o644)
- }
-
- for _, op := range spec.Operations {
- b.WriteString(fmt.Sprintf("- `%s` (id: %d)\n", op.Name, op.ID))
- }
- b.WriteString("\n")
-
- for _, op := range spec.Operations {
- b.WriteString(fmt.Sprintf("## Operation: %s\n\n", op.Name))
- b.WriteString(fmt.Sprintf("Operation ID: `%d`\n\n", op.ID))
- if strings.TrimSpace(op.Man) != "" {
- b.WriteString(op.Man)
- b.WriteString("\n\n")
- }
- if len(op.Params) == 0 {
- b.WriteString("No parameters.\n\n")
- continue
- }
-
- b.WriteString("| Code | Type | Required | Default | ID | RefSvcId | ValueList | Func | Regex | Min | Max | Sensitive | DependsOn |\n")
- b.WriteString("|---|---|---|---|---|---|---|---|---|---|---|---|---|\n")
- for _, p := range op.Params {
- b.WriteString(fmt.Sprintf("| `%s` | `%s` | `%t` | `%s` | `%d` | `%s` | `%s` | `%s` | `%s` | `%s` | `%s` | `%t` | `%s` |\n",
- esc(p.Code),
- esc(p.DataType),
- p.Required,
- esc(formatValue(p.Default)),
- p.ID,
- esc(formatRef(p.RefSvcId)),
- esc(formatList(p.ValueList)),
- esc(p.Func),
- esc(p.Regex),
- esc(formatValue(p.MinValue)),
- esc(formatValue(p.MaxValue)),
- p.IsSensitive,
- esc(formatValue(p.DependsOn)),
- ))
- }
- b.WriteString("\n")
- }
-
- return os.WriteFile(path, []byte(b.String()), 0o644)
-}
-
-func writeIndex(path string, entries []docEntry) error {
- var b strings.Builder
- b.WriteString("# Operations by service\n\n")
- b.WriteString("Auto-generated list of available operations per service.\n\n")
- b.WriteString("Note: non-CRUD operations should be invoked via an action resource,\n")
- b.WriteString("and domain operations are best represented as dedicated resources.\n\n")
- for _, e := range entries {
- b.WriteString(fmt.Sprintf("- %d - nubes_%s (%d ops): [%s](%s)\n", e.ServiceID, e.Name, e.OpCount, e.Title, e.File))
- }
- return os.WriteFile(path, []byte(b.String()), 0o644)
-}
-
-func sanitizeName(name string) string {
- name = strings.ToLower(strings.TrimSpace(name))
- name = strings.ReplaceAll(name, " ", "_")
- name = strings.ReplaceAll(name, "/", "_")
- name = strings.ReplaceAll(name, "\\", "_")
- name = strings.ReplaceAll(name, ":", "_")
- name = strings.ReplaceAll(name, "-", "_")
- return name
-}
-
-func serviceTitle(spec ServiceOpsSpec) string {
- if strings.TrimSpace(spec.ServiceDisplayName) != "" {
- return spec.ServiceDisplayName
- }
- if strings.TrimSpace(spec.ServiceShortName) != "" {
- return spec.ServiceShortName
- }
- return ""
-}
-
-func formatList(items []string) string {
- if len(items) == 0 {
- return ""
- }
- return strings.Join(items, ",")
-}
-
-func formatRef(value *int) string {
- if value == nil {
- return ""
- }
- return fmt.Sprintf("%d", *value)
-}
-
-func formatValue(value interface{}) string {
- if value == nil {
- return ""
- }
- switch t := value.(type) {
- case string:
- return t
- default:
- b, err := json.Marshal(t)
- if err != nil {
- return fmt.Sprintf("%v", value)
- }
- return string(b)
- }
-}
-
-func esc(value string) string {
- return strings.ReplaceAll(value, "|", "\\|")
-}
-
-func findRepoRoot() (string, error) {
- wd, err := os.Getwd()
- if err != nil {
- return "", err
- }
- current := wd
- for i := 0; i < 8; i++ {
- candidate := filepath.Join(current, "resources_yaml")
- info, err := os.Stat(candidate)
- if err == nil && info.IsDir() {
- return current, nil
- }
- parent := filepath.Dir(current)
- if parent == current {
- break
- }
- current = parent
- }
- return "", fmt.Errorf("failed to locate universal_rebuild repo root")
-}
-
-// ===== spec =====
-
-type ServiceOpsSpec struct {
- Name string `yaml:"name"`
- ServiceID int `yaml:"service_id"`
- ServiceDisplayName string `yaml:"service_display_name,omitempty"`
- ServiceShortName string `yaml:"service_short_name,omitempty"`
- ServiceMan string `yaml:"service_man,omitempty"`
- Operations []OperationSpec `yaml:"operations"`
-}
-
-type OperationSpec struct {
- Name string `yaml:"name"`
- ID int `yaml:"id"`
- Man string `yaml:"man,omitempty"`
- Params []ParamSpec `yaml:"params"`
-}
-
-type ParamSpec struct {
- ID int `yaml:"id"`
- Code string `yaml:"code"`
- DataType string `yaml:"data_type,omitempty"`
- Required bool `yaml:"required"`
- Default interface{} `yaml:"default,omitempty"`
- ValueList []string `yaml:"value_list,omitempty"`
- RefSvcId *int `yaml:"ref_svc_id,omitempty"`
- Func string `yaml:"func,omitempty"`
- Regex string `yaml:"regex,omitempty"`
- UniqueScope string `yaml:"unique_scope,omitempty"`
- MaxLength *int `yaml:"maxlength,omitempty"`
- MinLength *int `yaml:"minlength,omitempty"`
- MaxValue interface{} `yaml:"maxvalue,omitempty"`
- MinValue interface{} `yaml:"minvalue,omitempty"`
- Descr string `yaml:"descr,omitempty"`
- Man string `yaml:"man,omitempty"`
- Sort *int `yaml:"sort,omitempty"`
- DependsOn interface{} `yaml:"depends_on,omitempty"`
- IsModifiable *bool `yaml:"is_modifiable,omitempty"`
- IsSensitive bool `yaml:"is_sensitive,omitempty"`
-}
diff --git a/TOOLS/ops-generator/internal/docs/docs.go b/TOOLS/ops-generator/internal/docs/docs.go
new file mode 100644
index 0000000..63b8fd8
--- /dev/null
+++ b/TOOLS/ops-generator/internal/docs/docs.go
@@ -0,0 +1,159 @@
+// Package docs — генерация ops-документации.
+package docs
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "strings"
+
+ "ops-generator/internal/types"
+)
+
+// Entry — запись в индексе документации.
+type Entry struct {
+ ServiceID int
+ Name string
+ File string
+ Title string
+ OpCount int
+}
+
+// ServiceDoc генерирует Markdown-документацию операций сервиса.
+func ServiceDoc(path string, spec types.ServiceOpsSpec) error {
+ var b strings.Builder
+ b.WriteString(fmt.Sprintf("# Operations for nubes_%s\n\n", spec.Name))
+ b.WriteString(fmt.Sprintf("Service ID: `%d`\n\n", spec.ServiceID))
+
+ if t := Title(spec); t != "" {
+ b.WriteString(fmt.Sprintf("Service: %s\n\n", t))
+ }
+
+ if strings.TrimSpace(spec.ServiceMan) != "" {
+ b.WriteString("## UI description\n\n")
+ b.WriteString(spec.ServiceMan)
+ b.WriteString("\n\n")
+ }
+
+ b.WriteString("## Operations\n\n")
+ b.WriteString("Non-CRUD operations are typically executed via an action resource.\n")
+ b.WriteString("Domain operations (for example create_user, create_database) are best modeled as separate resources.\n\n")
+ if len(spec.Operations) == 0 {
+ b.WriteString("No operations found.\n")
+ return os.WriteFile(path, []byte(b.String()), 0o644)
+ }
+
+ for _, op := range spec.Operations {
+ b.WriteString(fmt.Sprintf("- `%s` (id: %d)\n", op.Name, op.ID))
+ }
+ b.WriteString("\n")
+
+ for _, op := range spec.Operations {
+ b.WriteString(fmt.Sprintf("## Operation: %s\n\n", op.Name))
+ b.WriteString(fmt.Sprintf("Operation ID: `%d`\n\n", op.ID))
+ if strings.TrimSpace(op.Man) != "" {
+ b.WriteString(op.Man)
+ b.WriteString("\n\n")
+ }
+ if len(op.Params) == 0 {
+ b.WriteString("No parameters.\n\n")
+ continue
+ }
+
+ b.WriteString("| Code | Type | Required | Default | ID | RefSvcId | ValueList | Func | Regex | Min | Max | Sensitive | DependsOn |\n")
+ b.WriteString("|---|---|---|---|---|---|---|---|---|---|---|---|---|\n")
+ for _, p := range op.Params {
+ b.WriteString(fmt.Sprintf("| `%s` | `%s` | `%t` | `%s` | `%d` | `%s` | `%s` | `%s` | `%s` | `%s` | `%s` | `%t` | `%s` |\n",
+ Esc(p.Code),
+ Esc(p.DataType),
+ p.Required,
+ Esc(Value(p.Default)),
+ p.ID,
+ Esc(Ref(p.RefSvcId)),
+ Esc(List(p.ValueList)),
+ Esc(p.Func),
+ Esc(p.Regex),
+ Esc(Value(p.MinValue)),
+ Esc(Value(p.MaxValue)),
+ p.IsSensitive,
+ Esc(Value(p.DependsOn)),
+ ))
+ }
+ b.WriteString("\n")
+ }
+
+ return os.WriteFile(path, []byte(b.String()), 0o644)
+}
+
+// Index генерирует index.md со списком всех операций.
+func Index(path string, entries []Entry) error {
+ var b strings.Builder
+ b.WriteString("# Operations by service\n\n")
+ b.WriteString("Auto-generated list of available operations per service.\n\n")
+ b.WriteString("Note: non-CRUD operations should be invoked via an action resource,\n")
+ b.WriteString("and domain operations are best represented as dedicated resources.\n\n")
+ for _, e := range entries {
+ b.WriteString(fmt.Sprintf("- %d - nubes_%s (%d ops): [%s](%s)\n", e.ServiceID, e.Name, e.OpCount, e.Title, e.File))
+ }
+ return os.WriteFile(path, []byte(b.String()), 0o644)
+}
+
+// Name нормализует имя файла.
+func Name(name string) string {
+ name = strings.ToLower(strings.TrimSpace(name))
+ name = strings.ReplaceAll(name, " ", "_")
+ name = strings.ReplaceAll(name, "/", "_")
+ name = strings.ReplaceAll(name, "\\", "_")
+ name = strings.ReplaceAll(name, ":", "_")
+ name = strings.ReplaceAll(name, "-", "_")
+ return name
+}
+
+// Title возвращает отображаемое имя сервиса.
+func Title(spec types.ServiceOpsSpec) string {
+ if strings.TrimSpace(spec.ServiceDisplayName) != "" {
+ return spec.ServiceDisplayName
+ }
+ if strings.TrimSpace(spec.ServiceShortName) != "" {
+ return spec.ServiceShortName
+ }
+ return ""
+}
+
+// List форматирует список значений.
+func List(items []string) string {
+ if len(items) == 0 {
+ return ""
+ }
+ return strings.Join(items, ",")
+}
+
+// Ref форматирует RefSvcId.
+func Ref(value *int) string {
+ if value == nil {
+ return ""
+ }
+ return fmt.Sprintf("%d", *value)
+}
+
+// Value форматирует значение параметра.
+func Value(value interface{}) string {
+ if value == nil {
+ return ""
+ }
+ switch t := value.(type) {
+ case string:
+ return t
+ default:
+ b, err := json.Marshal(t)
+ if err != nil {
+ return fmt.Sprintf("%v", value)
+ }
+ return string(b)
+ }
+}
+
+// Esc экранирует pipe-символы для Markdown-таблиц.
+func Esc(value string) string {
+ return strings.ReplaceAll(value, "|", "\\|")
+}
diff --git a/TOOLS/ops-generator/internal/types/types.go b/TOOLS/ops-generator/internal/types/types.go
new file mode 100644
index 0000000..33803a4
--- /dev/null
+++ b/TOOLS/ops-generator/internal/types/types.go
@@ -0,0 +1,44 @@
+// Package types — структуры данных для ops-документации.
+package types
+
+// ServiceOpsSpec — YAML-спек операций сервиса.
+type ServiceOpsSpec struct {
+ Name string `yaml:"name"`
+ ServiceID int `yaml:"service_id"`
+ ServiceDisplayName string `yaml:"service_display_name,omitempty"`
+ ServiceShortName string `yaml:"service_short_name,omitempty"`
+ ServiceMan string `yaml:"service_man,omitempty"`
+ Operations []OperationSpec `yaml:"operations"`
+}
+
+// OperationSpec — одна операция.
+type OperationSpec struct {
+ Name string `yaml:"name"`
+ ID int `yaml:"id"`
+ Man string `yaml:"man,omitempty"`
+ Params []ParamSpec `yaml:"params"`
+}
+
+// ParamSpec — параметр операции.
+type ParamSpec struct {
+ ID int `yaml:"id"`
+ Code string `yaml:"code"`
+ DataType string `yaml:"data_type,omitempty"`
+ Required bool `yaml:"required"`
+ Default interface{} `yaml:"default,omitempty"`
+ ValueList []string `yaml:"value_list,omitempty"`
+ RefSvcId *int `yaml:"ref_svc_id,omitempty"`
+ Func string `yaml:"func,omitempty"`
+ Regex string `yaml:"regex,omitempty"`
+ UniqueScope string `yaml:"unique_scope,omitempty"`
+ MaxLength *int `yaml:"maxlength,omitempty"`
+ MinLength *int `yaml:"minlength,omitempty"`
+ MaxValue interface{} `yaml:"maxvalue,omitempty"`
+ MinValue interface{} `yaml:"minvalue,omitempty"`
+ Descr string `yaml:"descr,omitempty"`
+ Man string `yaml:"man,omitempty"`
+ Sort *int `yaml:"sort,omitempty"`
+ DependsOn interface{} `yaml:"depends_on,omitempty"`
+ IsModifiable *bool `yaml:"is_modifiable,omitempty"`
+ IsSensitive bool `yaml:"is_sensitive,omitempty"`
+}
diff --git a/TOOLS/ops-generator/main.go b/TOOLS/ops-generator/main.go
new file mode 100644
index 0000000..afce4d4
--- /dev/null
+++ b/TOOLS/ops-generator/main.go
@@ -0,0 +1,106 @@
+// ops-generator — генератор per-service operations документации.
+package main
+
+import (
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "gopkg.in/yaml.v3"
+
+ "ops-generator/internal/docs"
+ "ops-generator/internal/types"
+)
+
+func main() {
+ repoRoot, err := findRepoRoot()
+ if err != nil {
+ panic(err)
+ }
+
+ yamlDir := strings.TrimSpace(os.Getenv("NUBES_OPS_YAML_DIR"))
+ if yamlDir == "" {
+ yamlDir = filepath.Join(repoRoot, "resources_ops_yaml")
+ }
+
+ docsDir := strings.TrimSpace(os.Getenv("NUBES_OPS_DOCS_DIR"))
+ if docsDir == "" {
+ root := filepath.Dir(repoRoot)
+ docsDir = filepath.Join(root, "docs", "30_registry", "resources", "operations")
+ }
+
+ if err := os.MkdirAll(docsDir, 0o755); err != nil {
+ panic(err)
+ }
+
+ entries := []docs.Entry{}
+
+ err = filepath.WalkDir(yamlDir, func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
+ return nil
+ }
+ b, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ var spec types.ServiceOpsSpec
+ if err := yaml.Unmarshal(b, &spec); err != nil {
+ return err
+ }
+ if spec.ServiceID == 0 || spec.Name == "" {
+ return nil
+ }
+
+ fileName := fmt.Sprintf("%d_%s.md", spec.ServiceID, docs.Name(spec.Name))
+ outPath := filepath.Join(docsDir, fileName)
+ if err := docs.ServiceDoc(outPath, spec); err != nil {
+ return err
+ }
+
+ entries = append(entries, docs.Entry{
+ ServiceID: spec.ServiceID,
+ Name: spec.Name,
+ File: fileName,
+ Title: docs.Title(spec),
+ OpCount: len(spec.Operations),
+ })
+ return nil
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ sort.Slice(entries, func(i, j int) bool { return entries[i].ServiceID < entries[j].ServiceID })
+ if err := docs.Index(filepath.Join(docsDir, "index.md"), entries); err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("Generated %d operations docs in %s\n", len(entries), docsDir)
+}
+
+func findRepoRoot() (string, error) {
+ wd, err := os.Getwd()
+ if err != nil {
+ return "", err
+ }
+ current := wd
+ for i := 0; i < 8; i++ {
+ candidate := filepath.Join(current, "resources_yaml")
+ info, err := os.Stat(candidate)
+ if err == nil && info.IsDir() {
+ return current, nil
+ }
+ parent := filepath.Dir(current)
+ if parent == current {
+ break
+ }
+ current = parent
+ }
+ return "", fmt.Errorf("failed to locate provider repo root")
+}
diff --git a/TOOLS/resource-generator/helpers/helpers.go b/TOOLS/resource-generator/internal/helpers/helpers.go
similarity index 99%
rename from TOOLS/resource-generator/helpers/helpers.go
rename to TOOLS/resource-generator/internal/helpers/helpers.go
index 0a12819..a1dac43 100644
--- a/TOOLS/resource-generator/helpers/helpers.go
+++ b/TOOLS/resource-generator/internal/helpers/helpers.go
@@ -8,7 +8,7 @@ import (
"strings"
"unicode"
- "resource-generator/types"
+ "resource-generator/internal/types"
)
// ToCamel конвертирует snake_case → CamelCase.
diff --git a/TOOLS/resource-generator/loader/loader.go b/TOOLS/resource-generator/internal/loader/loader.go
similarity index 99%
rename from TOOLS/resource-generator/loader/loader.go
rename to TOOLS/resource-generator/internal/loader/loader.go
index 771b64c..d4a8a2b 100644
--- a/TOOLS/resource-generator/loader/loader.go
+++ b/TOOLS/resource-generator/internal/loader/loader.go
@@ -11,8 +11,8 @@ import (
"gopkg.in/yaml.v3"
- "resource-generator/params"
- "resource-generator/types"
+ "resource-generator/internal/params"
+ "resource-generator/internal/types"
)
// LoadSpecs загружает все YAML-спеки из директории и строит GenResource/GenSubresource/GenAction.
diff --git a/TOOLS/resource-generator/params/params.go b/TOOLS/resource-generator/internal/params/params.go
similarity index 99%
rename from TOOLS/resource-generator/params/params.go
rename to TOOLS/resource-generator/internal/params/params.go
index 4974bfb..09afe16 100644
--- a/TOOLS/resource-generator/params/params.go
+++ b/TOOLS/resource-generator/internal/params/params.go
@@ -5,7 +5,7 @@ import (
"sort"
"strings"
- "resource-generator/types"
+ "resource-generator/internal/types"
)
// Merge объединяет несколько наборов параметров, убирая дубликаты по Code.
diff --git a/TOOLS/resource-generator/templates/templates.go b/TOOLS/resource-generator/internal/templates/templates.go
similarity index 100%
rename from TOOLS/resource-generator/templates/templates.go
rename to TOOLS/resource-generator/internal/templates/templates.go
diff --git a/TOOLS/resource-generator/types/types.go b/TOOLS/resource-generator/internal/types/types.go
similarity index 100%
rename from TOOLS/resource-generator/types/types.go
rename to TOOLS/resource-generator/internal/types/types.go
diff --git a/TOOLS/resource-generator/writers/writers.go b/TOOLS/resource-generator/internal/writers/writers.go
similarity index 97%
rename from TOOLS/resource-generator/writers/writers.go
rename to TOOLS/resource-generator/internal/writers/writers.go
index 21713c5..3b2dc4d 100644
--- a/TOOLS/resource-generator/writers/writers.go
+++ b/TOOLS/resource-generator/internal/writers/writers.go
@@ -8,9 +8,9 @@ import (
"path/filepath"
"text/template"
- "resource-generator/helpers"
- "resource-generator/templates"
- "resource-generator/types"
+ "resource-generator/internal/helpers"
+ "resource-generator/internal/templates"
+ "resource-generator/internal/types"
)
// WriteInstanceResource генерирует nubes_{service} (CRUD инстанса).
diff --git a/TOOLS/resource-generator/main.go b/TOOLS/resource-generator/main.go
index e247b37..b3aad42 100644
--- a/TOOLS/resource-generator/main.go
+++ b/TOOLS/resource-generator/main.go
@@ -20,8 +20,8 @@ import (
"path/filepath"
"strings"
- "resource-generator/loader"
- "resource-generator/writers"
+ "resource-generator/internal/loader"
+ "resource-generator/internal/writers"
)
func main() {
diff --git a/TOOLS/yaml-generator/client/client.go b/TOOLS/yaml-generator/internal/client/client.go
similarity index 98%
rename from TOOLS/yaml-generator/client/client.go
rename to TOOLS/yaml-generator/internal/client/client.go
index a505886..86276b5 100644
--- a/TOOLS/yaml-generator/client/client.go
+++ b/TOOLS/yaml-generator/internal/client/client.go
@@ -10,8 +10,8 @@ import (
"strings"
"time"
- "yaml-generator/normalize"
- "yaml-generator/types"
+ "yaml-generator/internal/normalize"
+ "yaml-generator/internal/types"
)
// Client — HTTP-клиент для Nubes API.
diff --git a/TOOLS/yaml-generator/config/config.go b/TOOLS/yaml-generator/internal/config/config.go
similarity index 95%
rename from TOOLS/yaml-generator/config/config.go
rename to TOOLS/yaml-generator/internal/config/config.go
index 8baa04e..a519737 100644
--- a/TOOLS/yaml-generator/config/config.go
+++ b/TOOLS/yaml-generator/internal/config/config.go
@@ -9,7 +9,7 @@ import (
"strconv"
"strings"
- "yaml-generator/normalize"
+ "yaml-generator/internal/normalize"
)
// Config — конфигурация генератора.
@@ -170,7 +170,7 @@ func ReadServicesList(path string) ([]ServiceRef, error) {
return services, nil
}
-// FindRepoRoot ищет корень репозитория по директории universal_rebuild.
+// FindRepoRoot ищет корень репозитория по директории provider.
func FindRepoRoot() (string, error) {
wd, err := os.Getwd()
if err != nil {
@@ -178,7 +178,7 @@ func FindRepoRoot() (string, error) {
}
current := wd
for i := 0; i < 8; i++ {
- candidate := filepath.Join(current, "universal_rebuild")
+ candidate := filepath.Join(current, "provider")
info, err := os.Stat(candidate)
if err == nil && info.IsDir() {
return candidate, nil
@@ -189,5 +189,5 @@ func FindRepoRoot() (string, error) {
}
current = parent
}
- return "", errors.New("failed to locate universal_rebuild repo root")
+ return "", errors.New("failed to locate provider repo root")
}
diff --git a/TOOLS/yaml-generator/normalize/normalize.go b/TOOLS/yaml-generator/internal/normalize/normalize.go
similarity index 100%
rename from TOOLS/yaml-generator/normalize/normalize.go
rename to TOOLS/yaml-generator/internal/normalize/normalize.go
diff --git a/TOOLS/yaml-generator/spec/spec.go b/TOOLS/yaml-generator/internal/spec/spec.go
similarity index 97%
rename from TOOLS/yaml-generator/spec/spec.go
rename to TOOLS/yaml-generator/internal/spec/spec.go
index 193dc7d..8961bdc 100644
--- a/TOOLS/yaml-generator/spec/spec.go
+++ b/TOOLS/yaml-generator/internal/spec/spec.go
@@ -6,7 +6,7 @@ import (
"path/filepath"
"strings"
- "yaml-generator/types"
+ "yaml-generator/internal/types"
)
// DefaultOutputParams возвращает стандартный набор выходных параметров.
diff --git a/TOOLS/yaml-generator/types/types.go b/TOOLS/yaml-generator/internal/types/types.go
similarity index 100%
rename from TOOLS/yaml-generator/types/types.go
rename to TOOLS/yaml-generator/internal/types/types.go
diff --git a/TOOLS/yaml-generator/main.go b/TOOLS/yaml-generator/main.go
index f254633..bde56a9 100644
--- a/TOOLS/yaml-generator/main.go
+++ b/TOOLS/yaml-generator/main.go
@@ -12,11 +12,11 @@ import (
"gopkg.in/yaml.v3"
- "yaml-generator/client"
- "yaml-generator/config"
- "yaml-generator/normalize"
- "yaml-generator/spec"
- "yaml-generator/types"
+ "yaml-generator/internal/client"
+ "yaml-generator/internal/config"
+ "yaml-generator/internal/normalize"
+ "yaml-generator/internal/spec"
+ "yaml-generator/internal/types"
)
func main() {
diff --git a/devops/01_generate_yamls.sh b/devops/01_generate_yamls.sh
index 7712569..a41d114 100755
--- a/devops/01_generate_yamls.sh
+++ b/devops/01_generate_yamls.sh
@@ -3,7 +3,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_DIR="${ROOT_DIR}/universal_rebuild"
+PROVIDER_DIR="${ROOT_DIR}/provider"
PROFILE_DIR="${PROFILE_DIR:-}"
if [[ "${1:-}" == "--profile" ]]; then
@@ -64,7 +64,7 @@ fi
# Назначение:
# - Берет список сервисов из services_list.txt.
# - Для каждого сервиса запрашивает единый spec через API и пишет YAML в
-# ${ROOT_DIR}/universal_rebuild/resources_yaml.
+# ${ROOT_DIR}/provider/resources_yaml.
# - Формат имени файла: ID_имя.yaml (например, 13_s3bucket.yaml).
#
# Источники данных:
diff --git a/devops/02_generate_resources_and_docs.sh b/devops/02_generate_resources_and_docs.sh
index f3ded27..a4b8d73 100755
--- a/devops/02_generate_resources_and_docs.sh
+++ b/devops/02_generate_resources_and_docs.sh
@@ -4,7 +4,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_DIR="${ROOT_DIR}/universal_rebuild"
+PROVIDER_DIR="${ROOT_DIR}/provider"
DOCS_DIR="${ROOT_DIR}/docs/30_registry/resources"
cd "$PROVIDER_DIR"
diff --git a/devops/02_generate_resources_and_docs_template.sh b/devops/02_generate_resources_and_docs_template.sh
index 777600a..438f4e6 100755
--- a/devops/02_generate_resources_and_docs_template.sh
+++ b/devops/02_generate_resources_and_docs_template.sh
@@ -6,7 +6,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_DIR="${ROOT_DIR}/universal_rebuild"
+PROVIDER_DIR="${ROOT_DIR}/provider"
RESOURCES_YAML_DIR="${PROVIDER_DIR}/resources_yaml"
DOCS_DIR="${ROOT_DIR}/docs/30_registry/resources"
SERVICES_LIST_PATH="${ROOT_DIR}/devops/config/services_list.txt"
diff --git a/devops/02_generate_resources_and_docs_template_v2.sh b/devops/02_generate_resources_and_docs_template_v2.sh
index 26d19fe..3e86dad 100755
--- a/devops/02_generate_resources_and_docs_template_v2.sh
+++ b/devops/02_generate_resources_and_docs_template_v2.sh
@@ -6,7 +6,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_DIR="${ROOT_DIR}/universal_rebuild"
+PROVIDER_DIR="${ROOT_DIR}/provider"
RESOURCES_YAML_DIR="${PROVIDER_DIR}/resources_yaml"
DOCS_DIR="${ROOT_DIR}/docs/30_registry/resources"
SERVICES_LIST_PATH="${ROOT_DIR}/devops/config/services_list.txt"
diff --git a/devops/02_generate_resources_and_docs_v2.sh b/devops/02_generate_resources_and_docs_v2.sh
index 2a0c2c6..13a39bc 100755
--- a/devops/02_generate_resources_and_docs_v2.sh
+++ b/devops/02_generate_resources_and_docs_v2.sh
@@ -6,7 +6,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_DIR="${ROOT_DIR}/universal_rebuild"
+PROVIDER_DIR="${ROOT_DIR}/provider"
PROFILE_DIR="${PROFILE_DIR:-}"
resolve_root_path() {
diff --git a/devops/03_build_and_upload_provider.sh b/devops/03_build_and_upload_provider.sh
index b8f88b1..3d2df4d 100755
--- a/devops/03_build_and_upload_provider.sh
+++ b/devops/03_build_and_upload_provider.sh
@@ -3,7 +3,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_MAIN="${ROOT_DIR}/universal_rebuild/main.go"
+PROVIDER_MAIN="${ROOT_DIR}/provider/main.go"
BUILD_SCRIPT="${ROOT_DIR}/devops/build-provider.sh"
PROFILE_DIR="${PROFILE_DIR:-}"
@@ -171,7 +171,7 @@ validate_registry_completeness() {
TMP_PROVIDER_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_PROVIDER_DIR"' EXIT
-cp -a "${ROOT_DIR}/universal_rebuild/." "$TMP_PROVIDER_DIR/"
+cp -a "${ROOT_DIR}/provider/." "$TMP_PROVIDER_DIR/"
mkdir -p "$TMP_PROVIDER_DIR/resources_yaml"
find "$TMP_PROVIDER_DIR/resources_yaml" -maxdepth 1 -type f -name '*.yaml' -delete
cp -a "${PROFILE_RESOURCES_YAML_DIR}"/*.yaml "$TMP_PROVIDER_DIR/resources_yaml/"
diff --git a/devops/04_build_and_publish_docs.sh b/devops/04_build_and_publish_docs.sh
index c992991..68428dc 100755
--- a/devops/04_build_and_publish_docs.sh
+++ b/devops/04_build_and_publish_docs.sh
@@ -3,7 +3,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_MAIN="${ROOT_DIR}/universal_rebuild/main.go"
+PROVIDER_MAIN="${ROOT_DIR}/provider/main.go"
PROFILE_DIR="${PROFILE_DIR:-}"
normalize_api_endpoint() {
@@ -74,7 +74,7 @@ TMP_DOCS_DIR=""
if [[ -n "$PROFILE_DIR" ]]; then
# ⛔ NEVER merge with docs/ — ONLY generated docs from docs_gen//
- DOCS_GEN_DIR="${DOCS_GEN_DIR:-universal_rebuild/docs_gen/test}"
+ DOCS_GEN_DIR="${DOCS_GEN_DIR:-provider/docs_gen/test}"
DOCS_GEN_DIR="$(resolve_root_path "$DOCS_GEN_DIR")"
if [[ -d "$DOCS_GEN_DIR" ]]; then
export MKDOCS_DOCS_DIR="$DOCS_GEN_DIR"
diff --git a/devops/13_generate_yamls_clean.sh b/devops/13_generate_yamls_clean.sh
index 125b06f..826d51b 100755
--- a/devops/13_generate_yamls_clean.sh
+++ b/devops/13_generate_yamls_clean.sh
@@ -3,7 +3,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_DIR="${ROOT_DIR}/universal_rebuild"
+PROVIDER_DIR="${ROOT_DIR}/provider"
if [[ ! -d "${PROVIDER_DIR}/resources_yaml" ]]; then
echo "Error: resources_yaml not found: ${PROVIDER_DIR}/resources_yaml" >&2
diff --git a/devops/build-provider.sh b/devops/build-provider.sh
index b9387ee..9099aca 100755
--- a/devops/build-provider.sh
+++ b/devops/build-provider.sh
@@ -29,7 +29,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
-PROVIDER_DIR="${PROVIDER_DIR:-${ROOT_DIR}/universal_rebuild}"
+PROVIDER_DIR="${PROVIDER_DIR:-${ROOT_DIR}/provider}"
BUILD_DIR="${BUILD_DIR:-${ROOT_DIR}/artifacts/provider_build}"
GPG_KEY_FILE="${GPG_KEY_FILE:-${ROOT_DIR}/secrets/private_key.asc}"
diff --git a/universal_rebuild/.gitignore b/provider/.gitignore
similarity index 100%
rename from universal_rebuild/.gitignore
rename to provider/.gitignore
diff --git a/universal_rebuild/go.mod b/provider/go.mod
similarity index 100%
rename from universal_rebuild/go.mod
rename to provider/go.mod
diff --git a/universal_rebuild/go.sum b/provider/go.sum
similarity index 100%
rename from universal_rebuild/go.sum
rename to provider/go.sum
diff --git a/universal_rebuild/internal/core/client.go b/provider/internal/core/client.go
similarity index 100%
rename from universal_rebuild/internal/core/client.go
rename to provider/internal/core/client.go
diff --git a/universal_rebuild/internal/core/client_test.go b/provider/internal/core/client_test.go
similarity index 100%
rename from universal_rebuild/internal/core/client_test.go
rename to provider/internal/core/client_test.go
diff --git a/universal_rebuild/internal/core/instance_outputs.go b/provider/internal/core/instance_outputs.go
similarity index 100%
rename from universal_rebuild/internal/core/instance_outputs.go
rename to provider/internal/core/instance_outputs.go
diff --git a/universal_rebuild/internal/core/instance_params.go b/provider/internal/core/instance_params.go
similarity index 100%
rename from universal_rebuild/internal/core/instance_params.go
rename to provider/internal/core/instance_params.go
diff --git a/universal_rebuild/internal/core/operation_timeout_override.go b/provider/internal/core/operation_timeout_override.go
similarity index 100%
rename from universal_rebuild/internal/core/operation_timeout_override.go
rename to provider/internal/core/operation_timeout_override.go
diff --git a/universal_rebuild/internal/core/operation_timeouts.go b/provider/internal/core/operation_timeouts.go
similarity index 100%
rename from universal_rebuild/internal/core/operation_timeouts.go
rename to provider/internal/core/operation_timeouts.go
diff --git a/universal_rebuild/internal/core/refsvc_resolve.go b/provider/internal/core/refsvc_resolve.go
similarity index 100%
rename from universal_rebuild/internal/core/refsvc_resolve.go
rename to provider/internal/core/refsvc_resolve.go
diff --git a/universal_rebuild/internal/provider/operation_timeouts.json b/provider/internal/provider/operation_timeouts.json
similarity index 100%
rename from universal_rebuild/internal/provider/operation_timeouts.json
rename to provider/internal/provider/operation_timeouts.json
diff --git a/universal_rebuild/internal/provider/operation_timeouts_embed.go b/provider/internal/provider/operation_timeouts_embed.go
similarity index 100%
rename from universal_rebuild/internal/provider/operation_timeouts_embed.go
rename to provider/internal/provider/operation_timeouts_embed.go
diff --git a/universal_rebuild/internal/provider/provider.go b/provider/internal/provider/provider.go
similarity index 100%
rename from universal_rebuild/internal/provider/provider.go
rename to provider/internal/provider/provider.go
diff --git a/universal_rebuild/internal/resources_core/crud.go b/provider/internal/resources_core/crud.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/crud.go
rename to provider/internal/resources_core/crud.go
diff --git a/universal_rebuild/internal/resources_core/crud_test.go b/provider/internal/resources_core/crud_test.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/crud_test.go
rename to provider/internal/resources_core/crud_test.go
diff --git a/universal_rebuild/internal/resources_core/domain_collision.go b/provider/internal/resources_core/domain_collision.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/domain_collision.go
rename to provider/internal/resources_core/domain_collision.go
diff --git a/universal_rebuild/internal/resources_core/helpers.go b/provider/internal/resources_core/helpers.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/helpers.go
rename to provider/internal/resources_core/helpers.go
diff --git a/universal_rebuild/internal/resources_core/instance_state.go b/provider/internal/resources_core/instance_state.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/instance_state.go
rename to provider/internal/resources_core/instance_state.go
diff --git a/universal_rebuild/internal/resources_core/json_normalize.go b/provider/internal/resources_core/json_normalize.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/json_normalize.go
rename to provider/internal/resources_core/json_normalize.go
diff --git a/universal_rebuild/internal/resources_core/json_planmodifier.go b/provider/internal/resources_core/json_planmodifier.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/json_planmodifier.go
rename to provider/internal/resources_core/json_planmodifier.go
diff --git a/universal_rebuild/internal/resources_core/operation_ids.go b/provider/internal/resources_core/operation_ids.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/operation_ids.go
rename to provider/internal/resources_core/operation_ids.go
diff --git a/universal_rebuild/internal/resources_core/outputs.go b/provider/internal/resources_core/outputs.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/outputs.go
rename to provider/internal/resources_core/outputs.go
diff --git a/universal_rebuild/internal/resources_core/params_compare.go b/provider/internal/resources_core/params_compare.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/params_compare.go
rename to provider/internal/resources_core/params_compare.go
diff --git a/universal_rebuild/internal/resources_core/params_mapping.go b/provider/internal/resources_core/params_mapping.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/params_mapping.go
rename to provider/internal/resources_core/params_mapping.go
diff --git a/universal_rebuild/internal/resources_core/params_ref_mapping.go b/provider/internal/resources_core/params_ref_mapping.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/params_ref_mapping.go
rename to provider/internal/resources_core/params_ref_mapping.go
diff --git a/universal_rebuild/internal/resources_core/params_validation_mapping.go b/provider/internal/resources_core/params_validation_mapping.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/params_validation_mapping.go
rename to provider/internal/resources_core/params_validation_mapping.go
diff --git a/universal_rebuild/internal/resources_core/ref_validation.go b/provider/internal/resources_core/ref_validation.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/ref_validation.go
rename to provider/internal/resources_core/ref_validation.go
diff --git a/universal_rebuild/internal/resources_core/required_params.go b/provider/internal/resources_core/required_params.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/required_params.go
rename to provider/internal/resources_core/required_params.go
diff --git a/universal_rebuild/internal/resources_core/required_params_compare.go b/provider/internal/resources_core/required_params_compare.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/required_params_compare.go
rename to provider/internal/resources_core/required_params_compare.go
diff --git a/universal_rebuild/internal/resources_core/resource_diagnostics.go b/provider/internal/resources_core/resource_diagnostics.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/resource_diagnostics.go
rename to provider/internal/resources_core/resource_diagnostics.go
diff --git a/universal_rebuild/internal/resources_core/resource_diagnostics_required.go b/provider/internal/resources_core/resource_diagnostics_required.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/resource_diagnostics_required.go
rename to provider/internal/resources_core/resource_diagnostics_required.go
diff --git a/universal_rebuild/internal/resources_core/service_operation_resource.go b/provider/internal/resources_core/service_operation_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/service_operation_resource.go
rename to provider/internal/resources_core/service_operation_resource.go
diff --git a/universal_rebuild/internal/resources_core/state_refresh.go b/provider/internal/resources_core/state_refresh.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/state_refresh.go
rename to provider/internal/resources_core/state_refresh.go
diff --git a/universal_rebuild/internal/resources_core/subresource_guard.go b/provider/internal/resources_core/subresource_guard.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/subresource_guard.go
rename to provider/internal/resources_core/subresource_guard.go
diff --git a/universal_rebuild/internal/resources_core/uuid_planmodifier.go b/provider/internal/resources_core/uuid_planmodifier.go
similarity index 100%
rename from universal_rebuild/internal/resources_core/uuid_planmodifier.go
rename to provider/internal/resources_core/uuid_planmodifier.go
diff --git a/universal_rebuild/internal/resources_gen/100_openwhisk_resource.go b/provider/internal/resources_gen/100_openwhisk_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/100_openwhisk_resource.go
rename to provider/internal/resources_gen/100_openwhisk_resource.go
diff --git a/universal_rebuild/internal/resources_gen/110_dnszone_resource.go b/provider/internal/resources_gen/110_dnszone_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/110_dnszone_resource.go
rename to provider/internal/resources_gen/110_dnszone_resource.go
diff --git a/universal_rebuild/internal/resources_gen/111_dnsrecord_reconcile_action.go b/provider/internal/resources_gen/111_dnsrecord_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/111_dnsrecord_reconcile_action.go
rename to provider/internal/resources_gen/111_dnsrecord_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/111_dnsrecord_resource.go b/provider/internal/resources_gen/111_dnsrecord_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/111_dnsrecord_resource.go
rename to provider/internal/resources_gen/111_dnsrecord_resource.go
diff --git a/universal_rebuild/internal/resources_gen/112_tenant_resource.go b/provider/internal/resources_gen/112_tenant_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/112_tenant_resource.go
rename to provider/internal/resources_gen/112_tenant_resource.go
diff --git a/universal_rebuild/internal/resources_gen/113_vc_complex_resource.go b/provider/internal/resources_gen/113_vc_complex_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/113_vc_complex_resource.go
rename to provider/internal/resources_gen/113_vc_complex_resource.go
diff --git a/universal_rebuild/internal/resources_gen/114_gitea_complex_resource.go b/provider/internal/resources_gen/114_gitea_complex_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/114_gitea_complex_resource.go
rename to provider/internal/resources_gen/114_gitea_complex_resource.go
diff --git a/universal_rebuild/internal/resources_gen/115_mariadb_database_resource.go b/provider/internal/resources_gen/115_mariadb_database_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/115_mariadb_database_resource.go
rename to provider/internal/resources_gen/115_mariadb_database_resource.go
diff --git a/universal_rebuild/internal/resources_gen/115_mariadb_reconcile_action.go b/provider/internal/resources_gen/115_mariadb_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/115_mariadb_reconcile_action.go
rename to provider/internal/resources_gen/115_mariadb_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/115_mariadb_resource.go b/provider/internal/resources_gen/115_mariadb_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/115_mariadb_resource.go
rename to provider/internal/resources_gen/115_mariadb_resource.go
diff --git a/universal_rebuild/internal/resources_gen/115_mariadb_user_resource.go b/provider/internal/resources_gen/115_mariadb_user_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/115_mariadb_user_resource.go
rename to provider/internal/resources_gen/115_mariadb_user_resource.go
diff --git a/universal_rebuild/internal/resources_gen/116_kafka_resource.go b/provider/internal/resources_gen/116_kafka_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/116_kafka_resource.go
rename to provider/internal/resources_gen/116_kafka_resource.go
diff --git a/universal_rebuild/internal/resources_gen/116_kafka_topic_resource.go b/provider/internal/resources_gen/116_kafka_topic_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/116_kafka_topic_resource.go
rename to provider/internal/resources_gen/116_kafka_topic_resource.go
diff --git a/universal_rebuild/internal/resources_gen/116_kafka_user_resource.go b/provider/internal/resources_gen/116_kafka_user_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/116_kafka_user_resource.go
rename to provider/internal/resources_gen/116_kafka_user_resource.go
diff --git a/universal_rebuild/internal/resources_gen/117_nifi_resource.go b/provider/internal/resources_gen/117_nifi_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/117_nifi_resource.go
rename to provider/internal/resources_gen/117_nifi_resource.go
diff --git a/universal_rebuild/internal/resources_gen/119_akhq_resource.go b/provider/internal/resources_gen/119_akhq_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/119_akhq_resource.go
rename to provider/internal/resources_gen/119_akhq_resource.go
diff --git a/universal_rebuild/internal/resources_gen/120_clickhouse_database_resource.go b/provider/internal/resources_gen/120_clickhouse_database_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/120_clickhouse_database_resource.go
rename to provider/internal/resources_gen/120_clickhouse_database_resource.go
diff --git a/universal_rebuild/internal/resources_gen/120_clickhouse_reconcile_action.go b/provider/internal/resources_gen/120_clickhouse_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/120_clickhouse_reconcile_action.go
rename to provider/internal/resources_gen/120_clickhouse_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/120_clickhouse_resource.go b/provider/internal/resources_gen/120_clickhouse_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/120_clickhouse_resource.go
rename to provider/internal/resources_gen/120_clickhouse_resource.go
diff --git a/universal_rebuild/internal/resources_gen/120_clickhouse_user_resource.go b/provider/internal/resources_gen/120_clickhouse_user_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/120_clickhouse_user_resource.go
rename to provider/internal/resources_gen/120_clickhouse_user_resource.go
diff --git a/universal_rebuild/internal/resources_gen/12_s3_reconcile_action.go b/provider/internal/resources_gen/12_s3_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/12_s3_reconcile_action.go
rename to provider/internal/resources_gen/12_s3_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/12_s3_resource.go b/provider/internal/resources_gen/12_s3_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/12_s3_resource.go
rename to provider/internal/resources_gen/12_s3_resource.go
diff --git a/universal_rebuild/internal/resources_gen/12_s3_sub_user_resource.go b/provider/internal/resources_gen/12_s3_sub_user_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/12_s3_sub_user_resource.go
rename to provider/internal/resources_gen/12_s3_sub_user_resource.go
diff --git a/universal_rebuild/internal/resources_gen/13_s3bucket_resource.go b/provider/internal/resources_gen/13_s3bucket_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/13_s3bucket_resource.go
rename to provider/internal/resources_gen/13_s3bucket_resource.go
diff --git a/universal_rebuild/internal/resources_gen/149_valo_tenant_resource.go b/provider/internal/resources_gen/149_valo_tenant_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/149_valo_tenant_resource.go
rename to provider/internal/resources_gen/149_valo_tenant_resource.go
diff --git a/universal_rebuild/internal/resources_gen/150_k8s_sthutrval_cluster_reconcile_action.go b/provider/internal/resources_gen/150_k8s_sthutrval_cluster_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/150_k8s_sthutrval_cluster_reconcile_action.go
rename to provider/internal/resources_gen/150_k8s_sthutrval_cluster_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/150_k8s_sthutrval_cluster_resource.go b/provider/internal/resources_gen/150_k8s_sthutrval_cluster_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/150_k8s_sthutrval_cluster_resource.go
rename to provider/internal/resources_gen/150_k8s_sthutrval_cluster_resource.go
diff --git a/universal_rebuild/internal/resources_gen/150_k8s_sthutrval_cluster_update_secrets_action.go b/provider/internal/resources_gen/150_k8s_sthutrval_cluster_update_secrets_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/150_k8s_sthutrval_cluster_update_secrets_action.go
rename to provider/internal/resources_gen/150_k8s_sthutrval_cluster_update_secrets_action.go
diff --git a/universal_rebuild/internal/resources_gen/150_k8s_sthutrval_cluster_user_resource.go b/provider/internal/resources_gen/150_k8s_sthutrval_cluster_user_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/150_k8s_sthutrval_cluster_user_resource.go
rename to provider/internal/resources_gen/150_k8s_sthutrval_cluster_user_resource.go
diff --git a/universal_rebuild/internal/resources_gen/19_vc_org_reconcile_action.go b/provider/internal/resources_gen/19_vc_org_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/19_vc_org_reconcile_action.go
rename to provider/internal/resources_gen/19_vc_org_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/19_vc_org_resource.go b/provider/internal/resources_gen/19_vc_org_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/19_vc_org_resource.go
rename to provider/internal/resources_gen/19_vc_org_resource.go
diff --git a/universal_rebuild/internal/resources_gen/19_vc_org_user_resource.go b/provider/internal/resources_gen/19_vc_org_user_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/19_vc_org_user_resource.go
rename to provider/internal/resources_gen/19_vc_org_user_resource.go
diff --git a/universal_rebuild/internal/resources_gen/1_dummy_reconcile_action.go b/provider/internal/resources_gen/1_dummy_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/1_dummy_reconcile_action.go
rename to provider/internal/resources_gen/1_dummy_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/1_dummy_redeploy_action.go b/provider/internal/resources_gen/1_dummy_redeploy_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/1_dummy_redeploy_action.go
rename to provider/internal/resources_gen/1_dummy_redeploy_action.go
diff --git a/universal_rebuild/internal/resources_gen/1_dummy_resource.go b/provider/internal/resources_gen/1_dummy_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/1_dummy_resource.go
rename to provider/internal/resources_gen/1_dummy_resource.go
diff --git a/universal_rebuild/internal/resources_gen/20_vc_org_saas_resource.go b/provider/internal/resources_gen/20_vc_org_saas_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/20_vc_org_saas_resource.go
rename to provider/internal/resources_gen/20_vc_org_saas_resource.go
diff --git a/universal_rebuild/internal/resources_gen/21_vc_vdc_reconcile_action.go b/provider/internal/resources_gen/21_vc_vdc_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/21_vc_vdc_reconcile_action.go
rename to provider/internal/resources_gen/21_vc_vdc_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/21_vc_vdc_resource.go b/provider/internal/resources_gen/21_vc_vdc_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/21_vc_vdc_resource.go
rename to provider/internal/resources_gen/21_vc_vdc_resource.go
diff --git a/universal_rebuild/internal/resources_gen/22_vc_nsxt_reconcile_action.go b/provider/internal/resources_gen/22_vc_nsxt_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/22_vc_nsxt_reconcile_action.go
rename to provider/internal/resources_gen/22_vc_nsxt_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/22_vc_nsxt_resource.go b/provider/internal/resources_gen/22_vc_nsxt_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/22_vc_nsxt_resource.go
rename to provider/internal/resources_gen/22_vc_nsxt_resource.go
diff --git a/universal_rebuild/internal/resources_gen/23_vc_vm_resource.go b/provider/internal/resources_gen/23_vc_vm_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/23_vc_vm_resource.go
rename to provider/internal/resources_gen/23_vc_vm_resource.go
diff --git a/universal_rebuild/internal/resources_gen/25_vcexternalip_resource.go b/provider/internal/resources_gen/25_vcexternalip_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/25_vcexternalip_resource.go
rename to provider/internal/resources_gen/25_vcexternalip_resource.go
diff --git a/universal_rebuild/internal/resources_gen/26_vapp_reconcile_action.go b/provider/internal/resources_gen/26_vapp_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/26_vapp_reconcile_action.go
rename to provider/internal/resources_gen/26_vapp_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/26_vapp_resource.go b/provider/internal/resources_gen/26_vapp_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/26_vapp_resource.go
rename to provider/internal/resources_gen/26_vapp_resource.go
diff --git a/universal_rebuild/internal/resources_gen/27_vc_vm_v2_resource.go b/provider/internal/resources_gen/27_vc_vm_v2_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/27_vc_vm_v2_resource.go
rename to provider/internal/resources_gen/27_vc_vm_v2_resource.go
diff --git a/universal_rebuild/internal/resources_gen/28_vc_vm_v3_reconcile_action.go b/provider/internal/resources_gen/28_vc_vm_v3_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/28_vc_vm_v3_reconcile_action.go
rename to provider/internal/resources_gen/28_vc_vm_v3_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/28_vc_vm_v3_redeploy_action.go b/provider/internal/resources_gen/28_vc_vm_v3_redeploy_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/28_vc_vm_v3_redeploy_action.go
rename to provider/internal/resources_gen/28_vc_vm_v3_redeploy_action.go
diff --git a/universal_rebuild/internal/resources_gen/28_vc_vm_v3_resource.go b/provider/internal/resources_gen/28_vc_vm_v3_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/28_vc_vm_v3_resource.go
rename to provider/internal/resources_gen/28_vc_vm_v3_resource.go
diff --git a/universal_rebuild/internal/resources_gen/29_vc_vdc_group_add_vdc_action.go b/provider/internal/resources_gen/29_vc_vdc_group_add_vdc_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/29_vc_vdc_group_add_vdc_action.go
rename to provider/internal/resources_gen/29_vc_vdc_group_add_vdc_action.go
diff --git a/universal_rebuild/internal/resources_gen/29_vc_vdc_group_reconcile_action.go b/provider/internal/resources_gen/29_vc_vdc_group_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/29_vc_vdc_group_reconcile_action.go
rename to provider/internal/resources_gen/29_vc_vdc_group_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/29_vc_vdc_group_remove_vdc_action.go b/provider/internal/resources_gen/29_vc_vdc_group_remove_vdc_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/29_vc_vdc_group_remove_vdc_action.go
rename to provider/internal/resources_gen/29_vc_vdc_group_remove_vdc_action.go
diff --git a/universal_rebuild/internal/resources_gen/29_vc_vdc_group_resource.go b/provider/internal/resources_gen/29_vc_vdc_group_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/29_vc_vdc_group_resource.go
rename to provider/internal/resources_gen/29_vc_vdc_group_resource.go
diff --git a/universal_rebuild/internal/resources_gen/29_vc_vdc_group_vdc_resource.go b/provider/internal/resources_gen/29_vc_vdc_group_vdc_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/29_vc_vdc_group_vdc_resource.go
rename to provider/internal/resources_gen/29_vc_vdc_group_vdc_resource.go
diff --git a/universal_rebuild/internal/resources_gen/2_template_reconcile_action.go b/provider/internal/resources_gen/2_template_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/2_template_reconcile_action.go
rename to provider/internal/resources_gen/2_template_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/2_template_resource.go b/provider/internal/resources_gen/2_template_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/2_template_resource.go
rename to provider/internal/resources_gen/2_template_resource.go
diff --git a/universal_rebuild/internal/resources_gen/32_vmpostgre_resource.go b/provider/internal/resources_gen/32_vmpostgre_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/32_vmpostgre_resource.go
rename to provider/internal/resources_gen/32_vmpostgre_resource.go
diff --git a/universal_rebuild/internal/resources_gen/50_nextcloud_backup_resource.go b/provider/internal/resources_gen/50_nextcloud_backup_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/50_nextcloud_backup_resource.go
rename to provider/internal/resources_gen/50_nextcloud_backup_resource.go
diff --git a/universal_rebuild/internal/resources_gen/50_nextcloud_reconcile_action.go b/provider/internal/resources_gen/50_nextcloud_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/50_nextcloud_reconcile_action.go
rename to provider/internal/resources_gen/50_nextcloud_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/50_nextcloud_recovery_action.go b/provider/internal/resources_gen/50_nextcloud_recovery_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/50_nextcloud_recovery_action.go
rename to provider/internal/resources_gen/50_nextcloud_recovery_action.go
diff --git a/universal_rebuild/internal/resources_gen/50_nextcloud_resource.go b/provider/internal/resources_gen/50_nextcloud_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/50_nextcloud_resource.go
rename to provider/internal/resources_gen/50_nextcloud_resource.go
diff --git a/universal_rebuild/internal/resources_gen/50_nextcloud_restart_action.go b/provider/internal/resources_gen/50_nextcloud_restart_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/50_nextcloud_restart_action.go
rename to provider/internal/resources_gen/50_nextcloud_restart_action.go
diff --git a/universal_rebuild/internal/resources_gen/81_superset_resource.go b/provider/internal/resources_gen/81_superset_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/81_superset_resource.go
rename to provider/internal/resources_gen/81_superset_resource.go
diff --git a/universal_rebuild/internal/resources_gen/82_harbor_resource.go b/provider/internal/resources_gen/82_harbor_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/82_harbor_resource.go
rename to provider/internal/resources_gen/82_harbor_resource.go
diff --git a/universal_rebuild/internal/resources_gen/88_k8s_ziti_controller_resource.go b/provider/internal/resources_gen/88_k8s_ziti_controller_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/88_k8s_ziti_controller_resource.go
rename to provider/internal/resources_gen/88_k8s_ziti_controller_resource.go
diff --git a/universal_rebuild/internal/resources_gen/89_flask_reconcile_action.go b/provider/internal/resources_gen/89_flask_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/89_flask_reconcile_action.go
rename to provider/internal/resources_gen/89_flask_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/89_flask_redeploy_action.go b/provider/internal/resources_gen/89_flask_redeploy_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/89_flask_redeploy_action.go
rename to provider/internal/resources_gen/89_flask_redeploy_action.go
diff --git a/universal_rebuild/internal/resources_gen/89_flask_resource.go b/provider/internal/resources_gen/89_flask_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/89_flask_resource.go
rename to provider/internal/resources_gen/89_flask_resource.go
diff --git a/universal_rebuild/internal/resources_gen/90_postgres_backup_resource.go b/provider/internal/resources_gen/90_postgres_backup_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/90_postgres_backup_resource.go
rename to provider/internal/resources_gen/90_postgres_backup_resource.go
diff --git a/universal_rebuild/internal/resources_gen/90_postgres_database_resource.go b/provider/internal/resources_gen/90_postgres_database_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/90_postgres_database_resource.go
rename to provider/internal/resources_gen/90_postgres_database_resource.go
diff --git a/universal_rebuild/internal/resources_gen/90_postgres_reconcile_action.go b/provider/internal/resources_gen/90_postgres_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/90_postgres_reconcile_action.go
rename to provider/internal/resources_gen/90_postgres_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/90_postgres_recovery_action.go b/provider/internal/resources_gen/90_postgres_recovery_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/90_postgres_recovery_action.go
rename to provider/internal/resources_gen/90_postgres_recovery_action.go
diff --git a/universal_rebuild/internal/resources_gen/90_postgres_resource.go b/provider/internal/resources_gen/90_postgres_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/90_postgres_resource.go
rename to provider/internal/resources_gen/90_postgres_resource.go
diff --git a/universal_rebuild/internal/resources_gen/90_postgres_restart_action.go b/provider/internal/resources_gen/90_postgres_restart_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/90_postgres_restart_action.go
rename to provider/internal/resources_gen/90_postgres_restart_action.go
diff --git a/universal_rebuild/internal/resources_gen/90_postgres_user_resource.go b/provider/internal/resources_gen/90_postgres_user_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/90_postgres_user_resource.go
rename to provider/internal/resources_gen/90_postgres_user_resource.go
diff --git a/universal_rebuild/internal/resources_gen/91_redis_reconcile_action.go b/provider/internal/resources_gen/91_redis_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/91_redis_reconcile_action.go
rename to provider/internal/resources_gen/91_redis_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/91_redis_resource.go b/provider/internal/resources_gen/91_redis_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/91_redis_resource.go
rename to provider/internal/resources_gen/91_redis_resource.go
diff --git a/universal_rebuild/internal/resources_gen/92_mongodb_resource.go b/provider/internal/resources_gen/92_mongodb_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/92_mongodb_resource.go
rename to provider/internal/resources_gen/92_mongodb_resource.go
diff --git a/universal_rebuild/internal/resources_gen/92_mongodb_user_resource.go b/provider/internal/resources_gen/92_mongodb_user_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/92_mongodb_user_resource.go
rename to provider/internal/resources_gen/92_mongodb_user_resource.go
diff --git a/universal_rebuild/internal/resources_gen/93_rabbitmq_resource.go b/provider/internal/resources_gen/93_rabbitmq_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/93_rabbitmq_resource.go
rename to provider/internal/resources_gen/93_rabbitmq_resource.go
diff --git a/universal_rebuild/internal/resources_gen/94_lucee_reconcile_action.go b/provider/internal/resources_gen/94_lucee_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/94_lucee_reconcile_action.go
rename to provider/internal/resources_gen/94_lucee_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/94_lucee_redeploy_action.go b/provider/internal/resources_gen/94_lucee_redeploy_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/94_lucee_redeploy_action.go
rename to provider/internal/resources_gen/94_lucee_redeploy_action.go
diff --git a/universal_rebuild/internal/resources_gen/94_lucee_resource.go b/provider/internal/resources_gen/94_lucee_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/94_lucee_resource.go
rename to provider/internal/resources_gen/94_lucee_resource.go
diff --git a/universal_rebuild/internal/resources_gen/94_lucee_restart_action.go b/provider/internal/resources_gen/94_lucee_restart_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/94_lucee_restart_action.go
rename to provider/internal/resources_gen/94_lucee_restart_action.go
diff --git a/universal_rebuild/internal/resources_gen/95_nodejs_reconcile_action.go b/provider/internal/resources_gen/95_nodejs_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/95_nodejs_reconcile_action.go
rename to provider/internal/resources_gen/95_nodejs_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/95_nodejs_redeploy_action.go b/provider/internal/resources_gen/95_nodejs_redeploy_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/95_nodejs_redeploy_action.go
rename to provider/internal/resources_gen/95_nodejs_redeploy_action.go
diff --git a/universal_rebuild/internal/resources_gen/95_nodejs_resource.go b/provider/internal/resources_gen/95_nodejs_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/95_nodejs_resource.go
rename to provider/internal/resources_gen/95_nodejs_resource.go
diff --git a/universal_rebuild/internal/resources_gen/96_pgadmin_redeploy_action.go b/provider/internal/resources_gen/96_pgadmin_redeploy_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/96_pgadmin_redeploy_action.go
rename to provider/internal/resources_gen/96_pgadmin_redeploy_action.go
diff --git a/universal_rebuild/internal/resources_gen/96_pgadmin_resource.go b/provider/internal/resources_gen/96_pgadmin_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/96_pgadmin_resource.go
rename to provider/internal/resources_gen/96_pgadmin_resource.go
diff --git a/universal_rebuild/internal/resources_gen/97_nodered_resource.go b/provider/internal/resources_gen/97_nodered_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/97_nodered_resource.go
rename to provider/internal/resources_gen/97_nodered_resource.go
diff --git a/universal_rebuild/internal/resources_gen/98_http_reconcile_action.go b/provider/internal/resources_gen/98_http_reconcile_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/98_http_reconcile_action.go
rename to provider/internal/resources_gen/98_http_reconcile_action.go
diff --git a/universal_rebuild/internal/resources_gen/98_http_redeploy_action.go b/provider/internal/resources_gen/98_http_redeploy_action.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/98_http_redeploy_action.go
rename to provider/internal/resources_gen/98_http_redeploy_action.go
diff --git a/universal_rebuild/internal/resources_gen/98_http_resource.go b/provider/internal/resources_gen/98_http_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/98_http_resource.go
rename to provider/internal/resources_gen/98_http_resource.go
diff --git a/universal_rebuild/internal/resources_gen/99_gitea_resource.go b/provider/internal/resources_gen/99_gitea_resource.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/99_gitea_resource.go
rename to provider/internal/resources_gen/99_gitea_resource.go
diff --git a/universal_rebuild/internal/resources_gen/registry.go b/provider/internal/resources_gen/registry.go
similarity index 100%
rename from universal_rebuild/internal/resources_gen/registry.go
rename to provider/internal/resources_gen/registry.go
diff --git a/universal_rebuild/main.go b/provider/main.go
similarity index 100%
rename from universal_rebuild/main.go
rename to provider/main.go
diff --git a/universal_rebuild/resources_yaml/100_openwhisk.yaml b/provider/resources_yaml/100_openwhisk.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/100_openwhisk.yaml
rename to provider/resources_yaml/100_openwhisk.yaml
diff --git a/universal_rebuild/resources_yaml/110_dnszone.yaml b/provider/resources_yaml/110_dnszone.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/110_dnszone.yaml
rename to provider/resources_yaml/110_dnszone.yaml
diff --git a/universal_rebuild/resources_yaml/111_dnsrecord.yaml b/provider/resources_yaml/111_dnsrecord.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/111_dnsrecord.yaml
rename to provider/resources_yaml/111_dnsrecord.yaml
diff --git a/universal_rebuild/resources_yaml/112_tenant.yaml b/provider/resources_yaml/112_tenant.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/112_tenant.yaml
rename to provider/resources_yaml/112_tenant.yaml
diff --git a/universal_rebuild/resources_yaml/113_vc_complex.yaml b/provider/resources_yaml/113_vc_complex.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/113_vc_complex.yaml
rename to provider/resources_yaml/113_vc_complex.yaml
diff --git a/universal_rebuild/resources_yaml/114_gitea_complex.yaml b/provider/resources_yaml/114_gitea_complex.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/114_gitea_complex.yaml
rename to provider/resources_yaml/114_gitea_complex.yaml
diff --git a/universal_rebuild/resources_yaml/115_mariadb.yaml b/provider/resources_yaml/115_mariadb.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/115_mariadb.yaml
rename to provider/resources_yaml/115_mariadb.yaml
diff --git a/universal_rebuild/resources_yaml/116_kafka.yaml b/provider/resources_yaml/116_kafka.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/116_kafka.yaml
rename to provider/resources_yaml/116_kafka.yaml
diff --git a/universal_rebuild/resources_yaml/117_nifi.yaml b/provider/resources_yaml/117_nifi.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/117_nifi.yaml
rename to provider/resources_yaml/117_nifi.yaml
diff --git a/universal_rebuild/resources_yaml/119_akhq.yaml b/provider/resources_yaml/119_akhq.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/119_akhq.yaml
rename to provider/resources_yaml/119_akhq.yaml
diff --git a/universal_rebuild/resources_yaml/120_clickhouse.yaml b/provider/resources_yaml/120_clickhouse.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/120_clickhouse.yaml
rename to provider/resources_yaml/120_clickhouse.yaml
diff --git a/universal_rebuild/resources_yaml/12_s3.yaml b/provider/resources_yaml/12_s3.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/12_s3.yaml
rename to provider/resources_yaml/12_s3.yaml
diff --git a/universal_rebuild/resources_yaml/13_s3bucket.yaml b/provider/resources_yaml/13_s3bucket.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/13_s3bucket.yaml
rename to provider/resources_yaml/13_s3bucket.yaml
diff --git a/universal_rebuild/resources_yaml/149_valo_tenant.yaml b/provider/resources_yaml/149_valo_tenant.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/149_valo_tenant.yaml
rename to provider/resources_yaml/149_valo_tenant.yaml
diff --git a/universal_rebuild/resources_yaml/150_k8s_sthutrval_cluster.yaml b/provider/resources_yaml/150_k8s_sthutrval_cluster.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/150_k8s_sthutrval_cluster.yaml
rename to provider/resources_yaml/150_k8s_sthutrval_cluster.yaml
diff --git a/universal_rebuild/resources_yaml/19_vc_org.yaml b/provider/resources_yaml/19_vc_org.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/19_vc_org.yaml
rename to provider/resources_yaml/19_vc_org.yaml
diff --git a/universal_rebuild/resources_yaml/1_dummy.yaml b/provider/resources_yaml/1_dummy.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/1_dummy.yaml
rename to provider/resources_yaml/1_dummy.yaml
diff --git a/universal_rebuild/resources_yaml/20_vc_org_saas.yaml b/provider/resources_yaml/20_vc_org_saas.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/20_vc_org_saas.yaml
rename to provider/resources_yaml/20_vc_org_saas.yaml
diff --git a/universal_rebuild/resources_yaml/21_vc_vdc.yaml b/provider/resources_yaml/21_vc_vdc.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/21_vc_vdc.yaml
rename to provider/resources_yaml/21_vc_vdc.yaml
diff --git a/universal_rebuild/resources_yaml/22_vc_nsxt.yaml b/provider/resources_yaml/22_vc_nsxt.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/22_vc_nsxt.yaml
rename to provider/resources_yaml/22_vc_nsxt.yaml
diff --git a/universal_rebuild/resources_yaml/23_vc_vm.yaml b/provider/resources_yaml/23_vc_vm.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/23_vc_vm.yaml
rename to provider/resources_yaml/23_vc_vm.yaml
diff --git a/universal_rebuild/resources_yaml/25_vcexternalip.yaml b/provider/resources_yaml/25_vcexternalip.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/25_vcexternalip.yaml
rename to provider/resources_yaml/25_vcexternalip.yaml
diff --git a/universal_rebuild/resources_yaml/26_vapp.yaml b/provider/resources_yaml/26_vapp.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/26_vapp.yaml
rename to provider/resources_yaml/26_vapp.yaml
diff --git a/universal_rebuild/resources_yaml/27_vc_vm_v2.yaml b/provider/resources_yaml/27_vc_vm_v2.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/27_vc_vm_v2.yaml
rename to provider/resources_yaml/27_vc_vm_v2.yaml
diff --git a/universal_rebuild/resources_yaml/28_vc_vm_v3.yaml b/provider/resources_yaml/28_vc_vm_v3.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/28_vc_vm_v3.yaml
rename to provider/resources_yaml/28_vc_vm_v3.yaml
diff --git a/universal_rebuild/resources_yaml/29_vc_vdc_group.yaml b/provider/resources_yaml/29_vc_vdc_group.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/29_vc_vdc_group.yaml
rename to provider/resources_yaml/29_vc_vdc_group.yaml
diff --git a/universal_rebuild/resources_yaml/2_template.yaml b/provider/resources_yaml/2_template.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/2_template.yaml
rename to provider/resources_yaml/2_template.yaml
diff --git a/universal_rebuild/resources_yaml/32_vmpostgre.yaml b/provider/resources_yaml/32_vmpostgre.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/32_vmpostgre.yaml
rename to provider/resources_yaml/32_vmpostgre.yaml
diff --git a/universal_rebuild/resources_yaml/50_nextcloud.yaml b/provider/resources_yaml/50_nextcloud.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/50_nextcloud.yaml
rename to provider/resources_yaml/50_nextcloud.yaml
diff --git a/universal_rebuild/resources_yaml/81_superset.yaml b/provider/resources_yaml/81_superset.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/81_superset.yaml
rename to provider/resources_yaml/81_superset.yaml
diff --git a/universal_rebuild/resources_yaml/82_harbor.yaml b/provider/resources_yaml/82_harbor.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/82_harbor.yaml
rename to provider/resources_yaml/82_harbor.yaml
diff --git a/universal_rebuild/resources_yaml/88_k8s_ziti_controller.yaml b/provider/resources_yaml/88_k8s_ziti_controller.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/88_k8s_ziti_controller.yaml
rename to provider/resources_yaml/88_k8s_ziti_controller.yaml
diff --git a/universal_rebuild/resources_yaml/89_flask.yaml b/provider/resources_yaml/89_flask.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/89_flask.yaml
rename to provider/resources_yaml/89_flask.yaml
diff --git a/universal_rebuild/resources_yaml/90_postgres.yaml b/provider/resources_yaml/90_postgres.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/90_postgres.yaml
rename to provider/resources_yaml/90_postgres.yaml
diff --git a/universal_rebuild/resources_yaml/91_redis.yaml b/provider/resources_yaml/91_redis.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/91_redis.yaml
rename to provider/resources_yaml/91_redis.yaml
diff --git a/universal_rebuild/resources_yaml/92_mongodb.yaml b/provider/resources_yaml/92_mongodb.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/92_mongodb.yaml
rename to provider/resources_yaml/92_mongodb.yaml
diff --git a/universal_rebuild/resources_yaml/93_rabbitmq.yaml b/provider/resources_yaml/93_rabbitmq.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/93_rabbitmq.yaml
rename to provider/resources_yaml/93_rabbitmq.yaml
diff --git a/universal_rebuild/resources_yaml/94_lucee.yaml b/provider/resources_yaml/94_lucee.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/94_lucee.yaml
rename to provider/resources_yaml/94_lucee.yaml
diff --git a/universal_rebuild/resources_yaml/95_nodejs.yaml b/provider/resources_yaml/95_nodejs.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/95_nodejs.yaml
rename to provider/resources_yaml/95_nodejs.yaml
diff --git a/universal_rebuild/resources_yaml/96_pgadmin.yaml b/provider/resources_yaml/96_pgadmin.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/96_pgadmin.yaml
rename to provider/resources_yaml/96_pgadmin.yaml
diff --git a/universal_rebuild/resources_yaml/97_nodered.yaml b/provider/resources_yaml/97_nodered.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/97_nodered.yaml
rename to provider/resources_yaml/97_nodered.yaml
diff --git a/universal_rebuild/resources_yaml/98_http.yaml b/provider/resources_yaml/98_http.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/98_http.yaml
rename to provider/resources_yaml/98_http.yaml
diff --git a/universal_rebuild/resources_yaml/99_gitea.yaml b/provider/resources_yaml/99_gitea.yaml
similarity index 100%
rename from universal_rebuild/resources_yaml/99_gitea.yaml
rename to provider/resources_yaml/99_gitea.yaml
diff --git a/universal_rebuild/resources_yaml/embed.go b/provider/resources_yaml/embed.go
similarity index 100%
rename from universal_rebuild/resources_yaml/embed.go
rename to provider/resources_yaml/embed.go