Files
tf_provider/universal_rebuild/internal/provider/provider.go
T

195 lines
7.9 KiB
Go

package provider
import (
"context"
"crypto/tls"
"net"
"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"
utls "github.com/refraction-networking/utls"
)
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
transport.ForceAttemptHTTP2 = false
// utls: маскируем Go TLS под Chrome, чтобы пройти DDoS-Guard (JA3 fingerprint).
// Стандартный crypto/tls блокируется на deck-api-*.ngcloud.ru.
transport.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
dialer := &net.Dialer{Timeout: 30 * time.Second}
conn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(addr)
uconn := utls.UClient(conn, &utls.Config{
ServerName: host,
InsecureSkipVerify: insecureSkipVerify,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1"},
}, utls.HelloChrome_120)
if err := uconn.HandshakeContext(ctx); err != nil {
conn.Close()
return nil, err
}
return uconn, nil
}
// Определяем уровень логирования операций: 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
}