diff --git a/cmd/flux/create_helmrelease.go b/cmd/flux/create_helmrelease.go index 8174f63a..71b8c2f7 100644 --- a/cmd/flux/create_helmrelease.go +++ b/cmd/flux/create_helmrelease.go @@ -18,6 +18,7 @@ package main import ( "context" + "encoding/json" "fmt" "io/ioutil" @@ -62,11 +63,12 @@ var createHelmReleaseCmd = &cobra.Command{ --source=Bucket/podinfo \ --chart=./charts/podinfo - # Create a HelmRelease with values from a local YAML file + # Create a HelmRelease with values from local YAML files flux create hr podinfo \ --source=HelmRepository/podinfo \ --chart=podinfo \ - --values=./my-values.yaml + --values=./my-values1.yaml \ + --values=./my-values2.yaml # Create a HelmRelease with values from a Kubernetes secret kubectl -n app create secret generic my-secret-values \ @@ -105,7 +107,7 @@ type helmReleaseFlags struct { chart string chartVersion string targetNamespace string - valuesFile string + valuesFile []string valuesFrom flags.HelmReleaseValuesFrom saName string } @@ -120,7 +122,7 @@ func init() { createHelmReleaseCmd.Flags().StringArrayVar(&helmReleaseArgs.dependsOn, "depends-on", nil, "HelmReleases that must be ready before this release can be installed, supported formats '' and '/'") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.targetNamespace, "target-namespace", "", "namespace to install this release, defaults to the HelmRelease namespace") createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.saName, "service-account", "", "the name of the service account to impersonate when reconciling this HelmRelease") - createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.valuesFile, "values", "", "local path to the values.yaml file") + createHelmReleaseCmd.Flags().StringArrayVar(&helmReleaseArgs.valuesFile, "values", nil, "local path to values.yaml files") createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.valuesFrom, "values-from", helmReleaseArgs.valuesFrom.Description()) createCmd.AddCommand(createHelmReleaseCmd) } @@ -175,18 +177,37 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error { helmRelease.Spec.ServiceAccountName = helmReleaseArgs.saName } - if helmReleaseArgs.valuesFile != "" { - data, err := ioutil.ReadFile(helmReleaseArgs.valuesFile) - if err != nil { - return fmt.Errorf("reading values from %s failed: %w", helmReleaseArgs.valuesFile, err) + if len(helmReleaseArgs.valuesFile) > 0 { + var valuesMap map[string]interface{} + for _, v := range helmReleaseArgs.valuesFile { + data, err := ioutil.ReadFile(v) + if err != nil { + return fmt.Errorf("reading values from %s failed: %w", v, err) + } + + jsonBytes, err := yaml.YAMLToJSON(data) + if err != nil { + return fmt.Errorf("converting values to JSON from %s failed: %w", v, err) + } + + jsonMap := make(map[string]interface{}) + if err := json.Unmarshal(jsonBytes, &jsonMap); err != nil { + return fmt.Errorf("unmarshaling values from %s failed: %w", v, err) + } + + if valuesMap == nil { + valuesMap = jsonMap + } else { + valuesMap = utils.MergeMaps(valuesMap, jsonMap) + } } - json, err := yaml.YAMLToJSON(data) + jsonRaw, err := json.Marshal(valuesMap) if err != nil { - return fmt.Errorf("converting values to JSON from %s failed: %w", helmReleaseArgs.valuesFile, err) + return fmt.Errorf("marshaling values failed: %w", err) } - helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: json} + helmRelease.Spec.Values = &apiextensionsv1.JSON{Raw: jsonRaw} } if helmReleaseArgs.valuesFrom.String() != "" { diff --git a/docs/cmd/flux_create_helmrelease.md b/docs/cmd/flux_create_helmrelease.md index 99915ae6..2b279003 100644 --- a/docs/cmd/flux_create_helmrelease.md +++ b/docs/cmd/flux_create_helmrelease.md @@ -32,11 +32,12 @@ flux create helmrelease [name] [flags] --source=Bucket/podinfo \ --chart=./charts/podinfo - # Create a HelmRelease with values from a local YAML file + # Create a HelmRelease with values from local YAML files flux create hr podinfo \ --source=HelmRepository/podinfo \ --chart=podinfo \ - --values=./my-values.yaml + --values=./my-values1.yaml \ + --values=./my-values2.yaml # Create a HelmRelease with values from a Kubernetes secret kubectl -n app create secret generic my-secret-values \ @@ -78,7 +79,7 @@ flux create helmrelease [name] [flags] --service-account string the name of the service account to impersonate when reconciling this HelmRelease --source helmChartSource source that contains the chart in the format '/', where kind must be one of: (HelmRepository, GitRepository, Bucket) --target-namespace string namespace to install this release, defaults to the HelmRelease namespace - --values string local path to the values.yaml file + --values stringArray local path to values.yaml files --values-from helmReleaseValuesFrom Kubernetes object reference that contains the values.yaml data key in the format '/', where kind must be one of: (Secret, ConfigMap) ``` diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 08b81451..1113ddda 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -393,3 +393,24 @@ func ValidateComponents(components []string) error { return nil } + +// TODO(stefan): move this to fluxcd/pkg +// taken from: https://github.com/fluxcd/helm-controller/blob/main/internal/util/util.go +func MergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + for k, v := range a { + out[k] = v + } + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = MergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +}