docs: Opus questions round 2 — deep analysis of generation scripts and tools
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
# Вопросы к Opus 4.8: Анализ генерационных скриптов и промежуточного кода
|
||||
|
||||
> **Контекст:** Ты уже проанализировал ПРОВАЙДЕР (client.go, crud.go, gen_v2). Теперь нужен анализ СКРИПТОВ ГЕНЕРАЦИИ — как API-данные превращаются в YAML, Go-код и документацию.
|
||||
> **Режим:** Plan (только чтение). Все пути относительно `/home/naeel/tf_provider/`.
|
||||
|
||||
---
|
||||
|
||||
## Вопрос 1: Bash-скрипты — полный аудит
|
||||
|
||||
**Файлы:**
|
||||
- `devops/01_generate_yamls.sh` — API → YAML
|
||||
- `devops/02_generate_resources_and_docs_v2.sh` — YAML → Go + Docs
|
||||
- `devops/03_build_and_upload_provider.sh` — сборка + S3
|
||||
- `devops/04_build_and_publish_docs.sh` — публикация документации
|
||||
|
||||
**Задача:** прочитай ВСЕ 4 скрипта полностью. Для каждого проверь:
|
||||
|
||||
1. **Обработка ошибок:**
|
||||
- Что происходит при `set -e` — на каком шаге скрипт упадёт?
|
||||
- Есть ли `trap` для очистки временных файлов?
|
||||
- Что если API вернул 500? Пустой ответ? Битый JSON?
|
||||
- Что если `services_list.txt` пустой?
|
||||
|
||||
2. **Зависимости:**
|
||||
- Какие внешние инструменты нужны (go, python3, mc, gpg, mkdocs)?
|
||||
- Проверяются ли они перед запуском?
|
||||
- Что если инструмент отсутствует — понятная ли ошибка?
|
||||
|
||||
3. **Идемпотентность:**
|
||||
- Можно ли перезапустить скрипт при обрыве?
|
||||
- Чистит ли он предыдущий мусор перед генерацией?
|
||||
- Что с параллельным запуском двух экземпляров?
|
||||
|
||||
4. **User-Agent / DDoS-Guard:**
|
||||
- `01_generate_yamls.sh` вызывает python `urllib` для получения имён сервисов (строка ~170). Есть ли там User-Agent?
|
||||
- Как Go-бинари (`service_spec_gen`) получают токен? Через env?
|
||||
|
||||
5. **Профили (test/prod/dev):**
|
||||
- Как скрипты определяют какой профиль использовать?
|
||||
- Что если `--profile` не указан? Есть ли защита от запуска без профиля?
|
||||
- Где хранятся сгенерированные артефакты для каждого профиля?
|
||||
|
||||
---
|
||||
|
||||
## Вопрос 2: service_spec_gen — API → YAML (ключевой генератор)
|
||||
|
||||
**Файл:** `universal_rebuild/tools/service_spec_gen/generate_service_spec.go`
|
||||
|
||||
Мы уже добавили User-Agent, но проверь остальное:
|
||||
|
||||
1. **HTTP-клиент:**
|
||||
- Есть ли retry при ошибках API (429, 503, network)?
|
||||
- Какой таймаут? `http.Client{Timeout: 30s}` — достаточно ли?
|
||||
- Используется ли `http.DefaultClient` или свой?
|
||||
|
||||
2. **Парсинг ответа API:**
|
||||
- Структуры `serviceInfo`, `serviceOperationInfo`, `operationInfo` — все ли поля API маппятся?
|
||||
- Есть ли поля которые API возвращает, но генератор игнорирует (потеря данных)?
|
||||
- Что если API вернёт неожиданный формат — будет `panic` или ошибка?
|
||||
|
||||
3. **Классификация операций (`classifyOperation`):**
|
||||
- Как определяется `kind` (instance/subresource/action)?
|
||||
- Правило: `create/modify/delete/suspend/resume` → instance, `create_*/modify_*/delete_*` → subresource, остальное → action. Все ли кейсы покрыты?
|
||||
- Что с новыми префиксами, которые могут появиться (например, `restart_*`)?
|
||||
|
||||
4. **Сбор параметров:**
|
||||
- Поля `ParamSpec` — все ли constraints из API сохраняются (`maxLength`, `minLength`, `regex`, `uniqueScope`, `dependsOn`)?
|
||||
- `normalizeDefault` — правильно ли обрабатывает null/числа/строки?
|
||||
- `normalizeValueList` — преобразует `[]interface{}` в `[]string`. Что если элемент не строка?
|
||||
|
||||
5. **Выходной YAML:**
|
||||
- `yaml.Marshal` — сохраняет ли все поля корректно (omitempty, вложенные структуры)?
|
||||
- Имя файла: `{id}_{name}.yaml`. Что если имя содержит спецсимволы?
|
||||
|
||||
---
|
||||
|
||||
## Вопрос 3: gen_v2 — YAML → Go (уже проанализирован, но проверь детали)
|
||||
|
||||
**Файл:** `universal_rebuild/tools/gen_v2/generate_resources_v2.go`
|
||||
|
||||
Мы уже добавили `validateSpec()`. Проверь:
|
||||
|
||||
1. **Обработка ошибок генерации:**
|
||||
- `format.Source` — что при ошибке форматирования? Пишет битый код или паникует?
|
||||
- `writeInstanceResource` / `writeSubresource` / `writeActionResource` — обрабатывают ли ошибки `template.Execute`?
|
||||
|
||||
2. **Вычисление CreateOnly:**
|
||||
- `computeCreateOnly` = параметр в create но не в modify → ForceNew. Что если modify-операция существует но не содержит этот параметр потому что он неизменяем? Это корректно?
|
||||
|
||||
3. **Subresource identity:**
|
||||
- Если нет modify → все параметры ForceNew. Если есть modify → как определяется identity?
|
||||
- `buildSubresourceForceNewCodes` — правильная ли логика?
|
||||
|
||||
4. **JSON-параметры:**
|
||||
- `data_type: json` → `IsJson=true` + `JsonNormalize` plan-modifier. Достаточно ли этого для всех JSON-типов (map, array, object)?
|
||||
|
||||
---
|
||||
|
||||
## Вопрос 4: docs_template_gen_v2 — генерация документации
|
||||
|
||||
**Файлы:** `universal_rebuild/tools/docs_template_gen_v2/main.go`
|
||||
|
||||
1. **Что именно генерируется?**
|
||||
- Какие страницы/файлы создаются?
|
||||
- Формат выхода — Markdown для mkdocs?
|
||||
- Все ли поля из YAML используются (MAN, params, outputs, lifecycle)?
|
||||
|
||||
2. **Покрытие:**
|
||||
- Генерируется ли документация для subresource и action ресурсов?
|
||||
- Есть ли index/overview страницы?
|
||||
- Примеры (HCL examples) — откуда берутся?
|
||||
|
||||
3. **Ошибки:**
|
||||
- Что при битом YAML? Пустых операциях?
|
||||
- Флаг `-exclude clickhouse` — почему хардкод?
|
||||
|
||||
---
|
||||
|
||||
## Вопрос 5: service_ops_gen и service_params_gen — нужны ли они?
|
||||
|
||||
**Файлы:**
|
||||
- `universal_rebuild/tools/service_ops_gen/generate_service_ops.go`
|
||||
- `universal_rebuild/tools/service_params_gen/generate_service_params.go`
|
||||
|
||||
Эти тулы выглядят как **устаревшие** (в коде написано APPEND-ONLY). `service_spec_gen` заменил их (генерирует унифицированный YAML вместо отдельных ops/params YAML).
|
||||
|
||||
1. Используются ли они где-то в скриптах?
|
||||
2. Можно ли их удалить?
|
||||
3. Есть ли `gen/` (v1) — он ещё нужен?
|
||||
|
||||
---
|
||||
|
||||
## Вопрос 6: embed.go и resources_yaml
|
||||
|
||||
**Файлы:**
|
||||
- `universal_rebuild/resources_yaml/embed.go`
|
||||
- `devops/profiles/*/generated/resources_yaml/embed.go`
|
||||
|
||||
1. `embed.go` использует `//go:embed *.yaml` — встраивает YAML в бинарник. Зачем?
|
||||
2. Где в коде провайдера используется `resources_yaml.Files`?
|
||||
3. Почему `embed.go` создаётся bash-скриптом, а не лежит в репо?
|
||||
|
||||
---
|
||||
|
||||
## Вопрос 7: Профили и CI
|
||||
|
||||
**Файлы:**
|
||||
- `devops/profiles/test/`, `prod/`, `dev/`
|
||||
- `devops/ci/pipeline.yaml`
|
||||
- `.github/workflows/`
|
||||
|
||||
1. Как CI-пайплайн связан с профилями?
|
||||
2. Есть ли автоматический прогон генерации в CI?
|
||||
3. Как происходит деплой — через operator в K8s или вручную?
|
||||
4. `devops/10_yaml_stability_run.sh` — зачем несколько скриптов стабильности (10-13)?
|
||||
|
||||
---
|
||||
|
||||
## Вопрос 8: Практическая проверка
|
||||
|
||||
Мы сегодня попробовали сгенерировать YAML для postgres (service_id=90) и обнаружили что API добавил 2 новые операции (`backup`, `reconcile`) которых нет в старом сгенерированном коде.
|
||||
|
||||
**Задача:** проверь ВСЕ сервисы из `services_list.txt` на наличие YAML в `devops/profiles/test/generated/resources_yaml/`. Какие сервисы есть в списке но отсутствуют в сгенерированных YAML? Какие YAML есть но сервис исключён из списка?
|
||||
|
||||
---
|
||||
|
||||
## Формат ответа
|
||||
|
||||
На каждый вопрос: **что прочитал → проблема → предложение → приоритет (P0/P1/P2)**.
|
||||
|
||||
Особое внимание:
|
||||
- Bash-скриптам (01_generate_yamls.sh — самый критичный)
|
||||
- HTTP-клиентам в Go-генераторах (UA, retry, таймауты)
|
||||
- Целостности данных (не теряем ли поля API при генерации YAML)
|
||||
@@ -0,0 +1,330 @@
|
||||
package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: postgres
|
||||
// Subresource: backup
|
||||
|
||||
var _ resource.Resource = &PostgresBackupResource{}
|
||||
|
||||
// PostgresBackupResource manages a subresource via operations.
|
||||
type PostgresBackupResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type PostgresBackupModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
PostgresID types.String `tfsdk:"postgres_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewPostgresBackupResource() resource.Resource {
|
||||
return &PostgresBackupResource{}
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_postgres_backup"
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"postgres_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"skip_missing_on_delete": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.PostgresID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{}
|
||||
identityCodes := []string{}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("backup", identityCodes)
|
||||
targetValue := strings.TrimSpace(idParams[idKey])
|
||||
if targetValue == "" {
|
||||
for _, code := range identityCodes {
|
||||
candidate := strings.TrimSpace(idParams[code])
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
targetValue = candidate
|
||||
idKey = code
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetValue != "" {
|
||||
found, known, err := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
if known && found {
|
||||
if adoptExistingOnCreate {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Объект уже есть, выполняется усыновление: "+targetValue)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс уже существует", "Найден объект с таким именем: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
operationTimeout := ""
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_backup", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_backup", params, operationTimeout)
|
||||
}
|
||||
if createErr != nil {
|
||||
if targetValue != "" {
|
||||
found, known, checkErr := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
|
||||
if checkErr != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", checkErr.Error())
|
||||
return
|
||||
}
|
||||
if known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс подтверждён в state_out", "Операция create вернула ошибку, но объект найден в state_out и принят в state: "+targetValue)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
return
|
||||
}
|
||||
}
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(createErr) {
|
||||
if targetValue != "" {
|
||||
found, known, checkErr := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
|
||||
if checkErr != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", checkErr.Error())
|
||||
return
|
||||
}
|
||||
if known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление")
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
return
|
||||
}
|
||||
}
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
found, known, err := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
if known && !found {
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция create завершилась успешно, но объект не найден в state_out: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
var state PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.ID.IsNull() || plan.ID.IsUnknown() {
|
||||
plan.ID = state.ID
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.PostgresID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{}
|
||||
|
||||
operationTimeout := ""
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(state.PostgresID.ValueString())
|
||||
if instanceUID == "" {
|
||||
return
|
||||
}
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{}
|
||||
identityCodes := []string{}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("backup", identityCodes)
|
||||
targetValue := strings.TrimSpace(idParams[idKey])
|
||||
if targetValue == "" {
|
||||
for _, code := range identityCodes {
|
||||
candidate := strings.TrimSpace(idParams[code])
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
targetValue = candidate
|
||||
idKey = code
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetValue != "" {
|
||||
found, known, err := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
if known && !found {
|
||||
if skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
operationTimeout := ""
|
||||
if !state.OperationTimeout.IsNull() && !state.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = state.OperationTimeout.ValueString()
|
||||
}
|
||||
if !state.LogLevel.IsNull() && !state.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, state.LogLevel.ValueString())
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
found, known, checkErr := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
|
||||
if checkErr != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", checkErr.Error())
|
||||
return
|
||||
}
|
||||
if known && found {
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция delete вернула not found, но объект всё ещё присутствует в state_out: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
if skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Операция delete вернула not found, объект отсутствует в state_out, удаление пропущено")
|
||||
return
|
||||
}
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if targetValue != "" {
|
||||
found, known, err := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
if known && found {
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция delete завершилась успешно, но объект всё ещё присутствует в state_out: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
client, ok := req.ProviderData.(*core.UniversalClient)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError("Error", "Invalid client type")
|
||||
return
|
||||
}
|
||||
r.client = client
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: postgres
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &PostgresReconcileResource{}
|
||||
|
||||
// PostgresReconcileResource triggers a one-time action.
|
||||
type PostgresReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type PostgresReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
PostgresID types.String `tfsdk:"postgres_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewPostgresReconcileResource() resource.Resource {
|
||||
return &PostgresReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_postgres_reconcile"
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"postgres_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan PostgresReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.PostgresID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
operationTimeout := ""
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state PostgresReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan PostgresReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.PostgresID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
client, ok := req.ProviderData.(*core.UniversalClient)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError("Error", "Invalid client type")
|
||||
return
|
||||
}
|
||||
r.client = client
|
||||
}
|
||||
Reference in New Issue
Block a user