Skip to content

Commit

Permalink
Embed Helm chart templates in CLI (#757)
Browse files Browse the repository at this point in the history
  • Loading branch information
ndhanushkodi authored Oct 7, 2021
1 parent 7788630 commit eb86243
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 5 deletions.
16 changes: 16 additions & 0 deletions charts/embed_chart.go
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions charts/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/hashicorp/consul-k8s/charts

go 1.16
1 change: 1 addition & 0 deletions cli/cmd/common/fixtures/consul/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
chart
1 change: 1 addition & 0 deletions cli/cmd/common/fixtures/consul/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
helpers
1 change: 1 addition & 0 deletions cli/cmd/common/fixtures/consul/templates/foo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo
1 change: 1 addition & 0 deletions cli/cmd/common/fixtures/consul/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
values
61 changes: 61 additions & 0 deletions cli/cmd/common/utils.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,81 @@
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"
)

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/<templatename>.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")
Expand Down
39 changes: 39 additions & 0 deletions cli/cmd/common/utils_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
10 changes: 5 additions & 5 deletions cli/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

0 comments on commit eb86243

Please sign in to comment.