Repository: roboll/helmfile
Pull request #1794 (2021-04-23 05:24:33)

feat: moved chartify logs to Debug
Chartify logs are very _chatty_ and stands out event in quiet mode. This PR moves them to debug to hide chart building process in normal output.


Patch:
diff --git a/.gitignore b/.gitignore
index 8f84f97..b1fd245 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ vendor/
 *.log
 .vagrant/
 *.lock
+test/integration/.gnupg/
diff --git a/Vagrantfile b/Vagrantfile
index 69368c6..97c1851 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -1,11 +1,11 @@
 Vagrant.configure("2") do |config|
-  config.vm.box = "ubuntu/xenial64"
+  config.vm.box = "ubuntu/focal64"
   config.vm.hostname = "minikube.box"
   config.vm.provision :shell, privileged: false,
     inline: <<-EOS
       set -e
       sudo apt-get update
-      sudo apt-get install -y make docker.io=18.09.7-*
+      sudo apt-get install -y make docker.io
       sudo systemctl start docker
       sudo usermod -G docker $USER
       cd /vagrant/.circleci
diff --git a/main.go b/main.go
index 0ab52a0..590bd23 100644
--- a/main.go
+++ b/main.go
@@ -225,6 +225,11 @@ func main() {
 					Value: 0,
 					Usage: "output NUM lines of context around changes",
 				},
