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