Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/compose/service_containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,12 @@ func (s *composeService) isServiceHealthy(ctx context.Context, containers Contai
}

func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers) (bool, int, error) {
if s.dryRun {
// dry-run never actually starts the dependency's container, so it
// can never observe a real "exited" state: simulate immediate
// success instead of polling forever.
return true, 0, nil
}
for _, ctr := range containers {
res, err := s.apiClient().ContainerInspect(ctx, ctr.ID, client.ContainerInspectOptions{})
if err != nil {
Expand Down
18 changes: 18 additions & 0 deletions pkg/compose/service_containers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,24 @@ func TestWaitDependencies(t *testing.T) {
}, nil)
assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers, 0))
})
t.Run("dry-run completes service_completed_successfully immediately", func(t *testing.T) {
tested.(*composeService).dryRun = true

project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{
"init": {Name: "init", Scale: intPtr(1)},
}}
dependencies := types.DependsOnConfig{
"init": {Condition: types.ServiceConditionCompletedSuccessfully, Required: true},
}
containers := Containers{{
ID: "init-ctr",
Names: []string{"/init-ctr"},
Labels: map[string]string{api.ServiceLabel: "init"},
}}
// no ContainerInspect expectation: dry-run must not inspect a
// container that was never created.
assert.NilError(t, tested.(*composeService).waitDependencies(t.Context(), &project, "app", dependencies, containers, 0))
})
}

func TestIsServiceHealthy(t *testing.T) {
Expand Down
7 changes: 7 additions & 0 deletions pkg/compose/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package compose

import (
"context"
"errors"
"fmt"

"github.com/moby/moby/client"
Expand All @@ -27,6 +28,12 @@ import (
)

func (s *composeService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
if s.dryRun {
// dry-run never starts a real container, so there is nothing for
// wait to observe: refuse upfront instead of racing the fake API
// client into a misleading "no containers" error.
return 0, errors.New("wait is not supported in dry-run mode")
}
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false, options.Services...)
if err != nil {
return 0, err
Expand Down
12 changes: 12 additions & 0 deletions pkg/compose/wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ func TestWait_NoContainersAtAll(t *testing.T) {
assert.Equal(t, *allCalls, 1)
}

// TestWait_DryRunNotSupported: dry-run refuses immediately, without
// listing containers.
func TestWait_DryRunNotSupported(t *testing.T) {
tested, _, runningCalls, allCalls := waitTestService(t, nil, nil)
tested.(*composeService).dryRun = true

_, err := tested.Wait(t.Context(), "proj", api.WaitOptions{})
assert.ErrorContains(t, err, "not supported in dry-run mode")
assert.Equal(t, *runningCalls, 0)
assert.Equal(t, *allCalls, 0)
}

// TestWait_RunningContainersSkipFallback: with a running container to
// observe, the fallback listing never runs — a stale exited one-off cannot
// short-circuit the wait.
Expand Down
Loading