From eb862436c39344e54a4c4ad3b247246f596dc384 Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Thu, 7 Oct 2021 16:07:42 -0700 Subject: [PATCH] Embed Helm chart templates in CLI (#757) --- charts/embed_chart.go | 16 +++++ charts/go.mod | 3 + cli/cmd/common/fixtures/consul/Chart.yaml | 1 + .../fixtures/consul/templates/_helpers.tpl | 1 + .../common/fixtures/consul/templates/foo.yaml | 1 + cli/cmd/common/fixtures/consul/values.yaml | 1 + cli/cmd/common/utils.go | 61 +++++++++++++++++++ cli/cmd/common/utils_test.go | 39 ++++++++++++ cli/cmd/install/install.go | 10 +-- cli/go.mod | 7 +++ 10 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 charts/embed_chart.go create mode 100644 charts/go.mod create mode 100644 cli/cmd/common/fixtures/consul/Chart.yaml create mode 100644 cli/cmd/common/fixtures/consul/templates/_helpers.tpl create mode 100644 cli/cmd/common/fixtures/consul/templates/foo.yaml create mode 100644 cli/cmd/common/fixtures/consul/values.yaml create mode 100644 cli/cmd/common/utils_test.go diff --git a/charts/embed_chart.go b/charts/embed_chart.go new file mode 100644 index 0000000000..6393508ebb --- /dev/null +++ b/charts/embed_chart.go @@ -0,0 +1,16 @@ +package charts + +import "embed" + +// ConsulHelmChart embeds the Consul Helm Chart files into an exported variable from this package. Changes to the chart +// files referenced below will be reflected in the embedded templates in the CLI at CLI compile time. +// +// This is currently only meant to be used by the consul-k8s CLI within this repository. Importing this package from the +// CLI allows us to embed the templates at compilation time. Since this is in a monorepo, we can directly reference this +// charts module as relative to the CLI module (with a replace directive), which allows us to not need to bump the +// charts module dependency manually or as part of a Makefile. +// +// The embed directive does not include files with underscores unless explicitly listed, which is why _helpers.tpl is +// explicitly embedded. +//go:embed consul/Chart.yaml consul/values.yaml consul/templates consul/templates/_helpers.tpl +var ConsulHelmChart embed.FS diff --git a/charts/go.mod b/charts/go.mod new file mode 100644 index 0000000000..b5195323c1 --- /dev/null +++ b/charts/go.mod @@ -0,0 +1,3 @@ +module github.com/hashicorp/consul-k8s/charts + +go 1.16 diff --git a/cli/cmd/common/fixtures/consul/Chart.yaml b/cli/cmd/common/fixtures/consul/Chart.yaml new file mode 100644 index 0000000000..54c6e609d9 --- /dev/null +++ b/cli/cmd/common/fixtures/consul/Chart.yaml @@ -0,0 +1 @@ +chart \ No newline at end of file diff --git a/cli/cmd/common/fixtures/consul/templates/_helpers.tpl b/cli/cmd/common/fixtures/consul/templates/_helpers.tpl new file mode 100644 index 0000000000..bdf8746bdb --- /dev/null +++ b/cli/cmd/common/fixtures/consul/templates/_helpers.tpl @@ -0,0 +1 @@ +helpers \ No newline at end of file diff --git a/cli/cmd/common/fixtures/consul/templates/foo.yaml b/cli/cmd/common/fixtures/consul/templates/foo.yaml new file mode 100644 index 0000000000..1910281566 --- /dev/null +++ b/cli/cmd/common/fixtures/consul/templates/foo.yaml @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/cli/cmd/common/fixtures/consul/values.yaml b/cli/cmd/common/fixtures/consul/values.yaml new file mode 100644 index 0000000000..972ac6208d --- /dev/null +++ b/cli/cmd/common/fixtures/consul/values.yaml @@ -0,0 +1 @@ +values \ No newline at end of file diff --git a/cli/cmd/common/utils.go b/cli/cmd/common/utils.go index 5c044a0e03..b02abfabf9 100644 --- a/cli/cmd/common/utils.go +++ b/cli/cmd/common/utils.go @@ -1,11 +1,14 @@ package common import ( + "embed" "fmt" "os" + "path/filepath" "strings" "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart/loader" helmCLI "helm.sh/helm/v3/pkg/cli" "k8s.io/cli-runtime/pkg/genericclioptions" ) @@ -13,8 +16,66 @@ import ( const ( DefaultReleaseName = "consul" DefaultReleaseNamespace = "consul" + chartFileName = "Chart.yaml" + valuesFileName = "values.yaml" + templatesDirName = "templates" + TopLevelChartDirName = "consul" ) +// ReadChartFiles reads the chart files from the embedded file system, and loads their contents into +// []*loader.BufferedFile. This is a format that the Helm Go SDK functions can read from to create a chart to install +// from. The names of these files are important, as there are case statements in the Helm Go SDK looking for files named +// "Chart.yaml" or "templates/.yaml", which is why even though the embedded file system has them named +// "consul/Chart.yaml" we have to strip the "consul" prefix out, which is done by the call to the helper method readFile. +func ReadChartFiles(chart embed.FS, chartDirName string) ([]*loader.BufferedFile, error) { + var chartFiles []*loader.BufferedFile + + // Load Chart.yaml and values.yaml first. + for _, f := range []string{chartFileName, valuesFileName} { + file, err := readFile(chart, filepath.Join(chartDirName, f), chartDirName) + if err != nil { + return nil, err + } + chartFiles = append(chartFiles, file) + } + + // Now load everything under templates/. + dirs, err := chart.ReadDir(filepath.Join(chartDirName, templatesDirName)) + if err != nil { + return nil, err + } + for _, f := range dirs { + if f.IsDir() { + // We only need to include files in the templates directory. + continue + } + + file, err := readFile(chart, filepath.Join(chartDirName, templatesDirName, f.Name()), chartDirName) + if err != nil { + return nil, err + } + chartFiles = append(chartFiles, file) + } + + return chartFiles, nil +} + +func readFile(chart embed.FS, f string, pathPrefix string) (*loader.BufferedFile, error) { + bytes, err := chart.ReadFile(f) + if err != nil { + return nil, err + } + // Remove the path prefix. + rel, err := filepath.Rel(pathPrefix, f) + if err != nil { + return nil, err + } + return &loader.BufferedFile{ + Name: rel, + Data: bytes, + }, nil +} + // Abort returns true if the raw input string is not equal to "y" or "yes". func Abort(raw string) bool { confirmation := strings.TrimSuffix(raw, "\n") diff --git a/cli/cmd/common/utils_test.go b/cli/cmd/common/utils_test.go new file mode 100644 index 0000000000..fc9bf5292c --- /dev/null +++ b/cli/cmd/common/utils_test.go @@ -0,0 +1,39 @@ +package common + +import ( + "embed" + "testing" + + "github.com/stretchr/testify/require" +) + +//go:embed fixtures/consul/* fixtures/consul/templates/_helpers.tpl +var testChart embed.FS + +func TestReadChartFiles(t *testing.T) { + files, err := ReadChartFiles(testChart, "fixtures/consul") + require.NoError(t, err) + var foundChart, foundValues, foundTemplate, foundHelper bool + for _, f := range files { + if f.Name == "Chart.yaml" { + require.Equal(t, "chart", string(f.Data)) + foundChart = true + } + if f.Name == "values.yaml" { + require.Equal(t, "values", string(f.Data)) + foundValues = true + } + if f.Name == "templates/foo.yaml" { + require.Equal(t, "foo", string(f.Data)) + foundTemplate = true + } + if f.Name == "templates/_helpers.tpl" { + require.Equal(t, "helpers", string(f.Data)) + foundHelper = true + } + } + require.True(t, foundChart) + require.True(t, foundValues) + require.True(t, foundTemplate) + require.True(t, foundHelper) +} diff --git a/cli/cmd/install/install.go b/cli/cmd/install/install.go index 9e1d406ed8..bef831802f 100644 --- a/cli/cmd/install/install.go +++ b/cli/cmd/install/install.go @@ -8,6 +8,7 @@ import ( "sync" "time" + consulChart "github.com/hashicorp/consul-k8s/charts" "github.com/hashicorp/consul-k8s/cli/cmd/common" "github.com/hashicorp/consul-k8s/cli/cmd/common/flag" "github.com/hashicorp/consul-k8s/cli/cmd/common/terminal" @@ -304,19 +305,18 @@ func (c *Command) Run(args []string) int { install.ReleaseName = common.DefaultReleaseName install.Namespace = c.flagNamespace install.CreateNamespace = true - install.ChartPathOptions.RepoURL = helmRepository install.Wait = c.flagWait install.Timeout = c.timeoutDuration - // Locate the chart, install it in some cache locally. - chartPath, err := install.ChartPathOptions.LocateChart("consul", settings) + // Read the embedded chart files into []*loader.BufferedFile. + chartFiles, err := common.ReadChartFiles(consulChart.ConsulHelmChart, common.TopLevelChartDirName) if err != nil { c.UI.Output(err.Error(), terminal.WithErrorStyle()) return 1 } - // Actually load the chart into memory. - chart, err := loader.Load(chartPath) + // Create a *chart.Chart object from the files to run the installation from. + chart, err := loader.LoadFiles(chartFiles) if err != nil { c.UI.Output(err.Error(), terminal.WithErrorStyle()) return 1 diff --git a/cli/go.mod b/cli/go.mod index 38ed649c60..470bc78c42 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -7,6 +7,7 @@ require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/fatih/color v1.9.0 github.com/golang/protobuf v1.5.2 // indirect + github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 github.com/hashicorp/go-hclog v0.16.2 github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/kr/text v0.2.0 @@ -27,3 +28,9 @@ require ( rsc.io/letsencrypt v0.0.3 // indirect sigs.k8s.io/yaml v1.2.0 ) + +// This replace directive is to avoid having to manually bump the version of the charts module upon changes to the Helm +// chart. When the CLI compiles, all changes to the local charts directory are picked up automatically. This directive +// works because of the monorepo setup, where the charts module and CLI module are in the same repository. Otherwise, +// this won't work. +replace github.com/hashicorp/consul-k8s/charts => ../charts