resource-generator: SubParams → nested SingleNestedAttribute/ListNestedAttribute + bump 5.0.73
This commit is contained in:
@@ -190,3 +190,131 @@ func PlanModifierExpr(p types.Param) string {
|
||||
return "stringplanmodifier.RequiresReplace()"
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Nested (map-fixed) helpers ──────────────────────────────────────────────
|
||||
|
||||
// IsNested возвращает true для параметров с SubParams (map-fixed/array-map-fixed).
|
||||
func IsNested(p types.Param) bool {
|
||||
return p.IsNested
|
||||
}
|
||||
|
||||
// IsNestedList возвращает true для array-map-fixed (список объектов).
|
||||
func IsNestedList(p types.Param) bool {
|
||||
return p.IsNested && strings.Contains(strings.ToLower(p.Type), "array")
|
||||
}
|
||||
|
||||
// NestedModelName возвращает имя вложенного Go-struct для ресурса и параметра.
|
||||
// Например: NestedModelName("postgres", "cluster_configuration") → "PostgresClusterConfigurationModel"
|
||||
func NestedModelName(resourceName, paramCode string) string {
|
||||
return ToCamel(resourceName) + ToCamel(paramCode) + "Model"
|
||||
}
|
||||
|
||||
// NestedTfType возвращает Go-тип для вложенного поля в Model:
|
||||
// *PostgresClusterConfigurationModel (SingleNestedAttribute) или
|
||||
// []PostgresClusterConfigurationModel (ListNestedAttribute — array-map-fixed).
|
||||
func NestedTfType(p types.Param, resourceName string) string {
|
||||
name := NestedModelName(resourceName, p.Code)
|
||||
if IsNestedList(p) {
|
||||
return "[]" + name
|
||||
}
|
||||
return "*" + name
|
||||
}
|
||||
|
||||
// NestedSchemaType возвращает тип схемы для nested параметра:
|
||||
// "SingleNested" (map-fixed) или "ListNested" (array-map-fixed).
|
||||
func NestedSchemaType(p types.Param) string {
|
||||
if IsNestedList(p) {
|
||||
return "ListNested"
|
||||
}
|
||||
return "SingleNested"
|
||||
}
|
||||
|
||||
// NestedSchemaBlock генерирует открывающую строку для nested-атрибута в Schema.
|
||||
func NestedSchemaBlock(p types.Param) string {
|
||||
t := NestedSchemaType(p)
|
||||
switch t {
|
||||
case "ListNested":
|
||||
return fmt.Sprintf("schema.%sAttribute{Optional: true,\n\t\t\tNestedObject: schema.NestedAttributeObject{Attributes: map[string]schema.Attribute{", t)
|
||||
default:
|
||||
req := "Required: true"
|
||||
if p.Default != "" {
|
||||
req = "Optional: true, Computed: true"
|
||||
}
|
||||
return fmt.Sprintf("schema.%sAttribute{%s,\n\t\t\tAttributes: map[string]schema.Attribute{", t, req)
|
||||
}
|
||||
}
|
||||
|
||||
// NestedSchemaEnd возвращает закрывающую строку для nested-атрибута.
|
||||
func NestedSchemaEnd(p types.Param) string {
|
||||
t := NestedSchemaType(p)
|
||||
if t == "ListNested" {
|
||||
return "}},"
|
||||
}
|
||||
return "},"
|
||||
}
|
||||
|
||||
// SubSchemaType возвращает тип схемы для подпараметра.
|
||||
func SubSchemaType(p types.Param) string {
|
||||
switch strings.ToLower(p.Type) {
|
||||
case "bool":
|
||||
return "Bool"
|
||||
case "int64", "int", "number":
|
||||
return "Int64"
|
||||
default:
|
||||
return "String"
|
||||
}
|
||||
}
|
||||
|
||||
// SubDefaultExpr возвращает выражение default для подпараметра.
|
||||
// ⚠️ Только Optional+Computed (Required несовместим с Default во фреймворке).
|
||||
func SubDefaultExpr(p types.Param) string {
|
||||
if p.Default == "" {
|
||||
return ""
|
||||
}
|
||||
switch strings.ToLower(p.Type) {
|
||||
case "bool":
|
||||
return fmt.Sprintf("booldefault.StaticBool(%s)", strings.ToLower(p.Default))
|
||||
case "int64", "int", "number":
|
||||
return fmt.Sprintf("int64default.StaticInt64(%s)", p.Default)
|
||||
default:
|
||||
return fmt.Sprintf("stringdefault.StaticString(%q)", p.Default)
|
||||
}
|
||||
}
|
||||
|
||||
// SubParamFormat возвращает JSON-ключ:значение для форматирования подпараметра.
|
||||
func SubParamFormat(p types.Param, varPrefix string) string {
|
||||
varName := varPrefix + "." + ToCamel(p.Code)
|
||||
switch strings.ToLower(p.Type) {
|
||||
case "bool":
|
||||
return fmt.Sprintf(`"%s": `+varName+`.ValueBool()`, p.Code)
|
||||
case "int64", "int", "number":
|
||||
return fmt.Sprintf(`"%s": %s `+varName+`.ValueInt64()`, p.Code, "%s")
|
||||
default:
|
||||
return fmt.Sprintf(`"%s": `+varName+`.ValueString()`, p.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// NestedJSONExpr возвращает выражение для сборки JSON из nested struct через resources_core.BuildJSON.
|
||||
// BuildJSON принимает map[string]string где ключи — имена полей,
|
||||
// значения — уже отформатированные строки (числа без кавычек, строки с кавычками).
|
||||
// Пример: resources_core.BuildJSON(map[string]string{"cpu": "500", "name": "\"hello\""}) → {"cpu":500,"name":"hello"}
|
||||
func NestedJSONExpr(p types.Param, varName string) string {
|
||||
pairs := make([]string, 0, len(p.SubParams))
|
||||
for _, sp := range p.SubParams {
|
||||
field := varName + "." + ToCamel(sp.Code)
|
||||
var valExpr string
|
||||
switch strings.ToLower(sp.Type) {
|
||||
case "bool":
|
||||
// BuildJSON ожидает "true"/"false" (без кавычек)
|
||||
valExpr = fmt.Sprintf(`fmt.Sprintf("%%v", %s.ValueBool())`, field)
|
||||
case "int64", "int", "number":
|
||||
// BuildJSON ожидает "500" (без кавычек)
|
||||
valExpr = fmt.Sprintf(`fmt.Sprintf("%%d", %s.ValueInt64())`, field)
|
||||
default:
|
||||
// BuildJSON ожидает "\"hello\"" (с кавычками)
|
||||
valExpr = fmt.Sprintf(`fmt.Sprintf("\"%%s\"", %s.ValueString())`, field)
|
||||
}
|
||||
pairs = append(pairs, fmt.Sprintf(`"%s": %s`, sp.Code, valExpr))
|
||||
}
|
||||
return fmt.Sprintf("resources_core.BuildJSON(map[string]string{%s})", strings.Join(pairs, ", "))
|
||||
}
|
||||
|
||||
@@ -38,6 +38,17 @@ type {{ToCamel .Name}}Resource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
{{- range .SchemaParams }}
|
||||
{{- if .IsNested }}
|
||||
// {{NestedModelName $.Name .Code}} — вложенная модель для map-fixed параметра {{.Code}}.
|
||||
type {{NestedModelName $.Name .Code}} struct {
|
||||
{{- range .SubParams }}
|
||||
{{ToCamel .Code}} {{ParamType .}} ` + "`" + `tfsdk:"{{ToSnake .Code}}" json:"{{.Code}}"` + "`" + `
|
||||
{{- end }}
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
type {{ToCamel .Name}}Model struct {
|
||||
ID types.String ` + "`" + `tfsdk:"id"` + "`" + `
|
||||
ResourceName types.String ` + "`" + `tfsdk:"resource_name"` + "`" + `
|
||||
@@ -50,8 +61,12 @@ type {{ToCamel .Name}}Model struct {
|
||||
GitRevision types.String ` + "`" + `tfsdk:"git_revision"` + "`" + `
|
||||
{{- end }}
|
||||
{{- range .SchemaParams }}
|
||||
{{- if .IsNested }}
|
||||
{{ToCamel .Code}} {{NestedTfType . $.Name}} ` + "`" + `tfsdk:"{{ToSnake .Code}}"` + "`" + `
|
||||
{{- else }}
|
||||
{{ToCamel .Code}} {{ParamType .}} ` + "`" + `tfsdk:"{{ToSnake .Code}}"` + "`" + `
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
SuspendOnDestroy types.Bool ` + "`" + `tfsdk:"suspend_on_destroy"` + "`" + `
|
||||
AdoptExistingOnCreate types.Bool ` + "`" + `tfsdk:"adopt_existing_on_create"` + "`" + `
|
||||
{{- range .OutputParams }}
|
||||
@@ -74,6 +89,16 @@ func (r *{{ToCamel .Name}}Resource) Schema(ctx context.Context, req resource.Sch
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
{{- range .SchemaParams }}
|
||||
{{- if .IsNested }}
|
||||
"{{ToSnake .Code}}": {{NestedSchemaBlock .}}
|
||||
{{- range .SubParams }}
|
||||
"{{ToSnake .Code}}": schema.{{SubSchemaType .}}Attribute{
|
||||
{{- if .Default }}Optional: true, Computed: true, Default: {{SubDefaultExpr .}},{{else if .Required}}Required: true,{{else}}Optional: true,{{end}}
|
||||
{{- if ne (ParamDescription .) "" }}MarkdownDescription: {{ParamDescription .}},{{end}}
|
||||
},
|
||||
{{- end }}
|
||||
{{NestedSchemaEnd .}}
|
||||
{{- else }}
|
||||
"{{ToSnake .Code}}": schema.{{if eq (ParamType .) "types.Bool"}}Bool{{else if eq (ParamType .) "types.Int64"}}Int64{{else}}String{{end}}Attribute{
|
||||
{{- if and .Required (eq (ParamDefaultExpr .) "") (eq .RefSvcId 0) }}Required: true,{{else}}Optional: true,{{end}}
|
||||
{{- if ne (ParamDefaultExpr .) "" }}Computed: true, Default: {{ParamDefaultExpr .}},{{- else if or .IsJson (gt .RefSvcId 0) }}Computed: true,{{- end }}
|
||||
@@ -82,6 +107,7 @@ func (r *{{ToCamel .Name}}Resource) Schema(ctx context.Context, req resource.Sch
|
||||
{{- if .IsJson }}PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()},{{- end }}
|
||||
},
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .HasRedeploy }}
|
||||
// --- Redeploy support (ARCHITECTURE.md) ---
|
||||
// При изменении git_revision вызывается redeploy вместо modify.
|
||||
@@ -204,8 +230,12 @@ func (r *{{ToCamel .Name}}Resource) ModifyPlan(ctx context.Context, req resource
|
||||
{{- end }}
|
||||
params := map[int]string{
|
||||
{{- range .CreateParams }}
|
||||
{{- if .IsNested }}
|
||||
{{.ID}}: {{NestedJSONExpr . "config"}},
|
||||
{{- else }}
|
||||
{{.ID}}: {{ParamFormat . (printf "config.%s" (ToCamel .Code))}},
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}
|
||||
desiredDomain := ""
|
||||
{{- if .HasDomainParam }}
|
||||
@@ -290,8 +320,12 @@ func (r *{{ToCamel .Name}}Resource) Create(ctx context.Context, req resource.Cre
|
||||
|
||||
params := map[int]string{
|
||||
{{- range .CreateParams }}
|
||||
{{- if .IsNested }}
|
||||
{{.ID}}: {{NestedJSONExpr . "data"}},
|
||||
{{- else }}
|
||||
{{.ID}}: {{ParamFormat . (printf "data.%s" (ToCamel .Code))}},
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
operationTimeout := ""
|
||||
@@ -506,8 +540,12 @@ func (r *{{ToCamel .Name}}Resource) Update(ctx context.Context, req resource.Upd
|
||||
{{- end }}
|
||||
params := map[int]string{
|
||||
{{- range .ModifyParams }}
|
||||
{{- if .IsNested }}
|
||||
{{.ID}}: {{NestedJSONExpr . "plan"}},
|
||||
{{- else }}
|
||||
{{.ID}}: {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}},
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}
|
||||
if err := resources_core.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
|
||||
@@ -27,18 +27,28 @@ func WriteInstanceResource(outDir string, svc types.GenResource) error {
|
||||
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 "`" },
|
||||
"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,
|
||||
"IsNested": helpers.IsNested,
|
||||
"IsNestedList": helpers.IsNestedList,
|
||||
"NestedModelName": helpers.NestedModelName,
|
||||
"NestedTfType": helpers.NestedTfType,
|
||||
"NestedSchemaType": helpers.NestedSchemaType,
|
||||
"NestedSchemaBlock": helpers.NestedSchemaBlock,
|
||||
"NestedSchemaEnd": helpers.NestedSchemaEnd,
|
||||
"NestedJSONExpr": helpers.NestedJSONExpr,
|
||||
"SubSchemaType": helpers.SubSchemaType,
|
||||
"SubDefaultExpr": helpers.SubDefaultExpr,
|
||||
"bt": func() string { return "`" },
|
||||
}).Parse(templates.Instance)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user