Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
67 changes: 4 additions & 63 deletions pkg/compose/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 6 additions & 9 deletions pkg/e2e/fixtures/watch/x-initialSync.yaml
Original file line number Diff line number Diff line change
@@ -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
target: /data/
action: sync
x-initialSync: true
23 changes: 19 additions & 4 deletions pkg/e2e/watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Loading