From 02d070ebdc2f9dc4553898d00d1ed998fe339bca Mon Sep 17 00:00:00 2001 From: Rafael da Fonseca Date: Fri, 7 Mar 2025 16:08:44 +0000 Subject: [PATCH 1/4] feat: Merge lists when passing multiple values files, allowing to signal a unique key for merging lists of maps Signed-off-by: Rafael da Fonseca --- pkg/chart/v2/loader/load.go | 63 +++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index 3c5463720..c00481f4b 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -24,6 +24,7 @@ import ( "log" "os" "path/filepath" + "reflect" "strings" "github.com/pkg/errors" @@ -236,21 +237,79 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) { // MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. // If the value is a map, the maps will be merged recursively. +// If the value is a list, the lists will be merged 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 val, 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) + out[k] = MergeMaps(bv, val) continue } } + } else if reflect.TypeOf(v).Kind() == reflect.Slice { + if sourceList, ok := out[k].([]map[string]interface{}); ok { + + val, ok := v.([]map[string]interface{}) + if !ok { + log.Println("Property mismatch during merge") + out[k] = v + continue + } + + out[k] = MergeMapLists(sourceList, val) + continue + } else if sourceList, ok := out[k].([]interface{}); ok { + if val, ok := v.([]interface{}); ok { + out[k] = append(sourceList, val...) + } else { + out[k] = v + } + continue + } + } out[k] = v } return out } + +// MergeMapLists merges two lists of maps. If a prefix of * is set on a map key, +// that key will be used to de-duplicate/merge with the source map +func MergeMapLists(a, b []map[string]interface{}) []map[string]interface{} { + out := a + for j, mapEntry := range b { + var mergeKey string + var mergeValue interface{} + for k, v := range mapEntry { + if strings.HasPrefix(k, "*") { + mergeKey = k + mergeValue = v + b[j][strings.TrimPrefix(mergeKey, "*")] = v + delete(b[j], mergeKey) + break + } + } + if len(mergeKey) > 0 { + strippedMergeKey := strings.TrimPrefix(mergeKey, "*") + for i, sourceMapEntry := range out { + for k, v := range sourceMapEntry { + if (k == strippedMergeKey || k == mergeKey) && v == mergeValue { + mergedMapEntry := MergeMaps(sourceMapEntry, mapEntry) + out[i] = mergedMapEntry + break + } + } + } + } else { + out = append(out, mapEntry) + } + } + return out + +} From 825fc2b0bc0e848ccaca96d0902be54c890d3328 Mon Sep 17 00:00:00 2001 From: Rafael da Fonseca Date: Fri, 7 Mar 2025 16:27:49 +0000 Subject: [PATCH 2/4] only merge lists with prefix Signed-off-by: Rafael da Fonseca --- pkg/chart/v2/loader/load.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index c00481f4b..3dd2d4c90 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -34,6 +34,8 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" ) +const mergePrefix = "*" + // ChartLoader loads a chart. type ChartLoader interface { Load() (*chart.Chart, error) @@ -252,23 +254,25 @@ func MergeMaps(a, b map[string]interface{}) map[string]interface{} { continue } } - } else if reflect.TypeOf(v).Kind() == reflect.Slice { - if sourceList, ok := out[k].([]map[string]interface{}); ok { + } else if reflect.TypeOf(v).Kind() == reflect.Slice && strings.HasPrefix(k, mergePrefix) { + strippedKey := strings.TrimPrefix(k, mergePrefix) + out[strippedKey] = out[k] + delete(out, k) + if sourceList, ok := out[strippedKey].([]map[string]interface{}); ok { val, ok := v.([]map[string]interface{}) if !ok { log.Println("Property mismatch during merge") - out[k] = v continue } - out[k] = MergeMapLists(sourceList, val) + out[strippedKey] = MergeMapLists(sourceList, val) continue - } else if sourceList, ok := out[k].([]interface{}); ok { + } else if sourceList, ok := out[strippedKey].([]interface{}); ok { if val, ok := v.([]interface{}); ok { - out[k] = append(sourceList, val...) + out[strippedKey] = append(sourceList, val...) } else { - out[k] = v + out[strippedKey] = v } continue } @@ -287,16 +291,16 @@ func MergeMapLists(a, b []map[string]interface{}) []map[string]interface{} { var mergeKey string var mergeValue interface{} for k, v := range mapEntry { - if strings.HasPrefix(k, "*") { + if strings.HasPrefix(k, mergePrefix) { mergeKey = k mergeValue = v - b[j][strings.TrimPrefix(mergeKey, "*")] = v + b[j][strings.TrimPrefix(mergeKey, mergePrefix)] = v delete(b[j], mergeKey) break } } if len(mergeKey) > 0 { - strippedMergeKey := strings.TrimPrefix(mergeKey, "*") + strippedMergeKey := strings.TrimPrefix(mergeKey, mergePrefix) for i, sourceMapEntry := range out { for k, v := range sourceMapEntry { if (k == strippedMergeKey || k == mergeKey) && v == mergeValue { From baa0fdf6e7cd0459e0d7a637f7990cf9b8e5b2af Mon Sep 17 00:00:00 2001 From: Rafael da Fonseca Date: Fri, 14 Mar 2025 09:16:00 +0000 Subject: [PATCH 3/4] Fix type assertions Signed-off-by: Rafael da Fonseca --- pkg/chart/v2/loader/load.go | 70 +++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index 3dd2d4c90..7cc47415a 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -24,7 +24,6 @@ import ( "log" "os" "path/filepath" - "reflect" "strings" "github.com/pkg/errors" @@ -34,7 +33,7 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" ) -const mergePrefix = "*" +const mergePrefix = "\\*" // ChartLoader loads a chart. type ChartLoader interface { @@ -232,6 +231,7 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) { }); err != nil { return nil, errors.Wrap(err, "cannot unmarshal yaml document") } + values = MergeMaps(values, currentMap) } return values, nil @@ -246,7 +246,6 @@ func MergeMaps(a, b map[string]interface{}) map[string]interface{} { out[k] = v } for k, v := range b { - if val, ok := v.(map[string]interface{}); ok { if bv, ok := out[k]; ok { if bv, ok := bv.(map[string]interface{}); ok { @@ -254,27 +253,38 @@ func MergeMaps(a, b map[string]interface{}) map[string]interface{} { continue } } - } else if reflect.TypeOf(v).Kind() == reflect.Slice && strings.HasPrefix(k, mergePrefix) { + } else { strippedKey := strings.TrimPrefix(k, mergePrefix) - out[strippedKey] = out[k] - delete(out, k) - if sourceList, ok := out[strippedKey].([]map[string]interface{}); ok { + if out[k] != nil { + out[strippedKey] = out[k] + delete(out, k) + } + if sourceList, ok := out[strippedKey].([]any); ok && strings.HasPrefix(k, mergePrefix) { - val, ok := v.([]map[string]interface{}) - if !ok { - log.Println("Property mismatch during merge") - continue - } + _, isMapSlice := sourceList[0].(map[string]any) + if isMapSlice { + val, ok := v.([]any) + if !ok { + // List is explicitly made null on a subsequent file + if v == nil { + delete(out, strippedKey) + continue + } else { + log.Printf("Property \"%s\" mismatch during merge", strippedKey) + continue + } + } - out[strippedKey] = MergeMapLists(sourceList, val) - continue - } else if sourceList, ok := out[strippedKey].([]interface{}); ok { - if val, ok := v.([]interface{}); ok { - out[strippedKey] = append(sourceList, val...) - } else { - out[strippedKey] = v + out[strippedKey] = MergeMapLists(sourceList, val) + continue + } else if sourceList, ok := out[strippedKey].([]any); ok { + if val, ok := v.([]any); ok { + out[strippedKey] = append(sourceList, val...) + } else { + out[strippedKey] = v + } + continue } - continue } } @@ -285,23 +295,37 @@ func MergeMaps(a, b map[string]interface{}) map[string]interface{} { // MergeMapLists merges two lists of maps. If a prefix of * is set on a map key, // that key will be used to de-duplicate/merge with the source map -func MergeMapLists(a, b []map[string]interface{}) []map[string]interface{} { +func MergeMapLists(a, b []any) []any { out := a for j, mapEntry := range b { + mapEntry, ok := mapEntry.(map[string]any) + if !ok { + continue + } + var mergeKey string var mergeValue interface{} for k, v := range mapEntry { if strings.HasPrefix(k, mergePrefix) { mergeKey = k mergeValue = v - b[j][strings.TrimPrefix(mergeKey, mergePrefix)] = v - delete(b[j], mergeKey) + bj, ok := b[j].(map[string]any) + if !ok { + continue + } + bj[strings.TrimPrefix(mergeKey, mergePrefix)] = v + delete(bj, mergeKey) break } } if len(mergeKey) > 0 { strippedMergeKey := strings.TrimPrefix(mergeKey, mergePrefix) + for i, sourceMapEntry := range out { + sourceMapEntry, ok := sourceMapEntry.(map[string]any) + if !ok { + continue + } for k, v := range sourceMapEntry { if (k == strippedMergeKey || k == mergeKey) && v == mergeValue { mergedMapEntry := MergeMaps(sourceMapEntry, mapEntry) From 79ff4200944656369e03959e3a86e5c59213bba9 Mon Sep 17 00:00:00 2001 From: Rafael da Fonseca Date: Fri, 14 Mar 2025 09:19:57 +0000 Subject: [PATCH 4/4] Apply var naming change suggestions Signed-off-by: Rafael da Fonseca --- pkg/chart/v2/loader/load.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index 7cc47415a..e87436218 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -303,23 +303,23 @@ func MergeMapLists(a, b []any) []any { continue } - var mergeKey string - var mergeValue interface{} + var uniqueKey string + var dedupValue interface{} for k, v := range mapEntry { if strings.HasPrefix(k, mergePrefix) { - mergeKey = k - mergeValue = v + uniqueKey = k + dedupValue = v bj, ok := b[j].(map[string]any) if !ok { continue } - bj[strings.TrimPrefix(mergeKey, mergePrefix)] = v - delete(bj, mergeKey) + bj[strings.TrimPrefix(uniqueKey, mergePrefix)] = v + delete(bj, uniqueKey) break } } - if len(mergeKey) > 0 { - strippedMergeKey := strings.TrimPrefix(mergeKey, mergePrefix) + if len(uniqueKey) > 0 { + strippedMergeKey := strings.TrimPrefix(uniqueKey, mergePrefix) for i, sourceMapEntry := range out { sourceMapEntry, ok := sourceMapEntry.(map[string]any) @@ -327,7 +327,7 @@ func MergeMapLists(a, b []any) []any { continue } for k, v := range sourceMapEntry { - if (k == strippedMergeKey || k == mergeKey) && v == mergeValue { + if (k == strippedMergeKey || k == uniqueKey) && v == dedupValue { mergedMapEntry := MergeMaps(sourceMapEntry, mapEntry) out[i] = mergedMapEntry break