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:
```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

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

@ -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.

@ -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}}", "<no value>"},
{"{{.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}}", "<no value>"},
}
for _, tt := range tests {

@ -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{}{})

Loading…
Cancel
Save