portainer/pkg/libhelm/sdk/client.go

166 lines
5.0 KiB
Go

package sdk
import (
"github.com/pkg/errors"
"github.com/portainer/portainer/pkg/libhelm/options"
"github.com/rs/zerolog/log"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli"
"k8s.io/apimachinery/pkg/api/meta"
"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"
"k8s.io/client-go/tools/clientcmd/api"
)
// newRESTClientGetter creates a custom RESTClientGetter using the provided client config
type clientConfigGetter struct {
clientConfig clientcmd.ClientConfig
namespace string
}
// initActionConfig initializes the action configuration with kubernetes config
func (hspm *HelmSDKPackageManager) initActionConfig(actionConfig *action.Configuration, namespace string, k8sAccess *options.KubernetesClusterAccess) error {
// If namespace is not provided, use the default namespace
if namespace == "" {
namespace = "default"
}
if k8sAccess == nil {
// Use default kubeconfig
settings := cli.New()
clientGetter := settings.RESTClientGetter()
return actionConfig.Init(clientGetter, namespace, "secret", hspm.logf)
}
// Create client config
configAPI := generateConfigAPI(namespace, k8sAccess)
clientConfig := clientcmd.NewDefaultClientConfig(*configAPI, &clientcmd.ConfigOverrides{})
// Create a custom RESTClientGetter that uses our in-memory config
clientGetter, err := newRESTClientGetter(clientConfig, namespace)
if err != nil {
log.Error().
Str("context", "HelmClient").
Str("cluster_name", k8sAccess.ClusterName).
Str("cluster_url", k8sAccess.ClusterServerURL).
Str("user_name", k8sAccess.UserName).
Err(err).
Msg("failed to create client getter")
return err
}
return actionConfig.Init(clientGetter, namespace, "secret", hspm.logf)
}
// generateConfigAPI generates a new kubeconfig configuration
func generateConfigAPI(namespace string, k8sAccess *options.KubernetesClusterAccess) *api.Config {
// Create in-memory kubeconfig configuration
configAPI := api.NewConfig()
// Create cluster
cluster := api.NewCluster()
cluster.Server = k8sAccess.ClusterServerURL
if k8sAccess.CertificateAuthorityFile != "" {
// If we have a CA file, use it
cluster.CertificateAuthority = k8sAccess.CertificateAuthorityFile
} else {
// Otherwise skip TLS verification
cluster.InsecureSkipTLSVerify = true
}
// Create auth info with token
authInfo := api.NewAuthInfo()
authInfo.Token = k8sAccess.AuthToken
// Create context
context := api.NewContext()
context.Cluster = k8sAccess.ClusterName
context.AuthInfo = k8sAccess.UserName
context.Namespace = namespace
// Add to config
configAPI.Clusters[k8sAccess.ClusterName] = cluster
configAPI.AuthInfos[k8sAccess.UserName] = authInfo
configAPI.Contexts[k8sAccess.ContextName] = context
configAPI.CurrentContext = k8sAccess.ContextName
return configAPI
}
func newRESTClientGetter(clientConfig clientcmd.ClientConfig, namespace string) (*clientConfigGetter, error) {
if clientConfig == nil {
log.Error().
Str("context", "HelmClient").
Msg("client config is nil")
return nil, errors.New("client config provided during the helm client initialization was nil. Check the kubernetes cluster access configuration")
}
return &clientConfigGetter{
clientConfig: clientConfig,
namespace: namespace,
}, nil
}
func (c *clientConfigGetter) ToRESTConfig() (*rest.Config, error) {
if c.clientConfig == nil {
log.Error().
Str("context", "HelmClient").
Msg("client config is nil")
return nil, errors.New("client config provided during the helm client initialization was nil. Check the kubernetes cluster access configuration")
}
return c.clientConfig.ClientConfig()
}
func (c *clientConfigGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
config, err := c.ToRESTConfig()
if err != nil {
return nil, err
}
// Create the discovery client
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
}
// Wrap the discovery client with a cached discovery client
return memory.NewMemCacheClient(discoveryClient), nil
}
func (c *clientConfigGetter) ToRESTMapper() (meta.RESTMapper, error) {
discoveryClient, err := c.ToDiscoveryClient()
if err != nil {
return nil, err
}
// Create a REST mapper from the discovery client
return restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient), nil
}
func (c *clientConfigGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return c.clientConfig
}
// parseValues parses YAML values data into a map
func (hspm *HelmSDKPackageManager) parseValues(data []byte) (map[string]any, error) {
// Use Helm's built-in chartutil.ReadValues which properly handles the conversion
// from map[interface{}]interface{} to map[string]interface{}
return chartutil.ReadValues(data)
}
// logf is a log helper function for Helm
func (hspm *HelmSDKPackageManager) logf(format string, v ...any) {
// Use zerolog for structured logging
log.Debug().
Str("context", "HelmClient").
Msgf(format, v...)
}