Skip to content

Commit 3402a94

Browse files
authored
Merge pull request #1439 from ltouati/fsnotify
Implement a notification based watcher
2 parents 3795677 + 1c5afbd commit 3402a94

File tree

88 files changed

+7336
-60
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+7336
-60
lines changed

Gopkg.lock

+296-38
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

+4
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,7 @@
6666
[[constraint]]
6767
name = "github.com/bmatcuk/doublestar"
6868
revision = "v1.1.1"
69+
70+
[[constraint]]
71+
name = "github.com/rjeczalik/notify"
72+
version = "0.9.2"

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ $(BUILD_DIR)/$(PROJECT): $(BUILD_DIR)/$(PROJECT)-$(GOOS)-$(GOARCH)
5555
cp $(BUILD_DIR)/$(PROJECT)-$(GOOS)-$(GOARCH) $@
5656

5757
$(BUILD_DIR)/$(PROJECT)-%-$(GOARCH): $(GO_FILES) $(BUILD_DIR)
58-
GOOS=$* GOARCH=$(GOARCH) CGO_ENABLED=0 go build -ldflags $(GO_LDFLAGS) -gcflags $(GO_GCFLAGS) -asmflags $(GO_ASMFLAGS) -o $@ $(BUILD_PACKAGE)
58+
GOOS=$* GOARCH=$(GOARCH) CGO_ENABLED=1 go build -ldflags $(GO_LDFLAGS) -gcflags $(GO_GCFLAGS) -asmflags $(GO_ASMFLAGS) -o $@ $(BUILD_PACKAGE)
5959

6060
%.sha256: %
6161
shasum -a 256 $< > $@
@@ -81,7 +81,7 @@ test:
8181

8282
.PHONY: install
8383
install: $(GO_FILES) $(BUILD_DIR)
84-
GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 go install -ldflags $(GO_LDFLAGS) -gcflags $(GO_GCFLAGS) -asmflags $(GO_ASMFLAGS) $(BUILD_PACKAGE)
84+
GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=1 go install -ldflags $(GO_LDFLAGS) -gcflags $(GO_GCFLAGS) -asmflags $(GO_ASMFLAGS) $(BUILD_PACKAGE)
8585

8686
.PHONY: integration
8787
integration: install $(BUILD_DIR)/$(PROJECT)

cmd/skaffold/app/cmd/dev.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func NewCmdDev(out io.Writer) *cobra.Command {
3838
}
3939
AddRunDevFlags(cmd)
4040
cmd.Flags().BoolVar(&opts.TailDev, "tail", true, "Stream logs from deployed objects")
41-
cmd.Flags().StringVar(&opts.Trigger, "trigger", "polling", "How are changes detected? (polling or manual)")
41+
cmd.Flags().StringVar(&opts.Trigger, "trigger", "polling", "How are changes detected? (polling, manual or notify)")
4242
cmd.Flags().BoolVar(&opts.Cleanup, "cleanup", true, "Delete deployments after dev mode is interrupted")
4343
cmd.Flags().StringArrayVarP(&opts.Watch, "watch-image", "w", nil, "Choose which artifacts to watch. Artifacts with image names that contain the expression will be watched only. Default is to watch sources for all artifacts")
4444
cmd.Flags().IntVarP(&opts.WatchPollInterval, "watch-poll-interval", "i", 1000, "Interval (in ms) between two checks for file changes")

docs/content/en/docs/references/cli/_index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ Flags:
213213
--skip-tests Whether to skip the tests after building
214214
--tail Stream logs from deployed objects (default true)
215215
--toot Emit a terminal beep after the deploy is complete
216-
--trigger string How are changes detected? (polling or manual) (default "polling")
216+
--trigger string How are changes detected? (polling, manual or notify) (default "polling")
217217
-w, --watch-image stringArray Choose which artifacts to watch. Artifacts with image names that contain the expression will be watched only. Default is to watch sources for all artifacts
218218
-i, --watch-poll-interval int Interval (in ms) between two checks for file changes (default 1000)
219219

hack/check-docs.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
cp docs/content/en/docs/references/cli/index_header docs/content/en/docs/references/cli/_index.md
1818
go run cmd/skaffold/man/man.go >> docs/content/en/docs/references/cli/_index.md
1919

