diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index 65a2afa5d..a98061462 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -31,6 +31,7 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" specs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -277,7 +278,7 @@ func containerEvents(containers Containers, eventFunc func(string) progress.Even return events } -// ServiceConditionRunningOrHealthy is a service condition on statys running or healthy +// ServiceConditionRunningOrHealthy is a service condition on status running or healthy const ServiceConditionRunningOrHealthy = "running_or_healthy" func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig) error { @@ -315,7 +316,8 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr case types.ServiceConditionHealthy: healthy, err := s.isServiceHealthy(ctx, project, dep, false) if err != nil { - return err + w.Events(containerEvents(containers, progress.ErrorEvent)) + return errors.Wrap(err, "dependency failed to start") } if healthy { w.Events(containerEvents(containers, progress.Healthy)) @@ -644,7 +646,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin } func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string, fallbackRunning bool) (bool, error) { - containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, service) + containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, service) if err != nil { return false, err } @@ -662,6 +664,10 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr 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) } diff --git a/pkg/e2e/fixtures/dependencies/dependency-exit.yaml b/pkg/e2e/fixtures/dependencies/dependency-exit.yaml new file mode 100644 index 000000000..7ba02ba79 --- /dev/null +++ b/pkg/e2e/fixtures/dependencies/dependency-exit.yaml @@ -0,0 +1,10 @@ +services: + web: + image: nginx:alpine + depends_on: + db: + condition: service_healthy + db: + image: alpine + command: sh -c "exit 1" + diff --git a/pkg/e2e/up_test.go b/pkg/e2e/up_test.go index ecb4595b1..3f58a9022 100644 --- a/pkg/e2e/up_test.go +++ b/pkg/e2e/up_test.go @@ -123,3 +123,18 @@ func TestUpWithBuildDependencies(t *testing.T) { res.Assert(t, icmd.Success) }) } + +func TestUpWithDependencyExit(t *testing.T) { + c := NewParallelCLI(t) + + t.Run("up with dependency to exit before being healthy", func(t *testing.T) { + res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/dependencies", + "-f", "fixtures/dependencies/dependency-exit.yaml", "up", "-d") + + t.Cleanup(func() { + 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)"}) + }) +}