diff --git a/docs/index.md b/docs/index.md index aa7277d5..7db517ad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -534,6 +534,7 @@ Helmfile uses some OS environment variables to override default behaviour: * `HELMFILE_DISABLE_INSECURE_FEATURES` - disable insecure features, expecting `true` lower case * `HELMFILE_DISABLE_RUNNER_UNIQUE_ID` - disable unique logging ID, expecting any non-empty value * `HELMFILE_SKIP_INSECURE_TEMPLATE_FUNCTIONS` - disable insecure template functions, expecting `true` lower case +* `HELMFILE_USE_HELM_STATUS_TO_CHECK_RELEASE_EXISTENCE` - expecting non-empty value to use `helm status` to check release existence, instead of `helm list` which is the default behaviour * `HELMFILE_EXPERIMENTAL` - enable experimental features, expecting `true` lower case * `HELMFILE_ENVIRONMENT` - specify [Helmfile environment](https://helmfile.readthedocs.io/en/latest/#environment), it has lower priority than CLI argument `--environment` * `HELMFILE_TEMPDIR` - specify directory to store temporary files diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 54d05b9c..5dfceb75 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2729,8 +2729,34 @@ func TestApply(t *testing.T) { diffs map[exectest.DiffKey]error upgraded []exectest.Release deleted []exectest.Release + envs map[string]string log string }{ + // + // helm-status-check-to-release-existence + // + { + name: "helm-status-check-to-release-existence", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: stable/mychart2 +- name: foo_notFound + chart: stable/mychart1 + installed: false +`, + }, + diffs: map[exectest.DiffKey]error{ + {Name: "bar", Chart: "stable/mychart2", Flags: "--kube-context default --detailed-exitcode --reset-values"}: nil, + {Name: "foo_notFound", Chart: "stable/mychart1", Flags: "--kube-context default --detailed-exitcode --reset-values"}: nil, + }, + lists: map[exectest.ListKey]string{}, + upgraded: []exectest.Release{}, + deleted: []exectest.Release{}, + envs: map[string]string{"HELMFILE_USE_HELM_STATUS_TO_CHECK_RELEASE_EXISTENCE": "true"}, + }, // // complex test cases for smoke testing // @@ -3457,73 +3483,6 @@ releases: upgraded: []exectest.Release{}, // as we check for log output, set concurrency to 1 to avoid non-deterministic test result concurrency: 1, - log: `processing file "helmfile.yaml" in directory "." -changing working directory to "/path/to" -first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= -first-pass uses: &{default map[] map[]} -first-pass rendering output of "helmfile.yaml.part.0": - 0: - 1: - 2: - 3: releases: - 4: - name: kubernetes-external-secrets - 5: chart: incubator/raw - 6: namespace: kube-system - 7: - 8: - name: external-secrets - 9: chart: incubator/raw -10: namespace: default -11: labels: -12: app: test -13: needs: -14: - kube-system/kubernetes-external-secrets -15: -16: - name: my-release -17: chart: incubator/raw -18: namespace: default -19: labels: -20: app: test -21: needs: -22: - default/external-secrets -23: - -first-pass produced: &{default map[] map[]} -first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} -vals: -map[] -defaultVals:[] -second-pass rendering result of "helmfile.yaml.part.0": - 0: - 1: - 2: - 3: releases: - 4: - name: kubernetes-external-secrets - 5: chart: incubator/raw - 6: namespace: kube-system - 7: - 8: - name: external-secrets - 9: chart: incubator/raw -10: namespace: default -11: labels: -12: app: test -13: needs: -14: - kube-system/kubernetes-external-secrets -15: -16: - name: my-release -17: chart: incubator/raw -18: namespace: default -19: labels: -20: app: test -21: needs: -22: - default/external-secrets -23: - -merged environment: &{default map[] map[]} -2 release(s) matching app=test found in helmfile.yaml - -err: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies -changing working directory back to "/path/to" -`, }, { // see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747 @@ -3561,72 +3520,6 @@ releases: error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile", // as we check for log output, set concurrency to 1 to avoid non-deterministic test result concurrency: 1, - log: `processing file "helmfile.yaml" in directory "." -changing working directory to "/path/to" -first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= -first-pass uses: &{default map[] map[]} -first-pass rendering output of "helmfile.yaml.part.0": - 0: - 1: - 2: - 3: releases: - 4: - name: kubernetes-external-secrets - 5: chart: incubator/raw - 6: namespace: kube-system - 7: - 8: - name: external-secrets - 9: chart: incubator/raw -10: namespace: default -11: labels: -12: app: test -13: needs: -14: - kube-system/kubernetes-external-secrets -15: -16: - name: my-release -17: chart: incubator/raw -18: namespace: default -19: labels: -20: app: test -21: needs: -22: - default/external-secrets -23: - -first-pass produced: &{default map[] map[]} -first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} -vals: -map[] -defaultVals:[] -second-pass rendering result of "helmfile.yaml.part.0": - 0: - 1: - 2: - 3: releases: - 4: - name: kubernetes-external-secrets - 5: chart: incubator/raw - 6: namespace: kube-system - 7: - 8: - name: external-secrets - 9: chart: incubator/raw -10: namespace: default -11: labels: -12: app: test -13: needs: -14: - kube-system/kubernetes-external-secrets -15: -16: - name: my-release -17: chart: incubator/raw -18: namespace: default -19: labels: -20: app: test -21: needs: -22: - default/external-secrets -23: - -merged environment: &{default map[] map[]} -0 release(s) matching app=test_non_existent found in helmfile.yaml - -changing working directory back to "/path/to" -`, }, // // error cases @@ -3656,45 +3549,6 @@ releases: deleted: []exectest.Release{}, concurrency: 1, error: `in ./helmfile.yaml: release "default//foo" depends on "default/ns1/bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`, - log: `processing file "helmfile.yaml" in directory "." -changing working directory to "/path/to" -first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= -first-pass uses: &{default map[] map[]} -first-pass rendering output of "helmfile.yaml.part.0": - 0: - 1: releases: - 2: - name: bar - 3: namespace: ns1 - 4: chart: mychart3 - 5: - name: foo - 6: chart: mychart1 - 7: needs: - 8: - ns1/bar - 9: - -first-pass produced: &{default map[] map[]} -first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} -vals: -map[] -defaultVals:[] -second-pass rendering result of "helmfile.yaml.part.0": - 0: - 1: releases: - 2: - name: bar - 3: namespace: ns1 - 4: chart: mychart3 - 5: - name: foo - 6: chart: mychart1 - 7: needs: - 8: - ns1/bar - 9: - -merged environment: &{default map[] map[]} -1 release(s) matching name=foo found in helmfile.yaml - -err: release "default//foo" depends on "default/ns1/bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies -changing working directory back to "/path/to" -`, }, { name: "non-existent release in needs", @@ -3720,46 +3574,6 @@ releases: deleted: []exectest.Release{}, concurrency: 1, error: "in ./helmfile.yaml: release(s) \"default//foo\" depend(s) on an undefined release \"default/ns1/bar\". Perhaps you made a typo in \"needs\" or forgot defining a release named \"bar\" with appropriate \"namespace\" and \"kubeContext\"?", - log: `processing file "helmfile.yaml" in directory "." -changing working directory to "/path/to" -first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= -first-pass uses: &{default map[] map[]} -first-pass rendering output of "helmfile.yaml.part.0": - 0: - 1: releases: - 2: - name: baz - 3: namespace: ns1 - 4: chart: mychart3 - 5: - name: foo - 6: chart: mychart1 - 7: needs: - 8: - ns1/bar - 9: - -first-pass produced: &{default map[] map[]} -first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} -vals: -map[] -defaultVals:[] -second-pass rendering result of "helmfile.yaml.part.0": - 0: - 1: releases: - 2: - name: baz - 3: namespace: ns1 - 4: chart: mychart3 - 5: - name: foo - 6: chart: mychart1 - 7: needs: - 8: - ns1/bar - 9: - -merged environment: &{default map[] map[]} -WARNING: release foo needs bar, but bar is not installed due to installed: false. Either mark bar as installed or remove bar from foo's needs -2 release(s) found in helmfile.yaml - -err: release(s) "default//foo" depend(s) on an undefined release "default/ns1/bar". Perhaps you made a typo in "needs" or forgot defining a release named "bar" with appropriate "namespace" and "kubeContext"? -changing working directory back to "/path/to" -`, }, { name: "duplicate releases", @@ -3789,56 +3603,17 @@ releases: deleted: []exectest.Release{}, concurrency: 1, error: "in ./helmfile.yaml: found 2 duplicate releases with ID \"default//foo\"", - log: `processing file "helmfile.yaml" in directory "." -changing working directory to "/path/to" -first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= -first-pass uses: &{default map[] map[]} -first-pass rendering output of "helmfile.yaml.part.0": - 0: - 1: releases: - 2: - name: bar - 3: namespace: ns1 - 4: chart: mychart3 - 5: - name: foo - 6: chart: mychart2 - 7: needs: - 8: - ns1/bar - 9: - name: foo -10: chart: mychart1 -11: needs: -12: - ns1/bar -13: - -first-pass produced: &{default map[] map[]} -first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} -vals: -map[] -defaultVals:[] -second-pass rendering result of "helmfile.yaml.part.0": - 0: - 1: releases: - 2: - name: bar - 3: namespace: ns1 - 4: chart: mychart3 - 5: - name: foo - 6: chart: mychart2 - 7: needs: - 8: - ns1/bar - 9: - name: foo -10: chart: mychart1 -11: needs: -12: - ns1/bar -13: - -merged environment: &{default map[] map[]} -err: found 2 duplicate releases with ID "default//foo" -changing working directory back to "/path/to" -`, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { + if tc.envs != nil { + for k, v := range tc.envs { + os.Setenv(k, v) + defer os.Unsetenv(k) + } + } wantUpgrades := tc.upgraded wantDeletes := tc.deleted diff --git a/pkg/app/testdata/testapply/duplicate_releases/log b/pkg/app/testdata/testapply/duplicate_releases/log new file mode 100644 index 00000000..5a14f57e --- /dev/null +++ b/pkg/app/testdata/testapply/duplicate_releases/log @@ -0,0 +1,44 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: bar + 3: namespace: ns1 + 4: chart: mychart3 + 5: - name: foo + 6: chart: mychart2 + 7: needs: + 8: - ns1/bar + 9: - name: foo +10: chart: mychart1 +11: needs: +12: - ns1/bar +13: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: bar + 3: namespace: ns1 + 4: chart: mychart3 + 5: - name: foo + 6: chart: mychart2 + 7: needs: + 8: - ns1/bar + 9: - name: foo +10: chart: mychart1 +11: needs: +12: - ns1/bar +13: + +merged environment: &{default map[] map[]} +err: found 2 duplicate releases with ID "default//foo" +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/testapply/helm-status-check-to-release-existence/log b/pkg/app/testdata/testapply/helm-status-check-to-release-existence/log new file mode 100644 index 00000000..223b67ad --- /dev/null +++ b/pkg/app/testdata/testapply/helm-status-check-to-release-existence/log @@ -0,0 +1,39 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: bar + 3: chart: stable/mychart2 + 4: - name: foo_notFound + 5: chart: stable/mychart1 + 6: installed: false + 7: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: bar + 3: chart: stable/mychart2 + 4: - name: foo_notFound + 5: chart: stable/mychart1 + 6: installed: false + 7: + +merged environment: &{default map[] map[]} +2 release(s) found in helmfile.yaml + +Checking release existence using `helm status` for release foo_notFound +invoking preapply hooks for 1 groups of releases in this order: +GROUP RELEASES +1 default//bar, default//foo_notFound + +invoking preapply hooks for releases in group 1/1: default//bar, default//foo_notFound +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/testapply/non-existent_release_in_needs/log b/pkg/app/testdata/testapply/non-existent_release_in_needs/log new file mode 100644 index 00000000..b13e2b15 --- /dev/null +++ b/pkg/app/testdata/testapply/non-existent_release_in_needs/log @@ -0,0 +1,39 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: namespace: ns1 + 4: chart: mychart3 + 5: - name: foo + 6: chart: mychart1 + 7: needs: + 8: - ns1/bar + 9: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: namespace: ns1 + 4: chart: mychart3 + 5: - name: foo + 6: chart: mychart1 + 7: needs: + 8: - ns1/bar + 9: + +merged environment: &{default map[] map[]} +WARNING: release foo needs bar, but bar is not installed due to installed: false. Either mark bar as installed or remove bar from foo's needs +2 release(s) found in helmfile.yaml + +err: release(s) "default//foo" depend(s) on an undefined release "default/ns1/bar". Perhaps you made a typo in "needs" or forgot defining a release named "bar" with appropriate "namespace" and "kubeContext"? +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/testapply/unselected_release_in_needs/log b/pkg/app/testdata/testapply/unselected_release_in_needs/log new file mode 100644 index 00000000..fd4dca83 --- /dev/null +++ b/pkg/app/testdata/testapply/unselected_release_in_needs/log @@ -0,0 +1,38 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: bar + 3: namespace: ns1 + 4: chart: mychart3 + 5: - name: foo + 6: chart: mychart1 + 7: needs: + 8: - ns1/bar + 9: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: bar + 3: namespace: ns1 + 4: chart: mychart3 + 5: - name: foo + 6: chart: mychart1 + 7: needs: + 8: - ns1/bar + 9: + +merged environment: &{default map[] map[]} +1 release(s) matching name=foo found in helmfile.yaml + +err: release "default//foo" depends on "default/ns1/bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/testapply/upgrades_with_bad_selector/log b/pkg/app/testdata/testapply/upgrades_with_bad_selector/log new file mode 100644 index 00000000..37de515c --- /dev/null +++ b/pkg/app/testdata/testapply/upgrades_with_bad_selector/log @@ -0,0 +1,65 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: + 2: + 3: releases: + 4: - name: kubernetes-external-secrets + 5: chart: incubator/raw + 6: namespace: kube-system + 7: + 8: - name: external-secrets + 9: chart: incubator/raw +10: namespace: default +11: labels: +12: app: test +13: needs: +14: - kube-system/kubernetes-external-secrets +15: +16: - name: my-release +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - default/external-secrets +23: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: + 2: + 3: releases: + 4: - name: kubernetes-external-secrets + 5: chart: incubator/raw + 6: namespace: kube-system + 7: + 8: - name: external-secrets + 9: chart: incubator/raw +10: namespace: default +11: labels: +12: app: test +13: needs: +14: - kube-system/kubernetes-external-secrets +15: +16: - name: my-release +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - default/external-secrets +23: + +merged environment: &{default map[] map[]} +0 release(s) matching app=test_non_existent found in helmfile.yaml + +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/testapply/upgrades_with_good_selector_with_--skip-needs=false_--include-needs=true/log b/pkg/app/testdata/testapply/upgrades_with_good_selector_with_--skip-needs=false_--include-needs=true/log new file mode 100644 index 00000000..b286816b --- /dev/null +++ b/pkg/app/testdata/testapply/upgrades_with_good_selector_with_--skip-needs=false_--include-needs=true/log @@ -0,0 +1,66 @@ +processing file "helmfile.yaml" in directory "." +changing working directory to "/path/to" +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: + 2: + 3: releases: + 4: - name: kubernetes-external-secrets + 5: chart: incubator/raw + 6: namespace: kube-system + 7: + 8: - name: external-secrets + 9: chart: incubator/raw +10: namespace: default +11: labels: +12: app: test +13: needs: +14: - kube-system/kubernetes-external-secrets +15: +16: - name: my-release +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - default/external-secrets +23: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: + 2: + 3: releases: + 4: - name: kubernetes-external-secrets + 5: chart: incubator/raw + 6: namespace: kube-system + 7: + 8: - name: external-secrets + 9: chart: incubator/raw +10: namespace: default +11: labels: +12: app: test +13: needs: +14: - kube-system/kubernetes-external-secrets +15: +16: - name: my-release +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - default/external-secrets +23: + +merged environment: &{default map[] map[]} +2 release(s) matching app=test found in helmfile.yaml + +err: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies +changing working directory back to "/path/to" diff --git a/pkg/envvar/const.go b/pkg/envvar/const.go index 17e89926..a40f5d43 100644 --- a/pkg/envvar/const.go +++ b/pkg/envvar/const.go @@ -3,6 +3,9 @@ package envvar const ( DisableInsecureFeatures = "HELMFILE_DISABLE_INSECURE_FEATURES" + // use helm status to check if a release exists before installing it + UseHelmStatusToCheckReleaseExistence = "HELMFILE_USE_HELM_STATUS_TO_CHECK_RELEASE_EXISTENCE" + // TODO: Remove this function once Helmfile v0.x SkipInsecureTemplateFunctions = "HELMFILE_SKIP_INSECURE_TEMPLATE_FUNCTIONS" diff --git a/pkg/exectest/helm.go b/pkg/exectest/helm.go index a805c9ce..3b7c405c 100644 --- a/pkg/exectest/helm.go +++ b/pkg/exectest/helm.go @@ -138,6 +138,9 @@ func (helm *Helm) DiffRelease(context helmexec.HelmContext, name, chart, namespa return err } func (helm *Helm) ReleaseStatus(context helmexec.HelmContext, release string, flags ...string) error { + if strings.Contains(release, "notFound") { + return errors.New("Error: release: not found") + } if strings.Contains(release, "error") { return errors.New("error") } diff --git a/pkg/state/state.go b/pkg/state/state.go index 0172c763..929bc5cc 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -29,6 +29,7 @@ import ( "github.com/helmfile/helmfile/pkg/argparser" "github.com/helmfile/helmfile/pkg/environment" + "github.com/helmfile/helmfile/pkg/envvar" "github.com/helmfile/helmfile/pkg/event" "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" @@ -698,13 +699,25 @@ func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValu } func (st *HelmState) isReleaseInstalled(context helmexec.HelmContext, helm helmexec.Interface, release ReleaseSpec) (bool, error) { + if os.Getenv(envvar.UseHelmStatusToCheckReleaseExistence) != "" { + st.logger.Debugf("Checking release existence using `helm status` for release %s", release.Name) + + flags := st.kubeConnectionFlags(&release) + if release.Namespace != "" { + flags = append(flags, "--namespace", release.Namespace) + } + err := helm.ReleaseStatus(context, release.Name, flags...) + if err != nil && strings.Contains(err.Error(), "Error: release: not found") { + return false, nil + } + return true, err + } + out, err := st.listReleases(context, helm, &release) if err != nil { return false, err - } else if out != "" { - return true, nil } - return false, nil + return out != "", nil } func (st *HelmState) DetectReleasesToBeDeletedForSync(helm helmexec.Interface, releases []ReleaseSpec) ([]ReleaseSpec, error) {