handle healthcheck.disable true in isServiceHealthy

Signed-off-by: Stavros Kois <s.kois@outlook.com>
This commit is contained in:
Stavros Kois 2026-01-05 11:44:07 +02:00 committed by Nicolas De loof
parent 0a07df0e5b
commit 2f108ffaa8
2 changed files with 144 additions and 1 deletions

View file

@ -833,7 +833,8 @@ func (s *composeService) isServiceHealthy(ctx context.Context, containers Contai
return false, fmt.Errorf("container %s exited (%d)", name, ctr.State.ExitCode)
}
if ctr.Config.Healthcheck == nil && fallbackRunning {
noHealthcheck := ctr.Config.Healthcheck == nil || (len(ctr.Config.Healthcheck.Test) > 0 && ctr.Config.Healthcheck.Test[0] == "NONE")
if noHealthcheck && fallbackRunning {
// Container does not define a health check, but we can fall back to "running" state
return ctr.State != nil && ctr.State.Status == container.StateRunning, nil
}

View file

@ -250,6 +250,148 @@ func TestWaitDependencies(t *testing.T) {
})
}
func TestIsServiceHealthy(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested, err := NewComposeService(cli)
assert.NilError(t, err)
cli.EXPECT().Client().Return(apiClient).AnyTimes()
ctx := context.Background()
t.Run("disabled healthcheck with fallback to running", func(t *testing.T) {
containerID := "test-container-id"
containers := Containers{
{ID: containerID},
}
// Container with disabled healthcheck (Test: ["NONE"])
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: containerID,
Name: "test-container",
State: &container.State{Status: "running"},
},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
},
},
}, nil)
isHealthy, err := tested.(*composeService).isServiceHealthy(ctx, containers, true)
assert.NilError(t, err)
assert.Equal(t, true, isHealthy, "Container with disabled healthcheck should be considered healthy when running with fallbackRunning=true")
})
t.Run("disabled healthcheck without fallback", func(t *testing.T) {
containerID := "test-container-id"
containers := Containers{
{ID: containerID},
}
// Container with disabled healthcheck (Test: ["NONE"]) but fallbackRunning=false
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: containerID,
Name: "test-container",
State: &container.State{Status: "running"},
},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
},
},
}, nil)
_, err := tested.(*composeService).isServiceHealthy(ctx, containers, false)
assert.ErrorContains(t, err, "has no healthcheck configured")
})
t.Run("no healthcheck with fallback to running", func(t *testing.T) {
containerID := "test-container-id"
containers := Containers{
{ID: containerID},
}
// Container with no healthcheck at all
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: containerID,
Name: "test-container",
State: &container.State{Status: "running"},
},
Config: &container.Config{
Healthcheck: nil,
},
}, nil)
isHealthy, err := tested.(*composeService).isServiceHealthy(ctx, containers, true)
assert.NilError(t, err)
assert.Equal(t, true, isHealthy, "Container with no healthcheck should be considered healthy when running with fallbackRunning=true")
})
t.Run("exited container with disabled healthcheck", func(t *testing.T) {
containerID := "test-container-id"
containers := Containers{
{ID: containerID},
}
// Container with disabled healthcheck but exited
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: containerID,
Name: "test-container",
State: &container.State{
Status: "exited",
ExitCode: 1,
},
},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"NONE"},
},
},
}, nil)
_, err := tested.(*composeService).isServiceHealthy(ctx, containers, true)
assert.ErrorContains(t, err, "exited")
})
t.Run("healthy container with healthcheck", func(t *testing.T) {
containerID := "test-container-id"
containers := Containers{
{ID: containerID},
}
// Container with actual healthcheck that is healthy
apiClient.EXPECT().ContainerInspect(ctx, containerID).Return(container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
ID: containerID,
Name: "test-container",
State: &container.State{
Status: "running",
Health: &container.Health{
Status: container.Healthy,
},
},
},
Config: &container.Config{
Healthcheck: &container.HealthConfig{
Test: []string{"CMD", "curl", "-f", "http://localhost"},
},
},
}, nil)
isHealthy, err := tested.(*composeService).isServiceHealthy(ctx, containers, false)
assert.NilError(t, err)
assert.Equal(t, true, isHealthy, "Container with healthy status should be healthy")
})
}
func TestCreateMobyContainer(t *testing.T) {
t.Run("connects container networks one by one if API <1.44", func(t *testing.T) {
mockCtrl := gomock.NewController(t)