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 }