From 97357b1bdff993d3618f4ad67ec9318650d0f2de Mon Sep 17 00:00:00 2001 From: Guillaume Lours Date: Thu, 1 Oct 2026 11:42:58 +0200 Subject: [PATCH] fix: drop dead x-develop/x-initialSync extension handling compose-go's loader now silently renames both deprecated x-initialSync and x-develop extensions to their official initial_sync and develop attributes before compose ever sees them as extensions, so the legacy lookups and deprecation warnings for both can no longer be reached. That's why the e2e test asserting on the x-initialSync warning broke under the bump. Simplify both lookups to rely on the promoted fields directly, and rewrite the e2e test to check the legacy attribute still triggers a real initial sync instead of asserting on an unreachable message. Signed-off-by: Guillaume Lours --- go.mod | 4 +- go.sum | 4 +- pkg/compose/watch.go | 67 ++--------------------- pkg/e2e/fixtures/watch/x-initialSync.yaml | 15 ++--- pkg/e2e/watch_test.go | 23 ++++++-- 5 files changed, 33 insertions(+), 80 deletions(-) diff --git a/go.mod b/go.mod index 96fb967f966..b9a8e3be652 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.15.1-0.20260918184426-f18e211cbeaf + github.com/compose-spec/compose-go/v2 v2.16.1 github.com/containerd/console v1.0.5 github.com/containerd/containerd/v2 v2.3.5 github.com/containerd/errdefs v1.0.0 @@ -19,7 +19,6 @@ require ( github.com/docker/go-units v0.5.0 github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 github.com/fsnotify/fsevents v0.2.0 - github.com/go-viper/mapstructure/v2 v2.5.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-version v1.9.0 @@ -77,6 +76,7 @@ require ( github.com/fvbommel/sortorder v1.2.0 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gofrs/flock v0.13.1 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect diff --git a/go.sum b/go.sum index 1d9c2d48b71..650e1ba2606 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= 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.15.1-0.20260918184426-f18e211cbeaf h1:ot3ZuEYubwVMNT5sO8/5MONyTkGi+GNxezgUT2Wwf9E= -github.com/compose-spec/compose-go/v2 v2.15.1-0.20260918184426-f18e211cbeaf/go.mod h1:Q1+qtN4vhzEjGrnqRtzx1xa8raDZQlMUe3WJxndYNiQ= +github.com/compose-spec/compose-go/v2 v2.16.1 h1:xuEQu32ghB2AK023Beumm//K8bz8u1AHC9P0zKp8jlw= +github.com/compose-spec/compose-go/v2 v2.16.1/go.mod h1:Q1+qtN4vhzEjGrnqRtzx1xa8raDZQlMUe3WJxndYNiQ= github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ= github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go index 220d4ca29ee..ec1a9246157 100644 --- a/pkg/compose/watch.go +++ b/pkg/compose/watch.go @@ -33,7 +33,6 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/utils" ccli "github.com/docker/cli/cli/command/container" - "github.com/go-viper/mapstructure/v2" "github.com/moby/buildkit/util/progress/progressui" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/image" @@ -206,15 +205,7 @@ func (s *composeService) watch(ctx context.Context, project *types.Project, opti paths []string ) for serviceName, service := range project.Services { - config, err := loadDevelopmentConfig(service, project) - if err != nil { - return nil, err - } - - if service.Develop != nil { - config = service.Develop - } - + config := service.Develop if config == nil { continue } @@ -307,19 +298,10 @@ func (s *composeService) watchTriggerPaths(ctx context.Context, service types.Se return paths, nil } -// initialSyncRequested tells whether a sync trigger requests an initial sync, -// honoring the DEPRECATED x-initialSync extension attribute +// initialSyncRequested tells whether a sync trigger requests an initial sync. +// compose-go promotes the legacy x-initialSync extension into InitialSync at load time. func initialSyncRequested(trigger types.Trigger) bool { - if trigger.InitialSync { - return true - } - var legacyInitialSync bool - success, err := trigger.Extensions.Get("x-initialSync", &legacyInitialSync) - if err == nil && success && legacyInitialSync { - logrus.Warnf("x-initialSync is DEPRECATED, please use the official `initial_sync` attribute\n") - return true - } - return false + return trigger.InitialSync } func getWatchRules(config *types.DevelopConfig, service types.ServiceConfig) ([]watchRule, error) { @@ -430,47 +412,6 @@ func (s *composeService) watchEvents(ctx context.Context, project *types.Project } } -func loadDevelopmentConfig(service types.ServiceConfig, project *types.Project) (*types.DevelopConfig, error) { - var config types.DevelopConfig - y, ok := service.Extensions["x-develop"] - if !ok { - return nil, nil - } - logrus.Warnf("x-develop is DEPRECATED, please use the official `develop` attribute") - err := mapstructure.Decode(y, &config) - if err != nil { - return nil, err - } - baseDir, err := filepath.EvalSymlinks(project.WorkingDir) - if err != nil { - return nil, fmt.Errorf("resolving symlink for %q: %w", project.WorkingDir, err) - } - - for i, trigger := range config.Watch { - if !filepath.IsAbs(trigger.Path) { - trigger.Path = filepath.Join(baseDir, trigger.Path) - } - if p, err := filepath.EvalSymlinks(trigger.Path); err == nil { - // this might fail because the path doesn't exist, etc. - trigger.Path = p - } - trigger.Path = filepath.Clean(trigger.Path) - if trigger.Path == "" { - return nil, errors.New("watch rules MUST define a path") - } - - if trigger.Action == types.WatchActionRebuild && service.Build == nil { - return nil, fmt.Errorf("service %s doesn't have a build section, can't apply %s on watch", types.WatchActionRebuild, service.Name) - } - if trigger.Action == types.WatchActionSyncExec && len(trigger.Exec.Command) == 0 { - return nil, fmt.Errorf("can't watch with action %q on service %s without a command", types.WatchActionSyncExec, service.Name) - } - - config.Watch[i] = trigger - } - return &config, nil -} - func checkIfPathAlreadyBindMounted(watchPath string, volumes []types.ServiceVolumeConfig) bool { for _, volume := range volumes { if volume.Bind != nil { diff --git a/pkg/e2e/fixtures/watch/x-initialSync.yaml b/pkg/e2e/fixtures/watch/x-initialSync.yaml index 6a954bd6aee..3407edf45d6 100644 --- a/pkg/e2e/fixtures/watch/x-initialSync.yaml +++ b/pkg/e2e/fixtures/watch/x-initialSync.yaml @@ -1,15 +1,12 @@ services: test: build: - dockerfile_inline: FROM alpine - command: ping localhost - volumes: - - /data + dockerfile_inline: | + FROM nginx + RUN mkdir /data/ develop: watch: - path: . - target: /data - action: sync+exec - exec: - command: echo "SUCCESS" - x-initialSync: true \ No newline at end of file + target: /data/ + action: sync + x-initialSync: true diff --git a/pkg/e2e/watch_test.go b/pkg/e2e/watch_test.go index 4ecc1b85864..f729133e832 100644 --- a/pkg/e2e/watch_test.go +++ b/pkg/e2e/watch_test.go @@ -550,26 +550,41 @@ func TestWatchIncludes(t *testing.T) { c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "kill", "-s", "9") } -func TestCheckWarningXInitialSyn(t *testing.T) { +// TestWatchLegacyXInitialSync checks that the deprecated x-initialSync extension +// still triggers an initial sync, now that compose-go promotes it to the +// official initial_sync attribute at load time instead of leaving it as an +// extension for compose to interpret. A file already present before `up --watch` +// starts must show up in the container without any subsequent filesystem event. +func TestWatchLegacyXInitialSync(t *testing.T) { c := NewCLI(t) - const projectName = "test_watch_warn_initial_syn" + const projectName = "test_watch_legacy_initial_sync" defer c.cleanupWithDown(t, projectName) tmpdir := t.TempDir() composeFilePath := filepath.Join(tmpdir, "compose.yaml") CopyFile(t, filepath.Join("fixtures", "watch", "x-initialSync.yaml"), composeFilePath) - cmd := c.NewDockerComposeCmd(t, "-p", projectName, "-f", composeFilePath, "--verbose", "up", "--watch") + assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "preexisting.txt"), []byte("preexisting"), 0o600)) + + 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.Combined(), "x-initialSync is DEPRECATED, please use the official `initial_sync` attribute") { + if strings.Contains(watch.Stdout(), "Attaching to ") { return poll.Success() } return poll.Continue("%v", watch.Stdout()) }) + poll.WaitOn(t, func(l poll.LogT) poll.Result { + cat := c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "exec", "test", "cat", "/data/preexisting.txt") + if strings.Contains(cat.Stdout(), "preexisting") { + return poll.Success() + } + return poll.Continue("%v", cat.Combined()) + }) + c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "kill", "-s", "9") }