Răsfoiți Sursa

wait on service containers as dependencies to be deterministic

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 2 ani în urmă
părinte
comite
52478f0c6e
3 a modificat fișierele cu 29 adăugiri și 30 ștergeri
  1. 21 22
      pkg/compose/convergence.go
  2. 6 6
      pkg/compose/start.go
  3. 2 2
      pkg/e2e/up_test.go

+ 21 - 22
pkg/compose/convergence.go

@@ -290,8 +290,8 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 			continue
 		}
 
-		containers = containers.filter(isService(dep))
-		w.Events(containerEvents(containers, progress.Waiting))
+		waitingFor := containers.filter(isService(dep))
+		w.Events(containerEvents(waitingFor, progress.Waiting))
 
 		dep, config := dep, config
 		eg.Go(func() error {
@@ -301,31 +301,31 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 				<-ticker.C
 				switch config.Condition {
 				case ServiceConditionRunningOrHealthy:
-					healthy, err := s.isServiceHealthy(ctx, containers, dep, true)
+					healthy, err := s.isServiceHealthy(ctx, waitingFor, true)
 					if err != nil {
 						return err
 					}
 					if healthy {
-						w.Events(containerEvents(containers, progress.Healthy))
+						w.Events(containerEvents(waitingFor, progress.Healthy))
 						return nil
 					}
 				case types.ServiceConditionHealthy:
-					healthy, err := s.isServiceHealthy(ctx, containers, dep, false)
+					healthy, err := s.isServiceHealthy(ctx, waitingFor, false)
 					if err != nil {
-						w.Events(containerEvents(containers, progress.ErrorEvent))
+						w.Events(containerEvents(waitingFor, progress.ErrorEvent))
 						return errors.Wrap(err, "dependency failed to start")
 					}
 					if healthy {
-						w.Events(containerEvents(containers, progress.Healthy))
+						w.Events(containerEvents(waitingFor, progress.Healthy))
 						return nil
 					}
 				case types.ServiceConditionCompletedSuccessfully:
-					exited, code, err := s.isServiceCompleted(ctx, containers, dep)
+					exited, code, err := s.isServiceCompleted(ctx, waitingFor)
 					if err != nil {
 						return err
 					}
 					if exited {
-						w.Events(containerEvents(containers, progress.Exited))
+						w.Events(containerEvents(waitingFor, progress.Exited))
 						if code != 0 {
 							return fmt.Errorf("service %q didn't complete successfully: exit %d", dep, code)
 						}
@@ -646,42 +646,41 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
 	return nil
 }
 
-func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, service string, fallbackRunning bool) (bool, error) {
-	if len(containers) == 0 {
-		return false, nil
-	}
+func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) {
 	for _, c := range containers {
 		container, err := s.apiClient().ContainerInspect(ctx, c.ID)
 		if err != nil {
 			return false, err
 		}
+		name := container.Name[1:]
+
+		if container.State.Status == "exited" {
+			return false, fmt.Errorf("container %s exited (%d)", name, container.State.ExitCode)
+		}
+
 		if container.Config.Healthcheck == nil && fallbackRunning {
 			// Container does not define a health check, but we can fall back to "running" state
 			return container.State != nil && container.State.Status == "running", nil
 		}
 
-		if container.State.Status == "exited" {
-			return false, fmt.Errorf("container for service %q exited (%d)", service, container.State.ExitCode)
-		}
-
 		if container.State == nil || container.State.Health == nil {
-			return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
+			return false, fmt.Errorf("container %s has no healthcheck configured", name)
 		}
 		switch container.State.Health.Status {
 		case moby.Healthy:
 			// Continue by checking the next container.
 		case moby.Unhealthy:
-			return false, fmt.Errorf("container for service %q is unhealthy", service)
+			return false, fmt.Errorf("container %s is unhealthy", name)
 		case moby.Starting:
 			return false, nil
 		default:
-			return false, fmt.Errorf("container for service %q had unexpected health status %q", service, container.State.Health.Status)
+			return false, fmt.Errorf("container %s had unexpected health status %q", name, container.State.Health.Status)
 		}
 	}
 	return true, nil
 }
 
-func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers, dep string) (bool, int, error) {
+func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers) (bool, int, error) {
 	for _, c := range containers {
 		container, err := s.apiClient().ContainerInspect(ctx, c.ID)
 		if err != nil {
@@ -712,7 +711,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
 	}
 
 	w := progress.ContextWriter(ctx)
-	for _, container := range containers {
+	for _, container := range containers.filter(isService(service.Name)) {
 		if container.State == ContainerRunning {
 			continue
 		}

+ 6 - 6
pkg/compose/start.go

@@ -18,18 +18,18 @@ package compose
 
 import (
 	"context"
-	"github.com/docker/docker/api/types/filters"
 	"strings"
 	"time"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/compose/v2/pkg/api"
+	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/utils"
+
+	"github.com/compose-spec/compose-go/types"
 	moby "github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/pkg/errors"
 	"golang.org/x/sync/errgroup"
-
-	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 )
 
 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
 			return err
 		}
 
-		return s.startService(ctx, project, service, containers.filter(isService(service.Name)))
+		return s.startService(ctx, project, service, containers)
 	})
 	if err != nil {
 		return err

+ 2 - 2
pkg/e2e/up_test.go

@@ -38,7 +38,7 @@ func TestUpServiceUnhealthy(t *testing.T) {
 	const projectName = "e2e-start-fail"
 
 	res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/start-fail/compose.yaml", "--project-name", projectName, "up", "-d")
-	res.Assert(t, icmd.Expected{ExitCode: 1, Err: `container for service "fail" is unhealthy`})
+	res.Assert(t, icmd.Expected{ExitCode: 1, Err: `container e2e-start-fail-fail-1 is unhealthy`})
 
 	c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
 }
@@ -135,6 +135,6 @@ func TestUpWithDependencyExit(t *testing.T) {
 			c.RunDockerComposeCmd(t, "--project-name", "dependencies", "down")
 		})
 
-		res.Assert(t, icmd.Expected{ExitCode: 1, Err: "dependency failed to start: container for service \"db\" exited (1)"})
+		res.Assert(t, icmd.Expected{ExitCode: 1, Err: "dependency failed to start: container dependencies-db-1 exited (1)"})
 	})
 }