fix(subresource): диагностика чтения пароля + одна точка заполнения в Create

По итогам код-ревью (коммит 64328ab):
- ResolveUserPasswordFromVault теперь возвращает и текст предупреждения: если пароль
  прочитать не удалось (недоступен API/Vault, пустое имя, нет записи), в выводе apply
  появляется Warning. Раньше поле молча становилось null и причина была невидима.
  Отсутствие секретов у родителя ошибкой не считается (для части сервисов это норма).
- В шаблоне подресурса заполнение вынесено в одно замыкание applyPassword(),
  вызываемое перед каждым resp.State.Set в Create (4 сохранения — 4 вызова).
  Убирает четыре одинаковые строки и снижает риск забыть новую ветку выхода.
- Тест проверяет: замыкание есть, предупреждение есть, и вызовов applyPassword()
  не меньше, чем сохранений состояния в Create.

Проверено: generated/test/go/90_postgres_user_resource.go — 4 State.Set, 4 вызова
(плюс одно упоминание в комментарии); go test ./... ok; go build ./internal/... и
полная сборка провайдера во временной копии с новым generated — чисто.
This commit is contained in:
Repinoid
2026-10-01 19:31:35 +03:00
parent 64328abe55
commit 93784e44a7
3 changed files with 68 additions and 29 deletions
@@ -81,24 +81,47 @@ func ExtractUserPassword(secretJSON string, username string) (string, error) {
// - удалось прочитать пароль → конкретное значение;
// - не удалось → возвращается 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)
}
}
}
//
// Второй результат — текст предупреждения: пустая строка, если всё хорошо либо у
// родителя вообще нет секретов в Vault (для части сервисов это норма). Почему не
// просто тихо вернуть null: иначе "пароль не прочитан" выглядел бы в apply как
// успешный пустой пароль, и причина (недоступен API/Vault, нет имени, нет записи)
// осталась бы невидимой.
func ResolveUserPasswordFromVault(ctx context.Context, client *core.UniversalClient, instanceUID string, username string, current types.String) (types.String, string) {
fallback := func() types.String {
if current.IsNull() || current.IsUnknown() {
return types.StringNull()
}
return current
}
if client == nil || strings.TrimSpace(instanceUID) == "" {
return fallback(), ""
}
details, err := client.GetInstanceStateDetails(ctx, instanceUID)
if err != nil {
return fallback(), fmt.Sprintf("не удалось прочитать состояние родительского инстанса %s: %v", instanceUID, err)
}
if details == nil || len(details.Vault.Fields) == 0 {
// У родителя нет секретов Vault — не ошибка.
return fallback(), ""
}
secrets, serr := client.GetInstanceVaultSecrets(ctx, instanceUID, details.Vault.Fields)
if serr != nil {
return fallback(), fmt.Sprintf("не удалось прочитать секреты Vault %v у инстанса %s: %v", details.Vault.Fields, instanceUID, serr)
}
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
return fallback(), fmt.Sprintf("пароль пользователя %q не найден в секретах Vault %v (инстанс %s)", username, details.Vault.Fields, instanceUID)
}