Repository: GoogleContainerTools/skaffold (go)

Issue:
`helm.setFiles` base-path not resolved correctly when imported as a config dependency
`helm.setFiles` base-path not resolved correctly when imported as a config dependency.
This is because the tag `skaffold="filepath"` is missing on this field in [config.go](https://github.com/GoogleContainerTools/skaffold/blob/b5b916d1c5eb32d3b4758e20c76094f9b85b7bf5/pkg/skaffold/schema/latest/config.go#L704)

_Originally posted by @ngraf in https://github.com/GoogleContainerTools/skaffold/issues/5396#issuecomment-796535815_

Pull request:
add `filepath` tag to `helm.valuesFiles` property
Fixes #5536 



Patch:
diff --git a/pkg/skaffold/docker/parse.go b/pkg/skaffold/docker/parse.go
index 93c9b503d..9e00afbe6 100644
--- a/pkg/skaffold/docker/parse.go
+++ b/pkg/skaffold/docker/parse.go
@@ -214,15 +214,16 @@ func extractCopyCommands(nodes []*parser.Node, onlyLastImage bool, cfg Config) (
 		switch node.Value {
 		case command.From:
 			from := fromInstruction(node)
+			if from.as != "" {
+				// Stage names are case insensitive
+				stages[strings.ToLower(from.as)] = true
+			}
+
 			if from.image == "" {
 				// some build args like artifact dependencies are not available until the first build sequence has completed.
 				// skip check if there are unavailable images
 				continue
 			}
-			if from.as != "" {
-				// Stage names are case insensitive
-				stages[strings.ToLower(from.as)] = true
-			}
 
 			// If `from` references a previous stage, then the `workdir`
 			// was already changed.
@@ -324,15 +325,13 @@ func expandOnbuildInstructions(nodes []*parser.Node, cfg Config) ([]*parser.Node
 			expandedNodes = append(expandedNodes, nodes[n:m+1]...)
 			n = m + 1
 
-			if from.image == "" {
-				// some build args like artifact dependencies are not available until the first build sequence has completed.
-				// skip check if there are unavailable images
-				continue
-			}
-
 			var onbuildNodes []*parser.Node
 			if ons, found := onbuildNodesCache[strings.ToLower(from.image)]; found {
 				onbuildNodes = ons
+			} else if from.image == "" {
+				// some build args like artifact dependencies are not available until the first build sequence has completed.
+				// skip check if there are unavailable images
+				onbuildNodes = []*parser.Node{}
 			} else if ons, err := parseOnbuild(from.image, cfg); err == nil {
 				onbuildNodes = ons
 			} else if warnMsg, ok := sErrors.IsOldImageManifestProblem(err); ok && warnMsg != "" {
diff --git a/pkg/skaffold/event/config.go b/pkg/skaffold/event/config.go
new file mode 100644
index 000000000..8adb50ec7
--- /dev/null
+++ b/pkg/skaffold/event/config.go
@@ -0,0 +1,29 @@
+/*
+Copyright 2021 The Skaffold Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package event
+
+import (
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
+)
+
+type Config interface {
+	GetKubeContext() string
+	AutoBuild() bool
+	AutoDeploy() bool
+	AutoSync() bool
+	GetPipelines() []latest.Pipeline
+}
diff --git a/pkg/skaffold/event/event.go b/pkg/skaffold/event/event.go
index 60ccec9bb..94b2b32c4 100644
--- a/pkg/skaffold/event/event.go
+++ b/pkg/skaffold/event/event.go
@@ -30,7 +30,6 @@ import (
 
 	sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/errors"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation"
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version"
 	"github.com/GoogleContainerTools/skaffold/proto/v1"
@@ -157,15 +156,15 @@ func (ev *eventHandler) forEachEvent(callback func(*proto.LogEntry) error) error
 	return <-listener.errors
 }
 
-func emptyState(pipelines []latest.Pipeline, kubeContext string, autoBuild, autoDeploy, autoSync bool) proto.State {
+func emptyState(cfg Config) proto.State {
 	builds := map[string]string{}
-	for _, p := range pipelines {
+	for _, p := range cfg.GetPipelines() {
 		for _, a := range p.Build.Artifacts {
 			builds[a.ImageName] = NotStarted
 		}
 	}
-	metadata := initializeMetadata(pipelines, kubeContext)
-	return emptyStateWithArtifacts(builds, metadata, autoBuild, autoDeploy, autoSync)
+	metadata := initializeMetadata(cfg.GetPipelines(), cfg.GetKubeContext())
+	return emptyStateWithArtifacts(builds, metadata, cfg.AutoBuild(), cfg.AutoDeploy(), cfg.AutoSync())
 }
 
 func emptyStateWithArtifacts(builds map[string]string, metadata *proto.Metadata, autoBuild, autoDeploy, autoSync bool) proto.State {
@@ -191,8 +190,8 @@ func emptyStateWithArtifacts(builds map[string]string, metadata *proto.Metadata,
 }
 
 // InitializeState instantiates the global state of the skaffold runner, as well as the event log.
-func InitializeState(c []latest.Pipeline, kc string, autoBuild, autoDeploy, autoSync bool) {
-	handler.setState(emptyState(c, kc, autoBuild, autoDeploy, autoSync))
+func InitializeState(cfg Config) {
+	handler.setState(emptyState(cfg))
 }
 
 // DeployInProgress notifies that a deployment has been started.
diff --git a/pkg/skaffold/runner/new.go b/pkg/skaffold/runner/new.go
index 0b049d804..c9913fc13 100644
--- a/pkg/skaffold/runner/new.go
+++ b/pkg/skaffold/runner/new.go
@@ -48,7 +48,7 @@ import (
 
 // NewForConfig returns a new SkaffoldRunner for a SkaffoldConfig
 func NewForConfig(runCtx *runcontext.RunContext) (*SkaffoldRunner, error) {
-	event.InitializeState(runCtx.GetPipelines(), runCtx.GetKubeContext(), runCtx.AutoBuild(), runCtx.AutoDeploy(), runCtx.AutoSync())
+	event.InitializeState(runCtx)
 	event.LogMetaEvent()
 	kubectlCLI := pkgkubectl.NewCLI(runCtx, "")
 
diff --git a/pkg/skaffold/tags/paths.go b/pkg/skaffold/tags/paths.go
index 6255b1f15..255a3ac73 100644
--- a/pkg/skaffold/tags/paths.go
+++ b/pkg/skaffold/tags/paths.go
@@ -64,14 +64,23 @@ func makeFilePathsAbsolute(config interface{}, base string) []error {
 					v.SetString(filepath.Join(base, path))
 					logrus.Tracef("setting absolute path for config field %q", f.Name)
 				case []string:
-					for i := 0; i < v.Len(); i++ {
-						elem := v.Index(i)
+					for j := 0; j < v.Len(); j++ {
+						elem := v.Index(j)
 						path := elem.String()
 						if path == "" || filepath.IsAbs(path) {
 							continue
 						}
 						elem.SetString(filepath.Join(base, path))
-						logrus.Tracef("setting absolute paths for config field %q index %d", f.Name, i)
+						logrus.Tracef("setting absolute paths for config field %q index %d", f.Name, j)
+					}
+				case map[string]string:
+					for _, key := range v.MapKeys() {
+						path := v.MapIndex(key).String()
+						if path == "" || filepath.IsAbs(path) {
+							continue
+						}
+						v.SetMapIndex(key, reflect.ValueOf(filepath.Join(base, path)))
+						logrus.Tracef("setting absolute paths for config field %q key %q", f.Name, key.String())
 					}
 				default:
 					return []error{fmt.Errorf("yaml tag `filepath` needs struct field %q to be string or string slice", f.Name)}


Test patch:
diff --git a/pkg/skaffold/build/local/local_test.go b/pkg/skaffold/build/local/local_test.go
index c3c017bcc..1853553fe 100644
--- a/pkg/skaffold/build/local/local_test.go
+++ b/pkg/skaffold/build/local/local_test.go
@@ -34,12 +34,12 @@ import (
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/warnings"
 	"github.com/GoogleContainerTools/skaffold/testutil"
+	testEvent "github.com/GoogleContainerTools/skaffold/testutil/event"
 )
 
 type testAuthHelper struct{}
@@ -229,13 +229,13 @@ func TestLocalRun(t *testing.T) {
 			t.Override(&docker.EvalBuildArgs, func(_ config.RunMode, _ string, _ string, args map[string]*string, _ map[string]*string) (map[string]*string, error) {
 				return args, nil
 			})
-			event.InitializeState([]latest.Pipeline{{
+			testEvent.InitializeState([]latest.Pipeline{{
 				Deploy: latest.DeployConfig{},
 				Build: latest.BuildConfig{
 					BuildType: latest.BuildType{
 						LocalBuild: &latest.LocalBuild{},
 					},
-				}}}, "", true, true, true)
+				}}})
 
 			builder, err := NewBuilder(&mockConfig{},
 				&latest.LocalBuild{
diff --git a/pkg/skaffold/build/scheduler_test.go b/pkg/skaffold/build/scheduler_test.go
index f93e637ab..fe0e5152e 100644
--- a/pkg/skaffold/build/scheduler_test.go
+++ b/pkg/skaffold/build/scheduler_test.go
@@ -30,9 +30,9 @@ import (
 	"github.com/google/go-cmp/cmp"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	"github.com/GoogleContainerTools/skaffold/testutil"
+	testEvent "github.com/GoogleContainerTools/skaffold/testutil/event"
 )
 
 func TestGetBuild(t *testing.T) {
@@ -393,7 +393,7 @@ func initializeEvents() {
 			},
 		},
 	}}
-	event.InitializeState(pipes, "temp", true, true, true)
+	testEvent.InitializeState(pipes)
 }
 
 func errorsComparer(a, b error) bool {
diff --git a/pkg/skaffold/deploy/status/status_check_test.go b/pkg/skaffold/deploy/status/status_check_test.go
index 4a176cb99..081db5f4b 100644
--- a/pkg/skaffold/deploy/status/status_check_test.go
+++ b/pkg/skaffold/deploy/status/status_check_test.go
@@ -36,12 +36,12 @@ import (
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/label"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/resource"
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
 	"github.com/GoogleContainerTools/skaffold/proto/v1"
 	"github.com/GoogleContainerTools/skaffold/testutil"
+	testEvent "github.com/GoogleContainerTools/skaffold/testutil/event"
 )
 
 func TestGetDeployments(t *testing.T) {
@@ -293,7 +293,7 @@ func TestGetDeployStatus(t *testing.T) {
 
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
-			event.InitializeState([]latest.Pipeline{{}}, "test", true, true, true)
+			testEvent.InitializeState([]latest.Pipeline{{}})
 			errCode, err := getSkaffoldDeployStatus(test.counter, test.deployments)
 			t.CheckError(test.shouldErr, err)
 			if test.shouldErr {
@@ -370,7 +370,7 @@ func TestPrintSummaryStatus(t *testing.T) {
 			out := new(bytes.Buffer)
 			rc := newCounter(10)
 			rc.pending = test.pending
-			event.InitializeState([]latest.Pipeline{{}}, "test", true, true, true)
+			testEvent.InitializeState([]latest.Pipeline{{}})
 			r := withStatus(resource.NewDeployment(test.deployment, test.namespace, 0), test.ae)
 			// report status once and set it changed to false.
 			r.ReportSinceLastUpdated(false)
@@ -460,7 +460,7 @@ func TestPrintStatus(t *testing.T) {
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
 			out := new(bytes.Buffer)
-			event.InitializeState([]latest.Pipeline{{}}, "test", true, true, true)
+			testEvent.InitializeState([]latest.Pipeline{{}})
 			checker := statusChecker{labeller: labeller}
 			actual := checker.printStatus(test.rs, out)
 			t.CheckDeepEqual(test.expectedOut, out.String())
@@ -591,7 +591,7 @@ func TestPollDeployment(t *testing.T) {
 		testutil.Run(t, test.description, func(t *testutil.T) {
 			t.Override(&util.DefaultExecCommand, test.command)
 			t.Override(&defaultPollPeriodInMilliseconds, 100)
-			event.InitializeState([]latest.Pipeline{{}}, "test", true, true, true)
+			testEvent.InitializeState([]latest.Pipeline{{}})
 			mockVal := mockValidator{runs: test.runs}
 			dep := test.dep.WithValidator(mockVal)
 
diff --git a/pkg/skaffold/docker/dependencies_test.go b/pkg/skaffold/docker/dependencies_test.go
index eefe09a88..73ca27d55 100644
--- a/pkg/skaffold/docker/dependencies_test.go
+++ b/pkg/skaffold/docker/dependencies_test.go
@@ -63,6 +63,21 @@ FROM nginx
 ADD *.go *.none /tmp/
 `
 
+const simpleArtifactDependency = `
+ARG BASE
+FROM $BASE
+COPY worker.go .
+`
+
+const multiStageArtifactDependency = `
+ARG BASE
+FROM golang:1.9.2
+COPY worker.go .
+
+FROM $BASE AS foo
+FROM foo as bar
+`
+
 const multiStageDockerfile1 = `
 FROM golang:1.9.2
 WORKDIR /go/src/github.com/r2d4/leeroy/
@@ -325,6 +340,18 @@ func TestGetDependencies(t *testing.T) {
 			workspace:   "",
 			expected:    []string{"Dockerfile", "server.go", "worker.go"},
 		},
+		{
+			description: "simple dockerfile with artifact dependency",
+			dockerfile:  simpleArtifactDependency,
+			workspace:   "",
+			expected:    []string{"Dockerfile", "worker.go"},
+		},
+		{
+			description: "multistage dockerfile with artifact dependency",
+			dockerfile:  multiStageArtifactDependency,
+			workspace:   "",
+			expected:    []string{"Dockerfile", "worker.go"},
+		},
 		{
 			description: "copy twice",
 			dockerfile:  multiCopy,
diff --git a/pkg/skaffold/event/event_test.go b/pkg/skaffold/event/event_test.go
index 41597647d..09b098a2b 100644
--- a/pkg/skaffold/event/event_test.go
+++ b/pkg/skaffold/event/event_test.go
@@ -67,7 +67,7 @@ func TestGetLogEvents(t *testing.T) {
 
 func TestGetState(t *testing.T) {
 	ev := newHandler()
-	ev.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	ev.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	ev.stateLock.Lock()
 	ev.state.BuildState.Artifacts["img"] = Complete
@@ -82,7 +82,7 @@ func TestDeployInProgress(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().DeployState.Status == NotStarted })
 	DeployInProgress()
@@ -93,7 +93,7 @@ func TestDeployFailed(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().DeployState.Status == NotStarted })
 	DeployFailed(errors.New("BUG"))
@@ -107,7 +107,7 @@ func TestDeployComplete(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().DeployState.Status == NotStarted })
 	DeployComplete()
@@ -121,11 +121,11 @@ func TestBuildInProgress(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{Build: latest.BuildConfig{
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{Build: latest.BuildConfig{
 		Artifacts: []*latest.Artifact{{
 			ImageName: "img",
 		}},
-	}}}, "test", true, true, true)
+	}}}, "test"))
 
 	wait(t, func() bool { return handler.getState().BuildState.Artifacts["img"] == NotStarted })
 	BuildInProgress("img")
@@ -136,11 +136,11 @@ func TestBuildFailed(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{Build: latest.BuildConfig{
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{Build: latest.BuildConfig{
 		Artifacts: []*latest.Artifact{{
 			ImageName: "img",
 		}},
-	}}}, "test", true, true, true)
+	}}}, "test"))
 
 	wait(t, func() bool { return handler.getState().BuildState.Artifacts["img"] == NotStarted })
 	BuildFailed("img", errors.New("BUG"))
@@ -154,11 +154,11 @@ func TestBuildComplete(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{Build: latest.BuildConfig{
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{Build: latest.BuildConfig{
 		Artifacts: []*latest.Artifact{{
 			ImageName: "img",
 		}},
-	}}}, "test", true, true, true)
+	}}}, "test"))
 
 	wait(t, func() bool { return handler.getState().BuildState.Artifacts["img"] == NotStarted })
 	BuildComplete("img")
@@ -169,7 +169,7 @@ func TestPortForwarded(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().ForwardedPorts[8080] == nil })
 	PortForwarded(8080, schemautil.FromInt(8888), "pod", "container", "ns", "portname", "resourceType", "resourceName", "127.0.0.1")
@@ -186,7 +186,7 @@ func TestStatusCheckEventStarted(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().StatusCheckState.Status == NotStarted })
 	StatusCheckEventStarted()
@@ -197,7 +197,7 @@ func TestStatusCheckEventInProgress(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().StatusCheckState.Status == NotStarted })
 	StatusCheckEventInProgress("[2/5 deployment(s) are still pending]")
@@ -208,7 +208,7 @@ func TestStatusCheckEventSucceeded(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().StatusCheckState.Status == NotStarted })
 	statusCheckEventSucceeded()
@@ -219,7 +219,7 @@ func TestStatusCheckEventFailed(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().StatusCheckState.Status == NotStarted })
 	StatusCheckEventEnded(proto.StatusCode_STATUSCHECK_FAILED_SCHEDULING, errors.New("one or more deployments failed"))
@@ -233,7 +233,7 @@ func TestResourceStatusCheckEventUpdated(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().StatusCheckState.Status == NotStarted })
 	ResourceStatusCheckEventUpdated("ns:pod/foo", proto.ActionableErr{
@@ -247,7 +247,7 @@ func TestResourceStatusCheckEventSucceeded(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().StatusCheckState.Status == NotStarted })
 	resourceStatusCheckEventSucceeded("ns:pod/foo")
@@ -258,7 +258,7 @@ func TestResourceStatusCheckEventFailed(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().StatusCheckState.Status == NotStarted })
 	resourceStatusCheckEventFailed("ns:pod/foo", proto.ActionableErr{
@@ -272,7 +272,7 @@ func TestFileSyncInProgress(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().FileSyncState.Status == NotStarted })
 	FileSyncInProgress(5, "image")
@@ -283,7 +283,7 @@ func TestFileSyncFailed(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().FileSyncState.Status == NotStarted })
 	FileSyncFailed(5, "image", errors.New("BUG"))
@@ -294,7 +294,7 @@ func TestFileSyncSucceeded(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	wait(t, func() bool { return handler.getState().FileSyncState.Status == NotStarted })
 	FileSyncSucceeded(5, "image")
@@ -305,7 +305,7 @@ func TestDebuggingContainer(t *testing.T) {
 	defer func() { handler = newHandler() }()
 
 	handler = newHandler()
-	handler.state = emptyState([]latest.Pipeline{{}}, "test", true, true, true)
+	handler.state = emptyState(mockCfg([]latest.Pipeline{{}}, "test"))
 
 	found := func() bool {
 		for _, dc := range handler.getState().DebuggingContainers {
@@ -598,3 +598,21 @@ func TestSaveEventsToFile(t *testing.T) {
 	testutil.CheckDeepEqual(t, 1, buildCompleteEvent)
 	testutil.CheckDeepEqual(t, 1, devLoopCompleteEvent)
 }
+
+type config struct {
+	pipes   []latest.Pipeline
+	kubectx string
+}
+
+func (c config) GetKubeContext() string          { return c.kubectx }
+func (c config) AutoBuild() bool                 { return true }
+func (c config) AutoDeploy() bool                { return true }
+func (c config) AutoSync() bool                  { return true }
+func (c config) GetPipelines() []latest.Pipeline { return c.pipes }
+
+func mockCfg(pipes []latest.Pipeline, kubectx string) config {
+	return config{
+		pipes:   pipes,
+		kubectx: kubectx,
+	}
+}
diff --git a/pkg/skaffold/event/util_test.go b/pkg/skaffold/event/util_test.go
index eb3ac5be8..303124d07 100644
--- a/pkg/skaffold/event/util_test.go
+++ b/pkg/skaffold/event/util_test.go
@@ -135,7 +135,7 @@ func TestEmptyState(t *testing.T) {
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
 			handler = &eventHandler{
-				state: emptyState([]latest.Pipeline{test.cfg}, test.cluster, true, true, true),
+				state: emptyState(mockCfg([]latest.Pipeline{test.cfg}, test.cluster)),
 			}
 			metadata := handler.state.Metadata
 			builders := metadata.Build.Builders
diff --git a/pkg/skaffold/kubernetes/portforward/entry_manager_test.go b/pkg/skaffold/kubernetes/portforward/entry_manager_test.go
index 9e1a08211..be78191bb 100644
--- a/pkg/skaffold/kubernetes/portforward/entry_manager_test.go
+++ b/pkg/skaffold/kubernetes/portforward/entry_manager_test.go
@@ -22,13 +22,13 @@ import (
 	"testing"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	"github.com/GoogleContainerTools/skaffold/testutil"
+	testEvent "github.com/GoogleContainerTools/skaffold/testutil/event"
 )
 
 func TestStop(t *testing.T) {
-	event.InitializeState([]latest.Pipeline{{}}, "test", true, true, true)
+	testEvent.InitializeState([]latest.Pipeline{{}})
 
 	pfe1 := newPortForwardEntry(0, latest.PortForwardResource{
 		Type:      constants.Pod,
diff --git a/pkg/skaffold/kubernetes/portforward/pod_forwarder_test.go b/pkg/skaffold/kubernetes/portforward/pod_forwarder_test.go
index 3a29e3a9d..91e184831 100644
--- a/pkg/skaffold/kubernetes/portforward/pod_forwarder_test.go
+++ b/pkg/skaffold/kubernetes/portforward/pod_forwarder_test.go
@@ -28,11 +28,11 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/apimachinery/pkg/watch"
 
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	schemautil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
 	"github.com/GoogleContainerTools/skaffold/testutil"
+	testEvent "github.com/GoogleContainerTools/skaffold/testutil/event"
 )
 
 func TestAutomaticPortForwardPod(t *testing.T) {
@@ -401,7 +401,7 @@ func TestAutomaticPortForwardPod(t *testing.T) {
 	}
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
-			event.InitializeState([]latest.Pipeline{{}}, "test", true, true, true)
+			testEvent.InitializeState([]latest.Pipeline{{}})
 			taken := map[int]struct{}{}
 			t.Override(&retrieveAvailablePort, mockRetrieveAvailablePort("127.0.0.1", taken, test.availablePorts))
 			t.Override(&topLevelOwnerKey, func(context.Context, metav1.Object, string) string { return "owner" })
@@ -474,7 +474,7 @@ func TestStartPodForwarder(t *testing.T) {
 
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
-			event.InitializeState([]latest.Pipeline{{}}, "", true, true, true)
+			testEvent.InitializeState([]latest.Pipeline{{}})
 			t.Override(&topLevelOwnerKey, func(context.Context, metav1.Object, string) string { return "owner" })
 			t.Override(&newPodWatcher, func(kubernetes.PodSelector, []string) kubernetes.PodWatcher {
 				return &fakePodWatcher{
diff --git a/pkg/skaffold/kubernetes/portforward/resource_forwarder_test.go b/pkg/skaffold/kubernetes/portforward/resource_forwarder_test.go
index d11e55c1e..8f1bccc99 100644
--- a/pkg/skaffold/kubernetes/portforward/resource_forwarder_test.go
+++ b/pkg/skaffold/kubernetes/portforward/resource_forwarder_test.go
@@ -34,12 +34,12 @@ import (
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/label"
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
 	kubernetesclient "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/client"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	schemautil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
 	"github.com/GoogleContainerTools/skaffold/testutil"
+	testEvent "github.com/GoogleContainerTools/skaffold/testutil/event"
 )
 
 type testForwarder struct {
@@ -121,7 +121,7 @@ func TestStart(t *testing.T) {
 	}
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
-			event.InitializeState([]latest.Pipeline{{}}, "", true, true, true)
+			testEvent.InitializeState([]latest.Pipeline{{}})
 			t.Override(&retrieveAvailablePort, mockRetrieveAvailablePort("127.0.0.1", map[int]struct{}{}, test.availablePorts))
 			t.Override(&retrieveServices, func(context.Context, string, []string) ([]*latest.PortForwardResource, error) {
 				return test.resources, nil
@@ -263,7 +263,7 @@ func TestUserDefinedResources(t *testing.T) {
 
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
-			event.InitializeState([]latest.Pipeline{{}}, "", true, true, true)
+			testEvent.InitializeState([]latest.Pipeline{{}})
 			t.Override(&retrieveAvailablePort, mockRetrieveAvailablePort("127.0.0.1", map[int]struct{}{}, []int{8080, 9000}))
 			t.Override(&retrieveServices, func(context.Context, string, []string) ([]*latest.PortForwardResource, error) {
 				return []*latest.PortForwardResource{svc}, nil
diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go
index b7e1a9582..3f5a56cbf 100644
--- a/pkg/skaffold/schema/latest/config.go
+++ b/pkg/skaffold/schema/latest/config.go
@@ -711,7 +711,7 @@ type HelmRelease struct {
 
 	// SetFiles are key-value pairs.
 	// If present, Skaffold will send `--set-file` flag to Helm CLI and append all pairs after the flag.
-	SetFiles map[string]string `yaml:"setFiles,omitempty"`
+	SetFiles map[string]string `yaml:"setFiles,omitempty" skaffold:"filepath"`
 
 	// CreateNamespace if `true`, Skaffold will send `--create-namespace` flag to Helm CLI.
 	// `--create-namespace` flag is available in Helm since version 3.2.
diff --git a/pkg/skaffold/tags/paths_test.go b/pkg/skaffold/tags/paths_test.go
index 263760f96..8499bd940 100644
--- a/pkg/skaffold/tags/paths_test.go
+++ b/pkg/skaffold/tags/paths_test.go
@@ -47,8 +47,8 @@ func TestSetAbsFilePaths(t *testing.T) {
 							KptDeploy:     &latest.KptDeploy{Dir: "."},
 							KubectlDeploy: &latest.KubectlDeploy{Manifests: []string{"foo/*", "/a/foo/*"}},
 							HelmDeploy: &latest.HelmDeploy{Releases: []latest.HelmRelease{
-								{ChartPath: "../charts", ValuesFiles: []string{"./values1.yaml", "./values2.yaml"}},
-								{RemoteChart: "foo/bar", ValuesFiles: []string{"./values1.yaml", "./values2.yaml"}},
+								{ChartPath: "../charts", ValuesFiles: []string{"./values1.yaml", "./values2.yaml"}, SetFiles: map[string]string{"envFile": "./values3.yaml", "configFile": "./values4.yaml", "anotherFile": "/c/values5.yaml"}},
+								{RemoteChart: "foo/bar", ValuesFiles: []string{"./values1.yaml", "./values2.yaml"}, SetFiles: map[string]string{"envFile": "./values3.yaml", "configFile": "./values4.yaml", "anotherFile": "/c/values5.yaml"}},
 							}},
 						},
 					},
@@ -68,8 +68,8 @@ func TestSetAbsFilePaths(t *testing.T) {
 							KptDeploy:     &latest.KptDeploy{Dir: "/a/b"},
 							KubectlDeploy: &latest.KubectlDeploy{Manifests: []string{"/a/b/foo/*", "/a/foo/*"}},
 							HelmDeploy: &latest.HelmDeploy{Releases: []latest.HelmRelease{
-								{ChartPath: "/a/charts", ValuesFiles: []string{"/a/b/values1.yaml", "/a/b/values2.yaml"}},
-								{RemoteChart: "foo/bar", ValuesFiles: []string{"/a/b/values1.yaml", "/a/b/values2.yaml"}},
+								{ChartPath: "/a/charts", ValuesFiles: []string{"/a/b/values1.yaml", "/a/b/values2.yaml"}, SetFiles: map[string]string{"envFile": "/a/b/values3.yaml", "configFile": "/a/b/values4.yaml", "anotherFile": "/c/values5.yaml"}},
+								{RemoteChart: "foo/bar", ValuesFiles: []string{"/a/b/values1.yaml", "/a/b/values2.yaml"}, SetFiles: map[string]string{"envFile": "/a/b/values3.yaml", "configFile": "/a/b/values4.yaml", "anotherFile": "/c/values5.yaml"}},
 							}},
 						},
 					},
diff --git a/pkg/skaffold/tags/paths_windows_test.go b/pkg/skaffold/tags/paths_windows_test.go
index 8fb2c6d27..81919d091 100644
--- a/pkg/skaffold/tags/paths_windows_test.go
+++ b/pkg/skaffold/tags/paths_windows_test.go
@@ -44,6 +44,10 @@ func TestSetAbsFilePaths(t *testing.T) {
 						DeployType: latest.DeployType{
 							KptDeploy:     &latest.KptDeploy{Dir: "."},
 							KubectlDeploy: &latest.KubectlDeploy{Manifests: []string{`foo\*`, `C:\a\foo\*`}},
+							HelmDeploy: &latest.HelmDeploy{Releases: []latest.HelmRelease{
+								{ChartPath: `..\charts`, ValuesFiles: []string{"values1.yaml", "values2.yaml"}, SetFiles: map[string]string{"envFile": "values3.yaml", "configFile": "values4.yaml", "anotherFile": `C:\c\values5.yaml`}},
+								{RemoteChart: "foo/bar", ValuesFiles: []string{"values1.yaml", "values2.yaml"}, SetFiles: map[string]string{"envFile": "values3.yaml", "configFile": "values4.yaml", "anotherFile": `C:\c\values5.yaml`}},
+							}},
 						},
 					},
 				},
@@ -61,6 +65,10 @@ func TestSetAbsFilePaths(t *testing.T) {
 						DeployType: latest.DeployType{
 							KptDeploy:     &latest.KptDeploy{Dir: `C:\a\b`},
 							KubectlDeploy: &latest.KubectlDeploy{Manifests: []string{`C:\a\b\foo\*`, `C:\a\foo\*`}},
+							HelmDeploy: &latest.HelmDeploy{Releases: []latest.HelmRelease{
+								{ChartPath: `C:\a\charts`, ValuesFiles: []string{`C:\a\b\values1.yaml`, `C:\a\b\values2.yaml`}, SetFiles: map[string]string{"envFile": `C:\a\b\values3.yaml`, "configFile": `C:\a\b\values4.yaml`, "anotherFile": `C:\c\values5.yaml`}},
+								{RemoteChart: "foo/bar", ValuesFiles: []string{`C:\a\b\values1.yaml`, `C:\a\b\values2.yaml`}, SetFiles: map[string]string{"envFile": `C:\a\b\values3.yaml`, "configFile": `C:\a\b\values4.yaml`, "anotherFile": `C:\c\values5.yaml`}},
+							}},
 						},
 					},
 				},
diff --git a/testutil/event/config.go b/testutil/event/config.go
new file mode 100644
index 000000000..1c313baa3
--- /dev/null
+++ b/testutil/event/config.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2021 The Skaffold Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package event
+
+import (
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
+)
+
+func InitializeState(pipes []latest.Pipeline) {
+	cfg := config{
+		pipes: pipes,
+	}
+	event.InitializeState(cfg)
+}
+
+type config struct {
+	pipes []latest.Pipeline
+}
+
+func (c config) AutoBuild() bool                 { return true }
+func (c config) AutoDeploy() bool                { return true }
+func (c config) AutoSync() bool                  { return true }
+func (c config) GetPipelines() []latest.Pipeline { return c.pipes }
+func (c config) GetKubeContext() string          { return "temp" }