fix(template): fix the bug when helm template with flag --output-dir

Issue:helm template with --output-dir doesn't write template with a hook to
file

Code Detail:
1. move the writeToFile function from action/install to helm/template
2. add function parseManifest to parse the Release's field Manifest.

Close #7836

Signed-off-by: Dong Gang <dong.gang@daocloud.io>
pull/7863/head
Dong Gang 6 years ago
parent c12a9aee02
commit 07a5e8389d

@ -20,6 +20,8 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"os"
"path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort" "sort"
@ -35,6 +37,8 @@ import (
"helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/releaseutil"
) )
const defaultDirectoryPermission = 0755
const templateDesc = ` const templateDesc = `
Render chart templates locally and display the output. 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 { if rel != nil {
var manifests bytes.Buffer var manifests bytes.Buffer
fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)) fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest))
newDir := client.OutputDir
if !client.DisableHooks { if newDir != "" {
for _, m := range rel.Hooks { // Aggregate all valid manifests into one big doc.
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) 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 return cmd
} }
// write the <data> to <output-dir>/<name>. <append> 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
}

@ -64,8 +64,6 @@ const releaseNameMaxLen = 53
// since there can be filepath in front of it. // since there can be filepath in front of it.
const notesFileSuffix = "NOTES.txt" const notesFileSuffix = "NOTES.txt"
const defaultDirectoryPermission = 0755
// Install performs an installation operation. // Install performs an installation operation.
type Install struct { type Install struct {
cfg *Configuration cfg *Configuration
@ -232,7 +230,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
rel := i.createRelease(chrt, vals) rel := i.createRelease(chrt, vals)
var manifestDoc *bytes.Buffer 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 // Even for errors, attach this if available
if manifestDoc != nil { if manifestDoc != nil {
rel.Manifest = manifestDoc.String() rel.Manifest = manifestDoc.String()
@ -476,7 +474,7 @@ func (i *Install) replaceRelease(rel *release.Release) error {
} }
// renderResources renders the templates in a chart // 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{} hs := []*release.Hook{}
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
@ -547,41 +545,14 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values
return hs, b, "", err return hs, b, "", err
} }
// Aggregate all valid manifests into one big doc.
fileWritten := make(map[string]bool)
if includeCrds { if includeCrds {
for _, crd := range ch.CRDObjects() { for _, crd := range ch.CRDObjects() {
if outputDir == "" { fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:]))
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
}
} }
} }
for _, m := range manifests { for _, m := range manifests {
if outputDir == "" { fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content)
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
}
} }
if pr != nil { if pr != nil {
@ -594,50 +565,6 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values
return hs, b, notes, nil return hs, b, notes, nil
} }
// write the <data> to <output-dir>/<name>. <append> 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. // NameAndChart returns the name and chart that should be used.
// //
// This will read the flags and handle name generation if necessary. // This will read the flags and handle name generation if necessary.

@ -170,7 +170,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
return nil, nil, err 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }

Loading…
Cancel
Save