diff --git a/docs/charts.md b/docs/charts.md index 63fbb0239..b1ed4da1c 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -211,10 +211,10 @@ Considering the template in the previous section, a `values.yaml` file that supplies the necessary values would look like this: ```yaml -imageRegistry = "quay.io/deis" -dockerTag = "latest" -pullPolicy = "alwaysPull" -storage = "s3" +imageRegistry: "quay.io/deis" +dockerTag: "latest" +pullPolicy: "alwaysPull" +storage: "s3" ``` A values file is formatted in YAML. A chart may include a default @@ -230,17 +230,17 @@ values file. For example, consider a `myvals.yaml` file that looks like this: ```yaml -storage = "gcs" +storage: "gcs" ``` When this is merged with the `values.yaml` in the chart, the resulting generated content will be: ```yaml -imageRegistry = "quay.io/deis" -dockerTag = "latest" -pullPolicy = "alwaysPull" -storage = "gcs" +imageRegistry: "quay.io/deis" +dockerTag: "latest" +pullPolicy: "alwaysPull" +storage: "gcs" ``` Note that only the last field was overridden. @@ -260,27 +260,91 @@ dependencies. The values file could supply values to all of these components: ```yaml -title = "My Wordpress Site" # Sent to the Wordpress template +title: "My Wordpress Site" # Sent to the Wordpress template -[mysql] -max_connections = 100 # Sent to MySQL -password = "secret" +mysql: + max_connections: 100 # Sent to MySQL + password: "secret" -[apache] -port = 8080 # Passed to Apache +apache: + port: 8080 # Passed to Apache ``` Charts at a higher level have access to all of the variables defined -beneath. So the wordpress chart can access `mysql.password`. But lower +beneath. So the wordpress chart can access `.mysql.password`. But lower level charts cannot access things in parent charts, so MySQL will not be able to access the `title` property. Nor, for that matter, can it access -`apache.port`. +`.apache.port`. Values are namespaced, but namespaces are pruned. So for the Wordpress -chart, it can access the MySQL password field as `mysql.password`. But +chart, it can access the MySQL password field as `.mysql.password`. But for the MySQL chart, the scope of the values has been reduced and the namespace prefix removed, so it will see the password field simply as -`password`. +`.password`. + +#### Global Values + +As of 2.0.0-Alpha.2, Helm supports sspecial "global" value. Consider +this modified version of the previous exampl: + +```yaml +title: "My Wordpress Site" # Sent to the Wordpress template + +global: + app: MyWordpress + +mysql: + max_connections: 100 # Sent to MySQL + password: "secret" + +apache: + port: 8080 # Passed to Apache +``` + +The above adds a `global` section with the value `app: MyWordpress`. +This value is available to _all_ charts as `.global.app`. + +For example, the `mysql` templates may access `app` as `{{.global.app}}`, and +so can the `apache` chart. Effectively, the values file above is +regenerated like this: + +```yaml +title: "My Wordpress Site" # Sent to the Wordpress template + +global: + app: MyWordpress + +mysql: + global: + app: MyWordpress + max_connections: 100 # Sent to MySQL + password: "secret" + +apache: + global: + app: MyWordpress + port: 8080 # Passed to Apache +``` + +This provides a way of sharing one top-level variable with all +subcharts, which is useful for things like setting `metadata` properties +like labels. + +If a subchart declares a global variable, that global will be passed +_downward_ (to the subchart's subcharts), but not _upward_ to the parent +chart. There is no way for a subchart to influence the values of the +parent chart. + +_Global sections are restricted to only simple key/value pairs. They do +not support nesting._ + +For example, the following is **illegal** and will not work: + +```yaml +global: + foo: # It is illegal to nest an object inside of global. + bar: baz +``` ### References diff --git a/pkg/chartutil/testdata/albatross/values.yaml b/pkg/chartutil/testdata/albatross/values.yaml index 0acfa292f..3121cd7ce 100644 --- a/pkg/chartutil/testdata/albatross/values.yaml +++ b/pkg/chartutil/testdata/albatross/values.yaml @@ -1 +1,4 @@ albatross: "true" + +global: + author: Coleridge diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index 7319d37f7..299b2337e 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -14,6 +14,9 @@ import ( // ErrNoTable indicates that a chart does not have a matching table. var ErrNoTable = errors.New("no table") +// GlobalKey is the name of the Values key that is used for storing global vars. +const GlobalKey = "global" + // Values represents a collection of chart values. type Values map[string]interface{} @@ -98,7 +101,7 @@ func ReadValuesFile(filename string) (Values, error) { // // The overrides map may be used to specifically override configuration values. // -// Values are coalesced together using the fillowing rules: +// Values are coalesced together using the following rules: // // - Values in a higher level chart always override values in a lower-level // dependency chart @@ -110,6 +113,7 @@ func CoalesceValues(chrt *chart.Chart, vals *chart.Config, overrides map[string] // Parse values if not nil. We merge these at the top level because // the passed-in values are in the same namespace as the parent chart. if vals != nil { + log.Printf("Merging overrides into config.") evals, err := ReadValues([]byte(vals.Raw)) if err != nil { return cvals, err @@ -150,10 +154,55 @@ func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) map[string]int return dest } if dv, ok := dest[subchart.Metadata.Name]; ok { - dest[subchart.Metadata.Name] = coalesce(subchart, dv.(map[string]interface{})) + dvmap := dv.(map[string]interface{}) + + // Get globals out of dest and merge them into dvmap. + coalesceGlobals(dvmap, dest) + + // Now coalesce the rest of the values. + dest[subchart.Metadata.Name] = coalesce(subchart, dvmap) + } + } + return dest +} + +// coalesceGlobals copies the globals out of src and merges them into dest. +// +// For convenience, returns dest. +func coalesceGlobals(dest, src map[string]interface{}) map[string]interface{} { + var dg, sg map[string]interface{} + + if destglob, ok := dest[GlobalKey]; !ok { + dg = map[string]interface{}{} + } else if dg, ok = destglob.(map[string]interface{}); !ok { + log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey) + return dg + } + + if srcglob, ok := src[GlobalKey]; !ok { + sg = map[string]interface{}{} + } else if sg, ok = srcglob.(map[string]interface{}); !ok { + log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey) + return dg + } + + // We manually copy (instead of using coalesceTables) because (a) we need + // to prevent loops, and (b) we disallow nesting tables under globals. + // Globals should _just_ be k/v pairs. + for key, val := range sg { + if istable(val) { + log.Printf("warning: nested values are illegal in globals (%s)", key) + continue + } else if dv, ok := dg[key]; ok && istable(dv) { + log.Printf("warning: nested values are illegal in globals (%s)", key) + continue } + // TODO: Do we need to do any additional checking on the value? + dg[key] = val } + dest[GlobalKey] = dg return dest + } // coalesceValues builds up a values map for a particular chart. diff --git a/pkg/chartutil/values_test.go b/pkg/chartutil/values_test.go index f15c4af47..edcb1ea12 100644 --- a/pkg/chartutil/values_test.go +++ b/pkg/chartutil/values_test.go @@ -146,7 +146,14 @@ func ttpl(tpl string, v map[string]interface{}) (string, error) { var testCoalesceValuesYaml = ` top: yup +global: + name: Ishmael + subject: Queequeg + pequod: + global: + name: Stinky + harpooner: Tashtego ahab: scope: whale ` @@ -174,9 +181,19 @@ func TestCoalesceValues(t *testing.T) { {"{{.top}}", "yup"}, {"{{.override}}", "good"}, {"{{.name}}", "moby"}, + {"{{.global.name}}", "Ishmael"}, + {"{{.global.subject}}", "Queequeg"}, + {"{{.global.harpooner}}", ""}, {"{{.pequod.name}}", "pequod"}, {"{{.pequod.ahab.name}}", "ahab"}, {"{{.pequod.ahab.scope}}", "whale"}, + {"{{.pequod.ahab.global.name}}", "Ishmael"}, + {"{{.pequod.ahab.global.subject}}", "Queequeg"}, + {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, + {"{{.pequod.global.name}}", "Ishmael"}, + {"{{.pequod.global.subject}}", "Queequeg"}, + {"{{.spouter.global.name}}", "Ishmael"}, + {"{{.spouter.global.harpooner}}", ""}, } for _, tt := range tests { diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 0753ed09c..85cfdf5ab 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -29,6 +29,7 @@ func TestRender(t *testing.T) { }, Templates: []*chart.Template{ {Name: "test1", Data: []byte("{{.outer | title }} {{.inner | title}}")}, + {Name: "test2", Data: []byte("{{.global.callme | lower }}")}, }, Values: &chart.Config{ Raw: "outer: DEFAULT\ninner: DEFAULT", @@ -41,6 +42,9 @@ func TestRender(t *testing.T) { overrides := map[string]interface{}{ "outer": "spouter", + "global": map[string]interface{}{ + "callme": "Ishmael", + }, } e := New() @@ -58,6 +62,11 @@ func TestRender(t *testing.T) { t.Errorf("Expected %q, got %q", expect, out["test1"]) } + expect = "ishmael" + if out["test2"] != expect { + t.Errorf("Expected %q, got %q", expect, out["test2"]) + } + if _, err := e.Render(c, v); err != nil { t.Errorf("Unexpected error: %s", err) } @@ -194,7 +203,7 @@ func TestRenderNestedValues(t *testing.T) { deepest := &chart.Chart{ Metadata: &chart.Metadata{Name: "deepest"}, Templates: []*chart.Template{ - {Name: deepestpath, Data: []byte(`And this same {{.what}} that smiles to-day`)}, + {Name: deepestpath, Data: []byte(`And this same {{.what}} that smiles {{.global.when}}`)}, }, Values: &chart.Config{Raw: `what: "milkshake"`}, } @@ -228,7 +237,9 @@ herrick: what: rosebuds herrick: deepest: - what: flower`, + what: flower +global: + when: to-day`, } inject, err := chartutil.CoalesceValues(outer, &injValues, map[string]interface{}{})