diff --git a/pkg/chart/dependency.go b/pkg/chart/dependency.go index eda0f5a89..f532fa642 100644 --- a/pkg/chart/dependency.go +++ b/pkg/chart/dependency.go @@ -17,6 +17,11 @@ package chart import "time" +type PostRendererOptions struct { + Command string `json:"command" yaml:"command"` + Args []string `json:"args,omitempty" yaml:"args,omitempty"` +} + // Dependency describes a chart upon which another chart depends. // // Dependencies can be used to express developer intent, or to capture the state @@ -47,6 +52,8 @@ type Dependency struct { ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"` // Alias usable alias to be used for the chart Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` + // A post rendering operation that will be applied to the rendered outputs of this dependency + PostRenderer *PostRendererOptions `json:"postRenderer,omitempty" yaml:"postRenderer,omitempty"` } // Validate checks for common problems with the dependency datastructure in diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index d8ee313e1..112977401 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -17,6 +17,7 @@ limitations under the License. package engine import ( + "bytes" "fmt" "log" "path" @@ -31,6 +32,7 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/postrender" ) // Engine is an implementation of the Helm rendering implementation for templates. @@ -75,7 +77,11 @@ func New(config *rest.Config) Engine { // bar chart during render time. func (e Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { tmap := allTemplates(chrt, values) - return e.render(tmap) + rendered, err := e.render(tmap) + if err != nil { + return nil, err + } + return postRenderDependency(nil, chrt, rendered) } // Render takes a chart, optional values, and value overrides, and attempts to @@ -307,6 +313,77 @@ func (e Engine) render(tpls map[string]renderable) (rendered map[string]string, return rendered, nil } +func postRenderDependency(parent *chart.Chart, chrt *chart.Chart, rendered map[string]string) (map[string]string, error) { + for _, c := range chrt.Dependencies() { + _, err := postRenderDependency(chrt, c, rendered) + + if err != nil { + return nil, err + } + } + + // Skip root chart since it has it's own post-render mechanism + if chrt.IsRoot() || parent == nil { + return rendered, nil + } + + var postRenderOptions *chart.PostRendererOptions + + for _, depMetadata := range parent.Metadata.Dependencies { + if depMetadata.Name == chrt.Name() { + postRenderOptions = depMetadata.PostRenderer + break + } + } + + if postRenderOptions == nil { + return rendered, nil + } + + postRenderer, err := postrender.NewExec(postRenderOptions.Command, postRenderOptions.Args...) + + if err != nil { + errorMessage := err.Error() + if strings.HasPrefix(errorMessage, "unable to find binary at ") { + log.Printf("[INFO] Skipping dependency post render operation because: %s", errorMessage) + + return rendered, nil + } + + return nil, err + } + + fullChartPath := chrt.ChartFullPath() + + for k, renderedContent := range rendered { + if len(strings.TrimSpace(renderedContent)) == 0 || strings.HasSuffix(k, "/templates/NOTES.txt") { + continue + } + + if strings.HasPrefix(k, fullChartPath+"/templates/") { + result, err := postRender(postRenderer, renderedContent) + + if err != nil { + return nil, err + } + + rendered[k] = result + } + } + + return rendered, nil +} + +func postRender(postRenderer postrender.PostRenderer, renderedContent string) (string, error) { + var buffer bytes.Buffer + buffer.WriteString(renderedContent) + newBuffer, err := postRenderer.Run(&buffer) + if err != nil { + return "", err + } + return newBuffer.String(), nil +} + func cleanupParseError(filename string, err error) error { tokens := strings.Split(err.Error(), ": ") if len(tokens) == 1 { diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index de1896a47..dc48aa3da 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -18,12 +18,14 @@ package engine import ( "fmt" + "os" "path" "strings" "sync" "testing" "text/template" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -1300,3 +1302,76 @@ func TestRenderTplMissingKeyString(t *testing.T) { t.Fatal(err) } } + +func TestRenderDependencyPostRenderer(t *testing.T) { + dep := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + }, + Templates: []*chart.File{ + {Name: "templates/foo1", Data: []byte("value: {{ add .Values.bar 2 }}\n")}, + {Name: "templates/NOTES.txt", Data: []byte("SOME TEXT")}, + {Name: "templates/empty", Data: []byte("")}, + }, + Values: map[string]interface{}{}, + } + + cwd, _ := os.Getwd() + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "moby", + Version: "1.2.3", + Dependencies: []*chart.Dependency{ + { + Name: "foo", + PostRenderer: &chart.PostRendererOptions{ + Command: cwd + "/../../testdata/postrender.sh", + }, + }, + }, + }, + Templates: []*chart.File{ + {Name: "templates/test1", Data: []byte("name: {{ .Chart.Name }}")}, + }, + Values: map[string]interface{}{}, + } + + c.SetDependencies(dep) + + vals := map[string]interface{}{ + "Values": map[string]any{ + "foo": map[string]any{ + "bar": 3, + }, + }, + } + + v, err := chartutil.CoalesceValues(c, vals) + if err != nil { + t.Fatalf("Failed to coalesce values: %s", err) + } + + var e Engine + out, err := e.Render(c, v) + if err != nil { + t.Errorf("Failed to render templates: %s", err) + return + } + + assert.Equal(t, len(out), 4) + + expected := "name: moby" + + fp := path.Join(c.ChartFullPath(), "templates/test1") + if out[fp] != expected { + t.Errorf("Expected %q, got %q", expected, out[fp]) + } + + expected = "value: 25\n" + + fp = path.Join(dep.ChartFullPath(), "templates/foo1") + if out[fp] != expected { + t.Errorf("Expected %q, got %q", expected, out[fp]) + } +} diff --git a/testdata/postrender.sh b/testdata/postrender.sh new file mode 100755 index 000000000..119aeb5f0 --- /dev/null +++ b/testdata/postrender.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +s=$(tee | sed 's/5/25/') + +echo "${s}" \ No newline at end of file