Skip to content

Commit 9a4e74c

Browse files
authored
Merge pull request docker#10209 from ndeloof/wait_containers
use containers we expect to start for wait condition
2 parents e908f41 + 52478f0 commit 9a4e74c

File tree

5 files changed

+53
-65
lines changed

5 files changed

+53
-65
lines changed

pkg/compose/convergence.go

Lines changed: 24 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"github.com/containerd/containerd/platforms"
2929
moby "github.com/docker/docker/api/types"
3030
containerType "github.com/docker/docker/api/types/container"
31-
"github.com/docker/docker/api/types/filters"
3231
"github.com/docker/docker/api/types/network"
3332
specs "github.com/opencontainers/image-spec/specs-go/v1"
3433
"github.com/pkg/errors"
@@ -281,7 +280,7 @@ func containerEvents(containers Containers, eventFunc func(string) progress.Even
281280
// ServiceConditionRunningOrHealthy is a service condition on status running or healthy
282281
const ServiceConditionRunningOrHealthy = "running_or_healthy"
283282

284-
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig) error {
283+
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig, containers Containers) error {
285284
eg, _ := errgroup.WithContext(ctx)
286285
w := progress.ContextWriter(ctx)
287286
for dep, config := range dependencies {
@@ -291,11 +290,8 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
291290
continue
292291
}
293292

294-
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, dep)
295-
if err != nil {
296-
return err
297-
}
298-
w.Events(containerEvents(containers, progress.Waiting))
293+
waitingFor := containers.filter(isService(dep))
294+
w.Events(containerEvents(waitingFor, progress.Waiting))
299295

300296
dep, config := dep, config
301297
eg.Go(func() error {
@@ -305,31 +301,31 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
305301
<-ticker.C
306302
switch config.Condition {
307303
case ServiceConditionRunningOrHealthy:
308-
healthy, err := s.isServiceHealthy(ctx, project, dep, true)
304+
healthy, err := s.isServiceHealthy(ctx, waitingFor, true)
309305
if err != nil {
310306
return err
311307
}
312308
if healthy {
313-
w.Events(containerEvents(containers, progress.Healthy))
309+
w.Events(containerEvents(waitingFor, progress.Healthy))
314310
return nil
315311
}
316312
case types.ServiceConditionHealthy:
317-
healthy, err := s.isServiceHealthy(ctx, project, dep, false)
313+
healthy, err := s.isServiceHealthy(ctx, waitingFor, false)
318314
if err != nil {
319-
w.Events(containerEvents(containers, progress.ErrorEvent))
315+
w.Events(containerEvents(waitingFor, progress.ErrorEvent))
320316
return errors.Wrap(err, "dependency failed to start")
321317
}
322318
if healthy {
323-
w.Events(containerEvents(containers, progress.Healthy))
319+
w.Events(containerEvents(waitingFor, progress.Healthy))
324320
return nil
325321
}
326322
case types.ServiceConditionCompletedSuccessfully:
327-
exited, code, err := s.isServiceCompleted(ctx, project, dep)
323+
exited, code, err := s.isServiceCompleted(ctx, waitingFor)
328324
if err != nil {
329325
return err
330326
}
331327
if exited {
332-
w.Events(containerEvents(containers, progress.Exited))
328+
w.Events(containerEvents(waitingFor, progress.Exited))
333329
if code != 0 {
334330
return fmt.Errorf("service %q didn't complete successfully: exit %d", dep, code)
335331
}
@@ -650,51 +646,41 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
650646
return nil
651647
}
652648

653-
func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string, fallbackRunning bool) (bool, error) {
654-
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, service)
655-
if err != nil {
656-
return false, err
657-
}
658-
659-
if len(containers) == 0 {
660-
return false, nil
661-
}
649+
func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) {
662650
for _, c := range containers {
663651
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
664652
if err != nil {
665653
return false, err
666654
}
655+
name := container.Name[1:]
656+
657+
if container.State.Status == "exited" {
658+
return false, fmt.Errorf("container %s exited (%d)", name, container.State.ExitCode)
659+
}
660+
667661
if container.Config.Healthcheck == nil && fallbackRunning {
668662
// Container does not define a health check, but we can fall back to "running" state
669663
return container.State != nil && container.State.Status == "running", nil
670664
}
671665

672-
if container.State.Status == "exited" {
673-
return false, fmt.Errorf("container for service %q exited (%d)", service, container.State.ExitCode)
674-
}
675-
676666
if container.State == nil || container.State.Health == nil {
677-
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
667+
return false, fmt.Errorf("container %s has no healthcheck configured", name)
678668
}
679669
switch container.State.Health.Status {
680670
case moby.Healthy:
681671
// Continue by checking the next container.
682672
case moby.Unhealthy:
683-
return false, fmt.Errorf("container for service %q is unhealthy", service)
673+
return false, fmt.Errorf("container %s is unhealthy", name)
684674
case moby.Starting:
685675
return false, nil
686676
default:
687-
return false, fmt.Errorf("container for service %q had unexpected health status %q", service, container.State.Health.Status)
677+
return false, fmt.Errorf("container %s had unexpected health status %q", name, container.State.Health.Status)
688678
}
689679
}
690680
return true, nil
691681
}
692682

693-
func (s *composeService) isServiceCompleted(ctx context.Context, project *types.Project, dep string) (bool, int, error) {
694-
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, dep)
695-
if err != nil {
696-
return false, 0, err
697-
}
683+
func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers) (bool, int, error) {
698684
for _, c := range containers {
699685
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
700686
if err != nil {
@@ -707,23 +693,12 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
707693
return false, 0, nil
708694
}
709695

710-
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
696+
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig, containers Containers) error {
711697
if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
712698
return nil
713699
}
714700

715-
err := s.waitDependencies(ctx, project, service.DependsOn)
716-
if err != nil {
717-
return err
718-
}
719-
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
720-
Filters: filters.NewArgs(
721-
projectFilter(project.Name),
722-
serviceFilter(service.Name),
723-
oneOffFilter(false),
724-
),
725-
All: true,
726-
})
701+
err := s.waitDependencies(ctx, project, service.DependsOn, containers)
727702
if err != nil {
728703
return err
729704
}
@@ -736,7 +711,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
736711
}
737712

