diff --git a/cmd/helm/template.go b/cmd/helm/template.go index a4438b50c..0ce2524bc 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -20,6 +20,8 @@ import ( "bytes" "fmt" "io" + "os" + "path" "path/filepath" "regexp" "sort" @@ -35,6 +37,8 @@ import ( "helm.sh/helm/v3/pkg/releaseutil" ) +const defaultDirectoryPermission = 0755 + const templateDesc = ` Render chart templates locally and display the output. @@ -77,10 +81,50 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if rel != nil { var manifests bytes.Buffer fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)) - - if !client.DisableHooks { - for _, m := range rel.Hooks { - fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) + newDir := client.OutputDir + if newDir != "" { + // Aggregate all valid manifests into one big doc. + fileWritten := make(map[string]bool) + if client.UseReleaseName { + newDir = filepath.Join(client.OutputDir, client.ReleaseName) + } + if client.IncludeCRDs { + for _, crd := range rel.Chart.CRDObjects() { + err = writeToFile(newDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name]) + if err != nil { + return err + } + fileWritten[crd.Name] = true + } + } + if rel.Manifest != "" { + splitManifests := releaseutil.SplitManifests(rel.Manifest) + for _, v := range splitManifests { + name, content, err := parseManifest(v) + if err != nil { + return err + } + err = writeToFile(newDir, name, content, fileWritten[name]) + if err != nil { + return err + } + fileWritten[name] = true + } + } + if !client.DisableHooks { + for _, m := range rel.Hooks { + err := writeToFile(newDir, m.Path, m.Manifest, false) + if err != nil { + return err + } + } + } + manifests.Reset() + } else { + if !client.DisableHooks { + for _, m := range rel.Hooks { + fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) + } } } @@ -154,3 +198,58 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return cmd } + +// write the to /. controls if the file is created or content will be appended +func writeToFile(outputDir string, name string, data string, append bool) error { + outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) + + err := ensureDirectoryForFile(outfileName) + if err != nil { + return err + } + + f, err := createOrOpenFile(outfileName, append) + if err != nil { + return err + } + + defer f.Close() + + _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) + + if err != nil { + return err + } + + fmt.Printf("wrote %s\n", outfileName) + return nil +} + +func createOrOpenFile(filename string, append bool) (*os.File, error) { + if append { + return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) + } + return os.Create(filename) +} + +// check if the directory exists to create file. creates if don't exists +func ensureDirectoryForFile(file string) error { + baseDir := path.Dir(file) + _, err := os.Stat(baseDir) + if err != nil && !os.IsNotExist(err) { + return err + } + + return os.MkdirAll(baseDir, defaultDirectoryPermission) +} + +// parseManifest parse manifest string and return name and content +func parseManifest(manifest string) (string, string, error) { + bs := bytes.NewBufferString(manifest) + fl, err := bs.ReadBytes('\n') + if err != nil { + return "", "", err + } + name := strings.TrimPrefix(string(fl[:len(fl)-1]), "# Source: ") + return name, bs.String(), nil +} diff --git a/pkg/action/install.go b/pkg/action/install.go index 4b4dd9214..7260b1184 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -64,8 +64,6 @@ const releaseNameMaxLen = 53 // since there can be filepath in front of it. const notesFileSuffix = "NOTES.txt" -const defaultDirectoryPermission = 0755 - // Install performs an installation operation. type Install struct { cfg *Configuration @@ -232,7 +230,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. 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) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.SubNotes, i.IncludeCRDs, i.PostRenderer) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() @@ -476,7 +474,7 @@ func (i *Install) replaceRelease(rel *release.Release) error { } // renderResources renders the templates in a chart -func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer) ([]*release.Hook, *bytes.Buffer, string, error) { +func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, subNotes, includeCrds bool, pr postrender.PostRenderer) ([]*release.Hook, *bytes.Buffer, string, error) { hs := []*release.Hook{} b := bytes.NewBuffer(nil) @@ -547,41 +545,14 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values return hs, b, "", err } - // Aggregate all valid manifests into one big doc. - fileWritten := make(map[string]bool) - if includeCrds { for _, crd := range ch.CRDObjects() { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:])) - } else { - err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name]) - if err != nil { - return hs, b, "", err - } - fileWritten[crd.Name] = true - } + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:])) } } for _, m := range manifests { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) - } else { - newDir := outputDir - if useReleaseName { - newDir = filepath.Join(outputDir, releaseName) - } - // NOTE: We do not have to worry about the post-renderer because - // output dir is only used by `helm template`. In the next major - // release, we should move this logic to template only as it is not - // used by install or upgrade - err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) - if err != nil { - return hs, b, "", err - } - fileWritten[m.Name] = true - } + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) } if pr != nil { @@ -594,50 +565,6 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values return hs, b, notes, nil } -// write the to /. controls if the file is created or content will be appended -func writeToFile(outputDir string, name string, data string, append bool) error { - outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) - - err := ensureDirectoryForFile(outfileName) - if err != nil { - return err - } - - f, err := createOrOpenFile(outfileName, append) - if err != nil { - return err - } - - defer f.Close() - - _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) - - if err != nil { - return err - } - - fmt.Printf("wrote %s\n", outfileName) - return nil -} - -func createOrOpenFile(filename string, append bool) (*os.File, error) { - if append { - return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) - } - return os.Create(filename) -} - -// check if the directory exists to create file. creates if don't exists -func ensureDirectoryForFile(file string) error { - baseDir := path.Dir(file) - _, err := os.Stat(baseDir) - if err != nil && !os.IsNotExist(err) { - return err - } - - return os.MkdirAll(baseDir, defaultDirectoryPermission) -} - // NameAndChart returns the name and chart that should be used. // // This will read the flags and handle name generation if necessary. diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index c8e71c6d4..0e780103e 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -170,7 +170,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) + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, u.SubNotes, false, u.PostRenderer) if err != nil { return nil, nil, err }