refactor: rename universal_rebuild/ → provider/
- Rename directory - Update all 9 devops scripts - Update 4 generator source files - Rebuild all binaries
This commit is contained in:
@@ -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"`
|
||||||
|
}
|
||||||
@@ -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("<div class=\"man-content\">\n")
|
||||||
|
b.WriteString(man)
|
||||||
|
b.WriteString("\n</div>\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<div class=\"lifecycle-note\">\n")
|
||||||
|
b.WriteString("<strong>Параметры поведения (кратко)</strong><br/>\n")
|
||||||
|
b.WriteString(fmt.Sprintf("По умолчанию: `suspend_on_destroy = %s`, `adopt_existing_on_create = %s`.<br/>\n", formatBoolTitle(lifecycle.SuspendOnDestroyDefault), formatBoolTitle(lifecycle.AdoptExistingOnCreateDefault)))
|
||||||
|
b.WriteString("При `terraform destroy` или удалении ресурса из манифеста:<br/>\n")
|
||||||
|
b.WriteString("- `suspend_on_destroy=true` — инстанс переводится в `Suspend` (не удаляется).<br/>\n")
|
||||||
|
b.WriteString("- `suspend_on_destroy=false` — Terraform удаляет ресурс только из state.<br/>\n")
|
||||||
|
b.WriteString("При `apply` флаг `adopt_existing_on_create` работает как авто-`import`:<br/>\n")
|
||||||
|
b.WriteString("- `false` — если ресурс уже есть, будет ошибка.<br/>\n")
|
||||||
|
b.WriteString("- `true` — Terraform может взять существующий инстанс под управление (`running` → adopt, `suspended` → resume+adopt при совпадении параметров).<br/>\n")
|
||||||
|
b.WriteString("Важно: один инстанс должен быть только в одном state. Иначе получите конфликт управления.\n")
|
||||||
|
b.WriteString("</div>\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("<table class=\"%s\">\n", tableClass))
|
||||||
|
if noDefault {
|
||||||
|
b.WriteString("<thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
|
||||||
|
} else {
|
||||||
|
b.WriteString("<thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Default</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
|
||||||
|
}
|
||||||
|
for _, p := range params {
|
||||||
|
b.WriteString("<tr>")
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(formatID(p.ID))))
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", formatParamCode(p.Code)))
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", formatTypeCell(p)))
|
||||||
|
if !noDefault {
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", defaultCell(p.Default)))
|
||||||
|
}
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(pickTextTable(p))))
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(collectConstraints(p))))
|
||||||
|
b.WriteString("</tr>\n")
|
||||||
|
}
|
||||||
|
b.WriteString("</tbody></table>\n")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderModifyTable(params []types.ParamSpec) string {
|
||||||
|
if len(params) == 0 {
|
||||||
|
return "None.\n"
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("<table class=\"resource-table resource-table-compact\">\n")
|
||||||
|
b.WriteString("<thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead>\n<tbody>\n")
|
||||||
|
for _, p := range params {
|
||||||
|
b.WriteString("<tr>")
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(formatID(p.ID))))
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", formatParamCode(p.Code)))
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", formatTypeCell(p)))
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(pickTextTable(p))))
|
||||||
|
b.WriteString(fmt.Sprintf("<td>%s</td>", escapeText(collectConstraints(p))))
|
||||||
|
b.WriteString("</tr>\n")
|
||||||
|
}
|
||||||
|
b.WriteString("</tbody></table>\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 "<code>" + escapeText(dtype) + "</code>"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "<code>" + escapeText(s) + "</code>"
|
||||||
|
}
|
||||||
|
return "<code>" + escapeText(formatLiteral(value)) + "</code>"
|
||||||
|
}
|
||||||
|
|
||||||
|
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/>", "<br/>")
|
||||||
|
text = strings.ReplaceAll(text, "<br />", "<br/>")
|
||||||
|
text = strings.ReplaceAll(text, "<br>", "<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 "<strong><code class=\"code-no-wrap\">" + code + "</code></strong>"
|
||||||
|
}
|
||||||
|
out := []string{}
|
||||||
|
prev := rune(0)
|
||||||
|
for _, ch := range code {
|
||||||
|
if ch == '_' {
|
||||||
|
out = append(out, "_<wbr>")
|
||||||
|
prev = ch
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if prev != 0 && (unicodeIsLower(prev) || unicodeIsDigit(prev)) && unicodeIsUpper(ch) {
|
||||||
|
out = append(out, "<wbr>")
|
||||||
|
}
|
||||||
|
out = append(out, string(ch))
|
||||||
|
prev = ch
|
||||||
|
}
|
||||||
|
return "<strong><code class=\"code-wrap\">" + strings.Join(out, "") + "</code></strong>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
+22
-948
File diff suppressed because it is too large
Load Diff
@@ -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: <repo>/resources_ops_yaml)
|
|
||||||
// - NUBES_OPS_DOCS_DIR (default: <repo>/../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"`
|
|
||||||
}
|
|
||||||
@@ -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, "|", "\\|")
|
||||||
|
}
|
||||||
@@ -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"`
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"resource-generator/types"
|
"resource-generator/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToCamel конвертирует snake_case → CamelCase.
|
// ToCamel конвертирует snake_case → CamelCase.
|
||||||
+2
-2
@@ -11,8 +11,8 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"resource-generator/params"
|
"resource-generator/internal/params"
|
||||||
"resource-generator/types"
|
"resource-generator/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadSpecs загружает все YAML-спеки из директории и строит GenResource/GenSubresource/GenAction.
|
// LoadSpecs загружает все YAML-спеки из директории и строит GenResource/GenSubresource/GenAction.
|
||||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"resource-generator/types"
|
"resource-generator/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Merge объединяет несколько наборов параметров, убирая дубликаты по Code.
|
// Merge объединяет несколько наборов параметров, убирая дубликаты по Code.
|
||||||
+3
-3
@@ -8,9 +8,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"resource-generator/helpers"
|
"resource-generator/internal/helpers"
|
||||||
"resource-generator/templates"
|
"resource-generator/internal/templates"
|
||||||
"resource-generator/types"
|
"resource-generator/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteInstanceResource генерирует nubes_{service} (CRUD инстанса).
|
// WriteInstanceResource генерирует nubes_{service} (CRUD инстанса).
|
||||||
@@ -20,8 +20,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"resource-generator/loader"
|
"resource-generator/internal/loader"
|
||||||
"resource-generator/writers"
|
"resource-generator/internal/writers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
+2
-2
@@ -10,8 +10,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"yaml-generator/normalize"
|
"yaml-generator/internal/normalize"
|
||||||
"yaml-generator/types"
|
"yaml-generator/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client — HTTP-клиент для Nubes API.
|
// Client — HTTP-клиент для Nubes API.
|
||||||
+4
-4
@@ -9,7 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"yaml-generator/normalize"
|
"yaml-generator/internal/normalize"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config — конфигурация генератора.
|
// Config — конфигурация генератора.
|
||||||
@@ -170,7 +170,7 @@ func ReadServicesList(path string) ([]ServiceRef, error) {
|
|||||||
return services, nil
|
return services, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindRepoRoot ищет корень репозитория по директории universal_rebuild.
|
// FindRepoRoot ищет корень репозитория по директории provider.
|
||||||
func FindRepoRoot() (string, error) {
|
func FindRepoRoot() (string, error) {
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -178,7 +178,7 @@ func FindRepoRoot() (string, error) {
|
|||||||
}
|
}
|
||||||
current := wd
|
current := wd
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
candidate := filepath.Join(current, "universal_rebuild")
|
candidate := filepath.Join(current, "provider")
|
||||||
info, err := os.Stat(candidate)
|
info, err := os.Stat(candidate)
|
||||||
if err == nil && info.IsDir() {
|
if err == nil && info.IsDir() {
|
||||||
return candidate, nil
|
return candidate, nil
|
||||||
@@ -189,5 +189,5 @@ func FindRepoRoot() (string, error) {
|
|||||||
}
|
}
|
||||||
current = parent
|
current = parent
|
||||||
}
|
}
|
||||||
return "", errors.New("failed to locate universal_rebuild repo root")
|
return "", errors.New("failed to locate provider repo root")
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"yaml-generator/types"
|
"yaml-generator/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultOutputParams возвращает стандартный набор выходных параметров.
|
// DefaultOutputParams возвращает стандартный набор выходных параметров.
|
||||||
@@ -12,11 +12,11 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"yaml-generator/client"
|
"yaml-generator/internal/client"
|
||||||
"yaml-generator/config"
|
"yaml-generator/internal/config"
|
||||||
"yaml-generator/normalize"
|
"yaml-generator/internal/normalize"
|
||||||
"yaml-generator/spec"
|
"yaml-generator/internal/spec"
|
||||||
"yaml-generator/types"
|
"yaml-generator/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
|
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
|
||||||
PROVIDER_DIR="${ROOT_DIR}/universal_rebuild"
|
PROVIDER_DIR="${ROOT_DIR}/provider"
|
||||||
PROFILE_DIR="${PROFILE_DIR:-}"
|
PROFILE_DIR="${PROFILE_DIR:-}"
|
||||||
|
|
||||||
if [[ "${1:-}" == "--profile" ]]; then
|
if [[ "${1:-}" == "--profile" ]]; then
|
||||||
@@ -64,7 +64,7 @@ fi
|
|||||||
# Назначение:
|
# Назначение:
|
||||||
# - Берет список сервисов из services_list.txt.
|
# - Берет список сервисов из services_list.txt.
|
||||||
# - Для каждого сервиса запрашивает единый spec через API и пишет YAML в
|
# - Для каждого сервиса запрашивает единый spec через API и пишет YAML в
|
||||||
# ${ROOT_DIR}/universal_rebuild/resources_yaml.
|
# ${ROOT_DIR}/provider/resources_yaml.
|
||||||
# - Формат имени файла: ID_имя.yaml (например, 13_s3bucket.yaml).
|
# - Формат имени файла: ID_имя.yaml (например, 13_s3bucket.yaml).
|
||||||
#
|
#
|
||||||
# Источники данных:
|
# Источники данных:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && 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"
|
DOCS_DIR="${ROOT_DIR}/docs/30_registry/resources"
|
||||||
|
|
||||||
cd "$PROVIDER_DIR"
|
cd "$PROVIDER_DIR"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && 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"
|
RESOURCES_YAML_DIR="${PROVIDER_DIR}/resources_yaml"
|
||||||
DOCS_DIR="${ROOT_DIR}/docs/30_registry/resources"
|
DOCS_DIR="${ROOT_DIR}/docs/30_registry/resources"
|
||||||
SERVICES_LIST_PATH="${ROOT_DIR}/devops/config/services_list.txt"
|
SERVICES_LIST_PATH="${ROOT_DIR}/devops/config/services_list.txt"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && 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"
|
RESOURCES_YAML_DIR="${PROVIDER_DIR}/resources_yaml"
|
||||||
DOCS_DIR="${ROOT_DIR}/docs/30_registry/resources"
|
DOCS_DIR="${ROOT_DIR}/docs/30_registry/resources"
|
||||||
SERVICES_LIST_PATH="${ROOT_DIR}/devops/config/services_list.txt"
|
SERVICES_LIST_PATH="${ROOT_DIR}/devops/config/services_list.txt"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
|
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
|
||||||
PROVIDER_DIR="${ROOT_DIR}/universal_rebuild"
|
PROVIDER_DIR="${ROOT_DIR}/provider"
|
||||||
PROFILE_DIR="${PROFILE_DIR:-}"
|
PROFILE_DIR="${PROFILE_DIR:-}"
|
||||||
|
|
||||||
resolve_root_path() {
|
resolve_root_path() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && 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"
|
BUILD_SCRIPT="${ROOT_DIR}/devops/build-provider.sh"
|
||||||
PROFILE_DIR="${PROFILE_DIR:-}"
|
PROFILE_DIR="${PROFILE_DIR:-}"
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ validate_registry_completeness() {
|
|||||||
TMP_PROVIDER_DIR="$(mktemp -d)"
|
TMP_PROVIDER_DIR="$(mktemp -d)"
|
||||||
trap 'rm -rf "$TMP_PROVIDER_DIR"' EXIT
|
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"
|
mkdir -p "$TMP_PROVIDER_DIR/resources_yaml"
|
||||||
find "$TMP_PROVIDER_DIR/resources_yaml" -maxdepth 1 -type f -name '*.yaml' -delete
|
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/"
|
cp -a "${PROFILE_RESOURCES_YAML_DIR}"/*.yaml "$TMP_PROVIDER_DIR/resources_yaml/"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && 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:-}"
|
PROFILE_DIR="${PROFILE_DIR:-}"
|
||||||
|
|
||||||
normalize_api_endpoint() {
|
normalize_api_endpoint() {
|
||||||
@@ -74,7 +74,7 @@ TMP_DOCS_DIR=""
|
|||||||
|
|
||||||
if [[ -n "$PROFILE_DIR" ]]; then
|
if [[ -n "$PROFILE_DIR" ]]; then
|
||||||
# ⛔ NEVER merge with docs/ — ONLY generated docs from docs_gen/<stand>/
|
# ⛔ NEVER merge with docs/ — ONLY generated docs from docs_gen/<stand>/
|
||||||
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")"
|
DOCS_GEN_DIR="$(resolve_root_path "$DOCS_GEN_DIR")"
|
||||||
if [[ -d "$DOCS_GEN_DIR" ]]; then
|
if [[ -d "$DOCS_GEN_DIR" ]]; then
|
||||||
export MKDOCS_DOCS_DIR="$DOCS_GEN_DIR"
|
export MKDOCS_DOCS_DIR="$DOCS_GEN_DIR"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && 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
|
if [[ ! -d "${PROVIDER_DIR}/resources_yaml" ]]; then
|
||||||
echo "Error: resources_yaml not found: ${PROVIDER_DIR}/resources_yaml" >&2
|
echo "Error: resources_yaml not found: ${PROVIDER_DIR}/resources_yaml" >&2
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && 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}"
|
BUILD_DIR="${BUILD_DIR:-${ROOT_DIR}/artifacts/provider_build}"
|
||||||
GPG_KEY_FILE="${GPG_KEY_FILE:-${ROOT_DIR}/secrets/private_key.asc}"
|
GPG_KEY_FILE="${GPG_KEY_FILE:-${ROOT_DIR}/secrets/private_key.asc}"
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user