From 5451cffce9aea37a5cb525a6364d03ad772d6c1d Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Tue, 20 Feb 2024 06:11:37 +0000 Subject: [PATCH] override helm.sh/helm/v3/pkg/engine.recAllTpls to support values `.TeaChart` --- cmd/teachart/install.go | 2 +- cmd/teachart/lint.go | 2 +- cmd/teachart/template.go | 2 +- pkg/engine/engine.go | 13 ++- pkg/engine/engine_test.go | 9 +- pkg/engine/{ => helm}/helm.go | 40 +++------ pkg/engine/helm/render.go | 97 +++++++++++++++++++++ pkg/options/global.go | 6 +- pkg/{engine => values}/values.go | 7 +- tests/data/chart/templates/test_values.yaml | 14 +-- 10 files changed, 137 insertions(+), 55 deletions(-) rename pkg/engine/{ => helm}/helm.go (73%) create mode 100644 pkg/engine/helm/render.go rename pkg/{engine => values}/values.go (52%) diff --git a/cmd/teachart/install.go b/cmd/teachart/install.go index 698356c..a0949f5 100644 --- a/cmd/teachart/install.go +++ b/cmd/teachart/install.go @@ -139,7 +139,7 @@ func NewInstallCmd(ctx context.Context, globalOptions *options.GlobalOptions) *c if err != nil { return errors.Wrap(err, "Create compose client error") } - renderEngine, err := engine.NewRenderEngine(opts.GetChartDir(), opts.GetTeaChart(), nil) + renderEngine, err := engine.NewRenderEngine(opts.GetChartDir(), opts.GetTeaChart(), false) if err != nil { return errors.Wrap(err, "Create helm engine error") } diff --git a/cmd/teachart/lint.go b/cmd/teachart/lint.go index d7f6b2d..ea64de2 100644 --- a/cmd/teachart/lint.go +++ b/cmd/teachart/lint.go @@ -88,7 +88,7 @@ func runLint(ctx context.Context, cmd *cobra.Command, opts lintOptions) error { logrus.Debugf("Temp directory created:%s", tempDir) // render templates - renderEngine, err := engine.NewRenderEngine(chartDir, opts.GetTeaChart(), &engine.NewEngineOptions{Strict: true}) + renderEngine, err := engine.NewRenderEngine(chartDir, opts.GetTeaChart(), true) if err != nil { return nil, errors.Wrap(err, "Create helm engine error") } diff --git a/cmd/teachart/template.go b/cmd/teachart/template.go index 01e7e45..80d9132 100644 --- a/cmd/teachart/template.go +++ b/cmd/teachart/template.go @@ -77,7 +77,7 @@ func NewTemplateCmd(ctx context.Context, globalOptions *options.GlobalOptions) * } func runTemplate(ctx context.Context, cmd *cobra.Command, opts templateOptions) error { - renderEngine, err := engine.NewRenderEngine(opts.GetChartDir(), opts.GetTeaChart(), nil) + renderEngine, err := engine.NewRenderEngine(opts.GetChartDir(), opts.GetTeaChart(), false) if err != nil { return errors.Wrap(err, "Create helm engine error") } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 62b0d26..66f15e3 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -3,17 +3,16 @@ package engine import ( - "helm.sh/helm/v3/pkg/cli/values" + "github.com/yp05327/teachart/pkg/engine/helm" + "github.com/yp05327/teachart/pkg/values" + helm_values "helm.sh/helm/v3/pkg/cli/values" ) -type NewEngineOptions struct { - Strict bool -} type RenderEngine interface { - Render(valueOpts values.Options, save bool) (map[string]string, error) + Render(valueOpts helm_values.Options, save bool) (map[string]string, error) GetConfigPaths(files map[string]string) []string } -func NewRenderEngine(chartDir string, teachart *TeaChart, opts *NewEngineOptions) (RenderEngine, error) { - return newHelm(chartDir, teachart, opts) +func NewRenderEngine(chartDir string, teachart *values.TeaChart, strict bool) (RenderEngine, error) { + return helm.New(chartDir, teachart, strict) } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 23c2f2d..16e037c 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -5,23 +5,24 @@ import ( "github.com/stretchr/testify/assert" "github.com/yp05327/teachart/pkg/app" - "helm.sh/helm/v3/pkg/cli/values" + "github.com/yp05327/teachart/pkg/values" + helm_values "helm.sh/helm/v3/pkg/cli/values" ) const testChartDir = "../../tests/data/chart" const testValuesFile = "../../tests/data/values.yaml" -var testTeaChart = TeaChart{ +var testTeaChart = values.TeaChart{ ProjectName: "test", ProjectDir: "", TempDir: app.DefaultTemplatesDir, } func TestHelmRender(t *testing.T) { - e, err := NewRenderEngine(testChartDir, &testTeaChart, &NewEngineOptions{Strict: true}) + e, err := NewRenderEngine(testChartDir, &testTeaChart, true) assert.NoError(t, err) - _, err = e.Render(values.Options{ + _, err = e.Render(helm_values.Options{ ValueFiles: []string{testValuesFile}, }, false) assert.NoError(t, err) diff --git a/pkg/engine/helm.go b/pkg/engine/helm/helm.go similarity index 73% rename from pkg/engine/helm.go rename to pkg/engine/helm/helm.go index 5941e9a..712bb06 100644 --- a/pkg/engine/helm.go +++ b/pkg/engine/helm/helm.go @@ -1,6 +1,6 @@ // Copyright © 2024 TeaChart Authors -package engine +package helm import ( "fmt" @@ -11,10 +11,11 @@ import ( "strings" "github.com/yp05327/teachart/pkg/app" + "github.com/yp05327/teachart/pkg/values" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli/values" + helm_values "helm.sh/helm/v3/pkg/cli/values" helm_engine "helm.sh/helm/v3/pkg/engine" "helm.sh/helm/v3/pkg/getter" ) @@ -23,55 +24,35 @@ type Helm struct { engine *helm_engine.Engine chart *chart.Chart - teachart *TeaChart + teachart *values.TeaChart } -func newHelm(chartDir string, teachart *TeaChart, opts *NewEngineOptions) (*Helm, error) { +func New(chartDir string, teachart *values.TeaChart, strict bool) (*Helm, error) { chart, err := loader.Load(chartDir) if err != nil { return nil, err } + teachart.Metadata = chart.Metadata + // TODO support Dependencies - if opts == nil { - opts = &NewEngineOptions{} - } return &Helm{ engine: &helm_engine.Engine{ - Strict: opts.Strict, + Strict: strict, }, chart: chart, teachart: teachart, }, nil } -func (h *Helm) Render(valueOpts values.Options, save bool) (map[string]string, error) { - renderValues, err := h.getRenderValues(valueOpts) - if err != nil { - return nil, err - } - files, err := h.engine.Render(h.chart, renderValues) - if err != nil { - return nil, err - } - if save { - err = h.save(files) - } - return files, err -} - -func (h *Helm) getRenderValues(valueOpts values.Options) (chartutil.Values, error) { +func (h *Helm) getRenderValues(valueOpts helm_values.Options) (chartutil.Values, error) { // user define values userValues, err := valueOpts.MergeValues(getter.Providers{}) if err != nil { return nil, err } - top := map[string]interface{}{ - "Chart": h.chart.Metadata, - } - - userValues["TeaChart"] = h.teachart + top := map[string]interface{}{} values, err := chartutil.CoalesceValues(h.chart, userValues) if err != nil { return top, err @@ -83,7 +64,6 @@ func (h *Helm) getRenderValues(valueOpts values.Options) (chartutil.Values, erro } top["Values"] = values return top, nil - } func (h *Helm) save(files map[string]string) error { diff --git a/pkg/engine/helm/render.go b/pkg/engine/helm/render.go new file mode 100644 index 0000000..856e9ad --- /dev/null +++ b/pkg/engine/helm/render.go @@ -0,0 +1,97 @@ +package helm + +import ( + "path" + _ "unsafe" + + "github.com/yp05327/teachart/pkg/values" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + helm_values "helm.sh/helm/v3/pkg/cli/values" + helm_engine "helm.sh/helm/v3/pkg/engine" +) + +type renderable struct { + // tpl is the current template. + tpl string + // vals are the values to be supplied to the template. + vals chartutil.Values + // namespace prefix to the templates of the current chart + basePath string +} + +type files map[string][]byte + +//go:linkname newFiles helm.sh/helm/v3/pkg/engine.newFiles +func newFiles(from []*chart.File) files + +//go:linkname isTemplateValid helm.sh/helm/v3/pkg/engine.isTemplateValid +func isTemplateValid(ch *chart.Chart, templateName string) bool + +//go:linkname render helm.sh/helm/v3/pkg/engine.(*Engine).render +func render(e *helm_engine.Engine, tpls map[string]renderable) (rendered map[string]string, err error) + +// override helm.sh/helm/v3/pkg/engine.recAllTpls +func helm_engine_recAllTpls(c *chart.Chart, teachart *values.TeaChart, templates map[string]renderable, vals chartutil.Values) map[string]interface{} { + subCharts := make(map[string]interface{}) + // override the chart meta data + chartMetaData := struct { + values.TeaChart + IsRoot bool + }{*teachart, c.IsRoot()} + + next := map[string]interface{}{ + "TeaChart": chartMetaData, + "Files": newFiles(c.Files), + "Release": vals["Release"], + "Capabilities": vals["Capabilities"], + "Values": make(chartutil.Values), + "Subcharts": subCharts, + } + + // If there is a {{.Values.ThisChart}} in the parent metadata, + // copy that into the {{.Values}} for this template. + if c.IsRoot() { + next["Values"] = vals["Values"] + } else if vs, err := vals.Table("Values." + c.Name()); err == nil { + next["Values"] = vs + } + + for _, child := range c.Dependencies() { + subCharts[child.Name()] = helm_engine_recAllTpls(child, teachart, templates, next) + } + + newParentID := c.ChartFullPath() + for _, t := range c.Templates { + if t == nil { + continue + } + if !isTemplateValid(c, t.Name) { + continue + } + templates[path.Join(newParentID, t.Name)] = renderable{ + tpl: string(t.Data), + vals: next, + basePath: path.Join(newParentID, "templates"), + } + } + + return next +} + +func (h *Helm) Render(valueOpts helm_values.Options, save bool) (map[string]string, error) { + renderValues, err := h.getRenderValues(valueOpts) + if err != nil { + return nil, err + } + templates := make(map[string]renderable) + helm_engine_recAllTpls(h.chart, h.teachart, templates, renderValues) + files, err := render(h.engine, templates) + if err != nil { + return nil, err + } + if save { + err = h.save(files) + } + return files, err +} diff --git a/pkg/options/global.go b/pkg/options/global.go index e90b367..b01b441 100644 --- a/pkg/options/global.go +++ b/pkg/options/global.go @@ -7,7 +7,7 @@ import ( "path/filepath" compose_cmd "github.com/docker/compose/v2/cmd/compose" - "github.com/yp05327/teachart/pkg/engine" + "github.com/yp05327/teachart/pkg/values" ) // GlobalOptions is the global configuration @@ -87,8 +87,8 @@ func (g *GlobalOptions) GetProjectOptions() *compose_cmd.ProjectOptions { } } -func (g *GlobalOptions) GetTeaChart() *engine.TeaChart { - return &engine.TeaChart{ +func (g *GlobalOptions) GetTeaChart() *values.TeaChart { + return &values.TeaChart{ ProjectName: g.GetProjectName(), ProjectDir: g.GetProjectDir(), TempDir: g.GetTempDir(), diff --git a/pkg/engine/values.go b/pkg/values/values.go similarity index 52% rename from pkg/engine/values.go rename to pkg/values/values.go index 4a32521..65a4957 100644 --- a/pkg/engine/values.go +++ b/pkg/values/values.go @@ -1,8 +1,13 @@ // Copyright © 2024 TeaChart Authors -package engine +package values +import "helm.sh/helm/v3/pkg/chart" + +// TeaChart is an extended chart metadata type TeaChart struct { + *chart.Metadata + ProjectName string ProjectDir string TempDir string diff --git a/tests/data/chart/templates/test_values.yaml b/tests/data/chart/templates/test_values.yaml index 5793faf..be30210 100644 --- a/tests/data/chart/templates/test_values.yaml +++ b/tests/data/chart/templates/test_values.yaml @@ -3,14 +3,14 @@ services: image: nginx:latest environment: # should have .Values.TeaChart - - PROJECT_NAME={{ .Values.TeaChart.ProjectName }} - - PROJECT_DIR={{ .Values.TeaChart.ProjectDir }} - - TEMP_DIR={{ .Values.TeaChart.TempDir }} + - PROJECT_NAME={{ .TeaChart.ProjectName }} + - PROJECT_DIR={{ .TeaChart.ProjectDir }} + - TEMP_DIR={{ .TeaChart.TempDir }} # helm chart metadata - - CHART_NAME={{ .Chart.Name }} - - CHART_DESCRIPTION={{ .Chart.Description }} - - CHART_VERSION={{ .Chart.Version }} - - CHART_APPVERSION={{ .Chart.AppVersion }} + - CHART_NAME={{ .TeaChart.Name }} + - CHART_DESCRIPTION={{ .TeaChart.Description }} + - CHART_VERSION={{ .TeaChart.Version }} + - CHART_APPVERSION={{ .TeaChart.AppVersion }} # default values - DEFAULT_STRING={{ .Values.default.string }} # user values from file