diff --git a/TOOLS/docs-generator/internal/writers/writers.go b/TOOLS/docs-generator/internal/writers/writers.go
index f4c52a7..4e8785e 100644
--- a/TOOLS/docs-generator/internal/writers/writers.go
+++ b/TOOLS/docs-generator/internal/writers/writers.go
@@ -46,6 +46,21 @@ func ResourceDocs(docsDir string, spec types.ServiceSpec, version string, apiEnd
}
}
+// 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
+}
+
// WriteFile пишет контент в файл.
func WriteFile(path, content string) {
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
@@ -72,21 +87,6 @@ func IndexMD(docsDir string, specs []types.ServiceSpec) {
}
}
-// 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 {
@@ -187,10 +187,15 @@ func htmlToMarkdown(s string) string {
func buildExamplePage(spec types.ServiceSpec, nav, version string, apiEndpoint string, providerSource 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(fmt.Sprintf("## Minimal example — only required parameters\n\n"))
+ b.WriteString("```hcl\n")
+ b.WriteString(minimalExampleBlock(spec, version, apiEndpoint, providerSource))
+ b.WriteString("```\n\n")
+ b.WriteString("Full example (all parameters, including defaults)
\n\n")
b.WriteString("```hcl\n")
b.WriteString(exampleBlock(spec, version, apiEndpoint, providerSource))
b.WriteString("```\n\n")
+ b.WriteString(" \n\n")
b.WriteString("## Outputs usage\n\n")
b.WriteString("```hcl\n")
b.WriteString("# Выходные параметры можно использовать как\n")
@@ -242,6 +247,40 @@ func exampleBlock(spec types.ServiceSpec, version string, apiEndpoint string, pr
return b.String()
}
+// minimalExampleBlock — только required параметры без default (минимальный работающий пример).
+func minimalExampleBlock(spec types.ServiceSpec, version string, apiEndpoint string, providerSource string) string {
+ createParams := FindParams(spec.Operations, "create")
+ var minimalParams []types.ParamSpec
+ for _, p := range createParams {
+ if p.Required && !hasDefault(p.Default) {
+ minimalParams = append(minimalParams, p)
+ }
+ }
+
+ var b strings.Builder
+ b.WriteString("terraform {\n")
+ b.WriteString(" required_providers {\n")
+ b.WriteString(" nubes = {\n")
+ b.WriteString(fmt.Sprintf(" source = \"%s\"\n", providerSource))
+ 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 = \"***\"\n")
+ b.WriteString(fmt.Sprintf(" api_endpoint = \"%s\"\n", apiEndpoint))
+ b.WriteString("}\n")
+ b.WriteString(fmt.Sprintf("resource \"nubes_%s\" \"baza\" {\n", spec.Name))
+ b.WriteString(" resource_name = \"my-resource\"\n\n")
+
+ for _, p := range minimalParams {
+ b.WriteString(formatParamOrBlock(p, " ", true))
+ }
+
+ b.WriteString("}\n")
+ return b.String()
+}
+
func buildCreateParamsPage(spec types.ServiceSpec, nav string) string {
var b strings.Builder
b.WriteString(buildHeader(spec, nav))
@@ -521,13 +560,12 @@ func renderParamTable(params []types.ParamSpec, requiredTable, noDefault bool) s
}
b.WriteString(fmt.Sprintf("
\n", tableClass))
if noDefault {
- b.WriteString("| ID | Code | Type | Description | Constraints |
\n\n")
+ b.WriteString("| Code | Type | Description | Constraints |
\n\n")
} else {
- b.WriteString("| ID | Code | Type | Default | Description | Constraints |
\n\n")
+ b.WriteString("| 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 {
@@ -547,10 +585,9 @@ func renderModifyTable(params []types.ParamSpec) string {
}
var b strings.Builder
b.WriteString("\n")
- b.WriteString("| ID | Code | Type | Description | Constraints |
\n\n")
+ b.WriteString("| 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))))
@@ -806,7 +843,7 @@ func collectConstraints(p types.ParamSpec) string {
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, ", ")))
+ parts = append(parts, fmt.Sprintf("Допустимые значения: %s", strings.Join(p.ValueList, ", ")))
}
if p.Func != "" {
parts = append(parts, fmt.Sprintf("func=%s", p.Func))
@@ -992,14 +1029,13 @@ func renderNestedParams(p types.ParamSpec) string {
}
b.WriteString(fmt.Sprintf("\n### %s\n\n", label))
b.WriteString("\n")
- b.WriteString("| ID | Code | Type | Required | Default | Description | Constraints |
\n\n")
+ b.WriteString("| Code | Type | Required | Default | Description | Constraints |
\n\n")
for _, sp := range p.SubParams {
req := "no"
if sp.Required {
req = "**yes**"
}
b.WriteString("")
- b.WriteString(fmt.Sprintf("| %s | ", escapeText(formatID(sp.ID))))
b.WriteString(fmt.Sprintf("%s | ", formatParamCode(sp.Code)))
b.WriteString(fmt.Sprintf("%s | ", formatTypeCell(sp)))
b.WriteString(fmt.Sprintf("%s | ", req))
@@ -1011,3 +1047,68 @@ func renderNestedParams(p types.ParamSpec) string {
b.WriteString("
\n")
return b.String()
}
+
+// serviceCategory возвращает категорию для сервиса.
+func serviceCategory(name string) string {
+ cats := map[string]string{
+ "postgres": "Базы данных", "redis": "Базы данных", "mongodb": "Базы данных",
+ "mariadb": "Базы данных", "clickhouse": "Базы данных",
+ "rabbitmq": "Очереди", "kafka": "Очереди",
+ "s3": "Хранилище", "s3bucket": "Хранилище", "nextcloud": "Хранилище",
+ "k8s_velero": "K8s", "k8s_sthutrval_cluster": "K8s", "k8s_openbao": "K8s",
+ "vc_mgmt_sthutrval_cluster": "K8s",
+ "vc_org": "VMware", "vc_vdc": "VMware", "vc_nsxt": "VMware",
+ "vcexternalip": "VMware", "vapp": "VMware", "vc_vm_v2": "VMware",
+ "vc_vm_v3": "VMware", "vc_vdc_group": "VMware",
+ "flask": "Приложения", "nodejs": "Приложения", "lucee": "Приложения",
+ "http": "Приложения", "gitea": "Приложения", "superset": "Приложения",
+ "pgadmin": "Приложения", "harbor": "Приложения", "akhq": "Приложения",
+ "llm_ai": "Приложения", "template": "Приложения", "dummy": "Приложения",
+ "valo_tenant": "Приложения",
+ "zones_v2": "Сеть", "dnsrecord": "Сеть",
+ }
+ if cat, ok := cats[name]; ok {
+ return cat
+ }
+ return "Другие"
+}
+
+// WriteNavFragment генерирует _nav_fragment.yml для mkdocs sidebar.
+func WriteNavFragment(docsDir string, specs []types.ServiceSpec) {
+ catMap := map[string][]types.ServiceSpec{}
+ for _, s := range specs {
+ cat := serviceCategory(s.Name)
+ catMap[cat] = append(catMap[cat], s)
+ }
+
+ var b bytes.Buffer
+ b.WriteString("# auto-generated by docs-generator — DO NOT EDIT\n")
+ b.WriteString("resources_nav:\n")
+
+ order := []string{"Базы данных", "Очереди", "Хранилище", "K8s", "VMware", "Приложения", "Сеть", "Другие"}
+ for _, cat := range order {
+ svcs := catMap[cat]
+ if len(svcs) == 0 {
+ continue
+ }
+ b.WriteString(fmt.Sprintf(" - %s:\n", cat))
+ for _, s := range svcs {
+ disp := s.ServiceDisplayName
+ if disp == "" {
+ disp = s.Name
+ }
+ b.WriteString(fmt.Sprintf(" - %s:\n", disp))
+ b.WriteString(fmt.Sprintf(" - Обзор: %s.md\n", s.Name))
+ b.WriteString(fmt.Sprintf(" - Параметры создания: %s_params_create.md\n", s.Name))
+ b.WriteString(fmt.Sprintf(" - Параметры изменения: %s_params_modify.md\n", s.Name))
+ b.WriteString(fmt.Sprintf(" - Выходные данные: %s_outputs.md\n", s.Name))
+ b.WriteString(fmt.Sprintf(" - Операции: %s_ops.md\n", s.Name))
+ b.WriteString(fmt.Sprintf(" - Пример: %s_example.md\n", s.Name))
+ }
+ }
+
+ outPath := filepath.Join(docsDir, "_nav_fragment.yml")
+ if err := os.WriteFile(outPath, b.Bytes(), 0o644); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: failed to write _nav_fragment.yml: %v\n", err)
+ }
+}
diff --git a/TOOLS/docs-generator/main.go b/TOOLS/docs-generator/main.go
index 946514f..9f44d27 100644
--- a/TOOLS/docs-generator/main.go
+++ b/TOOLS/docs-generator/main.go
@@ -75,6 +75,7 @@ func main() {
processedSpecs = append(processedSpecs, spec)
}
writers.IndexMD(docsDir, processedSpecs)
+ writers.WriteNavFragment(docsDir, processedSpecs)
}
func toSet(csv string) map[string]bool {
diff --git a/mkdocs.yml b/mkdocs.yml
index cf691d7..922859d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -24,10 +24,11 @@ theme:
primary: blue
accent: indigo
features:
- - navigation.tabs
- - navigation.tabs.sticky
- navigation.sections
- navigation.top
+ - navigation.path
+ - navigation.footer
+ - navigation.indexes
extra_css:
- 30_registry/assets/extra.css