738713
w := progress.ContextWriter(ctx)
739-
for _, container := range containers {
714+
for _, container := range containers.filter(isService(service.Name)) {
740715
if container.State == ContainerRunning {
741716
continue
742717
}

pkg/compose/convergence_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ func TestWaitDependencies(t *testing.T) {
229229
"db": {Condition: ServiceConditionRunningOrHealthy},
230230
"redis": {Condition: ServiceConditionRunningOrHealthy},
231231
}
232-
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
232+
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies, nil))
233233
})
234234
t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
235235
dbService := types.ServiceConfig{Name: "db", Scale: 1}
@@ -239,6 +239,6 @@ func TestWaitDependencies(t *testing.T) {
239239
"db": {Condition: types.ServiceConditionStarted},
240240
"redis": {Condition: types.ServiceConditionStarted},
241241
}
242-
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
242+
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies, nil))
243243
})
244244
}

pkg/compose/run.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,18 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
7676
if err := s.ensureImagesExists(ctx, project, opts.QuietPull); err != nil { // all dependencies already checked, but might miss service img
7777
return "", err
7878
}
79-
if !opts.NoDeps {
80-
if err := s.waitDependencies(ctx, project, service.DependsOn); err != nil {
81-
return "", err
82-
}
83-
}
8479

8580
observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
8681
if err != nil {
8782
return "", err
8883
}
8984
updateServices(&service, observedState)
9085

86+
if !opts.NoDeps {
87+
if err := s.waitDependencies(ctx, project, service.DependsOn, observedState); err != nil {
88+
return "", err
89+
}
90+
}
9191
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1,
9292
opts.AutoRemove, opts.UseNetworkAliases, opts.Interactive)
9393
if err != nil {

pkg/compose/start.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import (
2121
"strings"
2222
"time"
2323

24-
"github.com/compose-spec/compose-go/types"
24+
"github.com/docker/compose/v2/pkg/api"
25+
"github.com/docker/compose/v2/pkg/progress"
2526
"github.com/docker/compose/v2/pkg/utils"
27+
28+
"github.com/compose-spec/compose-go/types"
2629
moby "github.com/docker/docker/api/types"
30+
"github.com/docker/docker/api/types/filters"
2731
"github.com/pkg/errors"
2832
"golang.org/x/sync/errgroup"
29-
30-
"github.com/docker/compose/v2/pkg/api"
31-
"github.com/docker/compose/v2/pkg/progress"
3233
)
3334

3435
func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error {
@@ -75,13 +76,25 @@ func (s *composeService) start(ctx context.Context, projectName string, options
7576
})
7677
}
7778

78-
err := InDependencyOrder(ctx, project, func(c context.Context, name string) error {
79+
var containers Containers
80+
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
81+
Filters: filters.NewArgs(
82+
projectFilter(project.Name),
83+
oneOffFilter(false),
84+
),
85+
All: true,
86+
})
87+
if err != nil {
88+
return err
89+
}
90+
91+
err = InDependencyOrder(ctx, project, func(c context.Context, name string) error {
7992
service, err := project.GetService(name)
8093
if err != nil {
8194
return err
8295
}
8396

84-
return s.startService(ctx, project, service)
97+
return s.startService(ctx, project, service, containers)
8598
})
8699
if err != nil {
87100
return err
@@ -94,7 +107,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
94107
Condition: getDependencyCondition(s, project),
95108
}
96109
}
97-
err = s.waitDependencies(ctx, project, depends)
110+
err = s.waitDependencies(ctx, project, depends, containers)
98111
if err != nil {
99112
return err
100113
}

pkg/e2e/up_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func TestUpServiceUnhealthy(t *testing.T) {
3838
const projectName = "e2e-start-fail"
3939

4040
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/start-fail/compose.yaml", "--project-name", projectName, "up", "-d")
41-
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `container for service "fail" is unhealthy`})
41+
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `container e2e-start-fail-fail-1 is unhealthy`})
4242

4343
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
4444
}
@@ -135,6 +135,6 @@ func TestUpWithDependencyExit(t *testing.T) {
135135
c.RunDockerComposeCmd(t, "--project-name", "dependencies", "down")
136136
})
137137

138-
res.Assert(t, icmd.Expected{ExitCode: 1, Err: "dependency failed to start: container for service \"db\" exited (1)"})
138+
res.Assert(t, icmd.Expected{ExitCode: 1, Err: "dependency failed to start: container dependencies-db-1 exited (1)"})
139139
})
140140
}

0 commit comments

Comments
 (0)