pull/3252/merge
Morgan Parry 8 years ago committed by GitHub
commit 30a7941a07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -89,5 +89,8 @@ message Metadata {
map<string,string> annotations = 16; map<string,string> annotations = 16;
// KubeVersion is a SemVer constraint specifying the version of Kubernetes required. // 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;
} }

@ -214,6 +214,7 @@ message UpdateReleaseRequest {
// UpdateReleaseResponse is the response to an update request. // UpdateReleaseResponse is the response to an update request.
message UpdateReleaseResponse { message UpdateReleaseResponse {
hapi.release.Release release = 1; hapi.release.Release release = 1;
hapi.chart.Config final_values = 2;
} }
message RollbackReleaseRequest { message RollbackReleaseRequest {
@ -276,6 +277,7 @@ message InstallReleaseRequest {
// InstallReleaseResponse is the response from a release installation. // InstallReleaseResponse is the response from a release installation.
message InstallReleaseResponse { message InstallReleaseResponse {
hapi.release.Release release = 1; hapi.release.Release release = 1;
hapi.chart.Config final_values = 2;
} }
// UninstallReleaseRequest represents a request to uninstall a named release. // UninstallReleaseRequest represents a request to uninstall a named release.

@ -85,5 +85,5 @@ func (g *getCmd) run() error {
if err != nil { if err != nil {
return prettyError(err) return prettyError(err)
} }
return printRelease(g.out, res.Release) return printRelease(g.out, res.Release, nil)
} }

@ -283,7 +283,7 @@ func (i *installCmd) run() error {
if rel == nil { if rel == nil {
return nil return nil
} }
i.printRelease(rel) i.printRelease(rel, res.GetFinalValues())
// If this is a dry run, we can't display status. // If this is a dry run, we can't display status.
if i.dryRun { if i.dryRun {
@ -372,14 +372,14 @@ func vals(valueFiles valueFiles, values []string, stringValues []string) ([]byte
} }
// printRelease prints info about a release if the Debug is true. // 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 { if rel == nil {
return return
} }
// TODO: Switch to text/template like everything else. // TODO: Switch to text/template like everything else.
fmt.Fprintf(i.out, "NAME: %s\n", rel.Name) fmt.Fprintf(i.out, "NAME: %s\n", rel.Name)
if settings.Debug { if settings.Debug {
printRelease(i.out, rel) printRelease(i.out, rel, finalVals)
} }
} }
@ -511,7 +511,6 @@ func readFile(filePath string) ([]byte, error) {
// FIXME: maybe someone handle other protocols like ftp. // FIXME: maybe someone handle other protocols like ftp.
getterConstructor, err := p.ByScheme(u.Scheme) getterConstructor, err := p.ByScheme(u.Scheme)
if err != nil { if err != nil {
return ioutil.ReadFile(filePath) return ioutil.ReadFile(filePath)
} }

@ -23,6 +23,7 @@ import (
"time" "time"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/timeconv" "k8s.io/helm/pkg/timeconv"
) )
@ -44,23 +45,28 @@ MANIFEST:
{{.Release.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 { if rel == nil {
return nil return nil
} }
cfg, err := chartutil.CoalesceValues(rel.Chart, rel.Config) var valsStr string
if err != nil { if finalVals == nil {
return err vals, err := chartutil.CoalesceValues(rel.Chart, rel.Config)
} if err != nil {
cfgStr, err := cfg.YAML() return err
if err != nil { }
return err valsStr, err = vals.YAML()
if err != nil {
return err
}
} else {
valsStr = finalVals.Raw
} }
data := map[string]interface{}{ data := map[string]interface{}{
"Release": rel, "Release": rel,
"ComputedValues": cfgStr, "ComputedValues": valsStr,
"ReleaseDate": timeconv.Format(rel.Info.LastDeployed, time.ANSIC), "ReleaseDate": timeconv.Format(rel.Info.LastDeployed, time.ANSIC),
} }
return tpl(printReleaseTemplate, data, out) return tpl(printReleaseTemplate, data, out)

@ -195,6 +195,12 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
return err return err
} }
if c.Metadata.ExpandValues {
if vals, err = renderer.ExpandValues(vals); err != nil {
return err
}
}
out, err := renderer.Render(c, vals) out, err := renderer.Render(c, vals)
listManifests := []tiller.Manifest{} listManifests := []tiller.Manifest{}
if err != nil { if err != nil {
@ -221,7 +227,11 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
Namespace: t.namespace, Namespace: t.namespace,
Info: &release.Info{LastDeployed: timeconv.Timestamp(time.Now())}, 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})
} }
var manifestsToRender []tiller.Manifest var manifestsToRender []tiller.Manifest

@ -230,7 +230,7 @@ func (u *upgradeCmd) run() error {
} }
if settings.Debug { 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) fmt.Fprintf(u.out, "Release %q has been upgraded. Happy Helming!\n", u.release)

@ -56,6 +56,7 @@ maintainers: # (optional)
email: The maintainer's email (optional for each maintainer) email: The maintainer's email (optional for each maintainer)
url: A URL for the maintainer (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) 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). 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. appVersion: The version of the app that this contains (optional). This needn't be SemVer.
deprecated: Whether this chart is deprecated (optional, boolean) deprecated: Whether this chart is deprecated (optional, boolean)
@ -775,6 +776,95 @@ parent chart.
Also, global variables of parent charts take precedence over the global variables from subcharts. 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 ### References
When it comes to writing templates and values files, there are several When it comes to writing templates and values files, there are several

@ -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 for security reasons: `env` and `expandenv` (which would have given chart authors
access to Tiller's environment). access to Tiller's environment).
We also added two special template functions: `include` and `required`. The `include` We also added four special template functions: `include`, `required`, `tpl`, and
function allows you to bring in another template, and then pass the results to other `tval`.
template functions.
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 For example, this template snippet includes a template called `mytpl`, then
lowercases the result, then wraps that in double quotes. 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 }} 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 ## Quote Strings, Don't Quote Integers
When you are working with string data, you are always safer quoting the When you are working with string data, you are always safer quoting the

