devops/shuttles/terraform/terraform-provider-helm/helm/kubeconfig.go

238 lines
7.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package helm
import (
"context"
"fmt"
"os"
"path/filepath"
"sync"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/mitchellh/go-homedir"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// Struct holding k8s client config, burst limit for api requests, and mutex for sync
type KubeConfig struct {
ClientConfig clientcmd.ClientConfig
Burst int
sync.Mutex
}
// Converting KubeConfig to a REST config, which will be used to create k8s clients
func (k *KubeConfig) ToRESTConfig() (*rest.Config, error) {
config, err := k.ToRawKubeConfigLoader().ClientConfig()
return config, err
}
// Converting KubeConfig to a discovery client, which will be used to find api resources
func (k *KubeConfig) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
config, err := k.ToRESTConfig()
if err != nil {
return nil, err
}
config.Burst = k.Burst
return memory.NewMemCacheClient(discovery.NewDiscoveryClientForConfigOrDie(config)), nil
}
// Converting KubeConfig to a REST mapper, which will be used to map REST resources to their API obj
func (k *KubeConfig) ToRESTMapper() (meta.RESTMapper, error) {
discoveryClient, err := k.ToDiscoveryClient()
if err != nil {
return nil, err
}
// Using the appropriate types for the arguments
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
warningHandler := func(warning string) {
fmt.Printf("Warning: %s\n", warning)
}
// Pass the warning handler to the NewShortcutExpander function
expander := restmapper.NewShortcutExpander(mapper, discoveryClient, warningHandler)
return expander, nil
}
// Function returning raw k8s client config
func (k *KubeConfig) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return k.ClientConfig
}
// Generates a k8s client config, based on providers settings and namespace, which this config will be used to interact with the k8s cluster
func (m *Meta) NewKubeConfig(ctx context.Context, namespace string) (*KubeConfig, error) {
overrides := &clientcmd.ConfigOverrides{}
loader := &clientcmd.ClientConfigLoadingRules{}
configPaths := []string{}
if m == nil || m.Data == nil || m.Data.Kubernetes.IsNull() || m.Data.Kubernetes.IsUnknown() {
return nil, fmt.Errorf("configuration error: missing required structural data")
}
tflog.Debug(ctx, "Raw Kubernetes Data before conversion", map[string]interface{}{
"KubernetesData": m.Data.Kubernetes,
})
// Needing to get the Kubernetes configuration as an obj
var kubernetesConfig KubernetesConfigModel
diags := m.Data.Kubernetes.As(ctx, &kubernetesConfig, basetypes.ObjectAsOptions{})
if diags.HasError() {
for _, d := range diags {
tflog.Error(ctx, "Kubernetes config conversion error", map[string]interface{}{
"summary": d.Summary(),
"detail": d.Detail(),
})
}
return nil, fmt.Errorf("configuration error: unable to extract Kubernetes config")
}
// Check ConfigPath
if !kubernetesConfig.ConfigPath.IsNull() {
if v := kubernetesConfig.ConfigPath.ValueString(); v != "" {
configPaths = []string{v}
}
}
if !kubernetesConfig.ConfigPaths.IsNull() {
additionalPaths := expandStringSlice(kubernetesConfig.ConfigPaths.Elements())
configPaths = append(configPaths, additionalPaths...)
}
if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" {
configPaths = filepath.SplitList(v)
}
tflog.Debug(ctx, "Initial configPaths", map[string]interface{}{"configPaths": configPaths})
if len(configPaths) > 0 {
expandedPaths := []string{}
for _, p := range configPaths {
path, err := homedir.Expand(p)
if err != nil {
tflog.Error(ctx, "Error expanding home directory", map[string]interface{}{
"path": p,
"error": err,
})
return nil, err
}
expandedPaths = append(expandedPaths, path)
}
if len(expandedPaths) == 1 {
loader.ExplicitPath = expandedPaths[0]
} else {
loader.Precedence = expandedPaths
}
// Check ConfigContext
if !kubernetesConfig.ConfigContext.IsNull() {
overrides.CurrentContext = kubernetesConfig.ConfigContext.ValueString()
}
if !kubernetesConfig.ConfigContextAuthInfo.IsNull() {
overrides.Context.AuthInfo = kubernetesConfig.ConfigContextAuthInfo.ValueString()
}
if !kubernetesConfig.ConfigContextCluster.IsNull() {
overrides.Context.Cluster = kubernetesConfig.ConfigContextCluster.ValueString()
}
}
// Check and assign remaining fields
if !kubernetesConfig.Insecure.IsNull() {
overrides.ClusterInfo.InsecureSkipTLSVerify = kubernetesConfig.Insecure.ValueBool()
}
if !kubernetesConfig.TLSServerName.IsNull() {
overrides.ClusterInfo.TLSServerName = kubernetesConfig.TLSServerName.ValueString()
}
if !kubernetesConfig.ClusterCACertificate.IsNull() {
overrides.ClusterInfo.CertificateAuthorityData = []byte(kubernetesConfig.ClusterCACertificate.ValueString())
}
if !kubernetesConfig.ClientCertificate.IsNull() {
overrides.AuthInfo.ClientCertificateData = []byte(kubernetesConfig.ClientCertificate.ValueString())
}
if !kubernetesConfig.Host.IsNull() && kubernetesConfig.Host.ValueString() != "" {
hasCA := len(overrides.ClusterInfo.CertificateAuthorityData) != 0
hasCert := len(overrides.AuthInfo.ClientCertificateData) != 0
defaultTLS := hasCA || hasCert || overrides.ClusterInfo.InsecureSkipTLSVerify
host, _, err := rest.DefaultServerURL(kubernetesConfig.Host.ValueString(), "", schema.GroupVersion{}, defaultTLS)
if err != nil {
return nil, err
}
overrides.ClusterInfo.Server = host.String()
}
if !kubernetesConfig.Username.IsNull() {
overrides.AuthInfo.Username = kubernetesConfig.Username.ValueString()
}
if !kubernetesConfig.Password.IsNull() {
overrides.AuthInfo.Password = kubernetesConfig.Password.ValueString()
}
if !kubernetesConfig.ClientKey.IsNull() {
overrides.AuthInfo.ClientKeyData = []byte(kubernetesConfig.ClientKey.ValueString())
}
if !kubernetesConfig.Token.IsNull() {
overrides.AuthInfo.Token = kubernetesConfig.Token.ValueString()
}
if !kubernetesConfig.ProxyURL.IsNull() {
overrides.ClusterDefaults.ProxyURL = kubernetesConfig.ProxyURL.ValueString()
}
if kubernetesConfig.Exec != nil {
execConfig := kubernetesConfig.Exec
if !execConfig.APIVersion.IsNull() && !execConfig.Command.IsNull() {
args := []string{}
if !execConfig.Args.IsNull() && !execConfig.Args.IsUnknown() {
args = expandStringSlice(execConfig.Args.Elements())
}
env := []clientcmdapi.ExecEnvVar{}
if !execConfig.Env.IsNull() && !execConfig.Env.IsUnknown() {
for k, v := range execConfig.Env.Elements() {
env = append(env, clientcmdapi.ExecEnvVar{
Name: k,
Value: v.(types.String).ValueString(),
})
}
}
overrides.AuthInfo.Exec = &clientcmdapi.ExecConfig{
APIVersion: execConfig.APIVersion.ValueString(),
Command: execConfig.Command.ValueString(),
Args: args,
Env: env,
InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
}
}
}
overrides.Context.Namespace = "default"
if namespace != "" {
overrides.Context.Namespace = namespace
}
burstLimit := int(m.Data.BurstLimit.ValueInt64())
client := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides)
if client == nil {
return nil, fmt.Errorf("failed to initialize kubernetes config")
}
tflog.Info(ctx, "Successfully initialized kubernetes config")
return &KubeConfig{ClientConfig: client, Burst: burstLimit}, nil
}
func expandStringSlice(input []attr.Value) []string {
result := make([]string, len(input))
for i, v := range input {
result[i] = v.(types.String).ValueString()
}
return result
}