github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/helm/values.go (about) 1 package helm 2 3 import ( 4 "crypto/sha256" 5 "fmt" 6 7 "github.com/emosbaugh/yaml" 8 "github.com/pkg/errors" 9 ) 10 11 // MergeHelmValues merges user edited values from state file and vendor values from upstream Helm repo. 12 // base is the original config from state 13 // user is the modified config from state 14 // vendor is the new config from current chart 15 // Value priorities: user, vendor, base 16 func MergeHelmValues(baseValues, userValues, vendorValues string, preserveComments bool) (string, error) { 17 // First time merge is performed, there are no user values. We are shortcutting this 18 // in order to preserve original file formatting and comments 19 if userValues == "" { 20 return vendorValues, nil 21 } 22 if vendorValues == "" { 23 vendorValues = baseValues 24 } 25 26 var base, user, vendor yaml.MapSlice 27 28 // we can drop comments in base 29 if err := yaml.Unmarshal([]byte(baseValues), &base); err != nil { 30 return "", errors.Wrapf(err, "unmarshal base values") 31 } 32 // TODO: preserve user comments 33 if err := yaml.Unmarshal([]byte(userValues), &user); err != nil { 34 return "", errors.Wrapf(err, "unmarshal user values") 35 } 36 if preserveComments { 37 var unmarshaler yaml.CommentUnmarshaler 38 if err := unmarshaler.Unmarshal([]byte(vendorValues), &vendor); err != nil { 39 return "", errors.Wrapf(err, "unmarshal vendor values") 40 } 41 } else { 42 if err := yaml.Unmarshal([]byte(vendorValues), &vendor); err != nil { 43 return "", errors.Wrapf(err, "unmarshal vendor values") 44 } 45 } 46 47 merged, err := deepMerge(base, user, vendor) 48 if err != nil { 49 return "", errors.Wrap(err, "deep merge values") 50 } 51 52 vals, err := yaml.Marshal(merged) 53 if err != nil { 54 return "", errors.Wrapf(err, "marshal merged values") 55 } 56 return string(vals), nil 57 } 58 59 // Value priorities: user, vendor, base 60 func deepMerge(base, user, vendor yaml.MapSlice) (yaml.MapSlice, error) { 61 merged := yaml.MapSlice{} 62 63 allKeys := getAllKeys(vendor, user) // we can drop keys that have been dropped by the vendor 64 65 for _, k := range allKeys { 66 // don't merge comments 67 if _, ok := k.(yaml.Comment); ok { 68 merged = append(merged, yaml.MapItem{Key: k}) 69 continue 70 } 71 72 baseVal, baseOk := getValueFromKey(base, k) 73 userVal, userOk := getValueFromKey(user, k) 74 vendorVal, vendorOk := getValueFromKey(vendor, k) 75 76 numExistingMaps := 0 77 preprocessValue := func(exists bool, value interface{}) yaml.MapSlice { 78 if !exists { 79 return yaml.MapSlice{} 80 } 81 m, ok := value.(yaml.MapSlice) 82 if ok { 83 numExistingMaps++ 84 } 85 return m 86 } 87 88 baseSubmap := preprocessValue(baseOk, baseVal) 89 userSubmap := preprocessValue(userOk, userVal) 90 vendorSubmap := preprocessValue(vendorOk, vendorVal) 91 92 if numExistingMaps > 1 { 93 mergedSubmap, err := deepMerge(baseSubmap, userSubmap, vendorSubmap) 94 if err != nil { 95 return merged, errors.Wrapf(err, "merge submap at key %s", k) 96 } 97 merged = setValueAtKey(merged, k, mergedSubmap) 98 continue 99 } 100 101 if userOk && baseOk && vendorOk { 102 if eq, err := valuesEqual(userVal, baseVal); err != nil { 103 return merged, errors.Wrapf(err, "compare values at key %s", k) 104 } else if eq { 105 // user didn't change the value shipped in the previous version 106 // so we continue propagating vendor shipped values 107 merged = setValueAtKey(merged, k, vendorVal) 108 } else { 109 merged = setValueAtKey(merged, k, userVal) 110 } 111 } else if userOk { 112 merged = setValueAtKey(merged, k, userVal) 113 } else { 114 merged = setValueAtKey(merged, k, vendorVal) 115 } 116 } 117 return merged, nil 118 } 119 120 func getAllKeys(maps ...yaml.MapSlice) (allKeys []interface{}) { 121 seenKeys := map[interface{}]bool{} 122 for _, m := range maps { 123 for _, item := range m { 124 // comments are unique 125 if _, ok := item.Key.(yaml.Comment); ok { 126 allKeys = append(allKeys, item.Key) 127 } else if _, ok := seenKeys[item.Key]; !ok { 128 seenKeys[item.Key] = true 129 allKeys = append(allKeys, item.Key) 130 } 131 } 132 } 133 return 134 } 135 136 func getValueFromKey(m yaml.MapSlice, key interface{}) (interface{}, bool) { 137 for _, item := range m { 138 if item.Key == key { 139 return item.Value, true 140 } 141 } 142 return nil, false 143 } 144 145 func setValueAtKey(m yaml.MapSlice, key, value interface{}) (next yaml.MapSlice) { 146 var found bool 147 for _, item := range m { 148 if item.Key == key { 149 item.Value = value 150 found = true 151 } 152 next = append(next, item) 153 } 154 if !found { 155 next = append(next, yaml.MapItem{Key: key, Value: value}) 156 } 157 return 158 } 159 160 func valuesEqual(val1, val2 interface{}) (bool, error) { 161 val1Array, val1OK := val1.([]interface{}) 162 val2Array, val2OK := val2.([]interface{}) 163 if !val1OK && !val2OK { 164 // arrays are the only types expected here that won't compare with `==` 165 return val1 == val2, nil 166 } 167 if !val1OK || !val2OK { 168 return false, nil 169 } 170 171 // compare two arrays by checksumming their elements 172 arr1Checksums, err := arrayToChecksums(val1Array) 173 if err != nil { 174 return false, errors.Wrap(err, "array1 to checksums") 175 } 176 arr2Checksums, err := arrayToChecksums(val2Array) 177 if err != nil { 178 return false, errors.Wrap(err, "array2 to checksums") 179 } 180 181 for k := range arr1Checksums { 182 _, ok := arr2Checksums[k] 183 if !ok { 184 return false, nil 185 } 186 delete(arr2Checksums, k) 187 } 188 189 return len(arr2Checksums) == 0, nil 190 } 191 192 func arrayToChecksums(arr []interface{}) (map[string]bool, error) { 193 result := map[string]bool{} 194 for _, v := range arr { 195 str, err := yaml.Marshal(v) 196 if err != nil { 197 return nil, errors.Wrap(err, "marshal value into yaml") 198 } 199 sum := sha256.Sum256(str) 200 result[fmt.Sprintf("%x", sum)] = true 201 } 202 return result, nil 203 }