@ -166,49 +166,53 @@ func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap {
// Add the 'tpl' function here // Add the 'tpl' function here
funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) { funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) {
basePath, err := vals.PathValue("Template.BasePath") dummyName := "___tpl_template"
if err != nil { spec := renderSpec{
return "", fmt.Errorf("Cannot retrieve Template.Basepath from values inside tpl function: %s (%s)", tpl, err.Error()) tpls: map[string]renderable{dummyName: {tpl: tpl, vals: vals}},
}
r := renderable{
tpl: tpl,
vals: vals,
basePath: basePath.(string),
} }
result, err := e.renderFromSpec(&spec)
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)
if err != nil { if err != nil {
return "", fmt.Errorf("Error during tpl function execution for %q: %s", tpl, err.Error()) 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 return funcMap
} }
// render takes a map of templates/values and renders them. func (e *Engine) render(tpls map[string]renderable) (map[string]string, error) {
func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string, err 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 // 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 // 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. // 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 // 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 // to share common blocks, but to make the entire thing feel like a file-based
// template engine. // template engine.
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("rendering template failed: %v", r)
}
}()
t := template.New("gotpl") t := template.New("gotpl")
if e.Strict { if e.Strict {
t.Option("missingkey=error") t.Option("missingkey=error")
@ -220,52 +224,78 @@ func (e *Engine) render(tpls map[string]renderable) (rendered map[string]string,
funcMap := e.alterFuncMap(t) funcMap := e.alterFuncMap(t)
// We want to parse the templates in a predictable order. The order favors // Add the engine's currentTemplates to the template context
// higher-level (in file system) templates over deeply nested templates. // so they can be referenced in the tpl function
keys := sortTemplates(tpls) 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 { func (e *Engine) renderSingle(t *template.Template, name string, vals chartutil.Values, buf *bytes.Buffer) (result string, err error) {
r := tpls[fname] defer func() {
t = t.New(fname).Funcs(funcMap) if r := recover(); r != nil {
if _, err := t.Parse(r.tpl); err != nil { err = fmt.Errorf("rendering template failed: %v", r)
return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err)
} }
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 "<no value>" 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(), "<no value>", "", -1)
return result, nil
}
// Adding the engine's currentTemplates to the template context func (e *Engine) renderFromSpec(spec *renderSpec) (rendered map[string]string, err error) {
// so they can be referenced in the tpl function defer func() {
for fname, r := range e.CurrentTemplates { if r := recover(); r != nil {
if t.Lookup(fname) == nil { err = fmt.Errorf("rendering template failed: %v", r)
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)
}
} }
}()
prep, err := e.renderPrepare()
if err != nil {
return nil, err
} }
rendered = make(map[string]string, len(files)) if spec.order == nil {
var buf bytes.Buffer spec.order = make([]string, 0, len(spec.tpls))
for _, file := range files { for key := range spec.tpls {
// Don't render partials. We don't care out the direct output of partials. spec.order = append(spec.order, key)
// They are only included from other templates.
if strings.HasPrefix(path.Base(file), "_") {
continue
} }
// At render time, add information about the template that is being rendered. sort.Strings(spec.order)
vals := tpls[file].vals }
vals["Template"] = map[string]interface{}{"Name": file, "BasePath": tpls[file].basePath}
if err := t.ExecuteTemplate(&buf, file, vals); err != nil { t := prep.tpl
return map[string]string{}, fmt.Errorf("render error in %q: %s", file, err) 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 "<no value>" even if Options(missing=zero) rendered = make(map[string]string, len(spec.order))
// is set. Since missing=error will never get here, we do not need to handle var buf bytes.Buffer
// the Strict case. for _, name := range spec.order {
rendered[file] = strings.Replace(buf.String(), "<no value>", "", -1) if spec.filter == nil || spec.filter(name) {
buf.Reset() 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 return rendered, nil

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

@ -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"`)
}

@ -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"` 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 is a SemVer constraint specifying the version of Kubernetes required.
KubeVersion string `protobuf:"bytes,17,opt,name=kubeVersion" json:"kubeVersion,omitempty"` 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{} } func (m *Metadata) Reset() { *m = Metadata{} }
@ -235,6 +237,13 @@ func (m *Metadata) GetKubeVersion() string {
return "" return ""
} }
func (m *Metadata) GetExpandValues() bool {
if m != nil {
return m.ExpandValues
}
return false
}
func init() { func init() {
proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer") proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer")
proto.RegisterType((*Metadata)(nil), "hapi.chart.Metadata") proto.RegisterType((*Metadata)(nil), "hapi.chart.Metadata")
@ -244,33 +253,34 @@ func init() {
func init() { proto.RegisterFile("hapi/chart/metadata.proto", fileDescriptor2) } func init() { proto.RegisterFile("hapi/chart/metadata.proto", fileDescriptor2) }
var fileDescriptor2 = []byte{ var fileDescriptor2 = []byte{
// 435 bytes of a gzipped FileDescriptorProto // 452 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x5d, 0x6b, 0xd4, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x51, 0x6b, 0xd4, 0x40,
0x14, 0x35, 0xcd, 0x66, 0x77, 0x73, 0x63, 0x35, 0x0e, 0x52, 0xc6, 0x22, 0x12, 0x16, 0x85, 0x7d, 0x10, 0x36, 0xcd, 0xe5, 0xee, 0x32, 0x69, 0x35, 0x0e, 0x52, 0xd6, 0x22, 0x12, 0x0e, 0x85, 0x7b,
0xda, 0x82, 0xbe, 0x14, 0x1f, 0x04, 0x85, 0x52, 0x41, 0xbb, 0x95, 0xe0, 0x07, 0xf8, 0x36, 0x4d, 0xba, 0x82, 0xbe, 0x14, 0x1f, 0x04, 0x85, 0x52, 0x41, 0x7b, 0x95, 0xa0, 0x15, 0x7c, 0xdb, 0x26,
0x2e, 0xdd, 0x61, 0x93, 0x99, 0x30, 0x99, 0xad, 0xec, 0xaf, 0xf0, 0x2f, 0xcb, 0xdc, 0x64, 0x9a, 0x43, 0x6f, 0xb9, 0x64, 0x13, 0x36, 0x7b, 0xd5, 0xfb, 0x5d, 0xfe, 0x41, 0xd9, 0x49, 0x72, 0x97,
0xac, 0xf4, 0xed, 0x9e, 0x73, 0x66, 0xce, 0xcc, 0xbd, 0xf7, 0xc0, 0x8b, 0x8d, 0x68, 0xe4, 0x59, 0x13, 0xdf, 0xe6, 0xfb, 0xbe, 0xcd, 0x37, 0xfb, 0xcd, 0x4e, 0xe0, 0xf9, 0x4a, 0xd6, 0xea, 0x3c,
0xb1, 0x11, 0xc6, 0x9e, 0xd5, 0x68, 0x45, 0x29, 0xac, 0x58, 0x35, 0x46, 0x5b, 0xcd, 0xc0, 0x49, 0x5b, 0x49, 0x63, 0xcf, 0x4b, 0xb2, 0x32, 0x97, 0x56, 0x2e, 0x6a, 0x53, 0xd9, 0x0a, 0xc1, 0x49,
0x2b, 0x92, 0x16, 0x9f, 0x01, 0xae, 0x84, 0x54, 0x56, 0x48, 0x85, 0x86, 0x31, 0x98, 0x28, 0x51, 0x0b, 0x96, 0x66, 0x9f, 0x00, 0xae, 0xa5, 0xd2, 0x56, 0x2a, 0x4d, 0x06, 0x11, 0x46, 0x5a, 0x96,
0x23, 0x0f, 0xb2, 0x60, 0x19, 0xe7, 0x54, 0xb3, 0xe7, 0x10, 0x61, 0x2d, 0x64, 0xc5, 0x8f, 0x88, 0x24, 0xbc, 0xc4, 0x9b, 0x87, 0x29, 0xd7, 0xf8, 0x0c, 0x02, 0x2a, 0xa5, 0x2a, 0xc4, 0x11, 0x93,
0xec, 0x00, 0x4b, 0x21, 0xdc, 0x99, 0x8a, 0x87, 0xc4, 0xb9, 0x72, 0xf1, 0x37, 0x82, 0xf9, 0x55, 0x2d, 0xc0, 0x18, 0xfc, 0x8d, 0x29, 0x84, 0xcf, 0x9c, 0x2b, 0x67, 0x7f, 0x02, 0x98, 0x5e, 0x77,
0xff, 0xd0, 0x83, 0x46, 0x0c, 0x26, 0x1b, 0x5d, 0x63, 0xef, 0x43, 0x35, 0xe3, 0x30, 0x6b, 0xf5, 0x8d, 0xfe, 0x6b, 0x84, 0x30, 0x5a, 0x55, 0x25, 0x75, 0x3e, 0x5c, 0xa3, 0x80, 0x49, 0x53, 0x6d,
0xce, 0x14, 0xd8, 0xf2, 0x30, 0x0b, 0x97, 0x71, 0xee, 0xa1, 0x53, 0xee, 0xd0, 0xb4, 0x52, 0x2b, 0x4c, 0x46, 0x8d, 0xf0, 0x13, 0x7f, 0x1e, 0xa6, 0x3d, 0x74, 0xca, 0x03, 0x99, 0x46, 0x55, 0x5a,
0x3e, 0xa1, 0x0b, 0x1e, 0xb2, 0x0c, 0x92, 0x12, 0xdb, 0xc2, 0xc8, 0xc6, 0x3a, 0x35, 0x22, 0x75, 0x8c, 0xf8, 0x83, 0x1e, 0x62, 0x02, 0x51, 0x4e, 0x4d, 0x66, 0x54, 0x6d, 0x9d, 0x1a, 0xb0, 0x3a,
0x4c, 0xb1, 0x53, 0x98, 0x6f, 0x71, 0xff, 0x47, 0x9b, 0xb2, 0xe5, 0x53, 0xb2, 0xbd, 0xc7, 0xec, 0xa4, 0xf0, 0x0c, 0xa6, 0x6b, 0xda, 0xfe, 0xaa, 0x4c, 0xde, 0x88, 0x31, 0xdb, 0xee, 0x30, 0x5e,
0x1c, 0x92, 0xfa, 0xbe, 0xe1, 0x96, 0xcf, 0xb2, 0x70, 0x99, 0xbc, 0x3d, 0x59, 0x0d, 0x23, 0x59, 0x40, 0x54, 0xee, 0x02, 0x37, 0x62, 0x92, 0xf8, 0xf3, 0xe8, 0xcd, 0xe9, 0x62, 0x3f, 0x92, 0xc5,
0x0d, 0xf3, 0xc8, 0xc7, 0x47, 0xd9, 0x09, 0x4c, 0x51, 0xdd, 0x4a, 0x85, 0x7c, 0x4e, 0x4f, 0xf6, 0x7e, 0x1e, 0xe9, 0xf0, 0x28, 0x9e, 0xc2, 0x98, 0xf4, 0xbd, 0xd2, 0x24, 0xa6, 0xdc, 0xb2, 0x43,
0xc8, 0xf5, 0x25, 0x0b, 0xad, 0x78, 0xdc, 0xf5, 0xe5, 0x6a, 0xf6, 0x0a, 0x40, 0x34, 0xf2, 0x67, 0x2e, 0x97, 0xca, 0x2a, 0x2d, 0xc2, 0x36, 0x97, 0xab, 0xf1, 0x25, 0x80, 0xac, 0xd5, 0x6d, 0x17,
0xdf, 0x00, 0x90, 0x32, 0x62, 0xd8, 0x4b, 0x88, 0x0b, 0xad, 0x4a, 0x49, 0x1d, 0x24, 0x24, 0x0f, 0x00, 0x58, 0x19, 0x30, 0xf8, 0x02, 0xc2, 0xac, 0xd2, 0xb9, 0xe2, 0x04, 0x11, 0xcb, 0x7b, 0xc2,
0x84, 0x73, 0xb4, 0xe2, 0xb6, 0xe5, 0x8f, 0x3b, 0x47, 0x57, 0x77, 0x8e, 0x8d, 0x77, 0x3c, 0xf6, 0x39, 0x5a, 0x79, 0xdf, 0x88, 0xe3, 0xd6, 0xd1, 0xd5, 0xad, 0x63, 0xdd, 0x3b, 0x9e, 0xf4, 0x8e,
0x8e, 0x9e, 0x71, 0x7a, 0x89, 0x8d, 0xc1, 0x42, 0x58, 0x2c, 0xf9, 0x93, 0x2c, 0x58, 0xce, 0xf3, 0x3d, 0xe3, 0xf4, 0x9c, 0x6a, 0x43, 0x99, 0xb4, 0x94, 0x8b, 0xc7, 0x89, 0x37, 0x9f, 0xa6, 0x03,
0x11, 0xc3, 0x5e, 0xc3, 0xb1, 0x95, 0x55, 0x85, 0xc6, 0x5b, 0x3c, 0x25, 0x8b, 0x43, 0x92, 0x5d, 0x06, 0x5f, 0xc1, 0x89, 0x55, 0x45, 0x41, 0xa6, 0xb7, 0x78, 0xc2, 0x16, 0x87, 0x24, 0x5e, 0x41,
0x42, 0x22, 0x94, 0xd2, 0x56, 0xb8, 0x7f, 0xb4, 0x3c, 0xa5, 0xe9, 0xbc, 0x39, 0x98, 0x8e, 0xcf, 0x24, 0xb5, 0xae, 0xac, 0x74, 0xf7, 0x68, 0x44, 0xcc, 0xd3, 0x79, 0x7d, 0x30, 0x9d, 0x7e, 0x97,
0xd2, 0xc7, 0xe1, 0xdc, 0x85, 0xb2, 0x66, 0x9f, 0x8f, 0x6f, 0xba, 0x25, 0x6d, 0x77, 0x37, 0xe8, 0x3e, 0xec, 0xcf, 0x5d, 0x6a, 0x6b, 0xb6, 0xe9, 0xf0, 0x4b, 0xf7, 0x48, 0xeb, 0xcd, 0x1d, 0xf5,
0x1f, 0x7b, 0xd6, 0x2d, 0x69, 0x44, 0x9d, 0x7e, 0x80, 0xf4, 0x7f, 0x0b, 0x97, 0xaa, 0x2d, 0xee, 0xcd, 0x9e, 0xb6, 0x8f, 0x34, 0xa0, 0x70, 0x06, 0xc7, 0xf4, 0xbb, 0x96, 0x3a, 0xbf, 0x95, 0xc5,
0xfb, 0xd4, 0xb8, 0xd2, 0xa5, 0xef, 0x4e, 0x54, 0x3b, 0x9f, 0x9a, 0x0e, 0xbc, 0x3f, 0x3a, 0x0f, 0x86, 0x1a, 0x81, 0x7c, 0xe5, 0x03, 0xee, 0xec, 0x3d, 0xc4, 0xff, 0xb6, 0x71, 0x9b, 0xb7, 0xa6,
0x16, 0x19, 0x4c, 0x2f, 0xba, 0x05, 0x24, 0x30, 0xfb, 0xb1, 0xfe, 0xb2, 0xbe, 0xfe, 0xb5, 0x4e, 0x6d, 0xb7, 0x59, 0xae, 0x74, 0x1b, 0xfa, 0xe0, 0xce, 0xf7, 0x1b, 0xca, 0xe0, 0xdd, 0xd1, 0x85,
0x1f, 0xb1, 0x18, 0xa2, 0xcb, 0xeb, 0xef, 0xdf, 0xbe, 0xa6, 0xc1, 0xa7, 0xd9, 0xef, 0x88, 0xfe, 0x37, 0x4b, 0x60, 0x7c, 0xd9, 0x3e, 0x52, 0x04, 0x93, 0xef, 0xcb, 0xcf, 0xcb, 0x9b, 0x1f, 0xcb,
0x7c, 0x33, 0xa5, 0xdc, 0xbf, 0xfb, 0x17, 0x00, 0x00, 0xff, 0xff, 0x36, 0xf9, 0x0d, 0xa6, 0x14, 0xf8, 0x11, 0x86, 0x10, 0x5c, 0xdd, 0x7c, 0xfb, 0xfa, 0x25, 0xf6, 0x3e, 0x4e, 0x7e, 0x06, 0x9c,
0x03, 0x00, 0x00, 0xeb, 0x6e, 0xcc, 0xff, 0xc6, 0xdb, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xfa, 0x67, 0xed, 0x9b,
0x38, 0x03, 0x00, 0x00,
} }

@ -462,7 +462,8 @@ func (m *UpdateReleaseRequest) GetForce() bool {
// UpdateReleaseResponse is the response to an update request. // UpdateReleaseResponse is the response to an update request.
type UpdateReleaseResponse struct { 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{} } func (m *UpdateReleaseResponse) Reset() { *m = UpdateReleaseResponse{} }
@ -477,6 +478,13 @@ func (m *UpdateReleaseResponse) GetRelease() *hapi_release5.Release {
return nil return nil
} }
func (m *UpdateReleaseResponse) GetFinalValues() *hapi_chart.Config {
if m != nil {
return m.FinalValues
}
return nil
}
type RollbackReleaseRequest struct { type RollbackReleaseRequest struct {
// The name of the release // The name of the release
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` 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. // InstallReleaseResponse is the response from a release installation.
type InstallReleaseResponse struct { 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{} } func (m *InstallReleaseResponse) Reset() { *m = InstallReleaseResponse{} }
@ -687,6 +696,13 @@ func (m *InstallReleaseResponse) GetRelease() *hapi_release5.Release {
return nil 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. // UninstallReleaseRequest represents a request to uninstall a named release.
type UninstallReleaseRequest struct { type UninstallReleaseRequest struct {
// Name is the name of the release to delete. // 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) } func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 1217 bytes of a gzipped FileDescriptorProto // 1243 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0xe3, 0xc4, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xdd, 0x6e, 0xe3, 0xc4,
0x17, 0xaf, 0xf3, 0x9d, 0x93, 0x36, 0xff, 0x74, 0x9a, 0xb6, 0xae, 0xff, 0x0b, 0x2a, 0x46, 0xb0, 0x17, 0xaf, 0xf3, 0x9d, 0x93, 0x6e, 0xfe, 0xe9, 0x34, 0x6d, 0x5d, 0xff, 0x17, 0x54, 0x8c, 0x60,
0xd9, 0x85, 0x4d, 0x21, 0x70, 0x83, 0x84, 0x90, 0xba, 0xdd, 0xa8, 0x2d, 0x94, 0xae, 0xe4, 0x6c, 0xb3, 0x0b, 0x9b, 0x42, 0x80, 0x0b, 0x24, 0x84, 0xd4, 0xed, 0x46, 0xed, 0x42, 0xe9, 0x4a, 0xce,
0x17, 0x09, 0x01, 0x91, 0x9b, 0x4c, 0x5a, 0xb3, 0x8e, 0x27, 0x78, 0xc6, 0x65, 0x7b, 0xcb, 0x1d, 0x76, 0x91, 0x10, 0x10, 0xb9, 0xc9, 0xa4, 0x35, 0xeb, 0x78, 0x82, 0x67, 0x5c, 0xb6, 0x57, 0x20,
0x8f, 0xc2, 0x5b, 0xf0, 0x1e, 0x5c, 0xc2, 0x83, 0x20, 0xcf, 0x87, 0xeb, 0x49, 0xed, 0xd6, 0xf4, 0xee, 0x78, 0x14, 0xde, 0x82, 0xf7, 0xe0, 0x12, 0x1e, 0x04, 0x79, 0x3e, 0x5c, 0x4f, 0x6a, 0xb7,
0x26, 0x9e, 0x99, 0xf3, 0xfd, 0x3b, 0x67, 0xce, 0x9c, 0x80, 0x75, 0xe9, 0x2e, 0xbc, 0x3d, 0x8a, 0xa6, 0x17, 0xdc, 0xc4, 0x33, 0x73, 0xce, 0x9c, 0x73, 0xe6, 0xf7, 0x9b, 0x73, 0xe6, 0x04, 0xac,
0xc3, 0x2b, 0x6f, 0x82, 0xe9, 0x1e, 0xf3, 0x7c, 0x1f, 0x87, 0xfd, 0x45, 0x48, 0x18, 0x41, 0xdd, 0x73, 0x77, 0xe1, 0xed, 0x52, 0x1c, 0x5e, 0x78, 0x13, 0x4c, 0x77, 0x99, 0xe7, 0xfb, 0x38, 0xec,
0x98, 0xd6, 0x57, 0xb4, 0xbe, 0xa0, 0x59, 0x5b, 0x5c, 0x62, 0x72, 0xe9, 0x86, 0x4c, 0xfc, 0x0a, 0x2f, 0x42, 0xc2, 0x08, 0xea, 0xc6, 0xb2, 0xbe, 0x92, 0xf5, 0x85, 0xcc, 0xda, 0xe4, 0x3b, 0x26,
0x6e, 0x6b, 0x3b, 0x7d, 0x4e, 0x82, 0x99, 0x77, 0x21, 0x09, 0xc2, 0x44, 0x88, 0x7d, 0xec, 0x52, 0xe7, 0x6e, 0xc8, 0xc4, 0xaf, 0xd0, 0xb6, 0xb6, 0xd2, 0xeb, 0x24, 0x98, 0x79, 0x67, 0x52, 0x20,
0xac, 0xbe, 0x9a, 0x90, 0xa2, 0x79, 0xc1, 0x8c, 0x48, 0xc2, 0xff, 0x35, 0x02, 0xc3, 0x94, 0x8d, 0x5c, 0x84, 0xd8, 0xc7, 0x2e, 0xc5, 0xea, 0xab, 0x6d, 0x52, 0x32, 0x2f, 0x98, 0x11, 0x29, 0xf8,
0xc3, 0x28, 0x90, 0xc4, 0x1d, 0x8d, 0x48, 0x99, 0xcb, 0x22, 0xaa, 0x19, 0xbb, 0xc2, 0x21, 0xf5, 0xbf, 0x26, 0x60, 0x98, 0xb2, 0x71, 0x18, 0x05, 0x52, 0xb8, 0xad, 0x09, 0x29, 0x73, 0x59, 0x44,
0x48, 0xa0, 0xbe, 0x82, 0x66, 0xff, 0x59, 0x82, 0x8d, 0x13, 0x8f, 0x32, 0x47, 0x08, 0x52, 0x07, 0x35, 0x67, 0x17, 0x38, 0xa4, 0x1e, 0x09, 0xd4, 0x57, 0xc8, 0xec, 0x3f, 0x4a, 0xb0, 0x7e, 0xe4,
0xff, 0x12, 0x61, 0xca, 0x50, 0x17, 0xaa, 0xbe, 0x37, 0xf7, 0x98, 0x69, 0xec, 0x1a, 0xbd, 0xb2, 0x51, 0xe6, 0x88, 0x8d, 0xd4, 0xc1, 0x3f, 0x46, 0x98, 0x32, 0xd4, 0x85, 0xaa, 0xef, 0xcd, 0x3d,
0x23, 0x36, 0x68, 0x0b, 0x6a, 0x64, 0x36, 0xa3, 0x98, 0x99, 0xa5, 0x5d, 0xa3, 0xd7, 0x74, 0xe4, 0x66, 0x1a, 0x3b, 0x46, 0xaf, 0xec, 0x88, 0x09, 0xda, 0x84, 0x1a, 0x99, 0xcd, 0x28, 0x66, 0x66,
0x0e, 0x7d, 0x05, 0x75, 0x4a, 0x42, 0x36, 0x3e, 0xbf, 0x36, 0xcb, 0xbb, 0x46, 0xaf, 0x3d, 0xf8, 0x69, 0xc7, 0xe8, 0x35, 0x1d, 0x39, 0x43, 0x9f, 0x43, 0x9d, 0x92, 0x90, 0x8d, 0x4f, 0x2f, 0xcd,
0xa0, 0x9f, 0x85, 0x53, 0x3f, 0xb6, 0x34, 0x22, 0x21, 0xeb, 0xc7, 0x3f, 0xcf, 0xaf, 0x9d, 0x1a, 0xf2, 0x8e, 0xd1, 0x6b, 0x0f, 0xde, 0xe9, 0x67, 0xe1, 0xd4, 0x8f, 0x3d, 0x8d, 0x48, 0xc8, 0xfa,
0xe5, 0xdf, 0x58, 0xef, 0xcc, 0xf3, 0x19, 0x0e, 0xcd, 0x8a, 0xd0, 0x2b, 0x76, 0xe8, 0x10, 0x80, 0xf1, 0xcf, 0x93, 0x4b, 0xa7, 0x46, 0xf9, 0x37, 0xb6, 0x3b, 0xf3, 0x7c, 0x86, 0x43, 0xb3, 0x22,
0xeb, 0x25, 0xe1, 0x14, 0x87, 0x66, 0x95, 0xab, 0xee, 0x15, 0x50, 0xfd, 0x32, 0xe6, 0x77, 0x9a, 0xec, 0x8a, 0x19, 0x3a, 0x00, 0xe0, 0x76, 0x49, 0x38, 0xc5, 0xa1, 0x59, 0xe5, 0xa6, 0x7b, 0x05,
0x54, 0x2d, 0xd1, 0x97, 0xb0, 0x2a, 0x20, 0x19, 0x4f, 0xc8, 0x14, 0x53, 0xb3, 0xb6, 0x5b, 0xee, 0x4c, 0x3f, 0x8f, 0xf5, 0x9d, 0x26, 0x55, 0x43, 0xf4, 0x19, 0xac, 0x0a, 0x48, 0xc6, 0x13, 0x32,
0xb5, 0x07, 0x3b, 0x42, 0x95, 0x82, 0x7f, 0x24, 0x40, 0x3b, 0x20, 0x53, 0xec, 0xb4, 0x04, 0x7b, 0xc5, 0xd4, 0xac, 0xed, 0x94, 0x7b, 0xed, 0xc1, 0xb6, 0x30, 0xa5, 0xe0, 0x1f, 0x09, 0xd0, 0xf6,
0xbc, 0xa6, 0xe8, 0x11, 0x34, 0x03, 0x77, 0x8e, 0xe9, 0xc2, 0x9d, 0x60, 0xb3, 0xce, 0x3d, 0xbc, 0xc9, 0x14, 0x3b, 0x2d, 0xa1, 0x1e, 0x8f, 0x29, 0xba, 0x0f, 0xcd, 0xc0, 0x9d, 0x63, 0xba, 0x70,
0x39, 0xb0, 0x7f, 0x82, 0x86, 0x32, 0x6e, 0x0f, 0xa0, 0x26, 0x42, 0x43, 0x2d, 0xa8, 0x9f, 0x9d, 0x27, 0xd8, 0xac, 0xf3, 0x08, 0xaf, 0x16, 0xec, 0xef, 0xa1, 0xa1, 0x9c, 0xdb, 0x03, 0xa8, 0x89,
0x7e, 0x73, 0xfa, 0xf2, 0xbb, 0xd3, 0xce, 0x0a, 0x6a, 0x40, 0xe5, 0x74, 0xff, 0xdb, 0x61, 0xc7, 0xa3, 0xa1, 0x16, 0xd4, 0x4f, 0x8e, 0xbf, 0x3c, 0x7e, 0xfe, 0xf5, 0x71, 0x67, 0x05, 0x35, 0xa0,
0x40, 0xeb, 0xb0, 0x76, 0xb2, 0x3f, 0x7a, 0x35, 0x76, 0x86, 0x27, 0xc3, 0xfd, 0xd1, 0xf0, 0x45, 0x72, 0xbc, 0xf7, 0xd5, 0xb0, 0x63, 0xa0, 0x35, 0xb8, 0x77, 0xb4, 0x37, 0x7a, 0x31, 0x76, 0x86,
0xa7, 0x64, 0xbf, 0x0b, 0xcd, 0xc4, 0x67, 0x54, 0x87, 0xf2, 0xfe, 0xe8, 0x40, 0x88, 0xbc, 0x18, 0x47, 0xc3, 0xbd, 0xd1, 0xf0, 0x69, 0xa7, 0x64, 0xbf, 0x09, 0xcd, 0x24, 0x66, 0x54, 0x87, 0xf2,
0x8e, 0x0e, 0x3a, 0x86, 0xfd, 0xbb, 0x01, 0x5d, 0x3d, 0x45, 0x74, 0x41, 0x02, 0x8a, 0xe3, 0x1c, 0xde, 0x68, 0x5f, 0x6c, 0x79, 0x3a, 0x1c, 0xed, 0x77, 0x0c, 0xfb, 0x37, 0x03, 0xba, 0x3a, 0x45,
0x4d, 0x48, 0x14, 0x24, 0x39, 0xe2, 0x1b, 0x84, 0xa0, 0x12, 0xe0, 0xb7, 0x2a, 0x43, 0x7c, 0x1d, 0x74, 0x41, 0x02, 0x8a, 0x63, 0x8e, 0x26, 0x24, 0x0a, 0x12, 0x8e, 0xf8, 0x04, 0x21, 0xa8, 0x04,
0x73, 0x32, 0xc2, 0x5c, 0x9f, 0x67, 0xa7, 0xec, 0x88, 0x0d, 0xfa, 0x14, 0x1a, 0x32, 0x74, 0x6a, 0xf8, 0xb5, 0x62, 0x88, 0x8f, 0x63, 0x4d, 0x46, 0x98, 0xeb, 0x73, 0x76, 0xca, 0x8e, 0x98, 0xa0,
0x56, 0x76, 0xcb, 0xbd, 0xd6, 0x60, 0x53, 0x07, 0x44, 0x5a, 0x74, 0x12, 0x36, 0xfb, 0x10, 0xb6, 0x0f, 0xa1, 0x21, 0x8f, 0x4e, 0xcd, 0xca, 0x4e, 0xb9, 0xd7, 0x1a, 0x6c, 0xe8, 0x80, 0x48, 0x8f,
0x0f, 0xb1, 0xf2, 0x44, 0xe0, 0xa5, 0x2a, 0x26, 0xb6, 0xeb, 0xce, 0x31, 0x77, 0x26, 0xb6, 0xeb, 0x4e, 0xa2, 0x66, 0x1f, 0xc0, 0xd6, 0x01, 0x56, 0x91, 0x08, 0xbc, 0xd4, 0x8d, 0x89, 0xfd, 0xba,
0xce, 0x31, 0x32, 0xa1, 0x2e, 0xcb, 0x8d, 0xbb, 0x53, 0x75, 0xd4, 0xd6, 0x66, 0x60, 0xde, 0x56, 0x73, 0xcc, 0x83, 0x89, 0xfd, 0xba, 0x73, 0x8c, 0x4c, 0xa8, 0xcb, 0xeb, 0xc6, 0xc3, 0xa9, 0x3a,
0x24, 0xe3, 0xca, 0xd2, 0xf4, 0x21, 0x54, 0xe2, 0x9b, 0xc0, 0xd5, 0xb4, 0x06, 0x48, 0xf7, 0xf3, 0x6a, 0x6a, 0x33, 0x30, 0xaf, 0x1b, 0x92, 0xe7, 0xca, 0xb2, 0xf4, 0x2e, 0x54, 0xe2, 0x4c, 0xe0,
0x38, 0x98, 0x11, 0x87, 0xd3, 0xf5, 0x54, 0x95, 0x97, 0x53, 0x75, 0x94, 0xb6, 0x7a, 0x40, 0x02, 0x66, 0x5a, 0x03, 0xa4, 0xc7, 0xf9, 0x2c, 0x98, 0x11, 0x87, 0xcb, 0x75, 0xaa, 0xca, 0xcb, 0x54,
0x86, 0x03, 0xf6, 0x30, 0xff, 0x4f, 0x60, 0x27, 0x43, 0x93, 0x0c, 0x60, 0x0f, 0xea, 0xd2, 0x35, 0x1d, 0xa6, 0xbd, 0xee, 0x93, 0x80, 0xe1, 0x80, 0xdd, 0x2d, 0xfe, 0x23, 0xd8, 0xce, 0xb0, 0x24,
0xae, 0x2d, 0x17, 0x57, 0xc5, 0x65, 0xff, 0x5d, 0x82, 0xee, 0xd9, 0x62, 0xea, 0x32, 0xac, 0x48, 0x0f, 0xb0, 0x0b, 0x75, 0x19, 0x1a, 0xb7, 0x96, 0x8b, 0xab, 0xd2, 0xb2, 0xff, 0x2a, 0x41, 0xf7,
0x77, 0x38, 0xf5, 0x18, 0xaa, 0xbc, 0xa3, 0x48, 0x2c, 0xd6, 0x85, 0x6e, 0xd1, 0x76, 0x0e, 0xe2, 0x64, 0x31, 0x75, 0x19, 0x56, 0xa2, 0x1b, 0x82, 0x7a, 0x00, 0x55, 0x5e, 0x51, 0x24, 0x16, 0x6b,
0x5f, 0x47, 0xd0, 0xd1, 0x53, 0xa8, 0x5d, 0xb9, 0x7e, 0x84, 0x29, 0x07, 0x22, 0x41, 0x4d, 0x72, 0xc2, 0xb6, 0x28, 0x3b, 0xfb, 0xf1, 0xaf, 0x23, 0xe4, 0xe8, 0x11, 0xd4, 0x2e, 0x5c, 0x3f, 0xc2,
0xf2, 0x76, 0xe4, 0x48, 0x0e, 0xb4, 0x0d, 0xf5, 0x69, 0x78, 0x1d, 0xf7, 0x13, 0x7e, 0x05, 0x1b, 0x94, 0x03, 0x91, 0xa0, 0x26, 0x35, 0x79, 0x39, 0x72, 0xa4, 0x06, 0xda, 0x82, 0xfa, 0x34, 0xbc,
0x4e, 0x6d, 0x1a, 0x5e, 0x3b, 0x51, 0x80, 0xde, 0x87, 0xb5, 0xa9, 0x47, 0xdd, 0x73, 0x1f, 0x8f, 0x8c, 0xeb, 0x09, 0x4f, 0xc1, 0x86, 0x53, 0x9b, 0x86, 0x97, 0x4e, 0x14, 0xa0, 0xb7, 0xe1, 0xde,
0x2f, 0x09, 0x79, 0x43, 0xf9, 0x2d, 0x6c, 0x38, 0xab, 0xf2, 0xf0, 0x28, 0x3e, 0x43, 0x56, 0x5c, 0xd4, 0xa3, 0xee, 0xa9, 0x8f, 0xc7, 0xe7, 0x84, 0xbc, 0xa2, 0x3c, 0x0b, 0x1b, 0xce, 0xaa, 0x5c,
0x49, 0x93, 0x10, 0xbb, 0x0c, 0x9b, 0x35, 0x4e, 0x4f, 0xf6, 0x31, 0x86, 0xcc, 0x9b, 0x63, 0x12, 0x3c, 0x8c, 0xd7, 0x90, 0x15, 0xdf, 0xa4, 0x49, 0x88, 0x5d, 0x86, 0xcd, 0x1a, 0x97, 0x27, 0xf3,
0x31, 0x7e, 0x75, 0xca, 0x8e, 0xda, 0xa2, 0xf7, 0x60, 0x35, 0xc4, 0x14, 0xb3, 0xb1, 0xf4, 0xb2, 0x18, 0x43, 0xe6, 0xcd, 0x31, 0x89, 0x18, 0x4f, 0x9d, 0xb2, 0xa3, 0xa6, 0xe8, 0x2d, 0x58, 0x0d,
0xc1, 0x25, 0x5b, 0xfc, 0xec, 0xb5, 0x70, 0x0b, 0x41, 0xe5, 0x57, 0xd7, 0x63, 0x66, 0x93, 0x93, 0x31, 0xc5, 0x6c, 0x2c, 0xa3, 0x6c, 0xf0, 0x9d, 0x2d, 0xbe, 0xf6, 0x52, 0x84, 0x85, 0xa0, 0xf2,
0xf8, 0x5a, 0x88, 0x45, 0x14, 0x2b, 0x31, 0x50, 0x62, 0x11, 0xc5, 0x52, 0xac, 0x0b, 0xd5, 0x19, 0x93, 0xeb, 0x31, 0xb3, 0xc9, 0x45, 0x7c, 0x2c, 0xb6, 0x45, 0x14, 0xab, 0x6d, 0xa0, 0xb6, 0x45,
0x09, 0x27, 0xd8, 0x6c, 0x71, 0x9a, 0xd8, 0xd8, 0x47, 0xb0, 0xb9, 0x04, 0xf2, 0x43, 0xf3, 0xf5, 0x14, 0xcb, 0x6d, 0x5d, 0xa8, 0xce, 0x48, 0x38, 0xc1, 0x66, 0x8b, 0xcb, 0xc4, 0xc4, 0xfe, 0x19,
0x8f, 0x01, 0x5b, 0x0e, 0xf1, 0xfd, 0x73, 0x77, 0xf2, 0xa6, 0x40, 0xc6, 0x52, 0xe0, 0x96, 0xee, 0x36, 0x96, 0x40, 0xbe, 0x23, 0x5f, 0xe8, 0x13, 0x58, 0x9d, 0x79, 0x81, 0xeb, 0xab, 0x10, 0x4a,
0x06, 0xb7, 0x9c, 0x01, 0x6e, 0xaa, 0x08, 0x2b, 0x5a, 0x11, 0x6a, 0xb0, 0x57, 0xf3, 0x61, 0xaf, 0xb9, 0xf8, 0xb6, 0xb8, 0x9e, 0x08, 0xcb, 0xfe, 0xdb, 0x80, 0x4d, 0x87, 0xf8, 0xfe, 0xa9, 0x3b,
0xe9, 0xb0, 0x2b, 0x4c, 0xeb, 0x29, 0x4c, 0x13, 0xc0, 0x1a, 0x69, 0xc0, 0xbe, 0x86, 0xed, 0x5b, 0x79, 0x55, 0x80, 0xe8, 0x14, 0x27, 0xa5, 0x9b, 0x39, 0x29, 0x67, 0x70, 0x92, 0xba, 0xbb, 0x15,
0x51, 0x3e, 0x14, 0xb2, 0x3f, 0x4a, 0xb0, 0x79, 0x1c, 0x50, 0xe6, 0xfa, 0xfe, 0x12, 0x62, 0x49, 0xed, 0xee, 0x6a, 0x6c, 0x55, 0xf3, 0xd9, 0xaa, 0xe9, 0x6c, 0x29, 0x2a, 0xea, 0x29, 0x2a, 0x12,
0x3d, 0x1b, 0x85, 0xeb, 0xb9, 0xf4, 0x5f, 0xea, 0xb9, 0xac, 0x41, 0xae, 0xf2, 0x53, 0x49, 0xe5, 0x9c, 0x1b, 0x69, 0x9c, 0xbf, 0x80, 0xad, 0x6b, 0xa7, 0xbc, 0x6b, 0x66, 0xfc, 0x5e, 0x82, 0x8d,
0xa7, 0x50, 0x8d, 0x6b, 0x9d, 0xa5, 0xb6, 0xd4, 0x59, 0xd0, 0x3b, 0x00, 0xa2, 0x28, 0xb9, 0x72, 0x67, 0x01, 0x65, 0xae, 0xef, 0x2f, 0x21, 0x96, 0xa4, 0x81, 0x51, 0x38, 0x0d, 0x4a, 0xff, 0x26,
0x01, 0x6d, 0x93, 0x9f, 0x9c, 0xca, 0x46, 0xa2, 0xb2, 0xd1, 0xc8, 0xce, 0x46, 0xaa, 0xc2, 0xed, 0x0d, 0xca, 0x1a, 0xe4, 0x8a, 0x9f, 0x4a, 0x8a, 0x9f, 0x42, 0xa9, 0xa1, 0x15, 0xa4, 0xda, 0x52,
0x63, 0xd8, 0x5a, 0x86, 0xea, 0xa1, 0xb0, 0xff, 0x66, 0xc0, 0xf6, 0x59, 0xe0, 0x65, 0x02, 0x9f, 0x41, 0x42, 0x6f, 0x00, 0x88, 0xbb, 0xcc, 0x8d, 0x0b, 0x68, 0x9b, 0x7c, 0xe5, 0x58, 0xd6, 0x1f,
0x55, 0xaa, 0xb7, 0xa0, 0x28, 0x65, 0x40, 0xd1, 0x85, 0xea, 0x22, 0x0a, 0x2f, 0xb0, 0x84, 0x56, 0xc5, 0x46, 0x23, 0x9b, 0x8d, 0x54, 0x62, 0xd8, 0xbf, 0x18, 0xb0, 0xb9, 0x8c, 0xd5, 0x7f, 0x7c,
0x6c, 0xd2, 0x31, 0x56, 0xb4, 0x18, 0xed, 0x31, 0x98, 0xb7, 0x7d, 0x78, 0x60, 0x44, 0xb1, 0xd7, 0xc3, 0x7f, 0x35, 0x60, 0xeb, 0x24, 0xf0, 0x32, 0x09, 0xcb, 0xba, 0xe2, 0xd7, 0x20, 0x2c, 0x65,
0xc9, 0x4b, 0xd0, 0x14, 0x5d, 0xdf, 0xde, 0x80, 0xf5, 0x43, 0xcc, 0x5e, 0x8b, 0x6b, 0x21, 0xc3, 0x40, 0xd8, 0x85, 0xea, 0x22, 0x0a, 0xcf, 0xb0, 0xa4, 0x44, 0x4c, 0xd2, 0xd8, 0x54, 0x34, 0x6c,
0xb3, 0x87, 0x80, 0xd2, 0x87, 0x37, 0xf6, 0xe4, 0x91, 0x6e, 0x4f, 0x8d, 0x45, 0x8a, 0x5f, 0x71, 0xec, 0x31, 0x98, 0xd7, 0x63, 0xb8, 0x2b, 0x10, 0x28, 0xf5, 0xf0, 0x34, 0xc5, 0x23, 0x63, 0xaf,
0xd9, 0x5f, 0x70, 0xdd, 0x47, 0x1e, 0x65, 0x24, 0xbc, 0xbe, 0x0b, 0xba, 0x0e, 0x94, 0xe7, 0xee, 0xc3, 0xda, 0x01, 0x66, 0x2f, 0x45, 0x3a, 0xc9, 0xe3, 0xd9, 0x43, 0x40, 0xe9, 0xc5, 0x2b, 0x7f,
0x5b, 0xf9, 0x50, 0xc4, 0x4b, 0xfb, 0x90, 0x7b, 0x90, 0x88, 0x4a, 0x0f, 0xd2, 0xcf, 0xae, 0x51, 0x72, 0x49, 0xf7, 0xa7, 0xba, 0x30, 0xa5, 0xaf, 0xb4, 0xec, 0x4f, 0xb9, 0xed, 0x43, 0x8f, 0x32,
0xec, 0xd9, 0xfd, 0x01, 0xd0, 0x2b, 0x9c, 0x4c, 0x00, 0xf7, 0xbc, 0x58, 0x2a, 0x09, 0x25, 0xbd, 0x12, 0x5e, 0xde, 0x04, 0x5d, 0x07, 0xca, 0x73, 0xf7, 0xb5, 0x7c, 0x97, 0xe2, 0xa1, 0x7d, 0xc0,
0xd0, 0x4c, 0xa8, 0x4f, 0x7c, 0xec, 0x06, 0xd1, 0x42, 0xa6, 0x4d, 0x6d, 0xed, 0x1f, 0x61, 0x43, 0x23, 0x48, 0xb6, 0xca, 0x08, 0xd2, 0xaf, 0xbc, 0x51, 0xec, 0x95, 0xff, 0x16, 0xd0, 0x0b, 0x9c,
0xd3, 0x2e, 0xfd, 0x8c, 0xe3, 0xa1, 0x17, 0x52, 0x7b, 0xbc, 0x44, 0x9f, 0x43, 0x4d, 0x8c, 0x45, 0x34, 0x1c, 0xb7, 0x3c, 0x90, 0x8a, 0x84, 0x92, 0x7e, 0x41, 0x4d, 0xa8, 0x4f, 0x7c, 0xec, 0x06,
0x5c, 0x77, 0x7b, 0xf0, 0x48, 0xf7, 0x9b, 0x2b, 0x89, 0x02, 0x39, 0x47, 0x39, 0x92, 0x77, 0xf0, 0xd1, 0x42, 0xd2, 0xa6, 0xa6, 0xf6, 0x77, 0xb0, 0xae, 0x59, 0x97, 0x71, 0xc6, 0xe7, 0xa1, 0x67,
0x57, 0x03, 0xda, 0xea, 0xa1, 0x17, 0x43, 0x1b, 0xf2, 0x60, 0x35, 0x3d, 0xd1, 0xa0, 0x27, 0xf9, 0xd2, 0x7a, 0x3c, 0x44, 0x1f, 0x43, 0x4d, 0x74, 0x61, 0xdc, 0x76, 0x7b, 0x70, 0x5f, 0x8f, 0x9b,
0x33, 0xdd, 0xd2, 0x60, 0x6a, 0x3d, 0x2d, 0xc2, 0x2a, 0x22, 0xb0, 0x57, 0x3e, 0x31, 0x10, 0x85, 0x1b, 0x89, 0x02, 0xd9, 0xb6, 0x39, 0x52, 0x77, 0xf0, 0x67, 0x03, 0xda, 0xaa, 0xaf, 0x10, 0x3d,
0xce, 0xf2, 0xa0, 0x81, 0x9e, 0x65, 0xeb, 0xc8, 0x99, 0x6c, 0xac, 0x7e, 0x51, 0x76, 0x65, 0x16, 0x22, 0xf2, 0x60, 0x35, 0xdd, 0x40, 0xa1, 0x87, 0xf9, 0x2d, 0xe4, 0x52, 0x1f, 0x6c, 0x3d, 0x2a,
0x5d, 0xf1, 0x9a, 0xd1, 0xa7, 0x03, 0x74, 0xaf, 0x1a, 0x7d, 0x20, 0xb1, 0xf6, 0x0a, 0xf3, 0x27, 0xa2, 0x2a, 0x4e, 0x60, 0xaf, 0x7c, 0x60, 0x20, 0x0a, 0x9d, 0xe5, 0xbe, 0x06, 0x3d, 0xce, 0xb6,
0x76, 0x7f, 0x86, 0x35, 0xed, 0x85, 0x43, 0x39, 0x68, 0x65, 0xcd, 0x1a, 0xd6, 0x47, 0x85, 0x78, 0x91, 0xd3, 0x48, 0x59, 0xfd, 0xa2, 0xea, 0xca, 0x2d, 0xba, 0xe0, 0x77, 0x46, 0x6f, 0x46, 0xd0,
0x13, 0x5b, 0x73, 0x68, 0xeb, 0x4d, 0x0a, 0xe5, 0x28, 0xc8, 0xec, 0xfa, 0xd6, 0xc7, 0xc5, 0x98, 0xad, 0x66, 0xf4, 0xfe, 0xc7, 0xda, 0x2d, 0xac, 0x9f, 0xf8, 0xfd, 0x01, 0xee, 0x69, 0x0f, 0x2a,
0x13, 0x73, 0x14, 0x3a, 0xcb, 0x3d, 0x24, 0x2f, 0x8f, 0x39, 0xfd, 0x2e, 0x2f, 0x8f, 0x79, 0xad, 0xca, 0x41, 0x2b, 0xab, 0xb5, 0xb1, 0xde, 0x2b, 0xa4, 0x9b, 0xf8, 0x9a, 0x43, 0x5b, 0xaf, 0x6d,
0xc9, 0x5e, 0x41, 0x2e, 0xc0, 0x4d, 0x0b, 0x41, 0x8f, 0x73, 0x13, 0xa2, 0x77, 0x1e, 0xab, 0x77, 0x28, 0xc7, 0x40, 0xe6, 0x6b, 0x61, 0xbd, 0x5f, 0x4c, 0x39, 0x71, 0x47, 0xa1, 0xb3, 0x5c, 0x43,
0x3f, 0x63, 0x62, 0x62, 0x01, 0xff, 0x5b, 0x7a, 0x63, 0x51, 0x0e, 0x34, 0xd9, 0x03, 0x87, 0xf5, 0xf2, 0x78, 0xcc, 0xa9, 0x77, 0x79, 0x3c, 0xe6, 0x95, 0x26, 0x7b, 0x05, 0xb9, 0x00, 0x57, 0x25,
0xac, 0x20, 0xf7, 0x52, 0x50, 0xb2, 0x2b, 0xdd, 0x11, 0x94, 0xde, 0xf2, 0xee, 0x08, 0x6a, 0xa9, 0x04, 0x3d, 0xc8, 0x25, 0x44, 0xaf, 0x3c, 0x56, 0xef, 0x76, 0xc5, 0xc4, 0xc5, 0x02, 0xfe, 0xb7,
0xc1, 0xd9, 0x2b, 0xc8, 0x83, 0xb6, 0x13, 0x05, 0xd2, 0x74, 0xdc, 0x16, 0x50, 0x8e, 0xf4, 0xed, 0xf4, 0x36, 0xa3, 0x1c, 0x68, 0xb2, 0x1b, 0x15, 0xeb, 0x71, 0x41, 0xed, 0xa5, 0x43, 0xc9, 0xaa,
0xae, 0x66, 0x3d, 0x29, 0xc0, 0x79, 0x73, 0xbf, 0x9f, 0xc3, 0xf7, 0x0d, 0xc5, 0x7a, 0x5e, 0xe3, 0x74, 0xc3, 0xa1, 0xf4, 0x92, 0x77, 0xc3, 0xa1, 0x96, 0x0a, 0x9c, 0xbd, 0x82, 0x3c, 0x68, 0x3b,
0xff, 0x69, 0x3f, 0xfb, 0x37, 0x00, 0x00, 0xff, 0xff, 0xf3, 0x7c, 0x9c, 0x49, 0xc1, 0x0f, 0x00, 0x51, 0x20, 0x5d, 0xc7, 0x65, 0x01, 0xe5, 0xec, 0xbe, 0x5e, 0xd5, 0xac, 0x87, 0x05, 0x34, 0xaf,
0x00, 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,
} }

@ -90,6 +90,8 @@ type Engine interface {
// It receives a chart, a config, and a map of overrides to the config. // 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. // Overrides are assumed to be passed from the system, not the user.
Render(*chart.Chart, chartutil.Values) (map[string]string, error) 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. // KubeClient represents a client capable of communicating with the Kubernetes API.

@ -18,6 +18,7 @@ package environment
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"testing" "testing"
"time" "time"
@ -38,6 +39,10 @@ func (e *mockEngine) Render(chrt *chart.Chart, v chartutil.Values) (map[string]s
return e.out, nil return e.out, nil
} }
func (e *mockEngine) ExpandValues(chartutil.Values) (chartutil.Values, error) {
return nil, fmt.Errorf("not implemented")
}
type mockKubeClient struct{} type mockKubeClient struct{}
func (k *mockKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { func (k *mockKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error {

@ -24,6 +24,7 @@ import (
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks" "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/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
relutil "k8s.io/helm/pkg/releaseutil" relutil "k8s.io/helm/pkg/releaseutil"
@ -33,7 +34,7 @@ import (
// InstallRelease installs a release and stores the release record. // InstallRelease installs a release and stores the release record.
func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
s.Log("preparing install for %s", req.Name) s.Log("preparing install for %s", req.Name)
rel, err := s.prepareRelease(req) rel, finalVals, err := s.prepareRelease(req)
if err != nil { if err != nil {
s.Log("failed install prepare step: %s", err) s.Log("failed install prepare step: %s", err)
res := &services.InstallReleaseResponse{Release: rel} 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) s.Log("performing install for %s", req.Name)
res, err := s.performRelease(rel, req) res := &services.InstallReleaseResponse{Release: rel, FinalValues: finalVals}
if err != nil { if req.DryRun {
s.Log("failed install perform step: %s", err) 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. // 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 { if req.Chart == nil {
return nil, errMissingChart return nil, nil, errMissingChart
} }
name, err := s.uniqName(req.Name, req.ReuseName) name, err := s.uniqName(req.Name, req.ReuseName)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
caps, err := capabilities(s.clientset.Discovery()) caps, err := capabilities(s.clientset.Discovery())
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
revision := 1 revision := 1
@ -81,10 +89,10 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
} }
valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps)
if err != nil { 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 { if err != nil {
// Return a release with partial data so that client can show debugging // Return a release with partial data so that client can show debugging
// information. // information.
@ -104,7 +112,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
if manifestDoc != nil { if manifestDoc != nil {
rel.Manifest = manifestDoc.String() rel.Manifest = manifestDoc.String()
} }
return rel, err return rel, finalVals, err
} }
// Store a release. // Store a release.
@ -128,23 +136,15 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
} }
err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes())
return rel, err return rel, finalVals, err
} }
// performRelease runs a release. // performRelease runs a release.
func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) 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
}
// pre-install hooks // pre-install hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil {
return res, err return err
} }
} else { } else {
s.Log("install hooks disabled for %s", req.Name) 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 r.Info.Description = msg
s.recordRelease(old, true) s.recordRelease(old, true)
s.recordRelease(r, true) s.recordRelease(r, true)
return res, err return err
} }
default: default:
@ -194,7 +194,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
r.Info.Status.Code = release.Status_FAILED r.Info.Status.Code = release.Status_FAILED
r.Info.Description = msg r.Info.Description = msg
s.recordRelease(r, true) 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.Status.Code = release.Status_FAILED
r.Info.Description = msg r.Info.Description = msg
s.recordRelease(r, true) 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. // this stored in the future.
s.recordRelease(r, true) s.recordRelease(r, true)
return res, nil return nil
} }

@ -260,12 +260,12 @@ func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet
return chartutil.NewVersionSet(versions...), nil 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. // Guard to make sure Tiller is at the right version to handle this chart.
sver := version.GetVersion() sver := version.GetVersion()
if ch.Metadata.TillerVersion != "" && if ch.Metadata.TillerVersion != "" &&
!version.IsCompatibleRange(ch.Metadata.TillerVersion, sver) { !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 != "" { if ch.Metadata.KubeVersion != "" {
@ -273,15 +273,22 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
gitVersion := cap.KubeVersion.String() gitVersion := cap.KubeVersion.String()
k8sVersion := strings.Split(gitVersion, "+")[0] k8sVersion := strings.Split(gitVersion, "+")[0]
if !version.IsCompatibleRange(ch.Metadata.KubeVersion, k8sVersion) { 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) s.Log("rendering %s chart using values", ch.GetMetadata().Name)
renderer := s.engine(ch) 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) files, err := renderer.Render(ch, values)
if err != nil { 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, // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource,
@ -319,7 +326,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
b.WriteString("\n---\n# Source: " + name + "\n") b.WriteString("\n---\n# Source: " + name + "\n")
b.WriteString(content) b.WriteString(content)
} }
return nil, b, "", err return nil, b, "", nil, err
} }
// Aggregate all valid manifests into one big doc. // Aggregate all valid manifests into one big doc.
@ -329,7 +336,18 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
b.WriteString(m.Content) 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. // recordRelease with an update operation in case reuse has been set.

@ -24,6 +24,7 @@ import (
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks" "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/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/timeconv" "k8s.io/helm/pkg/timeconv"
@ -36,7 +37,7 @@ func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateRelease
return nil, err return nil, err
} }
s.Log("preparing update for %s", req.Name) 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 err != nil {
if req.Force { if req.Force {
// Use the --force, Luke. // 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) s.Log("performing update for %s", req.Name)
res, err := s.performUpdate(currentRelease, updatedRelease, req) res := &services.UpdateReleaseResponse{Release: updatedRelease, FinalValues: finalVals}
if err != nil { if req.DryRun {
return res, err s.Log("dry run for %s", updatedRelease.Name)
} res.Release.Info.Description = "Dry run complete"
} else {
if !req.DryRun { if err := s.performUpdate(currentRelease, updatedRelease, req); err != nil {
return res, err
}
s.Log("updating status for updated release for %s", req.Name) s.Log("updating status for updated release for %s", req.Name)
if err := s.env.Releases.Update(updatedRelease); err != nil { if err := s.env.Releases.Update(updatedRelease); err != nil {
return res, err 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. // 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 { if req.Chart == nil {
return nil, nil, errMissingChart return nil, nil, nil, errMissingChart
} }
// finds the deployed release with the given name // finds the deployed release with the given name
currentRelease, err := s.env.Releases.Deployed(req.Name) currentRelease, err := s.env.Releases.Deployed(req.Name)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
// determine if values will be reused // determine if values will be reused
if err := s.reuseValues(req, currentRelease); err != nil { 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 // finds the non-deleted release with the given name
lastRelease, err := s.env.Releases.Last(req.Name) lastRelease, err := s.env.Releases.Last(req.Name)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
// Increment revision count. This is passed to templates, and also stored on // 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()) caps, err := capabilities(s.clientset.Discovery())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps)
if err != nil { 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 { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
// Store an updated release. // Store an updated release.
@ -139,7 +142,7 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
updatedRelease.Info.Status.Notes = notesTxt updatedRelease.Info.Status.Notes = notesTxt
} }
err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) 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`. // 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 return nil, err
} }
newRelease, err := s.prepareRelease(&services.InstallReleaseRequest{ newRelease, finalVals, err := s.prepareRelease(&services.InstallReleaseRequest{
Chart: req.Chart, Chart: req.Chart,
Values: req.Values, Values: req.Values,
DryRun: req.DryRun, DryRun: req.DryRun,
@ -161,7 +164,7 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (
Timeout: req.Timeout, Timeout: req.Timeout,
Wait: req.Wait, Wait: req.Wait,
}) })
res := &services.UpdateReleaseResponse{Release: newRelease} res := &services.UpdateReleaseResponse{Release: newRelease, FinalValues: finalVals}
if err != nil { if err != nil {
s.Log("failed update prepare step: %s", err) s.Log("failed update prepare step: %s", err)
// On dry run, append the manifest contents to a failed release. This is // 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 return res, nil
} }
func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) 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
}
// pre-upgrade hooks // pre-upgrade hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil { if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil {
return res, err return err
} }
} else { } else {
s.Log("update hooks disabled for %s", req.Name) 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 updatedRelease.Info.Description = msg
s.recordRelease(originalRelease, true) s.recordRelease(originalRelease, true)
s.recordRelease(updatedRelease, true) s.recordRelease(updatedRelease, true)
return res, err return err
} }
// post-upgrade hooks // post-upgrade hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil { 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.Status.Code = release.Status_DEPLOYED
updatedRelease.Info.Description = "Upgrade complete" updatedRelease.Info.Description = "Upgrade complete"
return res, nil return nil
} }

Loading…
Cancel
Save