fix(core): гарантия known для read-back полей (unknown -> null), чтобы Computed без Default не ломал apply
This commit is contained in:
@@ -114,10 +114,8 @@ func RefreshResourceState[T any](ctx context.Context, client *core.UniversalClie
|
|||||||
if code == "" {
|
if code == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value, ok := paramsMap[code]
|
// Поле модели достаём ДО проверки наличия значения в API: оно нужно
|
||||||
if !ok {
|
// и в ветке "API не вернул код" (см. схлопывание unknown → null ниже).
|
||||||
continue
|
|
||||||
}
|
|
||||||
fieldName := strings.TrimSpace(input.Field)
|
fieldName := strings.TrimSpace(input.Field)
|
||||||
if fieldName == "" {
|
if fieldName == "" {
|
||||||
fieldName = toCamel(code)
|
fieldName = toCamel(code)
|
||||||
@@ -127,6 +125,25 @@ func RefreshResourceState[T any](ctx context.Context, client *core.UniversalClie
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value, ok := paramsMap[code]
|
||||||
|
if !ok {
|
||||||
|
// ИНВАРИАНТ: Computed-атрибут обязан быть KNOWN после apply/read.
|
||||||
|
//
|
||||||
|
// Параметры, которые провайдер читает обратно, объявлены в схеме как
|
||||||
|
// Optional+Computed (см. helpers.ShouldBeOptionalComputed). Если
|
||||||
|
// пользователь такой параметр не задал, в плане он = unknown, и именно
|
||||||
|
// провайдер обязан проставить конкретное значение. Когда платформа
|
||||||
|
// не вернула код в state_params, единственное корректное конкретное
|
||||||
|
// значение — null.
|
||||||
|
//
|
||||||
|
// Если оставить unknown, Terraform упадёт с
|
||||||
|
// "Provider produced invalid result object after apply: ... was unknown".
|
||||||
|
if inputFieldUnknown(fv) {
|
||||||
|
setInputFieldNull(fv)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if strings.EqualFold(code, "jsonEnv") && fv.Type() == typeString {
|
if strings.EqualFold(code, "jsonEnv") && fv.Type() == typeString {
|
||||||
// Preserve planned json_env when API returns equivalent JSON with different ordering.
|
// Preserve planned json_env when API returns equivalent JSON with different ordering.
|
||||||
if planned, ok := fv.Interface().(types.String); ok && !planned.IsNull() && !planned.IsUnknown() {
|
if planned, ok := fv.Interface().(types.String); ok && !planned.IsNull() && !planned.IsUnknown() {
|
||||||
@@ -156,6 +173,42 @@ func RefreshResourceState[T any](ctx context.Context, client *core.UniversalClie
|
|||||||
return state, diags
|
return state, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inputFieldUnknown сообщает, находится ли поле модели в состоянии unknown.
|
||||||
|
//
|
||||||
|
// Зачем: соблюдение инварианта «Computed-атрибут обязан быть known после
|
||||||
|
// apply/read». Если платформа не вернула значение в state_params, unknown
|
||||||
|
// оставлять нельзя — его надо схлопнуть в null (см. setInputFieldNull).
|
||||||
|
func inputFieldUnknown(fv reflect.Value) bool {
|
||||||
|
switch fv.Type() {
|
||||||
|
case typeString:
|
||||||
|
v, ok := fv.Interface().(types.String)
|
||||||
|
return ok && v.IsUnknown()
|
||||||
|
case typeBool:
|
||||||
|
v, ok := fv.Interface().(types.Bool)
|
||||||
|
return ok && v.IsUnknown()
|
||||||
|
case typeInt64:
|
||||||
|
v, ok := fv.Interface().(types.Int64)
|
||||||
|
return ok && v.IsUnknown()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// setInputFieldNull записывает в поле модели типизированный null.
|
||||||
|
//
|
||||||
|
// Зачем: null — это конкретное (known) значение, в отличие от unknown. Именно
|
||||||
|
// null приводит состояние Terraform в консистентный вид, когда платформа не
|
||||||
|
// сообщила значение для read-back параметра.
|
||||||
|
func setInputFieldNull(fv reflect.Value) {
|
||||||
|
switch fv.Type() {
|
||||||
|
case typeString:
|
||||||
|
fv.Set(reflect.ValueOf(types.StringNull()))
|
||||||
|
case typeBool:
|
||||||
|
fv.Set(reflect.ValueOf(types.BoolNull()))
|
||||||
|
case typeInt64:
|
||||||
|
fv.Set(reflect.ValueOf(types.Int64Null()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setFieldValue(field reflect.Value, value interface{}) {
|
func setFieldValue(field reflect.Value, value interface{}) {
|
||||||
switch field.Type() {
|
switch field.Type() {
|
||||||
case typeString:
|
case typeString:
|
||||||
|
|||||||
Reference in New Issue
Block a user