From bba555aa3f6dbb58470af2ddec0a8119d54b05b3 Mon Sep 17 00:00:00 2001 From: Morgan Parry Date: Tue, 31 Oct 2017 16:41:32 +0000 Subject: [PATCH] (feat) Add support for expanding templates in values This is enabled via a new expandValues flag in Chart.yaml --- _proto/hapi/chart/metadata.proto | 5 +- _proto/hapi/services/tiller.proto | 2 + cmd/helm/get.go | 2 +- cmd/helm/install.go | 7 +- cmd/helm/printer.go | 24 ++- cmd/helm/template.go | 12 +- cmd/helm/upgrade.go | 2 +- docs/charts.md | 90 ++++++++++ docs/charts_tips_and_tricks.md | 11 +- pkg/engine/engine.go | 156 ++++++++++------- pkg/engine/template_values.go | 176 +++++++++++++++++++ pkg/engine/template_values_test.go | 189 +++++++++++++++++++++ pkg/proto/hapi/chart/metadata.pb.go | 68 ++++---- pkg/proto/hapi/services/tiller.pb.go | 177 ++++++++++--------- pkg/tiller/environment/environment.go | 2 + pkg/tiller/environment/environment_test.go | 5 + pkg/tiller/release_install.go | 54 +++--- pkg/tiller/release_server.go | 30 +++- pkg/tiller/release_update.go | 59 +++---- 19 files changed, 814 insertions(+), 257 deletions(-) create mode 100644 pkg/engine/template_values.go create mode 100644 pkg/engine/template_values_test.go diff --git a/_proto/hapi/chart/metadata.proto b/_proto/hapi/chart/metadata.proto index 49d6a217a..99bfd85ef 100644 --- a/_proto/hapi/chart/metadata.proto +++ b/_proto/hapi/chart/metadata.proto @@ -89,5 +89,8 @@ message Metadata { map annotations = 16; // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. - string kubeVersion = 17; + string kubeVersion = 17; + + // Whether or not any templates found in values should be recursively expanded + bool expandValues = 18; } diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index 5897676ab..14a3ac158 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -214,6 +214,7 @@ message UpdateReleaseRequest { // UpdateReleaseResponse is the response to an update request. message UpdateReleaseResponse { hapi.release.Release release = 1; + hapi.chart.Config final_values = 2; } message RollbackReleaseRequest { @@ -276,6 +277,7 @@ message InstallReleaseRequest { // InstallReleaseResponse is the response from a release installation. message InstallReleaseResponse { hapi.release.Release release = 1; + hapi.chart.Config final_values = 2; } // UninstallReleaseRequest represents a request to uninstall a named release. diff --git a/cmd/helm/get.go b/cmd/helm/get.go index a2eb1d137..5d3102f6a 100644 --- a/cmd/helm/get.go +++ b/cmd/helm/get.go @@ -85,5 +85,5 @@ func (g *getCmd) run() error { if err != nil { return prettyError(err) } - return printRelease(g.out, res.Release) + return printRelease(g.out, res.Release, nil) } diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 1d8ec85f1..c30ded0be 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -283,7 +283,7 @@ func (i *installCmd) run() error { if rel == nil { return nil } - i.printRelease(rel) + i.printRelease(rel, res.GetFinalValues()) // If this is a dry run, we can't display status. if i.dryRun { @@ -377,14 +377,14 @@ func vals(valueFiles valueFiles, values []string, stringValues []string) ([]byte } // printRelease prints info about a release if the Debug is true. -func (i *installCmd) printRelease(rel *release.Release) { +func (i *installCmd) printRelease(rel *release.Release, finalVals *chart.Config) { if rel == nil { return } // TODO: Switch to text/template like everything else. fmt.Fprintf(i.out, "NAME: %s\n", rel.Name) if settings.Debug { - printRelease(i.out, rel) + printRelease(i.out, rel, finalVals) } } @@ -516,7 +516,6 @@ func readFile(filePath string) ([]byte, error) { // FIXME: maybe someone handle other protocols like ftp. getterConstructor, err := p.ByScheme(u.Scheme) - if err != nil { return ioutil.ReadFile(filePath) } diff --git a/cmd/helm/printer.go b/cmd/helm/printer.go index ebb24bf7d..da75a97c7 100644 --- a/cmd/helm/printer.go +++ b/cmd/helm/printer.go @@ -23,6 +23,7 @@ import ( "time" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/timeconv" ) @@ -44,23 +45,28 @@ MANIFEST: {{.Release.Manifest}} ` -func printRelease(out io.Writer, rel *release.Release) error { +func printRelease(out io.Writer, rel *release.Release, finalVals *chart.Config) error { if rel == nil { return nil } - cfg, err := chartutil.CoalesceValues(rel.Chart, rel.Config) - if err != nil { - return err - } - cfgStr, err := cfg.YAML() - if err != nil { - return err + var valsStr string + if finalVals == nil { + vals, err := chartutil.CoalesceValues(rel.Chart, rel.Config) + if err != nil { + return err + } + valsStr, err = vals.YAML() + if err != nil { + return err + } + } else { + valsStr = finalVals.Raw } data := map[string]interface{}{ "Release": rel, - "ComputedValues": cfgStr, + "ComputedValues": valsStr, "ReleaseDate": timeconv.Format(rel.Info.LastDeployed, time.ANSIC), } return tpl(printReleaseTemplate, data, out) diff --git a/cmd/helm/template.go b/cmd/helm/template.go index c04bc2dc8..388703e63 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -216,6 +216,12 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error { return err } + if c.Metadata.ExpandValues { + if vals, err = renderer.ExpandValues(vals); err != nil { + return err + } + } + out, err := renderer.Render(c, vals) listManifests := []tiller.Manifest{} if err != nil { @@ -254,7 +260,11 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error { Namespace: t.namespace, Info: &release.Info{LastDeployed: timeconv.Timestamp(time.Now())}, } - printRelease(os.Stdout, rel) + valsYaml, err := vals.YAML() + if err != nil { + return err + } + printRelease(os.Stdout, rel, &chart.Config{Raw: valsYaml}) } for _, m := range tiller.SortByKind(listManifests) { diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 125796762..c71ef57a8 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -230,7 +230,7 @@ func (u *upgradeCmd) run() error { } if settings.Debug { - printRelease(u.out, resp.Release) + printRelease(u.out, resp.Release, resp.GetFinalValues()) } fmt.Fprintf(u.out, "Release %q has been upgraded. Happy Helming!\n", u.release) diff --git a/docs/charts.md b/docs/charts.md index 8722e6862..955b31aae 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -57,6 +57,7 @@ maintainers: # (optional) email: The maintainer's email (optional for each maintainer) url: A URL for the maintainer (optional for each maintainer) engine: gotpl # The name of the template engine (optional, defaults to gotpl) +expandValues: Whether or not any templates found in values should be recursively expanded (optional, boolean) icon: A URL to an SVG or PNG image to be used as an icon (optional). appVersion: The version of the app that this contains (optional). This needn't be SemVer. deprecated: Whether this chart is deprecated (optional, boolean) @@ -776,6 +777,95 @@ parent chart. Also, global variables of parent charts take precedence over the global variables from subcharts. +### Templated Values + +If the `expandValues` flag in `Chart.yaml` is set to `true` then values can be composed +using other values, via a custom `tval` function. Since all files must be comprised of +valid YAML, all such values are composed within YAML strings. For example: + +```yaml +a: expanded +b: 'this will be {{ tval "a" }}' # => 'this will be expanded' +``` + +Expansion is performed recursively and multiple templates can be referenced: + +```yaml +a: expanded +b: + 1: this + 2: will + 3: be + 4: '{{ tval "a" }}' +c: '{{ tval "b.1" }} {{ tval "b.2" }} {{ tval "b.3" }} {{ tval "b.4" }}' # => 'this will be expanded' +``` + +You can also use all of the usual templating functions: + +```yaml +connection: '1.2.3.4:56' +port: '{{ index (splitList ":" (tval "connection")) 1 }}' # => 56 +``` + +As you can see in the above examples, the `.Values` prefix that you would need to write +elsewhere is implicit here. You can still explicitly reference values that way if you +wish but then there will be no recursive expansion. For example, for a release called +`zealous-zebu`: + +```yaml +a: 'My release is called {{ .Release.Name }}' +b: '{{ .Values.a }}' # Will not recursively expand; you will get the literal string 'My release is called {{ .Release.Name }}' +c: '{{ tval "a" }}' # Will recursively expand; you will get 'My release is called zealous-zebu' +``` + +One immediate application of this feature is to create extra values that are derived +from existing ones. For example, suppose that you wish to specify some database names +automatically based upon the release name: + +```yaml +databases: + cities: 'db-{{ .Release.Name }}-cities' + cities-dev: '{{ tval "databases.cities" }}-dev' +``` + +Another use case is for passing values down to child charts, without having to duplicate +the configuration in question. Suppose that in `requirements.yaml` you have configured +the following: + +```yaml +dependencies: + - name: child + ... +``` + +You can then pass through values from the parent chart by writing something like this in +the parent's `values.yaml`: + +```yaml +a: abc +b: def + +child: + c: '{{ tval "a" }}' + d: '{{ tval "b" }}ghi' + e: jkl +``` + +That is, you can pass values through unchanged (`c`), pass them through modified (`d`), +and of course inject additional values (`e`). As usual, the child chart will see these +as `.Values.c` etc. + +When this feature is enabled, if you wish to embed the literal string `{{` in a value +then you will need to write something like this: + +```yaml +a: '{{`{{ actual braces }}`}}' +``` + +Note that the `tval` function is not available in any other context. Also, +values used for [requirement conditions](#tags-and-condition-fields-in-requirementsyaml) +cannot be templated. + ### References When it comes to writing templates and values files, there are several diff --git a/docs/charts_tips_and_tricks.md b/docs/charts_tips_and_tricks.md index 484d8b936..51d793697 100644 --- a/docs/charts_tips_and_tricks.md +++ b/docs/charts_tips_and_tricks.md @@ -14,9 +14,11 @@ 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 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. +We also added four special template functions: `include`, `required`, `tpl`, and +`tval`. + +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`, then lowercases the result, then wraps that in double quotes. @@ -36,6 +38,9 @@ is required, and will print an error message when that entry is missing: value: {{required "A valid .Values.who entry required!" .Values.who }} ``` +The `tval` function enables templated values to be used. There are further +details [here](https://github.com/kubernetes/helm/blob/master/docs/charts.md#templated-values). + ## Quote Strings, Don't Quote Integers When you are working with string data, you are always safer quoting the diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 7a940fc84..4e8d6f570 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -166,49 +166,53 @@ func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap { // Add the 'tpl' function here funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) { - basePath, err := vals.PathValue("Template.BasePath") - if err != nil { - return "", fmt.Errorf("Cannot retrieve Template.Basepath from values inside tpl function: %s (%s)", tpl, err.Error()) - } - - r := renderable{ - tpl: tpl, - vals: vals, - basePath: basePath.(string), + dummyName := "___tpl_template" + spec := renderSpec{ + tpls: map[string]renderable{dummyName: {tpl: tpl, vals: vals}}, } - - templates := map[string]renderable{} - templateName, err := vals.PathValue("Template.Name") - if err != nil { - return "", fmt.Errorf("Cannot retrieve Template.Name from values inside tpl function: %s (%s)", tpl, err.Error()) - } - - templates[templateName.(string)] = r - - result, err := e.render(templates) + result, err := e.renderFromSpec(&spec) if err != nil { return "", fmt.Errorf("Error during tpl function execution for %q: %s", tpl, err.Error()) } - return result[templateName.(string)], nil + return result[dummyName], nil } return funcMap } -// render takes a map of templates/values and renders them. -func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string, err error) { +func (e *Engine) render(tpls map[string]renderable) (map[string]string, error) { + spec := renderSpec{ + tpls: tpls, + // We want to parse the templates in a predictable order. The order favors + // higher-level (in file system) templates over deeply nested templates. + order: sortTemplates(tpls), + // Don't render partials. We don't care about the direct output of partials. + // They are only included from other templates. + filter: func(name string) bool { return !strings.HasPrefix(path.Base(name), "_") }, + addTplMeta: true, + } + return e.renderFromSpec(&spec) +} + +type renderSpec struct { + tpls map[string]renderable + order []string + filter func(string) bool + addTplMeta bool +} + +type preparedTemplate struct { + tpl *template.Template + funcMap template.FuncMap +} + +func (e *Engine) renderPrepare() (*preparedTemplate, error) { // Basically, what we do here is start with an empty parent template and then // build up a list of templates -- one for each file. Once all of the templates // have been parsed, we loop through again and execute every template. - // // The idea with this process is to make it possible for more complex templates // to share common blocks, but to make the entire thing feel like a file-based // template engine. - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("rendering template failed: %v", r) - } - }() t := template.New("gotpl") if e.Strict { t.Option("missingkey=error") @@ -220,52 +224,78 @@ func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string, funcMap := e.alterFuncMap(t) - // We want to parse the templates in a predictable order. The order favors - // higher-level (in file system) templates over deeply nested templates. - keys := sortTemplates(tpls) + // Add the engine's currentTemplates to the template context + // so they can be referenced in the tpl function + for name, r := range e.CurrentTemplates { + if t.Lookup(name) == nil { + t = t.New(name).Funcs(funcMap) + if _, err := t.Parse(r.tpl); err != nil { + return nil, fmt.Errorf("parse error in %q: %s", name, err) + } + } + } - files := []string{} + return &preparedTemplate{tpl: t, funcMap: funcMap}, nil +} - for _, fname := range keys { - r := tpls[fname] - t = t.New(fname).Funcs(funcMap) - if _, err := t.Parse(r.tpl); err != nil { - return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) +func (e *Engine) renderSingle(t *template.Template, name string, vals chartutil.Values, buf *bytes.Buffer) (result string, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("rendering template failed: %v", r) } - files = append(files, fname) + }() + + buf.Reset() + if err := t.ExecuteTemplate(buf, name, vals); err != nil { + return "", fmt.Errorf("render error in %q: %s", name, err) } + // Work around the issue where Go will emit "" even if Options(missing=zero) + // is set. Since missing=error will never get here, we do not need to handle + // the Strict case. + result = strings.Replace(buf.String(), "", "", -1) + return result, nil +} - // Adding the engine's currentTemplates to the template context - // so they can be referenced in the tpl function - for fname, r := range e.CurrentTemplates { - if t.Lookup(fname) == nil { - t = t.New(fname).Funcs(funcMap) - if _, err := t.Parse(r.tpl); err != nil { - return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) - } +func (e *Engine) renderFromSpec(spec *renderSpec) (rendered map[string]string, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("rendering template failed: %v", r) } + }() + + prep, err := e.renderPrepare() + if err != nil { + return nil, err } - rendered = make(map[string]string, len(files)) - var buf bytes.Buffer - for _, file := range files { - // Don't render partials. We don't care out the direct output of partials. - // They are only included from other templates. - if strings.HasPrefix(path.Base(file), "_") { - continue + if spec.order == nil { + spec.order = make([]string, 0, len(spec.tpls)) + for key := range spec.tpls { + spec.order = append(spec.order, key) } - // At render time, add information about the template that is being rendered. - vals := tpls[file].vals - vals["Template"] = map[string]interface{}{"Name": file, "BasePath": tpls[file].basePath} - if err := t.ExecuteTemplate(&buf, file, vals); err != nil { - return map[string]string{}, fmt.Errorf("render error in %q: %s", file, err) + sort.Strings(spec.order) + } + + t := prep.tpl + for _, name := range spec.order { + t = t.New(name).Funcs(prep.funcMap) + if _, err := t.Parse(spec.tpls[name].tpl); err != nil { + return nil, fmt.Errorf("parse error in %q: %s", name, err) } + } - // Work around the issue where Go will emit "" even if Options(missing=zero) - // is set. Since missing=error will never get here, we do not need to handle - // the Strict case. - rendered[file] = strings.Replace(buf.String(), "", "", -1) - buf.Reset() + rendered = make(map[string]string, len(spec.order)) + var buf bytes.Buffer + for _, name := range spec.order { + if spec.filter == nil || spec.filter(name) { + if spec.addTplMeta { + // At render time, add information about the template that is being rendered. + spec.tpls[name].vals["Template"] = map[string]interface{}{"Name": name, "BasePath": spec.tpls[name].basePath} + } + if rendered[name], err = e.renderSingle(t, name, spec.tpls[name].vals, &buf); err != nil { + return nil, err + } + } } return rendered, nil diff --git a/pkg/engine/template_values.go b/pkg/engine/template_values.go new file mode 100644 index 000000000..467b9c573 --- /dev/null +++ b/pkg/engine/template_values.go @@ -0,0 +1,176 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "bytes" + "fmt" + "strconv" + "strings" + + "k8s.io/helm/pkg/chartutil" +) + +type expansionState struct { + origTop chartutil.Values + origVals chartutil.Values + engine *Engine + prepTpl *preparedTemplate + locks map[string]bool + valCache map[string]interface{} + rendBufs []*bytes.Buffer + rendDepth int +} + +// ExpandValues will expand all templates found in .Values. The usual .Values structure will be available +// via a new `tval` function instead; eg. {{ .Values.foo.bar }} can be accessed via {{ tval foo.bar }}. +// This permits recursive expansion; eg. a.b='{{a.c}}', a.c='{{a.d}}', a.d='d '=> a.b='d'. If .Values is +// used directly, no recursive expansion will occur. Also, all string values are expanded, so if literal +// {{ }} characters are required, they must be escaped; eg. {{ "{{ }}" }}. Values of any other type (eg. +// numeric) will remain unchanged. +func (engine *Engine) ExpandValues(top chartutil.Values) (chartutil.Values, error) { + vals, err := top.Table("Values") + if err != nil { + return top, nil + } + + prepTpl, err := engine.renderPrepare() + if err != nil { + return nil, fmt.Errorf("Error during templated value expansion: %s", err.Error()) + } + + state := expansionState{ + origTop: top, origVals: vals, engine: engine, prepTpl: prepTpl, locks: map[string]bool{}, + valCache: map[string]interface{}{}, rendBufs: []*bytes.Buffer{}, rendDepth: 0, + } + prepTpl.funcMap["tval"] = state.tvalImpl + + var expVals chartutil.Values + expVals, err = state.expandMapVal(vals, "") + if err != nil { + return nil, fmt.Errorf("Error during templated value expansion: %s", err.Error()) + } + + newTop := make(chartutil.Values, len(top)) + for k, v := range top { + newTop[k] = v + } + newTop["Values"] = expVals + return newTop, nil +} + +func (state *expansionState) tvalImpl(path string) (interface{}, error) { + // Lock state is only checked here. This is the only place in which we can jump to some + // other subtree of .Values, and if it's unlocked then it's fine to recurse into it. The + // only time we need to check for another lock is on any further jump (ie. tval call). + if _, locked := state.locks[path]; locked { + return "", fmt.Errorf("Cyclic reference to %q", path) + } + val, err := state.origVals.PathValue(path) + if err != nil { + if val, err = state.origVals.Table(path); err != nil { // PathValue() will not return maps + return "", fmt.Errorf("Value %q does not exist", path) + } + } + return state.expandVal(val, path) +} + +func joinPath(parent string, child string) string { + if len(parent) == 0 { + return child + } + return parent + "." + child +} + +func (state *expansionState) expandMapVal(vals map[string]interface{}, path string) (map[string]interface{}, error) { + state.locks[path] = true + defer delete(state.locks, path) + + newVals := make(map[string]interface{}, len(vals)) + for key, val := range vals { + newVal, err := state.expandVal(val, joinPath(path, key)) + if err != nil { + return nil, err + } + newVals[key] = newVal + } + return newVals, nil +} + +func (state *expansionState) expandArrayVal(vals []interface{}, path string) ([]interface{}, error) { + state.locks[path] = true + defer delete(state.locks, path) + + newVals := make([]interface{}, len(vals)) + for i, val := range vals { + newVal, err := state.expandVal(val, joinPath(path, strconv.Itoa(i))) + if err != nil { + return nil, err + } + newVals[i] = newVal + } + return newVals, nil +} + +func (state *expansionState) expandVal(val interface{}, path string) (interface{}, error) { + state.locks[path] = true + defer delete(state.locks, path) + + switch typedVal := val.(type) { + case chartutil.Values: + return state.expandMapVal(typedVal, path) + case map[string]interface{}: + return state.expandMapVal(typedVal, path) + case []interface{}: + return state.expandArrayVal(typedVal, path) + case string: + return state.renderVal(path, typedVal) + default: + return val, nil + } +} + +func (state *expansionState) renderVal(path string, val string) (interface{}, error) { + if !strings.Contains(val, "{{") { + return val, nil + } + + // We only cache string values that we have actually resolved. It's probably not worthwhile + // for anything else as either the value is trivial to retrieve (if there are no template + // expressions) or it is some structure (eg. a list) that will not usually be used directly + // (and is straightforward to reconstruct from cached values). + if precalc, found := state.valCache[path]; found { + return precalc, nil + } + + tplName := joinPath("__expand", path) + tpl := state.prepTpl.tpl.New(tplName).Funcs(state.prepTpl.funcMap) + if _, err := tpl.Parse(val); err != nil { + return nil, fmt.Errorf("Parse error in value %q: %s", path, err) + } + + if state.rendDepth == len(state.rendBufs) { + state.rendBufs = append(state.rendBufs, new(bytes.Buffer)) + } + state.rendDepth++ + rendered, err := state.engine.renderSingle(tpl, tplName, state.origTop, state.rendBufs[state.rendDepth-1]) + state.rendDepth-- + if err != nil { + return nil, fmt.Errorf("Error expanding value %q: %s", path, err.Error()) + } + state.valCache[path] = rendered + return rendered, nil +} diff --git a/pkg/engine/template_values_test.go b/pkg/engine/template_values_test.go new file mode 100644 index 000000000..41373c9ae --- /dev/null +++ b/pkg/engine/template_values_test.go @@ -0,0 +1,189 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "fmt" + "regexp" + "testing" + + "k8s.io/helm/pkg/chartutil" +) + +func expandValuesHelper(input string) (chartutil.Values, error) { + var vals chartutil.Values + vals = chartutil.FromYaml(input) + if err, found := vals["Error"]; found { + return nil, fmt.Errorf("Unexpected YAML parse failure: %s", err.(string)) + } + expanded, err := New().ExpandValues(chartutil.Values{"Values": vals}) + if err != nil { + return nil, fmt.Errorf("Expansion failed: %s", err.Error()) + } + return expanded, nil +} + +func checkFullExpansionHelper(input string, expected string) error { + expanded, err := expandValuesHelper(input) + if err != nil { + return err + } + output := chartutil.ToYaml(expanded["Values"]) + if "\n"+output != expected { + return fmt.Errorf("Unexpected result from ExpandValues().\nGot: %s\nExpected: %s", output, expected) + } + return nil +} + +func checkSingleValue(input string, path string, expected string) error { + expanded, err := expandValuesHelper(input) + if err != nil { + return err + } + val, err := expanded.PathValue("Values." + path) + if err != nil { + return err + } + if val != expected { + return fmt.Errorf("Unexpected expansion result for %q.\nGot: %s\nExpected: %s", path, val, expected) + } + return nil +} + +func TestTemplateValueExpansion(t *testing.T) { + input := ` +a: + aa: '{{ tval "b.ba.baa" }}2' # 12 + ab: '{{ tval "a.aa" }}3' # 123 +b: + ba: + baa: 1 + bb: '0{{ tval "a.ab" -}} 4 {{- tval "b.bc" }}' # 012345 + bc: 5 + bd: '{{ index (tval "d") 1 }}' # L1 +c: + ca: '{{ substr 2 5 (tval "b.bb") }}' # 234 + cb: '{{ .Values.b.ba.baa }}' # Can still access things this way (and get 1 here) + cc: '{{ .Values.a.aa }}' # ... but there will be no recursion, so this will just be '{{ tval.b.ba }}2' +d: +- L0 +- 'L{{ tval "b.ba.baa" }}' # L1 +e: + bool: true # Will just be left alone + float: 1.234 # " +` + + expected := ` +a: + aa: "12" + ab: "123" +b: + ba: + baa: 1 + bb: "012345" + bc: 5 + bd: L1 +c: + ca: "234" + cb: "1" + cc: '{{ tval "b.ba.baa" }}2' +d: +- L0 +- L1 +e: + bool: true + float: 1.234 +` + if err := checkFullExpansionHelper(input, expected); err != nil { + t.Error(err) + } + + input = ` +a: +- aa: + aaa: '{{ (tval "b").ba.baa }}' + aab: '{{ tval "b.ba.bac" }}' +b: + ba: + baa: 1 + bab: + - unused + - left + - right + bac: 2 +c: + ca: >- + {{ + print + (index (tval "b.ba").bab (atoi (index (tval "a") 0).aa.aaa)) + (index (tval "d") (atoi (index (tval "a") 0).aa.aaa)).db.dba.dbaa + }} +d: +- da: + - unused +- db: + dba: + dbaa: '{{ index (tval "b.ba.bab") (atoi (index (tval "a") 0).aa.aab) }}' +` + if err := checkSingleValue(input, "c.ca", "leftright"); err != nil { + t.Error(err) + } + + input = ` +a: + aa: cc +b: + bb: '{{ (index (tval "c") (tval "a.aa")).x.xx }}' +c: + cc: + x: + xx: '{{ tval "c.cd.x.xx" }}' + cd: + x: + xx: good +` + if err := checkSingleValue(input, "b.bb", "good"); err != nil { + t.Error(err) + } +} + +func TestTemplateValueExpansionErrors(t *testing.T) { + checkExpError := func(input string, errRegex string) { + if _, err := expandValuesHelper(input); err == nil { + t.Errorf("Expected error matching %q but expansion succeeded", errRegex) + } else if !regexp.MustCompile("(?i)" + errRegex).MatchString(err.Error()) { + t.Errorf("Expected error matching %q but got %q", errRegex, err.Error()) + } + } + + inputYaml := "a: { b: '{{ tval \"a.b\" }}' }" + checkExpError(inputYaml, `cyclic reference to "a.b"`) + + inputYaml = "a: { b: '{{ tval \"c.d\" }}' }\nc: { d: '{{ tval \"a.b\" }}' }" + checkExpError(inputYaml, `cyclic reference`) + inputYaml = "a: { b: '{{ tval \"c.d\" }}' }\nc: { d: '{{ tval \"e.f\" }}' }\ne: { f: '{{ tval \"g.h\" }}' }\ng: { h: '{{ tval \"a.b\" }}' }" + checkExpError(inputYaml, `cyclic reference`) + + inputYaml = "a: { b: '{{ tval \"c.d\" }}' }" + checkExpError(inputYaml, `value "c.d" does not exist`) + + inputYaml = "a: { b: '{{ tval \"c.d\" }}' }\nc: { d: '{{ invalid }}' }" + checkExpError(inputYaml, `function "invalid" not defined`) // This one happens during parse, so we don't catch it so specifically + checkExpError(inputYaml, `parse error in value "c.d"`) // The (decorated) template will still be reported though + + inputYaml = "a: { b: '{{ tval \"c.d\" }}' }\nc: { d: '{{ substr \"bad arg\" 0 0 }}' }" + checkExpError(inputYaml, `error expanding value "c.d"`) +} diff --git a/pkg/proto/hapi/chart/metadata.pb.go b/pkg/proto/hapi/chart/metadata.pb.go index 9daeaa9e5..a070dae3b 100644 --- a/pkg/proto/hapi/chart/metadata.pb.go +++ b/pkg/proto/hapi/chart/metadata.pb.go @@ -109,6 +109,8 @@ type Metadata struct { Annotations map[string]string `protobuf:"bytes,16,rep,name=annotations" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. KubeVersion string `protobuf:"bytes,17,opt,name=kubeVersion" json:"kubeVersion,omitempty"` + // Whether or not any templates found in values should be recursively expanded + ExpandValues bool `protobuf:"varint,18,opt,name=expandValues" json:"expandValues,omitempty"` } func (m *Metadata) Reset() { *m = Metadata{} } @@ -235,6 +237,13 @@ func (m *Metadata) GetKubeVersion() string { return "" } +func (m *Metadata) GetExpandValues() bool { + if m != nil { + return m.ExpandValues + } + return false +} + func init() { proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer") proto.RegisterType((*Metadata)(nil), "hapi.chart.Metadata") @@ -244,33 +253,34 @@ func init() { func init() { proto.RegisterFile("hapi/chart/metadata.proto", fileDescriptor2) } var fileDescriptor2 = []byte{ - // 435 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x5d, 0x6b, 0xd4, 0x40, - 0x14, 0x35, 0xcd, 0x66, 0x77, 0x73, 0x63, 0x35, 0x0e, 0x52, 0xc6, 0x22, 0x12, 0x16, 0x85, 0x7d, - 0xda, 0x82, 0xbe, 0x14, 0x1f, 0x04, 0x85, 0x52, 0x41, 0xbb, 0x95, 0xe0, 0x07, 0xf8, 0x36, 0x4d, - 0x2e, 0xdd, 0x61, 0x93, 0x99, 0x30, 0x99, 0xad, 0xec, 0xaf, 0xf0, 0x2f, 0xcb, 0xdc, 0x64, 0x9a, - 0xac, 0xf4, 0xed, 0x9e, 0x73, 0x66, 0xce, 0xcc, 0xbd, 0xf7, 0xc0, 0x8b, 0x8d, 0x68, 0xe4, 0x59, - 0xb1, 0x11, 0xc6, 0x9e, 0xd5, 0x68, 0x45, 0x29, 0xac, 0x58, 0x35, 0x46, 0x5b, 0xcd, 0xc0, 0x49, - 0x2b, 0x92, 0x16, 0x9f, 0x01, 0xae, 0x84, 0x54, 0x56, 0x48, 0x85, 0x86, 0x31, 0x98, 0x28, 0x51, - 0x23, 0x0f, 0xb2, 0x60, 0x19, 0xe7, 0x54, 0xb3, 0xe7, 0x10, 0x61, 0x2d, 0x64, 0xc5, 0x8f, 0x88, - 0xec, 0x00, 0x4b, 0x21, 0xdc, 0x99, 0x8a, 0x87, 0xc4, 0xb9, 0x72, 0xf1, 0x37, 0x82, 0xf9, 0x55, - 0xff, 0xd0, 0x83, 0x46, 0x0c, 0x26, 0x1b, 0x5d, 0x63, 0xef, 0x43, 0x35, 0xe3, 0x30, 0x6b, 0xf5, - 0xce, 0x14, 0xd8, 0xf2, 0x30, 0x0b, 0x97, 0x71, 0xee, 0xa1, 0x53, 0xee, 0xd0, 0xb4, 0x52, 0x2b, - 0x3e, 0xa1, 0x0b, 0x1e, 0xb2, 0x0c, 0x92, 0x12, 0xdb, 0xc2, 0xc8, 0xc6, 0x3a, 0x35, 0x22, 0x75, - 0x4c, 0xb1, 0x53, 0x98, 0x6f, 0x71, 0xff, 0x47, 0x9b, 0xb2, 0xe5, 0x53, 0xb2, 0xbd, 0xc7, 0xec, - 0x1c, 0x92, 0xfa, 0xbe, 0xe1, 0x96, 0xcf, 0xb2, 0x70, 0x99, 0xbc, 0x3d, 0x59, 0x0d, 0x23, 0x59, - 0x0d, 0xf3, 0xc8, 0xc7, 0x47, 0xd9, 0x09, 0x4c, 0x51, 0xdd, 0x4a, 0x85, 0x7c, 0x4e, 0x4f, 0xf6, - 0xc8, 0xf5, 0x25, 0x0b, 0xad, 0x78, 0xdc, 0xf5, 0xe5, 0x6a, 0xf6, 0x0a, 0x40, 0x34, 0xf2, 0x67, - 0xdf, 0x00, 0x90, 0x32, 0x62, 0xd8, 0x4b, 0x88, 0x0b, 0xad, 0x4a, 0x49, 0x1d, 0x24, 0x24, 0x0f, - 0x84, 0x73, 0xb4, 0xe2, 0xb6, 0xe5, 0x8f, 0x3b, 0x47, 0x57, 0x77, 0x8e, 0x8d, 0x77, 0x3c, 0xf6, - 0x8e, 0x9e, 0x71, 0x7a, 0x89, 0x8d, 0xc1, 0x42, 0x58, 0x2c, 0xf9, 0x93, 0x2c, 0x58, 0xce, 0xf3, - 0x11, 0xc3, 0x5e, 0xc3, 0xb1, 0x95, 0x55, 0x85, 0xc6, 0x5b, 0x3c, 0x25, 0x8b, 0x43, 0x92, 0x5d, - 0x42, 0x22, 0x94, 0xd2, 0x56, 0xb8, 0x7f, 0xb4, 0x3c, 0xa5, 0xe9, 0xbc, 0x39, 0x98, 0x8e, 0xcf, - 0xd2, 0xc7, 0xe1, 0xdc, 0x85, 0xb2, 0x66, 0x9f, 0x8f, 0x6f, 0xba, 0x25, 0x6d, 0x77, 0x37, 0xe8, - 0x1f, 0x7b, 0xd6, 0x2d, 0x69, 0x44, 0x9d, 0x7e, 0x80, 0xf4, 0x7f, 0x0b, 0x97, 0xaa, 0x2d, 0xee, - 0xfb, 0xd4, 0xb8, 0xd2, 0xa5, 0xef, 0x4e, 0x54, 0x3b, 0x9f, 0x9a, 0x0e, 0xbc, 0x3f, 0x3a, 0x0f, - 0x16, 0x19, 0x4c, 0x2f, 0xba, 0x05, 0x24, 0x30, 0xfb, 0xb1, 0xfe, 0xb2, 0xbe, 0xfe, 0xb5, 0x4e, - 0x1f, 0xb1, 0x18, 0xa2, 0xcb, 0xeb, 0xef, 0xdf, 0xbe, 0xa6, 0xc1, 0xa7, 0xd9, 0xef, 0x88, 0xfe, - 0x7c, 0x33, 0xa5, 0xdc, 0xbf, 0xfb, 0x17, 0x00, 0x00, 0xff, 0xff, 0x36, 0xf9, 0x0d, 0xa6, 0x14, - 0x03, 0x00, 0x00, + // 452 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x51, 0x6b, 0xd4, 0x40, + 0x10, 0x36, 0xcd, 0xe5, 0xee, 0x32, 0x69, 0x35, 0x0e, 0x52, 0xd6, 0x22, 0x12, 0x0e, 0x85, 0x7b, + 0xba, 0x82, 0xbe, 0x14, 0x1f, 0x04, 0x85, 0x52, 0x41, 0x7b, 0x95, 0xa0, 0x15, 0x7c, 0xdb, 0x26, + 0x43, 0x6f, 0xb9, 0x64, 0x13, 0x36, 0x7b, 0xd5, 0xfb, 0x5d, 0xfe, 0x41, 0xd9, 0x49, 0x72, 0x97, + 0x13, 0xdf, 0xe6, 0xfb, 0xbe, 0xcd, 0x37, 0xfb, 0xcd, 0x4e, 0xe0, 0xf9, 0x4a, 0xd6, 0xea, 0x3c, + 0x5b, 0x49, 0x63, 0xcf, 0x4b, 0xb2, 0x32, 0x97, 0x56, 0x2e, 0x6a, 0x53, 0xd9, 0x0a, 0xc1, 0x49, + 0x0b, 0x96, 0x66, 0x9f, 0x00, 0xae, 0xa5, 0xd2, 0x56, 0x2a, 0x4d, 0x06, 0x11, 0x46, 0x5a, 0x96, + 0x24, 0xbc, 0xc4, 0x9b, 0x87, 0x29, 0xd7, 0xf8, 0x0c, 0x02, 0x2a, 0xa5, 0x2a, 0xc4, 0x11, 0x93, + 0x2d, 0xc0, 0x18, 0xfc, 0x8d, 0x29, 0x84, 0xcf, 0x9c, 0x2b, 0x67, 0x7f, 0x02, 0x98, 0x5e, 0x77, + 0x8d, 0xfe, 0x6b, 0x84, 0x30, 0x5a, 0x55, 0x25, 0x75, 0x3e, 0x5c, 0xa3, 0x80, 0x49, 0x53, 0x6d, + 0x4c, 0x46, 0x8d, 0xf0, 0x13, 0x7f, 0x1e, 0xa6, 0x3d, 0x74, 0xca, 0x03, 0x99, 0x46, 0x55, 0x5a, + 0x8c, 0xf8, 0x83, 0x1e, 0x62, 0x02, 0x51, 0x4e, 0x4d, 0x66, 0x54, 0x6d, 0x9d, 0x1a, 0xb0, 0x3a, + 0xa4, 0xf0, 0x0c, 0xa6, 0x6b, 0xda, 0xfe, 0xaa, 0x4c, 0xde, 0x88, 0x31, 0xdb, 0xee, 0x30, 0x5e, + 0x40, 0x54, 0xee, 0x02, 0x37, 0x62, 0x92, 0xf8, 0xf3, 0xe8, 0xcd, 0xe9, 0x62, 0x3f, 0x92, 0xc5, + 0x7e, 0x1e, 0xe9, 0xf0, 0x28, 0x9e, 0xc2, 0x98, 0xf4, 0xbd, 0xd2, 0x24, 0xa6, 0xdc, 0xb2, 0x43, + 0x2e, 0x97, 0xca, 0x2a, 0x2d, 0xc2, 0x36, 0x97, 0xab, 0xf1, 0x25, 0x80, 0xac, 0xd5, 0x6d, 0x17, + 0x00, 0x58, 0x19, 0x30, 0xf8, 0x02, 0xc2, 0xac, 0xd2, 0xb9, 0xe2, 0x04, 0x11, 0xcb, 0x7b, 0xc2, + 0x39, 0x5a, 0x79, 0xdf, 0x88, 0xe3, 0xd6, 0xd1, 0xd5, 0xad, 0x63, 0xdd, 0x3b, 0x9e, 0xf4, 0x8e, + 0x3d, 0xe3, 0xf4, 0x9c, 0x6a, 0x43, 0x99, 0xb4, 0x94, 0x8b, 0xc7, 0x89, 0x37, 0x9f, 0xa6, 0x03, + 0x06, 0x5f, 0xc1, 0x89, 0x55, 0x45, 0x41, 0xa6, 0xb7, 0x78, 0xc2, 0x16, 0x87, 0x24, 0x5e, 0x41, + 0x24, 0xb5, 0xae, 0xac, 0x74, 0xf7, 0x68, 0x44, 0xcc, 0xd3, 0x79, 0x7d, 0x30, 0x9d, 0x7e, 0x97, + 0x3e, 0xec, 0xcf, 0x5d, 0x6a, 0x6b, 0xb6, 0xe9, 0xf0, 0x4b, 0xf7, 0x48, 0xeb, 0xcd, 0x1d, 0xf5, + 0xcd, 0x9e, 0xb6, 0x8f, 0x34, 0xa0, 0x70, 0x06, 0xc7, 0xf4, 0xbb, 0x96, 0x3a, 0xbf, 0x95, 0xc5, + 0x86, 0x1a, 0x81, 0x7c, 0xe5, 0x03, 0xee, 0xec, 0x3d, 0xc4, 0xff, 0xb6, 0x71, 0x9b, 0xb7, 0xa6, + 0x6d, 0xb7, 0x59, 0xae, 0x74, 0x1b, 0xfa, 0xe0, 0xce, 0xf7, 0x1b, 0xca, 0xe0, 0xdd, 0xd1, 0x85, + 0x37, 0x4b, 0x60, 0x7c, 0xd9, 0x3e, 0x52, 0x04, 0x93, 0xef, 0xcb, 0xcf, 0xcb, 0x9b, 0x1f, 0xcb, + 0xf8, 0x11, 0x86, 0x10, 0x5c, 0xdd, 0x7c, 0xfb, 0xfa, 0x25, 0xf6, 0x3e, 0x4e, 0x7e, 0x06, 0x9c, + 0xeb, 0x6e, 0xcc, 0xff, 0xc6, 0xdb, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xfa, 0x67, 0xed, 0x9b, + 0x38, 0x03, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index 37535aac7..6d72909a2 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -462,7 +462,8 @@ func (m *UpdateReleaseRequest) GetForce() bool { // UpdateReleaseResponse is the response to an update request. type UpdateReleaseResponse struct { - Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + FinalValues *hapi_chart.Config `protobuf:"bytes,2,opt,name=final_values,json=finalValues" json:"final_values,omitempty"` } func (m *UpdateReleaseResponse) Reset() { *m = UpdateReleaseResponse{} } @@ -477,6 +478,13 @@ func (m *UpdateReleaseResponse) GetRelease() *hapi_release5.Release { return nil } +func (m *UpdateReleaseResponse) GetFinalValues() *hapi_chart.Config { + if m != nil { + return m.FinalValues + } + return nil +} + type RollbackReleaseRequest struct { // The name of the release Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` @@ -672,7 +680,8 @@ func (m *InstallReleaseRequest) GetWait() bool { // InstallReleaseResponse is the response from a release installation. type InstallReleaseResponse struct { - Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + FinalValues *hapi_chart.Config `protobuf:"bytes,2,opt,name=final_values,json=finalValues" json:"final_values,omitempty"` } func (m *InstallReleaseResponse) Reset() { *m = InstallReleaseResponse{} } @@ -687,6 +696,13 @@ func (m *InstallReleaseResponse) GetRelease() *hapi_release5.Release { return nil } +func (m *InstallReleaseResponse) GetFinalValues() *hapi_chart.Config { + if m != nil { + return m.FinalValues + } + return nil +} + // UninstallReleaseRequest represents a request to uninstall a named release. type UninstallReleaseRequest struct { // Name is the name of the release to delete. @@ -1368,82 +1384,83 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 1217 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0xe3, 0xc4, - 0x17, 0xaf, 0xf3, 0x9d, 0x93, 0x36, 0xff, 0x74, 0x9a, 0xb6, 0xae, 0xff, 0x0b, 0x2a, 0x46, 0xb0, - 0xd9, 0x85, 0x4d, 0x21, 0x70, 0x83, 0x84, 0x90, 0xba, 0xdd, 0xa8, 0x2d, 0x94, 0xae, 0xe4, 0x6c, - 0x17, 0x09, 0x01, 0x91, 0x9b, 0x4c, 0x5a, 0xb3, 0x8e, 0x27, 0x78, 0xc6, 0x65, 0x7b, 0xcb, 0x1d, - 0x8f, 0xc2, 0x5b, 0xf0, 0x1e, 0x5c, 0xc2, 0x83, 0x20, 0xcf, 0x87, 0xeb, 0x49, 0xed, 0xd6, 0xf4, - 0x26, 0x9e, 0x99, 0xf3, 0xfd, 0x3b, 0x67, 0xce, 0x9c, 0x80, 0x75, 0xe9, 0x2e, 0xbc, 0x3d, 0x8a, - 0xc3, 0x2b, 0x6f, 0x82, 0xe9, 0x1e, 0xf3, 0x7c, 0x1f, 0x87, 0xfd, 0x45, 0x48, 0x18, 0x41, 0xdd, - 0x98, 0xd6, 0x57, 0xb4, 0xbe, 0xa0, 0x59, 0x5b, 0x5c, 0x62, 0x72, 0xe9, 0x86, 0x4c, 0xfc, 0x0a, - 0x6e, 0x6b, 0x3b, 0x7d, 0x4e, 0x82, 0x99, 0x77, 0x21, 0x09, 0xc2, 0x44, 0x88, 0x7d, 0xec, 0x52, - 0xac, 0xbe, 0x9a, 0x90, 0xa2, 0x79, 0xc1, 0x8c, 0x48, 0xc2, 0xff, 0x35, 0x02, 0xc3, 0x94, 0x8d, - 0xc3, 0x28, 0x90, 0xc4, 0x1d, 0x8d, 0x48, 0x99, 0xcb, 0x22, 0xaa, 0x19, 0xbb, 0xc2, 0x21, 0xf5, - 0x48, 0xa0, 0xbe, 0x82, 0x66, 0xff, 0x59, 0x82, 0x8d, 0x13, 0x8f, 0x32, 0x47, 0x08, 0x52, 0x07, - 0xff, 0x12, 0x61, 0xca, 0x50, 0x17, 0xaa, 0xbe, 0x37, 0xf7, 0x98, 0x69, 0xec, 0x1a, 0xbd, 0xb2, - 0x23, 0x36, 0x68, 0x0b, 0x6a, 0x64, 0x36, 0xa3, 0x98, 0x99, 0xa5, 0x5d, 0xa3, 0xd7, 0x74, 0xe4, - 0x0e, 0x7d, 0x05, 0x75, 0x4a, 0x42, 0x36, 0x3e, 0xbf, 0x36, 0xcb, 0xbb, 0x46, 0xaf, 0x3d, 0xf8, - 0xa0, 0x9f, 0x85, 0x53, 0x3f, 0xb6, 0x34, 0x22, 0x21, 0xeb, 0xc7, 0x3f, 0xcf, 0xaf, 0x9d, 0x1a, - 0xe5, 0xdf, 0x58, 0xef, 0xcc, 0xf3, 0x19, 0x0e, 0xcd, 0x8a, 0xd0, 0x2b, 0x76, 0xe8, 0x10, 0x80, - 0xeb, 0x25, 0xe1, 0x14, 0x87, 0x66, 0x95, 0xab, 0xee, 0x15, 0x50, 0xfd, 0x32, 0xe6, 0x77, 0x9a, - 0x54, 0x2d, 0xd1, 0x97, 0xb0, 0x2a, 0x20, 0x19, 0x4f, 0xc8, 0x14, 0x53, 0xb3, 0xb6, 0x5b, 0xee, - 0xb5, 0x07, 0x3b, 0x42, 0x95, 0x82, 0x7f, 0x24, 0x40, 0x3b, 0x20, 0x53, 0xec, 0xb4, 0x04, 0x7b, - 0xbc, 0xa6, 0xe8, 0x11, 0x34, 0x03, 0x77, 0x8e, 0xe9, 0xc2, 0x9d, 0x60, 0xb3, 0xce, 0x3d, 0xbc, - 0x39, 0xb0, 0x7f, 0x82, 0x86, 0x32, 0x6e, 0x0f, 0xa0, 0x26, 0x42, 0x43, 0x2d, 0xa8, 0x9f, 0x9d, - 0x7e, 0x73, 0xfa, 0xf2, 0xbb, 0xd3, 0xce, 0x0a, 0x6a, 0x40, 0xe5, 0x74, 0xff, 0xdb, 0x61, 0xc7, - 0x40, 0xeb, 0xb0, 0x76, 0xb2, 0x3f, 0x7a, 0x35, 0x76, 0x86, 0x27, 0xc3, 0xfd, 0xd1, 0xf0, 0x45, - 0xa7, 0x64, 0xbf, 0x0b, 0xcd, 0xc4, 0x67, 0x54, 0x87, 0xf2, 0xfe, 0xe8, 0x40, 0x88, 0xbc, 0x18, - 0x8e, 0x0e, 0x3a, 0x86, 0xfd, 0xbb, 0x01, 0x5d, 0x3d, 0x45, 0x74, 0x41, 0x02, 0x8a, 0xe3, 0x1c, - 0x4d, 0x48, 0x14, 0x24, 0x39, 0xe2, 0x1b, 0x84, 0xa0, 0x12, 0xe0, 0xb7, 0x2a, 0x43, 0x7c, 0x1d, - 0x73, 0x32, 0xc2, 0x5c, 0x9f, 0x67, 0xa7, 0xec, 0x88, 0x0d, 0xfa, 0x14, 0x1a, 0x32, 0x74, 0x6a, - 0x56, 0x76, 0xcb, 0xbd, 0xd6, 0x60, 0x53, 0x07, 0x44, 0x5a, 0x74, 0x12, 0x36, 0xfb, 0x10, 0xb6, - 0x0f, 0xb1, 0xf2, 0x44, 0xe0, 0xa5, 0x2a, 0x26, 0xb6, 0xeb, 0xce, 0x31, 0x77, 0x26, 0xb6, 0xeb, - 0xce, 0x31, 0x32, 0xa1, 0x2e, 0xcb, 0x8d, 0xbb, 0x53, 0x75, 0xd4, 0xd6, 0x66, 0x60, 0xde, 0x56, - 0x24, 0xe3, 0xca, 0xd2, 0xf4, 0x21, 0x54, 0xe2, 0x9b, 0xc0, 0xd5, 0xb4, 0x06, 0x48, 0xf7, 0xf3, - 0x38, 0x98, 0x11, 0x87, 0xd3, 0xf5, 0x54, 0x95, 0x97, 0x53, 0x75, 0x94, 0xb6, 0x7a, 0x40, 0x02, - 0x86, 0x03, 0xf6, 0x30, 0xff, 0x4f, 0x60, 0x27, 0x43, 0x93, 0x0c, 0x60, 0x0f, 0xea, 0xd2, 0x35, - 0xae, 0x2d, 0x17, 0x57, 0xc5, 0x65, 0xff, 0x5d, 0x82, 0xee, 0xd9, 0x62, 0xea, 0x32, 0xac, 0x48, - 0x77, 0x38, 0xf5, 0x18, 0xaa, 0xbc, 0xa3, 0x48, 0x2c, 0xd6, 0x85, 0x6e, 0xd1, 0x76, 0x0e, 0xe2, - 0x5f, 0x47, 0xd0, 0xd1, 0x53, 0xa8, 0x5d, 0xb9, 0x7e, 0x84, 0x29, 0x07, 0x22, 0x41, 0x4d, 0x72, - 0xf2, 0x76, 0xe4, 0x48, 0x0e, 0xb4, 0x0d, 0xf5, 0x69, 0x78, 0x1d, 0xf7, 0x13, 0x7e, 0x05, 0x1b, - 0x4e, 0x6d, 0x1a, 0x5e, 0x3b, 0x51, 0x80, 0xde, 0x87, 0xb5, 0xa9, 0x47, 0xdd, 0x73, 0x1f, 0x8f, - 0x2f, 0x09, 0x79, 0x43, 0xf9, 0x2d, 0x6c, 0x38, 0xab, 0xf2, 0xf0, 0x28, 0x3e, 0x43, 0x56, 0x5c, - 0x49, 0x93, 0x10, 0xbb, 0x0c, 0x9b, 0x35, 0x4e, 0x4f, 0xf6, 0x31, 0x86, 0xcc, 0x9b, 0x63, 0x12, - 0x31, 0x7e, 0x75, 0xca, 0x8e, 0xda, 0xa2, 0xf7, 0x60, 0x35, 0xc4, 0x14, 0xb3, 0xb1, 0xf4, 0xb2, - 0xc1, 0x25, 0x5b, 0xfc, 0xec, 0xb5, 0x70, 0x0b, 0x41, 0xe5, 0x57, 0xd7, 0x63, 0x66, 0x93, 0x93, - 0xf8, 0x5a, 0x88, 0x45, 0x14, 0x2b, 0x31, 0x50, 0x62, 0x11, 0xc5, 0x52, 0xac, 0x0b, 0xd5, 0x19, - 0x09, 0x27, 0xd8, 0x6c, 0x71, 0x9a, 0xd8, 0xd8, 0x47, 0xb0, 0xb9, 0x04, 0xf2, 0x43, 0xf3, 0xf5, - 0x8f, 0x01, 0x5b, 0x0e, 0xf1, 0xfd, 0x73, 0x77, 0xf2, 0xa6, 0x40, 0xc6, 0x52, 0xe0, 0x96, 0xee, - 0x06, 0xb7, 0x9c, 0x01, 0x6e, 0xaa, 0x08, 0x2b, 0x5a, 0x11, 0x6a, 0xb0, 0x57, 0xf3, 0x61, 0xaf, - 0xe9, 0xb0, 0x2b, 0x4c, 0xeb, 0x29, 0x4c, 0x13, 0xc0, 0x1a, 0x69, 0xc0, 0xbe, 0x86, 0xed, 0x5b, - 0x51, 0x3e, 0x14, 0xb2, 0x3f, 0x4a, 0xb0, 0x79, 0x1c, 0x50, 0xe6, 0xfa, 0xfe, 0x12, 0x62, 0x49, - 0x3d, 0x1b, 0x85, 0xeb, 0xb9, 0xf4, 0x5f, 0xea, 0xb9, 0xac, 0x41, 0xae, 0xf2, 0x53, 0x49, 0xe5, - 0xa7, 0x50, 0x8d, 0x6b, 0x9d, 0xa5, 0xb6, 0xd4, 0x59, 0xd0, 0x3b, 0x00, 0xa2, 0x28, 0xb9, 0x72, - 0x01, 0x6d, 0x93, 0x9f, 0x9c, 0xca, 0x46, 0xa2, 0xb2, 0xd1, 0xc8, 0xce, 0x46, 0xaa, 0xc2, 0xed, - 0x63, 0xd8, 0x5a, 0x86, 0xea, 0xa1, 0xb0, 0xff, 0x66, 0xc0, 0xf6, 0x59, 0xe0, 0x65, 0x02, 0x9f, - 0x55, 0xaa, 0xb7, 0xa0, 0x28, 0x65, 0x40, 0xd1, 0x85, 0xea, 0x22, 0x0a, 0x2f, 0xb0, 0x84, 0x56, - 0x6c, 0xd2, 0x31, 0x56, 0xb4, 0x18, 0xed, 0x31, 0x98, 0xb7, 0x7d, 0x78, 0x60, 0x44, 0xb1, 0xd7, - 0xc9, 0x4b, 0xd0, 0x14, 0x5d, 0xdf, 0xde, 0x80, 0xf5, 0x43, 0xcc, 0x5e, 0x8b, 0x6b, 0x21, 0xc3, - 0xb3, 0x87, 0x80, 0xd2, 0x87, 0x37, 0xf6, 0xe4, 0x91, 0x6e, 0x4f, 0x8d, 0x45, 0x8a, 0x5f, 0x71, - 0xd9, 0x5f, 0x70, 0xdd, 0x47, 0x1e, 0x65, 0x24, 0xbc, 0xbe, 0x0b, 0xba, 0x0e, 0x94, 0xe7, 0xee, - 0x5b, 0xf9, 0x50, 0xc4, 0x4b, 0xfb, 0x90, 0x7b, 0x90, 0x88, 0x4a, 0x0f, 0xd2, 0xcf, 0xae, 0x51, - 0xec, 0xd9, 0xfd, 0x01, 0xd0, 0x2b, 0x9c, 0x4c, 0x00, 0xf7, 0xbc, 0x58, 0x2a, 0x09, 0x25, 0xbd, - 0xd0, 0x4c, 0xa8, 0x4f, 0x7c, 0xec, 0x06, 0xd1, 0x42, 0xa6, 0x4d, 0x6d, 0xed, 0x1f, 0x61, 0x43, - 0xd3, 0x2e, 0xfd, 0x8c, 0xe3, 0xa1, 0x17, 0x52, 0x7b, 0xbc, 0x44, 0x9f, 0x43, 0x4d, 0x8c, 0x45, - 0x5c, 0x77, 0x7b, 0xf0, 0x48, 0xf7, 0x9b, 0x2b, 0x89, 0x02, 0x39, 0x47, 0x39, 0x92, 0x77, 0xf0, - 0x57, 0x03, 0xda, 0xea, 0xa1, 0x17, 0x43, 0x1b, 0xf2, 0x60, 0x35, 0x3d, 0xd1, 0xa0, 0x27, 0xf9, - 0x33, 0xdd, 0xd2, 0x60, 0x6a, 0x3d, 0x2d, 0xc2, 0x2a, 0x22, 0xb0, 0x57, 0x3e, 0x31, 0x10, 0x85, - 0xce, 0xf2, 0xa0, 0x81, 0x9e, 0x65, 0xeb, 0xc8, 0x99, 0x6c, 0xac, 0x7e, 0x51, 0x76, 0x65, 0x16, - 0x5d, 0xf1, 0x9a, 0xd1, 0xa7, 0x03, 0x74, 0xaf, 0x1a, 0x7d, 0x20, 0xb1, 0xf6, 0x0a, 0xf3, 0x27, - 0x76, 0x7f, 0x86, 0x35, 0xed, 0x85, 0x43, 0x39, 0x68, 0x65, 0xcd, 0x1a, 0xd6, 0x47, 0x85, 0x78, - 0x13, 0x5b, 0x73, 0x68, 0xeb, 0x4d, 0x0a, 0xe5, 0x28, 0xc8, 0xec, 0xfa, 0xd6, 0xc7, 0xc5, 0x98, - 0x13, 0x73, 0x14, 0x3a, 0xcb, 0x3d, 0x24, 0x2f, 0x8f, 0x39, 0xfd, 0x2e, 0x2f, 0x8f, 0x79, 0xad, - 0xc9, 0x5e, 0x41, 0x2e, 0xc0, 0x4d, 0x0b, 0x41, 0x8f, 0x73, 0x13, 0xa2, 0x77, 0x1e, 0xab, 0x77, - 0x3f, 0x63, 0x62, 0x62, 0x01, 0xff, 0x5b, 0x7a, 0x63, 0x51, 0x0e, 0x34, 0xd9, 0x03, 0x87, 0xf5, - 0xac, 0x20, 0xf7, 0x52, 0x50, 0xb2, 0x2b, 0xdd, 0x11, 0x94, 0xde, 0xf2, 0xee, 0x08, 0x6a, 0xa9, - 0xc1, 0xd9, 0x2b, 0xc8, 0x83, 0xb6, 0x13, 0x05, 0xd2, 0x74, 0xdc, 0x16, 0x50, 0x8e, 0xf4, 0xed, - 0xae, 0x66, 0x3d, 0x29, 0xc0, 0x79, 0x73, 0xbf, 0x9f, 0xc3, 0xf7, 0x0d, 0xc5, 0x7a, 0x5e, 0xe3, - 0xff, 0x69, 0x3f, 0xfb, 0x37, 0x00, 0x00, 0xff, 0xff, 0xf3, 0x7c, 0x9c, 0x49, 0xc1, 0x0f, 0x00, - 0x00, + // 1243 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xdd, 0x6e, 0xe3, 0xc4, + 0x17, 0xaf, 0xf3, 0x9d, 0x93, 0x6e, 0xfe, 0xe9, 0x34, 0x6d, 0x5d, 0xff, 0x17, 0x54, 0x8c, 0x60, + 0xb3, 0x0b, 0x9b, 0x42, 0x80, 0x0b, 0x24, 0x84, 0xd4, 0xed, 0x46, 0xed, 0x42, 0xe9, 0x4a, 0xce, + 0x76, 0x91, 0x10, 0x10, 0xb9, 0xc9, 0xa4, 0x35, 0xeb, 0x78, 0x82, 0x67, 0x5c, 0xb6, 0x57, 0x20, + 0xee, 0x78, 0x14, 0xde, 0x82, 0xf7, 0xe0, 0x12, 0x1e, 0x04, 0x79, 0x3e, 0x5c, 0x4f, 0x6a, 0xb7, + 0xa6, 0x17, 0xdc, 0xc4, 0x33, 0x73, 0xce, 0x9c, 0x73, 0xe6, 0xf7, 0x9b, 0x73, 0xe6, 0x04, 0xac, + 0x73, 0x77, 0xe1, 0xed, 0x52, 0x1c, 0x5e, 0x78, 0x13, 0x4c, 0x77, 0x99, 0xe7, 0xfb, 0x38, 0xec, + 0x2f, 0x42, 0xc2, 0x08, 0xea, 0xc6, 0xb2, 0xbe, 0x92, 0xf5, 0x85, 0xcc, 0xda, 0xe4, 0x3b, 0x26, + 0xe7, 0x6e, 0xc8, 0xc4, 0xaf, 0xd0, 0xb6, 0xb6, 0xd2, 0xeb, 0x24, 0x98, 0x79, 0x67, 0x52, 0x20, + 0x5c, 0x84, 0xd8, 0xc7, 0x2e, 0xc5, 0xea, 0xab, 0x6d, 0x52, 0x32, 0x2f, 0x98, 0x11, 0x29, 0xf8, + 0xbf, 0x26, 0x60, 0x98, 0xb2, 0x71, 0x18, 0x05, 0x52, 0xb8, 0xad, 0x09, 0x29, 0x73, 0x59, 0x44, + 0x35, 0x67, 0x17, 0x38, 0xa4, 0x1e, 0x09, 0xd4, 0x57, 0xc8, 0xec, 0x3f, 0x4a, 0xb0, 0x7e, 0xe4, + 0x51, 0xe6, 0x88, 0x8d, 0xd4, 0xc1, 0x3f, 0x46, 0x98, 0x32, 0xd4, 0x85, 0xaa, 0xef, 0xcd, 0x3d, + 0x66, 0x1a, 0x3b, 0x46, 0xaf, 0xec, 0x88, 0x09, 0xda, 0x84, 0x1a, 0x99, 0xcd, 0x28, 0x66, 0x66, + 0x69, 0xc7, 0xe8, 0x35, 0x1d, 0x39, 0x43, 0x9f, 0x43, 0x9d, 0x92, 0x90, 0x8d, 0x4f, 0x2f, 0xcd, + 0xf2, 0x8e, 0xd1, 0x6b, 0x0f, 0xde, 0xe9, 0x67, 0xe1, 0xd4, 0x8f, 0x3d, 0x8d, 0x48, 0xc8, 0xfa, + 0xf1, 0xcf, 0x93, 0x4b, 0xa7, 0x46, 0xf9, 0x37, 0xb6, 0x3b, 0xf3, 0x7c, 0x86, 0x43, 0xb3, 0x22, + 0xec, 0x8a, 0x19, 0x3a, 0x00, 0xe0, 0x76, 0x49, 0x38, 0xc5, 0xa1, 0x59, 0xe5, 0xa6, 0x7b, 0x05, + 0x4c, 0x3f, 0x8f, 0xf5, 0x9d, 0x26, 0x55, 0x43, 0xf4, 0x19, 0xac, 0x0a, 0x48, 0xc6, 0x13, 0x32, + 0xc5, 0xd4, 0xac, 0xed, 0x94, 0x7b, 0xed, 0xc1, 0xb6, 0x30, 0xa5, 0xe0, 0x1f, 0x09, 0xd0, 0xf6, + 0xc9, 0x14, 0x3b, 0x2d, 0xa1, 0x1e, 0x8f, 0x29, 0xba, 0x0f, 0xcd, 0xc0, 0x9d, 0x63, 0xba, 0x70, + 0x27, 0xd8, 0xac, 0xf3, 0x08, 0xaf, 0x16, 0xec, 0xef, 0xa1, 0xa1, 0x9c, 0xdb, 0x03, 0xa8, 0x89, + 0xa3, 0xa1, 0x16, 0xd4, 0x4f, 0x8e, 0xbf, 0x3c, 0x7e, 0xfe, 0xf5, 0x71, 0x67, 0x05, 0x35, 0xa0, + 0x72, 0xbc, 0xf7, 0xd5, 0xb0, 0x63, 0xa0, 0x35, 0xb8, 0x77, 0xb4, 0x37, 0x7a, 0x31, 0x76, 0x86, + 0x47, 0xc3, 0xbd, 0xd1, 0xf0, 0x69, 0xa7, 0x64, 0xbf, 0x09, 0xcd, 0x24, 0x66, 0x54, 0x87, 0xf2, + 0xde, 0x68, 0x5f, 0x6c, 0x79, 0x3a, 0x1c, 0xed, 0x77, 0x0c, 0xfb, 0x37, 0x03, 0xba, 0x3a, 0x45, + 0x74, 0x41, 0x02, 0x8a, 0x63, 0x8e, 0x26, 0x24, 0x0a, 0x12, 0x8e, 0xf8, 0x04, 0x21, 0xa8, 0x04, + 0xf8, 0xb5, 0x62, 0x88, 0x8f, 0x63, 0x4d, 0x46, 0x98, 0xeb, 0x73, 0x76, 0xca, 0x8e, 0x98, 0xa0, + 0x0f, 0xa1, 0x21, 0x8f, 0x4e, 0xcd, 0xca, 0x4e, 0xb9, 0xd7, 0x1a, 0x6c, 0xe8, 0x80, 0x48, 0x8f, + 0x4e, 0xa2, 0x66, 0x1f, 0xc0, 0xd6, 0x01, 0x56, 0x91, 0x08, 0xbc, 0xd4, 0x8d, 0x89, 0xfd, 0xba, + 0x73, 0xcc, 0x83, 0x89, 0xfd, 0xba, 0x73, 0x8c, 0x4c, 0xa8, 0xcb, 0xeb, 0xc6, 0xc3, 0xa9, 0x3a, + 0x6a, 0x6a, 0x33, 0x30, 0xaf, 0x1b, 0x92, 0xe7, 0xca, 0xb2, 0xf4, 0x2e, 0x54, 0xe2, 0x4c, 0xe0, + 0x66, 0x5a, 0x03, 0xa4, 0xc7, 0xf9, 0x2c, 0x98, 0x11, 0x87, 0xcb, 0x75, 0xaa, 0xca, 0xcb, 0x54, + 0x1d, 0xa6, 0xbd, 0xee, 0x93, 0x80, 0xe1, 0x80, 0xdd, 0x2d, 0xfe, 0x23, 0xd8, 0xce, 0xb0, 0x24, + 0x0f, 0xb0, 0x0b, 0x75, 0x19, 0x1a, 0xb7, 0x96, 0x8b, 0xab, 0xd2, 0xb2, 0xff, 0x2a, 0x41, 0xf7, + 0x64, 0x31, 0x75, 0x19, 0x56, 0xa2, 0x1b, 0x82, 0x7a, 0x00, 0x55, 0x5e, 0x51, 0x24, 0x16, 0x6b, + 0xc2, 0xb6, 0x28, 0x3b, 0xfb, 0xf1, 0xaf, 0x23, 0xe4, 0xe8, 0x11, 0xd4, 0x2e, 0x5c, 0x3f, 0xc2, + 0x94, 0x03, 0x91, 0xa0, 0x26, 0x35, 0x79, 0x39, 0x72, 0xa4, 0x06, 0xda, 0x82, 0xfa, 0x34, 0xbc, + 0x8c, 0xeb, 0x09, 0x4f, 0xc1, 0x86, 0x53, 0x9b, 0x86, 0x97, 0x4e, 0x14, 0xa0, 0xb7, 0xe1, 0xde, + 0xd4, 0xa3, 0xee, 0xa9, 0x8f, 0xc7, 0xe7, 0x84, 0xbc, 0xa2, 0x3c, 0x0b, 0x1b, 0xce, 0xaa, 0x5c, + 0x3c, 0x8c, 0xd7, 0x90, 0x15, 0xdf, 0xa4, 0x49, 0x88, 0x5d, 0x86, 0xcd, 0x1a, 0x97, 0x27, 0xf3, + 0x18, 0x43, 0xe6, 0xcd, 0x31, 0x89, 0x18, 0x4f, 0x9d, 0xb2, 0xa3, 0xa6, 0xe8, 0x2d, 0x58, 0x0d, + 0x31, 0xc5, 0x6c, 0x2c, 0xa3, 0x6c, 0xf0, 0x9d, 0x2d, 0xbe, 0xf6, 0x52, 0x84, 0x85, 0xa0, 0xf2, + 0x93, 0xeb, 0x31, 0xb3, 0xc9, 0x45, 0x7c, 0x2c, 0xb6, 0x45, 0x14, 0xab, 0x6d, 0xa0, 0xb6, 0x45, + 0x14, 0xcb, 0x6d, 0x5d, 0xa8, 0xce, 0x48, 0x38, 0xc1, 0x66, 0x8b, 0xcb, 0xc4, 0xc4, 0xfe, 0x19, + 0x36, 0x96, 0x40, 0xbe, 0x23, 0x5f, 0xe8, 0x13, 0x58, 0x9d, 0x79, 0x81, 0xeb, 0xab, 0x10, 0x4a, + 0xb9, 0xf8, 0xb6, 0xb8, 0x9e, 0x08, 0xcb, 0xfe, 0xdb, 0x80, 0x4d, 0x87, 0xf8, 0xfe, 0xa9, 0x3b, + 0x79, 0x55, 0x80, 0xe8, 0x14, 0x27, 0xa5, 0x9b, 0x39, 0x29, 0x67, 0x70, 0x92, 0xba, 0xbb, 0x15, + 0xed, 0xee, 0x6a, 0x6c, 0x55, 0xf3, 0xd9, 0xaa, 0xe9, 0x6c, 0x29, 0x2a, 0xea, 0x29, 0x2a, 0x12, + 0x9c, 0x1b, 0x69, 0x9c, 0xbf, 0x80, 0xad, 0x6b, 0xa7, 0xbc, 0x6b, 0x66, 0xfc, 0x5e, 0x82, 0x8d, + 0x67, 0x01, 0x65, 0xae, 0xef, 0x2f, 0x21, 0x96, 0xa4, 0x81, 0x51, 0x38, 0x0d, 0x4a, 0xff, 0x26, + 0x0d, 0xca, 0x1a, 0xe4, 0x8a, 0x9f, 0x4a, 0x8a, 0x9f, 0x42, 0xa9, 0xa1, 0x15, 0xa4, 0xda, 0x52, + 0x41, 0x42, 0x6f, 0x00, 0x88, 0xbb, 0xcc, 0x8d, 0x0b, 0x68, 0x9b, 0x7c, 0xe5, 0x58, 0xd6, 0x1f, + 0xc5, 0x46, 0x23, 0x9b, 0x8d, 0x54, 0x62, 0xd8, 0xbf, 0x18, 0xb0, 0xb9, 0x8c, 0xd5, 0x7f, 0x7c, + 0xc3, 0x7f, 0x35, 0x60, 0xeb, 0x24, 0xf0, 0x32, 0x09, 0xcb, 0xba, 0xe2, 0xd7, 0x20, 0x2c, 0x65, + 0x40, 0xd8, 0x85, 0xea, 0x22, 0x0a, 0xcf, 0xb0, 0xa4, 0x44, 0x4c, 0xd2, 0xd8, 0x54, 0x34, 0x6c, + 0xec, 0x31, 0x98, 0xd7, 0x63, 0xb8, 0x2b, 0x10, 0x28, 0xf5, 0xf0, 0x34, 0xc5, 0x23, 0x63, 0xaf, + 0xc3, 0xda, 0x01, 0x66, 0x2f, 0x45, 0x3a, 0xc9, 0xe3, 0xd9, 0x43, 0x40, 0xe9, 0xc5, 0x2b, 0x7f, + 0x72, 0x49, 0xf7, 0xa7, 0xba, 0x30, 0xa5, 0xaf, 0xb4, 0xec, 0x4f, 0xb9, 0xed, 0x43, 0x8f, 0x32, + 0x12, 0x5e, 0xde, 0x04, 0x5d, 0x07, 0xca, 0x73, 0xf7, 0xb5, 0x7c, 0x97, 0xe2, 0xa1, 0x7d, 0xc0, + 0x23, 0x48, 0xb6, 0xca, 0x08, 0xd2, 0xaf, 0xbc, 0x51, 0xec, 0x95, 0xff, 0x16, 0xd0, 0x0b, 0x9c, + 0x34, 0x1c, 0xb7, 0x3c, 0x90, 0x8a, 0x84, 0x92, 0x7e, 0x41, 0x4d, 0xa8, 0x4f, 0x7c, 0xec, 0x06, + 0xd1, 0x42, 0xd2, 0xa6, 0xa6, 0xf6, 0x77, 0xb0, 0xae, 0x59, 0x97, 0x71, 0xc6, 0xe7, 0xa1, 0x67, + 0xd2, 0x7a, 0x3c, 0x44, 0x1f, 0x43, 0x4d, 0x74, 0x61, 0xdc, 0x76, 0x7b, 0x70, 0x5f, 0x8f, 0x9b, + 0x1b, 0x89, 0x02, 0xd9, 0xb6, 0x39, 0x52, 0x77, 0xf0, 0x67, 0x03, 0xda, 0xaa, 0xaf, 0x10, 0x3d, + 0x22, 0xf2, 0x60, 0x35, 0xdd, 0x40, 0xa1, 0x87, 0xf9, 0x2d, 0xe4, 0x52, 0x1f, 0x6c, 0x3d, 0x2a, + 0xa2, 0x2a, 0x4e, 0x60, 0xaf, 0x7c, 0x60, 0x20, 0x0a, 0x9d, 0xe5, 0xbe, 0x06, 0x3d, 0xce, 0xb6, + 0x91, 0xd3, 0x48, 0x59, 0xfd, 0xa2, 0xea, 0xca, 0x2d, 0xba, 0xe0, 0x77, 0x46, 0x6f, 0x46, 0xd0, + 0xad, 0x66, 0xf4, 0xfe, 0xc7, 0xda, 0x2d, 0xac, 0x9f, 0xf8, 0xfd, 0x01, 0xee, 0x69, 0x0f, 0x2a, + 0xca, 0x41, 0x2b, 0xab, 0xb5, 0xb1, 0xde, 0x2b, 0xa4, 0x9b, 0xf8, 0x9a, 0x43, 0x5b, 0xaf, 0x6d, + 0x28, 0xc7, 0x40, 0xe6, 0x6b, 0x61, 0xbd, 0x5f, 0x4c, 0x39, 0x71, 0x47, 0xa1, 0xb3, 0x5c, 0x43, + 0xf2, 0x78, 0xcc, 0xa9, 0x77, 0x79, 0x3c, 0xe6, 0x95, 0x26, 0x7b, 0x05, 0xb9, 0x00, 0x57, 0x25, + 0x04, 0x3d, 0xc8, 0x25, 0x44, 0xaf, 0x3c, 0x56, 0xef, 0x76, 0xc5, 0xc4, 0xc5, 0x02, 0xfe, 0xb7, + 0xf4, 0x36, 0xa3, 0x1c, 0x68, 0xb2, 0x1b, 0x15, 0xeb, 0x71, 0x41, 0xed, 0xa5, 0x43, 0xc9, 0xaa, + 0x74, 0xc3, 0xa1, 0xf4, 0x92, 0x77, 0xc3, 0xa1, 0x96, 0x0a, 0x9c, 0xbd, 0x82, 0x3c, 0x68, 0x3b, + 0x51, 0x20, 0x5d, 0xc7, 0x65, 0x01, 0xe5, 0xec, 0xbe, 0x5e, 0xd5, 0xac, 0x87, 0x05, 0x34, 0xaf, + 0xf2, 0xfb, 0x09, 0x7c, 0xd3, 0x50, 0xaa, 0xa7, 0x35, 0xfe, 0x17, 0xfa, 0xa3, 0x7f, 0x02, 0x00, + 0x00, 0xff, 0xff, 0x8a, 0xf6, 0x81, 0x03, 0x30, 0x10, 0x00, 0x00, } diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 366fdf522..62e02c080 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -90,6 +90,8 @@ type Engine interface { // It receives a chart, a config, and a map of overrides to the config. // Overrides are assumed to be passed from the system, not the user. Render(*chart.Chart, chartutil.Values) (map[string]string, error) + // ExpandValues will expand all templates found in vals + ExpandValues(chartutil.Values) (chartutil.Values, error) } // KubeClient represents a client capable of communicating with the Kubernetes API. diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index d8c82b901..410131741 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -18,6 +18,7 @@ package environment import ( "bytes" + "fmt" "io" "testing" "time" @@ -38,6 +39,10 @@ func (e *mockEngine) Render(chrt *chart.Chart, v chartutil.Values) (map[string]s return e.out, nil } +func (e *mockEngine) ExpandValues(chartutil.Values) (chartutil.Values, error) { + return nil, fmt.Errorf("not implemented") +} + type mockKubeClient struct{} func (k *mockKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { diff --git a/pkg/tiller/release_install.go b/pkg/tiller/release_install.go index 8e7fd3acd..a2862d841 100644 --- a/pkg/tiller/release_install.go +++ b/pkg/tiller/release_install.go @@ -24,6 +24,7 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" relutil "k8s.io/helm/pkg/releaseutil" @@ -33,7 +34,7 @@ import ( // InstallRelease installs a release and stores the release record. func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { s.Log("preparing install for %s", req.Name) - rel, err := s.prepareRelease(req) + rel, finalVals, err := s.prepareRelease(req) if err != nil { s.Log("failed install prepare step: %s", err) res := &services.InstallReleaseResponse{Release: rel} @@ -47,27 +48,34 @@ func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea } s.Log("performing install for %s", req.Name) - res, err := s.performRelease(rel, req) - if err != nil { - s.Log("failed install perform step: %s", err) + res := &services.InstallReleaseResponse{Release: rel, FinalValues: finalVals} + if req.DryRun { + s.Log("dry run for %s", rel.Name) + res.Release.Info.Description = "Dry run complete" + } else { + if err := s.performRelease(rel, req); err != nil { + s.Log("failed install perform step: %s", err) + return res, err + } } - return res, err + + return res, nil } // prepareRelease builds a release for an install operation. -func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { +func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, *chart.Config, error) { if req.Chart == nil { - return nil, errMissingChart + return nil, nil, errMissingChart } name, err := s.uniqName(req.Name, req.ReuseName) if err != nil { - return nil, err + return nil, nil, err } caps, err := capabilities(s.clientset.Discovery()) if err != nil { - return nil, err + return nil, nil, err } revision := 1 @@ -81,10 +89,10 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re } valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) if err != nil { - return nil, err + return nil, nil, err } - hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) + hooks, manifestDoc, notesTxt, finalVals, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) if err != nil { // Return a release with partial data so that client can show debugging // information. @@ -104,7 +112,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re if manifestDoc != nil { rel.Manifest = manifestDoc.String() } - return rel, err + return rel, finalVals, err } // Store a release. @@ -128,23 +136,15 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re } err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) - return rel, err + return rel, finalVals, err } // performRelease runs a release. -func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { - res := &services.InstallReleaseResponse{Release: r} - - if req.DryRun { - s.Log("dry run for %s", r.Name) - res.Release.Info.Description = "Dry run complete" - return res, nil - } - +func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) error { // pre-install hooks if !req.DisableHooks { if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { - return res, err + return err } } else { s.Log("install hooks disabled for %s", req.Name) @@ -181,7 +181,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install r.Info.Description = msg s.recordRelease(old, true) s.recordRelease(r, true) - return res, err + return err } default: @@ -194,7 +194,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install r.Info.Status.Code = release.Status_FAILED r.Info.Description = msg s.recordRelease(r, true) - return res, fmt.Errorf("release %s failed: %s", r.Name, err) + return fmt.Errorf("release %s failed: %s", r.Name, err) } } @@ -206,7 +206,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install r.Info.Status.Code = release.Status_FAILED r.Info.Description = msg s.recordRelease(r, true) - return res, err + return err } } @@ -221,5 +221,5 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install // this stored in the future. s.recordRelease(r, true) - return res, nil + return nil } diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index a96c64938..3ff64c698 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -244,12 +244,12 @@ func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet return chartutil.NewVersionSet(versions...), nil } -func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, vs chartutil.VersionSet) ([]*release.Hook, *bytes.Buffer, string, error) { +func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, vs chartutil.VersionSet) ([]*release.Hook, *bytes.Buffer, string, *chart.Config, error) { // Guard to make sure Tiller is at the right version to handle this chart. sver := version.GetVersion() if ch.Metadata.TillerVersion != "" && !version.IsCompatibleRange(ch.Metadata.TillerVersion, sver) { - return nil, nil, "", fmt.Errorf("Chart incompatible with Tiller %s", sver) + return nil, nil, "", nil, fmt.Errorf("Chart incompatible with Tiller %s", sver) } if ch.Metadata.KubeVersion != "" { @@ -257,15 +257,22 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values gitVersion := cap.KubeVersion.String() k8sVersion := strings.Split(gitVersion, "+")[0] if !version.IsCompatibleRange(ch.Metadata.KubeVersion, k8sVersion) { - return nil, nil, "", fmt.Errorf("Chart requires kubernetesVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, k8sVersion) + return nil, nil, "", nil, fmt.Errorf("Chart requires kubernetesVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, k8sVersion) } } s.Log("rendering %s chart using values", ch.GetMetadata().Name) renderer := s.engine(ch) + if ch.Metadata.ExpandValues { + newVals, err := renderer.ExpandValues(values) + if err != nil { + return nil, nil, "", nil, err + } + values = newVals + } files, err := renderer.Render(ch, values) if err != nil { - return nil, nil, "", err + return nil, nil, "", nil, err } // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, @@ -303,7 +310,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values b.WriteString("\n---\n# Source: " + name + "\n") b.WriteString(content) } - return nil, b, "", err + return nil, b, "", nil, err } // Aggregate all valid manifests into one big doc. @@ -313,7 +320,18 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values b.WriteString(m.Content) } - return hooks, b, notes, nil + if !ch.Metadata.ExpandValues { + return hooks, b, notes, nil, nil + } + var finalValsStr string + finalVals, err := values.Table("Values") + if err == nil { + finalValsStr, err = finalVals.YAML() + } + if err != nil { + return nil, b, "", nil, err + } + return hooks, b, notes, &chart.Config{Raw: finalValsStr}, nil } // recordRelease with an update operation in case reuse has been set. diff --git a/pkg/tiller/release_update.go b/pkg/tiller/release_update.go index cb8b57792..662fc8df0 100644 --- a/pkg/tiller/release_update.go +++ b/pkg/tiller/release_update.go @@ -24,6 +24,7 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/timeconv" @@ -36,7 +37,7 @@ func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateRelease return nil, err } s.Log("preparing update for %s", req.Name) - currentRelease, updatedRelease, err := s.prepareUpdate(req) + currentRelease, updatedRelease, finalVals, err := s.prepareUpdate(req) if err != nil { if req.Force { // Use the --force, Luke. @@ -53,12 +54,14 @@ func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateRelease } s.Log("performing update for %s", req.Name) - res, err := s.performUpdate(currentRelease, updatedRelease, req) - if err != nil { - return res, err - } - - if !req.DryRun { + res := &services.UpdateReleaseResponse{Release: updatedRelease, FinalValues: finalVals} + if req.DryRun { + s.Log("dry run for %s", updatedRelease.Name) + res.Release.Info.Description = "Dry run complete" + } else { + if err := s.performUpdate(currentRelease, updatedRelease, req); err != nil { + return res, err + } s.Log("updating status for updated release for %s", req.Name) if err := s.env.Releases.Update(updatedRelease); err != nil { return res, err @@ -69,26 +72,26 @@ func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateRelease } // prepareUpdate builds an updated release for an update operation. -func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { +func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, *chart.Config, error) { if req.Chart == nil { - return nil, nil, errMissingChart + return nil, nil, nil, errMissingChart } // finds the deployed release with the given name currentRelease, err := s.env.Releases.Deployed(req.Name) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // If new values were not supplied in the upgrade, re-use the existing values. if err := s.reuseValues(req, currentRelease); err != nil { - return nil, nil, err + return nil, nil, nil, err } // finds the non-deleted release with the given name lastRelease, err := s.env.Releases.Last(req.Name) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Increment revision count. This is passed to templates, and also stored on @@ -106,16 +109,16 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele caps, err := capabilities(s.clientset.Discovery()) if err != nil { - return nil, nil, err + return nil, nil, nil, err } valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) + hooks, manifestDoc, notesTxt, finalVals, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Store an updated release. @@ -139,7 +142,7 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele updatedRelease.Info.Status.Notes = notesTxt } err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) - return currentRelease, updatedRelease, err + return currentRelease, updatedRelease, finalVals, err } // performUpdateForce performs the same action as a `helm delete && helm install --replace`. @@ -150,7 +153,7 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) ( return nil, err } - newRelease, err := s.prepareRelease(&services.InstallReleaseRequest{ + newRelease, finalVals, err := s.prepareRelease(&services.InstallReleaseRequest{ Chart: req.Chart, Values: req.Values, DryRun: req.DryRun, @@ -161,7 +164,7 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) ( Timeout: req.Timeout, Wait: req.Wait, }) - res := &services.UpdateReleaseResponse{Release: newRelease} + res := &services.UpdateReleaseResponse{Release: newRelease, FinalValues: finalVals} if err != nil { s.Log("failed update prepare step: %s", err) // On dry run, append the manifest contents to a failed release. This is @@ -249,19 +252,11 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) ( return res, nil } -func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { - res := &services.UpdateReleaseResponse{Release: updatedRelease} - - if req.DryRun { - s.Log("dry run for %s", updatedRelease.Name) - res.Release.Info.Description = "Dry run complete" - return res, nil - } - +func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) error { // pre-upgrade hooks if !req.DisableHooks { if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil { - return res, err + return err } } else { s.Log("update hooks disabled for %s", req.Name) @@ -273,13 +268,13 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R updatedRelease.Info.Description = msg s.recordRelease(originalRelease, true) s.recordRelease(updatedRelease, true) - return res, err + return err } // post-upgrade hooks if !req.DisableHooks { if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil { - return res, err + return err } } @@ -289,5 +284,5 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R updatedRelease.Info.Status.Code = release.Status_DEPLOYED updatedRelease.Info.Description = "Upgrade complete" - return res, nil + return nil }