From d2c4ad29796914e8c856e0a3d14b108e244a6b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Tue, 30 Jun 2026 15:44:36 +0400 Subject: [PATCH] add: universal provider --- .../internal/provider/operation_timeouts.json | 17 ++ .../provider/operation_timeouts_embed.go | 6 + .../internal/provider/provider.go | 177 ++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 universal_rebuild/internal/provider/operation_timeouts.json create mode 100644 universal_rebuild/internal/provider/operation_timeouts_embed.go create mode 100644 universal_rebuild/internal/provider/provider.go diff --git a/universal_rebuild/internal/provider/operation_timeouts.json b/universal_rebuild/internal/provider/operation_timeouts.json new file mode 100644 index 0000000..cbf0baa --- /dev/null +++ b/universal_rebuild/internal/provider/operation_timeouts.json @@ -0,0 +1,17 @@ +{ + "default_timeout": "10m", + "idle_timeout": "10m", + "operations": { + "create": "10m", + "modify": "7m", + "resume": "7m", + "suspend": "7m", + "create_user": "7m", + "delete_user": "6m", + "create_database": "7m", + "delete_database": "6m" + }, + "overrides": { + "services.90.create": "7m" + } +} diff --git a/universal_rebuild/internal/provider/operation_timeouts_embed.go b/universal_rebuild/internal/provider/operation_timeouts_embed.go new file mode 100644 index 0000000..45e94b9 --- /dev/null +++ b/universal_rebuild/internal/provider/operation_timeouts_embed.go @@ -0,0 +1,6 @@ +package provider + +import _ "embed" + +//go:embed operation_timeouts.json +var operationTimeoutsConfigEmbedded []byte diff --git a/universal_rebuild/internal/provider/provider.go b/universal_rebuild/internal/provider/provider.go new file mode 100644 index 0000000..32467d0 --- /dev/null +++ b/universal_rebuild/internal/provider/provider.go @@ -0,0 +1,177 @@ +package provider + +import ( + "context" + "crypto/tls" + "net/http" + "os" + "strings" + "time" + + "terraform-provider-nubes/internal/core" + "terraform-provider-nubes/internal/resources_core" + "terraform-provider-nubes/internal/resources_gen" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ provider.Provider = &NubesProvider{} + +type NubesProvider struct { + version string +} + +// NubesProviderModel — конфигурация провайдера из HCL-блока provider {}. +// Каждое поле соответствует атрибуту в Schema() ниже. +// Поля Optional: если не указаны — берутся из env-переменных или defaults. +type NubesProviderModel struct { + ApiEndpoint types.String `tfsdk:"api_endpoint"` + ApiToken types.String `tfsdk:"api_token"` + // Insecure отключает проверку TLS-сертификата сервера. + // НЕ использовать в продакшн! Только для dev-стендов с самоподписанным сертом. + // Может быть задан также через env NUBES_INSECURE=true. + Insecure types.Bool `tfsdk:"insecure"` + // LogLevel задаёт глобальный уровень вывода этапов операции. + // "none" (default) — молчать. "info" — имя этапа + длительность. + // "debug" — info + детали подэтапов (StageMsg без timestamp-мусора). + // Может быть переопределён атрибутом log_level в отдельном ресурсе. + LogLevel types.String `tfsdk:"log_level"` +} + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &NubesProvider{version: version} + } +} + +func (p *NubesProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "nubes" + resp.Version = p.version +} + +func (p *NubesProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "api_endpoint": schema.StringAttribute{ + MarkdownDescription: "API Gateway endpoint for Nubes Cloud", + Optional: true, + }, + "api_token": schema.StringAttribute{ + MarkdownDescription: "API authentication token", + Optional: true, + Sensitive: true, + }, + // insecure: отключает проверку TLS-сертификата Nubes API. + // По умолчанию false — сертификат проверяется (безопасно). + // Устанавливать true только на dev-стенде с самоподписанным сертом. + // Альтернатива: env NUBES_INSECURE=true (не требует правки .tf файлов). + "insecure": schema.BoolAttribute{ + MarkdownDescription: "Disable TLS certificate verification. Use only for dev environments with self-signed certs. Can also be set via NUBES_INSECURE env var.", + Optional: true, + }, + // log_level: уровень вывода информации об этапах операций. + // Значение применяется ко всем ресурсам, если не переопределено в ресурсе. + "log_level": schema.StringAttribute{ + MarkdownDescription: "Logging level for operation stages. Values: `none` (default, silent), `info` (stage name + duration), `debug` (info + detailed sub-step messages). Can be overridden per resource.", + Optional: true, + }, + }, + } +} + +func (p *NubesProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var config NubesProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + apiEndpoint := "https://deck-api.ngcloud.ru/api/v1/index.cfm" + apiToken := "" + + if !config.ApiEndpoint.IsNull() { + apiEndpoint = config.ApiEndpoint.ValueString() + } + + if !config.ApiToken.IsNull() { + apiToken = strings.TrimSpace(config.ApiToken.ValueString()) + } else { + if token := os.Getenv("NUBES_API_TOKEN"); token != "" { + apiToken = strings.TrimSpace(token) + } + } + + // --- TLS: определяем нужно ли пропустить проверку сертификата --- + // Приоритет: config.insecure > env NUBES_INSECURE > false (безопасный default). + // Nubes Cloud API использует валидный TLS-сертификат от доверенного CA, + // поэтому в продакшн InsecureSkipVerify должен быть false. + // true оставлен только для совместимости с dev-стендами без нормального сертификата. + insecureSkipVerify := false + if !config.Insecure.IsNull() && !config.Insecure.IsUnknown() { + // Явно задано в provider {} блоке HCL + insecureSkipVerify = config.Insecure.ValueBool() + } else if os.Getenv("NUBES_INSECURE") == "true" { + // Задано через переменную окружения (удобно для CI/CD без правки .tf файлов) + insecureSkipVerify = true + } + + // HTTP transport: клонируем DefaultTransport чтобы сохранить системные настройки (proxy, timeouts). + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSHandshakeTimeout = 60 * time.Second + // Force HTTP/1.1: Nubes API не поддерживает HTTP/2, принудительно отключаем. + // MinVersion TLS 1.2 — минимально безопасная версия TLS. + transport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: insecureSkipVerify, // false в prod, true только для dev + NextProtos: []string{"http/1.1"}, + MinVersion: tls.VersionTLS12, + } + transport.ForceAttemptHTTP2 = false + + // Определяем уровень логирования операций: none (тихий) / info / debug. + // none — дефолт, не засорять вывод terraform apply лишними строками. + logLevel := "none" + if !config.LogLevel.IsNull() && !config.LogLevel.IsUnknown() { + if v := strings.TrimSpace(config.LogLevel.ValueString()); v != "" { + logLevel = v + } + } + + client := &core.UniversalClient{ + HttpClient: &http.Client{ + Transport: transport, + Timeout: 300 * time.Second, + }, + ApiEndpoint: apiEndpoint, + ApiToken: apiToken, + ProviderVersion: p.version, + OperationTimeouts: core.DefaultOperationTimeouts(), + LogLevel: logLevel, + } + + if len(operationTimeoutsConfigEmbedded) > 0 { + timeouts, err := core.LoadOperationTimeoutsFromBytes(operationTimeoutsConfigEmbedded) + if err != nil { + resp.Diagnostics.AddError("Ошибка загрузки таймаутов операций", err.Error()) + return + } + client.OperationTimeouts = timeouts + } + + resp.DataSourceData = client + resp.ResourceData = client +} + +func (p *NubesProvider) Resources(ctx context.Context) []func() resource.Resource { + resources := resources_gen.AllResources() + resources = append(resources, resources_core.NewServiceOperationResource) + return resources +} + +func (p *NubesProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return nil +}