Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

helm: Set includeCrds to true by default #851

Merged
merged 5 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/docs/helm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@ The following options control how the command is invoked:
values: {
persistence: { enabled: true }
},
// Equivalent to: --include-crds; only relevant for Helm v3+.
includeCrds: true,
// Equivalent to: --api-versions v1 --api-versions apps/v1
apiVersions: ['v1', 'apps/v1']
// Equivalent to: --kube-version v1.20.0
Expand All @@ -90,6 +88,17 @@ The following options control how the command is invoked:
}
```

Tanka will install Custom Resource Definitions (CRDs) automatically, if the
Helm Chart requires them and ships them in `crds/`. This is equivalent to `helm
template --include-crds`. This can be disabled using `includeCrds: false`:

```jsonnet
{
grafana: helm.template("grafana", "./charts/grafana", {
includeCrds: false
})
}
```

## Vendoring Helm Charts

Expand Down
14 changes: 14 additions & 0 deletions pkg/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type Helm interface {

// Template returns the individual resources of a Helm Chart
Template(name, chart string, opts TemplateOpts) (manifest.List, error)

// ChartExists checks if a chart exists in the provided calledFromPath
ChartExists(chart string, opts *JsonnetOpts) (string, error)
}

// PullOpts are additional, non-required options for Helm.Pull
Expand Down Expand Up @@ -103,6 +106,17 @@ func (e ExecHelm) RepoUpdate(opts Opts) error {
return nil
}

func (e ExecHelm) ChartExists(chart string, opts *JsonnetOpts) (string, error) {
// resolve the Chart relative to the caller
callerDir := filepath.Dir(opts.CalledFrom)
chart = filepath.Join(callerDir, chart)
if _, err := os.Stat(chart); err != nil {
return "", fmt.Errorf("helmTemplate: Failed to find a chart at '%s': %s. See https://tanka.dev/helm#failed-to-find-chart", chart, err)
}

return chart, nil
}

// cmd returns a prepared exec.Cmd to use the `helm` binary
func (e ExecHelm) cmd(action string, args ...string) *exec.Cmd {
argv := []string{action}
Expand Down
19 changes: 12 additions & 7 deletions pkg/helm/jsonnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package helm
import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/google/go-jsonnet"
"github.com/google/go-jsonnet/ast"
Expand Down Expand Up @@ -54,10 +52,8 @@ func NativeFunc(h Helm) *jsonnet.NativeFunction {
return "", err
}

// resolve the Chart relative to the caller
callerDir := filepath.Dir(opts.CalledFrom)
chart := filepath.Join(callerDir, chartpath)
if _, err := os.Stat(chart); err != nil {
chart, err := h.ChartExists(chartpath, opts)
if err != nil {
return nil, fmt.Errorf("helmTemplate: Failed to find a chart at '%s': %s. See https://tanka.dev/helm#failed-to-find-chart", chart, err)
}

Expand All @@ -83,7 +79,16 @@ func parseOpts(data interface{}) (*JsonnetOpts, error) {
if err != nil {
return nil, err
}
var opts JsonnetOpts

// default IncludeCRDs to true, as this is the default in the `helm install`
// command. Needs to be specified here because the zero value of bool is
// false.
opts := JsonnetOpts{
TemplateOpts: TemplateOpts{
IncludeCRDs: true,
},
}

if err := json.Unmarshal(c, &opts); err != nil {
return nil, err
}
Expand Down
163 changes: 163 additions & 0 deletions pkg/helm/jsonnet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package helm

import (
"testing"

"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/grafana/tanka/pkg/kubernetes/manifest"
)

const calledFrom = "/my/path/here"

type MockHelm struct {
mock.Mock
}

// fulfill the Helm interface
func (m *MockHelm) Pull(chart, version string, opts PullOpts) error {
args := m.Called(chart, version, opts)
return args.Error(0)
}

func (m *MockHelm) RepoUpdate(opts Opts) error {
args := m.Called(opts)
return args.Error(0)
}

func (m *MockHelm) Template(name, chart string, opts TemplateOpts) (manifest.List, error) {
args := m.Called(name, chart, opts)

// figure out what arguments `helm template` would be called with and save
// them
execHelm := &ExecHelm{}
cmdArgs := execHelm.templateCommandArgs(name, chart, opts)
m.TestData().Set("templateCommandArgs", cmdArgs)

return args.Get(0).(manifest.List), args.Error(1)
}

func (m *MockHelm) ChartExists(chart string, opts *JsonnetOpts) (string, error) {
args := m.Called(chart, opts)
return args.String(0), args.Error(1)
}

func callNativeFucntion(t *testing.T, templateOpts TemplateOpts, parameters []interface{}) []string {
iainlane marked this conversation as resolved.
Show resolved Hide resolved
t.Helper()

helmMock := &MockHelm{}

helmMock.On(
"ChartExists",
"chart",
mock.AnythingOfType("*helm.JsonnetOpts")).
Return("/full/chart/path", nil).
Once()

// this verifies that the helmMock.Template() method is called with the
// correct arguments, i.e. includeCrds: true is set by default
helmMock.On("Template", "name", "/full/chart/path", templateOpts).
Return(manifest.List{}, nil).
Once()

nf := NativeFunc(helmMock)
require.NotNil(t, nf)

params := []string{
"name",
"chart",
}

opts := make(map[string]interface{})
opts["calledFrom"] = calledFrom

// params + opts
paramsInterface := make([]interface{}, 3)
paramsInterface[0] = params[0]
paramsInterface[1] = params[1]
paramsInterface[2] = opts

_, err := nf.Func(parameters)

require.NoError(t, err)

helmMock.AssertExpectations(t)

return helmMock.TestData().Get("templateCommandArgs").StringSlice()
}

// TestDefaultCommandineFlagsIncludeCrds tests that the includeCrds flag is set
// to true by default
func TestDefaultCommandineFlagsIncludeCrds(t *testing.T) {
julienduchesne marked this conversation as resolved.
Show resolved Hide resolved
iainlane marked this conversation as resolved.
Show resolved Hide resolved
kubeVersion := "1.18.0"

// we will check that the template function is called with these options,
// i.e. that includeCrds got set to true. This is not us passing an input,
// we are asserting here that the template function is called with these
// options.
templateOpts := TemplateOpts{
KubeVersion: kubeVersion,
IncludeCRDs: true,
}

params := []string{
"name",
"chart",
}

// we do not set includeCrds here, so it should be true by default
opts := make(map[string]interface{})
opts["calledFrom"] = calledFrom
opts["kubeVersion"] = kubeVersion

// params + opts
paramsInterface := make([]interface{}, 3)
paramsInterface[0] = params[0]
paramsInterface[1] = params[1]
paramsInterface[2] = opts

args := callNativeFucntion(t, templateOpts, paramsInterface)
julienduchesne marked this conversation as resolved.
Show resolved Hide resolved

// finally check that the actual command line arguments we will pass to
// `helm template` contain the --include-crds flag
require.Contains(t, args, "--include-crds")
}

// TestIncludeCrdsFalse tests that the includeCrds flag is can be set to false,
// and this makes it to the helm.Template() method call
func TestIncludeCrdsFalse(t *testing.T) {
kubeVersion := "1.18.0"

// we will check that the template function is called with these options,
// i.e. that includeCrds got set to false. This is not us passing an input,
// we are asserting here that the template function is called with these
// options.
templateOpts := TemplateOpts{
KubeVersion: kubeVersion,
IncludeCRDs: false,
}

params := []string{
"name",
"chart",
}

// we explicitly set includeCrds to false here
opts := make(map[string]interface{})
opts["calledFrom"] = calledFrom
opts["kubeVersion"] = kubeVersion
opts["includeCRDs"] = false

// params + opts
paramsInterface := make([]interface{}, 3)
paramsInterface[0] = params[0]
paramsInterface[1] = params[1]
paramsInterface[2] = opts

args := callNativeFucntion(t, templateOpts, paramsInterface)

// finally check that the actual command line arguments we will pass to
// `helm template` don't contain the --include-crds flag
require.NotContains(t, args, "--include-crds")
}
11 changes: 8 additions & 3 deletions pkg/helm/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ import (
yaml "gopkg.in/yaml.v3"
)

// Template expands a Helm Chart into a regular manifest.List using the `helm
// template` command
func (e ExecHelm) Template(name, chart string, opts TemplateOpts) (manifest.List, error) {
func (e ExecHelm) templateCommandArgs(name, chart string, opts TemplateOpts) []string {
args := []string{name, chart,
"--values", "-", // values from stdin
}
args = append(args, opts.Flags()...)
return args
}

// Template expands a Helm Chart into a regular manifest.List using the `helm
// template` command
func (e ExecHelm) Template(name, chart string, opts TemplateOpts) (manifest.List, error) {
args := e.templateCommandArgs(name, chart, opts)

cmd := e.cmd("template", args...)
var buf bytes.Buffer
Expand Down