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 == "" {
|
||||
continue
|
||||
}
|
||||
value, ok := paramsMap[code]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Поле модели достаём ДО проверки наличия значения в API: оно нужно
|
||||
// и в ветке "API не вернул код" (см. схлопывание unknown → null ниже).
|
||||
fieldName := strings.TrimSpace(input.Field)
|
||||
if fieldName == "" {
|
||||
fieldName = toCamel(code)
|
||||
@@ -127,6 +125,25 @@ func RefreshResourceState[T any](ctx context.Context, client *core.UniversalClie
|
||||
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 {
|
||||
// Preserve planned json_env when API returns equivalent JSON with different ordering.
|
||||
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
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
switch field.Type() {
|
||||
case typeString:
|
||||
|
||||
Reference in New Issue
Block a user