diff --git a/docker.go b/docker.go index e188c25df2..e3999a7f62 100644 --- a/docker.go +++ b/docker.go @@ -86,6 +86,8 @@ type DockerContainer struct { logProductionTimeout *time.Duration logger Logging lifecycleHooks []ContainerLifecycleHooks + + healthStatus string // container health status, will default to healthStatusNone if no healthcheck is present } // SetLogger sets the logger for the container @@ -1590,6 +1592,11 @@ func containerFromDockerResponse(ctx context.Context, response types.Container) return nil, err } + // the health status of the container, if any + if health := container.raw.State.Health; health != nil { + container.healthStatus = health.Status + } + return &container, nil } diff --git a/generic_test.go b/generic_test.go index 72688876ec..c566e925c1 100644 --- a/generic_test.go +++ b/generic_test.go @@ -134,7 +134,7 @@ func TestGenericReusableContainerInSubprocess(t *testing.T) { output := createReuseContainerInSubprocess(t) // check is reuse container with WaitingFor work correctly. - require.True(t, strings.Contains(output, "🚧 Waiting for container id")) + require.True(t, strings.Contains(output, "⏳ Waiting for container id")) require.True(t, strings.Contains(output, "🔔 Container is ready")) }() } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 15b135177f..efd2e054e6 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -278,7 +278,7 @@ func TestReadTCConfig(t *testing.T) { ``, map[string]string{ "TESTCONTAINERS_RYUK_RECONNECTION_TIMEOUT": "13s", - "TESTCONTAINERS_RYUK_CONNECTION_TIMEOUT": "12s", + "TESTCONTAINERS_RYUK_CONNECTION_TIMEOUT": "12s", }, Config{ RyukReconnectionTimeout: 13 * time.Second, @@ -291,7 +291,7 @@ func TestReadTCConfig(t *testing.T) { ryuk.reconnection.timeout=23s`, map[string]string{ "TESTCONTAINERS_RYUK_RECONNECTION_TIMEOUT": "13s", - "TESTCONTAINERS_RYUK_CONNECTION_TIMEOUT": "12s", + "TESTCONTAINERS_RYUK_CONNECTION_TIMEOUT": "12s", }, Config{ RyukReconnectionTimeout: 13 * time.Second, diff --git a/lifecycle.go b/lifecycle.go index 578773b9f5..f46318ce7c 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -207,7 +207,7 @@ var defaultReadinessHook = func() ContainerLifecycleHooks { // if a Wait Strategy has been specified, wait before returning if dockerContainer.WaitingFor != nil { dockerContainer.logger.Printf( - "🚧 Waiting for container id %s image: %s. Waiting for: %+v", + "⏳ Waiting for container id %s image: %s. Waiting for: %+v", dockerContainer.ID[:12], dockerContainer.Image, dockerContainer.WaitingFor, ) if err := dockerContainer.WaitingFor.WaitUntilReady(ctx, c); err != nil { diff --git a/reaper.go b/reaper.go index 54feb90cbe..4b5fabd09c 100644 --- a/reaper.go +++ b/reaper.go @@ -11,6 +11,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/errdefs" @@ -119,6 +120,15 @@ func lookUpReaperContainer(ctx context.Context, sessionID string) (*DockerContai reaperContainer = r + if r.healthStatus == types.Healthy || r.healthStatus == types.NoHealthcheck { + return nil + } + + // if a health status is present on the container, and the container is healthy, error + if r.healthStatus != "" { + return fmt.Errorf("container %s is not healthy, wanted status=%s, got status=%s", resp[0].ID[:8], types.Healthy, r.healthStatus) + } + return nil }, backoff.WithContext(exp, ctx)) if err != nil { @@ -162,6 +172,7 @@ func reuseOrCreateReaper(ctx context.Context, sessionID string, provider ReaperP if err != nil { return nil, err } + return reaperInstance, nil } @@ -192,6 +203,27 @@ func reuseReaperContainer(ctx context.Context, sessionID string, provider Reaper if err != nil { return nil, err } + + Logger.Printf("⏳ Waiting for Reaper port to be ready") + + var containerJson *types.ContainerJSON + + if containerJson, err = reaperContainer.Inspect(ctx); err != nil { + return nil, fmt.Errorf("failed to inspect reaper container %s: %w", reaperContainer.ID[:8], err) + } + + if containerJson != nil && containerJson.NetworkSettings != nil { + for port := range containerJson.NetworkSettings.Ports { + err := wait.ForListeningPort(port). + WithPollInterval(100*time.Millisecond). + WaitUntilReady(ctx, reaperContainer) + if err != nil { + return nil, fmt.Errorf("failed waiting for reaper container %s port %s/%s to be ready: %w", + reaperContainer.ID[:8], port.Proto(), port.Port(), err) + } + } + } + return &Reaper{ Provider: provider, SessionID: sessionID,