mirror of
https://github.com/docker/compose.git
synced 2026-08-27 03:45:29 +00:00
fix(watch): don't rebuild dependencies when a service changes
When a watched service is rebuilt, compose also rebuilt the services it depends on (which were then left not restarted). up builds its BuildOptions with Deps=true. The same options are reused by watch's rebuild(), which narrows Build.Services to the changed service but never cleared Build.Deps, so build() still included dependencies via IncludeDependencies. Clear Build.Deps in rebuild() so only the changed services are built. Fixes #13853 Signed-off-by: David Gageot <david.gageot@docker.com>
This commit is contained in:
parent
0afb4c8c4c
commit
caa454e04a
4 changed files with 111 additions and 1 deletions
|
|
@ -138,6 +138,10 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, opti
|
|||
return wait()
|
||||
}
|
||||
|
||||
func selectWatchServices(project *types.Project, services []string) (*types.Project, error) {
|
||||
return project.WithSelectedServices(services, types.IgnoreDependencies)
|
||||
}
|
||||
|
||||
type watchRule struct {
|
||||
types.Trigger
|
||||
include watch.PathMatcher
|
||||
|
|
@ -188,7 +192,7 @@ func (r watchRule) Matches(event watch.FileEvent) *sync.PathMapping {
|
|||
|
||||
func (s *composeService) watch(ctx context.Context, project *types.Project, options api.WatchOptions) (func() error, error) { //nolint: gocyclo
|
||||
var err error
|
||||
if project, err = project.WithSelectedServices(options.Services); err != nil {
|
||||
if project, err = selectWatchServices(project, options.Services); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
syncer, err := s.getSyncImplementation(project)
|
||||
|
|
@ -636,6 +640,7 @@ func (s *composeService) rebuild(ctx context.Context, project *types.Project, se
|
|||
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service(s) %q after changes were detected...", services))
|
||||
// restrict the build to ONLY this service, not any of its dependencies
|
||||
options.Build.Services = services
|
||||
options.Build.Deps = false
|
||||
options.Build.Progress = string(progressui.PlainMode)
|
||||
options.Build.Out = cutils.GetWriter(func(line string) {
|
||||
options.LogTo.Log(api.WatchLogger, line)
|
||||
|
|
|
|||
|
|
@ -180,6 +180,37 @@ func TestWatch_Sync(t *testing.T) {
|
|||
// TODO: there's not a great way to assert that the rebuild attempt happened
|
||||
}
|
||||
|
||||
func TestSelectWatchServicesIgnoresDependencies(t *testing.T) {
|
||||
project := &types.Project{
|
||||
Name: "myProjectName",
|
||||
Services: types.Services{
|
||||
"backend": {
|
||||
Name: "backend",
|
||||
},
|
||||
"stats": {
|
||||
Name: "stats",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"backend": {
|
||||
Condition: types.ServiceConditionStarted,
|
||||
Restart: true,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
selected, err := selectWatchServices(project, []string{"stats"})
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, ok := selected.Services["stats"]
|
||||
assert.Assert(t, ok)
|
||||
_, ok = selected.Services["backend"]
|
||||
assert.Assert(t, !ok)
|
||||
assert.Assert(t, len(selected.Services["stats"].DependsOn) == 0)
|
||||
assert.Assert(t, len(project.Services["stats"].DependsOn) != 0)
|
||||
}
|
||||
|
||||
type fakeSyncer struct {
|
||||
synced chan []*sync.PathMapping
|
||||
}
|
||||
|
|
|
|||
19
pkg/e2e/fixtures/watch/rebuild-deps.yaml
Normal file
19
pkg/e2e/fixtures/watch/rebuild-deps.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
services:
|
||||
backend:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM nginx
|
||||
RUN mkdir /data
|
||||
COPY backend /data/backend
|
||||
frontend:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM nginx
|
||||
RUN mkdir /data
|
||||
COPY frontend /data/frontend
|
||||
depends_on:
|
||||
- backend
|
||||
develop:
|
||||
watch:
|
||||
- path: frontend
|
||||
action: rebuild
|
||||
|
|
@ -368,6 +368,61 @@ func TestWatchMultiServices(t *testing.T) {
|
|||
c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "kill", "-s", "9")
|
||||
}
|
||||
|
||||
func TestWatchRebuildIgnoresDependencies(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "test_watch_rebuild_deps"
|
||||
|
||||
defer c.cleanupWithDown(t, projectName)
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
composeFilePath := filepath.Join(tmpdir, "compose.yaml")
|
||||
CopyFile(t, filepath.Join("fixtures", "watch", "rebuild-deps.yaml"), composeFilePath)
|
||||
|
||||
for _, svc := range []string{"backend", "frontend"} {
|
||||
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, svc), []byte("v1"), 0o600))
|
||||
}
|
||||
|
||||
cmd := c.NewDockerComposeCmd(t, "-p", projectName, "-f", composeFilePath, "up", "--build", "--watch")
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd.Stdout = buffer
|
||||
watch := icmd.StartCmd(cmd)
|
||||
t.Cleanup(func() {
|
||||
if watch.Cmd.Process != nil {
|
||||
_ = watch.Cmd.Process.Kill()
|
||||
}
|
||||
})
|
||||
|
||||
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())
|
||||
}, poll.WithTimeout(90*time.Second))
|
||||
|
||||
containerID := func(service string) string {
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "ps", "-q", service)
|
||||
return strings.TrimSpace(res.Stdout())
|
||||
}
|
||||
backendID := containerID("backend")
|
||||
assert.Assert(t, backendID != "")
|
||||
|
||||
t.Log("editing frontend code only")
|
||||
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "frontend"), []byte("v2"), 0o600))
|
||||
|
||||
poll.WaitOn(t, func(l poll.LogT) poll.Result {
|
||||
cat := c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "exec", "frontend", "cat", "/data/frontend")
|
||||
if strings.Contains(cat.Stdout(), "v2") {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("%v", cat.Combined())
|
||||
}, poll.WithTimeout(90*time.Second))
|
||||
|
||||
t.Log("backend must not be rebuilt nor recreated")
|
||||
assert.Equal(t, backendID, containerID("backend"))
|
||||
|
||||
c.RunDockerComposeCmdNoCheck(t, "-p", projectName, "kill", "-s", "9")
|
||||
}
|
||||
|
||||
func TestWatchIncludes(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "test_watch_includes"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue