Merge pull request #824 from technosophos/feat/global-vars

feat(chartutil): support global variables
pull/837/head
Matt Butcher 8 years ago committed by GitHub
commit 2b2a5788f3

@ -211,10 +211,10 @@ Considering the template in the previous section, a `values.yaml` file
that supplies the necessary values would look like this: that supplies the necessary values would look like this:
```yaml ```yaml
imageRegistry = "quay.io/deis" imageRegistry: "quay.io/deis"
dockerTag = "latest" dockerTag: "latest"
pullPolicy = "alwaysPull" pullPolicy: "alwaysPull"
storage = "s3" storage: "s3"
``` ```
A values file is formatted in YAML. A chart may include a default 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: this:
```yaml ```yaml
storage = "gcs" storage: "gcs"
``` ```
When this is merged with the `values.yaml` in the chart, the resulting When this is merged with the `values.yaml` in the chart, the resulting
generated content will be: generated content will be:
```yaml ```yaml
imageRegistry = "quay.io/deis" imageRegistry: "quay.io/deis"
dockerTag = "latest" dockerTag: "latest"
pullPolicy = "alwaysPull" pullPolicy: "alwaysPull"
storage = "gcs" storage: "gcs"
``` ```
Note that only the last field was overridden. Note that only the last field was overridden.
@ -260,27 +260,91 @@ dependencies. The values file could supply values to all of these
components: components:
```yaml ```yaml
title = "My Wordpress Site" # Sent to the Wordpress template title: "My Wordpress Site" # Sent to the Wordpress template
[mysql] mysql:
max_connections = 100 # Sent to MySQL max_connections: 100 # Sent to MySQL
password = "secret" password: "secret"
[apache] apache:
port = 8080 # Passed to Apache port: 8080 # Passed to Apache
``` ```
Charts at a higher level have access to all of the variables defined 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 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 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 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 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 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 ### References

@ -1 +1,4 @@
albatross: "true" albatross: "true"
global:
author: Coleridge

@ -14,6 +14,9 @@ import (
// ErrNoTable indicates that a chart does not have a matching table. // ErrNoTable indicates that a chart does not have a matching table.
var ErrNoTable = errors.New("no 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. // Values represents a collection of chart values.
type Values map[string]interface{} 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. // 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 // - Values in a higher level chart always override values in a lower-level
// dependency chart // 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 // 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. // the passed-in values are in the same namespace as the parent chart.
if vals != nil { if vals != nil {
log.Printf("Merging overrides into config.")
evals, err := ReadValues([]byte(vals.Raw)) evals, err := ReadValues([]byte(vals.Raw))
if err != nil { if err != nil {
return cvals, err return cvals, err
@ -150,10 +154,55 @@ func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) map[string]int
return dest return dest
} }
if dv, ok := dest[subchart.Metadata.Name]; ok { 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 return dest
} }
// coalesceValues builds up a values map for a particular chart. // coalesceValues builds up a values map for a particular chart.

@ -146,7 +146,14 @@ func ttpl(tpl string, v map[string]interface{}) (string, error) {
var testCoalesceValuesYaml = ` var testCoalesceValuesYaml = `
top: yup top: yup
global:
name: Ishmael
subject: Queequeg
pequod: pequod:
global:
name: Stinky
harpooner: Tashtego
ahab: ahab:
scope: whale scope: whale
` `
@ -174,9 +181,19 @@ func TestCoalesceValues(t *testing.T) {
{"{{.top}}", "yup"}, {"{{.top}}", "yup"},
{"{{.override}}", "good"}, {"{{.override}}", "good"},
{"{{.name}}", "moby"}, {"{{.name}}", "moby"},
{"{{.global.name}}", "Ishmael"},
{"{{.global.subject}}", "Queequeg"},
{"{{.global.harpooner}}", "<no value>"},
{"{{.pequod.name}}", "pequod"}, {"{{.pequod.name}}", "pequod"},
{"{{.pequod.ahab.name}}", "ahab"}, {"{{.pequod.ahab.name}}", "ahab"},
{"{{.pequod.ahab.scope}}", "whale"}, {"{{.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}}", "<no value>"},
} }
for _, tt := range tests { for _, tt := range tests {

@ -29,6 +29,7 @@ func TestRender(t *testing.T) {
}, },
Templates: []*chart.Template{ Templates: []*chart.Template{
{Name: "test1", Data: []byte("{{.outer | title }} {{.inner | title}}")}, {Name: "test1", Data: []byte("{{.outer | title }} {{.inner | title}}")},
{Name: "test2", Data: []byte("{{.global.callme | lower }}")},
}, },
Values: &chart.Config{ Values: &chart.Config{
Raw: "outer: DEFAULT\ninner: DEFAULT", Raw: "outer: DEFAULT\ninner: DEFAULT",
@ -41,6 +42,9 @@ func TestRender(t *testing.T) {
overrides := map[string]interface{}{ overrides := map[string]interface{}{
"outer": "spouter", "outer": "spouter",
"global": map[string]interface{}{
"callme": "Ishmael",
},
} }
e := New() e := New()
@ -58,6 +62,11 @@ func TestRender(t *testing.T) {
t.Errorf("Expected %q, got %q", expect, out["test1"]) 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 { if _, err := e.Render(c, v); err != nil {
t.Errorf("Unexpected error: %s", err) t.Errorf("Unexpected error: %s", err)
} }
@ -194,7 +203,7 @@ func TestRenderNestedValues(t *testing.T) {
deepest := &chart.Chart{ deepest := &chart.Chart{
Metadata: &chart.Metadata{Name: "deepest"}, Metadata: &chart.Metadata{Name: "deepest"},
Templates: []*chart.Template{ 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"`}, Values: &chart.Config{Raw: `what: "milkshake"`},
} }
@ -228,7 +237,9 @@ herrick:
what: rosebuds what: rosebuds
herrick: herrick:
deepest: deepest:
what: flower`, what: flower
global:
when: to-day`,
} }
inject, err := chartutil.CoalesceValues(outer, &injValues, map[string]interface{}{}) inject, err := chartutil.CoalesceValues(outer, &injValues, map[string]interface{}{})

Loading…
Cancel
Save