Commit 79a3db0a authored by Steve Wilkerson's avatar Steve Wilkerson

feat(helm): add support for required properties

Adds the `required` function in enginge.go to support required
properties in values.yml. When a chart developer wishes to specify
intent in requiring a value, they can use this function to declare
an error message that gets returned when chart rendering fails
when a required value is not present in values.yml.

Closes #1580
parent df92425f
......@@ -436,6 +436,9 @@ anything.
**NOTE:** If the `--set` flag is used on `helm install` or `helm upgrade`, those
values are simply converted to YAML on the client side.
**NOTE:** If any required entries in the values file exist, they can be declared
as required in the chart template by using the ['required' function](charts_tips_and_tricks.md)
Any of these values are then accessible inside of templates using the
`.Values` object:
......
......@@ -14,8 +14,8 @@ First, we added almost all of the functions in the
for security reasons: `env` and `expandenv` (which would have given chart authors
access to Tiller's environment).
We also added one special template function: `include`. The `include` function
allows you to bring in another template, and then pass the results to other
We also added two special template functions: `include` and `required`. The `include`
function allows you to bring in another template, and then pass the results to other
template functions.
For example, this template snippet includes a template called `mytpl.tpl`, then
......@@ -25,6 +25,17 @@ lowercases the result, then wraps that in double quotes.
value: {{include "mytpl.tpl" . | lower | quote}}
```
The `required` function allows you to declare a particular
values entry as required for template rendering. If the value is empty, the template
rendering will fail with a user submitted error message.
The following example of the `required` function declares an entry for .Values.who
is required, and will print an error message when that entry is missing:
```yaml
value: {{required "A valid .Values.who entry required!" .Values.who }}
```
## Quote Strings, Don't Quote Integers
When you are working with string data, you are always safer quoting the
......@@ -61,6 +72,30 @@ Because YAML ascribes significance to indentation levels and whitespace,
this is one great way to include snippets of code, but handle
indentation in a relevant context.
## Using the 'required' function
Go provides a way for setting template options to control behavior
when a map is indexed with a key that's not present in the map. This
is typically set with template.Options("missingkey=option"), where option
can be default, zero, or error. While setting this option to error will
stop execution with an arror, this would apply to every missing key in the
map. There may be situations where a chart developer wants to enforce this
behavior for select values in the values.yml file.
The `required` function gives developers the ability to declare a value entry
as required for template rendering. If the entry is empty in values.yml, the
template will not render and will return an error message supplied by the
developer.
For example:
```
{{ required "A valid foo is required!" .Values.foo }}
```
The above will render the template when .Values.foo is defined, but will fail
to render and exit when .Values.foo is undefined.
## Automatically Roll Deployments When ConfigMaps or Secrets change
Often times configmaps or secrets are injected as configuration
......
......@@ -63,6 +63,8 @@ func New() *Engine {
//
// - "include": This is late-bound in Engine.Render(). The version
// included in the FuncMap is a placeholder.
// - "required": This is late-bound in Engine.Render(). The version
// included in thhe FuncMap is a placeholder.
func FuncMap() template.FuncMap {
f := sprig.TxtFuncMap()
delete(f, "env")
......@@ -79,7 +81,8 @@ func FuncMap() template.FuncMap {
// This is a placeholder for the "include" function, which is
// late-bound to a template. By declaring it here, we preserve the
// integrity of the linter.
"include": func(string, interface{}) string { return "not implemented" },
"include": func(string, interface{}) string { return "not implemented" },
"required": func(string, interface{}) interface{} { return "not implemented" },
}
for k, v := range extra {
......@@ -143,6 +146,18 @@ func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap {
return buf.String()
}
// Add the 'required' function here
funcMap["required"] = func(warn string, val interface{}) (interface{}, error) {
if val == nil {
return val, fmt.Errorf(warn)
} else if _, ok := val.(string); ok {
if val == "" {
return val, fmt.Errorf(warn)
}
}
return val, nil
}
return funcMap
}
......
......@@ -49,7 +49,7 @@ func TestFuncMap(t *testing.T) {
}
// Test for Engine-specific template functions.
expect := []string{"include", "toYaml", "fromYaml", "toToml", "toJson", "fromJson"}
expect := []string{"include", "required", "toYaml", "fromYaml", "toToml", "toJson", "fromJson"}
for _, f := range expect {
if _, ok := fns[f]; !ok {
t.Errorf("Expected add-on function %q", f)
......@@ -409,4 +409,40 @@ func TestAlterFuncMap(t *testing.T) {
if got := out["conrad/templates/quote"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expect, got, out)
}
reqChart := &chart.Chart{
Metadata: &chart.Metadata{Name: "conan"},
Templates: []*chart.Template{
{Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)},
{Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)},
},
Values: &chart.Config{Raw: ``},
Dependencies: []*chart.Chart{},
}
reqValues := chartutil.Values{
"Values": chartutil.Values{
"who": "us",
"bases": 2,
},
"Chart": reqChart.Metadata,
"Release": chartutil.Values{
"Name": "That 90s meme",
},
}
outReq, err := New().Render(reqChart, reqValues)
if err != nil {
t.Fatal(err)
}
expectStr := "All your base are belong to us"
if gotStr := outReq["conan/templates/quote"]; gotStr != expectStr {
t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, outReq)
}
expectNum := "All 2 of them!"
if gotNum := outReq["conan/templates/bases"]; gotNum != expectNum {
t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, outReq)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment