diff --git a/pkg/components/util/helm.go b/pkg/components/util/helm.go index 573a8c302..52f5b2210 100644 --- a/pkg/components/util/helm.go +++ b/pkg/components/util/helm.go @@ -214,11 +214,7 @@ func chartFromManifests(metadata components.Metadata, manifests map[string]strin Name: p, } - // Apply rendered manifests to Manifests slice, which does not run through the rendering engine - // again when the chart is being installed. This is required, as some charts use complex escaping - // syntax, which breaks if the templates are evaluated twice. This, for example, breaks - // the prometheus-operator chart. - ch.Manifests = append(ch.Manifests, f) + ch.Templates = append(ch.Templates, f) } // If we collected any CRDs, put them in the special file in the dedicated crds/ directory. diff --git a/pkg/components/util/helm_test.go b/pkg/components/util/helm_test.go index 4c3f0d092..0acd1b791 100644 --- a/pkg/components/util/helm_test.go +++ b/pkg/components/util/helm_test.go @@ -106,11 +106,11 @@ metadata: t.Fatalf("Chart should be created, got: %v", err) } - if len(chart.Manifests) != 1 { //nolint:gomnd + if len(chart.Templates) != 1 { t.Fatalf("Manifest file with the namespace should still be added, as it may contain other objects") } - if len(chart.Manifests[0].Data) != 0 { + if len(chart.Templates[0].Data) != 0 { t.Fatalf("Namespace object should be removed from chart") } } @@ -140,7 +140,11 @@ metadata: t.Fatalf("Chart should be created, got: %v", err) } - if len(chart.Manifests[0].Data) == 0 { + if len(chart.Templates) != 1 { + t.Fatalf("templates should include exactly one object") + } + + if len(chart.Templates[0].Data) == 0 { t.Fatalf("Other objects should be retained in the file containing Namespace object") } } @@ -165,7 +169,7 @@ metadata: t.Fatalf("Chart should be created, got: %v", err) } - if len(chart.Manifests[0].Data) == 0 { + if len(chart.Templates[0].Data) == 0 { t.Fatalf("Only Namespace object with matching namespace name should be filtered") } } @@ -188,15 +192,15 @@ metadata: t.Fatalf("Chart should be created, got: %v", err) } - if len(chart.Manifests) != 1 { //nolint:gomnd + if len(chart.Templates) != 1 { t.Fatalf("Manifest file with the CRDs should still be added, as it may contain other objects") } - if len(chart.Manifests[0].Data) != 0 { + if len(chart.Templates[0].Data) != 0 { t.Fatalf("CRD object should be removed from the manifests file") } - if len(chart.Files) != 1 { //nolint:gomnd + if len(chart.Files) != 1 { t.Fatalf("CRD object should be added to Files field") } diff --git a/pkg/components/util/install.go b/pkg/components/util/install.go index 2b6b5e82e..1e23e8b31 100644 --- a/pkg/components/util/install.go +++ b/pkg/components/util/install.go @@ -15,6 +15,7 @@ package util import ( + "bytes" "context" "fmt" @@ -104,10 +105,45 @@ type helmAction struct { wait bool } +// postRender is a Lokomotive post-renderer for Helm, which allows bypassing Helm templating engine +// by copying manifests from Templates field and then including them into the chart. +// +// As we render original charts twice (once when we render manifests ourselves to be able to print them +// to the user and then again via Helm, when it installs/upgrades/uninstalls the release. +// +// This is required as some charts use complex escaping syntax, which breaks if the templates +// are evaluated twice. This, for example, breaks the prometheus-operator chart. +type postRender struct { + manifests bytes.Buffer +} + +// Run implements postrender.PostRenderer interface by ignoring given templates from Helm and returning +// manifests defined at creation time. +func (pr postRender) Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error) { + return &pr.manifests, nil +} + +// chartTemplatesToPostRender creates PostRenderer from given chart templates and resets +// chart's Templates field so it does not run twice trough rendering engine. +func chartTemplatesToPostRender(c *chart.Chart) postRender { + var buf bytes.Buffer + + for _, template := range c.Templates { + fmt.Fprintf(&buf, "\n---\n# %s\n%s", template.Name, template.Data) + } + + c.Templates = nil + + return postRender{ + manifests: buf, + } +} + func install(helmAction *helmAction, namespace string) error { install := action.NewInstall(helmAction.actionConfig) install.ReleaseName = helmAction.releaseName install.Namespace = namespace + install.PostRenderer = chartTemplatesToPostRender(helmAction.chart) // Currently, we install components one-by-one, in the order how they are // defined in the configuration and we do not support any dependencies between @@ -133,6 +169,7 @@ func upgrade(helmAction *helmAction) error { upgrade := action.NewUpgrade(helmAction.actionConfig) upgrade.Wait = helmAction.wait upgrade.RecreateResources = true + upgrade.PostRenderer = chartTemplatesToPostRender(helmAction.chart) if _, err := upgrade.Run(helmAction.releaseName, helmAction.chart, map[string]interface{}{}); err != nil { return fmt.Errorf("upgrading release failed: %w", err)