348 lines
10 KiB
Go
348 lines
10 KiB
Go
// Package params — слияние, фильтрация, анализ параметров и ForceNew.
|
|
//
|
|
// Основные операции:
|
|
// - Merge — объединяет параметры из нескольких операций по Code
|
|
// - ComputeCreateOnly — поля, которые есть в create, но отсутствуют в modify
|
|
// - BuildSubresourceForceNewCodes — поля, требующие пересоздания subresource
|
|
// - Analyze — определяет используемые типы (bool/int64/string) для импортов
|
|
// - AnalyzePlanModifiers — ForceNew-поля, требующие planmodifier.*.RequiresReplace()
|
|
package params
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
|
|
"resource-generator/internal/types"
|
|
)
|
|
|
|
// Merge объединяет несколько наборов параметров, убирая дубликаты по Code.
|
|
// При совпадении Code SubParams объединяются union'ом (новые поля из modify добавляются к create).
|
|
func Merge(sets ...[]types.Param) []types.Param {
|
|
seen := map[string]types.Param{}
|
|
for _, s := range sets {
|
|
for _, p := range s {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if existing, ok := seen[key]; !ok {
|
|
seen[key] = p
|
|
} else if p.HasSubParams && len(p.SubParams) > 0 {
|
|
merged := unionSubParams(existing.SubParams, p.SubParams)
|
|
existing.SubParams = merged
|
|
existing.HasSubParams = len(merged) > 0
|
|
seen[key] = existing
|
|
}
|
|
}
|
|
}
|
|
out := make([]types.Param, 0, len(seen))
|
|
for _, p := range seen {
|
|
out = append(out, p)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Code < out[j].Code })
|
|
return out
|
|
}
|
|
|
|
// unionSubParams добавляет sub-params из extra, которых ещё нет в base (сравнение по Code).
|
|
func unionSubParams(base, extra []types.Param) []types.Param {
|
|
inBase := make(map[string]struct{}, len(base))
|
|
for _, p := range base {
|
|
inBase[strings.ToLower(strings.TrimSpace(p.Code))] = struct{}{}
|
|
}
|
|
out := append([]types.Param(nil), base...)
|
|
for _, p := range extra {
|
|
if _, seen := inBase[strings.ToLower(strings.TrimSpace(p.Code))]; !seen {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Code < out[j].Code })
|
|
return out
|
|
}
|
|
|
|
// AlignParamTypes выравнивает типы параметров по схеме.
|
|
func AlignParamTypes(params []types.Param, schema []types.Param) []types.Param {
|
|
if len(params) == 0 || len(schema) == 0 {
|
|
return params
|
|
}
|
|
codeType := make(map[string]string, len(schema))
|
|
for _, p := range schema {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if strings.TrimSpace(p.Type) != "" {
|
|
codeType[key] = p.Type
|
|
}
|
|
}
|
|
if len(codeType) == 0 {
|
|
return params
|
|
}
|
|
|
|
for i, p := range params {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if t, ok := codeType[key]; ok && strings.TrimSpace(t) != "" {
|
|
params[i].Type = t
|
|
}
|
|
}
|
|
|
|
// Рекурсивно выравниваем SubParams (например cpu может быть string в modify, но int64 в create).
|
|
schemaByCode := make(map[string]types.Param, len(schema))
|
|
for _, p := range schema {
|
|
schemaByCode[strings.ToLower(strings.TrimSpace(p.Code))] = p
|
|
}
|
|
for i, p := range params {
|
|
if !p.HasSubParams {
|
|
continue
|
|
}
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if sp, ok := schemaByCode[key]; ok && len(sp.SubParams) > 0 {
|
|
params[i].SubParams = AlignParamTypes(p.SubParams, sp.SubParams)
|
|
}
|
|
}
|
|
|
|
return params
|
|
}
|
|
|
|
// ComputeCreateOnly вычисляет набор полей, которые есть только в create (не в modify).
|
|
func ComputeCreateOnly(createParams []types.Param, modifyParams []types.Param) map[string]struct{} {
|
|
modifyCodes := make(map[string]struct{}, len(modifyParams))
|
|
for _, p := range modifyParams {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key == "" {
|
|
continue
|
|
}
|
|
modifyCodes[key] = struct{}{}
|
|
}
|
|
|
|
createOnly := make(map[string]struct{})
|
|
for _, p := range createParams {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if _, ok := modifyCodes[key]; !ok {
|
|
createOnly[key] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return createOnly
|
|
}
|
|
|
|
// MarkCreateOnly проставляет CreateOnly=true для полей, которых нет в modify.
|
|
func MarkCreateOnly(schema []types.Param, createOnly map[string]struct{}) []types.Param {
|
|
if len(schema) == 0 || len(createOnly) == 0 {
|
|
return schema
|
|
}
|
|
|
|
for i, p := range schema {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if _, ok := createOnly[key]; ok {
|
|
schema[i].CreateOnly = true
|
|
}
|
|
}
|
|
|
|
return schema
|
|
}
|
|
|
|
// FilterCreateOnly возвращает только CreateOnly-параметры.
|
|
func FilterCreateOnly(schema []types.Param) []types.Param {
|
|
if len(schema) == 0 {
|
|
return []types.Param{}
|
|
}
|
|
|
|
result := make([]types.Param, 0, len(schema))
|
|
for _, p := range schema {
|
|
if p.CreateOnly {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// FilterCreateOnlyRequired возвращает обязательные CreateOnly-параметры.
|
|
func FilterCreateOnlyRequired(schema []types.Param) []types.Param {
|
|
if len(schema) == 0 {
|
|
return []types.Param{}
|
|
}
|
|
|
|
result := make([]types.Param, 0, len(schema))
|
|
for _, p := range schema {
|
|
if p.CreateOnly && p.Required {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// 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 {
|
|
switch strings.ToLower(p.Type) {
|
|
case "bool":
|
|
*usesBool = true
|
|
if p.Default != "" {
|
|
*needsBoolDefault = true
|
|
*hasDefaults = true
|
|
}
|
|
case "int", "int64", "number":
|
|
*usesInt64 = true
|
|
if p.Default != "" {
|
|
*needsInt64Default = true
|
|
*hasDefaults = true
|
|
}
|
|
default:
|
|
*usesString = true
|
|
if p.Default != "" {
|
|
*needsStringDefault = true
|
|
*hasDefaults = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// BuildSubresourceForceNewCodes строит карту параметров, требующих ForceNew.
|
|
func BuildSubresourceForceNewCodes(sr *types.GenSubresource, createOnly map[string]struct{}) map[string]struct{} {
|
|
forceNew := map[string]struct{}{}
|
|
if sr.ModifyOpName == "" {
|
|
for _, p := range sr.SchemaParams {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key != "" {
|
|
forceNew[key] = struct{}{}
|
|
}
|
|
}
|
|
return forceNew
|
|
}
|
|
for key := range createOnly {
|
|
forceNew[key] = struct{}{}
|
|
}
|
|
for _, p := range sr.DeleteParams {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key != "" {
|
|
forceNew[key] = struct{}{}
|
|
}
|
|
}
|
|
return forceNew
|
|
}
|
|
|
|
// MarkForceNew проставляет ForceNew=true для указанных параметров.
|
|
func MarkForceNew(params []types.Param, forceNew map[string]struct{}) []types.Param {
|
|
if len(params) == 0 || len(forceNew) == 0 {
|
|
return params
|
|
}
|
|
for i, p := range params {
|
|
key := strings.ToLower(strings.TrimSpace(p.Code))
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if _, ok := forceNew[key]; ok {
|
|
params[i].ForceNew = true
|
|
}
|
|
}
|
|
return params
|
|
}
|
|
|
|
// FilterForceNew возвращает только ForceNew-параметры.
|
|
func FilterForceNew(params []types.Param) []types.Param {
|
|
result := []types.Param{}
|
|
for _, p := range params {
|
|
if p.ForceNew {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// AnalyzeJsonPlanMod возвращает true, если хотя бы один параметр — JSON.
|
|
func AnalyzeJsonPlanMod(params []types.Param) bool {
|
|
for _, p := range params {
|
|
if p.IsJson {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasNestedParams возвращает true, если есть nested (только map-fixed, не array-map-fixed) параметры.
|
|
func HasNestedParams(params []types.Param) bool {
|
|
for _, p := range params {
|
|
if p.HasSubParams && !strings.Contains(strings.ToLower(p.Type), "array") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// AnalyzeNestedDefaults проверяет sub-params nested-параметров на наличие default'ов.
|
|
// Возвращает true если найдены nested параметры.
|
|
func AnalyzeNestedDefaults(needsBoolDefault *bool, needsInt64Default *bool, needsStringDefault *bool, params []types.Param) bool {
|
|
found := false
|
|
for _, p := range params {
|
|
if len(p.SubParams) == 0 {
|
|
continue
|
|
}
|
|
found = true
|
|
for _, sp := range p.SubParams {
|
|
if sp.Default == "" {
|
|
continue
|
|
}
|
|
switch strings.ToLower(sp.Type) {
|
|
case "bool":
|
|
*needsBoolDefault = true
|
|
case "int64", "int", "number":
|
|
*needsInt64Default = true
|
|
default:
|
|
*needsStringDefault = true
|
|
}
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
// HasRestoreCasingParams возвращает true, если есть ref_svc-параметры (кроме S3).
|
|
func HasRestoreCasingParams(params []types.Param) bool {
|
|
for _, p := range params {
|
|
if p.RefSvcId > 0 && p.RefSvcId != 12 && strings.EqualFold(strings.TrimSpace(p.Type), "string") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// AnalyzeNeedsStrings возвращает true, если есть строковые параметры.
|
|
func AnalyzeNeedsStrings(params []types.Param) bool {
|
|
for _, p := range params {
|
|
// Пропускаем IsNested (map-fixed без array): они не участвуют в strings.EqualFold
|
|
if p.HasSubParams && !strings.Contains(strings.ToLower(p.Type), "array") {
|
|
continue
|
|
}
|
|
t := strings.ToLower(strings.TrimSpace(p.Type))
|
|
if t != "bool" && t != "int" && t != "int64" && t != "number" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// AnalyzePlanModifiers анализирует ForceNew-параметры для добавления planmodifier-импортов.
|
|
func AnalyzePlanModifiers(needsBool *bool, needsInt64 *bool, needsString *bool, params []types.Param) {
|
|
for _, p := range params {
|
|
if !p.ForceNew {
|
|
continue
|
|
}
|
|
switch strings.ToLower(strings.TrimSpace(p.Type)) {
|
|
case "bool":
|
|
*needsBool = true
|
|
case "int", "int64", "number":
|
|
*needsInt64 = true
|
|
default:
|
|
*needsString = true
|
|
}
|
|
}
|
|
}
|