Repository: helm/helm (go)

Issue:
Usable sub-charts templates from parent chart
~~Templates are global are recursively namespaced~~ Named templates are global, so the guideline is to namespace them with the chart name to avoid collision.

This leads to the natural desire to use sub-charts templates from parent charts. But it doesn't work because `$.Charts` refers to the current chart (maybe `$.Templates` too? and others?), and I haven't found a way to reconstruct a context equal to the one used in the sub-chart.

# Use-case
My use-case is to get the service name of a sub-chart.

- chart `A` has sub-chart `postgresql`
- would like to use `{{ template "postgresql.fullname <some_context> }}` to know its Service name, to use postgresql.

`<some_context>` could be something like `(subchartContext "postgresql")` (or `(subchartContext "A.postgresql")`) (and support recursion)

# Workaround
The current workaround is to ~duplicate the template definition in the parent chart, which is not future-proof: the sub-chart internal implementation becomes a public API and cannot be easily changed, and if it happens the error is unclear.

I found workaround in official charts (https://github.com/kubernetes/charts), but not in the helm documentation; it could be documented. 
**UPDATE**:
Example: 
`stable/gitlab-ce` depends on `postgresql` and redefines `gitlab-ce.postgresql.fullname` by copying `postgresql.fullname` and adapting it (replacing `.Chart` by hardcoded values)
https://github.com/kubernetes/charts/blob/78c4b0163065699f4be8de1e795f4cbae74a98db/stable/gitlab-ce/templates/_helpers.tpl#L18-L24
It has already diverged from its source:  
https://github.com/kubernetes/charts/blob/78c4b0163065699f4be8de1e795f4cbae74a98db/stable/postgresql/templates/_helpers.tpl#L9-L25

Some existing discussion: 
- https://github.com/kubernetes/charts/issues/44#issuecomment-284420598
- https://stackoverflow.com/questions/47757968/how-to-reference-a-value-defined-in-a-template-in-a-sub-chart-in-helm-for-kubern

The larger use-case is to easily use sub-charts: 
instantiate sub-charts, use them from parent chart, make 2 sub-charts talk to each other.

- instantiate sub-chart: OK
- get resource names of the sub-chart from the parent chart template (Service, ConfigMap, Secret): 
  - workaround somewhat OK by duplicating sub-chart templates
  - longer term solution would require sub-chart explicitly declaring its API: 
    - expose rendered values for resources names
    - and some other values (like a resource name prefix for example, or a config map key value to avoid forcing k8s runtime evaluation)
- set values of sub-chart from parent chart *using templates*. 
Formulated like that it's obvious it doesn't make sense, but the use-case is real
  - chart A has 2 sub-charts: B and C; B exposes a service, and C should use it
  - one way would be in A to render the right B template to get the B service name, and use that to set as value in C.
  - I don't really have a current workaround, except delaying linking B and C to k8s runtime by storing everthing in ConfigMaps and adapt C to get the service value from ConfigMap instead of from its values. This is not great when B and C are external charts (e.g. from https://github.com/kubernetes/charts).

Maybe I should open another issue for the larger use-case.

Pull request:
feat(pkg/engine): expose subcharts scope in parent
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:
It expose the scope (.Values, .Charts, .Releases etc.) of subcharts to the parent in `.Subcharts.<subchart_name>`.
This is a proposal to fix #3920.

With a chart containing a sub-chart named "foo" and defining a template "bar.fullname", you could use this template in the parent as follows : `{{ template "bar.fullname" .Subcharts.foo }}`. The template would be rendered with the same scope/context as the subchart.

refs #3920

**Special notes for your reviewer**:

**If applicable**:
- [ ] this PR contains documentation
- [X] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility


Patch:
diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go
index c39aebdb4..da6263cd8 100644
--- a/pkg/engine/engine.go
+++ b/pkg/engine/engine.go
@@ -260,6 +260,7 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable)
 		if err := t.ExecuteTemplate(&buf, filename, vals); err != nil {
 			return map[string]string{}, cleanupExecError(filename, err)
 		}
+		delete(vals, "Template")
 
 		// Work around the issue where Go will emit "<no value>" even if Options(missing=zero)
 		// is set. Since missing=error will never get here, we do not need to handle
