diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 3d8e3246e..6574b87b0 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -72,7 +72,7 @@ jobs: kubectl -n helm-system wait helmreleases/valuesfrom --for=condition=ready --timeout=4m RESULT=$(helm -n helm-system get values valuesfrom) - EXPECTED=$(cat config/testdata/valuesfrom/result.txt) + EXPECTED=$(cat config/testdata/valuesfrom/golden.txt) if [ "$RESULT" != "$EXPECTED" ]; then echo -e "$RESULT\n\ndoes not equal\n\n$EXPECTED" fi diff --git a/Makefile b/Makefile index 0fe21e108..617b04b14 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ all: manager # Run tests test: generate fmt vet manifests api-docs - find . -maxdepth 2 -type f -name 'go.mod' -execdir go test ./... \; + find . -maxdepth 2 -type f -name 'go.mod' -execdir go test ./... -coverprofile cover.out \; # Build manager binary manager: generate fmt vet diff --git a/api/v2alpha1/reference_types.go b/api/v2alpha1/reference_types.go index 6834bff1e..18827ac4f 100644 --- a/api/v2alpha1/reference_types.go +++ b/api/v2alpha1/reference_types.go @@ -50,10 +50,16 @@ type ValuesReference struct { // +required Name string `json:"name"` - // ValuesKey is the key in the referent the values can be found at. - // Defaults to 'values.yaml'. + // ValuesKey is the data key where the values.yaml or a specific value can + // be found at. Defaults to 'values.yaml'. // +optional ValuesKey string `json:"valuesKey,omitempty"` + + // TargetPath is the YAML dot notation path the value should be merged at. + // When set, the ValuesKey is expected to be a single flat value. + // Defaults to 'None', which results in the values getting merged at the root. + // +optional + TargetPath string `json:"targetPath,omitempty"` } // GetValuesKey returns the defined ValuesKey or the default ('values.yaml'). diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml index 6b1c0405a..5a071c657 100644 --- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml +++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml @@ -281,9 +281,15 @@ spec: description: Name of the values referent. Should reside in the same namespace as the referring resource. type: string + targetPath: + description: TargetPath is the YAML dot notation path the value + should be merged at. When set, the ValuesKey is expected to + be a single flat value. Defaults to 'None', which results + in the values getting merged at the root. + type: string valuesKey: - description: ValuesKey is the key in the referent the values - can be found at. Defaults to 'values.yaml'. + description: ValuesKey is the data key where the values.yaml + or a specific value can be found at. Defaults to 'values.yaml'. type: string required: - kind diff --git a/config/testdata/valuesfrom/configmap.yaml b/config/testdata/valuesfrom/configmap.yaml index bf634406d..258f21a02 100644 --- a/config/testdata/valuesfrom/configmap.yaml +++ b/config/testdata/valuesfrom/configmap.yaml @@ -13,3 +13,7 @@ data: requests: cpu: 100m memory: 64Mi + resources.limits.cpu: 400m + list.example: "{a,b,c}" + list.example.overwrite: d + podAnnotation.example.value: demo diff --git a/config/testdata/valuesfrom/result.txt b/config/testdata/valuesfrom/golden.txt similarity index 61% rename from config/testdata/valuesfrom/result.txt rename to config/testdata/valuesfrom/golden.txt index e670960b0..c436e304e 100644 --- a/config/testdata/valuesfrom/result.txt +++ b/config/testdata/valuesfrom/golden.txt @@ -1,8 +1,15 @@ USER-SUPPLIED VALUES: +list: + example: + - a + - b + - d +podAnnotations: + some.k8s.annotation/env: demo replicaCount: 2 resources: limits: - cpu: 200m + cpu: 400m memory: 128Mi requests: cpu: 100m diff --git a/config/testdata/valuesfrom/helmrelease.yaml b/config/testdata/valuesfrom/helmrelease.yaml index dc76c158e..2d8147f5b 100644 --- a/config/testdata/valuesfrom/helmrelease.yaml +++ b/config/testdata/valuesfrom/helmrelease.yaml @@ -22,6 +22,22 @@ spec: - kind: ConfigMap name: valuesfrom-config valuesKey: resources.requests + - kind: ConfigMap + name: valuesfrom-config + valuesKey: resources.limits.cpu + targetPath: resources.limits.cpu + - kind: ConfigMap + name: valuesfrom-config + valuesKey: list.example + targetPath: list.example + - kind: ConfigMap + name: valuesfrom-config + valuesKey: list.example.overwrite + targetPath: list.example[2] + - kind: ConfigMap + name: valuesfrom-config + valuesKey: podAnnotation.example.value + targetPath: podAnnotations.some\.k8s\.annotation\/env - kind: Secret name: valuesfrom-secret values: diff --git a/config/testdata/valuesfrom/secret.yaml b/config/testdata/valuesfrom/secret.yaml index 67ef616a9..74ad6db25 100644 --- a/config/testdata/valuesfrom/secret.yaml +++ b/config/testdata/valuesfrom/secret.yaml @@ -3,5 +3,7 @@ kind: Secret metadata: name: valuesfrom-secret type: Opaque -data: - values.yaml: c2VydmljZUFjY291bnQ6CiAgZW5hYmxlZDogdHJ1ZQo= +stringData: + values.yaml: | + serviceAccount: + enabled: true diff --git a/controllers/helmrelease_controller.go b/controllers/helmrelease_controller.go index 078c6dc5c..5d6047661 100644 --- a/controllers/helmrelease_controller.go +++ b/controllers/helmrelease_controller.go @@ -37,6 +37,7 @@ import ( "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v3/pkg/strvals" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -472,11 +473,22 @@ func (r *HelmReleaseReconciler) composeValues(ctx context.Context, hr v2.HelmRel default: return nil, fmt.Errorf("unsupported ValuesReference kind '%s'", v.Kind) } - values, err := chartutil.ReadValues(valsData) - if err != nil { - return nil, fmt.Errorf("unable to read values from key '%s' in %s '%s': %w", v.GetValuesKey(), v.Kind, namespacedName, err) + switch v.TargetPath { + case "": + values, err := chartutil.ReadValues(valsData) + if err != nil { + return nil, fmt.Errorf("unable to read values from key '%s' in %s '%s': %w", v.GetValuesKey(), v.Kind, namespacedName, err) + } + result = mergeMaps(result, values) + default: + // TODO(hidde): this is a bit of hack, as it mimics the way the option string is passed + // to Helm from a CLI perspective. Given the parser is however not publicly accessible + // while it contains all logic around parsing the target path, it is a fair trade-off. + singleVal := v.TargetPath + "=" + string(valsData) + if err := strvals.ParseInto(singleVal, result); err != nil { + return nil, fmt.Errorf("unable to merge value from key '%s' in %s '%s' into target path '%s': %w", v.GetValuesKey(), v.Kind, namespacedName, v.TargetPath, err) + } } - result = mergeMaps(result, values) } return mergeMaps(result, hr.GetValues()), nil } diff --git a/docs/api/helmrelease.md b/docs/api/helmrelease.md index 980957f75..777146568 100644 --- a/docs/api/helmrelease.md +++ b/docs/api/helmrelease.md @@ -1362,8 +1362,22 @@ string
ValuesKey is the key in the referent the values can be found at. -Defaults to ‘values.yaml’.
+ValuesKey is the data key where the values.yaml or a specific value can +be found at. Defaults to ‘values.yaml’.
+targetPath
TargetPath is the YAML dot notation path the value should be merged at. +When set, the ValuesKey is expected to be a single flat value. +Defaults to ‘None’, which results in the values getting merged at the root.