From 24578a6411d58e59df8ca633c88d2828652b47a1 Mon Sep 17 00:00:00 2001 From: Torsten Walter Date: Mon, 20 May 2019 10:46:42 +0200 Subject: [PATCH 1/5] support --output-dir option for helm3 template Signed-off-by: Torsten Walter --- cmd/helm/template.go | 6 +++++ pkg/action/install.go | 53 ++++++++++++++++++++++++++++++++++++++++--- pkg/action/upgrade.go | 2 +- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 5dd723abe..aa61d0e43 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/spf13/pflag" "helm.sh/helm/cmd/helm/require" "helm.sh/helm/pkg/action" @@ -77,6 +78,11 @@ func newTemplateCmd(out io.Writer) *cobra.Command { } addInstallFlags(cmd.Flags(), client) + addTemplateFlags(cmd.Flags(), client) return cmd } + +func addTemplateFlags(f *pflag.FlagSet, client *action.Install) { + f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") +} diff --git a/pkg/action/install.go b/pkg/action/install.go index 33cd6b53a..2722d31bd 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -61,6 +61,8 @@ 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 @@ -79,6 +81,7 @@ type Install struct { ReleaseName string GenerateName bool NameTemplate string + OutputDir string } type ValueOptions struct { @@ -132,7 +135,7 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) { rel := i.createRelease(chrt, i.rawValues) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() @@ -305,7 +308,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) ([]*release.Hook, *bytes.Buffer, string, error) { +func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, outputDir string) ([]*release.Hook, *bytes.Buffer, string, error) { hs := []*release.Hook{} b := bytes.NewBuffer(nil) @@ -363,12 +366,56 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values // Aggregate all valid manifests into one big doc. for _, m := range manifests { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) + if outputDir == "" { + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) + } else { + err = writeToFile("build", m.Name, m.Content) + if err != nil { + return hs, b, "", err + } + } } return hs, b, notes, nil } +// write the to / +func writeToFile(outputDir string, name string, data string) error { + outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) + + err := ensureDirectoryForFile(outfileName) + if err != nil { + return err + } + + f, err := os.Create(outfileName) + if err != nil { + return err + } + + defer f.Close() + + _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s", name, data)) + + if err != nil { + return err + } + + fmt.Printf("wrote %s\n", outfileName) + return nil +} + +// 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) +} + // validateManifest checks to see whether the given manifest is valid for the current Kubernetes func (i *Install) validateManifest(manifest io.Reader) error { _, err := i.cfg.KubeClient.BuildUnstructured(manifest) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index eddacc6ea..7e79d3971 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -159,7 +159,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart) (*release.Rele return nil, nil, err } - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender) + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "") if err != nil { return nil, nil, err } From cb58035f90d79f2e34a29ebe8201339a86441585 Mon Sep 17 00:00:00 2001 From: Torsten Walter Date: Tue, 28 May 2019 17:05:29 +0200 Subject: [PATCH 2/5] use outputDir instead of hardcoded value Signed-off-by: Torsten Walter --- pkg/action/install.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 2722d31bd..1a463e64e 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -369,7 +369,7 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values if outputDir == "" { fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) } else { - err = writeToFile("build", m.Name, m.Content) + err = writeToFile(outputDir, m.Name, m.Content) if err != nil { return hs, b, "", err } From da260ec15fc4eba4a3cbe0791bd05b410bf75766 Mon Sep 17 00:00:00 2001 From: Torsten Walter Date: Tue, 28 May 2019 17:21:26 +0200 Subject: [PATCH 3/5] do not write empty templates to disk Signed-off-by: Torsten Walter --- pkg/action/install.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/action/install.go b/pkg/action/install.go index 1a463e64e..e21de0c91 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -25,6 +25,7 @@ import ( "os" "path" "path/filepath" + "regexp" "sort" "strings" "text/template" @@ -63,6 +64,8 @@ const notesFileSuffix = "NOTES.txt" const defaultDirectoryPermission = 0755 +var whitespaceRegex = regexp.MustCompile(`^\s*$`) + // Install performs an installation operation. type Install struct { cfg *Configuration @@ -369,6 +372,10 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values if outputDir == "" { fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) } else { + // blank template after execution + if whitespaceRegex.MatchString(m.Content) { + continue + } err = writeToFile(outputDir, m.Name, m.Content) if err != nil { return hs, b, "", err From 0528c7bb19daec796167d3a9705b2ac20543e8bd Mon Sep 17 00:00:00 2001 From: Torsten Walter Date: Mon, 3 Jun 2019 12:43:00 +0200 Subject: [PATCH 4/5] add test for output-dir Signed-off-by: Torsten Walter --- pkg/action/install_test.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 063be75a0..12f9bb3b2 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -18,6 +18,10 @@ package action import ( "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" "reflect" "regexp" "testing" @@ -354,3 +358,34 @@ func TestMergeValues(t *testing.T) { t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) } } + +func TestInstallReleaseOutputDir(t *testing.T) { + is := assert.New(t) + instAction := installAction(t) + instAction.rawValues = map[string]interface{}{} + + dir, err := ioutil.TempDir("", "output-dir") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(dir) + + instAction.OutputDir = dir + + _, err = instAction.Run(buildChart(withSampleTemplates())) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + + _, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye")) + is.NoError(err) + + _, err = os.Stat(filepath.Join(dir, "hello/templates/hello")) + is.NoError(err) + + _, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials")) + is.NoError(err) + + _, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) + is.True(os.IsNotExist(err)) +} From 829d8ff4d6b817c50ac5a2ede4b8b492f8cf4ee8 Mon Sep 17 00:00:00 2001 From: Torsten Walter Date: Mon, 3 Jun 2019 13:08:21 +0200 Subject: [PATCH 5/5] Revert "do not write empty templates to disk" This reverts commit da260ec15fc4eba4a3cbe0791bd05b410bf75766. Signed-off-by: Torsten Walter --- pkg/action/install.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index e21de0c91..1a463e64e 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -25,7 +25,6 @@ import ( "os" "path" "path/filepath" - "regexp" "sort" "strings" "text/template" @@ -64,8 +63,6 @@ const notesFileSuffix = "NOTES.txt" const defaultDirectoryPermission = 0755 -var whitespaceRegex = regexp.MustCompile(`^\s*$`) - // Install performs an installation operation. type Install struct { cfg *Configuration @@ -372,10 +369,6 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values if outputDir == "" { fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) } else { - // blank template after execution - if whitespaceRegex.MatchString(m.Content) { - continue - } err = writeToFile(outputDir, m.Name, m.Content) if err != nil { return hs, b, "", err