Repository: helm/helm (go)

Issue:
`helm template --output-dir x --name release` renders to colliding path
Output of `helm version`: `Client: &version.Version{SemVer:"v2.13.1", GitCommit:"618447cbf203d147601b4b9bd7f8c37a5d39fbb4", GitTreeState:"clean"}`

Output of `kubectl version`: N/A

For example, say I want to render two releases of nginx, mine1 and mine2:
```
% helm template -n mine1 --output-dir out docs/examples/nginx
wrote out/nginx/templates/pre-install-secret.yaml
wrote out/nginx/templates/configmap.yaml
wrote out/nginx/templates/service.yaml
wrote out/nginx/templates/service-test.yaml
wrote out/nginx/templates/deployment.yaml
wrote out/nginx/templates/post-install-job.yaml
% helm template -n mine2 --output-dir out docs/examples/nginx
wrote out/nginx/templates/pre-install-secret.yaml
wrote out/nginx/templates/configmap.yaml
wrote out/nginx/templates/service.yaml
wrote out/nginx/templates/service-test.yaml
wrote out/nginx/templates/deployment.yaml
wrote out/nginx/templates/post-install-job.yaml
```
The second invocation clobbers the first. I would have expected `out/nginx/templates` to include the release name to avoid such a collision.



Pull request:
feat(template): Allow template output to use release name
`helm template --output-dir` command uses the chart name only when writing templates to disk. This causes collision in the path when  running the command for different releases of the same chart. This feature allows the release name to be used to avoid second invocation clobbering the previous output.

New path format: `outputDir/<releaseName>/<chart>/templates`

Closes #5664 

Patch:
diff --git a/cmd/helm/template.go b/cmd/helm/template.go
index dc62c6e95..36c029e69 100644
--- a/cmd/helm/template.go
+++ b/cmd/helm/template.go
@@ -137,6 +137,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 	f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output")
 	f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
 	f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
+	f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
 
 	return cmd
 }
diff --git a/pkg/action/install.go b/pkg/action/install.go
index 292a7ec27..80bd9c88f 100644
--- a/pkg/action/install.go
+++ b/pkg/action/install.go
@@ -89,6 +89,9 @@ type Install struct {
 	APIVersions chartutil.VersionSet
 	// Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false
 	IsUpgrade bool
+	// Used by helm template to add the release as part of OutputDir path
+	// OutputDir/<ReleaseName>
+	UseReleaseName bool
 }
 
 // ChartPathOptions captures common options used for controlling chart paths
@@ -217,7 +220,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.OutputDir, i.SubNotes)
+	rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName)
 	// Even for errors, attach this if available
 	if manifestDoc != nil {
 		rel.Manifest = manifestDoc.String()
@@ -421,7 +424,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, outputDir string, subNotes bool) ([]*release.Hook, *bytes.Buffer, string, error) {
+func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName string, outputDir string, subNotes, useReleaseName bool) ([]*release.Hook, *bytes.Buffer, string, error) {
 	hs := []*release.Hook{}
 	b := bytes.NewBuffer(nil)
 
@@ -498,7 +501,11 @@ 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(outputDir, m.Name, m.Content, fileWritten[m.Name])
+			newDir := outputDir
+			if useReleaseName {
+				newDir = filepath.Join(outputDir, releaseName)
+			}
+			err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name])
 			if err != nil {
 				return hs, b, "", err
 			}
diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go
index 1db4184ff..ad3a235e6 100644
--- a/pkg/action/upgrade.go
+++ b/pkg/action/upgrade.go
@@ -161,7 +161,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)
+	hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false)
 	if err != nil {
 		return nil, nil, err
 	}


Test patch:
diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go
index d6f1c88cd..ba350819d 100644
--- a/pkg/action/install_test.go
+++ b/pkg/action/install_test.go
@@ -471,6 +471,46 @@ func TestInstallReleaseOutputDir(t *testing.T) {
 	is.True(os.IsNotExist(err))
 }
 
+func TestInstallOutputDirWithReleaseName(t *testing.T) {
+	is := assert.New(t)
+	instAction := installAction(t)
+	vals := map[string]interface{}{}
+
+	dir, err := ioutil.TempDir("", "output-dir")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	instAction.OutputDir = dir
+	instAction.UseReleaseName = true
+	instAction.ReleaseName = "madra"
+
+	newDir := filepath.Join(dir, instAction.ReleaseName)
+
+	_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
+	if err != nil {
+		t.Fatalf("Failed install: %s", err)
+	}
+
+	_, err = os.Stat(filepath.Join(newDir, "hello/templates/goodbye"))
+	is.NoError(err)
+
+	_, err = os.Stat(filepath.Join(newDir, "hello/templates/hello"))
+	is.NoError(err)
+
+	_, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials"))
+	is.NoError(err)
+
+	_, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac"))
+	is.NoError(err)
+
+	test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt")
+
+	_, err = os.Stat(filepath.Join(newDir, "hello/templates/empty"))
+	is.True(os.IsNotExist(err))
+}
+
 func TestNameAndChart(t *testing.T) {
 	is := assert.New(t)
 	instAction := installAction(t)