From 5ff524df0b44448300b43cebe26ffefba0943ef1 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 25 Feb 2025 10:30:52 +0100 Subject: [PATCH 1/2] introduce watch.include Signed-off-by: Nicolas De Loof --- go.mod | 2 +- go.sum | 4 ++-- pkg/compose/watch.go | 21 +++++++++++++++++++++ pkg/watch/notify.go | 9 +++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f28e626a19b..28b14cb5ce2 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.4.9-0.20250225151507-331db8fefcb7 + github.com/compose-spec/compose-go/v2 v2.4.9-0.20250302154753-e508c724a35f github.com/containerd/containerd/v2 v2.0.2 github.com/containerd/platforms v1.0.0-rc.1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc diff --git a/go.sum b/go.sum index 8acb7eddea5..bde32edf7ba 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.4.9-0.20250225151507-331db8fefcb7 h1:7NlxAsQcWvLpFlEHsBo80sJ1UMMs84kkf0yXGs6de2k= -github.com/compose-spec/compose-go/v2 v2.4.9-0.20250225151507-331db8fefcb7/go.mod h1:6k5l/0TxCg0/2uLEhRVEsoBWBprS2uvZi32J7xub3lo= +github.com/compose-spec/compose-go/v2 v2.4.9-0.20250302154753-e508c724a35f h1:kbmTPhf7d9kTnmH0ghAQTqxs1zenKnwKczJlC4z5WSc= +github.com/compose-spec/compose-go/v2 v2.4.9-0.20250302154753-e508c724a35f/go.mod h1:6k5l/0TxCg0/2uLEhRVEsoBWBprS2uvZi32J7xub3lo= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go index cff1d4f0b9b..72f329a6252 100644 --- a/pkg/compose/watch.go +++ b/pkg/compose/watch.go @@ -81,6 +81,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv type watchRule struct { types.Trigger + include watch.PathMatcher ignore watch.PathMatcher service string } @@ -90,6 +91,15 @@ func (r watchRule) Matches(event watch.FileEvent) *sync.PathMapping { if !pathutil.IsChild(r.Path, hostPath) { return nil } + included, err := r.include.Matches(hostPath) + if err != nil { + logrus.Warnf("error include matching %q: %v", hostPath, err) + return nil + } + if !included { + logrus.Debugf("%s is not matching include pattern", hostPath) + return nil + } isIgnored, err := r.ignore.Matches(hostPath) if err != nil { logrus.Warnf("error ignore matching %q: %v", hostPath, err) @@ -244,8 +254,19 @@ func getWatchRules(config *types.DevelopConfig, service types.ServiceConfig) ([] return nil, err } + var include watch.PathMatcher + if len(trigger.Include) == 0 { + include = watch.AnyMatcher{} + } else { + include, err = watch.NewDockerPatternMatcher(trigger.Path, trigger.Include) + if err != nil { + return nil, err + } + } + rules = append(rules, watchRule{ Trigger: trigger, + include: include, ignore: watch.NewCompositeMatcher( dockerIgnores, watch.EphemeralPathMatcher(), diff --git a/pkg/watch/notify.go b/pkg/watch/notify.go index b7629210676..d09a6be1525 100644 --- a/pkg/watch/notify.go +++ b/pkg/watch/notify.go @@ -68,6 +68,15 @@ type PathMatcher interface { MatchesEntireDir(file string) (bool, error) } +// AnyMatcher is a PathMatcher to match any path +type AnyMatcher struct{} + +func (AnyMatcher) Matches(f string) (bool, error) { return true, nil } +func (AnyMatcher) MatchesEntireDir(f string) (bool, error) { return true, nil } + +var _ PathMatcher = AnyMatcher{} + +// EmptyMatcher is a PathMatcher to match no path type EmptyMatcher struct{} func (EmptyMatcher) Matches(f string) (bool, error) { return false, nil } From 7bd320f8c7b76576e4d2ca25a2c5fca05d240203 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 3 Mar 2025 16:55:53 +0100 Subject: [PATCH 2/2] e2e test for watch.include Signed-off-by: Nicolas De Loof --- pkg/e2e/fixtures/watch/include.yaml | 12 ++++++++++ pkg/e2e/watch_test.go | 37 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 pkg/e2e/fixtures/watch/include.yaml diff --git a/pkg/e2e/fixtures/watch/include.yaml b/pkg/e2e/fixtures/watch/include.yaml new file mode 100644 index 00000000000..ccd9d45042b --- /dev/null +++ b/pkg/e2e/fixtures/watch/include.yaml @@ -0,0 +1,12 @@ +services: + a: + build: + dockerfile_inline: | + FROM nginx + RUN mkdir /data/ + develop: + watch: + - path: . + include: A.* + target: /data/ + action: sync diff --git a/pkg/e2e/watch_test.go b/pkg/e2e/watch_test.go index 1a8c92cc55f..5c4db47658b 100644 --- a/pkg/e2e/watch_test.go +++ b/pkg/e2e/watch_test.go @@ -368,3 +368,40 @@ func TestWatchMultiServices(t *testing.T) { c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "kill", "-s", "9") } + +func TestWatchIncludes(t *testing.T) { + c := NewCLI(t) + const projectName = "test_watch_includes" + + defer c.cleanupWithDown(t, projectName) + + tmpdir := t.TempDir() + composeFilePath := filepath.Join(tmpdir, "compose.yaml") + CopyFile(t, filepath.Join("fixtures", "watch", "include.yaml"), composeFilePath) + + cmd := c.NewDockerComposeCmd(t, "-p", projectName, "-f", composeFilePath, "up", "--watch") + buffer := bytes.NewBuffer(nil) + cmd.Stdout = buffer + watch := icmd.StartCmd(cmd) + + poll.WaitOn(t, func(l poll.LogT) poll.Result { + if strings.Contains(watch.Stdout(), "Attaching to ") { + return poll.Success() + } + return poll.Continue("%v", watch.Stdout()) + }) + + require.NoError(t, os.WriteFile(filepath.Join(tmpdir, "B.test"), []byte("test"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(tmpdir, "A.test"), []byte("test"), 0o600)) + + poll.WaitOn(t, func(l poll.LogT) poll.Result { + cat := c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "exec", "a", "ls", "/data/") + if strings.Contains(cat.Stdout(), "A.test") { + assert.Check(t, !strings.Contains(cat.Stdout(), "B.test")) + return poll.Success() + } + return poll.Continue("%v", cat.Combined()) + }) + + c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "kill", "-s", "9") +}