mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-03 05:41:39 +02:00
* fix: eliminate os.Chdir in sequential helmfiles to fix relative path resolution The sequential code path used within() → os.Chdir() to change the process-wide working directory when processing helmfile.d files. This broke relative environment variable paths (e.g. KUBECONFIG=kubeconfig.yaml) because they resolved from the wrong directory after chdir. Replace the chdir-based approach with the same baseDir parameter pattern used by the parallel code path, passing explicit directory context through loadDesiredStateFromYamlWithBaseDir() instead of mutating global process state. Closes #2409 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: restore within() for single-file sequential to preserve chart path format The previous approach used baseDir for all sequential processing, which changed chart path format in output (e.g. from "../../../../charts/raw" to "test/integration/charts/raw"). This broke integration tests that compare chart paths in expected output. Now the sequential branch uses two strategies: - Single file: use os.Chdir via within() to preserve backward-compatible relative chart paths in output - Multiple files with --sequential-helmfiles: use baseDir parameter to avoid os.Chdir, fixing relative env var paths like KUBECONFIG (#2409) Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: revert e2e snapshot outputs to match within() behavior The previous commit restored within() for single-file sequential processing, which produces relative chart paths (e.g. ../../charts/raw) and filename-only FilePath. Revert the e2e snapshot expected outputs to match main branch since single-file behavior is now identical. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: restructure integration test for multi-file sequential processing - Point -f at helmfile.d/ directly (not parent dir) so findDesiredStateFiles discovers the yaml files - Add second helmfile to trigger baseDir path (len > 1) - Inline environment config to avoid base file relative path issues - Verify both releases appear in output instead of comparing with parallel (which may differ in ordering) Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: reduce cognitive complexity and improve accuracy of sequential helmfiles Replace inline visitSubHelmfiles closure with calls to the existing processNestedHelmfiles() method, matching the parallel path. This eliminates duplicated nested logic and reduces gocognit complexity below the CI threshold of 110. Also fixes help text and docs to accurately describe that single-file processing still uses within(), and adds kubeContext verification to the integration test. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * test: validate kubeContext resolution in sequential helmfiles integration test Restructure the integration test to replicate the exact user scenario from issue #2409: - Multiple files in helmfile.d/ using bases: with relative paths (../bases/) for environments and defaults - Environment values set kubeContext via .Environment.Values - helmDefaults.kubeContext rendered from gotmpl - Local chart references (../../../../charts/raw) from helmfile.d/ - Run diff against the minikube cluster to exercise kubeContext resolution, which would fail with "context does not exist" if os.Chdir() broke relative path resolution - Also verify template output for both releases and relative values file (values/common.yaml) resolution Fix normalizeChart() in util.go to be idempotent — skip re-prefixing when the chart path already starts with basePath. This prevents double-prefixing of local chart paths (e.g. helmfile.d/test/.../raw) when normalizeChart is called multiple times (once during chart preparation and again during diff/sync). Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> --------- Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
5414 lines
130 KiB
Go
5414 lines
130 KiB
Go
package state
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
"github.com/helmfile/vals"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/helmfile/helmfile/pkg/environment"
|
|
"github.com/helmfile/helmfile/pkg/exectest"
|
|
"github.com/helmfile/helmfile/pkg/filesystem"
|
|
"github.com/helmfile/helmfile/pkg/helmexec"
|
|
"github.com/helmfile/helmfile/pkg/testhelper"
|
|
)
|
|
|
|
var logger = helmexec.NewLogger(io.Discard, "warn")
|
|
var valsRuntime, _ = vals.New(vals.Options{CacheSize: 32})
|
|
|
|
func injectFs(st *HelmState, fs *testhelper.TestFs) *HelmState {
|
|
st.fs = fs.ToFileSystem()
|
|
return st
|
|
}
|
|
|
|
func TestLabelParsing(t *testing.T) {
|
|
cases := []struct {
|
|
labelString string
|
|
expectedFilter LabelFilter
|
|
errorExected bool
|
|
}{
|
|
{"foo=bar", LabelFilter{positiveLabels: [][]string{{"foo", "bar"}}, negativeLabels: [][]string{}}, false},
|
|
{"foo!=bar", LabelFilter{positiveLabels: [][]string{}, negativeLabels: [][]string{{"foo", "bar"}}}, false},
|
|
{"foo!=bar,baz=bat", LabelFilter{positiveLabels: [][]string{{"baz", "bat"}}, negativeLabels: [][]string{{"foo", "bar"}}}, false},
|
|
{"foo", LabelFilter{positiveLabels: [][]string{}, negativeLabels: [][]string{}}, true},
|
|
{"foo!=bar=baz", LabelFilter{positiveLabels: [][]string{}, negativeLabels: [][]string{}}, true},
|
|
{"=bar", LabelFilter{positiveLabels: [][]string{}, negativeLabels: [][]string{}}, true},
|
|
}
|
|
for idx, c := range cases {
|
|
filter, err := ParseLabels(c.labelString)
|
|
if err != nil && !c.errorExected {
|
|
t.Errorf("[%d] Didn't expect an error parsing labels: %s", idx, err)
|
|
} else if err == nil && c.errorExected {
|
|
t.Errorf("[%d] Expected %s to result in an error but got none", idx, c.labelString)
|
|
} else if !reflect.DeepEqual(filter, c.expectedFilter) {
|
|
t.Errorf("[%d] parsed label did not result in expected filter: %v, expected: %v", idx, filter, c.expectedFilter)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHelmState_applyDefaultsTo(t *testing.T) {
|
|
type fields struct {
|
|
BaseChartPath string
|
|
Context string
|
|
Namespace string
|
|
Repositories []RepositorySpec
|
|
Releases []ReleaseSpec
|
|
}
|
|
type args struct {
|
|
spec ReleaseSpec
|
|
}
|
|
verify := false
|
|
specWithNamespace := ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &verify,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
Values: nil,
|
|
SetValues: nil,
|
|
EnvValues: nil,
|
|
}
|
|
|
|
specWithoutNamespace := specWithNamespace
|
|
specWithoutNamespace.Namespace = ""
|
|
specWithNamespaceFromFields := specWithNamespace
|
|
specWithNamespaceFromFields.Namespace = "test-namespace-field"
|
|
|
|
fieldsWithNamespace := fields{
|
|
BaseChartPath: ".",
|
|
Context: "test_context",
|
|
Namespace: specWithNamespaceFromFields.Namespace,
|
|
Repositories: nil,
|
|
Releases: []ReleaseSpec{
|
|
specWithNamespace,
|
|
},
|
|
}
|
|
|
|
fieldsWithoutNamespace := fieldsWithNamespace
|
|
fieldsWithoutNamespace.Namespace = ""
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want ReleaseSpec
|
|
}{
|
|
{
|
|
name: "Has a namespace from spec",
|
|
fields: fieldsWithoutNamespace,
|
|
args: args{
|
|
spec: specWithNamespace,
|
|
},
|
|
want: specWithNamespace,
|
|
},
|
|
{
|
|
name: "Has a namespace from flags and from spec",
|
|
fields: fieldsWithNamespace,
|
|
args: args{
|
|
spec: specWithNamespace,
|
|
},
|
|
want: specWithNamespaceFromFields,
|
|
},
|
|
{
|
|
name: "Spec and flag Has no a namespace",
|
|
fields: fieldsWithoutNamespace,
|
|
args: args{
|
|
spec: specWithoutNamespace,
|
|
},
|
|
want: specWithoutNamespace,
|
|
},
|
|
{
|
|
name: "Spec has no a namespace but from flag",
|
|
fields: fieldsWithNamespace,
|
|
args: args{
|
|
spec: specWithoutNamespace,
|
|
},
|
|
want: specWithNamespaceFromFields,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
basePath: tt.fields.BaseChartPath,
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
OverrideNamespace: tt.fields.Namespace,
|
|
Repositories: tt.fields.Repositories,
|
|
Releases: tt.fields.Releases,
|
|
},
|
|
}
|
|
if state.ApplyOverrides(&tt.args.spec); !reflect.DeepEqual(tt.args.spec, tt.want) {
|
|
t.Errorf("HelmState.ApplyOverrides() = %v, want %v", tt.args.spec, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func boolValue(v bool) *bool {
|
|
return &v
|
|
}
|
|
|
|
func TestHelmState_flagsForUpgrade(t *testing.T) {
|
|
enable := true
|
|
disable := false
|
|
postRendererDefault := "foo-default.sh"
|
|
postRendererRelease := "foo-release.sh"
|
|
some := func(v int) *int {
|
|
return &v
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
version *semver.Version
|
|
defaults HelmSpec
|
|
release *ReleaseSpec
|
|
syncOpts *SyncOpts
|
|
want []string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "no-options",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "verify",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--verify",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "verify-from-default",
|
|
defaults: HelmSpec{
|
|
Verify: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "enable-dns",
|
|
defaults: HelmSpec{
|
|
EnableDNS: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
EnableDNS: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--enable-dns",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "enable-dns-from-default",
|
|
defaults: HelmSpec{
|
|
EnableDNS: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
EnableDNS: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "force",
|
|
defaults: HelmSpec{
|
|
Force: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Force: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--force",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "force-from-default",
|
|
defaults: HelmSpec{
|
|
Force: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Force: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "recreate-pods",
|
|
defaults: HelmSpec{
|
|
RecreatePods: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
RecreatePods: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--recreate-pods",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "recreate-pods-from-default",
|
|
defaults: HelmSpec{
|
|
RecreatePods: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
RecreatePods: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "wait",
|
|
defaults: HelmSpec{
|
|
Wait: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Wait: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--wait",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "wait-for-jobs",
|
|
defaults: HelmSpec{
|
|
WaitForJobs: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
WaitForJobs: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--wait-for-jobs",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "devel",
|
|
defaults: HelmSpec{
|
|
Devel: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Wait: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--devel",
|
|
"--wait",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "devel-release",
|
|
defaults: HelmSpec{
|
|
Devel: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Devel: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "wait-from-default",
|
|
defaults: HelmSpec{
|
|
Wait: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Wait: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "timeout",
|
|
defaults: HelmSpec{
|
|
Timeout: 0,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Timeout: some(123),
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--timeout", "123s",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "timeout-from-default",
|
|
defaults: HelmSpec{
|
|
Timeout: 123,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Timeout: nil,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--timeout", "123s",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "timeout-from-cli-flag",
|
|
defaults: HelmSpec{
|
|
Timeout: 123,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Timeout: some(456),
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
syncOpts: &SyncOpts{
|
|
Timeout: 789,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--timeout", "789s",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "atomic",
|
|
defaults: HelmSpec{
|
|
Atomic: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Atomic: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--atomic",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "atomic-override-default",
|
|
defaults: HelmSpec{
|
|
Atomic: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Atomic: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "atomic-from-default",
|
|
defaults: HelmSpec{
|
|
Atomic: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--atomic",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "cleanup-on-fail",
|
|
defaults: HelmSpec{
|
|
CleanupOnFail: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
CleanupOnFail: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--cleanup-on-fail",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "cleanup-on-fail-override-default",
|
|
defaults: HelmSpec{
|
|
CleanupOnFail: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
CleanupOnFail: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "cleanup-on-fail-from-default",
|
|
defaults: HelmSpec{
|
|
CleanupOnFail: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--cleanup-on-fail",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-default-helm3.2",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
},
|
|
version: semver.MustParse("3.2.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--create-namespace",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-disabled-helm3.2",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &disable,
|
|
},
|
|
version: semver.MustParse("3.2.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-release-override-enabled-helm3.2",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &disable,
|
|
},
|
|
version: semver.MustParse("3.2.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &enable,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--create-namespace",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-release-override-disabled-helm3.2",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.2.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-unsupported",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("2.16.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
wantErr: "releases[].createNamespace requires Helm 3.2.0 or greater",
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-helmdefault",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
PostRenderer: &postRendererDefault,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererDefault,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-release",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
PostRenderer: &postRendererRelease,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererRelease,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-release-prior-helmdefault",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
PostRenderer: &postRendererDefault,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
PostRenderer: &postRendererRelease,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererRelease,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
basePath: "./",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{*tt.release},
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Version: tt.version,
|
|
}
|
|
|
|
args, _, err := state.flagsForUpgrade(helm, tt.release, 0, tt.syncOpts)
|
|
if err != nil && tt.wantErr == "" {
|
|
t.Errorf("unexpected error flagsForUpgrade: %v", err)
|
|
}
|
|
if tt.wantErr != "" && (err == nil || err.Error() != tt.wantErr) {
|
|
t.Errorf("expected error '%v'; got '%v'", err, tt.wantErr)
|
|
}
|
|
if !reflect.DeepEqual(args, tt.want) {
|
|
t.Errorf("flagsForUpgrade returned = %v, want %v", args, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_flagsForTemplate(t *testing.T) {
|
|
enable := true
|
|
disable := false
|
|
postRendererDefault := "foo-default.sh"
|
|
postRendererRelease := "foo-release.sh"
|
|
|
|
tests := []struct {
|
|
name string
|
|
version *semver.Version
|
|
defaults HelmSpec
|
|
release *ReleaseSpec
|
|
templateOpts TemplateOpts
|
|
environments map[string]EnvironmentSpec
|
|
envName string
|
|
want []string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "post-renderer-flags-use-helmdefault",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
PostRenderer: &postRendererDefault,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererDefault,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-release",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
PostRenderer: &postRendererRelease,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererRelease,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-release-prior-helmdefault",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
PostRenderer: &postRendererDefault,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
PostRenderer: &postRendererRelease,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererRelease,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "kube-version-flag-should-be-used",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
},
|
|
templateOpts: TemplateOpts{
|
|
KubeVersion: "1.100",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--kube-version", "1.100",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "kube-version-flag-should-be-respected",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
KubeVersion: "1.25",
|
|
},
|
|
templateOpts: TemplateOpts{
|
|
KubeVersion: "1.100",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--kube-version", "1.100",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
// Issue #2309: kube-context tests
|
|
{
|
|
name: "kube-context-from-release",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
KubeContext: "release-context",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--kube-context", "release-context",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "kube-context-from-helmdefaults",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
KubeContext: "default-context",
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--kube-context", "default-context",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "kube-context-release-overrides-helmdefaults",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
KubeContext: "default-context",
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
KubeContext: "release-context",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--kube-context", "release-context",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "kube-context-from-environment",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
environments: map[string]EnvironmentSpec{
|
|
"production": {KubeContext: "env-context"},
|
|
},
|
|
envName: "production",
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--kube-context", "env-context",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
envName := tt.envName
|
|
if envName == "" {
|
|
envName = "default"
|
|
}
|
|
environments := tt.environments
|
|
if environments == nil {
|
|
environments = make(map[string]EnvironmentSpec)
|
|
}
|
|
state := &HelmState{
|
|
basePath: "./",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{*tt.release},
|
|
HelmDefaults: tt.defaults,
|
|
Environments: environments,
|
|
Env: environment.Environment{Name: envName},
|
|
},
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Version: tt.version,
|
|
}
|
|
|
|
args, _, err := state.flagsForTemplate(helm, tt.release, 0, &(tt.templateOpts))
|
|
if err != nil && tt.wantErr == "" {
|
|
t.Errorf("unexpected error flagsForUpgrade: %v", err)
|
|
}
|
|
if tt.wantErr != "" && (err == nil || err.Error() != tt.wantErr) {
|
|
t.Errorf("expected error '%v'; got '%v'", err, tt.wantErr)
|
|
}
|
|
if !reflect.DeepEqual(args, tt.want) {
|
|
t.Errorf("flagsForUpgrade returned = %v, want %v", args, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_isLocalChart(t *testing.T) {
|
|
type args struct {
|
|
chart string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{
|
|
name: "local chart",
|
|
args: args{
|
|
chart: "./",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "repo chart",
|
|
args: args{
|
|
chart: "stable/genius",
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "empty",
|
|
args: args{
|
|
chart: "",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "parent local path",
|
|
args: args{
|
|
chart: "../examples",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "parent-parent local path",
|
|
args: args{
|
|
chart: "../../",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "absolute path",
|
|
args: args{
|
|
chart: "/foo/bar/baz",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "remote chart in 3-level deep dir (e.g. ChartCenter)",
|
|
args: args{
|
|
chart: "center/bar/baz",
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := isLocalChart(tt.args.chart); got != tt.want {
|
|
t.Errorf("%s(\"%s\") isLocalChart(): got %v, want %v", tt.name, tt.args.chart, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_normalizeChart(t *testing.T) {
|
|
type args struct {
|
|
basePath string
|
|
chart string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
}{
|
|
{
|
|
name: "construct local chart path",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "./app",
|
|
},
|
|
want: "/src/app",
|
|
},
|
|
{
|
|
name: "construct local chart path, without leading dot",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "published",
|
|
},
|
|
want: "/src/published",
|
|
},
|
|
{
|
|
name: "repo path",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "remote/app",
|
|
},
|
|
want: "remote/app",
|
|
},
|
|
{
|
|
name: "chartcenter repo path",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "center/stable/myapp",
|
|
},
|
|
want: "center/stable/myapp",
|
|
},
|
|
{
|
|
name: "construct local chart path, sibling dir",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "../app",
|
|
},
|
|
want: "/app",
|
|
},
|
|
{
|
|
name: "construct local chart path, parent dir",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "./..",
|
|
},
|
|
want: "/",
|
|
},
|
|
{
|
|
name: "too much parent levels",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "../../app",
|
|
},
|
|
want: "/app",
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := normalizeChart(tt.args.basePath, tt.args.chart); got != tt.want {
|
|
t.Errorf("normalizeChart() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// mocking helmexec.Interface
|
|
func TestHelmState_SyncRepos(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
repos []RepositorySpec
|
|
helm *exectest.Helm
|
|
envs map[string]string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "normal repository",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "",
|
|
Password: "",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "", "", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "ACR hosted repository",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
Managed: "acr",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "", "", "", "", "", "", "acr", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with cert and key",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "certfile",
|
|
KeyFile: "keyfile",
|
|
Username: "",
|
|
Password: "",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "certfile", "keyfile", "", "", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with ca file",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CaFile: "cafile",
|
|
Username: "",
|
|
Password: "",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "cafile", "", "", "", "", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with username and password",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "example_user",
|
|
Password: "example_password",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with username and password and pass-credentials",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "example_user",
|
|
Password: "example_password",
|
|
PassCredentials: true,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password", "", "true", "false"},
|
|
},
|
|
{
|
|
name: "repository without username and password and environment with username and password",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "",
|
|
Password: "",
|
|
},
|
|
},
|
|
envs: map[string]string{
|
|
"NAME_USERNAME": "example_user",
|
|
"NAME_PASSWORD": "example_password",
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with username and password and environment with username and password",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "example_user1",
|
|
Password: "example_password1",
|
|
},
|
|
},
|
|
envs: map[string]string{
|
|
"NAME_USERNAME": "example_user2",
|
|
"NAME_PASSWORD": "example_password2",
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "example_user1", "example_password1", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with skip-tls-verify",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "",
|
|
Password: "",
|
|
SkipTLSVerify: true,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "", "", "", "false", "true"},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
for k, v := range tt.envs {
|
|
t.Setenv(k, v)
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: tt.repos,
|
|
},
|
|
}
|
|
if _, _ = state.SyncRepos(tt.helm, map[string]bool{}); !reflect.DeepEqual(tt.helm.Repo, tt.want) {
|
|
t.Errorf("HelmState.SyncRepos() for [%s] = %v, want %v", tt.name, tt.helm.Repo, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleases(t *testing.T) {
|
|
postRenderer := "foo.sh"
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
wantReleases []exectest.Release
|
|
wantErrorMsgs []string
|
|
}{
|
|
{
|
|
name: "normal release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--reset-values"}}},
|
|
},
|
|
{
|
|
name: "escaped values",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "someList",
|
|
Value: "a,b,c",
|
|
},
|
|
{
|
|
Name: "json",
|
|
Value: "{\"name\": \"john\"}",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}", "--reset-values"}}},
|
|
},
|
|
{
|
|
name: "set single value from file",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "foo",
|
|
Value: "FOO",
|
|
},
|
|
{
|
|
Name: "bar",
|
|
File: "path/to/bar",
|
|
},
|
|
{
|
|
Name: "baz",
|
|
Value: "BAZ",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ", "--reset-values"}}},
|
|
},
|
|
{
|
|
name: "set single array value in an array",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "foo.bar[0]",
|
|
Values: []string{
|
|
"A",
|
|
"B",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}", "--reset-values"}}},
|
|
},
|
|
{
|
|
name: "post renderer helm 3",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
PostRenderer: &postRenderer,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{Helm3: true}, // Helm 3 keeps script paths unchanged
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--post-renderer", postRenderer, "--reset-values"}}},
|
|
},
|
|
{
|
|
name: "post renderer helm 4",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
PostRenderer: &postRenderer,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{Helm4: true}, // Helm 4 converts script paths to plugin names
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--post-renderer", "foo", "--reset-values"}}}, // "foo.sh" -> "foo"
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); len(errs) > 0 {
|
|
if len(errs) != len(tt.wantErrorMsgs) {
|
|
t.Fatalf("Unexpected errors: %v\nExpected: %v", errs, tt.wantErrorMsgs)
|
|
}
|
|
var mismatch int
|
|
for i := range tt.wantErrorMsgs {
|
|
expected := tt.wantErrorMsgs[i]
|
|
actual := errs[i].Error()
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Errorf("Unexpected error: expected=%v, got=%v", expected, actual)
|
|
}
|
|
}
|
|
if mismatch > 0 {
|
|
t.Fatalf("%d unexpected errors detected", mismatch)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(tt.helm.Releases, tt.wantReleases) {
|
|
t.Errorf("HelmState.SyncReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.wantReleases)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing.T) {
|
|
no := false
|
|
tests := []struct {
|
|
name string
|
|
release ReleaseSpec
|
|
listResult string
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "should install",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
},
|
|
listResult: ``,
|
|
expectedError: ``,
|
|
},
|
|
{
|
|
name: "should upgrade",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
expectedError: ``,
|
|
},
|
|
{
|
|
name: "should uninstall",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
Installed: &no,
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
expectedError: ``,
|
|
},
|
|
{
|
|
name: "should fail installing due to missing values file",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
Values: []any{"noexistent.values.yaml"},
|
|
},
|
|
listResult: ``,
|
|
expectedError: `failed processing release foo: values file matching "noexistent.values.yaml" does not exist in "."`,
|
|
},
|
|
{
|
|
name: "should fail upgrading due to missing values file",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
Values: []any{"noexistent.values.yaml"},
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
expectedError: `failed processing release foo: values file matching "noexistent.values.yaml" does not exist in "."`,
|
|
},
|
|
{
|
|
name: "should uninstall even when there is a missing values file",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
Values: []any{"noexistent.values.yaml"},
|
|
Installed: &no,
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
expectedError: ``,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
basePath: ".",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{tt.release},
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
fs := testhelper.NewTestFs(map[string]string{})
|
|
state = injectFs(state, fs)
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
//simulate the helm.list call result
|
|
helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$"}] = tt.listResult
|
|
|
|
affectedReleases := AffectedReleases{}
|
|
errs := state.SyncReleases(&affectedReleases, helm, []string{}, 1)
|
|
|
|
if tt.expectedError != "" {
|
|
if len(errs) == 0 {
|
|
t.Fatalf("expected error not occurred: expected=%s, got none", tt.expectedError)
|
|
}
|
|
if len(errs) != 1 {
|
|
t.Fatalf("too many errors: expected %d, got %d: %v", 1, len(errs), errs)
|
|
}
|
|
err := errs[0]
|
|
if err.Error() != tt.expectedError {
|
|
t.Fatalf("unexpected error: expected=%s, got=%v", tt.expectedError, err)
|
|
}
|
|
} else {
|
|
if len(errs) > 0 {
|
|
t.Fatalf("unexpected error(s): expected=0, got=%d: %v", len(errs), errs)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
|
|
no := false
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
installed []bool
|
|
wantAffected exectest.Affected
|
|
}{
|
|
{
|
|
name: "2 release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo",
|
|
Chart: "foo",
|
|
},
|
|
{
|
|
Name: "releaseNameBar",
|
|
Chart: "bar",
|
|
},
|
|
},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: []*exectest.Release{
|
|
{Name: "releaseNameFoo", Flags: []string{}},
|
|
{Name: "releaseNameBar", Flags: []string{}},
|
|
},
|
|
Deleted: nil,
|
|
Failed: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "2 removed",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo",
|
|
Chart: "foo",
|
|
Installed: &no,
|
|
},
|
|
{
|
|
Name: "releaseNameBar",
|
|
Chart: "foo",
|
|
Installed: &no,
|
|
},
|
|
},
|
|
installed: []bool{true, true},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: nil,
|
|
Deleted: []*exectest.Release{
|
|
{Name: "releaseNameFoo", Flags: []string{}},
|
|
{Name: "releaseNameBar", Flags: []string{}},
|
|
},
|
|
Failed: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "2 errors",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo-error",
|
|
Chart: "foo",
|
|
},
|
|
{
|
|
Name: "releaseNameBar-error",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: nil,
|
|
Deleted: nil,
|
|
Failed: []*exectest.Release{
|
|
{Name: "releaseNameFoo-error", Flags: []string{}},
|
|
{Name: "releaseNameBar-error", Flags: []string{}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "1 removed, 1 new, 1 error",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo",
|
|
Chart: "foo",
|
|
},
|
|
{
|
|
Name: "releaseNameBar",
|
|
Chart: "foo",
|
|
Installed: &no,
|
|
},
|
|
{
|
|
Name: "releaseNameFoo-error",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
installed: []bool{true, true, true},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: []*exectest.Release{
|
|
{Name: "releaseNameFoo", Flags: []string{}},
|
|
},
|
|
Deleted: []*exectest.Release{
|
|
{Name: "releaseNameBar", Flags: []string{}},
|
|
},
|
|
Failed: []*exectest.Release{
|
|
{Name: "releaseNameFoo-error", Flags: []string{}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
//simulate the release is already installed
|
|
for i, release := range tt.releases {
|
|
if tt.installed != nil && tt.installed[i] {
|
|
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = release.Name
|
|
}
|
|
}
|
|
|
|
affectedReleases := AffectedReleases{}
|
|
if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil {
|
|
if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) {
|
|
t.Errorf("HelmState.SynchAffectedRelease() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed)
|
|
} //else expected error
|
|
}
|
|
if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) {
|
|
t.Errorf("HelmState.SynchAffectedRelease() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded)
|
|
}
|
|
if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) {
|
|
t.Errorf("HelmState.SynchAffectedRelease() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleasesAffectedReleasesWithReinstallIfForbidden(t *testing.T) {
|
|
no := false
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
installed []bool
|
|
wantAffected exectest.Affected
|
|
}{
|
|
{
|
|
name: "2 new",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo-forbidden",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
},
|
|
{
|
|
Name: "releaseNameBar-forbidden",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
},
|
|
},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: []*exectest.Release{
|
|
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
|
{Name: "releaseNameBar-forbidden", Flags: []string{}},
|
|
},
|
|
Reinstalled: nil,
|
|
Deleted: nil,
|
|
Failed: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "1 removed, 1 new, 1 reinstalled first new",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo-forbidden",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
},
|
|
{
|
|
Name: "releaseNameBar",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
Installed: &no,
|
|
},
|
|
{
|
|
Name: "releaseNameFoo-forbidden",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
},
|
|
},
|
|
installed: []bool{true, true, true},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: []*exectest.Release{
|
|
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
|
},
|
|
Reinstalled: []*exectest.Release{
|
|
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
|
},
|
|
Deleted: []*exectest.Release{
|
|
{Name: "releaseNameBar", Flags: []string{}},
|
|
},
|
|
Failed: nil,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
//simulate the release is already installed
|
|
for i, release := range tt.releases {
|
|
if tt.installed != nil && tt.installed[i] {
|
|
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = release.Name
|
|
}
|
|
}
|
|
|
|
affectedReleases := AffectedReleases{}
|
|
if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil {
|
|
if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) {
|
|
t.Errorf("HelmState.SyncReleases() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed)
|
|
} //else expected error
|
|
}
|
|
if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) {
|
|
t.Errorf("HelmState.SyncReleases() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded)
|
|
}
|
|
if !testEq(affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) {
|
|
t.Errorf("HelmState.SyncReleases() reinstalled failed for [%s] = %v, want %v", tt.name, affectedReleases.Reinstalled, tt.wantAffected.Reinstalled)
|
|
}
|
|
if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) {
|
|
t.Errorf("HelmState.SyncReleases() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testEq(a []*ReleaseSpec, b []*exectest.Release) bool {
|
|
// If one is nil, the other must also be nil.
|
|
if (a == nil) != (b == nil) {
|
|
return false
|
|
}
|
|
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
for i := range a {
|
|
if a[i].Name != b[i].Name {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func TestGetDeployedVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
release ReleaseSpec
|
|
listResult string
|
|
installedVersion string
|
|
}{
|
|
{
|
|
name: "chart version",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
installedVersion: "2.0.4",
|
|
},
|
|
{
|
|
name: "chart version with a dash",
|
|
release: ReleaseSpec{
|
|
Name: "foo-bar",
|
|
Chart: "registry/foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-1.0.0-alpha.1 0.1.0 default`,
|
|
installedVersion: "1.0.0-alpha.1",
|
|
},
|
|
{
|
|
name: "chart version with dash and plus",
|
|
release: ReleaseSpec{
|
|
Name: "foo-bar",
|
|
Chart: "registry/foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-1.0.0-alpha+001 0.1.0 default`,
|
|
installedVersion: "1.0.0-alpha+001",
|
|
},
|
|
{
|
|
name: "chart version with dash and release with dash",
|
|
release: ReleaseSpec{
|
|
Name: "foo-bar",
|
|
Chart: "registry/foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo-bar-release 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-1.0.0-alpha+001 0.1.0 default`,
|
|
installedVersion: "1.0.0-alpha+001",
|
|
},
|
|
{
|
|
name: "chart version from helm show chart",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar 0.1.0 default`,
|
|
installedVersion: "3.2.0",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{tt.release},
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
// simulate the helm.list call result
|
|
helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = tt.listResult
|
|
|
|
affectedReleases := AffectedReleases{}
|
|
state.SyncReleases(&affectedReleases, helm, []string{}, 1)
|
|
|
|
if state.Releases[0].installedVersion != tt.installedVersion {
|
|
t.Errorf("HelmState.TestGetDeployedVersion() failed for [%s] = %v, want %v", tt.name, state.Releases[0].installedVersion, tt.installedVersion)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_DiffReleases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
wantReleases []exectest.Release
|
|
}{
|
|
{
|
|
name: "normal release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--reset-values"}}},
|
|
},
|
|
{
|
|
name: "escaped values",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "someList",
|
|
Value: "a,b,c",
|
|
},
|
|
{
|
|
Name: "json",
|
|
Value: "{\"name\": \"john\"}",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{
|
|
{Name: "releaseName", Flags: []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}", "--reset-values"}},
|
|
},
|
|
},
|
|
{
|
|
name: "set single value from file",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "foo",
|
|
Value: "FOO",
|
|
},
|
|
{
|
|
Name: "bar",
|
|
File: "path/to/bar",
|
|
},
|
|
{
|
|
Name: "baz",
|
|
Value: "BAZ",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{
|
|
{Name: "releaseName", Flags: []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ", "--reset-values"}},
|
|
},
|
|
},
|
|
{
|
|
name: "set single array value in an array",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "foo.bar[0]",
|
|
Values: []string{
|
|
"A",
|
|
"B",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{
|
|
{Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}", "--reset-values"}},
|
|
},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, []string{}, false, false, false, false, false)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected error: %v", errs)
|
|
}
|
|
if !reflect.DeepEqual(tt.helm.Diffed, tt.wantReleases) {
|
|
t.Errorf("HelmState.DiffReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.wantReleases)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_DiffFlags(t *testing.T) {
|
|
enable := true
|
|
disable := false
|
|
|
|
tests := []struct {
|
|
name string
|
|
defaults HelmSpec
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
wantDiffFlags []string
|
|
}{
|
|
{
|
|
name: "release with api version and kubeversion",
|
|
defaults: HelmSpec{},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
KubeVersion: "1.21",
|
|
ApiVersions: []string{"helmfile.test/v1", "helmfile.test/v2"},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: []string{"--api-versions", "helmfile.test/v1", "--api-versions", "helmfile.test/v2", "--kube-version", "1.21"},
|
|
},
|
|
{
|
|
name: "release with kubeversion and plain http which is ignored",
|
|
defaults: HelmSpec{},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
KubeVersion: "1.21",
|
|
PlainHttp: true,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: []string{"--kube-version", "1.21"},
|
|
},
|
|
{
|
|
name: "release with enable-dns",
|
|
defaults: HelmSpec{EnableDNS: false},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
EnableDNS: &enable,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: []string{"--enable-dns"},
|
|
},
|
|
{
|
|
name: "release with disable-dns override",
|
|
defaults: HelmSpec{EnableDNS: true},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
EnableDNS: &disable,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: nil,
|
|
},
|
|
{
|
|
name: "release with enable-dns from default",
|
|
defaults: HelmSpec{EnableDNS: true},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: []string{"--enable-dns"},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
for j := range tt.releases {
|
|
flags, _, errs := state.flagsForDiff(tt.helm, &tt.releases[j], false, 1, nil)
|
|
if errs != nil {
|
|
t.Errorf("unexpected error: %v", errs)
|
|
}
|
|
if !reflect.DeepEqual(flags, tt.wantDiffFlags) {
|
|
t.Errorf("HelmState.flagsForDiff() for [%s][%s] = %v, want %v", tt.name, tt.releases[j].Name, flags, tt.wantDiffFlags)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleasesCleanup(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
expectedNumRemovedFiles int
|
|
}{
|
|
{
|
|
name: "normal release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 0,
|
|
},
|
|
{
|
|
name: "inline values",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
Values: []any{
|
|
map[any]any{
|
|
"someList": "a,b,c",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 1,
|
|
},
|
|
{
|
|
name: "inline values and values file",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
Values: []any{
|
|
map[any]any{
|
|
"someList": "a,b,c",
|
|
},
|
|
"someFile",
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 2,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
numRemovedFiles := 0
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
testfs := testhelper.NewTestFs(map[string]string{
|
|
"/path/to/someFile": `foo: FOO`,
|
|
})
|
|
testfs.DeleteFile = func(f string) error {
|
|
numRemovedFiles += 1
|
|
return nil
|
|
}
|
|
state = injectFs(state, testfs)
|
|
if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); len(errs) > 0 {
|
|
t.Errorf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
if errs := state.Clean(); len(errs) > 0 {
|
|
t.Errorf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
if numRemovedFiles != tt.expectedNumRemovedFiles {
|
|
t.Errorf("unexpected number of removed files: expected %d, got %d", tt.expectedNumRemovedFiles, numRemovedFiles)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_DiffReleasesCleanup(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
expectedNumRemovedFiles int
|
|
}{
|
|
{
|
|
name: "normal release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 0,
|
|
},
|
|
{
|
|
name: "inline values",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
Values: []any{
|
|
map[any]any{
|
|
"someList": "a,b,c",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 1,
|
|
},
|
|
{
|
|
name: "inline values and values file",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
Values: []any{
|
|
map[any]any{
|
|
"someList": "a,b,c",
|
|
},
|
|
"someFile",
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 2,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
numRemovedFiles := 0
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
testfs := testhelper.NewTestFs(map[string]string{
|
|
"/path/to/someFile": `foo: bar
|
|
`,
|
|
})
|
|
testfs.DeleteFile = func(f string) error {
|
|
numRemovedFiles += 1
|
|
return nil
|
|
}
|
|
state = injectFs(state, testfs)
|
|
if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, []string{}, false, false, false, false, false); len(errs) > 0 {
|
|
t.Errorf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
if errs := state.Clean(); len(errs) > 0 {
|
|
t.Errorf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
if numRemovedFiles != tt.expectedNumRemovedFiles {
|
|
t.Errorf("unexpected number of removed files: expected %d, got %d", tt.expectedNumRemovedFiles, numRemovedFiles)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_UpdateDeps(t *testing.T) {
|
|
helm := &exectest.Helm{
|
|
UpdateDepsCallbacks: map[string]func(string) error{},
|
|
}
|
|
|
|
var generatedDir string
|
|
tempDir := func(dir, prefix string) (string, error) {
|
|
var err error
|
|
generatedDir, err = os.MkdirTemp(dir, prefix)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// nolint: unparam
|
|
helm.UpdateDepsCallbacks[generatedDir] = func(chart string) error {
|
|
content := []byte(`dependencies:
|
|
- name: envoy
|
|
repository: https://kubernetes-charts.storage.googleapis.com
|
|
version: 1.5.0
|
|
- name: envoy
|
|
repository: https://kubernetes-charts.storage.googleapis.com
|
|
version: 1.4.0
|
|
digest: sha256:8194b597c85bb3d1fee8476d4a486e952681d5c65f185ad5809f2118bc4079b5
|
|
generated: 2019-05-16T15:42:45.50486+09:00
|
|
`)
|
|
filename := filepath.Join(generatedDir, "Chart.lock")
|
|
logger.Debugf("test: writing %s: %s", filename, content)
|
|
return os.WriteFile(filename, content, 0644)
|
|
}
|
|
return generatedDir, nil
|
|
}
|
|
|
|
logger := helmexec.NewLogger(io.Discard, "debug")
|
|
basePath := "/src"
|
|
state := &HelmState{
|
|
basePath: basePath,
|
|
FilePath: "/src/helmfile.yaml",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "/example",
|
|
},
|
|
{
|
|
Chart: "./example",
|
|
},
|
|
{
|
|
Chart: "published/deeper",
|
|
},
|
|
{
|
|
Chart: "stable/envoy",
|
|
Version: "1.5.0",
|
|
},
|
|
{
|
|
Chart: "stable/envoy",
|
|
Version: "1.4.0",
|
|
},
|
|
},
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "stable",
|
|
URL: "https://kubernetes-charts.storage.googleapis.com",
|
|
},
|
|
},
|
|
},
|
|
tempDir: tempDir,
|
|
logger: logger,
|
|
}
|
|
|
|
fs := testhelper.NewTestFs(map[string]string{
|
|
"/example/Chart.yaml": `foo: FOO`,
|
|
"/src/example/Chart.yaml": `foo: FOO`,
|
|
})
|
|
fs.Cwd = basePath
|
|
state = injectFs(state, fs)
|
|
errs := state.UpdateDeps(helm, false)
|
|
|
|
want := []string{"/example", "./example", generatedDir}
|
|
if !reflect.DeepEqual(helm.Charts, want) {
|
|
t.Errorf("HelmState.UpdateDeps() = %v, want %v", helm.Charts, want)
|
|
}
|
|
if len(errs) != 0 {
|
|
t.Errorf("HelmState.UpdateDeps() - unexpected %d errors: %v", len(errs), errs)
|
|
}
|
|
|
|
resolved, err := state.ResolveDeps()
|
|
if err != nil {
|
|
t.Errorf("HelmState.ResolveDeps() - unexpected error: %v", err)
|
|
}
|
|
|
|
if resolved.Releases[3].Version != "1.5.0" {
|
|
t.Errorf("HelmState.ResolveDeps() - unexpected version number: expected=1.5.0, got=%s", resolved.Releases[5].Version)
|
|
}
|
|
if resolved.Releases[4].Version != "1.4.0" {
|
|
t.Errorf("HelmState.ResolveDeps() - unexpected version number: expected=1.4.0, got=%s", resolved.Releases[6].Version)
|
|
}
|
|
}
|
|
|
|
func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) {
|
|
logger := helmexec.NewLogger(io.Discard, "debug")
|
|
state := &HelmState{
|
|
basePath: "/src",
|
|
FilePath: "/src/helmfile.yaml",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "./..",
|
|
},
|
|
{
|
|
Chart: "../examples",
|
|
},
|
|
{
|
|
Chart: "../../helmfile",
|
|
},
|
|
{
|
|
Chart: "published",
|
|
},
|
|
{
|
|
Chart: "published/deeper",
|
|
},
|
|
{
|
|
Chart: "stable/envoy",
|
|
},
|
|
},
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "stable",
|
|
URL: "https://kubernetes-charts.storage.googleapis.com",
|
|
},
|
|
},
|
|
},
|
|
logger: logger,
|
|
fs: &filesystem.FileSystem{
|
|
ReadFile: func(f string) ([]byte, error) {
|
|
if f != "helmfile.lock" {
|
|
return nil, fmt.Errorf("stub: unexpected file: %s", f)
|
|
}
|
|
return nil, os.ErrNotExist
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := state.ResolveDeps()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHelmState_ResolveDeps_NoLockFile_WithCustomLockFile(t *testing.T) {
|
|
logger := helmexec.NewLogger(io.Discard, "debug")
|
|
state := &HelmState{
|
|
basePath: "/src",
|
|
FilePath: "/src/helmfile.yaml",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
LockFile: "custom-lock-file",
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "./..",
|
|
},
|
|
{
|
|
Chart: "../examples",
|
|
},
|
|
{
|
|
Chart: "../../helmfile",
|
|
},
|
|
{
|
|
Chart: "published",
|
|
},
|
|
{
|
|
Chart: "published/deeper",
|
|
},
|
|
{
|
|
Chart: "stable/envoy",
|
|
},
|
|
},
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "stable",
|
|
URL: "https://kubernetes-charts.storage.googleapis.com",
|
|
},
|
|
},
|
|
},
|
|
logger: logger,
|
|
fs: &filesystem.FileSystem{
|
|
ReadFile: func(f string) ([]byte, error) {
|
|
if f != filepath.Join("/src", "custom-lock-file") {
|
|
return nil, fmt.Errorf("stub: unexpected file: %s", f)
|
|
}
|
|
return nil, os.ErrNotExist
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := state.ResolveDeps()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
func TestHelmState_ReleaseStatuses(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
want []exectest.Release
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseA",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []exectest.Release{
|
|
{Name: "releaseA", Flags: []string{}},
|
|
},
|
|
},
|
|
{
|
|
name: "happy path",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "error",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "complain missing values file for desired release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "error",
|
|
Values: []any{
|
|
"foo.yaml",
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should not complain missing values file for undesired release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "error",
|
|
Values: []any{
|
|
"foo.yaml",
|
|
},
|
|
Installed: boolValue(false),
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
f := func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
fs: &filesystem.FileSystem{
|
|
FileExists: func(f string) (bool, error) {
|
|
if f != "foo.yaml" {
|
|
return false, fmt.Errorf("unexpected file: %s", f)
|
|
}
|
|
return true, nil
|
|
},
|
|
ReadFile: func(f string) ([]byte, error) {
|
|
if f != "foo.yaml" {
|
|
return nil, fmt.Errorf("unexpected file: %s", f)
|
|
}
|
|
return []byte{}, nil
|
|
},
|
|
},
|
|
}
|
|
errs := state.ReleaseStatuses(tt.helm, 1)
|
|
if (errs != nil) != tt.wantErr {
|
|
t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(tt.helm.Releases, tt.want) {
|
|
t.Errorf("HelmState.ReleaseStatuses() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.want)
|
|
}
|
|
}
|
|
t.Run(tt.name, f)
|
|
}
|
|
}
|
|
|
|
func TestHelmState_TestReleasesNoCleanUp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cleanup bool
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
want []exectest.Release
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseA",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []exectest.Release{{Name: "releaseA", Flags: []string{"--timeout", "1s"}}},
|
|
},
|
|
{
|
|
name: "do cleanup",
|
|
cleanup: true,
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseB",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []exectest.Release{{Name: "releaseB", Flags: []string{"--timeout", "1s"}}},
|
|
},
|
|
{
|
|
name: "happy path",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "error",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
}
|
|
errs := state.TestReleases(tt.helm, tt.cleanup, 1, 1)
|
|
if (errs != nil) != tt.wantErr {
|
|
t.Errorf("TestReleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(tt.helm.Releases, tt.want) {
|
|
t.Errorf("HelmState.TestReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConditionEnabled(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
condition string
|
|
values map[string]any
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "enabled",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{
|
|
"foo": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "disabled",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{
|
|
"foo": map[string]any{
|
|
"enabled": false,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "typo in condition",
|
|
condition: "fooo.enabled",
|
|
values: map[string]any{
|
|
"foo": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing enabled",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{
|
|
"foo": map[string]any{
|
|
"something else": false,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "foo nil",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{
|
|
"foo": nil,
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "foo missing",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "wrong suffix",
|
|
condition: "services.foo_enabled",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "too short condition",
|
|
condition: "rnd42",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nested values",
|
|
condition: "rnd42.really.enabled",
|
|
values: map[string]any{
|
|
"rnd42": map[string]any{
|
|
"really": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "nested values enabled missing",
|
|
condition: "rnd42.really.ok",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nested values unknown key",
|
|
condition: "rnd42.unknown.enabled",
|
|
values: map[string]any{
|
|
"rnd42": map[string]any{
|
|
"really": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nested values invalid type",
|
|
condition: "rnd42.invalid.enabled",
|
|
values: map[string]any{
|
|
"rnd42": map[string]any{
|
|
"invalid": "hello",
|
|
"really": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty",
|
|
condition: "",
|
|
want: true,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
res, err := ConditionEnabled(ReleaseSpec{Condition: tt.condition}, tt.values)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("ConditionEnabled() for %s expected err response", tt.name)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Errorf("ConditionEnabled() for %s unexpected err %v", tt.name, err)
|
|
}
|
|
if res != tt.want {
|
|
t.Errorf("ConditionEnabled() for %s = %v, want %v", tt.name, res, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_NoReleaseMatched(t *testing.T) {
|
|
releases := []ReleaseSpec{
|
|
{
|
|
Name: "releaseA",
|
|
Labels: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
labels string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
|
|
labels: "foo=bar",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "name does not exist",
|
|
labels: "name=releaseB",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "label does not match anything",
|
|
labels: "foo=notbar",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
f := func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
},
|
|
logger: logger,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
state.Selectors = []string{tt.labels}
|
|
errs := state.FilterReleases(false)
|
|
if (errs != nil) != tt.wantErr {
|
|
t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
|
return
|
|
}
|
|
}
|
|
t.Run(tt.name, f)
|
|
}
|
|
}
|
|
|
|
func TestHelmState_Delete(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
deleted []exectest.Release
|
|
wantErr bool
|
|
desired *bool
|
|
installed bool
|
|
purge bool
|
|
flags string
|
|
namespace string
|
|
kubeContext string
|
|
defKubeContext string
|
|
deleteWait bool
|
|
deleteTimeout int
|
|
}{
|
|
{
|
|
name: "delete wait enabled",
|
|
deleteWait: true,
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--wait"}}},
|
|
},
|
|
{
|
|
name: "delete wait with deleteTimeout",
|
|
deleteWait: true,
|
|
deleteTimeout: 800,
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--wait", "--timeout", "800s"}}},
|
|
},
|
|
{
|
|
name: "desired and installed (purge=false)",
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired(default) and installed (purge=false)",
|
|
wantErr: false,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired(default) and installed (purge=false) but error",
|
|
wantErr: true,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired and installed (purge=true)",
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: true,
|
|
purge: true,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired but not installed (purge=false)",
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: false,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired but not installed (purge=true)",
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: false,
|
|
purge: true,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "installed but filtered (purge=false)",
|
|
wantErr: false,
|
|
desired: boolValue(false),
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "installed but filtered (purge=true)",
|
|
wantErr: false,
|
|
desired: boolValue(false),
|
|
installed: true,
|
|
purge: true,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "not installed, and filtered (purge=false)",
|
|
wantErr: false,
|
|
desired: boolValue(false),
|
|
installed: false,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "not installed, and filtered (purge=true)",
|
|
wantErr: false,
|
|
desired: boolValue(false),
|
|
installed: false,
|
|
purge: true,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "with kubecontext",
|
|
wantErr: false,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: true,
|
|
kubeContext: "ctx",
|
|
flags: "--kube-contextctx",
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--kube-context", "ctx"}}},
|
|
},
|
|
{
|
|
name: "with default kubecontext",
|
|
wantErr: false,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: true,
|
|
defKubeContext: "defctx",
|
|
flags: "--kube-contextdefctx",
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--kube-context", "defctx"}}},
|
|
},
|
|
{
|
|
name: "with non-default and default kubecontexts",
|
|
wantErr: false,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: true,
|
|
kubeContext: "ctx",
|
|
defKubeContext: "defctx",
|
|
flags: "--kube-contextctx",
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--kube-context", "ctx"}}},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
name := "releaseA"
|
|
if tt.wantErr {
|
|
name = "releaseA-error"
|
|
}
|
|
release := ReleaseSpec{
|
|
Name: name,
|
|
Installed: tt.desired,
|
|
Namespace: tt.namespace,
|
|
KubeContext: tt.kubeContext,
|
|
}
|
|
releases := []ReleaseSpec{
|
|
release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
KubeContext: tt.defKubeContext,
|
|
DeleteWait: tt.deleteWait,
|
|
DeleteTimeout: tt.deleteTimeout,
|
|
},
|
|
Releases: releases,
|
|
},
|
|
logger: logger,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
Deleted: []exectest.Release{},
|
|
}
|
|
if tt.installed {
|
|
helm.Lists[exectest.ListKey{Filter: "^" + name + "$", Flags: tt.flags}] = name
|
|
}
|
|
affectedReleases := AffectedReleases{}
|
|
errs := state.DeleteReleases(&affectedReleases, helm, 1, tt.purge, "")
|
|
if errs != nil {
|
|
if !tt.wantErr || len(affectedReleases.DeleteFailed) != 1 || affectedReleases.DeleteFailed[0].Name != release.Name {
|
|
t.Errorf("DeleteReleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
|
return
|
|
}
|
|
} else if !(reflect.DeepEqual(tt.deleted, helm.Deleted) && (len(affectedReleases.Deleted) == len(tt.deleted))) {
|
|
t.Errorf("unexpected deletions happened: expected %v, got %v", tt.deleted, helm.Deleted)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDiffpareSyncReleases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
flags []string
|
|
diffOptions *DiffOpts
|
|
helmDefaults *HelmSpec
|
|
}{
|
|
{
|
|
name: "reuse-values",
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "reset-values",
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "default-reuse-values",
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
{
|
|
name: "force-reset-values",
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
{
|
|
name: "both-reset-reuse-values",
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "both-reset-reuse-default-reuse-values",
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
release := ReleaseSpec{
|
|
Name: tt.name,
|
|
}
|
|
releases := []ReleaseSpec{
|
|
release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
HelmDefaults: *tt.helmDefaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
results, es := state.prepareDiffReleases(helm, []string{}, 1, false, false, false, []string{}, false, false, false, tt.diffOptions)
|
|
|
|
require.Len(t, es, 0)
|
|
require.Len(t, results, 1)
|
|
|
|
r := results[0]
|
|
|
|
require.Equal(t, tt.flags, r.flags)
|
|
}
|
|
}
|
|
|
|
func TestPrepareSyncReleases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
flags []string
|
|
syncOptions *SyncOpts
|
|
helmDefaults *HelmSpec
|
|
}{
|
|
{
|
|
name: "reuse-values",
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "reset-values",
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "reuse-default-values",
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
{
|
|
name: "force-reset-values",
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
{
|
|
name: "both-reset-reuse-values",
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "both-reset-reuse-default-reuse-values",
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
release := ReleaseSpec{
|
|
Name: tt.name,
|
|
}
|
|
releases := []ReleaseSpec{
|
|
release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
HelmDefaults: *tt.helmDefaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
results, es := state.prepareSyncReleases(helm, []string{}, 1, tt.syncOptions)
|
|
|
|
require.Len(t, es, 0)
|
|
require.Len(t, results, 1)
|
|
|
|
r := results[0]
|
|
|
|
require.Equal(t, tt.flags, r.flags)
|
|
}
|
|
}
|
|
|
|
func TestReverse(t *testing.T) {
|
|
num := 8
|
|
st := &HelmState{}
|
|
|
|
for i := 0; i < num; i++ {
|
|
name := fmt.Sprintf("%d", i)
|
|
st.Helmfiles = append(st.Helmfiles, SubHelmfileSpec{
|
|
Path: name,
|
|
})
|
|
st.Releases = append(st.Releases, ReleaseSpec{
|
|
Name: name,
|
|
})
|
|
}
|
|
|
|
st.Reverse()
|
|
|
|
for i := 0; i < num; i++ {
|
|
j := num - 1 - i
|
|
want := fmt.Sprintf("%d", j)
|
|
|
|
if got := st.Helmfiles[i].Path; got != want {
|
|
t.Errorf("sub-helmfile at %d has incorrect path: want %q, got %q", i, want, got)
|
|
}
|
|
|
|
if got := st.Releases[i].Name; got != want {
|
|
t.Errorf("release at %d has incorrect name: want %q, got %q", i, want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_gatherUsernamePassword(t *testing.T) {
|
|
type args struct {
|
|
repoName string
|
|
username string
|
|
password string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
envUsernameKey string
|
|
envUsernameValue string
|
|
envPasswordKey string
|
|
envPasswordValue string
|
|
wantUsername string
|
|
wantPassword string
|
|
}{
|
|
{
|
|
name: "pass username/password from args",
|
|
args: args{
|
|
repoName: "myRegistry",
|
|
username: "username1",
|
|
password: "password1",
|
|
},
|
|
wantUsername: "username1",
|
|
wantPassword: "password1",
|
|
},
|
|
{
|
|
name: "repoName does not contain hyphen, read username/password from environment variables",
|
|
args: args{
|
|
repoName: "myRegistry",
|
|
},
|
|
envUsernameKey: "MYREGISTRY_USERNAME",
|
|
envUsernameValue: "username2",
|
|
envPasswordKey: "MYREGISTRY_PASSWORD",
|
|
envPasswordValue: "password2",
|
|
wantUsername: "username2",
|
|
wantPassword: "password2",
|
|
},
|
|
{
|
|
name: "repoName contain hyphen, read username/password from environment variables",
|
|
args: args{
|
|
repoName: "my-registry",
|
|
},
|
|
envUsernameKey: "MY_REGISTRY_USERNAME",
|
|
envUsernameValue: "username3",
|
|
envPasswordKey: "MY_REGISTRY_PASSWORD",
|
|
envPasswordValue: "password3",
|
|
wantUsername: "username3",
|
|
wantPassword: "password3",
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.envUsernameKey != "" && tt.envUsernameValue != "" {
|
|
t.Setenv(tt.envUsernameKey, tt.envUsernameValue)
|
|
}
|
|
if tt.envPasswordKey != "" && tt.envPasswordValue != "" {
|
|
t.Setenv(tt.envPasswordKey, tt.envPasswordValue)
|
|
}
|
|
|
|
gotUsername, gotPassword := gatherUsernamePassword(tt.args.repoName, tt.args.username, tt.args.password)
|
|
if gotUsername != tt.wantUsername || gotPassword != tt.wantPassword {
|
|
t.Errorf("gatherUsernamePassword() = got username/password %v/%v, want username/password %v/%v", gotUsername, gotPassword, tt.wantUsername, tt.wantPassword)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_extractRegistryHost(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
url string
|
|
want string
|
|
}{
|
|
{
|
|
name: "ECR with chart path",
|
|
url: "123456789012.dkr.ecr.us-east-1.amazonaws.com/helm-charts",
|
|
want: "123456789012.dkr.ecr.us-east-1.amazonaws.com",
|
|
},
|
|
{
|
|
name: "GHCR with nested path",
|
|
url: "ghcr.io/deliveryhero/helm-charts",
|
|
want: "ghcr.io",
|
|
},
|
|
{
|
|
name: "registry with port and path",
|
|
url: "registry.example.com:5000/helm-charts",
|
|
want: "registry.example.com:5000",
|
|
},
|
|
{
|
|
name: "registry without path",
|
|
url: "registry.example.com",
|
|
want: "registry.example.com",
|
|
},
|
|
{
|
|
name: "with oci:// prefix and path",
|
|
url: "oci://ghcr.io/charts/nginx",
|
|
want: "ghcr.io",
|
|
},
|
|
{
|
|
name: "with https:// prefix and path",
|
|
url: "https://registry.example.com/helm-charts",
|
|
want: "registry.example.com",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := extractRegistryHost(tt.url); got != tt.want {
|
|
t.Errorf("extractRegistryHost() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncRepos_OCI(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
repos []RepositorySpec
|
|
wantRegistryLoginHost string
|
|
}{
|
|
{
|
|
name: "OCI registry with chart path should extract host only",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "ecr",
|
|
URL: "123456789012.dkr.ecr.us-east-1.amazonaws.com/helm-charts",
|
|
OCI: true,
|
|
Username: "AWS",
|
|
Password: "token",
|
|
},
|
|
},
|
|
wantRegistryLoginHost: "123456789012.dkr.ecr.us-east-1.amazonaws.com",
|
|
},
|
|
{
|
|
name: "OCI registry without path should pass URL as-is",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "ghcr",
|
|
URL: "ghcr.io",
|
|
OCI: true,
|
|
Username: "user",
|
|
Password: "pass",
|
|
},
|
|
},
|
|
wantRegistryLoginHost: "ghcr.io",
|
|
},
|
|
{
|
|
name: "OCI registry with nested path",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "docker",
|
|
URL: "registry-1.docker.io/bitnamicharts",
|
|
OCI: true,
|
|
Username: "user",
|
|
Password: "pass",
|
|
},
|
|
},
|
|
wantRegistryLoginHost: "registry-1.docker.io",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
helm := &exectest.Helm{}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: tt.repos,
|
|
},
|
|
}
|
|
_, err := state.SyncRepos(helm, map[string]bool{})
|
|
if err != nil {
|
|
t.Errorf("SyncRepos() error = %v", err)
|
|
return
|
|
}
|
|
if helm.RegistryLoginHost != tt.wantRegistryLoginHost {
|
|
t.Errorf("RegistryLogin was called with host = %q, want %q", helm.RegistryLoginHost, tt.wantRegistryLoginHost)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateOutputFilePath(t *testing.T) {
|
|
tests := []struct {
|
|
envName string
|
|
filePath string
|
|
releaseName string
|
|
outputFileTemplate string
|
|
wantErr bool
|
|
expected string
|
|
}{
|
|
{
|
|
envName: "dev",
|
|
releaseName: "release1",
|
|
filePath: "/path/to/helmfile.yaml",
|
|
outputFileTemplate: "helmfile-{{ .Environment.Name }}.yaml",
|
|
expected: "helmfile-dev.yaml",
|
|
},
|
|
{
|
|
envName: "error",
|
|
releaseName: "release2",
|
|
filePath: "helmfile.yaml",
|
|
outputFileTemplate: "helmfile-{{ .Environment.Name",
|
|
wantErr: true,
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.envName, func(t *testing.T) {
|
|
st := &HelmState{
|
|
FilePath: tt.envName,
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Env: environment.Environment{
|
|
Name: tt.envName,
|
|
},
|
|
},
|
|
}
|
|
ra := &ReleaseSpec{
|
|
Name: tt.releaseName,
|
|
}
|
|
got, err := st.GenerateOutputFilePath(ra, tt.outputFileTemplate)
|
|
|
|
if tt.wantErr {
|
|
require.Errorf(t, err, "GenerateOutputFilePath() error = %v, want error", err)
|
|
} else {
|
|
require.NoError(t, err, "GenerateOutputFilePath() error = %v, want nil", err)
|
|
}
|
|
require.Equalf(t, got, tt.expected, "GenerateOutputFilePath() got = %v, want %v", got, tt.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFullFilePath(t *testing.T) {
|
|
fs := testhelper.NewTestFs(map[string]string{})
|
|
tests := []struct {
|
|
basePath string
|
|
filePath string
|
|
fs *filesystem.FileSystem
|
|
expected string
|
|
}{
|
|
{
|
|
basePath: ".",
|
|
filePath: "helmfile.yaml",
|
|
expected: "helmfile.yaml",
|
|
},
|
|
{
|
|
basePath: "./test-1/",
|
|
filePath: "helmfile.yaml",
|
|
expected: "test-1/helmfile.yaml",
|
|
},
|
|
{
|
|
basePath: "/test-2/",
|
|
filePath: "helmfile.yaml",
|
|
expected: "/test-2/helmfile.yaml",
|
|
},
|
|
{
|
|
basePath: "./test-3/",
|
|
filePath: "helmfile.yaml",
|
|
fs: fs.ToFileSystem(),
|
|
expected: "/path/to/test-3/helmfile.yaml",
|
|
},
|
|
{
|
|
basePath: "/test-4/",
|
|
filePath: "helmfile.yaml",
|
|
fs: fs.ToFileSystem(),
|
|
expected: "/path/to/test-4/helmfile.yaml",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.expected, func(t *testing.T) {
|
|
st := &HelmState{
|
|
basePath: tt.basePath,
|
|
FilePath: tt.filePath,
|
|
fs: tt.fs,
|
|
}
|
|
actual, err := st.FullFilePath()
|
|
require.Equalf(t, actual, tt.expected, "FullFilePath() got = %v, want %v", actual, tt.expected)
|
|
require.Equalf(t, err, nil, "error %v", err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetOCIQualifiedChartName(t *testing.T) {
|
|
devel := true
|
|
|
|
tests := []struct {
|
|
state HelmState
|
|
expected []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}
|
|
helmVersion string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Version: "0.1.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name:0.1.2", "chart-name", "0.1.2"},
|
|
},
|
|
},
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Version: "latest",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Version: "latest",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.7.0",
|
|
wantErr: true, // Now rejects "latest" for all Helm versions
|
|
},
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/chart-path",
|
|
OCI: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chart-name",
|
|
Version: "0.1.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name:0.1.2", "chart-name", "0.1.2"},
|
|
},
|
|
},
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Devel: &devel,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name", "chart-name", ""},
|
|
},
|
|
},
|
|
// Digest in version field
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Version: "2.0.0@sha256:abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name@sha256:abc123", "chart-name", "2.0.0"},
|
|
},
|
|
},
|
|
// Digest-only in version field
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Version: "@sha256:abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name@sha256:abc123", "chart-name", ""},
|
|
},
|
|
},
|
|
// Version tag in URL
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name:2.0.0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name", "chart-name", "2.0.0"},
|
|
},
|
|
},
|
|
// Digest in URL
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name@sha256:abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name@sha256:abc123", "chart-name", ""},
|
|
},
|
|
},
|
|
// Version + digest in URL
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name:2.0.0@sha256:abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name@sha256:abc123", "chart-name", "2.0.0"},
|
|
},
|
|
},
|
|
// Port with digest in URL
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry:5000/chart-path/chart-name@sha256:abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry:5000/chart-path/chart-name@sha256:abc123", "chart-name", ""},
|
|
},
|
|
},
|
|
// Digest in URL + version field
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name@sha256:abc123",
|
|
Version: "2.0.0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name@sha256:abc123", "chart-name", "2.0.0"},
|
|
},
|
|
},
|
|
// Repo-aliased OCI chart with digest in version
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/chart-path",
|
|
OCI: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chart-name",
|
|
Version: "2.0.0@sha256:abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name@sha256:abc123", "chart-name", "2.0.0"},
|
|
},
|
|
},
|
|
// Repo-aliased OCI chart with digest-only version
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/chart-path",
|
|
OCI: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chart-name",
|
|
Version: "@sha256:abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name@sha256:abc123", "chart-name", ""},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(fmt.Sprintf("%+v", tt.expected), func(t *testing.T) {
|
|
for i, r := range tt.state.Releases {
|
|
qualifiedChartName, chartName, chartVersion, err := tt.state.getOCIQualifiedChartName(&r)
|
|
if tt.wantErr {
|
|
require.Error(t, err, "getOCIQualifiedChartName() error = nil, want error")
|
|
return
|
|
}
|
|
require.NoError(t, err, "getOCIQualifiedChartName() error = %v, want nil", err)
|
|
if len(tt.expected) > 0 {
|
|
require.Equalf(t, qualifiedChartName, tt.expected[i].qualifiedChartName, "qualifiedChartName got = %v, want %v", qualifiedChartName, tt.expected[i].qualifiedChartName)
|
|
require.Equalf(t, chartName, tt.expected[i].chartName, "chartName got = %v, want %v", chartName, tt.expected[i].chartName)
|
|
require.Equalf(t, chartVersion, tt.expected[i].chartVersion, "chartVersion got = %v, want %v", chartVersion, tt.expected[i].chartVersion)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateChartPath(t *testing.T) {
|
|
tests := []struct {
|
|
testName string
|
|
chartName string
|
|
release *ReleaseSpec
|
|
outputDir string
|
|
outputDirTemplate string
|
|
wantErr bool
|
|
expected string
|
|
}{
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndDefaultReleaseVersion",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-name/chart-name/latest",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenReleaseVersion",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name", Version: "0.0.0"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-name/chart-name/0.0.0",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenReleaseNamespace",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name", Namespace: "release-namespace"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-namespace/release-name/chart-name/latest",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenReleaseKubeContext",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name", KubeContext: "kube-context"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/kube-context/release-name/chart-name/latest",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenReleaseNamespaceAndGivenReleaseKubeContext",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name", Namespace: "release-namespace", KubeContext: "kube-context"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-namespace/kube-context/release-name/chart-name/latest",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenOutputDirTemplateWithFieldNameOutputDir",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}",
|
|
wantErr: false,
|
|
expected: "/output-dir",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenOutputDirTemplateWithFieldNamesOutputDirAndReleaseName",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}/{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-name",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirTemplateWithFieldNamesOutputDir",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDirTemplate: "{{ .OutputDir }}",
|
|
wantErr: false,
|
|
expected: "",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirTemplateWithFieldNameReleaseName",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDirTemplate: "{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "release-name",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirTemplateWithStringAndFieldNameReleaseName",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDirTemplate: "./charts/{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "./charts/release-name",
|
|
},
|
|
{
|
|
testName: "ErrorReturnedWithGivenInvalidOutputDirTemplate",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDirTemplate: "{{ .OutputDir }",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirTemplateWithEnvironmentName",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}/{{ .Environment.Name }}",
|
|
wantErr: false,
|
|
expected: "/output-dir/test-env",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirTemplateWithEnvironmentValues",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}/{{ .Environment.Values.cluster.name }}",
|
|
wantErr: false,
|
|
expected: "/output-dir/my-test-cluster",
|
|
},
|
|
}
|
|
st := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Env: environment.Environment{
|
|
Name: "test-env",
|
|
Values: map[string]any{
|
|
"cluster": map[string]any{
|
|
"name": "my-test-cluster",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.testName, func(t *testing.T) {
|
|
got, err := st.generateChartPath(tt.chartName, tt.outputDir, tt.release, tt.outputDirTemplate)
|
|
|
|
if tt.wantErr {
|
|
require.Errorf(t, err, "GenerateChartPath() error \"%v\", want error", err)
|
|
} else {
|
|
require.NoError(t, err, "GenerateChartPath() error \"%v\", want no error", err)
|
|
}
|
|
require.Equalf(t, tt.expected, got, "GenerateChartPath() got \"%v\", want \"%v\"", got, tt.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateOutputDir(t *testing.T) {
|
|
tests := []struct {
|
|
testName string
|
|
release *ReleaseSpec
|
|
outputDir string
|
|
outputDirTemplate string
|
|
wantErr bool
|
|
expected string
|
|
}{
|
|
{
|
|
testName: "PathGeneratedWithEnvironmentName",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}/{{ .Environment.Name }}/{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "/output-dir/test-env/release-name",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithEnvironmentValues",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}/{{ .Environment.Values.cluster.name }}/{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "/output-dir/my-test-cluster/release-name",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithEnvironmentKubeContext",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}/{{ .Environment.KubeContext }}/{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "/output-dir/test-kubecontext/release-name",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.testName, func(t *testing.T) {
|
|
st := &HelmState{
|
|
FilePath: "test.yaml",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Env: environment.Environment{
|
|
Name: "test-env",
|
|
KubeContext: "test-kubecontext",
|
|
Values: map[string]any{
|
|
"cluster": map[string]any{
|
|
"name": "my-test-cluster",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
got, err := st.GenerateOutputDir(tt.outputDir, tt.release, tt.outputDirTemplate)
|
|
|
|
if tt.wantErr {
|
|
require.Errorf(t, err, "GenerateOutputDir() error \"%v\", want error", err)
|
|
} else {
|
|
require.NoError(t, err, "GenerateOutputDir() error \"%v\", want no error", err)
|
|
}
|
|
require.Equalf(t, tt.expected, got, "GenerateOutputDir() got \"%v\", want \"%v\"", got, tt.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommonDiffFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
// stripTrailingCR is a flag to strip trailing carriage returns from the output
|
|
stripTrailingCR bool
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "stripTrailingCR enabled",
|
|
stripTrailingCR: true,
|
|
expected: []string{
|
|
"--strip-trailing-cr",
|
|
},
|
|
},
|
|
{
|
|
name: "stripTrailingCR disenabled",
|
|
expected: []string{},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
st := &HelmState{}
|
|
result := st.commonDiffFlags(false, tt.stripTrailingCR, false, []string{}, false, false, false, &DiffOpts{})
|
|
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
}
|
|
|
|
func TestAppendChartDownloadFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
spec *ReleaseSetSpec
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "SkipTLSVerify in repo",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "testrepo",
|
|
URL: "registry/repo-path",
|
|
SkipTLSVerify: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "testrepo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--insecure-skip-tls-verify",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp in repo, SkipTLSVerify everywhere",
|
|
spec: &ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "testrepo",
|
|
URL: "registry/repo-path",
|
|
SkipTLSVerify: true,
|
|
PlainHttp: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "testrepo/chartname",
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
{
|
|
name: "SkipTLSVerify in defaults",
|
|
spec: &ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--insecure-skip-tls-verify",
|
|
},
|
|
},
|
|
{
|
|
name: "SkipTLSVerify in release",
|
|
spec: &ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "chartname",
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--insecure-skip-tls-verify",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp in defaults",
|
|
spec: &ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
PlainHttp: true,
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp in release",
|
|
spec: &ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "chartname",
|
|
PlainHttp: true,
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
ReleaseSetSpec: *tt.spec,
|
|
}
|
|
|
|
result := st.appendChartDownloadFlags([]string{}, &st.Releases[0])
|
|
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNeedsPlainHttp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
release *ReleaseSpec
|
|
repo *RepositorySpec
|
|
defaults HelmSpec
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "PlainHttp in Release",
|
|
release: &ReleaseSpec{
|
|
PlainHttp: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "PlainHttp in Repository",
|
|
repo: &RepositorySpec{
|
|
PlainHttp: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "PlainHttp in HelmDefaults",
|
|
defaults: HelmSpec{
|
|
PlainHttp: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "PlainHttp not set",
|
|
expected: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
}
|
|
require.Equal(t, tt.expected, st.needsPlainHttp(tt.release, tt.repo))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNeedsInsecureSkipTLSVerify(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
release *ReleaseSpec
|
|
repo *RepositorySpec
|
|
defaults HelmSpec
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "InsecureSkipTLSVerify in Release",
|
|
release: &ReleaseSpec{
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "SkipTLSVerify in Repository",
|
|
repo: &RepositorySpec{
|
|
SkipTLSVerify: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "InsecureSkipTLSVerify in HelmDefaults",
|
|
defaults: HelmSpec{
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "InsecureSkipTLSVerify not set",
|
|
expected: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
}
|
|
require.Equal(t, tt.expected, st.needsInsecureSkipTLSVerify(tt.release, tt.repo))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHideChartURL(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"http://username:password@example.com/", "http://---:---@example.com/"},
|
|
{"http://example.com@", "http://---:---@"},
|
|
{"https://username:password@example.com/", "https://---:---@example.com/"},
|
|
{"https://username:@password@example.com/", "https://---:---@example.com/"},
|
|
{"https://username::password@example.com/", "https://---:---@example.com/"},
|
|
{"https://username:httpd@example.com/", "https://---:---@example.com/"},
|
|
{"https://username:httpsd@example.com/", "https://---:---@example.com/"},
|
|
{"https://example.com/", "https://example.com/"},
|
|
}
|
|
for _, test := range tests {
|
|
result, _ := hideChartCredentials(test.input)
|
|
if result != test.expected {
|
|
t.Errorf("For input '%s', expected '%s', but got '%s'", test.input, test.expected, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_appendExtraDiffFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inputFlags []string
|
|
inputOpts *DiffOpts
|
|
inputDefaults []string
|
|
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Skipping default flags, because diffOpts is provided",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputOpts: &DiffOpts{
|
|
DiffArgs: "-bbbb --cccc",
|
|
},
|
|
inputDefaults: []string{"-dddd", "--eeee"},
|
|
expected: []string{"aaaaa", "-bbbb", "--cccc"},
|
|
},
|
|
{
|
|
name: "Use default flags, because diffOpts is not provided",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputDefaults: []string{"-dddd", "--eeee"},
|
|
expected: []string{"aaaaa", "-dddd", "--eeee"},
|
|
},
|
|
{
|
|
name: "Don't add non-flag arguments",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputDefaults: []string{"-d=ddd", "non-flag", "--eeee"},
|
|
expected: []string{"aaaaa", "-d=ddd", "--eeee"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := (&HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
DiffArgs: test.inputDefaults,
|
|
},
|
|
},
|
|
}).appendExtraDiffFlags(test.inputFlags, test.inputOpts)
|
|
if !reflect.DeepEqual(result, test.expected) {
|
|
t.Errorf("For input %v %v, expected %v, but got %v", test.inputFlags, test.inputOpts, test.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_appendExtraSyncFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inputFlags []string
|
|
inputOpts *SyncOpts
|
|
inputDefaults []string
|
|
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Skipping default flags, because diffOpts is provided",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputOpts: &SyncOpts{
|
|
SyncArgs: "-bbbb --cccc",
|
|
},
|
|
inputDefaults: []string{"-dddd", "--eeee"},
|
|
expected: []string{"aaaaa", "-bbbb", "--cccc"},
|
|
},
|
|
{
|
|
name: "Use default flags, because diffOpts is not provided",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputDefaults: []string{"-dddd", "--eeee"},
|
|
expected: []string{"aaaaa", "-dddd", "--eeee"},
|
|
},
|
|
{
|
|
name: "Don't add non-flag arguments",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputDefaults: []string{"-d=ddd", "non-flag", "--eeee"},
|
|
expected: []string{"aaaaa", "-d=ddd", "--eeee"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := (&HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
SyncArgs: test.inputDefaults,
|
|
},
|
|
},
|
|
}).appendExtraSyncFlags(test.inputFlags, test.inputOpts)
|
|
if !reflect.DeepEqual(result, test.expected) {
|
|
t.Errorf("For input %v %v, expected %v, but got %v", test.inputFlags, test.inputOpts, test.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_appendApiVersionsFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
kubeVersion string
|
|
flags []string
|
|
expectedFlags []string
|
|
releaseKubeVersion string
|
|
releaseApiVersion []string
|
|
stateKubeVersion string
|
|
stateApiVersion []string
|
|
}{
|
|
{
|
|
name: "no kubeVersion is set",
|
|
expectedFlags: []string{},
|
|
},
|
|
{
|
|
name: "flags are kept",
|
|
flags: []string{"--flag1", "--flag2"},
|
|
expectedFlags: []string{"--flag1", "--flag2"},
|
|
},
|
|
{
|
|
name: "kubeVersion is set",
|
|
kubeVersion: "1.18.0",
|
|
expectedFlags: []string{"--kube-version", "1.18.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion is set from release",
|
|
releaseKubeVersion: "1.19.0",
|
|
expectedFlags: []string{"--kube-version", "1.19.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion from release hasn't priority",
|
|
kubeVersion: "1.18.0",
|
|
releaseKubeVersion: "1.19.0",
|
|
expectedFlags: []string{"--kube-version", "1.18.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion is set from state",
|
|
stateKubeVersion: "1.18.0",
|
|
expectedFlags: []string{"--kube-version", "1.18.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion from state hasn't priority",
|
|
stateKubeVersion: "1.18.0",
|
|
releaseKubeVersion: "1.19.0",
|
|
expectedFlags: []string{"--kube-version", "1.19.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion priority",
|
|
stateKubeVersion: "1.18.0",
|
|
releaseKubeVersion: "1.19.0",
|
|
kubeVersion: "1.20.0",
|
|
expectedFlags: []string{"--kube-version", "1.20.0"},
|
|
},
|
|
{
|
|
name: "api-version are set from state",
|
|
stateApiVersion: []string{"v1,v2"},
|
|
expectedFlags: []string{"--api-versions", "v1,v2"},
|
|
},
|
|
{
|
|
name: "api-version are set from release",
|
|
releaseApiVersion: []string{"v1,v2"},
|
|
expectedFlags: []string{"--api-versions", "v1,v2"},
|
|
},
|
|
{
|
|
name: "api-version priority",
|
|
stateApiVersion: []string{"v1"},
|
|
releaseApiVersion: []string{"v2"},
|
|
expectedFlags: []string{"--api-versions", "v2"},
|
|
},
|
|
{
|
|
name: "api-version multiple values",
|
|
stateApiVersion: []string{"v1", "v2"},
|
|
expectedFlags: []string{"--api-versions", "v1", "--api-versions", "v2"},
|
|
},
|
|
{
|
|
name: "All kubeVersion and api-version are set",
|
|
kubeVersion: "1.18.0",
|
|
stateKubeVersion: "1.19.0",
|
|
releaseKubeVersion: "1.20.0",
|
|
stateApiVersion: []string{"v1"},
|
|
releaseApiVersion: []string{"v2"},
|
|
flags: []string{"--previous-flag-1", "--previous-flag-2"},
|
|
expectedFlags: []string{"--previous-flag-1", "--previous-flag-2", "--api-versions", "v2", "--kube-version", "1.18.0"},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if test.expectedFlags == nil {
|
|
test.expectedFlags = []string{}
|
|
}
|
|
if test.flags == nil {
|
|
test.flags = []string{}
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
KubeVersion: test.stateKubeVersion,
|
|
ApiVersions: test.stateApiVersion,
|
|
},
|
|
}
|
|
r := &ReleaseSpec{
|
|
KubeVersion: test.releaseKubeVersion,
|
|
ApiVersions: test.releaseApiVersion,
|
|
}
|
|
result := state.appendApiVersionsFlags(test.flags, r, test.kubeVersion)
|
|
assert.Equal(t, test.expectedFlags, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetOCIChartPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tempDir string
|
|
release *ReleaseSpec
|
|
chartName string
|
|
chartVersion string
|
|
outputDirTemplate string
|
|
expectedPath string
|
|
expectedErr bool
|
|
}{
|
|
{
|
|
name: "OCI chart with template",
|
|
tempDir: "charts",
|
|
release: &ReleaseSpec{
|
|
Name: "karpenter",
|
|
Chart: "karpenter/karpenter",
|
|
},
|
|
chartName: "karpenter",
|
|
chartVersion: "0.37.0",
|
|
outputDirTemplate: "{{ .OutputDir }}/",
|
|
expectedPath: "charts/",
|
|
expectedErr: false,
|
|
},
|
|
{
|
|
name: "OCI chart with template containing unknown values",
|
|
tempDir: "charts",
|
|
release: &ReleaseSpec{
|
|
Name: "karpenter",
|
|
Chart: "karpenter/karpenter",
|
|
},
|
|
chartName: "karpenter",
|
|
chartVersion: "0.37.0",
|
|
outputDirTemplate: "{{ .SomethingThatDoesNotExist }}/",
|
|
expectedPath: "",
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
name: "OCI chart without template",
|
|
tempDir: "charts",
|
|
release: &ReleaseSpec{
|
|
Name: "karpenter",
|
|
Chart: "karpenter/karpenter",
|
|
},
|
|
chartName: "karpenter",
|
|
chartVersion: "0.37.0",
|
|
expectedPath: "charts/karpenter/karpenter/0.37.0",
|
|
expectedErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{}
|
|
path, err := st.getOCIChartPath(tt.tempDir, tt.release, tt.chartName, tt.chartVersion, tt.outputDirTemplate)
|
|
|
|
if tt.expectedErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expectedPath, path)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_chartOCIFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
spec *ReleaseSetSpec
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "CaFile enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
CaFile: "cafile",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--ca-file",
|
|
"cafile",
|
|
},
|
|
},
|
|
{
|
|
name: "CertFile enabled and KeyFile disabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
CertFile: "certfile",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "CertFile disabled and KeyFile enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
KeyFile: "keyfile",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "CertFile and KeyFile enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
CertFile: "certfile",
|
|
KeyFile: "keyfile",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--cert-file",
|
|
"certfile",
|
|
"--key-file",
|
|
"keyfile",
|
|
},
|
|
},
|
|
{
|
|
name: "SkipTLSVerify enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
SkipTLSVerify: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--insecure-skip-tls-verify",
|
|
},
|
|
},
|
|
{
|
|
name: "Verify enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
Verify: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--verify",
|
|
},
|
|
},
|
|
{
|
|
name: "Keyring enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
Keyring: "keyring.pgp",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--keyring",
|
|
"keyring.pgp",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
PlainHttp: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp and TLS options enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
PlainHttp: true,
|
|
CaFile: "cafile",
|
|
CertFile: "certfile",
|
|
KeyFile: "keyfile",
|
|
SkipTLSVerify: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
ReleaseSetSpec: *tt.spec,
|
|
}
|
|
flags := st.chartOCIFlags(&tt.spec.Releases[0])
|
|
require.Equal(t, tt.expected, flags)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsOCIChart(t *testing.T) {
|
|
cases := []struct {
|
|
st *HelmState
|
|
chart string
|
|
expected bool
|
|
}{
|
|
{&HelmState{}, "oci://myrepo/mychart", true},
|
|
{&HelmState{}, "oci://myrepo/mychart:1.0.0", true},
|
|
{&HelmState{}, "myrepo/mychart", false},
|
|
{&HelmState{}, "myrepo/mychart:1.0.0", false},
|
|
{
|
|
&HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "ocirepo",
|
|
URL: "ocirepo.com",
|
|
OCI: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"ocirepo/chart",
|
|
true,
|
|
},
|
|
{
|
|
&HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "nonocirepo",
|
|
URL: "nonocirepo.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"nonocirepo/chart",
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := c.st.IsOCIChart(c.chart)
|
|
if actual != c.expected {
|
|
t.Errorf("IsOCIChart(%s) = %t; expected %t", c.chart, actual, c.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAppendVerifyFlags(t *testing.T) {
|
|
st := &HelmState{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
repo []RepositorySpec
|
|
helmDefaults HelmSpec
|
|
release *ReleaseSpec
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Release with true verify flag",
|
|
release: &ReleaseSpec{Verify: boolValue(true)},
|
|
repo: nil,
|
|
helmDefaults: HelmSpec{},
|
|
expected: []string{"--verify"},
|
|
},
|
|
{
|
|
name: "Release with false verify flag",
|
|
release: &ReleaseSpec{Verify: boolValue(false)},
|
|
repo: nil,
|
|
helmDefaults: HelmSpec{},
|
|
expected: []string(nil),
|
|
},
|
|
{
|
|
name: "Repository with verify flag",
|
|
helmDefaults: HelmSpec{},
|
|
repo: []RepositorySpec{
|
|
{
|
|
Name: "myrepo",
|
|
Verify: true,
|
|
},
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "myrepo/mychart",
|
|
},
|
|
expected: []string{"--verify"},
|
|
},
|
|
{
|
|
name: "Helm defaults with verify flag",
|
|
repo: nil,
|
|
helmDefaults: HelmSpec{
|
|
Verify: true,
|
|
},
|
|
release: &ReleaseSpec{},
|
|
expected: []string{"--verify"},
|
|
},
|
|
{
|
|
name: "No verify flag",
|
|
repo: nil,
|
|
helmDefaults: HelmSpec{},
|
|
release: &ReleaseSpec{},
|
|
expected: []string(nil),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st.ReleaseSetSpec.Repositories = tt.repo
|
|
st.ReleaseSetSpec.HelmDefaults = tt.helmDefaults
|
|
flags := st.appendVerifyFlags(nil, tt.release)
|
|
assert.Equal(t, tt.expected, flags)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestHelmState_setStringFlags tests the setStringFlags method
|
|
func TestHelmState_setStringFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setStringValues []SetValue
|
|
want []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "single value",
|
|
setStringValues: []SetValue{
|
|
{
|
|
Name: "key",
|
|
Value: "value",
|
|
},
|
|
},
|
|
want: []string{"--set-string", "key=value"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "multiple values",
|
|
setStringValues: []SetValue{
|
|
{
|
|
Name: "key",
|
|
Values: []string{"value1", "value2"},
|
|
},
|
|
},
|
|
want: []string{"--set-string", "key={value1,value2}"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "rendered value error",
|
|
setStringValues: []SetValue{
|
|
{
|
|
Name: "key",
|
|
Value: "ref+echo://value",
|
|
},
|
|
},
|
|
want: []string{"--set-string", "key=value"},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
got, err := st.setStringFlags(tt.setStringValues)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("setStringFlags() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("setStringFlags() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
func TestPrepareDiffReleases_ValueControlReleaseOverride(t *testing.T) {
|
|
tests := []struct {
|
|
flags []string
|
|
diffOptions *DiffOpts
|
|
helmDefaults *HelmSpec
|
|
release *ReleaseSpec
|
|
}{
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-from-release",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-from-cli",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-all",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-from-helm-defaults",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-from-release",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-cli-overrides-release",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
releases := []ReleaseSpec{
|
|
*tt.release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
HelmDefaults: *tt.helmDefaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
results, es := state.prepareDiffReleases(helm, []string{}, 1, false, false, false, []string{}, false, false, false, tt.diffOptions)
|
|
|
|
require.Len(t, es, 0)
|
|
require.Len(t, results, 1)
|
|
|
|
r := results[0]
|
|
|
|
require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name)
|
|
}
|
|
}
|
|
|
|
func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) {
|
|
tests := []struct {
|
|
flags []string
|
|
syncOptions *SyncOpts
|
|
helmDefaults *HelmSpec
|
|
release *ReleaseSpec
|
|
}{
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-from-release",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-from-cli",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-all",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-from-helm-defaults",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-from-release",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-cli-overrides-release",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
releases := []ReleaseSpec{
|
|
*tt.release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
HelmDefaults: *tt.helmDefaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
results, es := state.prepareSyncReleases(helm, []string{}, 1, tt.syncOptions)
|
|
|
|
require.Len(t, es, 0)
|
|
require.Len(t, results, 1)
|
|
|
|
r := results[0]
|
|
|
|
require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name)
|
|
}
|
|
}
|
|
|
|
func TestChartCacheKey(t *testing.T) {
|
|
st := &HelmState{}
|
|
|
|
// Test case 1: release with version
|
|
release1 := &ReleaseSpec{
|
|
Chart: "stable/nginx",
|
|
Version: "1.2.3",
|
|
}
|
|
|
|
key1 := st.getChartCacheKey(release1)
|
|
expected1 := ChartCacheKey{Chart: "stable/nginx", Version: "1.2.3"}
|
|
|
|
if key1 != expected1 {
|
|
t.Errorf("Expected %+v, got %+v", expected1, key1)
|
|
}
|
|
|
|
// Test case 2: release without version
|
|
release2 := &ReleaseSpec{
|
|
Chart: "stable/nginx",
|
|
}
|
|
|
|
key2 := st.getChartCacheKey(release2)
|
|
expected2 := ChartCacheKey{Chart: "stable/nginx", Version: ""}
|
|
|
|
if key2 != expected2 {
|
|
t.Errorf("Expected %+v, got %+v", expected2, key2)
|
|
}
|
|
}
|
|
|
|
func TestChartCache(t *testing.T) {
|
|
st := &HelmState{}
|
|
|
|
// Create a test key
|
|
key := ChartCacheKey{Chart: "stable/test", Version: "1.0.0"}
|
|
path := "/tmp/test-chart"
|
|
|
|
// Initially, chart should not be in cache
|
|
_, exists := st.checkChartCache(key)
|
|
if exists {
|
|
t.Error("Chart should not be in cache initially")
|
|
}
|
|
|
|
// Add to cache
|
|
st.addToChartCache(key, path)
|
|
|
|
// Now chart should be in cache
|
|
cachedPath, exists := st.checkChartCache(key)
|
|
if !exists {
|
|
t.Error("Chart should be in cache after adding")
|
|
}
|
|
if cachedPath != path {
|
|
t.Errorf("Expected path %s, got %s", path, cachedPath)
|
|
}
|
|
}
|
|
|
|
// TestHelmState_getKubeContext tests the kube-context resolution logic
|
|
// Issue #2309: This helper is used to pass kube-context to chartify
|
|
func TestHelmState_getKubeContext(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
defaults HelmSpec
|
|
environments map[string]EnvironmentSpec
|
|
envName string
|
|
release *ReleaseSpec
|
|
want string
|
|
}{
|
|
{
|
|
name: "returns empty when no context configured",
|
|
defaults: HelmSpec{},
|
|
release: &ReleaseSpec{Name: "test"},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "returns release kubeContext when set",
|
|
defaults: HelmSpec{
|
|
KubeContext: "default-context",
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "test",
|
|
KubeContext: "release-context",
|
|
},
|
|
want: "release-context",
|
|
},
|
|
{
|
|
name: "returns helmDefaults kubeContext when release not set",
|
|
defaults: HelmSpec{
|
|
KubeContext: "default-context",
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "test",
|
|
},
|
|
want: "default-context",
|
|
},
|
|
{
|
|
name: "environment overrides helmDefaults when release not set",
|
|
defaults: HelmSpec{
|
|
KubeContext: "default-context",
|
|
},
|
|
environments: map[string]EnvironmentSpec{
|
|
"production": {KubeContext: "env-context"},
|
|
},
|
|
envName: "production",
|
|
release: &ReleaseSpec{
|
|
Name: "test",
|
|
},
|
|
want: "env-context",
|
|
},
|
|
{
|
|
name: "release overrides environment",
|
|
defaults: HelmSpec{
|
|
KubeContext: "default-context",
|
|
},
|
|
environments: map[string]EnvironmentSpec{
|
|
"production": {KubeContext: "env-context"},
|
|
},
|
|
envName: "production",
|
|
release: &ReleaseSpec{
|
|
Name: "test",
|
|
KubeContext: "release-context",
|
|
},
|
|
want: "release-context",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
envName := tt.envName
|
|
if envName == "" {
|
|
envName = "default"
|
|
}
|
|
environments := tt.environments
|
|
if environments == nil {
|
|
environments = make(map[string]EnvironmentSpec)
|
|
}
|
|
|
|
state := &HelmState{
|
|
basePath: "./",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: tt.defaults,
|
|
Environments: environments,
|
|
Env: environment.Environment{
|
|
Name: envName,
|
|
},
|
|
},
|
|
}
|
|
|
|
got := state.getKubeContext(tt.release)
|
|
if got != tt.want {
|
|
t.Errorf("getKubeContext() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|