github.com/latiif/helm@v2.15.0+incompatible/pkg/chartutil/values_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  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  	"text/template"
    27  
    28  	"github.com/golang/protobuf/ptypes/any"
    29  
    30  	kversion "k8s.io/apimachinery/pkg/version"
    31  	"k8s.io/helm/pkg/proto/hapi/chart"
    32  	"k8s.io/helm/pkg/timeconv"
    33  	"k8s.io/helm/pkg/version"
    34  )
    35  
    36  func TestReadValues(t *testing.T) {
    37  	doc := `# Test YAML parse
    38  poet: "Coleridge"
    39  title: "Rime of the Ancient Mariner"
    40  stanza:
    41    - "at"
    42    - "length"
    43    - "did"
    44    - cross
    45    - an
    46    - Albatross
    47  
    48  mariner:
    49    with: "crossbow"
    50    shot: "ALBATROSS"
    51  
    52  water:
    53    water:
    54      where: "everywhere"
    55      nor: "any drop to drink"
    56      temperature: 1234567890
    57  `
    58  
    59  	data, err := ReadValues([]byte(doc))
    60  	if err != nil {
    61  		t.Fatalf("Error parsing bytes: %s", err)
    62  	}
    63  	matchValues(t, data)
    64  
    65  	tests := []string{`poet: "Coleridge"`, "# Just a comment", ""}
    66  
    67  	for _, tt := range tests {
    68  		data, err = ReadValues([]byte(tt))
    69  		if err != nil {
    70  			t.Fatalf("Error parsing bytes (%s): %s", tt, err)
    71  		}
    72  		if data == nil {
    73  			t.Errorf(`YAML string "%s" gave a nil map`, tt)
    74  		}
    75  	}
    76  }
    77  
    78  func TestToRenderValuesCaps(t *testing.T) {
    79  
    80  	chartValues := `
    81  name: al Rashid
    82  where:
    83    city: Basrah
    84    title: caliph
    85  `
    86  	overideValues := `
    87  name: Haroun
    88  where:
    89    city: Baghdad
    90    date: 809 CE
    91  `
    92  
    93  	c := &chart.Chart{
    94  		Metadata:  &chart.Metadata{Name: "test"},
    95  		Templates: []*chart.Template{},
    96  		Values:    &chart.Config{Raw: chartValues},
    97  		Dependencies: []*chart.Chart{
    98  			{
    99  				Metadata: &chart.Metadata{Name: "where"},
   100  				Values:   &chart.Config{Raw: ""},
   101  			},
   102  		},
   103  		Files: []*any.Any{
   104  			{TypeUrl: "scheherazade/shahryar.txt", Value: []byte("1,001 Nights")},
   105  		},
   106  	}
   107  	v := &chart.Config{Raw: overideValues}
   108  
   109  	o := ReleaseOptions{
   110  		Name:      "Seven Voyages",
   111  		Time:      timeconv.Now(),
   112  		Namespace: "al Basrah",
   113  		IsInstall: true,
   114  		Revision:  5,
   115  	}
   116  
   117  	caps := &Capabilities{
   118  		APIVersions:   DefaultVersionSet,
   119  		TillerVersion: version.GetVersionProto(),
   120  		KubeVersion:   &kversion.Info{Major: "1"},
   121  	}
   122  
   123  	res, err := ToRenderValuesCaps(c, v, o, caps)
   124  	if err != nil {
   125  		t.Fatal(err)
   126  	}
   127  
   128  	// Ensure that the top-level values are all set.
   129  	if name := res["Chart"].(*chart.Metadata).Name; name != "test" {
   130  		t.Errorf("Expected chart name 'test', got %q", name)
   131  	}
   132  	relmap := res["Release"].(map[string]interface{})
   133  	if name := relmap["Name"]; name.(string) != "Seven Voyages" {
   134  		t.Errorf("Expected release name 'Seven Voyages', got %q", name)
   135  	}
   136  	if rev := relmap["Revision"]; rev.(int) != 5 {
   137  		t.Errorf("Expected release revision %d, got %q", 5, rev)
   138  	}
   139  	if relmap["IsUpgrade"].(bool) {
   140  		t.Error("Expected upgrade to be false.")
   141  	}
   142  	if !relmap["IsInstall"].(bool) {
   143  		t.Errorf("Expected install to be true.")
   144  	}
   145  	if data := res["Files"].(Files)["scheherazade/shahryar.txt"]; string(data) != "1,001 Nights" {
   146  		t.Errorf("Expected file '1,001 Nights', got %q", string(data))
   147  	}
   148  	if !res["Capabilities"].(*Capabilities).APIVersions.Has("v1") {
   149  		t.Error("Expected Capabilities to have v1 as an API")
   150  	}
   151  	if res["Capabilities"].(*Capabilities).TillerVersion.SemVer == "" {
   152  		t.Error("Expected Capabilities to have a Tiller version")
   153  	}
   154  	if res["Capabilities"].(*Capabilities).KubeVersion.Major != "1" {
   155  		t.Error("Expected Capabilities to have a Kube version")
   156  	}
   157  
   158  	var vals Values
   159  	vals = res["Values"].(Values)
   160  
   161  	if vals["name"] != "Haroun" {
   162  		t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals)
   163  	}
   164  	where := vals["where"].(map[string]interface{})
   165  	expects := map[string]string{
   166  		"city":  "Baghdad",
   167  		"date":  "809 CE",
   168  		"title": "caliph",
   169  	}
   170  	for field, expect := range expects {
   171  		if got := where[field]; got != expect {
   172  			t.Errorf("Expected %q, got %q (%v)", expect, got, where)
   173  		}
   174  	}
   175  }
   176  
   177  func TestReadValuesFile(t *testing.T) {
   178  	data, err := ReadValuesFile("./testdata/coleridge.yaml")
   179  	if err != nil {
   180  		t.Fatalf("Error reading YAML file: %s", err)
   181  	}
   182  	matchValues(t, data)
   183  }
   184  
   185  func ExampleValues() {
   186  	doc := `
   187  title: "Moby Dick"
   188  chapter:
   189    one:
   190      title: "Loomings"
   191    two:
   192      title: "The Carpet-Bag"
   193    three:
   194      title: "The Spouter Inn"
   195  `
   196  	d, err := ReadValues([]byte(doc))
   197  	if err != nil {
   198  		panic(err)
   199  	}
   200  	ch1, err := d.Table("chapter.one")
   201  	if err != nil {
   202  		panic("could not find chapter one")
   203  	}
   204  	fmt.Print(ch1["title"])
   205  	// Output:
   206  	// Loomings
   207  }
   208  
   209  func TestTable(t *testing.T) {
   210  	doc := `
   211  title: "Moby Dick"
   212  chapter:
   213    one:
   214      title: "Loomings"
   215    two:
   216      title: "The Carpet-Bag"
   217    three:
   218      title: "The Spouter Inn"
   219  `
   220  	d, err := ReadValues([]byte(doc))
   221  	if err != nil {
   222  		t.Fatalf("Failed to parse the White Whale: %s", err)
   223  	}
   224  
   225  	if _, err := d.Table("title"); err == nil {
   226  		t.Fatalf("Title is not a table.")
   227  	}
   228  
   229  	if _, err := d.Table("chapter"); err != nil {
   230  		t.Fatalf("Failed to get the chapter table: %s\n%v", err, d)
   231  	}
   232  
   233  	if v, err := d.Table("chapter.one"); err != nil {
   234  		t.Errorf("Failed to get chapter.one: %s", err)
   235  	} else if v["title"] != "Loomings" {
   236  		t.Errorf("Unexpected title: %s", v["title"])
   237  	}
   238  
   239  	if _, err := d.Table("chapter.three"); err != nil {
   240  		t.Errorf("Chapter three is missing: %s\n%v", err, d)
   241  	}
   242  
   243  	if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil {
   244  		t.Errorf("I think you mean 'Epilogue'")
   245  	}
   246  }
   247  
   248  func matchValues(t *testing.T, data map[string]interface{}) {
   249  	if data["poet"] != "Coleridge" {
   250  		t.Errorf("Unexpected poet: %s", data["poet"])
   251  	}
   252  
   253  	if o, err := ttpl("{{len .stanza}}", data); err != nil {
   254  		t.Errorf("len stanza: %s", err)
   255  	} else if o != "6" {
   256  		t.Errorf("Expected 6, got %s", o)
   257  	}
   258  
   259  	if o, err := ttpl("{{.mariner.shot}}", data); err != nil {
   260  		t.Errorf(".mariner.shot: %s", err)
   261  	} else if o != "ALBATROSS" {
   262  		t.Errorf("Expected that mariner shot ALBATROSS")
   263  	}
   264  
   265  	if o, err := ttpl("{{.water.water.where}}", data); err != nil {
   266  		t.Errorf(".water.water.where: %s", err)
   267  	} else if o != "everywhere" {
   268  		t.Errorf("Expected water water everywhere")
   269  	}
   270  
   271  	if o, err := ttpl("{{.water.water.temperature}}", data); err != nil {
   272  		t.Errorf(".water.water.temperature: %s", err)
   273  	} else if o != "1234567890" {
   274  		t.Errorf("Expected water water temperature: 1234567890, got: %s", o)
   275  	}
   276  }
   277  
   278  func ttpl(tpl string, v map[string]interface{}) (string, error) {
   279  	var b bytes.Buffer
   280  	tt := template.Must(template.New("t").Parse(tpl))
   281  	if err := tt.Execute(&b, v); err != nil {
   282  		return "", err
   283  	}
   284  	return b.String(), nil
   285  }
   286  
   287  // ref: http://www.yaml.org/spec/1.2/spec.html#id2803362
   288  var testCoalesceValuesYaml = `
   289  top: yup
   290  bottom: null
   291  right: Null
   292  left: NULL
   293  front: ~
   294  back: ""
   295  
   296  global:
   297    name: Ishmael
   298    subject: Queequeg
   299    nested:
   300      boat: true
   301  
   302  pequod:
   303    global:
   304      name: Stinky
   305      harpooner: Tashtego
   306      nested:
   307        boat: false
   308        sail: true
   309    ahab:
   310      scope: whale
   311  
   312  # test coalesce with nested null values
   313  web:
   314    livenessProbe:
   315      httpGet: null
   316      exec:
   317        command:
   318        - curl
   319        - -f
   320        - http://localhost:8080/api/v1/info
   321      timeoutSeconds: null
   322    readinessProbe:
   323      httpGet: null
   324      exec:
   325        command:
   326        - curl
   327        - -f
   328        - http://localhost:8080/api/v1/info
   329      timeoutSeconds: null # catches the case where this wasn't defined in the original source...
   330  `
   331  
   332  func TestCoalesceValues(t *testing.T) {
   333  	tchart := "testdata/moby"
   334  	c, err := LoadDir(tchart)
   335  	if err != nil {
   336  		t.Fatal(err)
   337  	}
   338  
   339  	tvals := &chart.Config{Raw: testCoalesceValuesYaml}
   340  
   341  	v, err := CoalesceValues(c, tvals)
   342  	if err != nil {
   343  		t.Fatal(err)
   344  	}
   345  	j, _ := json.MarshalIndent(v, "", "  ")
   346  	t.Logf("Coalesced Values: %s", string(j))
   347  
   348  	tests := []struct {
   349  		tpl    string
   350  		expect string
   351  	}{
   352  		{"{{.top}}", "yup"},
   353  		{"{{.back}}", ""},
   354  		{"{{.name}}", "moby"},
   355  		{"{{.global.name}}", "Ishmael"},
   356  		{"{{.global.subject}}", "Queequeg"},
   357  		{"{{.global.harpooner}}", "<no value>"},
   358  		{"{{.pequod.name}}", "pequod"},
   359  		{"{{.pequod.ahab.name}}", "ahab"},
   360  		{"{{.pequod.ahab.scope}}", "whale"},
   361  		{"{{.pequod.ahab.global.name}}", "Ishmael"},
   362  		{"{{.pequod.ahab.global.subject}}", "Queequeg"},
   363  		{"{{.pequod.ahab.global.harpooner}}", "Tashtego"},
   364  		{"{{.pequod.global.name}}", "Ishmael"},
   365  		{"{{.pequod.global.subject}}", "Queequeg"},
   366  		{"{{.spouter.global.name}}", "Ishmael"},
   367  		{"{{.spouter.global.harpooner}}", "<no value>"},
   368  
   369  		{"{{.global.nested.boat}}", "true"},
   370  		{"{{.pequod.global.nested.boat}}", "true"},
   371  		{"{{.spouter.global.nested.boat}}", "true"},
   372  		{"{{.pequod.global.nested.sail}}", "true"},
   373  		{"{{.spouter.global.nested.sail}}", "<no value>"},
   374  
   375  		{"{{.web.livenessProbe.failureThreshold}}", "5"},
   376  		{"{{.web.livenessProbe.initialDelaySeconds}}", "10"},
   377  		{"{{.web.livenessProbe.periodSeconds}}", "15"},
   378  		{"{{.web.livenessProbe.exec}}", "map[command:[curl -f http://localhost:8080/api/v1/info]]"},
   379  
   380  		{"{{.web.readinessProbe.exec}}", "map[command:[curl -f http://localhost:8080/api/v1/info]]"},
   381  	}
   382  
   383  	for _, tt := range tests {
   384  		if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect {
   385  			t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o)
   386  		}
   387  	}
   388  
   389  	nullKeys := []string{"bottom", "right", "left", "front",
   390  		"web.livenessProbe.httpGet", "web.readinessProbe.httpGet", "web.livenessProbe.timeoutSeconds", "web.readinessProbe.timeoutSeconds"}
   391  	for _, nullKey := range nullKeys {
   392  		parts := strings.Split(nullKey, ".")
   393  		curMap := v
   394  		for partIdx, part := range parts {
   395  			nextVal, ok := curMap[part]
   396  			if partIdx == len(parts)-1 { // are we the last?
   397  				if ok {
   398  					t.Errorf("Expected key %q to be removed, still present", nullKey)
   399  					break
   400  				}
   401  			} else { // we are not the last
   402  				if !ok {
   403  					t.Errorf("Expected key %q to be removed, but partial parent path was not found", nullKey)
   404  					break
   405  				}
   406  				curMap, ok = nextVal.(map[string]interface{})
   407  				if !ok {
   408  					t.Errorf("Expected key %q to be removed, but partial parent path did not result in a map", nullKey)
   409  					break
   410  				}
   411  			}
   412  		}
   413  	}
   414  }
   415  
   416  func TestCoalesceTables(t *testing.T) {
   417  	dst := map[string]interface{}{
   418  		"name": "Ishmael",
   419  		"address": map[string]interface{}{
   420  			"street": "123 Spouter Inn Ct.",
   421  			"city":   "Nantucket",
   422  		},
   423  		"details": map[string]interface{}{
   424  			"friends": []string{"Tashtego"},
   425  		},
   426  		"boat": "pequod",
   427  	}
   428  	src := map[string]interface{}{
   429  		"occupation": "whaler",
   430  		"address": map[string]interface{}{
   431  			"state":  "MA",
   432  			"street": "234 Spouter Inn Ct.",
   433  		},
   434  		"details": "empty",
   435  		"boat": map[string]interface{}{
   436  			"mast": true,
   437  		},
   438  	}
   439  
   440  	// What we expect is that anything in dst overrides anything in src, but that
   441  	// otherwise the values are coalesced.
   442  	dst = coalesceTables(dst, src, "")
   443  
   444  	if dst["name"] != "Ishmael" {
   445  		t.Errorf("Unexpected name: %s", dst["name"])
   446  	}
   447  	if dst["occupation"] != "whaler" {
   448  		t.Errorf("Unexpected occupation: %s", dst["occupation"])
   449  	}
   450  
   451  	addr, ok := dst["address"].(map[string]interface{})
   452  	if !ok {
   453  		t.Fatal("Address went away.")
   454  	}
   455  
   456  	if addr["street"].(string) != "123 Spouter Inn Ct." {
   457  		t.Errorf("Unexpected address: %v", addr["street"])
   458  	}
   459  
   460  	if addr["city"].(string) != "Nantucket" {
   461  		t.Errorf("Unexpected city: %v", addr["city"])
   462  	}
   463  
   464  	if addr["state"].(string) != "MA" {
   465  		t.Errorf("Unexpected state: %v", addr["state"])
   466  	}
   467  
   468  	if det, ok := dst["details"].(map[string]interface{}); !ok {
   469  		t.Fatalf("Details is the wrong type: %v", dst["details"])
   470  	} else if _, ok := det["friends"]; !ok {
   471  		t.Error("Could not find your friends. Maybe you don't have any. :-(")
   472  	}
   473  
   474  	if dst["boat"].(string) != "pequod" {
   475  		t.Errorf("Expected boat string, got %v", dst["boat"])
   476  	}
   477  }
   478  
   479  func TestCoalesceSubchart(t *testing.T) {
   480  	tchart := "testdata/moby"
   481  	c, err := LoadDir(tchart)
   482  	if err != nil {
   483  		t.Fatal(err)
   484  	}
   485  
   486  	tvals := &chart.Config{}
   487  
   488  	v, err := CoalesceValues(c, tvals)
   489  	if err != nil {
   490  		t.Fatal(err)
   491  	}
   492  	j, _ := json.MarshalIndent(v, "", "  ")
   493  	t.Logf("Coalesced Values: %s", string(j))
   494  
   495  	subchartValues, ok := v["spouter"].(map[string]interface{})
   496  	if !ok {
   497  		t.Errorf("Subchart values not found")
   498  	}
   499  
   500  	if _, ok := subchartValues["foo"]; ok {
   501  		t.Errorf("Expected key foo to be removed, still present")
   502  	}
   503  }
   504  
   505  func TestPathValue(t *testing.T) {
   506  	doc := `
   507  title: "Moby Dick"
   508  chapter:
   509    one:
   510      title: "Loomings"
   511    two:
   512      title: "The Carpet-Bag"
   513    three:
   514      title: "The Spouter Inn"
   515  `
   516  	d, err := ReadValues([]byte(doc))
   517  	if err != nil {
   518  		t.Fatalf("Failed to parse the White Whale: %s", err)
   519  	}
   520  
   521  	if v, err := d.PathValue("chapter.one.title"); err != nil {
   522  		t.Errorf("Got error instead of title: %s\n%v", err, d)
   523  	} else if v != "Loomings" {
   524  		t.Errorf("No error but got wrong value for title: %s\n%v", err, d)
   525  	}
   526  	if _, err := d.PathValue("chapter.one.doesntexist"); err == nil {
   527  		t.Errorf("Non-existent key should return error: %s\n%v", err, d)
   528  	}
   529  	if _, err := d.PathValue("chapter.doesntexist.one"); err == nil {
   530  		t.Errorf("Non-existent key in middle of path should return error: %s\n%v", err, d)
   531  	}
   532  	if _, err := d.PathValue(""); err == nil {
   533  		t.Error("Asking for the value from an empty path should yield an error")
   534  	}
   535  	if v, err := d.PathValue("title"); err == nil {
   536  		if v != "Moby Dick" {
   537  			t.Errorf("Failed to return values for root key title")
   538  		}
   539  	}
   540  }
   541  
   542  func TestValuesMergeInto(t *testing.T) {
   543  	testCases := map[string]struct {
   544  		destination string
   545  		source      string
   546  		result      string
   547  	}{
   548  		"maps are merged": {
   549  			`
   550  resources:
   551   requests:
   552     cpu: 400m
   553   something: else
   554  `,
   555  			`
   556  resources:
   557   requests:
   558     cpu: 500m
   559  `,
   560  			`
   561  resources:
   562   requests:
   563     cpu: 500m
   564   something: else
   565  `,
   566  		},
   567  		"values are replaced": {
   568  			`
   569  firstKey: firstValue
   570  secondKey: secondValue
   571  thirdKey: thirdValue
   572  `,
   573  			`
   574  firstKey: newFirstValue
   575  thirdKey: newThirdValue
   576  `,
   577  			`
   578  firstKey: newFirstValue
   579  secondKey: secondValue
   580  thirdKey: newThirdValue
   581  `,
   582  		},
   583  		"new values are added": {
   584  			`
   585  existingKey: existingValue
   586  `,
   587  			`
   588  newKey: newValue
   589  anotherNewKey:
   590    nestedNewKey: nestedNewValue
   591  `,
   592  			`
   593  existingKey: existingValue
   594  newKey: newValue
   595  anotherNewKey:
   596    nestedNewKey: nestedNewValue
   597  `,
   598  		},
   599  	}
   600  
   601  	for name, tc := range testCases {
   602  		d, err := ReadValues([]byte(tc.destination))
   603  		if err != nil {
   604  			t.Error(err)
   605  		}
   606  		s, err := ReadValues([]byte(tc.source))
   607  		if err != nil {
   608  			t.Error(err)
   609  		}
   610  		expectedRes, err := ReadValues([]byte(tc.result))
   611  		if err != nil {
   612  			t.Error(err)
   613  		}
   614  
   615  		d.MergeInto(s)
   616  
   617  		if !reflect.DeepEqual(expectedRes, d) {
   618  			t.Errorf("%s: Expected %v, but got %v", name, expectedRes, d)
   619  		}
   620  	}
   621  }
   622  
   623  func TestOverriteTableItemWithNonTableValue(t *testing.T) {
   624  	// src has a table value for "foo"
   625  	src := map[string]interface{}{
   626  		"foo": map[string]interface{}{
   627  			"baz": "boz",
   628  		},
   629  	}
   630  
   631  	// dst has a non-table value for "foo"
   632  	dst := map[string]interface{}{
   633  		"foo": "bar",
   634  	}
   635  
   636  	// result - this may print a warning, but we has always "worked"
   637  	result := coalesceTables(dst, src, "")
   638  	expected := map[string]interface{}{
   639  		"foo": "bar",
   640  	}
   641  
   642  	if !reflect.DeepEqual(result, expected) {
   643  		t.Errorf("Expected %v, but got %v", expected, result)
   644  	}
   645  }
   646  
   647  func TestSubchartCoaleseWithNullValue(t *testing.T) {
   648  	v, err := CoalesceValues(&chart.Chart{
   649  		Metadata: &chart.Metadata{Name: "demo"},
   650  		Dependencies: []*chart.Chart{
   651  			{
   652  				Metadata: &chart.Metadata{Name: "logstash"},
   653  				Values: &chart.Config{
   654  					Raw: `livenessProbe: {httpGet: {path: "/", port: monitor}}`,
   655  				},
   656  			},
   657  		},
   658  		Values: &chart.Config{
   659  			Raw: `logstash: {livenessProbe: {httpGet: null, exec: "/bin/true"}}`,
   660  		},
   661  	}, &chart.Config{})
   662  	if err != nil {
   663  		t.Errorf("Failed with %s", err)
   664  	}
   665  	result := v.AsMap()
   666  	expected := map[string]interface{}{
   667  		"logstash": map[string]interface{}{
   668  			"global": map[string]interface{}{},
   669  			"livenessProbe": map[string]interface{}{
   670  				"exec": "/bin/true",
   671  			},
   672  		},
   673  	}
   674  	if !reflect.DeepEqual(result, expected) {
   675  		t.Errorf("got %+v, expected %+v", result, expected)
   676  	}
   677  }