20-
readonly CLI_CHANGES=`git status -s | grep "docs/" | wc -l`
20+
readonly CLI_CHANGES=`git status -s | grep "docs/" | grep -x vendor | wc -l`
2121

2222
if [ $CLI_CHANGES -gt 0 ]; then
2323
echo "You have skaffold command changes but haven't generated the CLI reference docs. Please run hack/check-docs.sh and commit the results!"
2424
exit 1
2525
fi
2626

27-
readonly DOCS_CHANGES=`git diff --name-status master | grep "docs/" | wc -l`
27+
readonly DOCS_CHANGES=`git diff --name-status master | grep "docs/" | grep -x vendor | wc -l`
2828

2929
if [ $DOCS_CHANGES -gt 0 ]; then
3030
echo "There are $DOCS_CHANGES changes in docs, testing site generation..."

pkg/skaffold/watch/triggers.go

+82-13
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,23 @@ package watch
1818

1919
import (
2020
"bufio"
21+
"context"
2122
"fmt"
2223
"io"
2324
"os"
2425
"strings"
26+
"sync/atomic"
2527
"time"
2628

2729
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
2830
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
31+
"github.com/rjeczalik/notify"
2932
"github.com/sirupsen/logrus"
3033
)
3134

3235
// Trigger describes a mechanism that triggers the watch.
3336
type Trigger interface {
34-
Start() (<-chan bool, func())
37+
Start(context.Context) (<-chan bool, error)
3538
WatchForChanges(io.Writer)
3639
Debounce() bool
3740
}
@@ -41,7 +44,11 @@ func NewTrigger(opts *config.SkaffoldOptions) (Trigger, error) {
4144
switch strings.ToLower(opts.Trigger) {
4245
case "polling":
4346
return &pollTrigger{
44-
Interval: time.Duration(opts.WatchPollInterval) * time.Millisecond,
47+
interval: time.Duration(opts.WatchPollInterval) * time.Millisecond,
48+
}, nil
49+
case "notify":
50+
return &fsNotifyTrigger{
51+
interval: time.Duration(opts.WatchPollInterval) * time.Millisecond,
4552
}, nil
4653
case "manual":
4754
return &manualTrigger{}, nil
@@ -52,7 +59,7 @@ func NewTrigger(opts *config.SkaffoldOptions) (Trigger, error) {
5259

5360
// pollTrigger watches for changes on a given interval of time.
5461
type pollTrigger struct {
55-
Interval time.Duration
62+
interval time.Duration
5663
}
5764

5865
// Debounce tells the watcher to debounce rapid sequence of changes.
@@ -61,27 +68,30 @@ func (t *pollTrigger) Debounce() bool {
6168
}
6269

6370
func (t *pollTrigger) WatchForChanges(out io.Writer) {
64-
color.Yellow.Fprintf(out, "Watching for changes every %v...\n", t.Interval)
71+
color.Yellow.Fprintf(out, "Watching for changes every %v...\n", t.interval)
6572
}
6673

6774
// Start starts a timer.
68-
func (t *pollTrigger) Start() (<-chan bool, func()) {
75+
func (t *pollTrigger) Start(ctx context.Context) (<-chan bool, error) {
6976
trigger := make(chan bool)
7077

71-
ticker := time.NewTicker(t.Interval)
78+
ticker := time.NewTicker(t.interval)
7279
go func() {
7380
for {
74-
<-ticker.C
75-
trigger <- true
81+
select {
82+
case <-ticker.C:
83+
trigger <- true
84+
case <-ctx.Done():
85+
ticker.Stop()
86+
}
7687
}
7788
}()
7889

79-
return trigger, ticker.Stop
90+
return trigger, nil
8091
}
8192

8293
// manualTrigger watches for changes when the user presses a key.
83-
type manualTrigger struct {
84-
}
94+
type manualTrigger struct{}
8595

8696
// Debounce tells the watcher to not debounce rapid sequence of changes.
8797
func (t *manualTrigger) Debounce() bool {
@@ -93,19 +103,78 @@ func (t *manualTrigger) WatchForChanges(out io.Writer) {
93103
}
94104

95105
// Start starts listening to pressed keys.
96-
func (t *manualTrigger) Start() (<-chan bool, func()) {
106+
func (t *manualTrigger) Start(ctx context.Context) (<-chan bool, error) {
97107
trigger := make(chan bool)
98108

109+
var stopped int32
110+
go func() {
111+
<-ctx.Done()
112+
atomic.StoreInt32(&stopped, 1)
113+
}()
114+
99115
reader := bufio.NewReader(os.Stdin)
100116
go func() {
101117
for {
102118
_, _, err := reader.ReadRune()
103119
if err != nil {
104120
logrus.Debugf("manual trigger error: %s", err)
105121
}
122+
123+
// Wait until the context is cancelled.
124+
if atomic.LoadInt32(&stopped) == 1 {
125+
return
126+
}
106127
trigger <- true
107128
}
108129
}()
109130

110-
return trigger, func() {}
131+
return trigger, nil
132+
}
133+
134+
// notifyTrigger watches for changes with fsnotify
135+
type fsNotifyTrigger struct {
136+
interval time.Duration
137+
}
138+
139+
// Debounce tells the watcher to not debounce rapid sequence of changes.
140+
func (t *fsNotifyTrigger) Debounce() bool {
141+
// This trigger has built-in debouncing.
142+
return false
143+
}
144+
145+
func (t *fsNotifyTrigger) WatchForChanges(out io.Writer) {
146+
color.Yellow.Fprintln(out, "Watching for changes...")
147+
}
148+
149+
// Start Listening for file system changes
150+
func (t *fsNotifyTrigger) Start(ctx context.Context) (<-chan bool, error) {
151+
// TODO(@dgageot): If file changes happen too quickly, events might be lost
152+
c := make(chan notify.EventInfo, 100)
153+
154+
// Watch current directory recursively
155+
if err := notify.Watch("./...", c, notify.All); err != nil {
156+
return nil, err
157+
}
158+
159+
trigger := make(chan bool)
160+
go func() {
161+
timer := time.NewTimer(1<<63 - 1) // Forever
162+
163+
for {
164+
select {
165+
case e := <-c:
166+
logrus.Debugln("Change detected", e)
167+
168+
// Wait t.interval before triggering.
169+
// This way, rapid stream of events will be grouped.
170+
timer.Reset(t.interval)
171+
case <-timer.C:
172+
trigger <- true
173+
case <-ctx.Done():
174+
timer.Stop()
175+
}
176+
}
177+
}()
178+
179+
return trigger, nil
111180
}

pkg/skaffold/watch/watch.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,13 @@ func (w *watchList) Register(deps func() ([]string, error), onChange func(Events
6868

6969
// Run watches files until the context is cancelled or an error occurs.
7070
func (w *watchList) Run(ctx context.Context, out io.Writer, onChange func() error) error {
71-
t, cleanup := w.trigger.Start()
72-
defer cleanup()
71+
ctxTrigger, cancelTrigger := context.WithCancel(ctx)
72+
defer cancelTrigger()
73+
74+
t, err := w.trigger.Start(ctxTrigger)
75+
if err != nil {
76+
return errors.Wrap(err, "unable to start trigger")
77+
}
7378

7479
changedComponents := map[int]bool{}
7580

pkg/skaffold/watch/watch_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func TestWatch(t *testing.T) {
7272

7373
// Watch folder
7474
watcher := NewWatcher(&pollTrigger{
75-
Interval: 10 * time.Millisecond,
75+
interval: 10 * time.Millisecond,
7676
})
7777
err := watcher.Register(folder.List, folderChanged.call)
7878
testutil.CheckError(t, false, err)

vendor/github.com/Microsoft/go-winio/archive/tar/LICENSE

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/bmatcuk/doublestar/test/b/symlink-dir

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/bmatcuk/doublestar/test/broken-symlink

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/bmatcuk/doublestar/test/working-symlink

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/cli/scripts/docs/generate-authors.sh

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/contrib/syntax/vim/LICENSE

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Loading

vendor/github.com/docker/docker/hack/generate-authors.sh

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/integration-cli/fixtures/https/ca.pem

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/integration-cli/fixtures/https/client-cert.pem

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/integration-cli/fixtures/https/client-key.pem

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/integration-cli/fixtures/https/server-cert.pem

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/docker/docker/integration-cli/fixtures/https/server-key.pem

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)