diff --git a/cmd/helm/template.go b/cmd/helm/template.go index f9c51542a..b82433ade 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -168,6 +168,10 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } } + for _, warn := range rel.Warnings { + warning(warn.Message) + } + return err }, } diff --git a/pkg/action/action.go b/pkg/action/action.go index 82760250f..573008808 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -101,19 +101,20 @@ type Configuration struct { // // TODO: This function is badly in need of a refactor. // TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed -// This code has to do with writing files to disk. -func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) { +// +// This code has to do with writing files to disk. +func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, []engine.Warning, error) { hs := []*release.Hook{} b := bytes.NewBuffer(nil) caps, err := cfg.getCapabilities() if err != nil { - return hs, b, "", err + return hs, b, "", nil, err } if ch.Metadata.KubeVersion != "" { if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { - return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) + return hs, b, "", nil, errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) } } @@ -125,18 +126,20 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu // is mocked. It is not up to the template author to decide when the user wants to // connect to the cluster. So when the user says to dry run, respect the user's // wishes and do not connect to the cluster. + var theEngine engine.Engine if !dryRun && cfg.RESTClientGetter != nil { restConfig, err := cfg.RESTClientGetter.ToRESTConfig() if err != nil { - return hs, b, "", err + return hs, b, "", nil, err } - files, err2 = engine.RenderWithClient(ch, values, restConfig) + theEngine = engine.NewEngineWithClient(restConfig) } else { - files, err2 = engine.Render(ch, values) + theEngine = engine.NewEngine() } + files, err2 = theEngine.Render(ch, values) if err2 != nil { - return hs, b, "", err2 + return hs, b, "", nil, err2 } // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, @@ -175,7 +178,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu } fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content) } - return hs, b, "", err + return hs, b, "", nil, err } // Aggregate all valid manifests into one big doc. @@ -188,7 +191,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu } else { err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name]) if err != nil { - return hs, b, "", err + return hs, b, "", nil, err } fileWritten[crd.Name] = true } @@ -209,7 +212,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu // used by install or upgrade err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) if err != nil { - return hs, b, "", err + return hs, b, "", nil, err } fileWritten[m.Name] = true } @@ -218,11 +221,11 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu if pr != nil { b, err = pr.Run(b) if err != nil { - return hs, b, notes, errors.Wrap(err, "error while running post render on files") + return hs, b, notes, nil, errors.Wrap(err, "error while running post render on files") } } - return hs, b, notes, nil + return hs, b, notes, theEngine.GetWarnings(), nil } // RESTClientGetter gets the rest client diff --git a/pkg/action/install.go b/pkg/action/install.go index fa5508234..aaafc1421 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -256,7 +256,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma rel := i.createRelease(chrt, vals) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) + rel.Hooks, manifestDoc, rel.Info.Notes, rel.Warnings, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 690397d4a..62b629685 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -231,7 +231,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) + hooks, manifestDoc, notesTxt, warnings, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) if err != nil { return nil, nil, err } @@ -250,6 +250,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin }, Version: revision, Manifest: manifestDoc.String(), + Warnings: warnings, Hooks: hooks, } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 00494f9d7..38deb347b 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -42,6 +42,21 @@ type Engine struct { LintMode bool // the rest config to connect to the kubernetes api config *rest.Config + + warnings *Warnings +} + +type Warning struct { + Message string +} + +type Warnings []Warning + +func (w *Warnings) append(warning Warning) { + if w == nil { + w = &Warnings{} + } + *w = append(*w, warning) } // Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. @@ -71,16 +86,26 @@ func (e Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]s // Render takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. func Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { - return new(Engine).Render(chrt, values) + return NewEngine().Render(chrt, values) } // RenderWithClient takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. This engine is client aware and so can have template // functions that interact with the client func RenderWithClient(chrt *chart.Chart, values chartutil.Values, config *rest.Config) (map[string]string, error) { + return NewEngineWithClient(config).Render(chrt, values) +} + +func NewEngineWithClient(config *rest.Config) Engine { + engine := NewEngine() + engine.config = config + return engine +} + +func NewEngine() Engine { return Engine{ - config: config, - }.Render(chrt, values) + warnings: &Warnings{}, + } } // renderable is an object that can be rendered. @@ -103,6 +128,13 @@ func warnWrap(warn string) string { return warnStartDelim + warn + warnEndDelim } +func (e Engine) GetWarnings() []Warning { + if e.warnings == nil { + panic("Use NewEngine or NewEngineWithClient to create Engine") + } + return *e.warnings +} + // initFunMap creates the Engine's FuncMap and adds context-specific functions. func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) { funcMap := funcMap() @@ -189,6 +221,17 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render funcMap["lookup"] = NewLookupFunction(e.config) } + funcMap["warn"] = func(msg string) string { + if e.warnings == nil { // Fallback if Engine was not created with constructor + fmt.Println("Warning:", msg) + } else { + e.warnings.append(Warning{ + Message: msg, + }) + } + return "" + } + t.Funcs(funcMap) } diff --git a/pkg/release/release.go b/pkg/release/release.go index b90612873..aa504e7e1 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -15,7 +15,10 @@ limitations under the License. package release -import "helm.sh/helm/v3/pkg/chart" +import ( + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/engine" +) // Release describes a deployment of a chart, together with the chart // and the variables used to deploy that chart. @@ -37,6 +40,8 @@ type Release struct { Version int `json:"version,omitempty"` // Namespace is the kubernetes namespace of the release. Namespace string `json:"namespace,omitempty"` + + Warnings []engine.Warning // Labels of the release. // Disabled encoding into Json cause labels are stored in storage driver metadata field. Labels map[string]string `json:"-"`