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..1a463e64e 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(outputDir, 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/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)) +} 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 }