Repository: GoogleContainerTools/skaffold (Go)

Issue:
artifact caching enables deploying with bad image?
Actual behavior with Repro: 
1. skaffold dev on getting-started example 
2. break getting started with compilation error  
---> nice error message, and no log tailing, "Watching for changes...". cool. 
3. delete Dockerfile...
---> returns to logging and deploys an old version? 

Expected: 
3. should result in a build error and not log tailing / deployment

<details>

```
skaffold dev                                                           1013ms  Fri Apr 10 17:00:41 2020
Listing files to watch...
 - skaffold-example
Generating tags...
 - skaffold-example -> skaffold-example:v1.7.0-26-gbd0eb2f56
Checking cache...
 - skaffold-example: Found Locally
Tags used in deployment:
 - skaffold-example -> skaffold-example:5107b3a279c92b348665b9e9fba60d59e9e2378423bfa0b4cb828db9eec950a3
   local images can't be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
 - pod/getting-started created
Waiting for deployments to stabilize...
Deployments stabilized in 129.172454ms
Watching for changes...
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
Generating tags...
 - skaffold-example -> skaffold-example:v1.7.0-26-gbd0eb2f56-dirty
Checking cache...
 - skaffold-example: Not found. Building
Found [minikube] context, using local docker daemon.
Building [skaffold-example]...
Sending build context to Docker daemon  3.072kB
Step 1/6 : FROM golang:1.12.9-alpine3.10 as builder
 ---> e0d646523991
Step 2/6 : COPY main.go .
 ---> f187df952d41
Step 3/6 : RUN go build -o /app main.go
 ---> Running in bf50faab1c64
# command-line-arguments
./main.go:9:3: undefined: mt
WARN[0016] Skipping deploy due to error: build failed: building [skaffold-example]: build artifact: unable to stream build output: The command '/bin/sh -c go build -o /app main.go' returned a non-zero code: 2 
Watching for changes...
WARN[0022] error computing file changes: listing files: listing files: opening dockerfile "/Users/balintp/skaffold/examples/getting-started/Dockerfile": open /Users/balintp/skaffold/examples/getting-started/Dockerfile: no such file or directory 
WARN[0022] skaffold may not run successfully!           
Tags used in deployment:
 - skaffold-example -> skaffold-example:5107b3a279c92b348665b9e9fba60d59e9e2378423bfa0b4cb828db9eec950a3
   local images can't be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
 - pod/getting-started configured
Waiting for deployments to stabilize...
Deployments stabilized in 114.956245ms
Watching for changes...
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
^CCleaning up...
 - pod "getting-started" deleted

```

</details>


Discussion:

