diff --git a/cmd/helm/install_test.go b/cmd/helm/install_test.go index 36de648f9..67a6eb64d 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -201,6 +201,12 @@ func TestInstall(t *testing.T) { name: "install chart with only crds", cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default", }, + // Install, base case with map and subcharts + { + name: "basic install with a map and subcharts", + cmd: "install aeneas testdata/testcharts/chart-with-map --namespace default", + golden: "output/install.txt", + }, } runTestActionCmd(t, tests) diff --git a/cmd/helm/testdata/testcharts/chart-with-map/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-map/Chart.yaml new file mode 100644 index 000000000..5820feeac --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-map/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +description: Chart with map and subcharts +name: chart-with-map +version: 0.0.1 +dependencies: + - name: subchart + version: 0.0.1 diff --git a/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/Chart.yaml new file mode 100644 index 000000000..d2c31040f --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: subchart +name: subchart +version: 0.0.1 diff --git a/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/map.yaml b/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/map.yaml new file mode 100644 index 000000000..0f2db07df --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/map.yaml @@ -0,0 +1 @@ +derived: {{ printf "%s-%s" .Values.default "a" }} diff --git a/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/templates/test1.yaml b/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/templates/test1.yaml new file mode 100644 index 000000000..6d32b25a1 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/templates/test1.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "aaa" + labels: + derived: {{ .Values.derived }} + default: {{ .Values.default }} diff --git a/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/values.yaml b/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/values.yaml new file mode 100644 index 000000000..110b50eb0 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-map/charts/subchart/values.yaml @@ -0,0 +1 @@ +default: "subchart-default" diff --git a/cmd/helm/testdata/testcharts/chart-with-map/map.yaml b/cmd/helm/testdata/testcharts/chart-with-map/map.yaml new file mode 100644 index 000000000..1e4f06510 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-map/map.yaml @@ -0,0 +1,4 @@ +derived: {{ printf "%s-%s" .Values.public1 "a" }} + +subchart: + default: "aa" diff --git a/cmd/helm/testdata/testcharts/chart-with-map/templates/test1.yaml b/cmd/helm/testdata/testcharts/chart-with-map/templates/test1.yaml new file mode 100644 index 000000000..89f228cd3 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-map/templates/test1.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "aaa" + labels: + derived: {{ .Values.derived }} + public1: {{ .Values.public1 }} diff --git a/cmd/helm/testdata/testcharts/chart-with-map/values.yaml b/cmd/helm/testdata/testcharts/chart-with-map/values.yaml new file mode 100644 index 000000000..a116e287d --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-map/values.yaml @@ -0,0 +1 @@ +public1: a diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index bd75375a4..2ff3ea520 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -42,6 +42,8 @@ type Chart struct { Templates []*File `json:"templates"` // Values are default config for this chart. Values map[string]interface{} `json:"values"` + // Map are default derivations of values of this chart. + Map *File `json:"map"` // Schema is an optional JSON schema for imposing structure on Values Schema []byte `json:"schema"` // Files are miscellaneous files in a chart archive, diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go index dd4fd2dff..402e3c9d4 100644 --- a/pkg/chart/loader/load.go +++ b/pkg/chart/loader/load.go @@ -99,6 +99,8 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { if err := yaml.Unmarshal(f.Data, &c.Values); err != nil { return c, errors.Wrap(err, "cannot load values.yaml") } + case f.Name == "map.yaml": + c.Map = &chart.File{Name: f.Name, Data: f.Data} case f.Name == "values.schema.json": c.Schema = f.Data diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 5aa0ed8ec..598240840 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -28,6 +28,7 @@ import ( "github.com/pkg/errors" "k8s.io/client-go/rest" + "sigs.k8s.io/yaml" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" @@ -351,6 +352,33 @@ func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil. next["Values"] = vs } + // apply the map's transformation and coalesce it with `next` + if c.Map != nil { + // render the map's template + mapTemplate := make(map[string]renderable) + mapTemplate["map"] = renderable{ + tpl: string(c.Map.Data), + vals: next, + basePath: c.ChartFullPath(), + } + // todo: pass conf to Engine here + val, err := Engine{}.render(mapTemplate) + if err != nil { + // todo + fmt.Println("ERRRRORRR:", err) + return + } + // construct the map values from the resulting yaml + mapped := map[string]interface{}{} + if err := yaml.Unmarshal([]byte(val["map"]), &mapped); err != nil { + // todo + fmt.Println("ERRRRORRR:", err) + return + } + // coalesce with preference to `mapped` + next["Values"] = chartutil.CoalesceTables(mapped, next["Values"].(chartutil.Values).AsMap()) + } + for _, child := range c.Dependencies() { recAllTpls(child, templates, next) } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 87e84c48b..d2cb91595 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -738,3 +738,50 @@ func TestRenderRecursionLimit(t *testing.T) { } } + +func TestRenderMap(t *testing.T) { + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "moby", + Version: "1.2.3", + }, + Templates: []*chart.File{ + {Name: "templates/test1", Data: []byte("{{.Values.outer | title }} {{.Values.inner | title}}")}, + {Name: "templates/test2", Data: []byte("{{toJson .Values}}")}, + }, + // maps "outer" + Map: &chart.File{ + Name: "map", + Data: []byte("outer: {{.Values.inner}}-a"), + }, + Values: map[string]interface{}{"inner": "DEFAULT"}, + } + + // no "outer" + vals := map[string]interface{}{ + "Values": chartutil.Values{ + "inner": "inn", + }, + } + + v, err := chartutil.CoalesceValues(c, vals) + if err != nil { + t.Fatalf("Failed to coalesce values: %s", err) + } + out, err := Render(c, v) + if err != nil { + t.Errorf("Failed to render templates: %s", err) + } + + // "outer" was mapped + expect := map[string]string{ + "moby/templates/test1": "Inn-A Inn", + "moby/templates/test2": `{"inner":"inn","outer":"inn-a"}`, + } + + for name, data := range expect { + if out[name] != data { + t.Errorf("Expected %q, got %q", data, out[name]) + } + } +}