github.com/appscode/helm@v3.0.0-alpha.1+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  	"io/ioutil"
    24  	"testing"
    25  	"text/template"
    26  
    27  	"helm.sh/helm/pkg/chart"
    28  )
    29  
    30  func TestReadValues(t *testing.T) {
    31  	doc := `# Test YAML parse
    32  poet: "Coleridge"
    33  title: "Rime of the Ancient Mariner"
    34  stanza:
    35    - "at"
    36    - "length"
    37    - "did"
    38    - cross
    39    - an
    40    - Albatross
    41  
    42  mariner:
    43    with: "crossbow"
    44    shot: "ALBATROSS"
    45  
    46  water:
    47    water:
    48      where: "everywhere"
    49      nor: "any drop to drink"
    50  `
    51  
    52  	data, err := ReadValues([]byte(doc))
    53  	if err != nil {
    54  		t.Fatalf("Error parsing bytes: %s", err)
    55  	}
    56  	matchValues(t, data)
    57  
    58  	tests := []string{`poet: "Coleridge"`, "# Just a comment", ""}
    59  
    60  	for _, tt := range tests {
    61  		data, err = ReadValues([]byte(tt))
    62  		if err != nil {
    63  			t.Fatalf("Error parsing bytes (%s): %s", tt, err)
    64  		}
    65  		if data == nil {
    66  			t.Errorf(`YAML string "%s" gave a nil map`, tt)
    67  		}
    68  	}
    69  }
    70  
    71  func TestToRenderValues(t *testing.T) {
    72  
    73  	chartValues := map[string]interface{}{
    74  		"name": "al Rashid",
    75  		"where": map[string]interface{}{
    76  			"city":  "Basrah",
    77  			"title": "caliph",
    78  		},
    79  	}
    80  
    81  	overideValues := map[string]interface{}{
    82  		"name": "Haroun",
    83  		"where": map[string]interface{}{
    84  			"city": "Baghdad",
    85  			"date": "809 CE",
    86  		},
    87  	}
    88  
    89  	c := &chart.Chart{
    90  		Metadata:  &chart.Metadata{Name: "test"},
    91  		Templates: []*chart.File{},
    92  		Values:    chartValues,
    93  		Files: []*chart.File{
    94  			{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
    95  		},
    96  	}
    97  	c.AddDependency(&chart.Chart{
    98  		Metadata: &chart.Metadata{Name: "where"},
    99  	})
   100  
   101  	o := ReleaseOptions{
   102  		Name:      "Seven Voyages",
   103  		IsInstall: true,
   104  	}
   105  
   106  	res, err := ToRenderValues(c, overideValues, o, nil)
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  
   111  	// Ensure that the top-level values are all set.
   112  	if name := res["Chart"].(*chart.Metadata).Name; name != "test" {
   113  		t.Errorf("Expected chart name 'test', got %q", name)
   114  	}
   115  	relmap := res["Release"].(map[string]interface{})
   116  	if name := relmap["Name"]; name.(string) != "Seven Voyages" {
   117  		t.Errorf("Expected release name 'Seven Voyages', got %q", name)
   118  	}
   119  	if relmap["IsUpgrade"].(bool) {
   120  		t.Error("Expected upgrade to be false.")
   121  	}
   122  	if !relmap["IsInstall"].(bool) {
   123  		t.Errorf("Expected install to be true.")
   124  	}
   125  	if !res["Capabilities"].(*Capabilities).APIVersions.Has("v1") {
   126  		t.Error("Expected Capabilities to have v1 as an API")
   127  	}
   128  	if res["Capabilities"].(*Capabilities).KubeVersion.Major != "1" {
   129  		t.Error("Expected Capabilities to have a Kube version")
   130  	}
   131  
   132  	vals := res["Values"].(Values)
   133  	if vals["name"] != "Haroun" {
   134  		t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals)
   135  	}
   136  	where := vals["where"].(map[string]interface{})
   137  	expects := map[string]string{
   138  		"city":  "Baghdad",
   139  		"date":  "809 CE",
   140  		"title": "caliph",
   141  	}
   142  	for field, expect := range expects {
   143  		if got := where[field]; got != expect {
   144  			t.Errorf("Expected %q, got %q (%v)", expect, got, where)
   145  		}
   146  	}
   147  }
   148  
   149  func TestReadValuesFile(t *testing.T) {
   150  	data, err := ReadValuesFile("./testdata/coleridge.yaml")
   151  	if err != nil {
   152  		t.Fatalf("Error reading YAML file: %s", err)
   153  	}
   154  	matchValues(t, data)
   155  }
   156  
   157  func TestValidateAgainstSingleSchema(t *testing.T) {
   158  	values, err := ReadValuesFile("./testdata/test-values.yaml")
   159  	if err != nil {
   160  		t.Fatalf("Error reading YAML file: %s", err)
   161  	}
   162  	schema, err := ioutil.ReadFile("./testdata/test-values.schema.json")
   163  	if err != nil {
   164  		t.Fatalf("Error reading YAML file: %s", err)
   165  	}
   166  
   167  	if err := ValidateAgainstSingleSchema(values, schema); err != nil {
   168  		t.Errorf("Error validating Values against Schema: %s", err)
   169  	}
   170  }
   171  
   172  func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
   173  	values, err := ReadValuesFile("./testdata/test-values-negative.yaml")
   174  	if err != nil {
   175  		t.Fatalf("Error reading YAML file: %s", err)
   176  	}
   177  	schema, err := ioutil.ReadFile("./testdata/test-values.schema.json")
   178  	if err != nil {
   179  		t.Fatalf("Error reading YAML file: %s", err)
   180  	}
   181  
   182  	var errString string
   183  	if err := ValidateAgainstSingleSchema(values, schema); err == nil {
   184  		t.Fatalf("Expected an error, but got nil")
   185  	} else {
   186  		errString = err.Error()
   187  	}
   188  
   189  	expectedErrString := `- (root): employmentInfo is required
   190  - age: Must be greater than or equal to 0/1
   191  `
   192  	if errString != expectedErrString {
   193  		t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
   194  	}
   195  }
   196  
   197  const subchrtSchema = `{
   198    "$schema": "http://json-schema.org/draft-07/schema#",
   199    "title": "Values",
   200    "type": "object",
   201    "properties": {
   202      "age": {
   203        "description": "Age",
   204        "minimum": 0,
   205        "type": "integer"
   206      }
   207    },
   208    "required": [
   209      "age"
   210    ]
   211  }
   212  `
   213  
   214  func TestValidateAgainstSchema(t *testing.T) {
   215  	subchrtJSON := []byte(subchrtSchema)
   216  	subchrt := &chart.Chart{
   217  		Metadata: &chart.Metadata{
   218  			Name: "subchrt",
   219  		},
   220  		Schema: subchrtJSON,
   221  	}
   222  	chrt := &chart.Chart{
   223  		Metadata: &chart.Metadata{
   224  			Name: "chrt",
   225  		},
   226  	}
   227  	chrt.AddDependency(subchrt)
   228  
   229  	vals := map[string]interface{}{
   230  		"name": "John",
   231  		"subchrt": map[string]interface{}{
   232  			"age": 25,
   233  		},
   234  	}
   235  
   236  	if err := ValidateAgainstSchema(chrt, vals); err != nil {
   237  		t.Errorf("Error validating Values against Schema: %s", err)
   238  	}
   239  }
   240  
   241  func TestValidateAgainstSchemaNegative(t *testing.T) {
   242  	subchrtJSON := []byte(subchrtSchema)
   243  	subchrt := &chart.Chart{
   244  		Metadata: &chart.Metadata{
   245  			Name: "subchrt",
   246  		},
   247  		Schema: subchrtJSON,
   248  	}
   249  	chrt := &chart.Chart{
   250  		Metadata: &chart.Metadata{
   251  			Name: "chrt",
   252  		},
   253  	}
   254  	chrt.AddDependency(subchrt)
   255  
   256  	vals := map[string]interface{}{
   257  		"name":    "John",
   258  		"subchrt": map[string]interface{}{},
   259  	}
   260  
   261  	var errString string
   262  	if err := ValidateAgainstSchema(chrt, vals); err == nil {
   263  		t.Fatalf("Expected an error, but got nil")
   264  	} else {
   265  		errString = err.Error()
   266  	}
   267  
   268  	expectedErrString := `subchrt:
   269  - (root): age is required
   270  `
   271  	if errString != expectedErrString {
   272  		t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
   273  	}
   274  }
   275  
   276  func ExampleValues() {
   277  	doc := `
   278  title: "Moby Dick"
   279  chapter:
   280    one:
   281      title: "Loomings"
   282    two:
   283      title: "The Carpet-Bag"
   284    three:
   285      title: "The Spouter Inn"
   286  `
   287  	d, err := ReadValues([]byte(doc))
   288  	if err != nil {
   289  		panic(err)
   290  	}
   291  	ch1, err := d.Table("chapter.one")
   292  	if err != nil {
   293  		panic("could not find chapter one")
   294  	}
   295  	fmt.Print(ch1["title"])
   296  	// Output:
   297  	// Loomings
   298  }
   299  
   300  func TestTable(t *testing.T) {
   301  	doc := `
   302  title: "Moby Dick"
   303  chapter:
   304    one:
   305      title: "Loomings"
   306    two:
   307      title: "The Carpet-Bag"
   308    three:
   309      title: "The Spouter Inn"
   310  `
   311  	d, err := ReadValues([]byte(doc))
   312  	if err != nil {
   313  		t.Fatalf("Failed to parse the White Whale: %s", err)
   314  	}
   315  
   316  	if _, err := d.Table("title"); err == nil {
   317  		t.Fatalf("Title is not a table.")
   318  	}
   319  
   320  	if _, err := d.Table("chapter"); err != nil {
   321  		t.Fatalf("Failed to get the chapter table: %s\n%v", err, d)
   322  	}
   323  
   324  	if v, err := d.Table("chapter.one"); err != nil {
   325  		t.Errorf("Failed to get chapter.one: %s", err)
   326  	} else if v["title"] != "Loomings" {
   327  		t.Errorf("Unexpected title: %s", v["title"])
   328  	}
   329  
   330  	if _, err := d.Table("chapter.three"); err != nil {
   331  		t.Errorf("Chapter three is missing: %s\n%v", err, d)
   332  	}
   333  
   334  	if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil {
   335  		t.Errorf("I think you mean 'Epilogue'")
   336  	}
   337  }
   338  
   339  func matchValues(t *testing.T, data map[string]interface{}) {
   340  	if data["poet"] != "Coleridge" {
   341  		t.Errorf("Unexpected poet: %s", data["poet"])
   342  	}
   343  
   344  	if o, err := ttpl("{{len .stanza}}", data); err != nil {
   345  		t.Errorf("len stanza: %s", err)
   346  	} else if o != "6" {
   347  		t.Errorf("Expected 6, got %s", o)
   348  	}
   349  
   350  	if o, err := ttpl("{{.mariner.shot}}", data); err != nil {
   351  		t.Errorf(".mariner.shot: %s", err)
   352  	} else if o != "ALBATROSS" {
   353  		t.Errorf("Expected that mariner shot ALBATROSS")
   354  	}
   355  
   356  	if o, err := ttpl("{{.water.water.where}}", data); err != nil {
   357  		t.Errorf(".water.water.where: %s", err)
   358  	} else if o != "everywhere" {
   359  		t.Errorf("Expected water water everywhere")
   360  	}
   361  }
   362  
   363  func ttpl(tpl string, v map[string]interface{}) (string, error) {
   364  	var b bytes.Buffer
   365  	tt := template.Must(template.New("t").Parse(tpl))
   366  	err := tt.Execute(&b, v)
   367  	return b.String(), err
   368  }
   369  
   370  // ref: http://www.yaml.org/spec/1.2/spec.html#id2803362
   371  var testCoalesceValuesYaml = []byte(`
   372  top: yup
   373  bottom: null
   374  right: Null
   375  left: NULL
   376  front: ~
   377  back: ""
   378  
   379  global:
   380    name: Ishmael
   381    subject: Queequeg
   382    nested:
   383      boat: true
   384  
   385  pequod:
   386    global:
   387      name: Stinky
   388      harpooner: Tashtego
   389      nested:
   390        boat: false
   391        sail: true
   392    ahab:
   393      scope: whale
   394  `)
   395  
   396  func TestCoalesceValues(t *testing.T) {
   397  	c := loadChart(t, "testdata/moby")
   398  
   399  	vals, err := ReadValues(testCoalesceValuesYaml)
   400  	if err != nil {
   401  		t.Fatal(err)
   402  	}
   403  
   404  	v, err := CoalesceValues(c, vals)
   405  	if err != nil {
   406  		t.Fatal(err)
   407  	}
   408  	j, _ := json.MarshalIndent(v, "", "  ")
   409  	t.Logf("Coalesced Values: %s", string(j))
   410  
   411  	tests := []struct {
   412  		tpl    string
   413  		expect string
   414  	}{
   415  		{"{{.top}}", "yup"},
   416  		{"{{.back}}", ""},
   417  		{"{{.name}}", "moby"},
   418  		{"{{.global.name}}", "Ishmael"},
   419  		{"{{.global.subject}}", "Queequeg"},
   420  		{"{{.global.harpooner}}", "<no value>"},
   421  		{"{{.pequod.name}}", "pequod"},
   422  		{"{{.pequod.ahab.name}}", "ahab"},
   423  		{"{{.pequod.ahab.scope}}", "whale"},
   424  		{"{{.pequod.ahab.global.name}}", "Ishmael"},
   425  		{"{{.pequod.ahab.global.subject}}", "Queequeg"},
   426  		{"{{.pequod.ahab.global.harpooner}}", "Tashtego"},
   427  		{"{{.pequod.global.name}}", "Ishmael"},
   428  		{"{{.pequod.global.subject}}", "Queequeg"},
   429  		{"{{.spouter.global.name}}", "Ishmael"},
   430  		{"{{.spouter.global.harpooner}}", "<no value>"},
   431  
   432  		{"{{.global.nested.boat}}", "true"},
   433  		{"{{.pequod.global.nested.boat}}", "true"},
   434  		{"{{.spouter.global.nested.boat}}", "true"},
   435  		{"{{.pequod.global.nested.sail}}", "true"},
   436  		{"{{.spouter.global.nested.sail}}", "<no value>"},
   437  	}
   438  
   439  	for _, tt := range tests {
   440  		if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect {
   441  			t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o)
   442  		}
   443  	}
   444  
   445  	nullKeys := []string{"bottom", "right", "left", "front"}
   446  	for _, nullKey := range nullKeys {
   447  		if _, ok := v[nullKey]; ok {
   448  			t.Errorf("Expected key %q to be removed, still present", nullKey)
   449  		}
   450  	}
   451  }
   452  
   453  func TestCoalesceTables(t *testing.T) {
   454  	dst := map[string]interface{}{
   455  		"name": "Ishmael",
   456  		"address": map[string]interface{}{
   457  			"street": "123 Spouter Inn Ct.",
   458  			"city":   "Nantucket",
   459  		},
   460  		"details": map[string]interface{}{
   461  			"friends": []string{"Tashtego"},
   462  		},
   463  		"boat": "pequod",
   464  	}
   465  	src := map[string]interface{}{
   466  		"occupation": "whaler",
   467  		"address": map[string]interface{}{
   468  			"state":  "MA",
   469  			"street": "234 Spouter Inn Ct.",
   470  		},
   471  		"details": "empty",
   472  		"boat": map[string]interface{}{
   473  			"mast": true,
   474  		},
   475  	}
   476  
   477  	// What we expect is that anything in dst overrides anything in src, but that
   478  	// otherwise the values are coalesced.
   479  	CoalesceTables(dst, src)
   480  
   481  	if dst["name"] != "Ishmael" {
   482  		t.Errorf("Unexpected name: %s", dst["name"])
   483  	}
   484  	if dst["occupation"] != "whaler" {
   485  		t.Errorf("Unexpected occupation: %s", dst["occupation"])
   486  	}
   487  
   488  	addr, ok := dst["address"].(map[string]interface{})
   489  	if !ok {
   490  		t.Fatal("Address went away.")
   491  	}
   492  
   493  	if addr["street"].(string) != "123 Spouter Inn Ct." {
   494  		t.Errorf("Unexpected address: %v", addr["street"])
   495  	}
   496  
   497  	if addr["city"].(string) != "Nantucket" {
   498  		t.Errorf("Unexpected city: %v", addr["city"])
   499  	}
   500  
   501  	if addr["state"].(string) != "MA" {
   502  		t.Errorf("Unexpected state: %v", addr["state"])
   503  	}
   504  
   505  	if det, ok := dst["details"].(map[string]interface{}); !ok {
   506  		t.Fatalf("Details is the wrong type: %v", dst["details"])
   507  	} else if _, ok := det["friends"]; !ok {
   508  		t.Error("Could not find your friends. Maybe you don't have any. :-(")
   509  	}
   510  
   511  	if dst["boat"].(string) != "pequod" {
   512  		t.Errorf("Expected boat string, got %v", dst["boat"])
   513  	}
   514  }
   515  
   516  func TestPathValue(t *testing.T) {
   517  	doc := `
   518  title: "Moby Dick"
   519  chapter:
   520    one:
   521      title: "Loomings"
   522    two:
   523      title: "The Carpet-Bag"
   524    three:
   525      title: "The Spouter Inn"
   526  `
   527  	d, err := ReadValues([]byte(doc))
   528  	if err != nil {
   529  		t.Fatalf("Failed to parse the White Whale: %s", err)
   530  	}
   531  
   532  	if v, err := d.PathValue("chapter.one.title"); err != nil {
   533  		t.Errorf("Got error instead of title: %s\n%v", err, d)
   534  	} else if v != "Loomings" {
   535  		t.Errorf("No error but got wrong value for title: %s\n%v", err, d)
   536  	}
   537  	if _, err := d.PathValue("chapter.one.doesntexist"); err == nil {
   538  		t.Errorf("Non-existent key should return error: %s\n%v", err, d)
   539  	}
   540  	if _, err := d.PathValue("chapter.doesntexist.one"); err == nil {
   541  		t.Errorf("Non-existent key in middle of path should return error: %s\n%v", err, d)
   542  	}
   543  	if _, err := d.PathValue(""); err == nil {
   544  		t.Error("Asking for the value from an empty path should yield an error")
   545  	}
   546  	if v, err := d.PathValue("title"); err == nil {
   547  		if v != "Moby Dick" {
   548  			t.Errorf("Failed to return values for root key title")
   549  		}
   550  	}
   551  }