@@ -344,7 +345,8 @@ func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable {
 //
 // As it recurses, it also sets the values to be appropriate for the template
 // scope.
-func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.Values) {
+func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.Values) map[string]interface{} {
+	subCharts := make(map[string]interface{})
 	chartMetaData := struct {
 		chart.Metadata
 		IsRoot bool
@@ -356,6 +358,7 @@ func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.
 		"Release":      vals["Release"],
 		"Capabilities": vals["Capabilities"],
 		"Values":       make(chartutil.Values),
+		"Subcharts":    subCharts,
 	}
 
 	// If there is a {{.Values.ThisChart}} in the parent metadata,
@@ -367,7 +370,7 @@ func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.
 	}
 
 	for _, child := range c.Dependencies() {
-		recAllTpls(child, templates, next)
+		subCharts[child.Name()] = recAllTpls(child, templates, next)
 	}
 
 	newParentID := c.ChartFullPath()
@@ -381,6 +384,8 @@ func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.
 			basePath: path.Join(newParentID, "templates"),
 		}
 	}
+
+	return next
 }
 
 // isTemplateValid returns true if the template is valid for the chart type


Test patch:
diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go
index a07cab4f5..54cd21ae2 100644
--- a/pkg/engine/engine_test.go
+++ b/pkg/engine/engine_test.go
@@ -435,6 +435,8 @@ func TestRenderNestedValues(t *testing.T) {
 	// Ensure namespacing rules are working.
 	deepestpath := "templates/inner.tpl"
 	checkrelease := "templates/release.tpl"
+	// Ensure subcharts scopes are working.
+	subchartspath := "templates/subcharts.tpl"
 
 	deepest := &chart.Chart{
 		Metadata: &chart.Metadata{Name: "deepest"},
@@ -442,7 +444,7 @@ func TestRenderNestedValues(t *testing.T) {
 			{Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)},
 			{Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)},
 		},
-		Values: map[string]interface{}{"what": "milkshake"},
+		Values: map[string]interface{}{"what": "milkshake", "where": "here"},
 	}
 
 	inner := &chart.Chart{
@@ -450,7 +452,7 @@ func TestRenderNestedValues(t *testing.T) {
 		Templates: []*chart.File{
 			{Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)},
 		},
-		Values: map[string]interface{}{"who": "Robert"},
+		Values: map[string]interface{}{"who": "Robert", "what": "glasses"},
 	}
 	inner.AddDependency(deepest)
 
@@ -458,12 +460,14 @@ func TestRenderNestedValues(t *testing.T) {
 		Metadata: &chart.Metadata{Name: "top"},
 		Templates: []*chart.File{
 			{Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)},
+			{Name: subchartspath, Data: []byte(`The glorious Lamp of {{.Subcharts.herrick.Subcharts.deepest.Values.where}}, the {{.Subcharts.herrick.Values.what}}`)},
 		},
 		Values: map[string]interface{}{
 			"what": "stinkweed",
 			"who":  "me",
 			"herrick": map[string]interface{}{
-				"who": "time",
+				"who":  "time",
+				"what": "Sun",
 			},
 		},
 	}
@@ -473,7 +477,8 @@ func TestRenderNestedValues(t *testing.T) {
 		"what": "rosebuds",
 		"herrick": map[string]interface{}{
 			"deepest": map[string]interface{}{
-				"what": "flower",
+				"what":  "flower",
+				"where": "Heaven",
 			},
 		},
 		"global": map[string]interface{}{
@@ -520,6 +525,11 @@ func TestRenderNestedValues(t *testing.T) {
 	if out[fullcheckrelease] != "Tomorrow will be dyin" {
 		t.Errorf("Unexpected release: %q", out[fullcheckrelease])
 	}
+
+	fullchecksubcharts := "top/" + subchartspath
+	if out[fullchecksubcharts] != "The glorious Lamp of Heaven, the Sun" {
+		t.Errorf("Unexpected subcharts: %q", out[fullchecksubcharts])
+	}
 }
 
 func TestRenderBuiltinValues(t *testing.T) {
@@ -539,6 +549,7 @@ func TestRenderBuiltinValues(t *testing.T) {
 		Metadata: &chart.Metadata{Name: "Troy"},
 		Templates: []*chart.File{
 			{Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
+			{Name: "templates/Amata", Data: []byte(`{{.Subcharts.Latium.Chart.Name}} {{.Subcharts.Latium.Files.author | printf "%s"}}`)},
 		},
 	}
 	outer.AddDependency(inner)
@@ -561,6 +572,7 @@ func TestRenderBuiltinValues(t *testing.T) {
 	expects := map[string]string{
 		"Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid",
 		"Troy/templates/Aeneas":                "Troy/templates/AeneasTroyAeneid",
+		"Troy/templates/Amata":                 "Latium Virgil",
 		"Troy/charts/Latium/templates/From":    "Virgil Aeneid",
 	}
 	for file, expect := range expects {