Files
backroad/pkg/libhelm/sdk/values.go
2025-10-15 11:36:20 +13:00

156 lines
4.4 KiB
Go

package sdk
import (
"os"
"github.com/pkg/errors"
"github.com/portainer/portainer/pkg/libhelm/options"
"github.com/portainer/portainer/pkg/libhelm/release"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v2"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
)
// GetHelmValuesFromFile reads the values file and parses it into a map[string]any
// and returns the map.
func GetHelmValuesFromFile(valuesFile string) (map[string]any, error) {
var vals map[string]any
if valuesFile != "" {
log.Debug().
Str("context", "HelmClient").
Str("values_file", valuesFile).
Msg("Reading values file")
valuesData, err := os.ReadFile(valuesFile)
if err != nil {
log.Error().
Str("context", "HelmClient").
Str("values_file", valuesFile).
Err(err).
Msg("Failed to read values file")
return nil, errors.Wrap(err, "failed to read values file")
}
vals, err = parseValues(valuesData)
if err != nil {
log.Error().
Str("context", "HelmClient").
Str("values_file", valuesFile).
Err(err).
Msg("Failed to parse values file")
return nil, errors.Wrap(err, "failed to parse values file")
}
}
return vals, nil
}
// parseValues parses YAML values data into a map
func 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)
}
// MergeValues merges two maps recursively, with values from the override map taking precedence
// over values from the base map. It returns a new map containing the merged values.
func MergeValues(base, override map[string]any) map[string]any {
if base == nil {
return override
}
if override == nil {
return base
}
for k, v := range override {
if vMap, ok := v.(map[string]any); ok {
if baseMap, ok := base[k].(map[string]any); ok {
base[k] = MergeValues(baseMap, vMap)
continue
}
}
base[k] = v
}
return base
}
func (hspm *HelmSDKPackageManager) getValues(getOpts options.GetOptions) (release.Values, error) {
log.Debug().
Str("context", "HelmClient").
Str("namespace", getOpts.Namespace).
Str("name", getOpts.Name).
Msg("Getting values")
actionConfig := new(action.Configuration)
err := hspm.initActionConfig(actionConfig, getOpts.Namespace, getOpts.KubernetesClusterAccess)
if err != nil {
log.Error().
Str("context", "HelmClient").
Str("namespace", getOpts.Namespace).
Err(err).Msg("Failed to initialise helm configuration")
return release.Values{}, err
}
userValuesClient := hspm.initValuesClient(actionConfig, getOpts)
userSuppliedValues, err := userValuesClient.Run(getOpts.Name)
if err != nil {
log.Error().
Str("context", "HelmClient").
Str("namespace", getOpts.Namespace).
Err(err).Msg("Failed to get user supplied values")
return release.Values{}, err
}
// Create separate client for computed values
computedValuesClient := action.NewGetValues(actionConfig)
computedValuesClient.AllValues = true
computedValues, err := computedValuesClient.Run(getOpts.Name)
if err != nil {
log.Error().
Str("context", "HelmClient").
Str("namespace", getOpts.Namespace).
Err(err).Msg("Failed to get computed values")
return release.Values{}, err
}
userSuppliedValuesByte, err := yaml.Marshal(userSuppliedValues)
if err != nil {
log.Error().
Str("context", "HelmClient").
Err(err).Msg("Failed to marshal user supplied values")
return release.Values{}, err
}
computedValuesByte, err := yaml.Marshal(computedValues)
if err != nil {
log.Error().
Str("context", "HelmClient").
Err(err).Msg("Failed to marshal computed values")
return release.Values{}, err
}
// Handle the case where the values are an empty object
userSuppliedValuesString := string(userSuppliedValuesByte)
if userSuppliedValuesString == "{}\n" {
userSuppliedValuesString = ""
}
computedValuesString := string(computedValuesByte)
if computedValuesString == "{}\n" {
computedValuesString = ""
}
return release.Values{
UserSuppliedValues: userSuppliedValuesString,
ComputedValues: computedValuesString,
}, nil
}
func (hspm *HelmSDKPackageManager) initValuesClient(actionConfig *action.Configuration, getOpts options.GetOptions) *action.GetValues {
valuesClient := action.NewGetValues(actionConfig)
if getOpts.Revision > 0 {
valuesClient.Version = getOpts.Revision
}
return valuesClient
}