fix(subresource): password заполняется во ВСЕХ ветках Create, а не только в успешной

Баг (воспроизведён на TEST 2026-10-01): при усыновлении уже существующего
пользователя БД Create выходит по раннему return (ветка 'Подресурс уже существует'
-> State.Set -> return), оставляя Computed-атрибут password в состоянии unknown.
Terraform отказывался: 'Provider returned invalid result object after apply: the
provider still indicated an unknown value for nubes_postgres_user.crud_user_0.password'.

Исправление:
- новая функция resources_core.ResolveUserPasswordFromVault(ctx, client, instanceUID,
  username, current): читает пароль из Vault родителя, иначе возвращает current,
  иначе типизированный null (null для Terraform — конкретное, known значение);
- в шаблоне подресурса password инициализируется null сразу после получения
  instanceUID, а перед КАЖДЫМ resp.State.Set в Create проставляется конкретным
  значением — включая все ветки усыновления;
- регрессионный тест усилен: считает сохранения состояния и вызовы заполнения в
  Create и падает, если хоть в одной ветке password останется unknown.

Проверено: generated/test/go/90_postgres_user_resource.go — 4 State.Set и 4
вызова заполнения; go test ./... ok; go build — чисто.
This commit is contained in:
Repinoid
2026-10-01 19:26:34 +03:00
parent 2c6998975f
commit 64328abe55
3 changed files with 86 additions and 24 deletions
@@ -1,9 +1,14 @@
package resources_core
import (
"context"
"encoding/json"
"fmt"
"strings"
"terraform-provider-nubes/internal/core"
"github.com/hashicorp/terraform-plugin-framework/types"
)
// ExtractUserPassword вытаскивает пароль пользователя из JSON-значения секрета Vault
@@ -61,3 +66,39 @@ func ExtractUserPassword(secretJSON string, username string) (string, error) {
return "", nil
}
// ResolveUserPasswordFromVault читает пароль пользователя из секретов Vault
// родительского инстанса и возвращает готовое значение для атрибута password.
//
// Зачем функция, а не код в шаблоне: подресурс сохраняет состояние в НЕСКОЛЬКИХ
// местах (обычный create, усыновление уже существующего объекта, подтверждение по
// state_out). Если хотя бы в одной ветке оставить password неизвестным, Terraform
// падает с «Provider returned invalid result object after apply: ... still indicated
// an unknown value». Поэтому все ветки обязаны проставлять КОНКРЕТНОЕ значение —
// через эту функцию.
//
// Поведение:
// - удалось прочитать пароль → конкретное значение;
// - не удалось → возвращается current, если оно уже известно (например, из state),
// иначе типизированный null (null тоже «known» для Terraform).
func ResolveUserPasswordFromVault(ctx context.Context, client *core.UniversalClient, instanceUID string, username string, current types.String) types.String {
if client != nil && strings.TrimSpace(instanceUID) != "" {
if details, err := client.GetInstanceStateDetails(ctx, instanceUID); err == nil && details != nil && len(details.Vault.Fields) > 0 {
if secrets, serr := client.GetInstanceVaultSecrets(ctx, instanceUID, details.Vault.Fields); serr == nil {
for _, secretValue := range secrets {
pw, perr := ExtractUserPassword(secretValue, username)
if perr != nil {
continue
}
if pw != "" {
return types.StringValue(pw)
}
}
}
}
}
if current.IsNull() || current.IsUnknown() {
return types.StringNull()
}
return current
}