github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/chartutil/coalesce_test.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package chartutil 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "testing" 23 24 "github.com/stretchr/testify/assert" 25 26 "github.com/stefanmcshane/helm/pkg/chart" 27 ) 28 29 // ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 30 var testCoalesceValuesYaml = []byte(` 31 top: yup 32 bottom: null 33 right: Null 34 left: NULL 35 front: ~ 36 back: "" 37 nested: 38 boat: null 39 40 global: 41 name: Ishmael 42 subject: Queequeg 43 nested: 44 boat: true 45 46 pequod: 47 global: 48 name: Stinky 49 harpooner: Tashtego 50 nested: 51 boat: false 52 sail: true 53 ahab: 54 scope: whale 55 boat: null 56 nested: 57 foo: true 58 bar: null 59 `) 60 61 func withDeps(c *chart.Chart, deps ...*chart.Chart) *chart.Chart { 62 c.AddDependency(deps...) 63 return c 64 } 65 66 func TestCoalesceValues(t *testing.T) { 67 is := assert.New(t) 68 69 c := withDeps(&chart.Chart{ 70 Metadata: &chart.Metadata{Name: "moby"}, 71 Values: map[string]interface{}{ 72 "back": "exists", 73 "bottom": "exists", 74 "front": "exists", 75 "left": "exists", 76 "name": "moby", 77 "nested": map[string]interface{}{"boat": true}, 78 "override": "bad", 79 "right": "exists", 80 "scope": "moby", 81 "top": "nope", 82 "global": map[string]interface{}{ 83 "nested2": map[string]interface{}{"l0": "moby"}, 84 }, 85 }, 86 }, 87 withDeps(&chart.Chart{ 88 Metadata: &chart.Metadata{Name: "pequod"}, 89 Values: map[string]interface{}{ 90 "name": "pequod", 91 "scope": "pequod", 92 "global": map[string]interface{}{ 93 "nested2": map[string]interface{}{"l1": "pequod"}, 94 }, 95 }, 96 }, 97 &chart.Chart{ 98 Metadata: &chart.Metadata{Name: "ahab"}, 99 Values: map[string]interface{}{ 100 "global": map[string]interface{}{ 101 "nested": map[string]interface{}{"foo": "bar"}, 102 "nested2": map[string]interface{}{"l2": "ahab"}, 103 }, 104 "scope": "ahab", 105 "name": "ahab", 106 "boat": true, 107 "nested": map[string]interface{}{"foo": false, "bar": true}, 108 }, 109 }, 110 ), 111 &chart.Chart{ 112 Metadata: &chart.Metadata{Name: "spouter"}, 113 Values: map[string]interface{}{ 114 "scope": "spouter", 115 "global": map[string]interface{}{ 116 "nested2": map[string]interface{}{"l1": "spouter"}, 117 }, 118 }, 119 }, 120 ) 121 122 vals, err := ReadValues(testCoalesceValuesYaml) 123 if err != nil { 124 t.Fatal(err) 125 } 126 127 // taking a copy of the values before passing it 128 // to CoalesceValues as argument, so that we can 129 // use it for asserting later 130 valsCopy := make(Values, len(vals)) 131 for key, value := range vals { 132 valsCopy[key] = value 133 } 134 135 v, err := CoalesceValues(c, vals) 136 if err != nil { 137 t.Fatal(err) 138 } 139 j, _ := json.MarshalIndent(v, "", " ") 140 t.Logf("Coalesced Values: %s", string(j)) 141 142 tests := []struct { 143 tpl string 144 expect string 145 }{ 146 {"{{.top}}", "yup"}, 147 {"{{.back}}", ""}, 148 {"{{.name}}", "moby"}, 149 {"{{.global.name}}", "Ishmael"}, 150 {"{{.global.subject}}", "Queequeg"}, 151 {"{{.global.harpooner}}", "<no value>"}, 152 {"{{.pequod.name}}", "pequod"}, 153 {"{{.pequod.ahab.name}}", "ahab"}, 154 {"{{.pequod.ahab.scope}}", "whale"}, 155 {"{{.pequod.ahab.nested.foo}}", "true"}, 156 {"{{.pequod.ahab.global.name}}", "Ishmael"}, 157 {"{{.pequod.ahab.global.nested.foo}}", "bar"}, 158 {"{{.pequod.ahab.global.subject}}", "Queequeg"}, 159 {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, 160 {"{{.pequod.global.name}}", "Ishmael"}, 161 {"{{.pequod.global.nested.foo}}", "<no value>"}, 162 {"{{.pequod.global.subject}}", "Queequeg"}, 163 {"{{.spouter.global.name}}", "Ishmael"}, 164 {"{{.spouter.global.harpooner}}", "<no value>"}, 165 166 {"{{.global.nested.boat}}", "true"}, 167 {"{{.pequod.global.nested.boat}}", "true"}, 168 {"{{.spouter.global.nested.boat}}", "true"}, 169 {"{{.pequod.global.nested.sail}}", "true"}, 170 {"{{.spouter.global.nested.sail}}", "<no value>"}, 171 172 {"{{.global.nested2.l0}}", "moby"}, 173 {"{{.global.nested2.l1}}", "<no value>"}, 174 {"{{.global.nested2.l2}}", "<no value>"}, 175 {"{{.pequod.global.nested2.l0}}", "moby"}, 176 {"{{.pequod.global.nested2.l1}}", "pequod"}, 177 {"{{.pequod.global.nested2.l2}}", "<no value>"}, 178 {"{{.pequod.ahab.global.nested2.l0}}", "moby"}, 179 {"{{.pequod.ahab.global.nested2.l1}}", "pequod"}, 180 {"{{.pequod.ahab.global.nested2.l2}}", "ahab"}, 181 {"{{.spouter.global.nested2.l0}}", "moby"}, 182 {"{{.spouter.global.nested2.l1}}", "spouter"}, 183 {"{{.spouter.global.nested2.l2}}", "<no value>"}, 184 } 185 186 for _, tt := range tests { 187 if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect { 188 t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o) 189 } 190 } 191 192 nullKeys := []string{"bottom", "right", "left", "front"} 193 for _, nullKey := range nullKeys { 194 if _, ok := v[nullKey]; ok { 195 t.Errorf("Expected key %q to be removed, still present", nullKey) 196 } 197 } 198 199 if _, ok := v["nested"].(map[string]interface{})["boat"]; ok { 200 t.Error("Expected nested boat key to be removed, still present") 201 } 202 203 subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{}) 204 if _, ok := subchart["boat"]; ok { 205 t.Error("Expected subchart boat key to be removed, still present") 206 } 207 208 if _, ok := subchart["nested"].(map[string]interface{})["bar"]; ok { 209 t.Error("Expected subchart nested bar key to be removed, still present") 210 } 211 212 // CoalesceValues should not mutate the passed arguments 213 is.Equal(valsCopy, vals) 214 } 215 216 func TestCoalesceTables(t *testing.T) { 217 dst := map[string]interface{}{ 218 "name": "Ishmael", 219 "address": map[string]interface{}{ 220 "street": "123 Spouter Inn Ct.", 221 "city": "Nantucket", 222 "country": nil, 223 }, 224 "details": map[string]interface{}{ 225 "friends": []string{"Tashtego"}, 226 }, 227 "boat": "pequod", 228 "hole": nil, 229 } 230 src := map[string]interface{}{ 231 "occupation": "whaler", 232 "address": map[string]interface{}{ 233 "state": "MA", 234 "street": "234 Spouter Inn Ct.", 235 "country": "US", 236 }, 237 "details": "empty", 238 "boat": map[string]interface{}{ 239 "mast": true, 240 }, 241 "hole": "black", 242 } 243 244 // What we expect is that anything in dst overrides anything in src, but that 245 // otherwise the values are coalesced. 246 CoalesceTables(dst, src) 247 248 if dst["name"] != "Ishmael" { 249 t.Errorf("Unexpected name: %s", dst["name"]) 250 } 251 if dst["occupation"] != "whaler" { 252 t.Errorf("Unexpected occupation: %s", dst["occupation"]) 253 } 254 255 addr, ok := dst["address"].(map[string]interface{}) 256 if !ok { 257 t.Fatal("Address went away.") 258 } 259 260 if addr["street"].(string) != "123 Spouter Inn Ct." { 261 t.Errorf("Unexpected address: %v", addr["street"]) 262 } 263 264 if addr["city"].(string) != "Nantucket" { 265 t.Errorf("Unexpected city: %v", addr["city"]) 266 } 267 268 if addr["state"].(string) != "MA" { 269 t.Errorf("Unexpected state: %v", addr["state"]) 270 } 271 272 if _, ok = addr["country"]; ok { 273 t.Error("The country is not left out.") 274 } 275 276 if det, ok := dst["details"].(map[string]interface{}); !ok { 277 t.Fatalf("Details is the wrong type: %v", dst["details"]) 278 } else if _, ok := det["friends"]; !ok { 279 t.Error("Could not find your friends. Maybe you don't have any. :-(") 280 } 281 282 if dst["boat"].(string) != "pequod" { 283 t.Errorf("Expected boat string, got %v", dst["boat"]) 284 } 285 286 if _, ok = dst["hole"]; ok { 287 t.Error("The hole still exists.") 288 } 289 290 dst2 := map[string]interface{}{ 291 "name": "Ishmael", 292 "address": map[string]interface{}{ 293 "street": "123 Spouter Inn Ct.", 294 "city": "Nantucket", 295 "country": "US", 296 }, 297 "details": map[string]interface{}{ 298 "friends": []string{"Tashtego"}, 299 }, 300 "boat": "pequod", 301 "hole": "black", 302 } 303 304 // What we expect is that anything in dst should have all values set, 305 // this happens when the --reuse-values flag is set but the chart has no modifications yet 306 CoalesceTables(dst2, nil) 307 308 if dst2["name"] != "Ishmael" { 309 t.Errorf("Unexpected name: %s", dst2["name"]) 310 } 311 312 addr2, ok := dst2["address"].(map[string]interface{}) 313 if !ok { 314 t.Fatal("Address went away.") 315 } 316 317 if addr2["street"].(string) != "123 Spouter Inn Ct." { 318 t.Errorf("Unexpected address: %v", addr2["street"]) 319 } 320 321 if addr2["city"].(string) != "Nantucket" { 322 t.Errorf("Unexpected city: %v", addr2["city"]) 323 } 324 325 if addr2["country"].(string) != "US" { 326 t.Errorf("Unexpected Country: %v", addr2["country"]) 327 } 328 329 if det2, ok := dst2["details"].(map[string]interface{}); !ok { 330 t.Fatalf("Details is the wrong type: %v", dst2["details"]) 331 } else if _, ok := det2["friends"]; !ok { 332 t.Error("Could not find your friends. Maybe you don't have any. :-(") 333 } 334 335 if dst2["boat"].(string) != "pequod" { 336 t.Errorf("Expected boat string, got %v", dst2["boat"]) 337 } 338 339 if dst2["hole"].(string) != "black" { 340 t.Errorf("Expected hole string, got %v", dst2["boat"]) 341 } 342 } 343 344 func TestCoalesceValuesWarnings(t *testing.T) { 345 346 c := withDeps(&chart.Chart{ 347 Metadata: &chart.Metadata{Name: "level1"}, 348 Values: map[string]interface{}{ 349 "name": "moby", 350 }, 351 }, 352 withDeps(&chart.Chart{ 353 Metadata: &chart.Metadata{Name: "level2"}, 354 Values: map[string]interface{}{ 355 "name": "pequod", 356 }, 357 }, 358 &chart.Chart{ 359 Metadata: &chart.Metadata{Name: "level3"}, 360 Values: map[string]interface{}{ 361 "name": "ahab", 362 "boat": true, 363 "spear": map[string]interface{}{ 364 "tip": true, 365 "sail": map[string]interface{}{ 366 "cotton": true, 367 }, 368 }, 369 }, 370 }, 371 ), 372 ) 373 374 vals := map[string]interface{}{ 375 "level2": map[string]interface{}{ 376 "level3": map[string]interface{}{ 377 "boat": map[string]interface{}{"mast": true}, 378 "spear": map[string]interface{}{ 379 "tip": map[string]interface{}{ 380 "sharp": true, 381 }, 382 "sail": true, 383 }, 384 }, 385 }, 386 } 387 388 warnings := make([]string, 0) 389 printf := func(format string, v ...interface{}) { 390 t.Logf(format, v...) 391 warnings = append(warnings, fmt.Sprintf(format, v...)) 392 } 393 394 _, err := coalesce(printf, c, vals, "") 395 if err != nil { 396 t.Fatal(err) 397 } 398 399 t.Logf("vals: %v", vals) 400 assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.") 401 assert.Contains(t, warnings, "warning: destination for level1.level2.level3.spear.tip is a table. Ignoring non-table value (true)") 402 assert.Contains(t, warnings, "warning: cannot overwrite table with non table for level1.level2.level3.spear.sail (map[cotton:true])") 403 404 } 405 406 func TestConcatPrefix(t *testing.T) { 407 assert.Equal(t, "b", concatPrefix("", "b")) 408 assert.Equal(t, "a.b", concatPrefix("a", "b")) 409 }