Patch:
diff --git a/pkg/skaffold/docker/parse.go b/pkg/skaffold/docker/parse.go
index faa4fa82ae7..90dcd46462a 100644
--- a/pkg/skaffold/docker/parse.go
+++ b/pkg/skaffold/docker/parse.go
@@ -66,7 +66,7 @@ var (
 func readCopyCmdsFromDockerfile(onlyLastImage bool, absDockerfilePath, workspace string, buildArgs map[string]*string, insecureRegistries map[string]bool) ([]fromTo, error) {
 	f, err := os.Open(absDockerfilePath)
 	if err != nil {
-		return nil, fmt.Errorf("opening dockerfile %q: %w", absDockerfilePath, err)
+		return nil, err
 	}
 	defer f.Close()
 
diff --git a/pkg/skaffold/filemon/monitor.go b/pkg/skaffold/filemon/monitor.go
index 42129356524..bd3cfd9efa5 100644
--- a/pkg/skaffold/filemon/monitor.go
+++ b/pkg/skaffold/filemon/monitor.go
@@ -16,8 +16,6 @@ limitations under the License.
 
 package filemon
 
-import "fmt"
-
 // Monitor monitors files changes for multiples components.
 type Monitor interface {
 	Register(deps func() ([]string, error), onChange func(Events)) error
@@ -48,7 +46,7 @@ type component struct {
 func (w *watchList) Register(deps func() ([]string, error), onChange func(Events)) error {
 	state, err := Stat(deps)
 	if err != nil {
-		return fmt.Errorf("listing files: %w", err)
+		return err
 	}
 
 	w.components = append(w.components, &component{
@@ -69,7 +67,7 @@ func (w *watchList) Run(debounce bool) error {
 	for i, component := range w.components {
 		state, err := Stat(component.deps)
 		if err != nil {
-			return fmt.Errorf("listing files: %w", err)
+			return err
 		}
 		e := events(component.state, state)
 
diff --git a/pkg/skaffold/runner/listen.go b/pkg/skaffold/runner/listen.go
index 011efa71a5f..44133440fb3 100644
--- a/pkg/skaffold/runner/listen.go
+++ b/pkg/skaffold/runner/listen.go
@@ -78,9 +78,10 @@ func (l *SkaffoldListener) WatchForChanges(ctx context.Context, out io.Writer, d
 
 func (l *SkaffoldListener) do(ctx context.Context, out io.Writer, devLoop func(context.Context, io.Writer) error) error {
 	if err := l.Monitor.Run(l.Trigger.Debounce()); err != nil {
-		logrus.Warnf("error computing file changes: %s", err.Error())
-		logrus.Warnf("skaffold may not run successfully!")
+		logrus.Warnf("Ignoring changes: %s", err.Error())
+		return nil
 	}
+
 	if err := devLoop(ctx, out); err != nil {
 		// propagating this error up causes a new runner to be created
 		// and a new dev loop to start
@@ -89,5 +90,6 @@ func (l *SkaffoldListener) do(ctx context.Context, out io.Writer, devLoop func(c
 		}
 		logrus.Errorf("error running dev loop: %s", err.Error())
 	}
+
 	return nil
 }


Test patch:
diff --git a/pkg/skaffold/runner/listen_test.go b/pkg/skaffold/runner/listen_test.go
new file mode 100644
index 00000000000..253546ae69a
--- /dev/null
+++ b/pkg/skaffold/runner/listen_test.go
@@ -0,0 +1,98 @@
+/*
+Copyright 2020 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 runner
+
+import (
+	"context"
+	"errors"
+	"io"
+	"io/ioutil"
+	"testing"
+
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/filemon"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/trigger"
+	"github.com/GoogleContainerTools/skaffold/testutil"
+)
+
+// errMonitor is a filemon.Monitor that always fail to run.
+type errMonitor struct {
+	filemon.Monitor
+}
+
+func (f *errMonitor) Run(debounce bool) error {
+	return errors.New("BUG")
+}
+
+// fakeMonitor is a filemon.Monitor that always succeeds.
+type fakeMonitor struct {
+	filemon.Monitor
+}
+
+func (f *fakeMonitor) Run(debounce bool) error {
+	return nil
+}
+
+type fakeTriggger struct {
+	trigger.Trigger
+}
+
+func (f *fakeTriggger) Debounce() bool {
+	return false
+}
+
+func TestSkipDevLoopOnMonitorError(t *testing.T) {
+	listener := &SkaffoldListener{
+		Monitor: &errMonitor{},
+		Trigger: &fakeTriggger{},
+	}
+
+	var devLoopWasCalled bool
+	err := listener.do(context.Background(), ioutil.Discard, func(context.Context, io.Writer) error {
+		devLoopWasCalled = true
+		return nil
+	})
+
+	testutil.CheckErrorAndDeepEqual(t, false, err, false, devLoopWasCalled)
+}
+
+func TestContinueOnDevLoopError(t *testing.T) {
+	listener := &SkaffoldListener{
+		Monitor: &fakeMonitor{},
+		Trigger: &fakeTriggger{},
+	}
+
+	err := listener.do(context.Background(), ioutil.Discard, func(context.Context, io.Writer) error {
+		return errors.New("devloop error")
+	})
+
+	testutil.CheckError(t, false, err)
+}
+
+func TestReportDevLoopError(t *testing.T) {
+	listener := &SkaffoldListener{
+		Monitor: &fakeMonitor{},
+		Trigger: &fakeTriggger{},
+	}
+
+	err := listener.do(context.Background(), ioutil.Discard, func(context.Context, io.Writer) error {
+		return ErrorConfigurationChanged
+	})
+
+	if err != ErrorConfigurationChanged {
+		t.Fatalf("should have returned a ErrorConfigurationChanged error, returned %v", err)
+	}
+}