Repository: GoogleContainerTools/skaffold
Pull request #7421 (2022-05-18 16:17:18)

feat: Add helm init images for multiple services project
Add helm init for images. 

In this PR, 
1) In the `GetImages` functionality, instead of using chart names which could be used in the template, I made the following changes
 - run `helm template --output-dir <tmpDir> -f values.yaml`
 - Parse all the templated k8s manifests in tmpDir and get images.



To try this out
1) LOCAL=true make install
2) git clone https://github.com/tejal29/helm-examples
3) cd go-guestbook
4) run 
```
skaffold init -f skaffold.yaml.out

diff skaffold.yaml skaffold.yaml.out
```
5) Run skaffold dev and make sure the example is running.
```
skaffold run -d <your registry> 
```

codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#7421](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (b3bd3b8) into [main](https://codecov.io/gh/GoogleContainerTools/skaffold/commit/290280e8c6c19231e9ed5b5c027f2c01d22f75be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (290280e) will **decrease** coverage by `3.97%`.
> The diff coverage is `55.07%`.

```diff
@@            Coverage Diff             @@
##             main    #7421      +/-   ##
==========================================
- Coverage   70.48%   66.50%   -3.98%     
==========================================
  Files         515      570      +55     
  Lines       23150    27575    +4425     
==========================================
+ Hits        16317    18339    +2022     
- Misses       5776     7897    +2121     
- Partials     1057     1339     +282     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | Coverage Δ | |
|---|---|---|
| [cmd/skaffold/app/cmd/credits/export.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY3JlZGl0cy9leHBvcnQuZ28=) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/deploy.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZGVwbG95Lmdv) | `50.00% <0.00%> (-3.85%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/test.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdGVzdC5nbw==) | `44.44% <0.00%> (ø)` | |
| [cmd/skaffold/skaffold.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL3NrYWZmb2xkLmdv) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/render.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcmVuZGVyLmdv) | `34.37% <10.00%> (-7.01%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/inspect\_tests.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF90ZXN0cy5nbw==) | `62.50% <14.28%> (-1.14%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/lsp.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvbHNwLmdv) | `28.12% <28.12%> (ø)` | |
| [cmd/skaffold/app/cmd/fix.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZml4Lmdv) | `55.93% <38.88%> (-20.54%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/verify.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdmVyaWZ5Lmdv) | `41.17% <41.17%> (ø)` | |
| [cmd/skaffold/app/cmd/lint.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvbGludC5nbw==) | `42.85% <42.85%> (ø)` | |
| ... and [300 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7421/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

:mega: Codecov can now indicate which changes are the most critical in Pull Requests. [Learn more](https://about.codecov.io/product/feature/runtime-insights/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)


Patch:
diff --git a/cmd/skaffold/app/cmd/runner.go b/cmd/skaffold/app/cmd/runner.go
index 6546faebf..d4628ddfa 100644
--- a/cmd/skaffold/app/cmd/runner.go
+++ b/cmd/skaffold/app/cmd/runner.go
@@ -83,6 +83,7 @@ func runContext(ctx context.Context, out io.Writer, opts config.SkaffoldOptions)
 	if err != nil {
 		return nil, nil, err
 	}
+	setDefaultRendererAndDeployer(cfgSet)
 
 	if err := validation.Process(cfgSet, validation.GetValidationOpts(opts)); err != nil {
 		return nil, nil, fmt.Errorf("invalid skaffold config: %w", err)
@@ -149,3 +150,13 @@ func warnIfUpdateIsAvailable() {
 		log.Entry(context.TODO()).Warn(warning)
 	}
 }
+
+func setDefaultRendererAndDeployer(configs parser.SkaffoldConfigSet) {
+	// do not set a default deployer or renderer in a multi-config application.
+	if len(configs) > 1 {
+		return
+	}
+	// there always exists at least one config
+	defaults.SetDefaultRenderer(configs[0].SkaffoldConfig)
+	defaults.SetDefaultDeployer(configs[0].SkaffoldConfig)
+}
diff --git a/integration/examples/multi-config-microservices/leeroy-app/skaffold.yaml b/integration/examples/multi-config-microservices/leeroy-app/skaffold.yaml
index a7d7c2564..7dc9aedf0 100644
--- a/integration/examples/multi-config-microservices/leeroy-app/skaffold.yaml
+++ b/integration/examples/multi-config-microservices/leeroy-app/skaffold.yaml
@@ -13,6 +13,8 @@ build:
 manifests:
   rawYaml:
     - kubernetes/*
+deploy:
+  kubectl: {}
 portForward:
 - resourceType: deployment
   resourceName: leeroy-app
diff --git a/integration/examples/multi-config-microservices/leeroy-web/skaffold.yaml b/integration/examples/multi-config-microservices/leeroy-web/skaffold.yaml
index ff9bd6014..d428d6d68 100644
--- a/integration/examples/multi-config-microservices/leeroy-web/skaffold.yaml
+++ b/integration/examples/multi-config-microservices/leeroy-web/skaffold.yaml
@@ -11,8 +11,10 @@ build:
     - image: base
       alias: BASE
 manifests:
-    rawYaml:
-      - kubernetes/*
+  rawYaml:
+    - kubernetes/*
+deploy:
+  kubectl: {}
 portForward:
 - resourceType: deployment
   resourceName: leeroy-web
diff --git a/pkg/skaffold/build/bazel/build.go b/pkg/skaffold/build/bazel/build.go
index 4e3b57130..5fc40c76c 100644
--- a/pkg/skaffold/build/bazel/build.go
+++ b/pkg/skaffold/build/bazel/build.go
@@ -23,6 +23,7 @@ import (
 	"io"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"strings"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
@@ -84,7 +85,7 @@ func (b *Builder) buildTar(ctx context.Context, out io.Writer, workspace string,
 		return "", fmt.Errorf("getting bazel tar path: %w", err)
 	}
 
-	return tarPath, nil
+	return filepath.Join(workspace, tarPath), nil
 }
 
 func (b *Builder) loadImage(ctx context.Context, out io.Writer, tarPath string, a *latest.BazelArtifact, tag string) (string, error) {
diff --git a/pkg/skaffold/deploy/helm/helm.go b/pkg/skaffold/deploy/helm/helm.go
index 8c62c55b5..2294ebea7 100644
--- a/pkg/skaffold/deploy/helm/helm.go
+++ b/pkg/skaffold/deploy/helm/helm.go
@@ -457,8 +457,11 @@ func (h *Deployer) Render(ctx context.Context, out io.Writer, builds []graph.Art
 	if err != nil {
 		return err
 	}
-
-	return manifest.Write(modifiedManifests.String(), filepath, out)
+	modifiedText := modifiedManifests.String()
+	if filepath == "" {
+		modifiedText += "\n"
+	}
+	return manifest.Write(modifiedText, filepath, out)
 }
 
 func (h *Deployer) HasRunnableHooks() bool {
diff --git a/pkg/skaffold/deploy/kubectl/kubectl.go b/pkg/skaffold/deploy/kubectl/kubectl.go
index 1f148f3dc..3e0a79cbb 100644
--- a/pkg/skaffold/deploy/kubectl/kubectl.go
+++ b/pkg/skaffold/deploy/kubectl/kubectl.go
@@ -80,7 +80,6 @@ type Deployer struct {
 	insecureRegistries map[string]bool
 	labeller           *label.DefaultLabeller
 	skipRender         bool
-	hydrationDir       string
 	namespaces         *[]string
 
 	transformableAllowlist map[apimachinery.GroupKind]latest.ResourceFilter
@@ -89,7 +88,7 @@ type Deployer struct {
 
 // NewDeployer returns a new Deployer for a DeployConfig filled
 // with the needed configuration for `kubectl apply`
-func NewDeployer(cfg Config, labeller *label.DefaultLabeller, d *latest.KubectlDeploy, hydrationDir string) (*Deployer, error) {
+func NewDeployer(cfg Config, labeller *label.DefaultLabeller, d *latest.KubectlDeploy) (*Deployer, error) {
 	defaultNamespace := ""
 	if d.DefaultNamespace != nil {
 		var err error
@@ -132,7 +131,6 @@ func NewDeployer(cfg Config, labeller *label.DefaultLabeller, d *latest.KubectlD
 		// hydratedManifests refers to the DIR in the `skaffold apply DIR`. Used in both v1 and v2.
 		hydratedManifests: cfg.HydratedManifests(),
 		// hydrationDir refers to the path where the hydrated manifests are stored, this is introduced in v2.
-		hydrationDir: hydrationDir,
 
 		transformableAllowlist: transformableAllowlist,
 		transformableDenylist:  transformableDenylist,
diff --git a/pkg/skaffold/initializer/analyze/analyze.go b/pkg/skaffold/initializer/analyze/analyze.go
index 6417fdcd5..8aa55aa01 100644
--- a/pkg/skaffold/initializer/analyze/analyze.go
+++ b/pkg/skaffold/initializer/analyze/analyze.go
@@ -50,7 +50,8 @@ type ProjectAnalysis struct {
 }
 
 type HelmChartInfo struct {
-	chartPaths map[string][]string
+	chartPaths    map[string][]string
+	buildersFound []build.InitBuilder
 }
 
 func (h *HelmChartInfo) Charts() map[string][]string {
@@ -75,7 +76,8 @@ func (a *ProjectAnalysis) KustomizeBases() []string {
 
 func (a *ProjectAnalysis) HelmChartInfo() HelmChartInfo {
 	return HelmChartInfo{
-		chartPaths: a.helmAnalyzer.chartDirs,
+		chartPaths:    a.helmAnalyzer.chartDirs,
+		buildersFound: a.Builders(),
 	}
 }
 
diff --git a/pkg/skaffold/initializer/analyze/helm.go b/pkg/skaffold/initializer/analyze/helm.go
index ffa9b850a..959fce29b 100644
--- a/pkg/skaffold/initializer/analyze/helm.go
+++ b/pkg/skaffold/initializer/analyze/helm.go
@@ -50,7 +50,7 @@ func (h *helmAnalyzer) analyzeFile(ctx context.Context, fp string) error {
 			if hasChart(dir) {
 				h.chartDirs[dir] = []string{fp}
 			}
-			log.Entry(context.TODO()).Debugf("ignoring a yaml file %s not part of any chart ", fp)
+			log.Entry(context.TODO()).Debugf("not detecting a yaml file %s as value file. Not part of any chart", fp)
 		}
 	}
 	return nil
diff --git a/pkg/skaffold/initializer/deploy/helm.go b/pkg/skaffold/initializer/deploy/helm.go
index 273aa958f..f14442426 100644
--- a/pkg/skaffold/initializer/deploy/helm.go
+++ b/pkg/skaffold/initializer/deploy/helm.go
@@ -20,13 +20,16 @@ import (
 	"context"
 	"io/ioutil"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"strings"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/analyze"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/errors"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
 )
 
@@ -36,7 +39,9 @@ const (
 
 // for testing
 var (
-	readFile = ioutil.ReadFile
+	readFile    = ioutil.ReadFile
+	tempDir     = ioutil.TempDir
+	osRemoveAll = os.RemoveAll
 )
 
 // helm implements deploymentInitializer for the helm deployer.
@@ -46,9 +51,10 @@ type helm struct {
 
 type chart struct {
 	name        string
-	chartValues map[string]string
+	chartValues map[string]interface{}
 	path        string
 	valueFiles  []string
+	version     string
 }
 
 // newHelmInitializer returns a helm config generator.
@@ -61,15 +67,7 @@ func newHelmInitializer(chartValuesMap map[string][]string) helm {
 			log.Entry(context.TODO()).Infof("Skipping chart dir %s, as %s could not be parsed as valid yaml", chDir, chFile)
 			continue
 		}
-		name := getChartName(parsed, chDir)
-		// to make skaffold.yaml more portable across OS-es we should always generate /-delimited filePaths
-		replaced := strings.ReplaceAll(chDir, string(os.PathSeparator), "/")
-		charts = append(charts, chart{
-			chartValues: parsed,
-			name:        name,
-			path:        replaced,
-			valueFiles:  vfs,
-		})
+		charts = append(charts, buildChart(parsed, chDir, vfs))
 	}
 	return helm{
 		charts: charts,
@@ -81,11 +79,20 @@ func newHelmInitializer(chartValuesMap map[string][]string) helm {
 func (h helm) DeployConfig() (latest.DeployConfig, []latest.Profile) {
 	releases := []latest.HelmRelease{}
 	for _, ch := range h.charts {
-		releases = append(releases, latest.HelmRelease{
+		// to make skaffold.yaml more portable across OS-es we should always generate /-delimited filePaths
+		rPath := strings.ReplaceAll(ch.path, string(os.PathSeparator), "/")
+		rVfs := make([]string, len(ch.valueFiles))
+		for i, vf := range ch.valueFiles {
+			rVfs[i] = strings.ReplaceAll(vf, string(os.PathSeparator), "/")
+		}
+
+		r := latest.HelmRelease{
 			Name:        ch.name,
-			ChartPath:   ch.path,
-			ValuesFiles: ch.valueFiles,
-		})
+			ChartPath:   rPath,
+			Version:     ch.version,
+			ValuesFiles: rVfs,
+		}
+		releases = append(releases, r)
 	}
 	return latest.DeployConfig{
 		DeployType: latest.DeployType{
@@ -110,28 +117,97 @@ func (h helm) AddManifestForImage(string, string) {}
 
 // GetImages return an empty string for helm.
 func (h helm) GetImages() []string {
+	// Run helm template in each top level dir.
+	// Parse templated manifest files and then get image names.
 	artifacts := []string{}
+	td, err := tempDir("", "skaffold_")
+	if err != nil {
+		log.Entry(context.TODO()).Fatalf("cannot create temporary directory. Encountered error: %s", err)
+	}
+	defer osRemoveAll(td)
 	for _, ch := range h.charts {
-		artifacts = append(artifacts, ch.name)
+		args := []string{"template", ch.path}
+		for _, v := range ch.valueFiles {
+			args = append(args, "-f", v)
+		}
+		o, err := tempDir(td, ch.name)
+		if err != nil {
+			log.Entry(context.TODO()).Fatalf("cannot create temporary directory. Encountered error: %s", err)
+		}
+		args = append(args, "--output-dir", o)
+		cmd := exec.Command("helm", args...)
+		err = util.RunCmd(context.TODO(), cmd)
+		if err != nil {
+			log.Entry(context.TODO()).Warnf("could not initialize builders for helm chart %q.\nCommand %q encountered error: %s", ch.name, cmd, err)
+			continue
+		}
+		// read all templates generated
+		files := getAllFiles(o)
+		for _, file := range files {
+			images, err := kubernetes.ParseImagesFromKubernetesYaml(file)
+			if err != nil {
+				log.Entry(context.TODO()).Warnf("could not initialize builder for helm chart %q.\nCould not parse %q output due to error: %s", ch.name, cmd, err)
+			} else {
+				artifacts = append(artifacts, images...)
+			}
+		}
 	}
 	return artifacts
 }
 
-func parseChartValues(fp string) (map[string]string, error) {
+func parseChartValues(fp string) (map[string]interface{}, error) {
 	in, err := readFile(fp)
 	if err != nil {
 		return nil, err
 	}
-	m := map[string]string{}
-	if err := yaml.UnmarshalStrict(in, &m); err != nil {
-		return nil, err
+	m := map[string]interface{}{}
+	if errY := yaml.UnmarshalStrict(in, &m); errY != nil {
+		return nil, errY
 	}
 	return m, nil
 }
 
-func getChartName(parsed map[string]string, chDir string) string {
+func getChartName(parsed map[string]interface{}, chDir string) string {
 	if v, ok := parsed[nameKey]; ok {
-		return v
+		return v.(string)
 	}
 	return filepath.Base(chDir)
 }
+
+func buildChart(parsed map[string]interface{}, chDir string, vfs []string) chart {
+	ch := chart{
+		chartValues: parsed,
+		name:        getChartName(parsed, chDir),
+		path:        chDir,
+		valueFiles:  vfs,
+	}
+	if v := getVersion(parsed); v != "" {
+		ch.version = v
+	}
+	return ch
+}
+
+func getVersion(m map[string]interface{}) string {
+	if v, ok := m["version"]; ok {
+		return v.(string)
+	}
+	return ""
+}
+
+func getAllFiles(o string) []string {
+	var files []string
+	err := filepath.Walk(o, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if info.IsDir() {
+			return nil
+		}
+		files = append(files, path)
+		return nil
+	})
+	if err != nil {
+		log.Entry(context.TODO()).Fatalf("could not walk directory %q due to error: %s", o, err)
+	}
+	return files
+}
diff --git a/pkg/skaffold/runner/deployer.go b/pkg/skaffold/runner/deployer.go
index 7e4c80a4b..8bc903a7c 100644
--- a/pkg/skaffold/runner/deployer.go
+++ b/pkg/skaffold/runner/deployer.go
@@ -115,7 +115,7 @@ func GetDeployer(ctx context.Context, runCtx *runcontext.RunContext, labeller *l
 			}
 		}
 
-		return getDefaultDeployer(runCtx, labeller, hydrationDir)
+		return getDefaultDeployer(runCtx, labeller)
 	}
 
 	var deployers []deploy.Deployer
@@ -158,7 +158,7 @@ func GetDeployer(ctx context.Context, runCtx *runcontext.RunContext, labeller *l
 		}
 
 		if d.KubectlDeploy != nil {
-			deployer, err := kubectl.NewDeployer(dCtx, labeller, d.KubectlDeploy, hydrationDir)
+			deployer, err := kubectl.NewDeployer(dCtx, labeller, d.KubectlDeploy)
 			if err != nil {
 				return nil, err
 			}
@@ -213,7 +213,7 @@ The default deployer will honor a select set of deploy configuration from an exi
 For a multi-config project, we do not currently support resolving conflicts between differing sets of this deploy configuration.
 Therefore, in this function we do implicit validation of the provided configuration, and fail if any conflict cannot be resolved.
 */
-func getDefaultDeployer(runCtx *runcontext.RunContext, labeller *label.DefaultLabeller, hydrationDir string) (deploy.Deployer, error) {
+func getDefaultDeployer(runCtx *runcontext.RunContext, labeller *label.DefaultLabeller) (deploy.Deployer, error) {
 	deployCfgs := runCtx.DeployConfigs()
 
 	var kFlags *latest.KubectlFlags
@@ -280,7 +280,7 @@ func getDefaultDeployer(runCtx *runcontext.RunContext, labeller *label.DefaultLa
 		DefaultNamespace: defaultNamespace,
 	}
 	dCtx := &deployerCtx{runCtx, latest.DeployConfig{StatusCheck: statusCheck, KubeContext: kubeContext, DeployType: latest.DeployType{KubectlDeploy: k}}}
-	defaultDeployer, err := kubectl.NewDeployer(dCtx, labeller, k, hydrationDir)
+	defaultDeployer, err := kubectl.NewDeployer(dCtx, labeller, k)
 	if err != nil {
 		return nil, fmt.Errorf("instantiating default kubectl deployer: %w", err)
 	}
diff --git a/pkg/skaffold/schema/defaults/defaults.go b/pkg/skaffold/schema/defaults/defaults.go
index b79ea6df5..d1a06b6dc 100644
--- a/pkg/skaffold/schema/defaults/defaults.go
+++ b/pkg/skaffold/schema/defaults/defaults.go
@@ -108,8 +108,6 @@ func Set(c *latest.SkaffoldConfig) error {
 	}
 
 	setDefaultTestWorkspace(c)
-	SetDefaultRenderer(c)
-	SetDefaultDeployer(c)
 	return nil
 }
 


Test patch:
diff --git a/integration/init_test.go b/integration/init_test.go
index 60f605735..3da5e5c05 100644
--- a/integration/init_test.go
+++ b/integration/init_test.go
@@ -29,10 +29,7 @@ import (
 	"github.com/GoogleContainerTools/skaffold/testutil"
 )
 
-func TestInitCompose(t *testing.T) {
-	// TODO: This test shall pass once render v2 is completed.
-	t.SkipNow()
-
+func TestInit(t *testing.T) {
 	MarkIntegrationTest(t, CanRunWithoutGcp)
 
 	tests := []struct {
@@ -40,10 +37,18 @@ func TestInitCompose(t *testing.T) {
 		dir  string
 		args []string
 	}{
+		/*
+			// Fix after https://github.com/GoogleContainerTools/skaffold/issues/6722
+				{
+					name: "compose",
+					dir:  "testdata/init/compose",
+					args: []string{"--compose-file", "docker-compose.yaml"},
+				},
+		*/
 		{
-			name: "compose",
-			dir:  "testdata/init/compose",
-			args: []string{"--compose-file", "docker-compose.yaml"},
+			name: "helm init",
+			dir:  "testdata/init/helm-project",
+			args: []string{"--XXenableBuildpacksInit=false"},
 		},
 	}
 	for _, test := range tests {
@@ -52,18 +57,17 @@ func TestInitCompose(t *testing.T) {
 
 			initArgs := append([]string{"--force"}, test.args...)
 			skaffold.Init(initArgs...).InDir(test.dir).WithConfig("skaffold.yaml.out").RunOrFail(t.T)
-
 			checkGeneratedConfig(t, test.dir)
 
 			// Make sure the skaffold yaml and the kubernetes manifests created by kompose are ok
 			skaffold.Run().InDir(test.dir).WithConfig("skaffold.yaml.out").InNs(ns.Name).RunOrFail(t.T)
+			defer skaffold.Delete().InDir(test.dir).WithConfig("skaffold.yaml.out").InNs(ns.Name)
 		})
 	}
 }
 
 func TestInitManifestGeneration(t *testing.T) {
-	// TODO: This test shall pass once render v2 is completed.
-	t.SkipNow()
+	t.Skipf("Fix after https://github.com/GoogleContainerTools/skaffold/issues/6722")
 
 	MarkIntegrationTest(t, CanRunWithoutGcp)
 
@@ -99,13 +103,13 @@ func TestInitManifestGeneration(t *testing.T) {
 
 			// Make sure the skaffold yaml and the kubernetes manifests created by kompose are ok
 			skaffold.Run().InDir(test.dir).WithConfig("skaffold.yaml.out").InNs(ns.Name).RunOrFail(t.T)
+			skaffold.Delete().InDir(test.dir).WithConfig("skaffold.yaml.out").InNs(ns.Name).RunOrFail(t.T)
 		})
 	}
 }
 
 func TestInitKustomize(t *testing.T) {
-	// TODO: This test shall pass once render v2 is completed.
-	t.SkipNow()
+	t.Skipf("Fix after https://github.com/GoogleContainerTools/skaffold/issues/6722")
 
 	MarkIntegrationTest(t, CanRunWithoutGcp)
 
@@ -127,12 +131,12 @@ func TestInitKustomize(t *testing.T) {
 		checkGeneratedConfig(t, dir)
 
 		skaffold.Run().InDir(dir).WithConfig("skaffold.yaml.out").InNs(ns.Name).RunOrFail(t.T)
+		skaffold.Delete().InDir(dir).WithConfig("skaffold.yaml.out").InNs(ns.Name).RunOrFail(t.T)
 	})
 }
 
 func TestInitWithCLIArtifact(t *testing.T) {
-	// TODO: This test shall pass once render v2 is completed.
-	t.SkipNow()
+	t.Skipf("Fix after https://github.com/GoogleContainerTools/skaffold/issues/6722")
 
 	MarkIntegrationTest(t, CanRunWithoutGcp)
 
@@ -148,6 +152,7 @@ func TestInitWithCLIArtifact(t *testing.T) {
 
 		// Make sure the skaffold yaml is ok
 		skaffold.Run().InDir(dir).WithConfig("skaffold.yaml.out").InNs(ns.Name).RunOrFail(t.T)
+		skaffold.Delete().InDir(dir).WithConfig("skaffold.yaml.out").InNs(ns.Name).RunOrFail(t.T)
 	})
 }
 
diff --git a/integration/testdata/gcp-with-location/Dockerfile b/integration/testdata/gcb-with-location/Dockerfile
similarity index 100%
rename from integration/testdata/gcp-with-location/Dockerfile
rename to integration/testdata/gcb-with-location/Dockerfile
diff --git a/integration/testdata/gcp-with-location/go.mod b/integration/testdata/gcb-with-location/go.mod
similarity index 100%
rename from integration/testdata/gcp-with-location/go.mod
rename to integration/testdata/gcb-with-location/go.mod
diff --git a/integration/testdata/gcp-with-location/k8s-pod.yaml b/integration/testdata/gcb-with-location/k8s-pod.yaml
similarity index 62%
rename from integration/testdata/gcp-with-location/k8s-pod.yaml
rename to integration/testdata/gcb-with-location/k8s-pod.yaml
index 5e86139c9..e8e559450 100644
--- a/integration/testdata/gcp-with-location/k8s-pod.yaml
+++ b/integration/testdata/gcb-with-location/k8s-pod.yaml
@@ -5,4 +5,4 @@ metadata:
 spec:
   containers:
   - name: getting-started
-    image: skaffold-example-with-location
+    image: gcr.io/k8s-skaffold/skaffold-example-with-location
diff --git a/integration/testdata/gcp-with-location/main.go b/integration/testdata/gcb-with-location/main.go
similarity index 100%
rename from integration/testdata/gcp-with-location/main.go
rename to integration/testdata/gcb-with-location/main.go
diff --git a/integration/testdata/gcp-with-location/skaffold.yaml b/integration/testdata/gcb-with-location/skaffold.yaml
similarity index 54%
rename from integration/testdata/gcp-with-location/skaffold.yaml
rename to integration/testdata/gcb-with-location/skaffold.yaml
index 9cbb88d49..cfba17007 100644
--- a/integration/testdata/gcp-with-location/skaffold.yaml
+++ b/integration/testdata/gcb-with-location/skaffold.yaml
@@ -1,12 +1,11 @@
 apiVersion: skaffold/v3alpha1
 kind: Config
 build:
+  artifacts:
+    - image: gcr.io/k8s-skaffold/skaffold-example-with-location
   googleCloudBuild:
     projectId: k8s-skaffold
     region: asia-east1
-  artifacts:
-  - image: gcr.io/k8s-skaffold/skaffold-example-with-location
-deploy:
-  kubectl:
-    manifests:
-      - k8s-*
+manifests:
+  rawYaml:
+    - k8s-*
\ No newline at end of file
diff --git a/integration/testdata/gcp-with-location/README.md b/integration/testdata/gcp-with-location/README.md
deleted file mode 100644
index 2e4105950..000000000
--- a/integration/testdata/gcp-with-location/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-### Example: Getting started with a simple go app
-
-[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/GoogleContainerTools/skaffold&cloudshell_open_in_editor=README.md&cloudshell_workspace=examples/google-cloud-build)
-
-This is a simple example based on:
-
-* **building** a single Go file app and with a multistage `Dockerfile` using Google Cloud Build
-* **tagging** using the default tagPolicy (`gitCommit`)
-* **deploying** a single container pod using `kubectl`
diff --git a/integration/testdata/init/helm-project/hello-world/Chart.yaml b/integration/testdata/init/helm-project/hello-world/Chart.yaml
new file mode 100644
index 000000000..344f8579b
--- /dev/null
+++ b/integration/testdata/init/helm-project/hello-world/Chart.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+description: hello-world
+name: hello-world
+version: 0.1.0
diff --git a/integration/testdata/init/helm-project/hello-world/src/Dockerfile b/integration/testdata/init/helm-project/hello-world/src/Dockerfile
new file mode 100644
index 000000000..8f6762484
--- /dev/null
+++ b/integration/testdata/init/helm-project/hello-world/src/Dockerfile
@@ -0,0 +1,7 @@
+FROM golang:1.15-alpine as builder
+COPY main.go .
+RUN go build -o /app main.go
+
+FROM alpine:3
+CMD ["./app"]
+COPY --from=builder /app .
diff --git a/integration/testdata/init/helm-project/hello-world/src/main.go b/integration/testdata/init/helm-project/hello-world/src/main.go
new file mode 100644
index 000000000..593721cfe
--- /dev/null
+++ b/integration/testdata/init/helm-project/hello-world/src/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+	"fmt"
+	"time"
+)
+
+func main() {
+	for {
+		fmt.Println("Hello world!")
+
+		time.Sleep(time.Second * 1)
+	}
+}
diff --git a/integration/testdata/init/helm-project/hello-world/templates/hello-world-dep.yaml b/integration/testdata/init/helm-project/hello-world/templates/hello-world-dep.yaml
new file mode 100644
index 000000000..f89982ff1
--- /dev/null
+++ b/integration/testdata/init/helm-project/hello-world/templates/hello-world-dep.yaml
@@ -0,0 +1,19 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ .Chart.Name }}
+  labels:
+    app: {{ .Chart.Name }}
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: {{ .Chart.Name }}
+  template:
+    metadata:
+      labels:
+        app: {{ .Chart.Name }}
+    spec:
+      containers:
+      - name: {{ .Chart.Name }}
+        image: {{ .Values.image }}
diff --git a/integration/testdata/init/helm-project/hello-world/val.yaml b/integration/testdata/init/helm-project/hello-world/val.yaml
new file mode 100644
index 000000000..10d81708b
--- /dev/null
+++ b/integration/testdata/init/helm-project/hello-world/val.yaml
@@ -0,0 +1,2 @@
+# This is another YAML-formatted file.
+image: go-hello
\ No newline at end of file
diff --git a/integration/testdata/init/helm-project/hello-world/values.yaml b/integration/testdata/init/helm-project/hello-world/values.yaml
new file mode 100644
index 000000000..f1f861eb5
--- /dev/null
+++ b/integration/testdata/init/helm-project/hello-world/values.yaml
@@ -0,0 +1,3 @@
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+replicaCount: 2
\ No newline at end of file
diff --git a/integration/testdata/init/helm-project/skaffold.yaml b/integration/testdata/init/helm-project/skaffold.yaml
new file mode 100644
index 000000000..3b6cb6e62
--- /dev/null
+++ b/integration/testdata/init/helm-project/skaffold.yaml
@@ -0,0 +1,19 @@
+apiVersion: skaffold/v3alpha1
+kind: Config
+metadata:
+  name: helm-project
+build:
+  artifacts:
+  - image: go-hello
+    context: hello-world/src
+    docker:
+      dockerfile: Dockerfile
+deploy:
+  helm:
+    releases:
+    - name: hello-world
+      chartPath: hello-world
+      valuesFiles:
+      - hello-world/val.yaml
+      - hello-world/values.yaml
+      version: 0.1.0
diff --git a/integration/testdata/modules/app1/skaffold.yaml b/integration/testdata/modules/app1/skaffold.yaml
index b397b2a4b..8654d50a0 100644
--- a/integration/testdata/modules/app1/skaffold.yaml
+++ b/integration/testdata/modules/app1/skaffold.yaml
@@ -19,3 +19,6 @@ build:
 manifests:
   rawYaml:
   - 'k8s.yaml'
+
+deploy:
+  kubectl: {}
diff --git a/integration/testdata/modules/app2/skaffold.yaml b/integration/testdata/modules/app2/skaffold.yaml
index 659f45bf7..378908298 100644
--- a/integration/testdata/modules/app2/skaffold.yaml
+++ b/integration/testdata/modules/app2/skaffold.yaml
@@ -20,3 +20,6 @@ build:
 manifests:
   rawYaml:
   - 'k8s.yaml'
+
+deploy:
+  kubectl: {}
diff --git a/integration/testdata/modules/app3/skaffold.yaml b/integration/testdata/modules/app3/skaffold.yaml
index 240f278b0..3cc508552 100644
--- a/integration/testdata/modules/app3/skaffold.yaml
+++ b/integration/testdata/modules/app3/skaffold.yaml
@@ -15,3 +15,6 @@ build:
 manifests:
   rawYaml:
   - 'k8s.yaml'
+
+deploy:
+  kubectl: {}
diff --git a/pkg/skaffold/build/bazel/build_test.go b/pkg/skaffold/build/bazel/build_test.go
index 5e376582a..dc4d08e41 100644
--- a/pkg/skaffold/build/bazel/build_test.go
+++ b/pkg/skaffold/build/bazel/build_test.go
@@ -52,6 +52,29 @@ func TestBuildBazel(t *testing.T) {
 	})
 }
 
+func TestBazelTarPathFRespectWorkspace(t *testing.T) {
+	testutil.Run(t, "", func(t *testutil.T) {
+		t.Override(&util.DefaultExecCommand, testutil.CmdRun("bazel build //:app.tar --color=no").AndRunOut(
+			"bazel cquery //:app.tar --output starlark --starlark:expr target.files.to_list()[0].path",
+			"app.tar"))
+		testutil.CreateFakeImageTar("bazel:app", "../app.tar")
+
+		artifact := &latest.Artifact{
+			Workspace: "..",
+			ArtifactType: latest.ArtifactType{
+				BazelArtifact: &latest.BazelArtifact{
+					BuildTarget: "//:app.tar",
+				},
+			},
+		}
+
+		builder := NewArtifactBuilder(fakeLocalDaemon(), &mockConfig{}, false)
+		_, err := builder.Build(context.Background(), ioutil.Discard, artifact, "img:tag", platform.Matcher{})
+
+		t.CheckNoError(err)
+	})
+}
+
 func TestBuildBazelFailInvalidTarget(t *testing.T) {
 	testutil.Run(t, "", func(t *testutil.T) {
 		artifact := &latest.Artifact{
diff --git a/pkg/skaffold/deploy/helm/helm_test.go b/pkg/skaffold/deploy/helm/helm_test.go
index ae2e53c86..38b09a14f 100644
--- a/pkg/skaffold/deploy/helm/helm_test.go
+++ b/pkg/skaffold/deploy/helm/helm_test.go
@@ -1423,7 +1423,7 @@ func TestHelmRender(t *testing.T) {
 
 			if file != "" {
 				dat, _ := ioutil.ReadFile(file)
-				t.CheckDeepEqual(string(dat), test.expected)
+				t.CheckDeepEqual(test.expected, string(dat))
 			}
 		})
 	}
diff --git a/pkg/skaffold/deploy/kubectl/kubectl_test.go b/pkg/skaffold/deploy/kubectl/kubectl_test.go
index acc2da71c..d624e67cc 100644
--- a/pkg/skaffold/deploy/kubectl/kubectl_test.go
+++ b/pkg/skaffold/deploy/kubectl/kubectl_test.go
@@ -28,7 +28,6 @@ import (
 	"time"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/label"
 	deployutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/util"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
@@ -235,7 +234,7 @@ func TestKubectlV1RenderDeploy(t *testing.T) {
 				},
 				RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{
 					Namespace: skaffoldNamespaceOption}},
-			}, &label.DefaultLabeller{}, &test.kubectl, filepath.Join(tmpDir.Root(), constants.DefaultHydrationDir))
+			}, &label.DefaultLabeller{}, &test.kubectl)
 			t.RequireNoError(err)
 
 			rc := latest.RenderConfig{Generate: test.generate}
@@ -327,7 +326,7 @@ func TestKubectlCleanup(t *testing.T) {
 			k, err := NewDeployer(&kubectlConfig{
 				workingDir: ".",
 				RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: TestNamespace}},
-			}, &label.DefaultLabeller{}, &test.kubectl, filepath.Join(tmpDir.Root(), constants.DefaultHydrationDir))
+			}, &label.DefaultLabeller{}, &test.kubectl)
 			t.RequireNoError(err)
 			rc := latest.RenderConfig{Generate: test.generate}
 			mockCfg := &kubectlConfig{
@@ -387,7 +386,7 @@ func TestKubectlDeployerRemoteCleanup(t *testing.T) {
 			k, err := NewDeployer(&kubectlConfig{
 				workingDir: ".",
 				RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: TestNamespace}},
-			}, &label.DefaultLabeller{}, &test.kubectl, filepath.Join(tmpDir.Root(), constants.DefaultHydrationDir))
+			}, &label.DefaultLabeller{}, &test.kubectl)
 			t.RequireNoError(err)
 
 			err = k.Cleanup(context.Background(), ioutil.Discard, false, nil)
@@ -416,8 +415,7 @@ func TestKubectlRedeploy(t *testing.T) {
 				Enabled: true,
 				Delay:   0 * time.Millisecond,
 				Max:     10 * time.Second},
-		}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-app.yaml"), tmpDir.Path("deployment-web.yaml")}},
-			filepath.Join(tmpDir.Root(), constants.DefaultHydrationDir))
+		}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-app.yaml"), tmpDir.Path("deployment-web.yaml")}})
 		t.RequireNoError(err)
 
 		// Deploy both manifests
@@ -485,8 +483,7 @@ func TestKubectlWaitForDeletions(t *testing.T) {
 				Delay:   0 * time.Millisecond,
 				Max:     10 * time.Second,
 			},
-		}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-web.yaml")}},
-			filepath.Join(tmpDir.Root(), constants.DefaultHydrationDir))
+		}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-web.yaml")}})
 		t.RequireNoError(err)
 
 		var out bytes.Buffer
@@ -524,8 +521,7 @@ func TestKubectlWaitForDeletionsFails(t *testing.T) {
 				Delay:   10 * time.Second,
 				Max:     100 * time.Millisecond,
 			},
-		}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-web.yaml")}},
-			filepath.Join(tmpDir.Root(), constants.DefaultHydrationDir))
+		}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-web.yaml")}})
 		t.RequireNoError(err)
 
 		m, err := manifest.Load(bytes.NewReader([]byte(DeploymentWebYAMLv1)))
@@ -588,8 +584,7 @@ func TestDependencies(t *testing.T) {
 				Touch("00/b.yaml", "00/a.yaml").
 				Chdir()
 
-			k, err := NewDeployer(&kubectlConfig{}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: test.manifests},
-				filepath.Join(tmpDir.Root(), constants.DefaultHydrationDir))
+			k, err := NewDeployer(&kubectlConfig{}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: test.manifests})
 			t.RequireNoError(err)
 
 			dependencies, err := k.Dependencies()
@@ -645,7 +640,7 @@ func TestGCSManifests(t *testing.T) {
 				workingDir: ".",
 				skipRender: test.skipRender,
 				RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: TestNamespace}},
-			}, &label.DefaultLabeller{}, &latest.KubectlDeploy{}, filepath.Join("", constants.DefaultHydrationDir))
+			}, &label.DefaultLabeller{}, &latest.KubectlDeploy{})
 			t.RequireNoError(err)
 
 			err = k.Deploy(context.Background(), ioutil.Discard, nil, m)
@@ -682,7 +677,7 @@ func TestHasRunnableHooks(t *testing.T) {
 	}
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
-			k, err := NewDeployer(&kubectlConfig{}, &label.DefaultLabeller{}, &test.cfg, "")
+			k, err := NewDeployer(&kubectlConfig{}, &label.DefaultLabeller{}, &test.cfg)
 			t.RequireNoError(err)
 			actual := k.HasRunnableHooks()
 			t.CheckDeepEqual(test.expected, actual)
diff --git a/pkg/skaffold/initializer/deploy/helm_test.go b/pkg/skaffold/initializer/deploy/helm_test.go
index 1d4374457..5fb671ac4 100644
--- a/pkg/skaffold/initializer/deploy/helm_test.go
+++ b/pkg/skaffold/initializer/deploy/helm_test.go
@@ -17,9 +17,15 @@ limitations under the License.
 package deploy
 
 import (
+	"errors"
+	"fmt"
 	"testing"
 
+	logrustest "github.com/sirupsen/logrus/hooks/test"
+
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
 	"github.com/GoogleContainerTools/skaffold/testutil"
 )
 
@@ -29,6 +35,24 @@ func TestDeployConfig(t *testing.T) {
 		input       map[string][]string
 		expected    []latest.HelmRelease
 	}{
+		{
+			description: "charts with one or more values file",
+			input: map[string][]string{
+				"charts":     {"charts/val.yml", "charts/values.yaml"},
+				"charts-foo": {"charts-foo/values.yaml"},
+			},
+			expected: []latest.HelmRelease{
+				{
+					Name:        "charts-foo",
+					ChartPath:   "charts-foo",
+					ValuesFiles: []string{"charts-foo/values.yaml"},
+				},
+				{
+					Name:        "charts",
+					ChartPath:   "charts",
+					ValuesFiles: []string{"charts/val.yml", "charts/values.yaml"},
+				},
+			}},
 		{
 			description: "charts with one or more values file",
 			input: map[string][]string{
@@ -60,3 +84,113 @@ func TestDeployConfig(t *testing.T) {
 		})
 	}
 }
+
+func TestGetImages(t *testing.T) {
+	tests := []struct {
+		description string
+		values      []string
+		templates   []string
+		cmd         string
+		err         error
+		shouldMatch string
+		expected    []string
+	}{
+		{
+			description: "helm templates multiple value files",
+			templates:   []string{backendTemp},
+			values:      []string{"backend/val.yml", "backend/values.yaml"},
+			cmd:         "helm template backend -f backend/val.yml -f backend/values.yaml",
+			expected:    []string{"go-guestbook-backend"},
+		},
+		{
+			description: "no values files",
+			values:      []string{},
+			cmd:         "helm template backend",
+			templates:   []string{backendTemp},
+			expected:    []string{"go-guestbook-backend"},
+		},
+		{
+			description: "err parsing template",
+			values:      []string{"backend/values.yaml"},
+			cmd:         "helm template backend -f backend/values.yaml",
+			templates:   []string{"invalid"},
+			shouldMatch: "reading Kubernetes YAML: yaml: unmarshal errors:\n  line 1: cannot unmarshal !!str `invalid` into kubernetes.yamlObject",
+		},
+		{
+			description: "err when running helm template",
+			values:      []string{"backend/values.yaml"},
+			cmd:         "helm template backend -f backend/values.yaml",
+			err:         errors.New("invalid"),
+			shouldMatch: "encountered error: invalid",
+			expected:    []string{},
+		},
+	}
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			t.Override(&readFile, func(_ string) ([]byte, error) {
+				return []byte{}, nil
+			})
+			h := newHelmInitializer(map[string][]string{"backend": test.values})
+			hook := &logrustest.Hook{}
+			log.AddHook(hook)
+			tmpDir := t.NewTempDir()
+			for i, contents := range test.templates {
+				tmpDir.Write(fmt.Sprintf("template/template_%d.yaml", i), contents)
+			}
+			cmd := fmt.Sprintf("%s --output-dir %s", test.cmd, tmpDir.Path("template"))
+			t.Override(&util.DefaultExecCommand, testutil.CmdRunErr(cmd, test.err))
+			t.Override(&tempDir, func(dir, pattern string) (name string, err error) {
+				return tmpDir.Path("template"), nil
+			})
+			images := h.GetImages()
+			t.CheckElementsMatch(test.expected, images)
+			t.CheckMatches(test.shouldMatch, lastEntryHook(hook))
+		})
+	}
+}
+
+func lastEntryHook(hook *logrustest.Hook) string {
+	for _, entry := range hook.AllEntries() {
+		return entry.Message
+	}
+	return ""
+}
+
+var backendTemp = `---
+# Source: backend/templates/service.yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: backend
+  labels:
+    app: backend
+    tier: backend
+spec:
+  type: ClusterIP
+  selector:
+    app: backend
+    tier: backend
+  ports:
+  - port: 8080
+    targetPort: http-server
+---
+# Source: backend/templates/deployment.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: backend
+  labels:
+    app: backend
+spec:
+  selector:
+    matchLabels:
+      app: backend
+  replicas: 2
+  template:
+    metadata:
+      labels:
+        app: backend
+    spec:
+      containers:
+      - name: backend
+        image: go-guestbook-backend`
diff --git a/pkg/skaffold/initializer/helm_test.go b/pkg/skaffold/initializer/helm_test.go
index beb977069..cf02fec67 100644
--- a/pkg/skaffold/initializer/helm_test.go
+++ b/pkg/skaffold/initializer/helm_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package initializer
 
 import (
+	"path/filepath"
 	"testing"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
@@ -60,10 +61,10 @@ spec:
 		{
 			description: "helm charts with values files",
 			filesWithContents: map[string]string{
-				"apache/Chart.yaml":               "",
-				"apache/values.yaml":              "",
-				"apache/another.yml":              "",
-				"apache/templates/deployment.yml": deployment,
+				filepath.Join("apache", "Chart.yaml"):                  "",
+				filepath.Join("apache", "values.yaml"):                 "",
+				filepath.Join("apache", "another.yml"):                 "",
+				filepath.Join("apache", "templates", "deployment.yml"): deployment,
 			},
 			expected: []latest.HelmRelease{
 				{
@@ -75,14 +76,14 @@ spec:
 		{
 			description: "helm charts with multiple sub charts",
 			filesWithContents: map[string]string{
-				"apache/Chart.yaml":                        "",
-				"apache/values.yaml":                       "",
-				"apache/subchart/Chart.yaml":               "",
-				"apache/templates/deployment.yml":          deployment,
-				"apache/subchart/templates/deployment.yml": deployment,
-				"apache/subchart/val.yaml":                 "",
-				"apache/subchart2/Chart.yaml":              "",
-				"apache/subchart2/values.yaml":             "",
+				filepath.Join("apache", "Chart.yaml"):                              "",
+				filepath.Join("apache", "values.yaml"):                             "",
+				filepath.Join("apache", "subchart", "Chart.yaml"):                  "",
+				filepath.Join("apache", "templates", "deployment.yml"):             deployment,
+				filepath.Join("apache", "subchart", "templates", "deployment.yml"): deployment,
+				filepath.Join("apache", "subchart", "val.yaml"):                    "",
+				filepath.Join("apache", "subchart2", "Chart.yaml"):                 "",
+				filepath.Join("apache", "subchart2", "values.yaml"):                "",
 			},
 			expected: []latest.HelmRelease{
 				{
diff --git a/pkg/skaffold/initializer/testdata/init/helm-deployment/charts/templates/deployment.yaml b/pkg/skaffold/initializer/testdata/init/helm-deployment/charts/templates/deployment.yaml
index b36776483..78207bf88 100644
--- a/pkg/skaffold/initializer/testdata/init/helm-deployment/charts/templates/deployment.yaml
+++ b/pkg/skaffold/initializer/testdata/init/helm-deployment/charts/templates/deployment.yaml
@@ -5,7 +5,6 @@ metadata:
   labels:
     app: {{ .Chart.Name }}
 spec:
-  replicas: 1
   selector:
     matchLabels:
       app: {{ .Chart.Name }}
diff --git a/pkg/skaffold/initializer/testdata/init/helm-deployment/charts/values.yaml b/pkg/skaffold/initializer/testdata/init/helm-deployment/charts/values.yaml
index ba42ad6e1..d191de401 100644
--- a/pkg/skaffold/initializer/testdata/init/helm-deployment/charts/values.yaml
+++ b/pkg/skaffold/initializer/testdata/init/helm-deployment/charts/values.yaml
@@ -2,3 +2,4 @@
 # This is a YAML-formatted file.
 # Declare variables to be passed into your templates.
 replicaCount: 2
+image: skaffold-helm
diff --git a/pkg/skaffold/initializer/testdata/init/helm-deployment/skaffold.yaml b/pkg/skaffold/initializer/testdata/init/helm-deployment/skaffold.yaml
index 426731a83..ae17e86c7 100644
--- a/pkg/skaffold/initializer/testdata/init/helm-deployment/skaffold.yaml
+++ b/pkg/skaffold/initializer/testdata/init/helm-deployment/skaffold.yaml
@@ -12,5 +12,6 @@ deploy:
     releases:
     - name: skaffold-helm
       chartPath: charts
+      version: 0.1.0
       valuesFiles:
       - charts/values.yaml
diff --git a/pkg/skaffold/runner/deployer_test.go b/pkg/skaffold/runner/deployer_test.go
index f5ba47de6..ce5158354 100644
--- a/pkg/skaffold/runner/deployer_test.go
+++ b/pkg/skaffold/runner/deployer_test.go
@@ -95,7 +95,7 @@ func TestGetDeployer(tOuter *testing.T) {
 						Pipelines: runcontext.NewPipelines([]latest.Pipeline{{}}),
 					}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 						Flags: latest.KubectlFlags{},
-					}, "")).(deploy.Deployer),
+					})).(deploy.Deployer),
 				}, false),
 			},
 			{
@@ -135,7 +135,7 @@ func TestGetDeployer(tOuter *testing.T) {
 					Pipelines: runcontext.NewPipelines([]latest.Pipeline{{}}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
-				}, "")).(deploy.Deployer),
+				})).(deploy.Deployer),
 			},
 			{
 				description: "apply forces creation of kubectl deployer with helm config",
@@ -152,7 +152,7 @@ func TestGetDeployer(tOuter *testing.T) {
 					Pipelines: runcontext.NewPipelines([]latest.Pipeline{{}}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
-				}, "")).(deploy.Deployer),
+				})).(deploy.Deployer),
 			},
 			{
 				description: "multiple deployers",
@@ -229,7 +229,7 @@ func TestGetDeployer(tOuter *testing.T) {
 					Pipelines: runcontext.NewPipelines([]latest.Pipeline{{}}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
-				}, "")).(deploy.Deployer),
+				})).(deploy.Deployer),
 			},
 			{
 				description: "apply works with Cloud Run",
@@ -324,7 +324,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 					Pipelines: runcontext.NewPipelines([]latest.Pipeline{{}}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
-				}, "")).(*kubectl.Deployer),
+				})).(*kubectl.Deployer),
 			},
 			{
 				name: "one config with kubectl deploy, with flags",
@@ -343,7 +343,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 						Apply:  []string{"--foo"},
 						Global: []string{"--bar"},
 					},
-				}, "")).(*kubectl.Deployer),
+				})).(*kubectl.Deployer),
 			},
 			{
 				name: "two kubectl configs with mismatched flags should fail",
@@ -374,7 +374,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 					Pipelines: runcontext.NewPipelines([]latest.Pipeline{{}}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
-				}, "")).(*kubectl.Deployer),
+				})).(*kubectl.Deployer),
 			},
 			{
 				name: "one config with kustomize deploy",
@@ -385,7 +385,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 					Pipelines: runcontext.NewPipelines([]latest.Pipeline{{}}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
-				}, "")).(*kubectl.Deployer),
+				})).(*kubectl.Deployer),
 			},
 		}
 
@@ -401,7 +401,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 				}
 				deployer, err := getDefaultDeployer(&runcontext.RunContext{
 					Pipelines: runcontext.NewPipelines(pipelines),
-				}, &label.DefaultLabeller{}, "")
+				}, &label.DefaultLabeller{})
 
 				t.CheckErrorAndFailNow(test.shouldErr, err)