package utils import "sigs.k8s.io/yaml" // MergeYAML merges raw yaml, with preference given to override func MergeYAML(base, override []byte) (map[string]interface{}, error) { baseVals := map[string]interface{}{} overrideVals := map[string]interface{}{} err := yaml.Unmarshal(base, &baseVals) if err != nil { return nil, err } err = yaml.Unmarshal(override, &overrideVals) if err != nil { return nil, err } return CoalesceValues(baseVals, overrideVals), nil } // CoalesceValues replaces arrays and scalar values, merges maps func CoalesceValues(base, override map[string]interface{}) map[string]interface{} { if base == nil && override != nil { return override } else if override == nil { return base } for key, val := range base { if oVal, ok := override[key]; ok { if oVal == nil { delete(override, key) } else if isYAMLTable(oVal) && isYAMLTable(val) { oMapVal, _ := oVal.(map[string]interface{}) bMapVal, _ := val.(map[string]interface{}) override[key] = mergeMaps(bMapVal, oMapVal) } } else { override[key] = val } } return override } func DeepCoalesceValues(base, override map[string]interface{}) map[string]interface{} { if base == nil && override != nil { return override } else if override == nil { return base } for key, val := range base { if oVal, ok := override[key]; ok { if oVal == nil { delete(override, key) } else if isYAMLTable(oVal) && isYAMLTable(val) { oMapVal, _ := oVal.(map[string]interface{}) bMapVal, _ := val.(map[string]interface{}) override[key] = mergeMaps(bMapVal, oMapVal) } else if _, ok := val.(map[string]interface{}); ok { override[key] = CoalesceValues(val.(map[string]interface{}), oVal.(map[string]interface{})) } else { override[key] = oVal } } else { override[key] = val } } return override } func isYAMLTable(v interface{}) bool { _, ok := v.(map[string]interface{}) return ok } // mergeMaps merges any number of maps together, with maps later in the slice taking // precedent func mergeMaps(maps ...map[string]interface{}) map[string]interface{} { // merge bottom-up if len(maps) > 2 { mLen := len(maps) newMaps := maps[0 : mLen-2] // reduce length of maps by 1 and merge again newMaps = append(newMaps, mergeMaps(maps[mLen-2], maps[mLen-1])) return mergeMaps(newMaps...) } else if len(maps) == 2 { if maps[0] == nil { return maps[1] } if maps[1] == nil { return maps[0] } for key, map0Val := range maps[0] { if map1Val, ok := maps[1][key]; ok && map1Val == nil { delete(maps[1], key) } else if !ok { maps[1][key] = map0Val } else if isYAMLTable(map0Val) { if isYAMLTable(map1Val) { mergeMaps(map0Val.(map[string]interface{}), map1Val.(map[string]interface{})) } } } return maps[1] } else if len(maps) == 1 { return maps[0] } return nil }