package provider import ( "context" "crypto/tls" "net/http" "os" "strings" "time" "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" "terraform-provider-mycloud/internal/core" "terraform-provider-mycloud/internal/generated" ) 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". // Может быть переопределён на уровне ресурса через атрибут log_level ресурса. LogLevel types.String `tfsdk:"log_level"` } type NubesClient struct { HttpClient *http.Client ApiEndpoint string ApiToken string } 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: уровень вывода этапов операций во время terraform apply/destroy. // "none" (default) — не выводить ничего. // "info" — выводить строку на каждый этап: [OK ] Валидация — 74.8 sec // "debug" — как info + детали каждого этапа без timestamp-мусора. // Может быть переопределён на уровне ресурса через атрибут log_level. "log_level": schema.StringAttribute{ MarkdownDescription: "Operation stages log level: none (default), info, debug.", 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 } // Default values 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 { // Try to get from environment if token := os.Getenv("NUBES_API_TOKEN"); token != "" { apiToken = strings.TrimSpace(token) } else { // Try to read from ~/.nubes_token file homeDir, err := os.UserHomeDir() if err == nil { tokenFile := homeDir + "/.nubes_token" if data, err := os.ReadFile(tokenFile); err == nil { apiToken = strings.TrimSpace(string(data)) } } } } // --- 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 } // Custom transport based on DefaultTransport. // Клонируем 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 // Use Core Universal Client client := &core.UniversalClient{ HttpClient: &http.Client{ Transport: transport, Timeout: 300 * time.Second, }, ApiEndpoint: apiEndpoint, ApiToken: apiToken, LogLevel: config.LogLevel.ValueString(), } resp.DataSourceData = client resp.ResourceData = client } func (p *NubesProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ NewTubulusResource, NewOrganizationResource, NewVMResource, NewVDCResource, NewEdgeResource, NewVAppResource, NewQuickStartResource, NewPostgresResource, NewS3BucketResource, NewPgAdminResource, // Generated Resources generated.NewBolvankaResource, generated.NewBolvankaUniversalResource, generated.NewBolvankaUniversalLifecycleResource, } } func (p *NubesProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewVDCDataSource, NewEdgeDataSource, NewVAppDataSource, NewServiceInstanceDataSource, } }