+				cli.StringFlag{
+					Name:  "output",
+					Value: "",
+					Usage: "output format for diff plugin",
+				},
 			},
 			Action: action(func(a *app.App, c configImpl) error {
 				return a.Diff(c)
@@ -441,6 +446,11 @@ func main() {
 					Value: 0,
 					Usage: "output NUM lines of context around changes",
 				},
+				cli.StringFlag{
+					Name:  "output",
+					Value: "",
+					Usage: "output format for diff plugin",
+				},
 				cli.BoolFlag{
 					Name:  "detailed-exitcode",
 					Usage: "return a non-zero exit code 2 instead of 0 when there were changes detected AND the changes are synced successfully",
@@ -855,6 +865,10 @@ func (c configImpl) Context() int {
 	return c.c.Int("context")
 }
 
+func (c configImpl) DiffOutput() string {
+	return c.c.String("output")
+}
+
 func (c configImpl) SkipCleanup() bool {
 	return c.c.Bool("skip-cleanup")
 }
diff --git a/pkg/app/app.go b/pkg/app/app.go
index 638c118..e7c63b3 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -1153,6 +1153,7 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) {
 	diffOpts := &state.DiffOpts{
 		NoColor:           c.NoColor(),
 		Context:           c.Context(),
+		Output:            c.DiffOutput(),
 		Set:               c.Set(),
 		SkipCleanup:       c.RetainValuesFiles() || c.SkipCleanup(),
 		SkipDiffOnInstall: c.SkipDiffOnInstall(),
@@ -1378,6 +1379,7 @@ func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error)
 
 	opts := &state.DiffOpts{
 		Context: c.Context(),
+		Output:  c.DiffOutput(),
 		NoColor: c.NoColor(),
 		Set:     c.Set(),
 	}
diff --git a/pkg/app/config.go b/pkg/app/config.go
index 358a2ee..f797a61 100644
--- a/pkg/app/config.go
+++ b/pkg/app/config.go
@@ -54,6 +54,7 @@ type ApplyConfigProvider interface {
 
 	NoColor() bool
 	Context() int
+	DiffOutput() string
 
 	RetainValuesFiles() bool
 	SkipCleanup() bool
@@ -104,6 +105,7 @@ type DiffConfigProvider interface {
 	DetailedExitcode() bool
 	NoColor() bool
 	Context() int
+	DiffOutput() string
 
 	concurrencyConfig
 }
diff --git a/pkg/state/state.go b/pkg/state/state.go
index 1508eef..4bb6588 100644
--- a/pkg/state/state.go
+++ b/pkg/state/state.go
@@ -332,6 +332,9 @@ const MissingFileHandlerWarn = "Warn"
 const MissingFileHandlerDebug = "Debug"
 
 func (st *HelmState) ApplyOverrides(spec *ReleaseSpec) {
+	if spec.KubeContext == "" {
+		spec.KubeContext = st.HelmDefaults.KubeContext
+	}
 	if st.OverrideNamespace != "" {
 		spec.Namespace = st.OverrideNamespace
 
@@ -1066,6 +1069,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
 					c := chartify.New(
 						chartify.HelmBin(st.DefaultHelmBinary),
 						chartify.UseHelm3(helm3),
+						chartify.WithLogf(st.logger.Debugf),
 					)
 
 					chartifyOpts := chartification.Opts
@@ -1664,6 +1668,10 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu
 					flags = append(flags, "--context", fmt.Sprintf("%d", opts.Context))
 				}
 
+				if opts.Output != "" {
+					flags = append(flags, "--output", fmt.Sprintf("%s", opts.Output))
+				}
+
 				if opts.Set != nil {
 					for _, s := range opts.Set {
 						flags = append(flags, "--set", s)
@@ -1739,6 +1747,7 @@ func (st *HelmState) createHelmContextWithWriter(spec *ReleaseSpec, w io.Writer)
 
 type DiffOpts struct {
 	Context int
+	Output  string
 	NoColor bool
 	Set     []string
 
diff --git a/pkg/state/state_exec_tmpl.go b/pkg/state/state_exec_tmpl.go
index 01ea35b..3e12cd6 100644
--- a/pkg/state/state_exec_tmpl.go
+++ b/pkg/state/state_exec_tmpl.go
@@ -23,10 +23,11 @@ func (st *HelmState) createReleaseTemplateData(release *ReleaseSpec, vals map[st
 		Chart:       st.OverrideChart,
 		Values:      vals,
 		Release: releaseTemplateDataRelease{
-			Name:      release.Name,
-			Chart:     release.Chart,
-			Namespace: release.Namespace,
-			Labels:    release.Labels,
+			Name:        release.Name,
+			Chart:       release.Chart,
+			Namespace:   release.Namespace,
+			Labels:      release.Labels,
+			KubeContext: release.KubeContext,
 		},
 	}
 	tmplData.StateValues = &tmplData.Values
diff --git a/pkg/state/state_run.go b/pkg/state/state_run.go
index 020ed7b..37456b9 100644
--- a/pkg/state/state_run.go
+++ b/pkg/state/state_run.go
@@ -151,13 +151,27 @@ func GroupReleasesByDependency(releases []Release, opts PlanOptions) ([][]Releas
 
 		// Only compute dependencies from non-filtered releases
 		if !r.Filtered {
-			d.Add(id, dag.Dependencies(r.Needs))
+			// Since the representation differs between needs and id,
+			// correct it by prepending KubeContext.
+			var needs []string
+			for i := 0; i < len(r.Needs); i++ {
+				n := r.Needs[i]
+				if r.KubeContext != "" {
+					n = r.KubeContext + "/" + n
+				}
+				needs = append(needs, n)
+			}
+			d.Add(id, dag.Dependencies(needs))
 		}
 	}
 
 	for _, r := range releases {
 		if !r.Filtered {
 			for _, n := range r.Needs {
+				// To map n into idToReleases correctly, prepend KubeContext to n.
+				if r.KubeContext != "" {
+					n = r.KubeContext + "/" + n
+				}
 				if _, ok := idToReleases[n]; !ok {
 					id := ReleaseToID(&r.ReleaseSpec)
 					return nil, fmt.Errorf("%q depends on nonexistent release %q", id, n)
diff --git a/pkg/state/types.go b/pkg/state/types.go
index a8c094f..674a5ee 100644
--- a/pkg/state/types.go
+++ b/pkg/state/types.go
@@ -52,4 +52,7 @@ type releaseTemplateDataRelease struct {
 
 	// Chart is ReleaseSpec.Chart
 	Chart string
+
+	// KubeContext is ReleaseSpec.KubeContext
+	KubeContext string
 }


Test patch:
diff --git a/pkg/app/app_apply_nokubectx_test.go b/pkg/app/app_apply_nokubectx_test.go
new file mode 100644
index 0000000..a4a9e22
--- /dev/null
+++ b/pkg/app/app_apply_nokubectx_test.go
@@ -0,0 +1,1064 @@
+package app
+
+import (
+	"bufio"
+	"bytes"
+	"io"
+	"path/filepath"
+	"sync"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/roboll/helmfile/pkg/exectest"
+	"github.com/roboll/helmfile/pkg/helmexec"
+	"github.com/roboll/helmfile/pkg/testhelper"
+	"github.com/variantdev/vals"
+)
+
+func TestApply_3(t *testing.T) {
+	type fields struct {
+		skipNeeds    bool
+		includeNeeds bool
+	}
+
+	type testcase struct {
+		fields            fields
+		ns                string
+		concurrency       int
+		skipDiffOnInstall bool
+		error             string
+		files             map[string]string
+		selectors         []string
+		lists             map[exectest.ListKey]string
+		diffs             map[exectest.DiffKey]error
+		upgraded          []exectest.Release
+		deleted           []exectest.Release
+		log               string
+	}
+
+	check := func(t *testing.T, tc testcase) {
+		t.Helper()
+
+		wantUpgrades := tc.upgraded
+		wantDeletes := tc.deleted
+
+		var helm = &exectest.Helm{
+			FailOnUnexpectedList: true,
+			FailOnUnexpectedDiff: true,
+			Lists:                tc.lists,
+			Diffs:                tc.diffs,
+			DiffMutex:            &sync.Mutex{},
+			ChartsMutex:          &sync.Mutex{},
+			ReleasesMutex:        &sync.Mutex{},
+		}
+
+		bs := &bytes.Buffer{}
+
+		func() {
+			t.Helper()
+
+			logReader, logWriter := io.Pipe()
+
+			logFlushed := &sync.WaitGroup{}
+			// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
+			logFlushed.Add(1)
+			go func() {
+				scanner := bufio.NewScanner(logReader)
+				for scanner.Scan() {
+					bs.Write(scanner.Bytes())
+					bs.WriteString("\n")
+				}
+				logFlushed.Done()
+			}()
+
+			defer func() {
+				// This is here to avoid data-trace on bytes buffer `bs` to capture logs
+				if err := logWriter.Close(); err != nil {
+					panic(err)
+				}
+				logFlushed.Wait()
+			}()
+
+			logger := helmexec.NewLogger(logWriter, "debug")
+
+			valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
+			if err != nil {
+				t.Errorf("unexpected error creating vals runtime: %v", err)
+			}
+
+			app := appWithFs(&App{
+				OverrideHelmBinary:  DefaultHelmBinary,
+				glob:                filepath.Glob,
+				abs:                 filepath.Abs,
+				OverrideKubeContext: "",
+				Env:                 "default",
+				Logger:              logger,
+				helms: map[helmKey]helmexec.Interface{
+					createHelmKey("helm", ""): helm,
+				},
+				valsRuntime: valsRuntime,
+			}, tc.files)
+
+			if tc.ns != "" {
+				app.Namespace = tc.ns
+			}
+
+			if tc.selectors != nil {
+				app.Selectors = tc.selectors
+			}
+
+			syncErr := app.Apply(applyConfig{
+				// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
+				concurrency:       tc.concurrency,
+				logger:            logger,
+				skipDiffOnInstall: tc.skipDiffOnInstall,
+				skipNeeds:         tc.fields.skipNeeds,
+				includeNeeds:      tc.fields.includeNeeds,
+			})
+
+			var gotErr string
+			if syncErr != nil {
+				gotErr = syncErr.Error()
+			}
+
+			if d := cmp.Diff(tc.error, gotErr); d != "" {
+				t.Fatalf("unexpected error: want (-), got (+): %s", d)
+			}
+
+			if len(wantUpgrades) > len(helm.Releases) {
+				t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
+			}
+
+			for relIdx := range wantUpgrades {
+				if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
+					t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
+				}
+				for flagIdx := range wantUpgrades[relIdx].Flags {
+					if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
+						t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
+					}
+				}
+			}
+
+			if len(wantDeletes) > len(helm.Deleted) {
+				t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
+			}
+
+			for relIdx := range wantDeletes {
+				if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
+					t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
+				}
+				for flagIdx := range wantDeletes[relIdx].Flags {
+					if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
+						t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
+					}
+				}
+			}
+		}()
+
+		if tc.log != "" {
+			actual := bs.String()
+
+			diff, exists := testhelper.Diff(tc.log, actual, 3)
+			if exists {
+				t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
+			}
+		}
+	}
+
+	t.Run("skip-needs=true", func(t *testing.T) {
+		check(t, testcase{
+			fields: fields{
+				skipNeeds: true,
+			},
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+
+- 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
+`,
+			},
+			selectors: []string{"app=test"},
+			upgraded: []exectest.Release{
+				{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
+				{Name: "my-release", Flags: []string{"--namespace", "default"}},
+			},
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:       helmexec.ExitError{Code: 2},
+			},
+			// 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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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
+
+Affected releases are:
+  external-secrets (incubator/raw) UPDATED
+  my-release (incubator/raw) UPDATED
+
+processing 2 groups of releases in this order:
+GROUP RELEASES
+1     default/external-secrets
+2     default/my-release
+
+processing releases in group 1/2: default/external-secrets
+getting deployed release version failed:unexpected list key: {^external-secrets$ --deleting--deployed--failed--pending}
+processing releases in group 2/2: default/my-release
+getting deployed release version failed:unexpected list key: {^my-release$ --deleting--deployed--failed--pending}
+
+UPDATED RELEASES:
+NAME               CHART           VERSION
+external-secrets   incubator/raw          
+my-release         incubator/raw          
+
+`,
+		})
+	})
+
+	t.Run("skip-needs=true with no diff on a release", func(t *testing.T) {
+		check(t, testcase{
+			fields: fields{
+				skipNeeds: true,
+			},
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+
+- 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
+`,
+			},
+			selectors: []string{"app=test"},
+			upgraded: []exectest.Release{
+				{Name: "external-secrets", Flags: []string{"--namespace", "default"}},
+			},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^external-secrets$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+^external-secrets$ 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	raw-3.1.0	3.1.0      	default
+`,
+			},
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:       nil,
+			},
+			// 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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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
+
+Affected releases are:
+  external-secrets (incubator/raw) UPDATED
+
+processing 1 groups of releases in this order:
+GROUP RELEASES
+1     default/external-secrets
+
+processing releases in group 1/1: default/external-secrets
+
+UPDATED RELEASES:
+NAME               CHART           VERSION
+external-secrets   incubator/raw     3.1.0
+
+`,
+		})
+	})
+
+	t.Run("skip-needs=false include-needs=true", func(t *testing.T) {
+		check(t, testcase{
+			fields: fields{
+				skipNeeds:    false,
+				includeNeeds: true,
+			},
+			error: ``,
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+
+- 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
+`,
+			},
+			selectors: []string{"app=test"},
+			upgraded:  []exectest.Release{},
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "kubernetes-external-secrets", Chart: "incubator/raw", Flags: "--namespacekube-system--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:                helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:                      helmexec.ExitError{Code: 2},
+			},
+			// 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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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
+
+Affected releases are:
+  external-secrets (incubator/raw) UPDATED
+  kubernetes-external-secrets (incubator/raw) UPDATED
+  my-release (incubator/raw) UPDATED
+
+processing 3 groups of releases in this order:
+GROUP RELEASES
+1     kube-system/kubernetes-external-secrets
+2     default/external-secrets
+3     default/my-release
+
+processing releases in group 1/3: kube-system/kubernetes-external-secrets
+getting deployed release version failed:unexpected list key: {^kubernetes-external-secrets$ --deleting--deployed--failed--pending}
+processing releases in group 2/3: default/external-secrets
+getting deployed release version failed:unexpected list key: {^external-secrets$ --deleting--deployed--failed--pending}
+processing releases in group 3/3: default/my-release
+getting deployed release version failed:unexpected list key: {^my-release$ --deleting--deployed--failed--pending}
+
+UPDATED RELEASES:
+NAME                          CHART           VERSION
+kubernetes-external-secrets   incubator/raw          
+external-secrets              incubator/raw          
+my-release                    incubator/raw          
+
+`,
+		})
+	})
+
+	t.Run("skip-needs=false include-needs=true but no diff on needed release", func(t *testing.T) {
+		check(t, testcase{
+			fields: fields{
+				skipNeeds:    false,
+				includeNeeds: true,
+			},
+			error: ``,
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+
+- 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
+`,
+			},
+			selectors: []string{"app=test"},
+			upgraded:  []exectest.Release{},
+			lists:     nil,
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "kubernetes-external-secrets", Chart: "incubator/raw", Flags: "--namespacekube-system--detailed-exitcode"}: nil,
+				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:                helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:                      helmexec.ExitError{Code: 2},
+			},
+			// 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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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
+
+Affected releases are:
+  external-secrets (incubator/raw) UPDATED
+  my-release (incubator/raw) UPDATED
+
+processing 2 groups of releases in this order:
+GROUP RELEASES
+1     default/external-secrets
+2     default/my-release
+
+processing releases in group 1/2: default/external-secrets
+getting deployed release version failed:unexpected list key: {^external-secrets$ --deleting--deployed--failed--pending}
+processing releases in group 2/2: default/my-release
+getting deployed release version failed:unexpected list key: {^my-release$ --deleting--deployed--failed--pending}
+
+UPDATED RELEASES:
+NAME               CHART           VERSION
+external-secrets   incubator/raw          
+my-release         incubator/raw          
+
+`,
+		})
+	})
+
+	t.Run("skip-needs=false include-needs=true with installed but disabled release", func(t *testing.T) {
+		check(t, testcase{
+			fields: fields{
+				skipNeeds:    false,
+				includeNeeds: true,
+			},
+			error: ``,
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+  installed: false
+
+- 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
+`,
+			},
+			selectors: []string{"app=test"},
+			upgraded:  []exectest.Release{},
+			lists: map[exectest.ListKey]string{
+				// delete frontend-v1 and backend-v1
+				exectest.ListKey{Filter: "^kubernetes-external-secrets$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+^kubernetes-external-secrets$ 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	backend-3.1.0	3.1.0      	default
+`,
+			},
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:       helmexec.ExitError{Code: 2},
+			},
+			// 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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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:   installed: false
+ 8: 
+ 9: - name: external-secrets
+10:   chart: incubator/raw
+11:   namespace: default
+12:   labels:
+13:     app: test
+14:   needs:
+15:   - kube-system/kubernetes-external-secrets
+16: 
+17: - name: my-release
+18:   chart: incubator/raw
+19:   namespace: default
+20:   labels:
+21:     app: test
+22:   needs:
+23:   - default/external-secrets
+24: 
+
+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:   installed: false
+ 8: 
+ 9: - name: external-secrets
+10:   chart: incubator/raw
+11:   namespace: default
+12:   labels:
+13:     app: test
+14:   needs:
+15:   - kube-system/kubernetes-external-secrets
+16: 
+17: - name: my-release
+18:   chart: incubator/raw
+19:   namespace: default
+20:   labels:
+21:     app: test
+22:   needs:
+23:   - default/external-secrets
+24: 
+
+merged environment: &{default map[] map[]}
+2 release(s) matching app=test found in helmfile.yaml
+
+Affected releases are:
+  external-secrets (incubator/raw) UPDATED
+  kubernetes-external-secrets (incubator/raw) DELETED
+  my-release (incubator/raw) UPDATED
+
+processing 1 groups of releases in this order:
+GROUP RELEASES
+1     kube-system/kubernetes-external-secrets
+
+processing releases in group 1/1: kube-system/kubernetes-external-secrets
+processing 2 groups of releases in this order:
+GROUP RELEASES
+1     default/external-secrets
+2     default/my-release
+
+processing releases in group 1/2: default/external-secrets
+getting deployed release version failed:unexpected list key: {^external-secrets$ --deleting--deployed--failed--pending}
+processing releases in group 2/2: default/my-release
+getting deployed release version failed:unexpected list key: {^my-release$ --deleting--deployed--failed--pending}
+
+UPDATED RELEASES:
+NAME               CHART           VERSION
+external-secrets   incubator/raw          
+my-release         incubator/raw          
+
+
+DELETED RELEASES:
+NAME
+kubernetes-external-secrets
+`,
+		})
+	})
+
+	t.Run("skip-needs=false include-needs=true with not installed and disabled release", func(t *testing.T) {
+		check(t, testcase{
+			fields: fields{
+				skipNeeds:    false,
+				includeNeeds: true,
+			},
+			error: ``,
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+  installed: false
+
+- 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
+`,
+			},
+			selectors: []string{"app=test"},
+			upgraded:  []exectest.Release{},
+			lists: map[exectest.ListKey]string{
+				// delete frontend-v1 and backend-v1
+				exectest.ListKey{Filter: "^kubernetes-external-secrets$", Flags: helmV2ListFlagsWithoutKubeContext}: ``,
+			},
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:       helmexec.ExitError{Code: 2},
+			},
+			// 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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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:   installed: false
+ 8: 
+ 9: - name: external-secrets
+10:   chart: incubator/raw
+11:   namespace: default
+12:   labels:
+13:     app: test
+14:   needs:
+15:   - kube-system/kubernetes-external-secrets
+16: 
+17: - name: my-release
+18:   chart: incubator/raw
+19:   namespace: default
+20:   labels:
+21:     app: test
+22:   needs:
+23:   - default/external-secrets
+24: 
+
+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:   installed: false
+ 8: 
+ 9: - name: external-secrets
+10:   chart: incubator/raw
+11:   namespace: default
+12:   labels:
+13:     app: test
+14:   needs:
+15:   - kube-system/kubernetes-external-secrets
+16: 
+17: - name: my-release
+18:   chart: incubator/raw
+19:   namespace: default
+20:   labels:
+21:     app: test
+22:   needs:
+23:   - default/external-secrets
+24: 
+
+merged environment: &{default map[] map[]}
+2 release(s) matching app=test found in helmfile.yaml
+
+Affected releases are:
+  external-secrets (incubator/raw) UPDATED
+  my-release (incubator/raw) UPDATED
+
+processing 2 groups of releases in this order:
+GROUP RELEASES
+1     default/external-secrets
+2     default/my-release
+
+processing releases in group 1/2: default/external-secrets
+getting deployed release version failed:unexpected list key: {^external-secrets$ --deleting--deployed--failed--pending}
+processing releases in group 2/2: default/my-release
+getting deployed release version failed:unexpected list key: {^my-release$ --deleting--deployed--failed--pending}
+
+UPDATED RELEASES:
+NAME               CHART           VERSION
+external-secrets   incubator/raw          
+my-release         incubator/raw          
+
+`,
+		})
+	})
+
+	t.Run("bad --selector", func(t *testing.T) {
+		check(t, testcase{
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+
+- 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
+`,
+			},
+			selectors: []string{"app=test_non_existent"},
+			upgraded:  []exectest.Release{},
+			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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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
+
+`,
+		})
+	})
+}
diff --git a/pkg/app/app_apply_test.go b/pkg/app/app_apply_test.go
index 9f217fa..59c2bfb 100644
--- a/pkg/app/app_apply_test.go
+++ b/pkg/app/app_apply_test.go
@@ -277,12 +277,12 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
-2     default/my-release
+1     default/default/external-secrets
+2     default/default/my-release
 
-processing releases in group 1/2: default/external-secrets
+processing releases in group 1/2: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: default/my-release
+processing releases in group 2/2: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -408,9 +408,9 @@ Affected releases are:
 
 processing 1 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
+1     default/default/external-secrets
 
-processing releases in group 1/1: default/external-secrets
+processing releases in group 1/1: default/default/external-secrets
 
 UPDATED RELEASES:
 NAME               CHART           VERSION
@@ -532,15 +532,15 @@ Affected releases are:
 
 processing 3 groups of releases in this order:
 GROUP RELEASES
-1     kube-system/kubernetes-external-secrets
-2     default/external-secrets
-3     default/my-release
+1     default/kube-system/kubernetes-external-secrets
+2     default/default/external-secrets
+3     default/default/my-release
 
-processing releases in group 1/3: kube-system/kubernetes-external-secrets
+processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
 getting deployed release version failed:unexpected list key: {^kubernetes-external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/3: default/external-secrets
+processing releases in group 2/3: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 3/3: default/my-release
+processing releases in group 3/3: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -665,12 +665,12 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
-2     default/my-release
+1     default/default/external-secrets
+2     default/default/my-release
 
-processing releases in group 1/2: default/external-secrets
+processing releases in group 1/2: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: default/my-release
+processing releases in group 2/2: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -802,17 +802,17 @@ Affected releases are:
 
 processing 1 groups of releases in this order:
 GROUP RELEASES
-1     kube-system/kubernetes-external-secrets
+1     default/kube-system/kubernetes-external-secrets
 
-processing releases in group 1/1: kube-system/kubernetes-external-secrets
+processing releases in group 1/1: default/kube-system/kubernetes-external-secrets
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
-2     default/my-release
+1     default/default/external-secrets
+2     default/default/my-release
 
-processing releases in group 1/2: default/external-secrets
+processing releases in group 1/2: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: default/my-release
+processing releases in group 2/2: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -945,12 +945,12 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
-2     default/my-release
+1     default/default/external-secrets
+2     default/default/my-release
 
-processing releases in group 1/2: default/external-secrets
+processing releases in group 1/2: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: default/my-release
+processing releases in group 2/2: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
diff --git a/pkg/app/app_sync_test.go b/pkg/app/app_sync_test.go
index 382dad6..d36dc37 100644
--- a/pkg/app/app_sync_test.go
+++ b/pkg/app/app_sync_test.go
@@ -271,12 +271,12 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
-2     default/my-release
+1     default/default/external-secrets
+2     default/default/my-release
 
-processing releases in group 1/2: default/external-secrets
+processing releases in group 1/2: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: default/my-release
+processing releases in group 2/2: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -395,15 +395,15 @@ Affected releases are:
 
 processing 3 groups of releases in this order:
 GROUP RELEASES
-1     kube-system/kubernetes-external-secrets
-2     default/external-secrets
-3     default/my-release
+1     default/kube-system/kubernetes-external-secrets
+2     default/default/external-secrets
+3     default/default/my-release
 
-processing releases in group 1/3: kube-system/kubernetes-external-secrets
+processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
 getting deployed release version failed:unexpected list key: {^kubernetes-external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/3: default/external-secrets
+processing releases in group 2/3: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 3/3: default/my-release
+processing releases in group 3/3: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -532,17 +532,17 @@ Affected releases are:
 
 processing 1 groups of releases in this order:
 GROUP RELEASES
-1     kube-system/kubernetes-external-secrets
+1     default/kube-system/kubernetes-external-secrets
 
-processing releases in group 1/1: kube-system/kubernetes-external-secrets
+processing releases in group 1/1: default/kube-system/kubernetes-external-secrets
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
-2     default/my-release
+1     default/default/external-secrets
+2     default/default/my-release
 
-processing releases in group 1/2: default/external-secrets
+processing releases in group 1/2: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: default/my-release
+processing releases in group 2/2: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -671,12 +671,12 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
-2     default/my-release
+1     default/default/external-secrets
+2     default/default/my-release
 
-processing releases in group 1/2: default/external-secrets
+processing releases in group 1/2: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: default/my-release
+processing releases in group 2/2: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go
index d10602a..71749b8 100644
--- a/pkg/app/app_test.go
+++ b/pkg/app/app_test.go
@@ -499,7 +499,7 @@ releases:
 		{label: "name!=", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[0]: in /path/to/helmfile.d/a1.yaml: Malformed label: name!=. Expected label in form k=v or k!=v"},
 		{label: "name", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[0]: in /path/to/helmfile.d/a1.yaml: Malformed label: name. Expected label in form k=v or k!=v"},
 		// See https://github.com/roboll/helmfile/issues/193
-		{label: "duplicatedNs=yes", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[2]: in /path/to/helmfile.d/b.yaml: duplicate release \"foo\" found in namespace \"zoo\": there were 2 releases named \"foo\" matching specified selector"},
+		{label: "duplicatedNs=yes", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[2]: in /path/to/helmfile.d/b.yaml: duplicate release \"foo\" found in namespace \"zoo\" in kubecontext \"default\": there were 2 releases named \"foo\" matching specified selector"},
 		{label: "duplicatedCtx=yes", expectedCount: 0, expectErr: true, errMsg: "in ./helmfile.yaml: in .helmfiles[2]: in /path/to/helmfile.d/b.yaml: duplicate release \"foo\" found in namespace \"zoo\" in kubecontext \"baz\": there were 2 releases named \"foo\" matching specified selector"},
 		{label: "duplicatedOK=yes", expectedCount: 2, expectErr: false},
 	}
@@ -2331,6 +2331,7 @@ type applyConfig struct {
 	suppressDiff      bool
 	noColor           bool
 	context           int
+	diffOutput        string
 	concurrency       int
 	detailedExitcode  bool
 	interactive       bool
@@ -2408,6 +2409,10 @@ func (a applyConfig) Context() int {
 	return a.context
 }
 
+func (a applyConfig) DiffOutput() string {
+	return a.diffOutput
+}
+
 func (a applyConfig) Concurrency() int {
 	return a.concurrency
 }
@@ -2974,30 +2979,30 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     frontend-v1
-2     backend-v1
+1     default/frontend-v1
+2     default/backend-v1
 
-processing releases in group 1/2: frontend-v1
-processing releases in group 2/2: backend-v1
+processing releases in group 1/2: default/frontend-v1
+processing releases in group 2/2: default/backend-v1
 processing 5 groups of releases in this order:
 GROUP RELEASES
-1     logging, front-proxy
-2     database, servicemesh
-3     anotherbackend
-4     backend-v2
-5     frontend-v3
+1     default/logging, default/front-proxy
+2     default/database, default/servicemesh
+3     default/anotherbackend
+4     default/backend-v2
+5     default/frontend-v3
 
-processing releases in group 1/5: logging, front-proxy
+processing releases in group 1/5: default/logging, default/front-proxy
 getting deployed release version failed:unexpected list key: {^logging$ --kube-contextdefault--deleting--deployed--failed--pending}
 getting deployed release version failed:unexpected list key: {^front-proxy$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/5: database, servicemesh
+processing releases in group 2/5: default/database, default/servicemesh
 getting deployed release version failed:unexpected list key: {^database$ --kube-contextdefault--deleting--deployed--failed--pending}
 getting deployed release version failed:unexpected list key: {^servicemesh$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 3/5: anotherbackend
+processing releases in group 3/5: default/anotherbackend
 getting deployed release version failed:unexpected list key: {^anotherbackend$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 4/5: backend-v2
+processing releases in group 4/5: default/backend-v2
 getting deployed release version failed:unexpected list key: {^backend-v2$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 5/5: frontend-v3
+processing releases in group 5/5: default/frontend-v3
 getting deployed release version failed:unexpected list key: {^frontend-v3$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -3123,13 +3128,13 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     baz, bar
-2     foo
+1     default/baz, default/bar
+2     default/foo
 
-processing releases in group 1/2: baz, bar
+processing releases in group 1/2: default/baz, default/bar
 getting deployed release version failed:unexpected list key: {^baz$ --kube-contextdefault--deleting--deployed--failed--pending}
 getting deployed release version failed:unexpected list key: {^bar$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: foo
+processing releases in group 2/2: default/foo
 getting deployed release version failed:unexpected list key: {^foo$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -3233,11 +3238,11 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     baz, bar
-2     foo
+1     default/baz, default/bar
+2     default/foo
 
-processing releases in group 1/2: baz, bar
-processing releases in group 2/2: foo
+processing releases in group 1/2: default/baz, default/bar
+processing releases in group 2/2: default/foo
 getting deployed release version failed:Failed to get the version for:mychart1
 
 UPDATED RELEASES:
@@ -3341,11 +3346,11 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     baz, bar
-2     foo
+1     default/baz, default/bar
+2     default/foo
 
-processing releases in group 1/2: baz, bar
-processing releases in group 2/2: foo
+processing releases in group 1/2: default/baz, default/bar
+processing releases in group 2/2: default/foo
 getting deployed release version failed:Failed to get the version for:mychart1
 
 UPDATED RELEASES:
@@ -3606,12 +3611,12 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     tns1/ns1/foo
-2     tns2/ns2/bar
+1     default/tns1/ns1/foo
+2     default/tns2/ns2/bar
 
-processing releases in group 1/2: tns1/ns1/foo
+processing releases in group 1/2: default/tns1/ns1/foo
 getting deployed release version failed:unexpected list key: {^foo$ --tiller-namespacetns1--kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: tns2/ns2/bar
+processing releases in group 2/2: default/tns2/ns2/bar
 getting deployed release version failed:unexpected list key: {^bar$ --tiller-namespacetns2--kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -3945,12 +3950,12 @@ Affected releases are:
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     default/external-secrets
-2     default/my-release
+1     default/default/external-secrets
+2     default/default/my-release
 
-processing releases in group 1/2: default/external-secrets
+processing releases in group 1/2: default/default/external-secrets
 getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
-processing releases in group 2/2: default/my-release
+processing releases in group 2/2: default/default/my-release
 getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
 
 UPDATED RELEASES:
@@ -3968,7 +3973,7 @@ my-release         incubator/raw
 				skipNeeds:    false,
 				includeNeeds: true,
 			},
-			error: `in ./helmfile.yaml: release "default/external-secrets" depends on "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`,
+			error: `in ./helmfile.yaml: 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`,
 			files: map[string]string{
 				"/path/to/helmfile.yaml": `
 {{ $mark := "a" }}
@@ -4066,7 +4071,7 @@ second-pass rendering result of "helmfile.yaml.part.0":
 merged environment: &{default map[] map[]}
 2 release(s) matching app=test found in helmfile.yaml
 
-err: release "default/external-secrets" depends on "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
+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
 `,
 		},
 		{
@@ -4196,7 +4201,7 @@ releases:
 			upgraded:    []exectest.Release{},
 			deleted:     []exectest.Release{},
 			concurrency: 1,
-			error:       `in ./helmfile.yaml: "foo" depends on nonexistent release "bar"`,
+			error:       `in ./helmfile.yaml: "default/foo" depends on nonexistent release "default/bar"`,
 			log: `processing file "helmfile.yaml" in directory "."
 first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
 first-pass uses: &{default map[] map[]}
@@ -4232,7 +4237,7 @@ second-pass rendering result of "helmfile.yaml.part.0":
 merged environment: &{default map[] map[]}
 2 release(s) found in helmfile.yaml
 
-err: "foo" depends on nonexistent release "bar"
+err: "default/foo" depends on nonexistent release "default/bar"
 `,
 		},
 	}
diff --git a/pkg/app/destroy_nokubectx_test.go b/pkg/app/destroy_nokubectx_test.go
new file mode 100644
index 0000000..f248568
--- /dev/null
+++ b/pkg/app/destroy_nokubectx_test.go
@@ -0,0 +1,765 @@
+package app
+
+import (
+	"bufio"
+	"bytes"
+	"io"
+	"path/filepath"
+	"sync"
+	"testing"
+
+	"github.com/roboll/helmfile/pkg/exectest"
+	"github.com/roboll/helmfile/pkg/helmexec"
+	"github.com/roboll/helmfile/pkg/testhelper"
+	"github.com/variantdev/vals"
+)
+
+func TestDestroy_2(t *testing.T) {
+	type testcase struct {
+		helm3       bool
+		ns          string
+		concurrency int
+		error       string
+		files       map[string]string
+		selectors   []string
+		lists       map[exectest.ListKey]string
+		diffs       map[exectest.DiffKey]error
+		upgraded    []exectest.Release
+		deleted     []exectest.Release
+		log         string
+	}
+
+	check := func(t *testing.T, tc testcase) {
+		t.Helper()
+
+		wantUpgrades := tc.upgraded
+		wantDeletes := tc.deleted
+
+		var helm = &exectest.Helm{
+			Helm3:                tc.helm3,
+			FailOnUnexpectedList: true,
+			FailOnUnexpectedDiff: true,
+			Lists:                tc.lists,
+			Diffs:                tc.diffs,
+			DiffMutex:            &sync.Mutex{},
+			ChartsMutex:          &sync.Mutex{},
+			ReleasesMutex:        &sync.Mutex{},
+		}
+
+		bs := &bytes.Buffer{}
+
+		func() {
+			t.Helper()
+
+			logReader, logWriter := io.Pipe()
+
+			logFlushed := &sync.WaitGroup{}
+			// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
+			logFlushed.Add(1)
+			go func() {
+				scanner := bufio.NewScanner(logReader)
+				for scanner.Scan() {
+					bs.Write(scanner.Bytes())
+					bs.WriteString("\n")
+				}
+				logFlushed.Done()
+			}()
+
+			defer func() {
+				// This is here to avoid data-trace on bytes buffer `bs` to capture logs
+				if err := logWriter.Close(); err != nil {
+					panic(err)
+				}
+				logFlushed.Wait()
+			}()
+
+			logger := helmexec.NewLogger(logWriter, "debug")
+
+			valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
+			if err != nil {
+				t.Errorf("unexpected error creating vals runtime: %v", err)
+			}
+
+			app := appWithFs(&App{
+				OverrideHelmBinary:  DefaultHelmBinary,
+				glob:                filepath.Glob,
+				abs:                 filepath.Abs,
+				OverrideKubeContext: "",
+				Env:                 "default",
+				Logger:              logger,
+				helms: map[helmKey]helmexec.Interface{
+					createHelmKey("helm", ""): helm,
+				},
+				valsRuntime: valsRuntime,
+			}, tc.files)
+
+			if tc.ns != "" {
+				app.Namespace = tc.ns
+			}
+
+			if tc.selectors != nil {
+				app.Selectors = tc.selectors
+			}
+
+			destroyErr := app.Destroy(destroyConfig{
+				// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
+				concurrency: tc.concurrency,
+				logger:      logger,
+			})
+
+			if tc.error == "" && destroyErr != nil {
+				t.Fatalf("unexpected error: %v", destroyErr)
+			} else if tc.error != "" && destroyErr == nil {
+				t.Fatal("expected error did not occur")
+			} else if tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error() {
+				t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
+			}
+
+			if len(wantUpgrades) > len(helm.Releases) {
+				t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
+			}
+
+			for relIdx := range wantUpgrades {
+				if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
+					t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
+				}
+				for flagIdx := range wantUpgrades[relIdx].Flags {
+					if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
+						t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
+					}
+				}
+			}
+
+			if len(wantDeletes) > len(helm.Deleted) {
+				t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
+			}
+
+			for relIdx := range wantDeletes {
+				if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
+					t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
+				}
+				for flagIdx := range wantDeletes[relIdx].Flags {
+					if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
+						t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
+					}
+				}
+			}
+		}()
+
+		if tc.log != "" {
+			actual := bs.String()
+
+			diff, exists := testhelper.Diff(tc.log, actual, 3)
+			if exists {
+				t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
+			}
+		}
+	}
+
+	files := map[string]string{
+		"/path/to/helmfile.yaml": `
+releases:
+- name: database
+  chart: charts/mysql
+  needs:
+  - logging
+- name: frontend-v1
+  chart: charts/frontend
+  installed: false
+  needs:
+  - servicemesh
+  - logging
+  - backend-v1
+- name: frontend-v2
+  chart: charts/frontend
+  needs:
+  - servicemesh
+  - logging
+  - backend-v2
+- name: frontend-v3
+  chart: charts/frontend
+  needs:
+  - servicemesh
+  - logging
+  - backend-v2
+- name: backend-v1
+  chart: charts/backend
+  installed: false
+  needs:
+  - servicemesh
+  - logging
+  - database
+  - anotherbackend
+- name: backend-v2
+  chart: charts/backend
+  needs:
+  - servicemesh
+  - logging
+  - database
+  - anotherbackend
+- name: anotherbackend
+  chart: charts/anotherbackend
+  needs:
+  - servicemesh
+  - logging
+  - database
+- name: servicemesh
+  chart: charts/istio
+  needs:
+  - logging
+- name: logging
+  chart: charts/fluent-bit
+- name: front-proxy
+  chart: stable/envoy
+`,
+	}
+
+	filesForTwoReleases := map[string]string{
+		"/path/to/helmfile.yaml": `
+releases:
+- name: backend-v1
+  chart: charts/backend
+  installed: false
+- name: frontend-v1
+  chart: charts/frontend
+  needs:
+  - backend-v1
+`,
+	}
+
+	t.Run("smoke", func(t *testing.T) {
+		//
+		// complex test cases for smoke testing
+		//
+		check(t, testcase{
+			files: files,
+			diffs: map[exectest.DiffKey]error{},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+`,
+				exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+frontend-v2 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	frontend-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+frontend-v3 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	frontend-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+`,
+				exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+backend-v2 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	backend-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+logging	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	fluent-bit-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+front-proxy 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	envoy-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+servicemesh 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	istio-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+database 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mysql-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+anotherbackend 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	anotherbackend-3.1.0	3.1.0      	default
+`,
+			},
+			// Disable concurrency to avoid in-deterministic result
+			concurrency: 1,
+			upgraded:    []exectest.Release{},
+			deleted: []exectest.Release{
+				{Name: "frontend-v3", Flags: []string{}},
+				{Name: "frontend-v2", Flags: []string{}},
+				{Name: "frontend-v1", Flags: []string{}},
+				{Name: "backend-v2", Flags: []string{}},
+				{Name: "backend-v1", Flags: []string{}},
+				{Name: "anotherbackend", Flags: []string{}},
+				{Name: "servicemesh", Flags: []string{}},
+				{Name: "database", Flags: []string{}},
+				{Name: "front-proxy", Flags: []string{}},
+				{Name: "logging", Flags: []string{}},
+			},
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+first-pass uses: &{default map[] map[]}
+first-pass rendering output of "helmfile.yaml.part.0":
+ 0: 
+ 1: releases:
+ 2: - name: database
+ 3:   chart: charts/mysql
+ 4:   needs:
+ 5:   - logging
+ 6: - name: frontend-v1
+ 7:   chart: charts/frontend
+ 8:   installed: false
+ 9:   needs:
+10:   - servicemesh
+11:   - logging
+12:   - backend-v1
+13: - name: frontend-v2
+14:   chart: charts/frontend
+15:   needs:
+16:   - servicemesh
+17:   - logging
+18:   - backend-v2
+19: - name: frontend-v3
+20:   chart: charts/frontend
+21:   needs:
+22:   - servicemesh
+23:   - logging
+24:   - backend-v2
+25: - name: backend-v1
+26:   chart: charts/backend
+27:   installed: false
+28:   needs:
+29:   - servicemesh
+30:   - logging
+31:   - database
+32:   - anotherbackend
+33: - name: backend-v2
+34:   chart: charts/backend
+35:   needs:
+36:   - servicemesh
+37:   - logging
+38:   - database
+39:   - anotherbackend
+40: - name: anotherbackend
+41:   chart: charts/anotherbackend
+42:   needs:
+43:   - servicemesh
+44:   - logging
+45:   - database
+46: - name: servicemesh
+47:   chart: charts/istio
+48:   needs:
+49:   - logging
+50: - name: logging
+51:   chart: charts/fluent-bit
+52: - name: front-proxy
+53:   chart: stable/envoy
+54: 
+
+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: database
+ 3:   chart: charts/mysql
+ 4:   needs:
+ 5:   - logging
+ 6: - name: frontend-v1
+ 7:   chart: charts/frontend
+ 8:   installed: false
+ 9:   needs:
+10:   - servicemesh
+11:   - logging
+12:   - backend-v1
+13: - name: frontend-v2
+14:   chart: charts/frontend
+15:   needs:
+16:   - servicemesh
+17:   - logging
+18:   - backend-v2
+19: - name: frontend-v3
+20:   chart: charts/frontend
+21:   needs:
+22:   - servicemesh
+23:   - logging
+24:   - backend-v2
+25: - name: backend-v1
+26:   chart: charts/backend
+27:   installed: false
+28:   needs:
+29:   - servicemesh
+30:   - logging
+31:   - database
+32:   - anotherbackend
+33: - name: backend-v2
+34:   chart: charts/backend
+35:   needs:
+36:   - servicemesh
+37:   - logging
+38:   - database
+39:   - anotherbackend
+40: - name: anotherbackend
+41:   chart: charts/anotherbackend
+42:   needs:
+43:   - servicemesh
+44:   - logging
+45:   - database
+46: - name: servicemesh
+47:   chart: charts/istio
+48:   needs:
+49:   - logging
+50: - name: logging
+51:   chart: charts/fluent-bit
+52: - name: front-proxy
+53:   chart: stable/envoy
+54: 
+
+merged environment: &{default map[] map[]}
+10 release(s) found in helmfile.yaml
+
+processing 5 groups of releases in this order:
+GROUP RELEASES
+1     frontend-v3, frontend-v2, frontend-v1
+2     backend-v2, backend-v1
+3     anotherbackend
+4     servicemesh, database
+5     front-proxy, logging
+
+processing releases in group 1/5: frontend-v3, frontend-v2, frontend-v1
+release "frontend-v3" processed
+release "frontend-v2" processed
+release "frontend-v1" processed
+processing releases in group 2/5: backend-v2, backend-v1
+release "backend-v2" processed
+release "backend-v1" processed
+processing releases in group 3/5: anotherbackend
+release "anotherbackend" processed
+processing releases in group 4/5: servicemesh, database
+release "servicemesh" processed
+release "database" processed
+processing releases in group 5/5: front-proxy, logging
+release "front-proxy" processed
+release "logging" processed
+
+DELETED RELEASES:
+NAME
+frontend-v3
+frontend-v2
+frontend-v1
+backend-v2
+backend-v1
+anotherbackend
+servicemesh
+database
+front-proxy
+logging
+`,
+		})
+	})
+
+	t.Run("destroy only one release with selector", func(t *testing.T) {
+		check(t, testcase{
+			files:     files,
+			selectors: []string{"name=logging"},
+			diffs:     map[exectest.DiffKey]error{},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+`,
+				exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+frontend-v2 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	frontend-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+frontend-v3 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	frontend-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+`,
+				exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+backend-v2 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	backend-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+logging	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	fluent-bit-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+front-proxy 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	envoy-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+servicemesh 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	istio-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+database 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mysql-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+anotherbackend 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	anotherbackend-3.1.0	3.1.0      	default
+`,
+			},
+			// Disable concurrency to avoid in-deterministic result
+			concurrency: 1,
+			upgraded:    []exectest.Release{},
+			deleted: []exectest.Release{
+				{Name: "logging", Flags: []string{}},
+			},
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+first-pass uses: &{default map[] map[]}
+first-pass rendering output of "helmfile.yaml.part.0":
+ 0: 
+ 1: releases:
+ 2: - name: database
+ 3:   chart: charts/mysql
+ 4:   needs:
+ 5:   - logging
+ 6: - name: frontend-v1
+ 7:   chart: charts/frontend
+ 8:   installed: false
+ 9:   needs:
+10:   - servicemesh
+11:   - logging
+12:   - backend-v1
+13: - name: frontend-v2
+14:   chart: charts/frontend
+15:   needs:
+16:   - servicemesh
+17:   - logging
+18:   - backend-v2
+19: - name: frontend-v3
+20:   chart: charts/frontend
+21:   needs:
+22:   - servicemesh
+23:   - logging
+24:   - backend-v2
+25: - name: backend-v1
+26:   chart: charts/backend
+27:   installed: false
+28:   needs:
+29:   - servicemesh
+30:   - logging
+31:   - database
+32:   - anotherbackend
+33: - name: backend-v2
+34:   chart: charts/backend
+35:   needs:
+36:   - servicemesh
+37:   - logging
+38:   - database
+39:   - anotherbackend
+40: - name: anotherbackend
+41:   chart: charts/anotherbackend
+42:   needs:
+43:   - servicemesh
+44:   - logging
+45:   - database
+46: - name: servicemesh
+47:   chart: charts/istio
+48:   needs:
+49:   - logging
+50: - name: logging
+51:   chart: charts/fluent-bit
+52: - name: front-proxy
+53:   chart: stable/envoy
+54: 
+
+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: database
+ 3:   chart: charts/mysql
+ 4:   needs:
+ 5:   - logging
+ 6: - name: frontend-v1
+ 7:   chart: charts/frontend
+ 8:   installed: false
+ 9:   needs:
+10:   - servicemesh
+11:   - logging
+12:   - backend-v1
+13: - name: frontend-v2
+14:   chart: charts/frontend
+15:   needs:
+16:   - servicemesh
+17:   - logging
+18:   - backend-v2
+19: - name: frontend-v3
+20:   chart: charts/frontend
+21:   needs:
+22:   - servicemesh
+23:   - logging
+24:   - backend-v2
+25: - name: backend-v1
+26:   chart: charts/backend
+27:   installed: false
+28:   needs:
+29:   - servicemesh
+30:   - logging
+31:   - database
+32:   - anotherbackend
+33: - name: backend-v2
+34:   chart: charts/backend
+35:   needs:
+36:   - servicemesh
+37:   - logging
+38:   - database
+39:   - anotherbackend
+40: - name: anotherbackend
+41:   chart: charts/anotherbackend
+42:   needs:
+43:   - servicemesh
+44:   - logging
+45:   - database
+46: - name: servicemesh
+47:   chart: charts/istio
+48:   needs:
+49:   - logging
+50: - name: logging
+51:   chart: charts/fluent-bit
+52: - name: front-proxy
+53:   chart: stable/envoy
+54: 
+
+merged environment: &{default map[] map[]}
+1 release(s) matching name=logging found in helmfile.yaml
+
+processing 1 groups of releases in this order:
+GROUP RELEASES
+1     logging
+
+processing releases in group 1/1: logging
+release "logging" processed
+
+DELETED RELEASES:
+NAME
+logging
+`,
+		})
+	})
+
+	t.Run("destroy installed but disabled release", func(t *testing.T) {
+		check(t, testcase{
+			files: filesForTwoReleases,
+			diffs: map[exectest.DiffKey]error{},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+`,
+				exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+`,
+			},
+			// Disable concurrency to avoid in-deterministic result
+			concurrency: 1,
+			upgraded:    []exectest.Release{},
+			deleted: []exectest.Release{
+				{Name: "frontend-v1", Flags: []string{}},
+			},
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+first-pass uses: &{default map[] map[]}
+first-pass rendering output of "helmfile.yaml.part.0":
+ 0: 
+ 1: releases:
+ 2: - name: backend-v1
+ 3:   chart: charts/backend
+ 4:   installed: false
+ 5: - name: frontend-v1
+ 6:   chart: charts/frontend
+ 7:   needs:
+ 8:   - backend-v1
+ 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: backend-v1
+ 3:   chart: charts/backend
+ 4:   installed: false
+ 5: - name: frontend-v1
+ 6:   chart: charts/frontend
+ 7:   needs:
+ 8:   - backend-v1
+ 9: 
+
+merged environment: &{default map[] map[]}
+2 release(s) found in helmfile.yaml
+
+processing 2 groups of releases in this order:
+GROUP RELEASES
+1     frontend-v1
+2     backend-v1
+
+processing releases in group 1/2: frontend-v1
+release "frontend-v1" processed
+processing releases in group 2/2: backend-v1
+release "backend-v1" processed
+
+DELETED RELEASES:
+NAME
+frontend-v1
+backend-v1
+`,
+		})
+	})
+
+	t.Run("helm3", func(t *testing.T) {
+		check(t, testcase{
+			helm3: true,
+			files: filesForTwoReleases,
+			diffs: map[exectest.DiffKey]error{},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV3ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+`,
+				exectest.ListKey{Filter: "^backend-v1$", Flags: helmV3ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+`,
+			},
+			// Disable concurrency to avoid in-deterministic result
+			concurrency: 1,
+			upgraded:    []exectest.Release{},
+			deleted: []exectest.Release{
+				{Name: "frontend-v1", Flags: []string{}},
+			},
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+first-pass uses: &{default map[] map[]}
+first-pass rendering output of "helmfile.yaml.part.0":
+ 0: 
+ 1: releases:
+ 2: - name: backend-v1
+ 3:   chart: charts/backend
+ 4:   installed: false
+ 5: - name: frontend-v1
+ 6:   chart: charts/frontend
+ 7:   needs:
+ 8:   - backend-v1
+ 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: backend-v1
+ 3:   chart: charts/backend
+ 4:   installed: false
+ 5: - name: frontend-v1
+ 6:   chart: charts/frontend
+ 7:   needs:
+ 8:   - backend-v1
+ 9: 
+
+merged environment: &{default map[] map[]}
+2 release(s) found in helmfile.yaml
+
+processing 2 groups of releases in this order:
+GROUP RELEASES
+1     frontend-v1
+2     backend-v1
+
+processing releases in group 1/2: frontend-v1
+release "frontend-v1" processed
+processing releases in group 2/2: backend-v1
+release "backend-v1" processed
+
+DELETED RELEASES:
+NAME
+frontend-v1
+backend-v1
+`,
+		})
+	})
+}
diff --git a/pkg/app/destroy_test.go b/pkg/app/destroy_test.go
index 1e3de39..774df16 100644
--- a/pkg/app/destroy_test.go
+++ b/pkg/app/destroy_test.go
@@ -17,7 +17,9 @@ import (
 
 const (
 	helmV2ListFlags = "--kube-contextdefault--deleting--deployed--failed--pending"
+	helmV2ListFlagsWithoutKubeContext = "--deleting--deployed--failed--pending"
 	helmV3ListFlags = "--kube-contextdefault--uninstalling--deployed--failed--pending"
+	helmV3ListFlagsWithoutKubeContext = "--uninstalling--deployed--failed--pending"
 )
 
 type destroyConfig struct {
@@ -435,25 +437,25 @@ merged environment: &{default map[] map[]}
 
 processing 5 groups of releases in this order:
 GROUP RELEASES
-1     frontend-v3, frontend-v2, frontend-v1
-2     backend-v2, backend-v1
-3     anotherbackend
-4     servicemesh, database
-5     front-proxy, logging
+1     default/frontend-v3, default/frontend-v2, default/frontend-v1
+2     default/backend-v2, default/backend-v1
+3     default/anotherbackend
+4     default/servicemesh, default/database
+5     default/front-proxy, default/logging
 
-processing releases in group 1/5: frontend-v3, frontend-v2, frontend-v1
+processing releases in group 1/5: default/frontend-v3, default/frontend-v2, default/frontend-v1
 release "frontend-v3" processed
 release "frontend-v2" processed
 release "frontend-v1" processed
-processing releases in group 2/5: backend-v2, backend-v1
+processing releases in group 2/5: default/backend-v2, default/backend-v1
 release "backend-v2" processed
 release "backend-v1" processed
-processing releases in group 3/5: anotherbackend
+processing releases in group 3/5: default/anotherbackend
 release "anotherbackend" processed
-processing releases in group 4/5: servicemesh, database
+processing releases in group 4/5: default/servicemesh, default/database
 release "servicemesh" processed
 release "database" processed
-processing releases in group 5/5: front-proxy, logging
+processing releases in group 5/5: default/front-proxy, default/logging
 release "front-proxy" processed
 release "logging" processed
 
@@ -641,9 +643,9 @@ merged environment: &{default map[] map[]}
 
 processing 1 groups of releases in this order:
 GROUP RELEASES
-1     logging
+1     default/logging
 
-processing releases in group 1/1: logging
+processing releases in group 1/1: default/logging
 release "logging" processed
 
 DELETED RELEASES:
@@ -706,12 +708,12 @@ merged environment: &{default map[] map[]}
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     frontend-v1
-2     backend-v1
+1     default/frontend-v1
+2     default/backend-v1
 
-processing releases in group 1/2: frontend-v1
+processing releases in group 1/2: default/frontend-v1
 release "frontend-v1" processed
-processing releases in group 2/2: backend-v1
+processing releases in group 2/2: default/backend-v1
 release "backend-v1" processed
 
 DELETED RELEASES:
@@ -776,12 +778,12 @@ merged environment: &{default map[] map[]}
 
 processing 2 groups of releases in this order:
 GROUP RELEASES
-1     frontend-v1
-2     backend-v1
+1     default/frontend-v1
+2     default/backend-v1
 
-processing releases in group 1/2: frontend-v1
+processing releases in group 1/2: default/frontend-v1
 release "frontend-v1" processed
-processing releases in group 2/2: backend-v1
+processing releases in group 2/2: default/backend-v1
 release "backend-v1" processed
 
 DELETED RELEASES:
diff --git a/pkg/app/diff_nokubectx_test.go b/pkg/app/diff_nokubectx_test.go
new file mode 100644
index 0000000..cd64dcd
--- /dev/null
+++ b/pkg/app/diff_nokubectx_test.go
@@ -0,0 +1,1330 @@
+package app
+
+import (
+	"bufio"
+	"bytes"
+	"io"
+	"path/filepath"
+	"sync"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/roboll/helmfile/pkg/exectest"
+	"github.com/roboll/helmfile/pkg/helmexec"
+	"github.com/roboll/helmfile/pkg/testhelper"
+	"github.com/variantdev/vals"
+)
+
+func TestDiff_2(t *testing.T) {
+	type flags struct {
+		skipNeeds bool
+	}
+
+	testcases := []struct {
+		name             string
+		loc              string
+		ns               string
+		concurrency      int
+		detailedExitcode bool
+		error            string
+		flags            flags
+		files            map[string]string
+		selectors        []string
+		lists            map[exectest.ListKey]string
+		diffs            map[exectest.DiffKey]error
+		upgraded         []exectest.Release
+		deleted          []exectest.Release
+		log              string
+	}{
+		//
+		// complex test cases for smoke testing
+		//
+		{
+			name: "smoke",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: database
+  chart: charts/mysql
+  needs:
+  - logging
+- name: frontend-v1
+  chart: charts/frontend
+  installed: false
+  needs:
+  - servicemesh
+  - logging
+  - backend-v1
+- name: frontend-v2
+  chart: charts/frontend
+  needs:
+  - servicemesh
+  - logging
+  - backend-v2
+- name: frontend-v3
+  chart: charts/frontend
+  needs:
+  - servicemesh
+  - logging
+  - backend-v2
+- name: backend-v1
+  chart: charts/backend
+  installed: false
+  needs:
+  - servicemesh
+  - logging
+  - database
+  - anotherbackend
+- name: backend-v2
+  chart: charts/backend
+  needs:
+  - servicemesh
+  - logging
+  - database
+  - anotherbackend
+- name: anotherbackend
+  chart: charts/anotherbackend
+  needs:
+  - servicemesh
+  - logging
+  - database
+- name: servicemesh
+  chart: charts/istio
+  needs:
+  - logging
+- name: logging
+  chart: charts/fluent-bit
+- name: front-proxy
+  chart: stable/envoy
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				// noop on frontend-v2
+				exectest.DiffKey{Name: "frontend-v2", Chart: "charts/frontend", Flags: "--detailed-exitcode"}: nil,
+				// install frontend-v3
+				exectest.DiffKey{Name: "frontend-v3", Chart: "charts/frontend", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				// upgrades
+				exectest.DiffKey{Name: "logging", Chart: "charts/fluent-bit", Flags: "--detailed-exitcode"}:            helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "front-proxy", Chart: "stable/envoy", Flags: "--detailed-exitcode"}:             helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "servicemesh", Chart: "charts/istio", Flags: "--detailed-exitcode"}:             helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "database", Chart: "charts/mysql", Flags: "--detailed-exitcode"}:                helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "backend-v2", Chart: "charts/backend", Flags: "--detailed-exitcode"}:            helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "anotherbackend", Chart: "charts/anotherbackend", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			lists: map[exectest.ListKey]string{
+				// delete frontend-v1 and backend-v1
+				exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+frontend-v1 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	backend-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+backend-v1 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	backend-3.1.0	3.1.0      	default
+`,
+			},
+			// Disable concurrency to avoid in-deterministic result
+			concurrency: 1,
+			upgraded:    []exectest.Release{},
+			deleted:     []exectest.Release{},
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+first-pass uses: &{default map[] map[]}
+first-pass rendering output of "helmfile.yaml.part.0":
+ 0: 
+ 1: releases:
+ 2: - name: database
+ 3:   chart: charts/mysql
+ 4:   needs:
+ 5:   - logging
+ 6: - name: frontend-v1
+ 7:   chart: charts/frontend
+ 8:   installed: false
+ 9:   needs:
+10:   - servicemesh
+11:   - logging
+12:   - backend-v1
+13: - name: frontend-v2
+14:   chart: charts/frontend
+15:   needs:
+16:   - servicemesh
+17:   - logging
+18:   - backend-v2
+19: - name: frontend-v3
+20:   chart: charts/frontend
+21:   needs:
+22:   - servicemesh
+23:   - logging
+24:   - backend-v2
+25: - name: backend-v1
+26:   chart: charts/backend
+27:   installed: false
+28:   needs:
+29:   - servicemesh
+30:   - logging
+31:   - database
+32:   - anotherbackend
+33: - name: backend-v2
+34:   chart: charts/backend
+35:   needs:
+36:   - servicemesh
+37:   - logging
+38:   - database
+39:   - anotherbackend
+40: - name: anotherbackend
+41:   chart: charts/anotherbackend
+42:   needs:
+43:   - servicemesh
+44:   - logging
+45:   - database
+46: - name: servicemesh
+47:   chart: charts/istio
+48:   needs:
+49:   - logging
+50: - name: logging
+51:   chart: charts/fluent-bit
+52: - name: front-proxy
+53:   chart: stable/envoy
+54: 
+
+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: database
+ 3:   chart: charts/mysql
+ 4:   needs:
+ 5:   - logging
+ 6: - name: frontend-v1
+ 7:   chart: charts/frontend
+ 8:   installed: false
+ 9:   needs:
+10:   - servicemesh
+11:   - logging
+12:   - backend-v1
+13: - name: frontend-v2
+14:   chart: charts/frontend
+15:   needs:
+16:   - servicemesh
+17:   - logging
+18:   - backend-v2
+19: - name: frontend-v3
+20:   chart: charts/frontend
+21:   needs:
+22:   - servicemesh
+23:   - logging
+24:   - backend-v2
+25: - name: backend-v1
+26:   chart: charts/backend
+27:   installed: false
+28:   needs:
+29:   - servicemesh
+30:   - logging
+31:   - database
+32:   - anotherbackend
+33: - name: backend-v2
+34:   chart: charts/backend
+35:   needs:
+36:   - servicemesh
+37:   - logging
+38:   - database
+39:   - anotherbackend
+40: - name: anotherbackend
+41:   chart: charts/anotherbackend
+42:   needs:
+43:   - servicemesh
+44:   - logging
+45:   - database
+46: - name: servicemesh
+47:   chart: charts/istio
+48:   needs:
+49:   - logging
+50: - name: logging
+51:   chart: charts/fluent-bit
+52: - name: front-proxy
+53:   chart: stable/envoy
+54: 
+
+merged environment: &{default map[] map[]}
+10 release(s) found in helmfile.yaml
+
+Affected releases are:
+  anotherbackend (charts/anotherbackend) UPDATED
+  backend-v1 (charts/backend) DELETED
+  backend-v2 (charts/backend) UPDATED
+  database (charts/mysql) UPDATED
+  front-proxy (stable/envoy) UPDATED
+  frontend-v1 (charts/frontend) DELETED
+  frontend-v3 (charts/frontend) UPDATED
+  logging (charts/fluent-bit) UPDATED
+  servicemesh (charts/istio) UPDATED
+
+`,
+		},
+		//
+		// noop: no changes
+		//
+		{
+			name: "noop",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+- name: foo
+  chart: mychart1
+  installed: false
+  needs:
+  - bar
+`,
+			},
+			detailedExitcode: true,
+			error:            "",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: nil,
+			},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlagsWithoutKubeContext}: ``,
+				exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default
+`,
+			},
+			upgraded: []exectest.Release{},
+			deleted:  []exectest.Release{},
+		},
+		//
+		// install
+		//
+		{
+			name: "install",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: baz
+  chart: mychart3
+- name: foo
+  chart: mychart1
+  needs:
+  - bar
+- name: bar
+  chart: mychart2
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "baz", Chart: "mychart3", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			lists:       map[exectest.ListKey]string{},
+			upgraded:    []exectest.Release{},
+			deleted:     []exectest.Release{},
+			concurrency: 1,
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+first-pass uses: &{default map[] map[]}
+first-pass rendering output of "helmfile.yaml.part.0":
+ 0: 
+ 1: releases:
+ 2: - name: baz
+ 3:   chart: mychart3
+ 4: - name: foo
+ 5:   chart: mychart1
+ 6:   needs:
+ 7:   - bar
+ 8: - name: bar
+ 9:   chart: mychart2
+10: 
+
+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:   chart: mychart3
+ 4: - name: foo
+ 5:   chart: mychart1
+ 6:   needs:
+ 7:   - bar
+ 8: - name: bar
+ 9:   chart: mychart2
+10: 
+
+merged environment: &{default map[] map[]}
+3 release(s) found in helmfile.yaml
+
+Affected releases are:
+  bar (mychart2) UPDATED
+  baz (mychart3) UPDATED
+  foo (mychart1) UPDATED
+
+`,
+		},
+		//
+		// upgrades
+		//
+		{
+			name: "upgrade when foo needs bar",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+- name: foo
+  chart: mychart1
+  needs:
+  - bar
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+		},
+		{
+			name: "upgrade when bar needs foo",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: foo
+  chart: mychart1
+- name: bar
+  chart: mychart2
+  needs:
+  - foo
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+		},
+		{
+			name: "upgrade when foo needs bar, with ns override",
+			loc:  location(),
+			ns:   "testNamespace",
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+- name: foo
+  chart: mychart1
+  needs:
+  - bar
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+		},
+		{
+			name: "upgrade when bar needs foo, with ns override",
+			loc:  location(),
+			ns:   "testNamespace",
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: foo
+  chart: mychart1
+- name: bar
+  chart: mychart2
+  needs:
+  - foo
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+		},
+		{
+			name: "upgrade when ns1/foo needs ns2/bar",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: foo
+  chart: mychart1
+  namespace: ns1
+  needs:
+  - ns2/bar
+- name: bar
+  chart: mychart2
+  namespace: ns2
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+		},
+		{
+			name: "upgrade when ns2/bar needs ns1/foo",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+  namespace: ns2
+  needs:
+  - ns1/foo
+- name: foo
+  chart: mychart1
+  namespace: ns1
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+		},
+		{
+			name: "upgrade when tns1/ns1/foo needs tns2/ns2/bar",
+			loc:  location(),
+
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: foo
+  chart: mychart1
+  namespace: ns1
+  tillerNamespace: tns1
+  needs:
+  - tns2/ns2/bar
+- name: bar
+  chart: mychart2
+  namespace: ns2
+  tillerNamespace: tns2
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+		},
+		{
+			name: "upgrade when tns2/ns2/bar needs tns1/ns1/foo",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+  namespace: ns2
+  tillerNamespace: tns2
+  needs:
+  - tns1/ns1/foo
+- name: foo
+  chart: mychart1
+  namespace: ns1
+  tillerNamespace: tns1
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+first-pass uses: &{default map[] map[]}
+first-pass rendering output of "helmfile.yaml.part.0":
+ 0: 
+ 1: releases:
+ 2: - name: bar
+ 3:   chart: mychart2
+ 4:   namespace: ns2
+ 5:   tillerNamespace: tns2
+ 6:   needs:
+ 7:   - tns1/ns1/foo
+ 8: - name: foo
+ 9:   chart: mychart1
+10:   namespace: ns1
+11:   tillerNamespace: tns1
+12: 
+
+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: mychart2
+ 4:   namespace: ns2
+ 5:   tillerNamespace: tns2
+ 6:   needs:
+ 7:   - tns1/ns1/foo
+ 8: - name: foo
+ 9:   chart: mychart1
+10:   namespace: ns1
+11:   tillerNamespace: tns1
+12: 
+
+merged environment: &{default map[] map[]}
+2 release(s) found in helmfile.yaml
+
+Affected releases are:
+  bar (mychart2) UPDATED
+  foo (mychart1) UPDATED
+
+`,
+		},
+		//
+		// deletes: deleting all releases in the correct order
+		//
+		{
+			name: "delete foo and bar when foo needs bar",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+  installed: false
+- name: foo
+  chart: mychart1
+  installed: false
+  needs:
+  - bar
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+foo 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart1-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default
+`,
+			},
+			deleted: []exectest.Release{},
+		},
+		{
+			name: "delete foo and bar when bar needs foo",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+  installed: false
+  needs:
+  - foo
+- name: foo
+  chart: mychart1
+  installed: false
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+foo 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart1-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default
+`,
+			},
+			deleted: []exectest.Release{},
+		},
+		//
+		// upgrade and delete: upgrading one while deleting another
+		//
+		{
+			name: "delete foo when foo needs bar",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+- name: foo
+  chart: mychart1
+  installed: false
+  needs:
+  - bar
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+foo 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart1-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default
+`,
+			},
+			upgraded: []exectest.Release{},
+			deleted:  []exectest.Release{},
+		},
+		{
+			name: "delete bar when foo needs bar",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: bar
+  chart: mychart2
+  installed: false
+- name: foo
+  chart: mychart1
+  needs:
+  - bar
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+foo 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart1-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default
+`,
+			},
+			upgraded: []exectest.Release{},
+			deleted:  []exectest.Release{},
+		},
+		{
+			name: "delete foo when bar needs foo",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: foo
+  chart: mychart1
+  installed: false
+- name: bar
+  chart: mychart2
+  needs:
+  - foo
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+foo 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart1-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default
+`,
+			},
+			upgraded: []exectest.Release{},
+			deleted:  []exectest.Release{},
+		},
+		{
+			name: "delete bar when bar needs foo",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: foo
+  chart: mychart1
+- name: bar
+  chart: mychart2
+  installed: false
+  needs:
+  - foo
+`,
+			},
+			detailedExitcode: true,
+			error:            "Identified at least one change",
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+			},
+			lists: map[exectest.ListKey]string{
+				exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+foo 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart1-3.1.0	3.1.0      	default
+`,
+				exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE
+bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default
+`,
+			},
+			upgraded: []exectest.Release{},
+			deleted:  []exectest.Release{},
+		},
+		//
+		// upgrades with selector
+		//
+		{
+			// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
+			name:  "upgrades with good selector with --skip-needs=true",
+			loc:   location(),
+			flags: flags{skipNeeds: true},
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+
+- 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
+`,
+			},
+			selectors:        []string{"app=test"},
+			detailedExitcode: true,
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:       helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+			// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
+			concurrency: 1,
+			error:       "Identified at least one change",
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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
+
+Affected releases are:
+  external-secrets (incubator/raw) UPDATED
+  my-release (incubator/raw) UPDATED
+
+`,
+		},
+		{
+			name:  "upgrades with good selector with --skip-needs=false",
+			loc:   location(),
+			flags: flags{skipNeeds: false},
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+
+- 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
+`,
+			},
+			selectors:        []string{"app=test"},
+			detailedExitcode: true,
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--namespacedefault--detailed-exitcode"}:       helmexec.ExitError{Code: 2},
+			},
+			upgraded: []exectest.Release{},
+			// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
+			concurrency: 1,
+			error:       `in ./helmfile.yaml: release "default/external-secrets" depends on "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`,
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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/external-secrets" depends on "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
+`,
+		},
+		{
+			// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
+			name: "upgrades with bad selector",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+{{ $mark := "a" }}
+
+releases:
+- name: kubernetes-external-secrets
+  chart: incubator/raw
+  namespace: kube-system
+
+- 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
+`,
+			},
+			selectors:        []string{"app=test_non_existent"},
+			detailedExitcode: true,
+			diffs:            map[exectest.DiffKey]error{},
+			upgraded:         []exectest.Release{},
+			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 "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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
+
+`,
+		},
+		//
+		// error cases
+		//
+		{
+			name: "non-existent release in needs",
+			loc:  location(),
+			files: map[string]string{
+				"/path/to/helmfile.yaml": `
+releases:
+- name: baz
+  namespace: ns1
+  chart: mychart3
+- name: foo
+  chart: mychart1
+  needs:
+  - bar
+`,
+			},
+			detailedExitcode: true,
+			diffs: map[exectest.DiffKey]error{
+				exectest.DiffKey{Name: "baz", Chart: "mychart3", Flags: "--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
+				exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--detailed-exitcode"}:               helmexec.ExitError{Code: 2},
+			},
+			lists:       map[exectest.ListKey]string{},
+			upgraded:    []exectest.Release{},
+			deleted:     []exectest.Release{},
+			concurrency: 1,
+			error:       `in ./helmfile.yaml: "foo" depends on nonexistent release "bar"`,
+			log: `processing file "helmfile.yaml" in directory "."
+first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
+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:   - 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:   - bar
+ 9: 
+
+merged environment: &{default map[] map[]}
+2 release(s) found in helmfile.yaml
+
+err: "foo" depends on nonexistent release "bar"
+`,
+		},
+	}
+
+	for i := range testcases {
+		tc := testcases[i]
+		t.Run(tc.name, func(t *testing.T) {
+			wantUpgrades := tc.upgraded
+			wantDeletes := tc.deleted
+
+			var helm = &exectest.Helm{
+				FailOnUnexpectedList: true,
+				FailOnUnexpectedDiff: true,
+				Lists:                tc.lists,
+				Diffs:                tc.diffs,
+				DiffMutex:            &sync.Mutex{},
+				ChartsMutex:          &sync.Mutex{},
+				ReleasesMutex:        &sync.Mutex{},
+			}
+
+			bs := &bytes.Buffer{}
+
+			func() {
+				logReader, logWriter := io.Pipe()
+
+				logFlushed := &sync.WaitGroup{}
+				// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
+				logFlushed.Add(1)
+				go func() {
+					scanner := bufio.NewScanner(logReader)
+					for scanner.Scan() {
+						bs.Write(scanner.Bytes())
+						bs.WriteString("\n")
+					}
+					logFlushed.Done()
+				}()
+
+				defer func() {
+					// This is here to avoid data-trace on bytes buffer `bs` to capture logs
+					if err := logWriter.Close(); err != nil {
+						panic(err)
+					}
+					logFlushed.Wait()
+				}()
+
+				logger := helmexec.NewLogger(logWriter, "debug")
+
+				valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
+				if err != nil {
+					t.Errorf("unexpected error creating vals runtime: %v", err)
+				}
+
+				app := appWithFs(&App{
+					OverrideHelmBinary:  DefaultHelmBinary,
+					glob:                filepath.Glob,
+					abs:                 filepath.Abs,
+					OverrideKubeContext: "",
+					Env:                 "default",
+					Logger:              logger,
+					helms: map[helmKey]helmexec.Interface{
+						createHelmKey("helm", ""): helm,
+					},
+					valsRuntime: valsRuntime,
+				}, tc.files)
+
+				if tc.ns != "" {
+					app.Namespace = tc.ns
+				}
+
+				if tc.selectors != nil {
+					app.Selectors = tc.selectors
+				}
+
+				diffErr := app.Diff(diffConfig{
+					// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
+					concurrency:      tc.concurrency,
+					logger:           logger,
+					detailedExitcode: tc.detailedExitcode,
+					skipNeeds:        tc.flags.skipNeeds,
+				})
+
+				var diffErrStr string
+				if diffErr != nil {
+					diffErrStr = diffErr.Error()
+				}
+
+				if d := cmp.Diff(tc.error, diffErrStr); d != "" {
+					t.Fatalf("invalid error: want (-), got (+): %s", d)
+				}
+
+				if len(wantUpgrades) > len(helm.Releases) {
+					t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
+				}
+
+				for relIdx := range wantUpgrades {
+					if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
+						t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
+					}
+					for flagIdx := range wantUpgrades[relIdx].Flags {
+						if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
+							t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
+						}
+					}
+				}
+
+				if len(wantDeletes) > len(helm.Deleted) {
+					t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
+				}
+
+				for relIdx := range wantDeletes {
+					if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
+						t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
+					}
+					for flagIdx := range wantDeletes[relIdx].Flags {
+						if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
+							t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
+						}
+					}
+				}
+			}()
+
+			if tc.log != "" {
+				actual := bs.String()
+
+				diff, exists := testhelper.Diff(tc.log, actual, 3)
+				if exists {
+					t.Errorf("unexpected log for data defined %s:\nDIFF\n%s\nEOD", tc.loc, diff)
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/app/diff_test.go b/pkg/app/diff_test.go
index fa9fe04..049b787 100644
--- a/pkg/app/diff_test.go
+++ b/pkg/app/diff_test.go
@@ -32,6 +32,7 @@ type diffConfig struct {
 	suppressDiff      bool
 	noColor           bool
 	context           int
+	diffOutput        string
 	concurrency       int
 	detailedExitcode  bool
 	interactive       bool
@@ -94,6 +95,10 @@ func (a diffConfig) Context() int {
 	return a.context
 }
 
+func (a diffConfig) DiffOutput() string {
+	return a.diffOutput
+}
+
 func (a diffConfig) Concurrency() int {
 	return a.concurrency
 }
@@ -1062,7 +1067,7 @@ releases:
 			upgraded: []exectest.Release{},
 			// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
 			concurrency: 1,
-			error:       `in ./helmfile.yaml: release "default/external-secrets" depends on "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`,
+			error:       `in ./helmfile.yaml: 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`,
 			log: `processing file "helmfile.yaml" in directory "."
 first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
 first-pass uses: &{default map[] map[]}
@@ -1126,7 +1131,7 @@ second-pass rendering result of "helmfile.yaml.part.0":
 merged environment: &{default map[] map[]}
 2 release(s) matching app=test found in helmfile.yaml
 
-err: release "default/external-secrets" depends on "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
+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
 `,
 		},
 		{
@@ -1258,7 +1263,7 @@ releases:
 			upgraded:    []exectest.Release{},
 			deleted:     []exectest.Release{},
 			concurrency: 1,
-			error:       `in ./helmfile.yaml: "foo" depends on nonexistent release "bar"`,
+			error:       `in ./helmfile.yaml: "default/foo" depends on nonexistent release "default/bar"`,
 			log: `processing file "helmfile.yaml" in directory "."
 first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
 first-pass uses: &{default map[] map[]}
@@ -1294,7 +1299,7 @@ second-pass rendering result of "helmfile.yaml.part.0":
 merged environment: &{default map[] map[]}
 2 release(s) found in helmfile.yaml
 
-err: "foo" depends on nonexistent release "bar"
+err: "default/foo" depends on nonexistent release "default/bar"
 `,
 		},
 	}
diff --git a/test/integration/run.sh b/test/integration/run.sh
index 8d246bb..53775fb 100755
--- a/test/integration/run.sh
+++ b/test/integration/run.sh
@@ -26,7 +26,7 @@ export HELM_HOME="${HELM_DATA_HOME}"
 export HELM_PLUGINS="${HELM_DATA_HOME}/plugins"
 export HELM_CONFIG_HOME="${helm_dir}/config"
 HELM_SECRETS_VERSION=3.5.0
-HELM_DIFF_VERSION=3.0.0-rc.7
+HELM_DIFF_VERSION=3.1.3
 export GNUPGHOME="${PWD}/${dir}/.gnupg"
 export SOPS_PGP_FP="B2D6D7BBEC03B2E66571C8C00AD18E16CFDEF700"
 
@@ -95,6 +95,9 @@ bash -c "${helmfile} -f ${dir}/happypath.yaml --no-color diff --detailed-exitcod
 info "Diffing ${dir}/happypath.yaml with limited context"
 bash -c "${helmfile} -f ${dir}/happypath.yaml diff --context 3 --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff"
 
+info "Diffing ${dir}/happypath.yaml with altered output"
+bash -c "${helmfile} -f ${dir}/happypath.yaml diff --output simple --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff"
+
 info "Templating ${dir}/happypath.yaml"
 rm -rf ${dir}/tmp
 ${helmfile} -f ${dir}/happypath.yaml --debug template --output-dir tmp