702 lines
25 KiB
Go
702 lines
25 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package helm
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
|
"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/schema/validator"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
"helm.sh/helm/v3/pkg/action"
|
|
"helm.sh/helm/v3/pkg/cli"
|
|
"helm.sh/helm/v3/pkg/registry"
|
|
"helm.sh/helm/v3/pkg/storage/driver"
|
|
)
|
|
|
|
var _ provider.Provider = &HelmProvider{}
|
|
|
|
func New(version string) func() provider.Provider {
|
|
return func() provider.Provider {
|
|
return &HelmProvider{
|
|
version: version,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Meta contains the client configuration for the provider
|
|
type Meta struct {
|
|
providerData *HelmProvider
|
|
Data *HelmProviderModel
|
|
Settings *cli.EnvSettings
|
|
RegistryClient *registry.Client
|
|
HelmDriver string
|
|
// Experimental feature toggles
|
|
Experiments map[string]bool
|
|
Mutex sync.Mutex
|
|
}
|
|
|
|
// HelmProviderModel contains the configuration for the provider
|
|
type HelmProviderModel struct {
|
|
Debug types.Bool `tfsdk:"debug"`
|
|
PluginsPath types.String `tfsdk:"plugins_path"`
|
|
RegistryConfigPath types.String `tfsdk:"registry_config_path"`
|
|
RepositoryConfigPath types.String `tfsdk:"repository_config_path"`
|
|
RepositoryCache types.String `tfsdk:"repository_cache"`
|
|
HelmDriver types.String `tfsdk:"helm_driver"`
|
|
BurstLimit types.Int64 `tfsdk:"burst_limit"`
|
|
Kubernetes types.Object `tfsdk:"kubernetes"`
|
|
Registries types.List `tfsdk:"registries"`
|
|
Experiments *ExperimentsConfigModel `tfsdk:"experiments"`
|
|
}
|
|
|
|
// ExperimentsConfigModel configures the experiments that are enabled or disabled
|
|
type ExperimentsConfigModel struct {
|
|
Manifest types.Bool `tfsdk:"manifest"`
|
|
}
|
|
|
|
// RegistryConfigModel configures an OCI registry
|
|
type RegistryConfigModel struct {
|
|
URL types.String `tfsdk:"url"`
|
|
Username types.String `tfsdk:"username"`
|
|
Password types.String `tfsdk:"password"`
|
|
}
|
|
|
|
// KubernetesConfigModel configures a Kubernetes client
|
|
type KubernetesConfigModel struct {
|
|
Host types.String `tfsdk:"host"`
|
|
Username types.String `tfsdk:"username"`
|
|
Password types.String `tfsdk:"password"`
|
|
Insecure types.Bool `tfsdk:"insecure"`
|
|
TLSServerName types.String `tfsdk:"tls_server_name"`
|
|
ClientCertificate types.String `tfsdk:"client_certificate"`
|
|
ClientKey types.String `tfsdk:"client_key"`
|
|
ClusterCACertificate types.String `tfsdk:"cluster_ca_certificate"`
|
|
ConfigPaths types.List `tfsdk:"config_paths"`
|
|
ConfigPath types.String `tfsdk:"config_path"`
|
|
ConfigContext types.String `tfsdk:"config_context"`
|
|
ConfigContextAuthInfo types.String `tfsdk:"config_context_auth_info"`
|
|
ConfigContextCluster types.String `tfsdk:"config_context_cluster"`
|
|
Token types.String `tfsdk:"token"`
|
|
ProxyURL types.String `tfsdk:"proxy_url"`
|
|
Exec *ExecConfigModel `tfsdk:"exec"`
|
|
}
|
|
|
|
// ExecConfigModel configures an external command to configure the Kubernetes client
|
|
type ExecConfigModel struct {
|
|
APIVersion types.String `tfsdk:"api_version"`
|
|
Command types.String `tfsdk:"command"`
|
|
Env types.Map `tfsdk:"env"`
|
|
Args types.List `tfsdk:"args"`
|
|
}
|
|
|
|
// HelmProvider is the top level provider struct
|
|
type HelmProvider struct {
|
|
meta *Meta
|
|
version string
|
|
}
|
|
|
|
func (p *HelmProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
|
|
resp.TypeName = "helm"
|
|
resp.Version = p.version
|
|
}
|
|
|
|
// /////////////////////// START OF SCHEMA CREATION ///////////////////////////////
|
|
// Defines attributes that are avaiable in the provider
|
|
func (p *HelmProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: "Schema to define attributes that are available in the provider",
|
|
Attributes: map[string]schema.Attribute{
|
|
"debug": schema.BoolAttribute{
|
|
Description: "Debug indicates whether or not Helm is running in Debug mode.",
|
|
Optional: true,
|
|
},
|
|
"plugins_path": schema.StringAttribute{
|
|
Description: "The path to the helm plugins directory",
|
|
Optional: true,
|
|
},
|
|
"registry_config_path": schema.StringAttribute{
|
|
Description: "The path to the registry config file",
|
|
Optional: true,
|
|
},
|
|
"repository_config_path": schema.StringAttribute{
|
|
Description: "The path to the file containing repository names and URLs",
|
|
Optional: true,
|
|
},
|
|
"repository_cache": schema.StringAttribute{
|
|
Description: "The path to the file containing cached repository indexes",
|
|
Optional: true,
|
|
},
|
|
"helm_driver": schema.StringAttribute{
|
|
Description: "The backend storage driver. Values are: configmap, secret, memory, sql",
|
|
Optional: true,
|
|
Validators: []validator.String{
|
|
stringvalidator.OneOf(strings.ToLower(driver.MemoryDriverName),
|
|
strings.ToLower(driver.ConfigMapsDriverName),
|
|
strings.ToLower(driver.SecretsDriverName),
|
|
strings.ToLower(driver.SQLDriverName)),
|
|
},
|
|
},
|
|
|
|
"burst_limit": schema.Int64Attribute{
|
|
Optional: true,
|
|
Description: "Helm burst limit. Increase this if you have a cluster with many CRDs",
|
|
},
|
|
"kubernetes": schema.SingleNestedAttribute{
|
|
Optional: true,
|
|
Description: "Kubernetes Configuration",
|
|
Attributes: kubernetesResourceSchema(),
|
|
},
|
|
"registries": schema.ListNestedAttribute{
|
|
Optional: true,
|
|
Description: "RegistryClient configuration.",
|
|
NestedObject: schema.NestedAttributeObject{
|
|
Attributes: registriesResourceSchema(),
|
|
},
|
|
},
|
|
"experiments": schema.SingleNestedAttribute{
|
|
Optional: true,
|
|
Description: "Enable and disable experimental features.",
|
|
Attributes: experimentsSchema(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func experimentsSchema() map[string]schema.Attribute {
|
|
return map[string]schema.Attribute{
|
|
"manifest": schema.BoolAttribute{
|
|
Optional: true,
|
|
Description: "Enable full diff by storing the rendered manifest in the state.",
|
|
},
|
|
}
|
|
}
|
|
|
|
func registriesResourceSchema() map[string]schema.Attribute {
|
|
return map[string]schema.Attribute{
|
|
"url": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "OCI URL in form of oci://host:port or oci://host",
|
|
},
|
|
"username": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "The username to use for the OCI HTTP basic authentication when accessing the Kubernetes master endpoint.",
|
|
},
|
|
"password": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "The password to use for the OCI HTTP basic authentication when accessing the Kubernetes master endpoint.",
|
|
},
|
|
}
|
|
}
|
|
|
|
func kubernetesResourceSchema() map[string]schema.Attribute {
|
|
return map[string]schema.Attribute{
|
|
"host": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "The hostname (in form of URI) of kubernetes master",
|
|
},
|
|
"username": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint",
|
|
},
|
|
"password": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
|
|
},
|
|
"insecure": schema.BoolAttribute{
|
|
Optional: true,
|
|
Description: "Whether server should be accessed without verifying the TLS certificate.",
|
|
},
|
|
"tls_server_name": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "Server name passed to the server for SNI and is used in the client to check server certificates against.",
|
|
},
|
|
"client_certificate": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "PEM-encoded client certificate for TLS authentication.",
|
|
},
|
|
"client_key": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "PEM-encoded client certificate key for TLS authentication.",
|
|
},
|
|
"cluster_ca_certificate": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "PEM-encoded root certificates bundle for TLS authentication.",
|
|
},
|
|
"config_paths": schema.ListAttribute{
|
|
Optional: true,
|
|
ElementType: types.StringType,
|
|
Description: "A list of paths to kube config files. Can be set with KUBE_CONFIG_PATHS environment variable.",
|
|
},
|
|
"config_path": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH.",
|
|
Validators: []validator.String{
|
|
stringvalidator.ConflictsWith(
|
|
path.Root("kubernetes").AtName("config_paths").Expression(),
|
|
),
|
|
},
|
|
},
|
|
|
|
"config_context": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "Context to choose from the config file. Can be sourced from KUBE_CTX.",
|
|
},
|
|
"config_context_auth_info": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "Authentication info context of the kube config (name of the kubeconfig user, --user flag in kubectl). Can be sourced from KUBE_CTX_AUTH_INFO.",
|
|
},
|
|
"config_context_cluster": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "Cluster context of the kube config (name of the kubeconfig cluster, --cluster flag in kubectl). Can be sourced from KUBE_CTX_CLUSTER.",
|
|
},
|
|
"token": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "Token to authenticate a service account.",
|
|
},
|
|
"proxy_url": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "URL to the proxy to be used for all API requests.",
|
|
},
|
|
"exec": schema.SingleNestedAttribute{
|
|
Optional: true,
|
|
Description: "Exec configuration for Kubernetes authentication",
|
|
Attributes: execSchema(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func execSchema() map[string]schema.Attribute {
|
|
return map[string]schema.Attribute{
|
|
"api_version": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "API version for the exec plugin.",
|
|
},
|
|
"command": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "Command to run for Kubernetes exec plugin",
|
|
},
|
|
"env": schema.MapAttribute{
|
|
Optional: true,
|
|
ElementType: types.StringType,
|
|
Description: "Environment variables for the exec plugin",
|
|
},
|
|
"args": schema.ListAttribute{
|
|
Optional: true,
|
|
ElementType: types.StringType,
|
|
Description: "Arguments for the exec plugin",
|
|
},
|
|
}
|
|
}
|
|
|
|
func execSchemaAttrTypes() map[string]attr.Type {
|
|
return map[string]attr.Type{
|
|
"api_version": types.StringType,
|
|
"command": types.StringType,
|
|
"args": types.ListType{ElemType: types.StringType},
|
|
"env": types.MapType{ElemType: types.StringType},
|
|
}
|
|
}
|
|
|
|
///////////////////// END OF SCHEMA CREATION ///////////////////////////////
|
|
|
|
// Setting up the provider, anything we need to get the provider running, probbaly authentication. like the api
|
|
func (p *HelmProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
|
|
if req.ClientCapabilities.DeferralAllowed && !req.Config.Raw.IsFullyKnown() {
|
|
resp.Deferred = &provider.Deferred{
|
|
Reason: provider.DeferredReasonProviderConfigUnknown,
|
|
}
|
|
}
|
|
|
|
pluginsPath := os.Getenv("HELM_PLUGINS_PATH")
|
|
registryConfigPath := os.Getenv("HELM_REGISTRY_CONFIG_PATH")
|
|
repositoryConfigPath := os.Getenv("HELM_REPOSITORY_CONFIG_PATH")
|
|
repositoryCache := os.Getenv("HELM_REPOSITORY_CACHE")
|
|
helmDriver := os.Getenv("HELM_DRIVER")
|
|
burstLimitStr := os.Getenv("HELM_BURST_LIMIT")
|
|
kubeHost := os.Getenv("KUBE_HOST")
|
|
kubeUser := os.Getenv("KUBE_USER")
|
|
kubePassword := os.Getenv("KUBE_PASSWORD")
|
|
kubeInsecureStr := os.Getenv("KUBE_INSECURE")
|
|
kubeTLSServerName := os.Getenv("KUBE_TLS_SERVER_NAME")
|
|
kubeClientCert := os.Getenv("KUBE_CLIENT_CERT_DATA")
|
|
kubeClientKey := os.Getenv("KUBE_CLIENT_KEY_DATA")
|
|
kubeCaCert := os.Getenv("KUBE_CLUSTER_CA_CERT_DATA")
|
|
kubeConfigPaths := os.Getenv("KUBE_CONFIG_PATHS")
|
|
kubeConfigPath := os.Getenv("KUBE_CONFIG_PATH")
|
|
kubeConfigContext := os.Getenv("KUBE_CTX")
|
|
kubeConfigContextAuthInfo := os.Getenv("KUBE_CTX_AUTH_INFO")
|
|
kubeConfigContextCluster := os.Getenv("KUBE_CTX_CLUSTER")
|
|
kubeToken := os.Getenv("KUBE_TOKEN")
|
|
kubeProxy := os.Getenv("KUBE_PROXY")
|
|
|
|
// Initialize the HelmProviderModel with values from the config
|
|
var config HelmProviderModel
|
|
diags := req.Config.Get(ctx, &config)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
if !config.PluginsPath.IsNull() {
|
|
pluginsPath = config.PluginsPath.ValueString()
|
|
}
|
|
if !config.RegistryConfigPath.IsNull() {
|
|
registryConfigPath = config.RegistryConfigPath.ValueString()
|
|
}
|
|
if !config.RepositoryConfigPath.IsNull() {
|
|
repositoryConfigPath = config.RepositoryConfigPath.ValueString()
|
|
}
|
|
if !config.RepositoryCache.IsNull() {
|
|
repositoryCache = config.RepositoryCache.ValueString()
|
|
}
|
|
if !config.HelmDriver.IsNull() {
|
|
helmDriver = config.HelmDriver.ValueString()
|
|
}
|
|
var burstLimit int64
|
|
if burstLimitStr != "" {
|
|
var err error
|
|
burstLimit, err = strconv.ParseInt(burstLimitStr, 10, 64)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Invalid burst limit",
|
|
fmt.Sprintf("Invalid burst limit value: %s", burstLimitStr),
|
|
)
|
|
return
|
|
}
|
|
}
|
|
if !config.BurstLimit.IsNull() {
|
|
burstLimit = config.BurstLimit.ValueInt64()
|
|
}
|
|
var kubeInsecure bool
|
|
if kubeInsecureStr != "" {
|
|
var err error
|
|
kubeInsecure, err = strconv.ParseBool(kubeInsecureStr)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Invalid insecure value",
|
|
fmt.Sprintf("Invalid insecure value: %s", kubeInsecureStr),
|
|
)
|
|
return
|
|
}
|
|
}
|
|
|
|
var kubernetesConfig KubernetesConfigModel
|
|
if !config.Kubernetes.IsNull() && !config.Kubernetes.IsUnknown() {
|
|
diags := req.Config.GetAttribute(ctx, path.Root("kubernetes"), &kubernetesConfig)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
}
|
|
|
|
if !kubernetesConfig.Insecure.IsNull() {
|
|
kubeInsecure = kubernetesConfig.Insecure.ValueBool()
|
|
}
|
|
var kubeConfigPathsList []attr.Value
|
|
if !kubernetesConfig.Host.IsNull() {
|
|
kubeHost = kubernetesConfig.Host.ValueString()
|
|
}
|
|
if !kubernetesConfig.Username.IsNull() {
|
|
kubeUser = kubernetesConfig.Username.ValueString()
|
|
}
|
|
if !kubernetesConfig.Password.IsNull() {
|
|
kubePassword = kubernetesConfig.Password.ValueString()
|
|
}
|
|
if !kubernetesConfig.TLSServerName.IsNull() {
|
|
kubeTLSServerName = kubernetesConfig.TLSServerName.ValueString()
|
|
}
|
|
if !kubernetesConfig.ClientCertificate.IsNull() {
|
|
kubeClientCert = kubernetesConfig.ClientCertificate.ValueString()
|
|
}
|
|
if !kubernetesConfig.ClientKey.IsNull() {
|
|
kubeClientKey = kubernetesConfig.ClientKey.ValueString()
|
|
}
|
|
if !kubernetesConfig.ClusterCACertificate.IsNull() {
|
|
kubeCaCert = kubernetesConfig.ClusterCACertificate.ValueString()
|
|
}
|
|
if kubeConfigPaths != "" {
|
|
for _, path := range filepath.SplitList(kubeConfigPaths) {
|
|
kubeConfigPathsList = append(kubeConfigPathsList, types.StringValue(path))
|
|
}
|
|
}
|
|
if !kubernetesConfig.ConfigPaths.IsNull() {
|
|
var paths []string
|
|
diags = kubernetesConfig.ConfigPaths.ElementsAs(ctx, &paths, false)
|
|
resp.Diagnostics.Append(diags...)
|
|
for _, path := range paths {
|
|
kubeConfigPathsList = append(kubeConfigPathsList, types.StringValue(path))
|
|
}
|
|
}
|
|
if !kubernetesConfig.ConfigPath.IsNull() {
|
|
kubeConfigPath = kubernetesConfig.ConfigPath.ValueString()
|
|
}
|
|
if !kubernetesConfig.ConfigContext.IsNull() {
|
|
kubeConfigContext = kubernetesConfig.ConfigContext.ValueString()
|
|
}
|
|
if !kubernetesConfig.ConfigContextAuthInfo.IsNull() {
|
|
kubeConfigContextAuthInfo = kubernetesConfig.ConfigContextAuthInfo.ValueString()
|
|
}
|
|
if !kubernetesConfig.ConfigContextCluster.IsNull() {
|
|
kubeConfigContextCluster = kubernetesConfig.ConfigContextCluster.ValueString()
|
|
}
|
|
if !kubernetesConfig.Token.IsNull() {
|
|
kubeToken = kubernetesConfig.Token.ValueString()
|
|
}
|
|
if !kubernetesConfig.ProxyURL.IsNull() {
|
|
kubeProxy = kubernetesConfig.ProxyURL.ValueString()
|
|
}
|
|
tflog.Debug(ctx, "Config values after overrides", map[string]interface{}{
|
|
"config": config,
|
|
})
|
|
debug := os.Getenv("HELM_DEBUG") == "true" || config.Debug.ValueBool()
|
|
settings := cli.New()
|
|
settings.Debug = debug
|
|
if pluginsPath != "" {
|
|
settings.PluginsDirectory = pluginsPath
|
|
}
|
|
if registryConfigPath != "" {
|
|
settings.RegistryConfig = registryConfigPath
|
|
}
|
|
if repositoryConfigPath != "" {
|
|
settings.RepositoryConfig = repositoryConfigPath
|
|
}
|
|
if repositoryCache != "" {
|
|
settings.RepositoryCache = repositoryCache
|
|
}
|
|
tflog.Debug(ctx, "Helm settings initialized", map[string]interface{}{
|
|
"settings": settings,
|
|
})
|
|
kubeConfigPathsListValue, diags := types.ListValue(types.StringType, kubeConfigPathsList)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
manifestExperiment := false
|
|
if config.Experiments != nil {
|
|
manifestExperiment = config.Experiments.Manifest.ValueBool()
|
|
}
|
|
|
|
var execAttrValue attr.Value = types.ObjectNull(execSchemaAttrTypes())
|
|
|
|
if kubernetesConfig.Exec != nil {
|
|
// Check if `api_version` and `command` are set (since they're required fields)
|
|
if !kubernetesConfig.Exec.APIVersion.IsNull() && !kubernetesConfig.Exec.Command.IsNull() {
|
|
execAttrValue = types.ObjectValueMust(execSchemaAttrTypes(), map[string]attr.Value{
|
|
"api_version": types.StringValue(kubernetesConfig.Exec.APIVersion.ValueString()),
|
|
"command": types.StringValue(kubernetesConfig.Exec.Command.ValueString()),
|
|
"args": types.ListValueMust(types.StringType, kubernetesConfig.Exec.Args.Elements()),
|
|
"env": types.MapValueMust(types.StringType, kubernetesConfig.Exec.Env.Elements()),
|
|
})
|
|
}
|
|
}
|
|
|
|
kubernetesConfigObjectValue, diags := types.ObjectValue(map[string]attr.Type{
|
|
"host": types.StringType,
|
|
"username": types.StringType,
|
|
"password": types.StringType,
|
|
"insecure": types.BoolType,
|
|
"tls_server_name": types.StringType,
|
|
"client_certificate": types.StringType,
|
|
"client_key": types.StringType,
|
|
"cluster_ca_certificate": types.StringType,
|
|
"config_paths": types.ListType{ElemType: types.StringType},
|
|
"config_path": types.StringType,
|
|
"config_context": types.StringType,
|
|
"config_context_auth_info": types.StringType,
|
|
"config_context_cluster": types.StringType,
|
|
"token": types.StringType,
|
|
"proxy_url": types.StringType,
|
|
"exec": types.ObjectType{AttrTypes: execSchemaAttrTypes()},
|
|
}, map[string]attr.Value{
|
|
"host": types.StringValue(kubeHost),
|
|
"username": types.StringValue(kubeUser),
|
|
"password": types.StringValue(kubePassword),
|
|
"insecure": types.BoolValue(kubeInsecure),
|
|
"tls_server_name": types.StringValue(kubeTLSServerName),
|
|
"client_certificate": types.StringValue(kubeClientCert),
|
|
"client_key": types.StringValue(kubeClientKey),
|
|
"cluster_ca_certificate": types.StringValue(kubeCaCert),
|
|
"config_paths": kubeConfigPathsListValue,
|
|
"config_path": types.StringValue(kubeConfigPath),
|
|
"config_context": types.StringValue(kubeConfigContext),
|
|
"config_context_auth_info": types.StringValue(kubeConfigContextAuthInfo),
|
|
"config_context_cluster": types.StringValue(kubeConfigContextCluster),
|
|
"token": types.StringValue(kubeToken),
|
|
"proxy_url": types.StringValue(kubeProxy),
|
|
"exec": execAttrValue,
|
|
})
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
meta := &Meta{
|
|
Data: &HelmProviderModel{
|
|
Debug: types.BoolValue(debug),
|
|
PluginsPath: types.StringValue(pluginsPath),
|
|
RegistryConfigPath: types.StringValue(registryConfigPath),
|
|
RepositoryConfigPath: types.StringValue(repositoryConfigPath),
|
|
RepositoryCache: types.StringValue(repositoryCache),
|
|
HelmDriver: types.StringValue(helmDriver),
|
|
BurstLimit: types.Int64Value(burstLimit),
|
|
Kubernetes: kubernetesConfigObjectValue,
|
|
Experiments: &ExperimentsConfigModel{
|
|
Manifest: types.BoolValue(manifestExperiment),
|
|
},
|
|
},
|
|
Settings: settings,
|
|
HelmDriver: helmDriver,
|
|
Experiments: map[string]bool{
|
|
"manifest": manifestExperiment,
|
|
},
|
|
}
|
|
registryClient, err := registry.NewClient()
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Registry client initialization failed",
|
|
fmt.Sprintf("Unable to create Helm registry client: %s", err),
|
|
)
|
|
return
|
|
}
|
|
|
|
meta.RegistryClient = registryClient
|
|
if !config.Registries.IsUnknown() {
|
|
var registryConfigs []RegistryConfigModel
|
|
diags := config.Registries.ElementsAs(ctx, ®istryConfigs, false)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
for _, r := range registryConfigs {
|
|
if r.URL.IsNull() || r.Username.IsNull() || r.Password.IsNull() {
|
|
resp.Diagnostics.AddError(
|
|
"OCI Registry login failed",
|
|
"Registry URL, Username, or Password is null",
|
|
)
|
|
return
|
|
}
|
|
|
|
err := OCIRegistryPerformLogin(ctx, meta, meta.RegistryClient, r.URL.ValueString(), r.Username.ValueString(), r.Password.ValueString())
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"OCI Registry login failed",
|
|
err.Error(),
|
|
)
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
tflog.Debug(ctx, "No registry configurations found")
|
|
}
|
|
resp.DataSourceData = meta
|
|
resp.ResourceData = meta
|
|
|
|
tflog.Debug(ctx, "Configure method completed successfully")
|
|
}
|
|
|
|
func (p *HelmProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
|
|
return []func() datasource.DataSource{
|
|
NewHelmTemplate,
|
|
}
|
|
}
|
|
|
|
func (p *HelmProvider) Resources(ctx context.Context) []func() resource.Resource {
|
|
return []func() resource.Resource{
|
|
NewHelmRelease,
|
|
}
|
|
}
|
|
|
|
func OCIRegistryLogin(ctx context.Context, meta *Meta, actionConfig *action.Configuration, registryClient *registry.Client, repository, chartName, username, password string) diag.Diagnostics {
|
|
var diags diag.Diagnostics
|
|
|
|
actionConfig.RegistryClient = registryClient
|
|
|
|
var ociURL string
|
|
if registry.IsOCI(repository) {
|
|
ociURL = repository
|
|
} else if registry.IsOCI(chartName) {
|
|
ociURL = chartName
|
|
}
|
|
|
|
if ociURL == "" {
|
|
return diags
|
|
}
|
|
|
|
if username != "" && password != "" {
|
|
err := OCIRegistryPerformLogin(ctx, meta, registryClient, ociURL, username, password)
|
|
if err != nil {
|
|
diags.AddError(
|
|
"OCI Registry Login Failed",
|
|
fmt.Sprintf("Failed to log in to OCI registry %q: %s", ociURL, err.Error()),
|
|
)
|
|
}
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
// registryClient = client used to comm with the registry, oci urls, un, and pw used for authentication
|
|
func OCIRegistryPerformLogin(ctx context.Context, meta *Meta, registryClient *registry.Client, ociURL, username, password string) error {
|
|
loggedInOCIRegistries := make(map[string]string)
|
|
// getting the oci url, and extracting the host.
|
|
u, err := url.Parse(ociURL)
|
|
if err != nil {
|
|
return fmt.Errorf("could not parse OCI registry URL: %v", err)
|
|
}
|
|
meta.Mutex.Lock()
|
|
defer meta.Mutex.Unlock()
|
|
if _, ok := loggedInOCIRegistries[u.Host]; ok {
|
|
tflog.Info(ctx, fmt.Sprintf("Already logged into OCI registry %q", u.Host))
|
|
return nil
|
|
}
|
|
// Now we perform the login, with the provided username and password by calling the login method
|
|
err = registryClient.Login(u.Host, registry.LoginOptBasicAuth(username, password))
|
|
if err != nil {
|
|
return fmt.Errorf("could not login to OCI registry %q: %v", u.Host, err)
|
|
}
|
|
loggedInOCIRegistries[u.Host] = ""
|
|
tflog.Info(ctx, fmt.Sprintf("Logged into OCI registry %q", u.Host))
|
|
return nil
|
|
}
|
|
|
|
// GetHelmConfiguration retrieves the Helm configuration for a given namespace
|
|
func (m *Meta) GetHelmConfiguration(ctx context.Context, namespace string) (*action.Configuration, error) {
|
|
if m == nil {
|
|
tflog.Error(ctx, "Meta is nil")
|
|
return nil, fmt.Errorf("Meta is nil")
|
|
}
|
|
|
|
tflog.Info(context.Background(), "[INFO] GetHelmConfiguration start")
|
|
actionConfig := new(action.Configuration)
|
|
kc, err := m.NewKubeConfig(ctx, namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := actionConfig.Init(kc, namespace, m.HelmDriver, func(format string, v ...interface{}) {
|
|
tflog.Info(context.Background(), fmt.Sprintf(format, v...))
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
tflog.Info(context.Background(), "[INFO] GetHelmConfiguration success")
|
|
// returning the initializing action.Configuration object
|
|
return actionConfig, nil
|
|
}
|