From 9cf6b59cd83b12aec2d3bbc3fa2e659f448ae917 Mon Sep 17 00:00:00 2001 From: a-hat <51818964+a-hat@users.noreply.github.com> Date: Fri, 27 Dec 2019 00:30:39 +0100 Subject: [PATCH] feat: Option to pass apiVersions to `helm diff` and `helm template` (#1046) This makes it possible to pass the API Capabilities to helmfile when executing a task that does not render against an actual cluster (diff, template, apply). Resolves #1014 --- README.md | 13 +++++++- pkg/app/app_test.go | 77 +++++++++++++++++++++++++++++++++++++++++++++ pkg/state/state.go | 12 +++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c86d8ed5..5389aa70 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,6 @@ helmDefaults: # limit the maximum number of revisions saved per release. Use 0 for no limit (default 10) historyMax: 10 - # The desired states of Helm releases. # # Helmfile runs various helm commands to converge the current state in the live cluster to the desired state defined here. @@ -267,6 +266,18 @@ bases: - environments.yaml - defaults.yaml - templates.yaml + +# +# Advanced Configuration: API Capabilities +# +# Some helmfile tasks render releases locally without querying an actual cluster (diff, apply, template), +# and in this case `.Capabilities.APIVersions` cannot be populated. +# When a chart queries for a specific CRD, this can lead to unexpected results. +# +# Configure a fixed list of api versions to pass to helm via the --api-versions flag: +apiVersions: +- example/v1 + ``` ## Templating diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index db6179db..09fd382b 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2092,6 +2092,64 @@ releases: } } +func TestTemplate_ApiVersions(t *testing.T) { + files := map[string]string{ + "/path/to/helmfile.yaml": ` +apiVersions: +- helmfile.test/v1 +- helmfile.test/v2 +releases: +- name: myrelease1 + chart: mychart1 +`, + } + + var helm = &mockHelmExec{} + var wantReleases = []mockTemplates{ + {name: "myrelease1", chart: "mychart1", flags: []string{"--api-versions", "helmfile.test/v1", "--api-versions", "helmfile.test/v2", "--namespace", "testNamespace", "--output-dir", "output/subdir/helmfile-[a-z0-9]{8}-myrelease1"}}, + } + + var buffer bytes.Buffer + logger := helmexec.NewLogger(&buffer, "debug") + + valsRuntime, err := vals.New(vals.Options{CacheSize: 32}) + if err != nil { + t.Errorf("unexpected error creating vals runtime: %v", err) + } + + app := appWithFs(&App{ + glob: filepath.Glob, + abs: filepath.Abs, + KubeContext: "default", + Env: "default", + Logger: logger, + helmExecer: helm, + Namespace: "testNamespace", + valsRuntime: valsRuntime, + }, files) + app.Template(configImpl{}) + + for i := range wantReleases { + if wantReleases[i].name != helm.templated[i].name { + t.Errorf("name = [%v], want %v", helm.templated[i].name, wantReleases[i].name) + } + if !strings.Contains(helm.templated[i].chart, wantReleases[i].chart) { + t.Errorf("chart = [%v], want %v", helm.templated[i].chart, wantReleases[i].chart) + } + for j := range wantReleases[i].flags { + if j == 7 { + matched, _ := regexp.Match(wantReleases[i].flags[j], []byte(helm.templated[i].flags[j])) + if !matched { + t.Errorf("HelmState.TemplateReleases() = [%v], want %v", helm.templated[i].flags[j], wantReleases[i].flags[j]) + } + } else if wantReleases[i].flags[j] != helm.templated[i].flags[j] { + t.Errorf("HelmState.TemplateReleases() = [%v], want %v", helm.templated[i].flags[j], wantReleases[i].flags[j]) + } + } + + } +} + func TestApply(t *testing.T) { testcases := []struct { name string @@ -3351,6 +3409,25 @@ Affected releases are: err: "foo" has dependency to inexistent release "bar" `, }, + { + name: "pass apiVersions to helm diff", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +apiVersions: +- xxx/v1 +releases: +- name: foo + chart: mychart1 +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--api-versionsxxx/v1--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "foo", Flags: []string{"--kube-context", "default"}}, + }, + }, } for i := range testcases { diff --git a/pkg/state/state.go b/pkg/state/state.go index ef8a7a22..23a48794 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -47,6 +47,7 @@ type HelmState struct { Repositories []RepositorySpec `yaml:"repositories,omitempty"` Releases []ReleaseSpec `yaml:"releases,omitempty"` Selectors []string `yaml:"-"` + ApiVersions []string `yaml:"apiVersions,omitempty"` Templates map[string]TemplateSpec `yaml:"templates"` @@ -1590,6 +1591,8 @@ func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseS return nil, err } + flags = st.appendApiVersionsFlags(flags) + common, err := st.namespaceAndValuesFlags(helm, release, workerIndex) if err != nil { return nil, err @@ -1615,6 +1618,8 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, return nil, err } + flags = st.appendApiVersionsFlags(flags) + common, err := st.namespaceAndValuesFlags(helm, release, workerIndex) if err != nil { return nil, err @@ -1622,6 +1627,13 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, return append(flags, common...), nil } +func (st *HelmState) appendApiVersionsFlags(flags []string) []string { + for _, a := range st.ApiVersions { + flags = append(flags, "--api-versions", a) + } + return flags +} + func (st *HelmState) isDevelopment(release *ReleaseSpec) bool { result := st.HelmDefaults.Devel if release.Devel != nil {