Skip to content

Commit 52478f0

Browse files
ndeloofglours
authored andcommitted
wait on service containers as dependencies to be deterministic
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent b5f0a4e commit 52478f0

File tree

3 files changed

+29
-30
lines changed

3 files changed

+29
-30
lines changed

pkg/compose/convergence.go

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,8 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
290290
continue
291291
}
292292

293-
containers = containers.filter(isService(dep))
294-
w.Events(containerEvents(containers, progress.Waiting))
293+
waitingFor := containers.filter(isService(dep))
294+
w.Events(containerEvents(waitingFor, progress.Waiting))
295295

296296
dep, config := dep, config
297297
eg.Go(func() error {
@@ -301,31 +301,31 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
301301
<-ticker.C
302302
switch config.Condition {
303303
case ServiceConditionRunningOrHealthy:
304-
healthy, err := s.isServiceHealthy(ctx, containers, dep, true)
304+
healthy, err := s.isServiceHealthy(ctx, waitingFor, true)
305305
if err != nil {
306306
return err
307307
}
308308
if healthy {
309-
w.Events(containerEvents(containers, progress.Healthy))
309+
w.Events(containerEvents(waitingFor, progress.Healthy))
310310
return nil
311311
}
312312
case types.ServiceConditionHealthy:
313-
healthy, err := s.isServiceHealthy(ctx, containers, dep, false)
313+
healthy, err := s.isServiceHealthy(ctx, waitingFor, false)
314314
if err != nil {
315-
w.Events(containerEvents(containers, progress.ErrorEvent))
315+
w.Events(containerEvents(waitingFor, progress.ErrorEvent))
316316
return errors.Wrap(err, "dependency failed to start")
317317
}
318318
if healthy {
319-
w.Events(containerEvents(containers, progress.Healthy))
319+
w.Events(containerEvents(waitingFor, progress.Healthy))
320320
return nil
321321
}
322322
case types.ServiceConditionCompletedSuccessfully:
323-
exited, code, err := s.isServiceCompleted(ctx, containers, dep)
323+
exited, code, err := s.isServiceCompleted(ctx, waitingFor)
324324
if err != nil {
325325
return err
326326
}
327327
if exited {
328-
w.Events(containerEvents(containers, progress.Exited))
328+
w.Events(containerEvents(waitingFor, progress.Exited))
329329
if code != 0 {
330330
return fmt.Errorf("service %q didn't complete successfully: exit %d", dep, code)
331331
}
@@ -646,42 +646,41 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
646646
return nil
647647
}
648648

649-
func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, service string, fallbackRunning bool) (bool, error) {
650-
if len(containers) == 0 {
651-
return false, nil
652-
}
649+
func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) {
653650
for _, c := range containers {
654651
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
655652
if err != nil {
656653
return false, err
657654
}
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+
658661
if container.Config.Healthcheck == nil && fallbackRunning {
659662
// Container does not define a health check, but we can fall back to "running" state
660663
return container.State != nil && container.State.Status == "running", nil
661664
}
662665

663-
if container.State.Status == "exited" {
664-
return false, fmt.Errorf("container for service %q exited (%d)", service, container.State.ExitCode)
665-
}
666-
667666
if container.State == nil || container.State.Health == nil {
668-
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)
669668
}
670669
switch container.State.Health.Status {
671670
case moby.Healthy:
672671
// Continue by checking the next container.
673672
case moby.Unhealthy:
674-
return false, fmt.Errorf("container for service %q is unhealthy", service)
673+
return false, fmt.Errorf("container %s is unhealthy", name)
675674
case moby.Starting:
676675
return false, nil
677676
default:
678-
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)
679678
}
680679
}
681680
return true, nil
682681
}
683682

684-
func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers, dep string) (bool, int, error) {
683+
func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers) (bool, int, error) {
685684
for _, c := range containers {
686685
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
687686
if err != nil {
@@ -712,7 +711,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
712711
}
713712

714713
w := progress.ContextWriter(ctx)
715-
for _, container := range containers {
714+
for _, container := range containers.filter(isService(service.Name)) {
716715
if container.State == ContainerRunning {
717716
continue
718717
}

pkg/compose/start.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ package compose
1818

1919
import (
2020
"context"
21-
"github.com/docker/docker/api/types/filters"
2221
"strings"
2322
"time"
2423

25-
"github.com/compose-spec/compose-go/types"
24+
"github.com/docker/compose/v2/pkg/api"
25+
"github.com/docker/compose/v2/pkg/progress"
2626
"github.com/docker/compose/v2/pkg/utils"
27+
28+
"github.com/compose-spec/compose-go/types"
2729
moby "github.com/docker/docker/api/types"
30+
"github.com/docker/docker/api/types/filters"
2831
"github.com/pkg/errors"
2932
"golang.org/x/sync/errgroup"
30-
31-
"github.com/docker/compose/v2/pkg/api"
32-
"github.com/docker/compose/v2/pkg/progress"
3333
)
3434

3535
func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error {
@@ -94,7 +94,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
9494
return err
9595
}
9696

97-
return s.startService(ctx, project, service, containers.filter(isService(service.Name)))
97+
return s.startService(ctx, project, service, containers)
9898
})
9999
if err != nil {
100100
return err

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)