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  }