package app import ( "bytes" "os" "testing" "github.com/google/go-cmp/cmp" "github.com/helmfile/vals" "github.com/stretchr/testify/assert" "go.uber.org/zap" ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testutil" ) func testListWithEnvironment(t *testing.T, cfg configImpl) { type testcase struct { environment string ns string error string selectors []string expected string } check := func(t *testing.T, tc testcase, cfg configImpl) { t.Helper() bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { t.Helper() valsRuntime, err := vals.New(vals.Options{CacheSize: 32}) if err != nil { t.Errorf("unexpected error creating vals runtime: %v", err) } files := map[string]string{ "/path/to/helmfile.d/helmfile_1.yaml": ` environments: development: {} shared: {} --- releases: - name: logging chart: incubator/raw namespace: kube-system - name: kubernetes-external-secrets chart: incubator/raw namespace: kube-system needs: - kube-system/logging - name: external-secrets chart: incubator/raw namespace: default labels: app: test needs: - kube-system/kubernetes-external-secrets - name: my-release chart: incubator/raw namespace: default labels: app: test needs: - default/external-secrets # Disabled releases are treated as missing - name: disabled chart: incubator/raw namespace: kube-system installed: false - name: test2 chart: incubator/raw needs: - kube-system/disabled - name: test3 chart: incubator/raw needs: - test2 `, "/path/to/helmfile.d/helmfile_2.yaml": ` environments: test: {} shared: {} --- repositories: - name: bitnami url: https://charts.bitnami.com/bitnami releases: - name: cache namespace: my-app chart: bitnami/redis version: 17.0.7 labels: app: test - name: database namespace: my-app chart: bitnami/postgres version: 11.6.22 `, "/path/to/helmfile.d/helmfile_3.yaml": ` releases: - name: global chart: incubator/raw namespace: kube-system `, } app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: tc.environment, Logger: logger, valsRuntime: valsRuntime, }, files) expectNoCallsToHelm(app) if tc.ns != "" { app.Namespace = tc.ns } if tc.selectors != nil { app.Selectors = tc.selectors } var listErr error out, err := testutil.CaptureStdout(func() { listErr = app.ListReleases(cfg) }) assert.NoError(t, err) var gotErr string if listErr != nil { gotErr = listErr.Error() } if d := cmp.Diff(tc.error, gotErr); d != "" { t.Fatalf("unexpected error: want (-), got (+): %s", d) } assert.Equal(t, tc.expected, out) }) testhelper.RequireLog(t, "app_list_test", bs) } t.Run("default environment includes all releases", func(t *testing.T) { check(t, testcase{ environment: "default", expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION logging kube-system true true incubator/raw kubernetes-external-secrets kube-system true true incubator/raw external-secrets default true true app:test incubator/raw my-release default true true app:test incubator/raw disabled kube-system true false incubator/raw test2 true true incubator/raw test3 true true incubator/raw cache my-app true true app:test bitnami/redis 17.0.7 database my-app true true bitnami/postgres 11.6.22 global kube-system true true incubator/raw `, }, cfg) }) t.Run("fail on unknown environment", func(t *testing.T) { check(t, testcase{ environment: "staging", error: `err: no releases found that matches specified selector() and environment(staging), in any helmfile`, }, cfg) }) t.Run("list releases matching selector and environment", func(t *testing.T) { check(t, testcase{ environment: "development", selectors: []string{"app=test"}, expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION external-secrets default true true app:test incubator/raw my-release default true true app:test incubator/raw `, }, cfg) }) t.Run("filters releases for environment used in one file only", func(t *testing.T) { check(t, testcase{ environment: "test", expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION cache my-app true true app:test bitnami/redis 17.0.7 database my-app true true bitnami/postgres 11.6.22 `, }, cfg) }) t.Run("filters releases for environment used in multiple files", func(t *testing.T) { check(t, testcase{ environment: "shared", // 'global' release has no environments, so is still excluded expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION logging kube-system true true incubator/raw kubernetes-external-secrets kube-system true true incubator/raw external-secrets default true true app:test incubator/raw my-release default true true app:test incubator/raw disabled kube-system true false incubator/raw test2 true true incubator/raw test3 true true incubator/raw cache my-app true true app:test bitnami/redis 17.0.7 database my-app true true bitnami/postgres 11.6.22 `, }, cfg) }) } func TestListWithEnvironment(t *testing.T) { t.Run("with skipCharts=false", func(t *testing.T) { testListWithEnvironment(t, configImpl{skipCharts: false}) }) t.Run("with skipCharts=true", func(t *testing.T) { testListWithEnvironment(t, configImpl{skipCharts: true}) }) } func testListWithJSONOutput(t *testing.T, cfg configImpl) { cfg.output = "json" files := map[string]string{ "/path/to/helmfile.d/first.yaml": ` environments: default: values: - myrelease2: enabled: false --- releases: - name: myrelease1 chart: mychart1 installed: false labels: id: myrelease1 - name: myrelease2 chart: mychart1 condition: myrelease2.enabled `, "/path/to/helmfile.d/second.yaml": ` releases: - name: myrelease3 chart: mychart1 installed: true - name: myrelease4 chart: mychart1 labels: id: myrelease1 `, } stdout := os.Stdout defer func() { os.Stdout = stdout }() var buffer bytes.Buffer logger := helmexec.NewLogger(&buffer, "debug") app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, Namespace: "testNamespace", }, files) expectNoCallsToHelm(app) out, err := testutil.CaptureStdout(func() { err := app.ListReleases(cfg) assert.Nil(t, err) }) assert.NoError(t, err) expected := `[{"name":"myrelease1","namespace":"testNamespace","enabled":true,"installed":false,"labels":"id:myrelease1","chart":"mychart1","version":""},{"name":"myrelease2","namespace":"testNamespace","enabled":false,"installed":true,"labels":"","chart":"mychart1","version":""},{"name":"myrelease3","namespace":"testNamespace","enabled":true,"installed":true,"labels":"","chart":"mychart1","version":""},{"name":"myrelease4","namespace":"testNamespace","enabled":true,"installed":true,"labels":"id:myrelease1","chart":"mychart1","version":""}] ` assert.Equal(t, expected, out) } func TestListWithJSONOutput(t *testing.T) { t.Run("with skipCharts=false", func(t *testing.T) { testListWithJSONOutput(t, configImpl{skipCharts: false}) }) t.Run("with skipCharts=true", func(t *testing.T) { testListWithJSONOutput(t, configImpl{skipCharts: true}) }) }