refactor(gen_v2): split into 7 Go packages (types, templates, helpers, params, loader, writers, main)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+35
-19
@@ -1,5 +1,5 @@
|
|||||||
// helpers — конвертация имён, форматирование, plan-модификаторы.
|
// Package helpers — конвертация имён, форматирование, plan-модификаторы.
|
||||||
package main
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -7,9 +7,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func toCamel(s string) string {
|
// ToCamel конвертирует snake_case → CamelCase.
|
||||||
|
func ToCamel(s string) string {
|
||||||
parts := strings.FieldsFunc(s, func(r rune) bool { return r == '_' || r == '-' })
|
parts := strings.FieldsFunc(s, func(r rune) bool { return r == '_' || r == '-' })
|
||||||
for i, p := range parts {
|
for i, p := range parts {
|
||||||
if len(p) == 0 {
|
if len(p) == 0 {
|
||||||
@@ -18,10 +21,11 @@ func toCamel(s string) string {
|
|||||||
parts[i] = strings.ToUpper(p[:1]) + p[1:]
|
parts[i] = strings.ToUpper(p[:1]) + p[1:]
|
||||||
}
|
}
|
||||||
out := strings.Join(parts, "")
|
out := strings.Join(parts, "")
|
||||||
return ensureGoIdent(out)
|
return EnsureGoIdent(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSnake(s string) string {
|
// ToSnake конвертирует CamelCase → snake_case.
|
||||||
|
func ToSnake(s string) string {
|
||||||
var out []rune
|
var out []rune
|
||||||
for i, r := range s {
|
for i, r := range s {
|
||||||
if i > 0 && r >= 'A' && r <= 'Z' {
|
if i > 0 && r >= 'A' && r <= 'Z' {
|
||||||
@@ -32,7 +36,8 @@ func toSnake(s string) string {
|
|||||||
return string(out)
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func paramType(p Param) string {
|
// ParamType возвращает Go-тип для параметра (types.Bool/Int64/String).
|
||||||
|
func ParamType(p types.Param) string {
|
||||||
switch strings.ToLower(p.Type) {
|
switch strings.ToLower(p.Type) {
|
||||||
case "bool":
|
case "bool":
|
||||||
return "types.Bool"
|
return "types.Bool"
|
||||||
@@ -43,7 +48,8 @@ func paramType(p Param) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func paramDefaultExpr(p Param) string {
|
// ParamDefaultExpr возвращает выражение значения по умолчанию.
|
||||||
|
func ParamDefaultExpr(p types.Param) string {
|
||||||
if p.Default == "" {
|
if p.Default == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -58,7 +64,8 @@ func paramDefaultExpr(p Param) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func paramFormat(p Param, varName string) string {
|
// ParamFormat возвращает вызов Format* для форматирования значения параметра.
|
||||||
|
func ParamFormat(p types.Param, varName string) string {
|
||||||
switch strings.ToLower(p.Type) {
|
switch strings.ToLower(p.Type) {
|
||||||
case "bool":
|
case "bool":
|
||||||
return fmt.Sprintf("resources_core.FormatBool(%s)", varName)
|
return fmt.Sprintf("resources_core.FormatBool(%s)", varName)
|
||||||
@@ -69,7 +76,8 @@ func paramFormat(p Param, varName string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func paramParse(p Param) string {
|
// ParamParse возвращает имя Parse*-функции для параметра.
|
||||||
|
func ParamParse(p types.Param) string {
|
||||||
switch strings.ToLower(p.Type) {
|
switch strings.ToLower(p.Type) {
|
||||||
case "bool":
|
case "bool":
|
||||||
return "resources_core.ParseBool"
|
return "resources_core.ParseBool"
|
||||||
@@ -80,7 +88,8 @@ func paramParse(p Param) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func paramDescription(p Param) string {
|
// ParamDescription формирует MarkdownDescription для параметра.
|
||||||
|
func ParamDescription(p types.Param) string {
|
||||||
var desc string
|
var desc string
|
||||||
if strings.TrimSpace(p.Descr) != "" {
|
if strings.TrimSpace(p.Descr) != "" {
|
||||||
desc = strings.TrimSpace(p.Descr)
|
desc = strings.TrimSpace(p.Descr)
|
||||||
@@ -109,7 +118,8 @@ func paramDescription(p Param) string {
|
|||||||
return fmt.Sprintf("%q", desc)
|
return fmt.Sprintf("%q", desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputParamType(p OutputParam) string {
|
// OutputParamType возвращает Go-тип для выходного параметра (types.Map/List/String).
|
||||||
|
func OutputParamType(p types.OutputParam) string {
|
||||||
switch strings.ToLower(p.Type) {
|
switch strings.ToLower(p.Type) {
|
||||||
case "map":
|
case "map":
|
||||||
return "types.Map"
|
return "types.Map"
|
||||||
@@ -120,19 +130,23 @@ func outputParamType(p OutputParam) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputParamIsMap(p OutputParam) bool {
|
// OutputParamIsMap возвращает true, если выходной параметр — map.
|
||||||
|
func OutputParamIsMap(p types.OutputParam) bool {
|
||||||
return strings.EqualFold(strings.TrimSpace(p.Type), "map")
|
return strings.EqualFold(strings.TrimSpace(p.Type), "map")
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputParamIsList(p OutputParam) bool {
|
// OutputParamIsList возвращает true, если выходной параметр — list.
|
||||||
|
func OutputParamIsList(p types.OutputParam) bool {
|
||||||
return strings.EqualFold(strings.TrimSpace(p.Type), "list")
|
return strings.EqualFold(strings.TrimSpace(p.Type), "list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputParamSensitive(p OutputParam) bool {
|
// OutputParamSensitive возвращает true, если параметр секретный.
|
||||||
|
func OutputParamSensitive(p types.OutputParam) bool {
|
||||||
return p.Sensitive
|
return p.Sensitive
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureGoIdent(s string) string {
|
// EnsureGoIdent проверяет, что строка начинается с буквы или _.
|
||||||
|
func EnsureGoIdent(s string) string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return "R"
|
return "R"
|
||||||
}
|
}
|
||||||
@@ -143,8 +157,8 @@ func ensureGoIdent(s string) string {
|
|||||||
return "R" + s
|
return "R" + s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FormatSourceOrWarn форматирует Go-код. При ошибке пишет warning в stderr.
|
||||||
func formatSourceOrWarn(filePath string, src []byte) []byte {
|
func FormatSourceOrWarn(filePath string, src []byte) []byte {
|
||||||
formatted, err := format.Source(src)
|
formatted, err := format.Source(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARNING: gofmt failed for %s: %v — writing unformatted code\n", filePath, err)
|
fmt.Fprintf(os.Stderr, "WARNING: gofmt failed for %s: %v — writing unformatted code\n", filePath, err)
|
||||||
@@ -153,7 +167,8 @@ func formatSourceOrWarn(filePath string, src []byte) []byte {
|
|||||||
return formatted
|
return formatted
|
||||||
}
|
}
|
||||||
|
|
||||||
func planModifierType(p Param) string {
|
// PlanModifierType возвращает суффикс типа план-модификатора (Bool/Int64/String).
|
||||||
|
func PlanModifierType(p types.Param) string {
|
||||||
switch strings.ToLower(strings.TrimSpace(p.Type)) {
|
switch strings.ToLower(strings.TrimSpace(p.Type)) {
|
||||||
case "bool":
|
case "bool":
|
||||||
return "Bool"
|
return "Bool"
|
||||||
@@ -164,7 +179,8 @@ func planModifierType(p Param) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func planModifierExpr(p Param) string {
|
// PlanModifierExpr возвращает выражение план-модификатора RequiresReplace.
|
||||||
|
func PlanModifierExpr(p types.Param) string {
|
||||||
switch strings.ToLower(strings.TrimSpace(p.Type)) {
|
switch strings.ToLower(strings.TrimSpace(p.Type)) {
|
||||||
case "bool":
|
case "bool":
|
||||||
return "boolplanmodifier.RequiresReplace()"
|
return "boolplanmodifier.RequiresReplace()"
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
// loader — загрузка YAML-спеков и построение ресурсов.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func loadSpecs(dir string) ([]GenResource, []GenSubresource, []GenAction, error) {
|
|
||||||
var services []GenResource
|
|
||||||
var subs []GenSubresource
|
|
||||||
var actions []GenAction
|
|
||||||
domainServiceIDsSet := map[int]struct{}{}
|
|
||||||
walkErr := filepath.WalkDir(dir, 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 ServiceSpec
|
|
||||||
if err := yaml.Unmarshal(b, &spec); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// P1.4: fail-fast на неизвестных kind'ах и отсутствующих обязательных полях.
|
|
||||||
if err := validateSpec(path, &spec); err != nil {
|
|
||||||
return fmt.Errorf("%s: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
createParams := []Param{}
|
|
||||||
modifyParams := []Param{}
|
|
||||||
supportsSuspendDestroy := false
|
|
||||||
for _, op := range spec.Operations {
|
|
||||||
if op.Kind != "instance" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch op.Action {
|
|
||||||
case "create":
|
|
||||||
createParams = convertParams(op.Params)
|
|
||||||
case "modify":
|
|
||||||
modifyParams = convertParams(op.Params)
|
|
||||||
case "suspend":
|
|
||||||
supportsSuspendDestroy = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
schemaParams := mergeParams(createParams, modifyParams)
|
|
||||||
createParams = alignParamTypes(createParams, schemaParams)
|
|
||||||
modifyParams = alignParamTypes(modifyParams, schemaParams)
|
|
||||||
createOnly := computeCreateOnly(createParams, modifyParams)
|
|
||||||
schemaParams = markCreateOnly(schemaParams, createOnly)
|
|
||||||
hasDomainParam := false
|
|
||||||
for _, param := range schemaParams {
|
|
||||||
if strings.EqualFold(strings.TrimSpace(param.Code), "domain") {
|
|
||||||
hasDomainParam = true
|
|
||||||
domainServiceIDsSet[spec.ServiceID] = struct{}{}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adoptExistingOnCreate := false
|
|
||||||
|
|
||||||
suspendOnDestroy := true
|
|
||||||
if spec.Lifecycle.SuspendOnDestroyDefault != nil {
|
|
||||||
suspendOnDestroy = *spec.Lifecycle.SuspendOnDestroyDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
gr := GenResource{
|
|
||||||
Name: spec.Name,
|
|
||||||
ServiceID: spec.ServiceID,
|
|
||||||
CreateParams: createParams,
|
|
||||||
ModifyParams: modifyParams,
|
|
||||||
SchemaParams: schemaParams,
|
|
||||||
CreateOnlyParams: filterCreateOnly(schemaParams),
|
|
||||||
CreateOnlyRequiredParams: filterCreateOnlyRequired(schemaParams),
|
|
||||||
OutputParams: normalizeOutputParams(spec.Outputs.Params),
|
|
||||||
HasRefSvcParams: hasRefSvcParams(schemaParams),
|
|
||||||
SupportsSuspendDestroy: supportsSuspendDestroy,
|
|
||||||
SuspendOnDestroy: suspendOnDestroy,
|
|
||||||
AdoptExistingOnCreate: adoptExistingOnCreate,
|
|
||||||
HasDomainParam: hasDomainParam,
|
|
||||||
}
|
|
||||||
analyzeParams(&gr.UsesBool, &gr.UsesInt64, &gr.UsesString, &gr.HasDefaults, &gr.NeedsBoolDefault, &gr.NeedsInt64Default, &gr.NeedsStringDefault, gr.SchemaParams)
|
|
||||||
gr.NeedsJsonPlanMod = analyzeJsonPlanMod(gr.SchemaParams)
|
|
||||||
// NeedsStringsImport = true если:
|
|
||||||
// 1) есть ref_svc params (RefSvcId > 0, != 12) — шаблон генерирует restore-casing блоки с strings.EqualFold
|
|
||||||
// 2) есть строковые ModifyParams — шаблон генерирует EqualFold в hasServiceParamChanges (Update)
|
|
||||||
// 3) есть строковые CreateOnlyParams — шаблон генерирует EqualFold в create-only validation (ModifyPlan)
|
|
||||||
// RefSvcId=12 (S3 Object Storage) НЕ генерирует restore-блок — для S3 UUID обязателен точный формат
|
|
||||||
gr.NeedsStringsImport = hasRestoreCasingParams(gr.SchemaParams) || analyzeNeedsStrings(gr.ModifyParams) || analyzeNeedsStrings(gr.CreateOnlyRequiredParams) || analyzeNeedsStrings(gr.CreateOnlyParams)
|
|
||||||
|
|
||||||
// --- Action processing (ДО append, чтобы HasRedeploy попал в слайс) ---
|
|
||||||
// Правила из ARCHITECTURE.md:
|
|
||||||
// - redeploy → встроить как git_revision в основной ресурс
|
|
||||||
// - restart, recovery, reconcile → исключить (ручные операции, только UI)
|
|
||||||
// - всё остальное → стандартный action-ресурс (на будущее, если появятся новые)
|
|
||||||
for _, op := range spec.Operations {
|
|
||||||
if op.Kind != "action" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch op.Action {
|
|
||||||
case "redeploy":
|
|
||||||
gr.HasRedeploy = true
|
|
||||||
case "restart", "recovery", "reconcile":
|
|
||||||
// Исключены
|
|
||||||
default:
|
|
||||||
act := GenAction{
|
|
||||||
ServiceName: spec.Name,
|
|
||||||
ServiceID: spec.ServiceID,
|
|
||||||
ActionName: op.Action,
|
|
||||||
OperationName: op.Name,
|
|
||||||
Params: convertParams(op.Params),
|
|
||||||
}
|
|
||||||
act.SchemaParams = act.Params
|
|
||||||
analyzeParams(&act.UsesBool, &act.UsesInt64, &act.UsesString, &act.HasDefaults, &act.NeedsBoolDefault, &act.NeedsInt64Default, &act.NeedsStringDefault, act.SchemaParams)
|
|
||||||
act.NeedsJsonPlanMod = analyzeJsonPlanMod(act.SchemaParams)
|
|
||||||
act.NeedsStringsImport = analyzeNeedsStrings(act.SchemaParams)
|
|
||||||
actions = append(actions, act)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
services = append(services, gr)
|
|
||||||
|
|
||||||
srByName := map[string]*GenSubresource{}
|
|
||||||
for _, op := range spec.Operations {
|
|
||||||
if op.Kind != "subresource" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := strings.TrimSpace(op.Subresource)
|
|
||||||
if name == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sr := srByName[name]
|
|
||||||
if sr == nil {
|
|
||||||
sr = &GenSubresource{ServiceName: spec.Name, ServiceID: spec.ServiceID, SubName: name}
|
|
||||||
srByName[name] = sr
|
|
||||||
}
|
|
||||||
switch op.Action {
|
|
||||||
case "create":
|
|
||||||
sr.CreateOpName = op.Name
|
|
||||||
sr.CreateParams = convertParams(op.Params)
|
|
||||||
case "modify":
|
|
||||||
sr.ModifyOpName = op.Name
|
|
||||||
sr.ModifyParams = convertParams(op.Params)
|
|
||||||
case "delete":
|
|
||||||
sr.DeleteOpName = op.Name
|
|
||||||
sr.DeleteParams = convertParams(op.Params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, sr := range srByName {
|
|
||||||
sr.SchemaParams = mergeParams(sr.CreateParams, sr.ModifyParams, sr.DeleteParams)
|
|
||||||
sr.CreateParams = alignParamTypes(sr.CreateParams, sr.SchemaParams)
|
|
||||||
sr.ModifyParams = alignParamTypes(sr.ModifyParams, sr.SchemaParams)
|
|
||||||
sr.DeleteParams = alignParamTypes(sr.DeleteParams, sr.SchemaParams)
|
|
||||||
createOnly := computeCreateOnly(sr.CreateParams, sr.ModifyParams)
|
|
||||||
sr.SchemaParams = markCreateOnly(sr.SchemaParams, createOnly)
|
|
||||||
forceNewCodes := buildSubresourceForceNewCodes(sr, createOnly)
|
|
||||||
sr.SchemaParams = markForceNew(sr.SchemaParams, forceNewCodes)
|
|
||||||
sr.IdentityParams = filterForceNew(sr.SchemaParams)
|
|
||||||
if len(sr.IdentityParams) == 0 {
|
|
||||||
sr.IdentityParams = sr.SchemaParams
|
|
||||||
}
|
|
||||||
sr.HasRefSvcParams = hasRefSvcParams(sr.SchemaParams)
|
|
||||||
analyzeParams(&sr.UsesBool, &sr.UsesInt64, &sr.UsesString, &sr.HasDefaults, &sr.NeedsBoolDefault, &sr.NeedsInt64Default, &sr.NeedsStringDefault, sr.SchemaParams)
|
|
||||||
analyzePlanModifiers(&sr.NeedsBoolPlanMod, &sr.NeedsInt64PlanMod, &sr.NeedsStringPlanMod, sr.SchemaParams)
|
|
||||||
sr.NeedsJsonPlanMod = analyzeJsonPlanMod(sr.SchemaParams)
|
|
||||||
sr.NeedsStringsImport = analyzeNeedsStrings(sr.ModifyParams)
|
|
||||||
subs = append(subs, *sr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if walkErr != nil {
|
|
||||||
return nil, nil, nil, walkErr
|
|
||||||
}
|
|
||||||
|
|
||||||
domainServiceIDs := make([]int, 0, len(domainServiceIDsSet))
|
|
||||||
for serviceID := range domainServiceIDsSet {
|
|
||||||
domainServiceIDs = append(domainServiceIDs, serviceID)
|
|
||||||
}
|
|
||||||
sort.Ints(domainServiceIDs)
|
|
||||||
for idx := range services {
|
|
||||||
if len(domainServiceIDs) == 0 {
|
|
||||||
services[idx].DomainServiceIDs = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
services[idx].DomainServiceIDs = append([]int(nil), domainServiceIDs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name })
|
|
||||||
sort.Slice(subs, func(i, j int) bool {
|
|
||||||
if subs[i].ServiceName == subs[j].ServiceName {
|
|
||||||
return subs[i].SubName < subs[j].SubName
|
|
||||||
}
|
|
||||||
return subs[i].ServiceName < subs[j].ServiceName
|
|
||||||
})
|
|
||||||
sort.Slice(actions, func(i, j int) bool {
|
|
||||||
if actions[i].ServiceName == actions[j].ServiceName {
|
|
||||||
return actions[i].ActionName < actions[j].ActionName
|
|
||||||
}
|
|
||||||
return actions[i].ServiceName < actions[j].ServiceName
|
|
||||||
})
|
|
||||||
|
|
||||||
return services, subs, actions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertParams(params []ParamSpec) []Param {
|
|
||||||
out := make([]Param, 0, len(params))
|
|
||||||
for _, p := range params {
|
|
||||||
typeName := normalizeParamType(p.DataType)
|
|
||||||
defVal := normalizeDefault(p.Default)
|
|
||||||
ref := 0
|
|
||||||
if p.RefSvcId != nil {
|
|
||||||
ref = *p.RefSvcId
|
|
||||||
}
|
|
||||||
out = append(out, Param{
|
|
||||||
ID: p.ID,
|
|
||||||
Code: p.Code,
|
|
||||||
Type: typeName,
|
|
||||||
Required: p.Required,
|
|
||||||
Default: defVal,
|
|
||||||
RefSvcId: ref,
|
|
||||||
Descr: strings.TrimSpace(p.Descr),
|
|
||||||
Man: strings.TrimSpace(p.Man),
|
|
||||||
Sensitive: p.Sensitive,
|
|
||||||
// IsJson: флаг для добавления JsonNormalize план-модификатора в схему.
|
|
||||||
IsJson: strings.EqualFold(strings.TrimSpace(p.DataType), "json"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeParamType(value string) string {
|
|
||||||
v := strings.ToLower(strings.TrimSpace(value))
|
|
||||||
if strings.Contains(v, "bool") {
|
|
||||||
return "bool"
|
|
||||||
}
|
|
||||||
if strings.Contains(v, "int") || strings.Contains(v, "number") {
|
|
||||||
return "int64"
|
|
||||||
}
|
|
||||||
return "string"
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeDefault(value interface{}) string {
|
|
||||||
if value == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
switch t := value.(type) {
|
|
||||||
case string:
|
|
||||||
return strings.TrimSpace(t)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%v", t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeOutputParams(params []OutputParam) []OutputParam {
|
|
||||||
if len(params) == 0 {
|
|
||||||
return []OutputParam{}
|
|
||||||
}
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasRefSvcParams(params []Param) bool {
|
|
||||||
for _, p := range params {
|
|
||||||
if p.RefSvcId > 0 && strings.EqualFold(strings.TrimSpace(p.Type), "string") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
// Package loader — загрузка YAML-спеков и построение ресурсов.
|
||||||
|
package loader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/params"
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadSpecs загружает все YAML-спеки из директории и строит GenResource/GenSubresource/GenAction.
|
||||||
|
func LoadSpecs(dir string) ([]types.GenResource, []types.GenSubresource, []types.GenAction, error) {
|
||||||
|
var services []types.GenResource
|
||||||
|
var subs []types.GenSubresource
|
||||||
|
var actions []types.GenAction
|
||||||
|
domainServiceIDsSet := map[int]struct{}{}
|
||||||
|
walkErr := filepath.WalkDir(dir, 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.ServiceSpec
|
||||||
|
if err := yaml.Unmarshal(b, &spec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// P1.4: fail-fast на неизвестных kind'ах и отсутствующих обязательных полях.
|
||||||
|
if err := ValidateSpec(path, &spec); err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createParams := []types.Param{}
|
||||||
|
modifyParams := []types.Param{}
|
||||||
|
supportsSuspendDestroy := false
|
||||||
|
for _, op := range spec.Operations {
|
||||||
|
if op.Kind != "instance" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch op.Action {
|
||||||
|
case "create":
|
||||||
|
createParams = ConvertParams(op.Params)
|
||||||
|
case "modify":
|
||||||
|
modifyParams = ConvertParams(op.Params)
|
||||||
|
case "suspend":
|
||||||
|
supportsSuspendDestroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schemaParams := params.Merge(createParams, modifyParams)
|
||||||
|
createParams = params.AlignParamTypes(createParams, schemaParams)
|
||||||
|
modifyParams = params.AlignParamTypes(modifyParams, schemaParams)
|
||||||
|
createOnly := params.ComputeCreateOnly(createParams, modifyParams)
|
||||||
|
schemaParams = params.MarkCreateOnly(schemaParams, createOnly)
|
||||||
|
hasDomainParam := false
|
||||||
|
for _, param := range schemaParams {
|
||||||
|
if strings.EqualFold(strings.TrimSpace(param.Code), "domain") {
|
||||||
|
hasDomainParam = true
|
||||||
|
domainServiceIDsSet[spec.ServiceID] = struct{}{}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adoptExistingOnCreate := false
|
||||||
|
|
||||||
|
suspendOnDestroy := true
|
||||||
|
if spec.Lifecycle.SuspendOnDestroyDefault != nil {
|
||||||
|
suspendOnDestroy = *spec.Lifecycle.SuspendOnDestroyDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
gr := types.GenResource{
|
||||||
|
Name: spec.Name,
|
||||||
|
ServiceID: spec.ServiceID,
|
||||||
|
CreateParams: createParams,
|
||||||
|
ModifyParams: modifyParams,
|
||||||
|
SchemaParams: schemaParams,
|
||||||
|
CreateOnlyParams: params.FilterCreateOnly(schemaParams),
|
||||||
|
CreateOnlyRequiredParams: params.FilterCreateOnlyRequired(schemaParams),
|
||||||
|
OutputParams: NormalizeOutputParams(spec.Outputs.Params),
|
||||||
|
HasRefSvcParams: HasRefSvcParams(schemaParams),
|
||||||
|
SupportsSuspendDestroy: supportsSuspendDestroy,
|
||||||
|
SuspendOnDestroy: suspendOnDestroy,
|
||||||
|
AdoptExistingOnCreate: adoptExistingOnCreate,
|
||||||
|
HasDomainParam: hasDomainParam,
|
||||||
|
}
|
||||||
|
params.Analyze(&gr.UsesBool, &gr.UsesInt64, &gr.UsesString, &gr.HasDefaults, &gr.NeedsBoolDefault, &gr.NeedsInt64Default, &gr.NeedsStringDefault, gr.SchemaParams)
|
||||||
|
gr.NeedsJsonPlanMod = params.AnalyzeJsonPlanMod(gr.SchemaParams)
|
||||||
|
gr.NeedsStringsImport = params.HasRestoreCasingParams(gr.SchemaParams) || params.AnalyzeNeedsStrings(gr.ModifyParams) || params.AnalyzeNeedsStrings(gr.CreateOnlyRequiredParams) || params.AnalyzeNeedsStrings(gr.CreateOnlyParams)
|
||||||
|
|
||||||
|
// --- Action processing (ДО append, чтобы HasRedeploy попал в слайс) ---
|
||||||
|
for _, op := range spec.Operations {
|
||||||
|
if op.Kind != "action" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch op.Action {
|
||||||
|
case "redeploy":
|
||||||
|
gr.HasRedeploy = true
|
||||||
|
case "restart", "recovery", "reconcile":
|
||||||
|
// Исключены
|
||||||
|
default:
|
||||||
|
act := types.GenAction{
|
||||||
|
ServiceName: spec.Name,
|
||||||
|
ServiceID: spec.ServiceID,
|
||||||
|
ActionName: op.Action,
|
||||||
|
OperationName: op.Name,
|
||||||
|
Params: ConvertParams(op.Params),
|
||||||
|
}
|
||||||
|
act.SchemaParams = act.Params
|
||||||
|
params.Analyze(&act.UsesBool, &act.UsesInt64, &act.UsesString, &act.HasDefaults, &act.NeedsBoolDefault, &act.NeedsInt64Default, &act.NeedsStringDefault, act.SchemaParams)
|
||||||
|
act.NeedsJsonPlanMod = params.AnalyzeJsonPlanMod(act.SchemaParams)
|
||||||
|
act.NeedsStringsImport = params.AnalyzeNeedsStrings(act.SchemaParams)
|
||||||
|
actions = append(actions, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
services = append(services, gr)
|
||||||
|
|
||||||
|
srByName := map[string]*types.GenSubresource{}
|
||||||
|
for _, op := range spec.Operations {
|
||||||
|
if op.Kind != "subresource" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.TrimSpace(op.Subresource)
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sr := srByName[name]
|
||||||
|
if sr == nil {
|
||||||
|
sr = &types.GenSubresource{ServiceName: spec.Name, ServiceID: spec.ServiceID, SubName: name}
|
||||||
|
srByName[name] = sr
|
||||||
|
}
|
||||||
|
switch op.Action {
|
||||||
|
case "create":
|
||||||
|
sr.CreateOpName = op.Name
|
||||||
|
sr.CreateParams = ConvertParams(op.Params)
|
||||||
|
case "modify":
|
||||||
|
sr.ModifyOpName = op.Name
|
||||||
|
sr.ModifyParams = ConvertParams(op.Params)
|
||||||
|
case "delete":
|
||||||
|
sr.DeleteOpName = op.Name
|
||||||
|
sr.DeleteParams = ConvertParams(op.Params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, sr := range srByName {
|
||||||
|
sr.SchemaParams = params.Merge(sr.CreateParams, sr.ModifyParams, sr.DeleteParams)
|
||||||
|
sr.CreateParams = params.AlignParamTypes(sr.CreateParams, sr.SchemaParams)
|
||||||
|
sr.ModifyParams = params.AlignParamTypes(sr.ModifyParams, sr.SchemaParams)
|
||||||
|
sr.DeleteParams = params.AlignParamTypes(sr.DeleteParams, sr.SchemaParams)
|
||||||
|
createOnly := params.ComputeCreateOnly(sr.CreateParams, sr.ModifyParams)
|
||||||
|
sr.SchemaParams = params.MarkCreateOnly(sr.SchemaParams, createOnly)
|
||||||
|
forceNewCodes := params.BuildSubresourceForceNewCodes(sr, createOnly)
|
||||||
|
sr.SchemaParams = params.MarkForceNew(sr.SchemaParams, forceNewCodes)
|
||||||
|
sr.IdentityParams = params.FilterForceNew(sr.SchemaParams)
|
||||||
|
if len(sr.IdentityParams) == 0 {
|
||||||
|
sr.IdentityParams = sr.SchemaParams
|
||||||
|
}
|
||||||
|
sr.HasRefSvcParams = HasRefSvcParams(sr.SchemaParams)
|
||||||
|
params.Analyze(&sr.UsesBool, &sr.UsesInt64, &sr.UsesString, &sr.HasDefaults, &sr.NeedsBoolDefault, &sr.NeedsInt64Default, &sr.NeedsStringDefault, sr.SchemaParams)
|
||||||
|
params.AnalyzePlanModifiers(&sr.NeedsBoolPlanMod, &sr.NeedsInt64PlanMod, &sr.NeedsStringPlanMod, sr.SchemaParams)
|
||||||
|
sr.NeedsJsonPlanMod = params.AnalyzeJsonPlanMod(sr.SchemaParams)
|
||||||
|
sr.NeedsStringsImport = params.AnalyzeNeedsStrings(sr.ModifyParams)
|
||||||
|
subs = append(subs, *sr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if walkErr != nil {
|
||||||
|
return nil, nil, nil, walkErr
|
||||||
|
}
|
||||||
|
|
||||||
|
domainServiceIDs := make([]int, 0, len(domainServiceIDsSet))
|
||||||
|
for serviceID := range domainServiceIDsSet {
|
||||||
|
domainServiceIDs = append(domainServiceIDs, serviceID)
|
||||||
|
}
|
||||||
|
sort.Ints(domainServiceIDs)
|
||||||
|
for idx := range services {
|
||||||
|
if len(domainServiceIDs) == 0 {
|
||||||
|
services[idx].DomainServiceIDs = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
services[idx].DomainServiceIDs = append([]int(nil), domainServiceIDs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name })
|
||||||
|
sort.Slice(subs, func(i, j int) bool {
|
||||||
|
if subs[i].ServiceName == subs[j].ServiceName {
|
||||||
|
return subs[i].SubName < subs[j].SubName
|
||||||
|
}
|
||||||
|
return subs[i].ServiceName < subs[j].ServiceName
|
||||||
|
})
|
||||||
|
sort.Slice(actions, func(i, j int) bool {
|
||||||
|
if actions[i].ServiceName == actions[j].ServiceName {
|
||||||
|
return actions[i].ActionName < actions[j].ActionName
|
||||||
|
}
|
||||||
|
return actions[i].ServiceName < actions[j].ServiceName
|
||||||
|
})
|
||||||
|
|
||||||
|
return services, subs, actions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertParams конвертирует []ParamSpec → []Param с нормализацией типов.
|
||||||
|
func ConvertParams(params []types.ParamSpec) []types.Param {
|
||||||
|
out := make([]types.Param, 0, len(params))
|
||||||
|
for _, p := range params {
|
||||||
|
typeName := NormalizeParamType(p.DataType)
|
||||||
|
defVal := NormalizeDefault(p.Default)
|
||||||
|
ref := 0
|
||||||
|
if p.RefSvcId != nil {
|
||||||
|
ref = *p.RefSvcId
|
||||||
|
}
|
||||||
|
out = append(out, types.Param{
|
||||||
|
ID: p.ID,
|
||||||
|
Code: p.Code,
|
||||||
|
Type: typeName,
|
||||||
|
Required: p.Required,
|
||||||
|
Default: defVal,
|
||||||
|
RefSvcId: ref,
|
||||||
|
Descr: strings.TrimSpace(p.Descr),
|
||||||
|
Man: strings.TrimSpace(p.Man),
|
||||||
|
Sensitive: p.Sensitive,
|
||||||
|
IsJson: strings.EqualFold(strings.TrimSpace(p.DataType), "json"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeParamType нормализует строку data_type → bool | int64 | string.
|
||||||
|
func NormalizeParamType(value string) string {
|
||||||
|
v := strings.ToLower(strings.TrimSpace(value))
|
||||||
|
if strings.Contains(v, "bool") {
|
||||||
|
return "bool"
|
||||||
|
}
|
||||||
|
if strings.Contains(v, "int") || strings.Contains(v, "number") {
|
||||||
|
return "int64"
|
||||||
|
}
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeDefault нормализует значение по умолчанию в строку.
|
||||||
|
func NormalizeDefault(value interface{}) string {
|
||||||
|
if value == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch t := value.(type) {
|
||||||
|
case string:
|
||||||
|
return strings.TrimSpace(t)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeOutputParams нормализует выходные параметры.
|
||||||
|
func NormalizeOutputParams(params []types.OutputParam) []types.OutputParam {
|
||||||
|
if len(params) == 0 {
|
||||||
|
return []types.OutputParam{}
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasRefSvcParams проверяет наличие ref_svc-параметров.
|
||||||
|
func HasRefSvcParams(params []types.Param) bool {
|
||||||
|
for _, p := range params {
|
||||||
|
if p.RefSvcId > 0 && strings.EqualFold(strings.TrimSpace(p.Type), "string") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// KnownKinds — допустимые значения Kind операций.
|
||||||
|
var KnownKinds = map[string]bool{
|
||||||
|
"instance": true,
|
||||||
|
"subresource": true,
|
||||||
|
"action": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateSpec проверяет YAML-спек на обязательные поля и неизвестные kinds.
|
||||||
|
// P1.4: fail-fast — паника при неизвестном kind вместо тихого игнорирования.
|
||||||
|
func ValidateSpec(path string, spec *types.ServiceSpec) error {
|
||||||
|
if spec.Name == "" {
|
||||||
|
return fmt.Errorf("missing required field: name")
|
||||||
|
}
|
||||||
|
if spec.ServiceID <= 0 {
|
||||||
|
return fmt.Errorf("missing required field: service_id")
|
||||||
|
}
|
||||||
|
for i, op := range spec.Operations {
|
||||||
|
if op.Kind == "" {
|
||||||
|
return fmt.Errorf("operation[%d] %q: missing required field: kind", i, op.Name)
|
||||||
|
}
|
||||||
|
if !KnownKinds[op.Kind] {
|
||||||
|
return fmt.Errorf("operation[%d] %q: unknown kind %q (valid: instance, subresource, action)", i, op.Name, op.Kind)
|
||||||
|
}
|
||||||
|
if op.Action == "" {
|
||||||
|
return fmt.Errorf("operation[%d] %q (kind=%s): missing required field: action", i, op.Name, op.Kind)
|
||||||
|
}
|
||||||
|
if op.Kind == "subresource" && op.Subresource == "" {
|
||||||
|
return fmt.Errorf("operation[%d] %q (kind=subresource): missing required field: subresource", i, op.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -16,10 +16,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/loader"
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/writers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -36,61 +38,27 @@ func main() {
|
|||||||
outDir = filepath.Join(root, "internal", "resources_gen")
|
outDir = filepath.Join(root, "internal", "resources_gen")
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceResources, subresources, actions, err := loadSpecs(resourcesDir)
|
instanceResources, subresources, actions, err := loader.LoadSpecs(resourcesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, svc := range instanceResources {
|
for _, svc := range instanceResources {
|
||||||
if err := writeInstanceResource(outDir, svc); err != nil {
|
if err := writers.WriteInstanceResource(outDir, svc); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, sr := range subresources {
|
for _, sr := range subresources {
|
||||||
if err := writeSubresource(outDir, sr); err != nil {
|
if err := writers.WriteSubresource(outDir, sr); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, act := range actions {
|
for _, act := range actions {
|
||||||
if err := writeActionResource(outDir, act); err != nil {
|
if err := writers.WriteActionResource(outDir, act); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := writeRegistry(outDir, instanceResources, subresources, actions); err != nil {
|
if err := writers.WriteRegistry(outDir, instanceResources, subresources, actions); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// knownKinds — допустимые значения Kind операций.
|
|
||||||
var knownKinds = map[string]bool{
|
|
||||||
"instance": true,
|
|
||||||
"subresource": true,
|
|
||||||
"action": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateSpec проверяет YAML-спек на обязательные поля и неизвестные kinds.
|
|
||||||
// P1.4: fail-fast — паника при неизвестном kind вместо тихого игнорирования.
|
|
||||||
func validateSpec(path string, spec *ServiceSpec) error {
|
|
||||||
if spec.Name == "" {
|
|
||||||
return fmt.Errorf("missing required field: name")
|
|
||||||
}
|
|
||||||
if spec.ServiceID <= 0 {
|
|
||||||
return fmt.Errorf("missing required field: service_id")
|
|
||||||
}
|
|
||||||
for i, op := range spec.Operations {
|
|
||||||
if op.Kind == "" {
|
|
||||||
return fmt.Errorf("operation[%d] %q: missing required field: kind", i, op.Name)
|
|
||||||
}
|
|
||||||
if !knownKinds[op.Kind] {
|
|
||||||
return fmt.Errorf("operation[%d] %q: unknown kind %q (valid: instance, subresource, action)", i, op.Name, op.Kind)
|
|
||||||
}
|
|
||||||
if op.Action == "" {
|
|
||||||
return fmt.Errorf("operation[%d] %q (kind=%s): missing required field: action", i, op.Name, op.Kind)
|
|
||||||
}
|
|
||||||
if op.Kind == "subresource" && op.Subresource == "" {
|
|
||||||
return fmt.Errorf("operation[%d] %q (kind=subresource): missing required field: subresource", i, op.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
+39
-30
@@ -1,13 +1,16 @@
|
|||||||
// params — слияние, фильтрация, анализ параметров и ForceNew.
|
// Package params — слияние, фильтрация, анализ параметров и ForceNew.
|
||||||
package main
|
package params
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mergeParams(sets ...[]Param) []Param {
|
// Merge объединяет несколько наборов параметров, убирая дубликаты по Code.
|
||||||
seen := map[string]Param{}
|
func Merge(sets ...[]types.Param) []types.Param {
|
||||||
|
seen := map[string]types.Param{}
|
||||||
for _, s := range sets {
|
for _, s := range sets {
|
||||||
for _, p := range s {
|
for _, p := range s {
|
||||||
key := strings.ToLower(strings.TrimSpace(p.Code))
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
||||||
@@ -19,7 +22,7 @@ func mergeParams(sets ...[]Param) []Param {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out := make([]Param, 0, len(seen))
|
out := make([]types.Param, 0, len(seen))
|
||||||
for _, p := range seen {
|
for _, p := range seen {
|
||||||
out = append(out, p)
|
out = append(out, p)
|
||||||
}
|
}
|
||||||
@@ -27,7 +30,8 @@ func mergeParams(sets ...[]Param) []Param {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func alignParamTypes(params []Param, schema []Param) []Param {
|
// AlignParamTypes выравнивает типы параметров по схеме.
|
||||||
|
func AlignParamTypes(params []types.Param, schema []types.Param) []types.Param {
|
||||||
if len(params) == 0 || len(schema) == 0 {
|
if len(params) == 0 || len(schema) == 0 {
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
@@ -58,7 +62,8 @@ func alignParamTypes(params []Param, schema []Param) []Param {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeCreateOnly(createParams []Param, modifyParams []Param) map[string]struct{} {
|
// ComputeCreateOnly вычисляет набор полей, которые есть только в create (не в modify).
|
||||||
|
func ComputeCreateOnly(createParams []types.Param, modifyParams []types.Param) map[string]struct{} {
|
||||||
modifyCodes := make(map[string]struct{}, len(modifyParams))
|
modifyCodes := make(map[string]struct{}, len(modifyParams))
|
||||||
for _, p := range modifyParams {
|
for _, p := range modifyParams {
|
||||||
key := strings.ToLower(strings.TrimSpace(p.Code))
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
||||||
@@ -82,7 +87,8 @@ func computeCreateOnly(createParams []Param, modifyParams []Param) map[string]st
|
|||||||
return createOnly
|
return createOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
func markCreateOnly(schema []Param, createOnly map[string]struct{}) []Param {
|
// MarkCreateOnly проставляет CreateOnly=true для полей, которых нет в modify.
|
||||||
|
func MarkCreateOnly(schema []types.Param, createOnly map[string]struct{}) []types.Param {
|
||||||
if len(schema) == 0 || len(createOnly) == 0 {
|
if len(schema) == 0 || len(createOnly) == 0 {
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
@@ -100,12 +106,13 @@ func markCreateOnly(schema []Param, createOnly map[string]struct{}) []Param {
|
|||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterCreateOnly(schema []Param) []Param {
|
// FilterCreateOnly возвращает только CreateOnly-параметры.
|
||||||
|
func FilterCreateOnly(schema []types.Param) []types.Param {
|
||||||
if len(schema) == 0 {
|
if len(schema) == 0 {
|
||||||
return []Param{}
|
return []types.Param{}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]Param, 0, len(schema))
|
result := make([]types.Param, 0, len(schema))
|
||||||
for _, p := range schema {
|
for _, p := range schema {
|
||||||
if p.CreateOnly {
|
if p.CreateOnly {
|
||||||
result = append(result, p)
|
result = append(result, p)
|
||||||
@@ -114,12 +121,13 @@ func filterCreateOnly(schema []Param) []Param {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterCreateOnlyRequired(schema []Param) []Param {
|
// FilterCreateOnlyRequired возвращает обязательные CreateOnly-параметры.
|
||||||
|
func FilterCreateOnlyRequired(schema []types.Param) []types.Param {
|
||||||
if len(schema) == 0 {
|
if len(schema) == 0 {
|
||||||
return []Param{}
|
return []types.Param{}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]Param, 0, len(schema))
|
result := make([]types.Param, 0, len(schema))
|
||||||
for _, p := range schema {
|
for _, p := range schema {
|
||||||
if p.CreateOnly && p.Required {
|
if p.CreateOnly && p.Required {
|
||||||
result = append(result, p)
|
result = append(result, p)
|
||||||
@@ -128,7 +136,8 @@ func filterCreateOnlyRequired(schema []Param) []Param {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyzeParams(usesBool *bool, usesInt64 *bool, usesString *bool, hasDefaults *bool, needsBoolDefault *bool, needsInt64Default *bool, needsStringDefault *bool, params []Param) {
|
// Analyze анализирует типы параметров и проставляет флаги uses/needs.
|
||||||
|
func Analyze(usesBool *bool, usesInt64 *bool, usesString *bool, hasDefaults *bool, needsBoolDefault *bool, needsInt64Default *bool, needsStringDefault *bool, params []types.Param) {
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
switch strings.ToLower(p.Type) {
|
switch strings.ToLower(p.Type) {
|
||||||
case "bool":
|
case "bool":
|
||||||
@@ -153,8 +162,8 @@ func analyzeParams(usesBool *bool, usesInt64 *bool, usesString *bool, hasDefault
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildSubresourceForceNewCodes строит карту параметров, требующих ForceNew.
|
||||||
func buildSubresourceForceNewCodes(sr *GenSubresource, createOnly map[string]struct{}) map[string]struct{} {
|
func BuildSubresourceForceNewCodes(sr *types.GenSubresource, createOnly map[string]struct{}) map[string]struct{} {
|
||||||
forceNew := map[string]struct{}{}
|
forceNew := map[string]struct{}{}
|
||||||
if sr.ModifyOpName == "" {
|
if sr.ModifyOpName == "" {
|
||||||
for _, p := range sr.SchemaParams {
|
for _, p := range sr.SchemaParams {
|
||||||
@@ -177,7 +186,8 @@ func buildSubresourceForceNewCodes(sr *GenSubresource, createOnly map[string]str
|
|||||||
return forceNew
|
return forceNew
|
||||||
}
|
}
|
||||||
|
|
||||||
func markForceNew(params []Param, forceNew map[string]struct{}) []Param {
|
// MarkForceNew проставляет ForceNew=true для указанных параметров.
|
||||||
|
func MarkForceNew(params []types.Param, forceNew map[string]struct{}) []types.Param {
|
||||||
if len(params) == 0 || len(forceNew) == 0 {
|
if len(params) == 0 || len(forceNew) == 0 {
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
@@ -193,8 +203,9 @@ func markForceNew(params []Param, forceNew map[string]struct{}) []Param {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterForceNew(params []Param) []Param {
|
// FilterForceNew возвращает только ForceNew-параметры.
|
||||||
result := []Param{}
|
func FilterForceNew(params []types.Param) []types.Param {
|
||||||
|
result := []types.Param{}
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
if p.ForceNew {
|
if p.ForceNew {
|
||||||
result = append(result, p)
|
result = append(result, p)
|
||||||
@@ -203,9 +214,8 @@ func filterForceNew(params []Param) []Param {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// analyzeJsonPlanMod возвращает true, если хотя бы один параметр является JSON-строкой.
|
// AnalyzeJsonPlanMod возвращает true, если хотя бы один параметр — JSON.
|
||||||
// Используется для условного добавления импорта planmodifier в сгенерированные файлы.
|
func AnalyzeJsonPlanMod(params []types.Param) bool {
|
||||||
func analyzeJsonPlanMod(params []Param) bool {
|
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
if p.IsJson {
|
if p.IsJson {
|
||||||
return true
|
return true
|
||||||
@@ -214,9 +224,8 @@ func analyzeJsonPlanMod(params []Param) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasRestoreCasingParams возвращает true, если есть ref_svc params с RefSvcId > 0 && != 12
|
// HasRestoreCasingParams возвращает true, если есть ref_svc-параметры (кроме S3).
|
||||||
// (restore-casing блок в Create/Read/Update использует strings.EqualFold для них).
|
func HasRestoreCasingParams(params []types.Param) bool {
|
||||||
func hasRestoreCasingParams(params []Param) bool {
|
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
if p.RefSvcId > 0 && p.RefSvcId != 12 && strings.EqualFold(strings.TrimSpace(p.Type), "string") {
|
if p.RefSvcId > 0 && p.RefSvcId != 12 && strings.EqualFold(strings.TrimSpace(p.Type), "string") {
|
||||||
return true
|
return true
|
||||||
@@ -225,9 +234,8 @@ func hasRestoreCasingParams(params []Param) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// analyzeNeedsStrings возвращает true, если среди params есть хотя бы один строковый параметр.
|
// AnalyzeNeedsStrings возвращает true, если есть строковые параметры.
|
||||||
// Используется для условного добавления импорта "strings" (EqualFold в Update).
|
func AnalyzeNeedsStrings(params []types.Param) bool {
|
||||||
func analyzeNeedsStrings(params []Param) bool {
|
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
t := strings.ToLower(strings.TrimSpace(p.Type))
|
t := strings.ToLower(strings.TrimSpace(p.Type))
|
||||||
if t != "bool" && t != "int" && t != "int64" && t != "number" {
|
if t != "bool" && t != "int" && t != "int64" && t != "number" {
|
||||||
@@ -237,7 +245,8 @@ func analyzeNeedsStrings(params []Param) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyzePlanModifiers(needsBool *bool, needsInt64 *bool, needsString *bool, params []Param) {
|
// AnalyzePlanModifiers анализирует ForceNew-параметры для добавления planmodifier-импортов.
|
||||||
|
func AnalyzePlanModifiers(needsBool *bool, needsInt64 *bool, needsString *bool, params []types.Param) {
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
if !p.ForceNew {
|
if !p.ForceNew {
|
||||||
continue
|
continue
|
||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
package main
|
package templates
|
||||||
|
|
||||||
const instanceTemplate = `package resources_gen
|
const Instance = `package resources_gen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -608,7 +608,7 @@ func (r *{{ToCamel .Name}}Resource) Configure(_ context.Context, req resource.Co
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const subresourceTemplate = `package resources_gen
|
const Subresource = `package resources_gen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1052,7 +1052,7 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Configure(_
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const actionTemplate = `package resources_gen
|
const Action = `package resources_gen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
+8
-9
@@ -1,6 +1,5 @@
|
|||||||
// types — структуры данных для генератора Terraform-провайдера.
|
// Package types — структуры данных для генератора Terraform-провайдера.
|
||||||
package main
|
package types
|
||||||
|
|
||||||
|
|
||||||
// ─── Входные структуры (из YAML) ───────────────────────────────────────────
|
// ─── Входные структуры (из YAML) ───────────────────────────────────────────
|
||||||
|
|
||||||
@@ -50,6 +49,7 @@ type ServiceSpec struct {
|
|||||||
Operations []OperationSpec `yaml:"operations"`
|
Operations []OperationSpec `yaml:"operations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Param — нормализованный параметр (после конвертации из ParamSpec).
|
||||||
type Param struct {
|
type Param struct {
|
||||||
ID int
|
ID int
|
||||||
Code string
|
Code string
|
||||||
@@ -68,6 +68,7 @@ type Param struct {
|
|||||||
IsJson bool
|
IsJson bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenResource — ресурс инстанса (nubes_{service}).
|
||||||
type GenResource struct {
|
type GenResource struct {
|
||||||
Name string
|
Name string
|
||||||
ServiceID int
|
ServiceID int
|
||||||
@@ -103,6 +104,7 @@ type GenResource struct {
|
|||||||
NeedsStringsImport bool
|
NeedsStringsImport bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenSubresource — подресурс (nubes_{service}_{sub}).
|
||||||
type GenSubresource struct {
|
type GenSubresource struct {
|
||||||
ServiceName string
|
ServiceName string
|
||||||
ServiceID int
|
ServiceID int
|
||||||
@@ -126,12 +128,11 @@ type GenSubresource struct {
|
|||||||
NeedsBoolPlanMod bool
|
NeedsBoolPlanMod bool
|
||||||
NeedsInt64PlanMod bool
|
NeedsInt64PlanMod bool
|
||||||
NeedsStringPlanMod bool
|
NeedsStringPlanMod bool
|
||||||
// NeedsJsonPlanMod: хотя бы один атрибут имеет IsJson=true.
|
NeedsJsonPlanMod bool
|
||||||
NeedsJsonPlanMod bool
|
|
||||||
// NeedsStringsImport: есть строковые ModifyParams (нужен EqualFold в Update).
|
|
||||||
NeedsStringsImport bool
|
NeedsStringsImport bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenAction — action-ресурс (почти не используется, redeploy встроен в GenResource).
|
||||||
type GenAction struct {
|
type GenAction struct {
|
||||||
ServiceName string
|
ServiceName string
|
||||||
ServiceID int
|
ServiceID int
|
||||||
@@ -146,8 +147,6 @@ type GenAction struct {
|
|||||||
NeedsBoolDefault bool
|
NeedsBoolDefault bool
|
||||||
NeedsInt64Default bool
|
NeedsInt64Default bool
|
||||||
NeedsStringDefault bool
|
NeedsStringDefault bool
|
||||||
// NeedsJsonPlanMod: хотя бы один атрибут имеет IsJson=true.
|
NeedsJsonPlanMod bool
|
||||||
NeedsJsonPlanMod bool
|
|
||||||
// NeedsStringsImport: есть строковые SchemaParams (нужен EqualFold).
|
|
||||||
NeedsStringsImport bool
|
NeedsStringsImport bool
|
||||||
}
|
}
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
// writers — генерация Go-файлов ресурсов и registry.go.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeInstanceResource(outDir string, svc GenResource) error {
|
|
||||||
fileName := fmt.Sprintf("%d_%s_resource.go", svc.ServiceID, svc.Name)
|
|
||||||
filePath := filepath.Join(outDir, fileName)
|
|
||||||
|
|
||||||
tpl, err := template.New("resource").Funcs(template.FuncMap{
|
|
||||||
"ToCamel": toCamel,
|
|
||||||
"ToSnake": toSnake,
|
|
||||||
"ParamType": paramType,
|
|
||||||
"ParamDefaultExpr": paramDefaultExpr,
|
|
||||||
"ParamFormat": paramFormat,
|
|
||||||
"ParamParse": paramParse,
|
|
||||||
"ParamDescription": paramDescription,
|
|
||||||
"OutputType": outputParamType,
|
|
||||||
"OutputIsMap": outputParamIsMap,
|
|
||||||
"OutputIsList": outputParamIsList,
|
|
||||||
"OutputSensitive": outputParamSensitive,
|
|
||||||
"bt": func() string { return "`" },
|
|
||||||
}).Parse(instanceTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := tpl.Execute(&buf, svc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
formatted := formatSourceOrWarn(filePath, buf.Bytes())
|
|
||||||
|
|
||||||
return os.WriteFile(filePath, formatted, 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeSubresource(outDir string, sr GenSubresource) error {
|
|
||||||
name := fmt.Sprintf("%s_%s", sr.ServiceName, sr.SubName)
|
|
||||||
fileName := fmt.Sprintf("%d_%s_resource.go", sr.ServiceID, name)
|
|
||||||
filePath := filepath.Join(outDir, fileName)
|
|
||||||
|
|
||||||
tpl, err := template.New("subresource").Funcs(template.FuncMap{
|
|
||||||
"ToCamel": toCamel,
|
|
||||||
"ToSnake": toSnake,
|
|
||||||
"ParamType": paramType,
|
|
||||||
"ParamDefaultExpr": paramDefaultExpr,
|
|
||||||
"ParamFormat": paramFormat,
|
|
||||||
"ParamParse": paramParse,
|
|
||||||
"ParamDescription": paramDescription,
|
|
||||||
"PlanModifierType": planModifierType,
|
|
||||||
"PlanModifierExpr": planModifierExpr,
|
|
||||||
"bt": func() string { return "`" },
|
|
||||||
}).Parse(subresourceTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := tpl.Execute(&buf, sr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
formatted := formatSourceOrWarn(filePath, buf.Bytes())
|
|
||||||
|
|
||||||
return os.WriteFile(filePath, formatted, 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeActionResource(outDir string, act GenAction) error {
|
|
||||||
name := fmt.Sprintf("%s_%s", act.ServiceName, act.ActionName)
|
|
||||||
fileName := fmt.Sprintf("%d_%s_action.go", act.ServiceID, name)
|
|
||||||
filePath := filepath.Join(outDir, fileName)
|
|
||||||
|
|
||||||
tpl, err := template.New("action").Funcs(template.FuncMap{
|
|
||||||
"ToCamel": toCamel,
|
|
||||||
"ToSnake": toSnake,
|
|
||||||
"ParamType": paramType,
|
|
||||||
"ParamDefaultExpr": paramDefaultExpr,
|
|
||||||
"ParamFormat": paramFormat,
|
|
||||||
"ParamParse": paramParse,
|
|
||||||
"ParamDescription": paramDescription,
|
|
||||||
"bt": func() string { return "`" },
|
|
||||||
}).Parse(actionTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := tpl.Execute(&buf, act); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
formatted := formatSourceOrWarn(filePath, buf.Bytes())
|
|
||||||
|
|
||||||
return os.WriteFile(filePath, formatted, 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeRegistry(outDir string, services []GenResource, subs []GenSubresource, actions []GenAction) error {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.WriteString("package resources_gen\n\n")
|
|
||||||
buf.WriteString("import \"github.com/hashicorp/terraform-plugin-framework/resource\"\n\n")
|
|
||||||
buf.WriteString("// Code generated by tools/gen_v2. DO NOT EDIT.\n")
|
|
||||||
buf.WriteString("func AllResources() []func() resource.Resource {\n")
|
|
||||||
buf.WriteString("\treturn []func() resource.Resource{\n")
|
|
||||||
for _, svc := range services {
|
|
||||||
buf.WriteString(fmt.Sprintf("\t\tNew%[1]sResource,\n", toCamel(svc.Name)))
|
|
||||||
}
|
|
||||||
for _, sr := range subs {
|
|
||||||
buf.WriteString(fmt.Sprintf("\t\tNew%[1]sResource,\n", toCamel(sr.ServiceName+"_"+sr.SubName)))
|
|
||||||
}
|
|
||||||
for _, act := range actions {
|
|
||||||
buf.WriteString(fmt.Sprintf("\t\tNew%[1]sResource,\n", toCamel(act.ServiceName+"_"+act.ActionName)))
|
|
||||||
}
|
|
||||||
buf.WriteString("\t}\n")
|
|
||||||
buf.WriteString("}\n")
|
|
||||||
|
|
||||||
regPath := filepath.Join(outDir, "registry.go")
|
|
||||||
formatted := formatSourceOrWarn(regPath, buf.Bytes())
|
|
||||||
|
|
||||||
return os.WriteFile(regPath, formatted, 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
// Package writers — генерация Go-файлов ресурсов и registry.go.
|
||||||
|
package writers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/helpers"
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/templates"
|
||||||
|
"terraform-provider-nubes/tools/gen_v2/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteInstanceResource генерирует nubes_{service} (CRUD инстанса).
|
||||||
|
func WriteInstanceResource(outDir string, svc types.GenResource) error {
|
||||||
|
fileName := fmt.Sprintf("%d_%s_resource.go", svc.ServiceID, svc.Name)
|
||||||
|
filePath := filepath.Join(outDir, fileName)
|
||||||
|
|
||||||
|
tpl, err := template.New("resource").Funcs(template.FuncMap{
|
||||||
|
"ToCamel": helpers.ToCamel,
|
||||||
|
"ToSnake": helpers.ToSnake,
|
||||||
|
"ParamType": helpers.ParamType,
|
||||||
|
"ParamDefaultExpr": helpers.ParamDefaultExpr,
|
||||||
|
"ParamFormat": helpers.ParamFormat,
|
||||||
|
"ParamParse": helpers.ParamParse,
|
||||||
|
"ParamDescription": helpers.ParamDescription,
|
||||||
|
"OutputType": helpers.OutputParamType,
|
||||||
|
"OutputIsMap": helpers.OutputParamIsMap,
|
||||||
|
"OutputIsList": helpers.OutputParamIsList,
|
||||||
|
"OutputSensitive": helpers.OutputParamSensitive,
|
||||||
|
"bt": func() string { return "`" },
|
||||||
|
}).Parse(templates.Instance)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tpl.Execute(&buf, svc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||||
|
|
||||||
|
return os.WriteFile(filePath, formatted, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteSubresource генерирует nubes_{service}_{sub} (пользователи, базы, ...).
|
||||||
|
func WriteSubresource(outDir string, sr types.GenSubresource) error {
|
||||||
|
name := fmt.Sprintf("%s_%s", sr.ServiceName, sr.SubName)
|
||||||
|
fileName := fmt.Sprintf("%d_%s_resource.go", sr.ServiceID, name)
|
||||||
|
filePath := filepath.Join(outDir, fileName)
|
||||||
|
|
||||||
|
tpl, err := template.New("subresource").Funcs(template.FuncMap{
|
||||||
|
"ToCamel": helpers.ToCamel,
|
||||||
|
"ToSnake": helpers.ToSnake,
|
||||||
|
"ParamType": helpers.ParamType,
|
||||||
|
"ParamDefaultExpr": helpers.ParamDefaultExpr,
|
||||||
|
"ParamFormat": helpers.ParamFormat,
|
||||||
|
"ParamParse": helpers.ParamParse,
|
||||||
|
"ParamDescription": helpers.ParamDescription,
|
||||||
|
"PlanModifierType": helpers.PlanModifierType,
|
||||||
|
"PlanModifierExpr": helpers.PlanModifierExpr,
|
||||||
|
"bt": func() string { return "`" },
|
||||||
|
}).Parse(templates.Subresource)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tpl.Execute(&buf, sr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||||
|
|
||||||
|
return os.WriteFile(filePath, formatted, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteActionResource генерирует action-ресурс (почти не используется).
|
||||||
|
func WriteActionResource(outDir string, act types.GenAction) error {
|
||||||
|
name := fmt.Sprintf("%s_%s", act.ServiceName, act.ActionName)
|
||||||
|
fileName := fmt.Sprintf("%d_%s_action.go", act.ServiceID, name)
|
||||||
|
filePath := filepath.Join(outDir, fileName)
|
||||||
|
|
||||||
|
tpl, err := template.New("action").Funcs(template.FuncMap{
|
||||||
|
"ToCamel": helpers.ToCamel,
|
||||||
|
"ToSnake": helpers.ToSnake,
|
||||||
|
"ParamType": helpers.ParamType,
|
||||||
|
"ParamDefaultExpr": helpers.ParamDefaultExpr,
|
||||||
|
"ParamFormat": helpers.ParamFormat,
|
||||||
|
"ParamParse": helpers.ParamParse,
|
||||||
|
"ParamDescription": helpers.ParamDescription,
|
||||||
|
"bt": func() string { return "`" },
|
||||||
|
}).Parse(templates.Action)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tpl.Execute(&buf, act); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted := helpers.FormatSourceOrWarn(filePath, buf.Bytes())
|
||||||
|
|
||||||
|
return os.WriteFile(filePath, formatted, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteRegistry генерирует registry.go со списком всех ресурсов.
|
||||||
|
func WriteRegistry(outDir string, services []types.GenResource, subs []types.GenSubresource, actions []types.GenAction) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("package resources_gen\n\n")
|
||||||
|
buf.WriteString("import \"github.com/hashicorp/terraform-plugin-framework/resource\"\n\n")
|
||||||
|
buf.WriteString("// Code generated by tools/gen_v2. DO NOT EDIT.\n")
|
||||||
|
buf.WriteString("func AllResources() []func() resource.Resource {\n")
|
||||||
|
buf.WriteString("\treturn []func() resource.Resource{\n")
|
||||||
|
for _, svc := range services {
|
||||||
|
buf.WriteString(fmt.Sprintf("\t\tNew%[1]sResource,\n", helpers.ToCamel(svc.Name)))
|
||||||
|
}
|
||||||
|
for _, sr := range subs {
|
||||||
|
buf.WriteString(fmt.Sprintf("\t\tNew%[1]sResource,\n", helpers.ToCamel(sr.ServiceName+"_"+sr.SubName)))
|
||||||
|
}
|
||||||
|
for _, act := range actions {
|
||||||
|
buf.WriteString(fmt.Sprintf("\t\tNew%[1]sResource,\n", helpers.ToCamel(act.ServiceName+"_"+act.ActionName)))
|
||||||
|
}
|
||||||
|
buf.WriteString("\t}\n")
|
||||||
|
buf.WriteString("}\n")
|
||||||
|
|
||||||
|
regPath := filepath.Join(outDir, "registry.go")
|
||||||
|
formatted := helpers.FormatSourceOrWarn(regPath, buf.Bytes())
|
||||||
|
|
||||||
|
return os.WriteFile(regPath, formatted, 0644)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user