Add Health attribute on the docker ps command

Signed-off-by: Muhammad Daffa Dinaya <muhammaddaffadinaya@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Muhammad Daffa Dinaya
2025-07-16 13:20:45 +00:00
committed by Sebastiaan van Stijn
parent 81caabae43
commit 6e7a2c830d
9 changed files with 133 additions and 0 deletions

View File

@@ -5346,6 +5346,29 @@ definitions:
List of mounts used by the container.
items:
$ref: "#/definitions/MountPoint"
Health:
type: "object"
description: |-
Summary of health status
Added in v1.52, before that version all container summary not include Health.
After this attribute introduced, it includes containers with no health checks configured,
or containers that are not running with none
properties:
Status:
type: "string"
description: |-
the health status of the container
enum:
- "none"
- "starting"
- "healthy"
- "unhealthy"
example: "healthy"
FailingStreak:
description: "FailingStreak is the number of consecutive failures"
type: "integer"
example: 0
Driver:
description: "Driver represents a driver (network, logging, secrets)."

View File

@@ -128,6 +128,7 @@ type Summary struct {
NetworkMode string `json:",omitempty"`
Annotations map[string]string `json:",omitempty"`
}
Health *HealthSummary `json:",omitempty"`
NetworkSettings *NetworkSettingsSummary
Mounts []MountPoint
}

View File

@@ -26,6 +26,12 @@ type Health struct {
Log []*HealthcheckResult // Log contains the last few results (oldest first)
}
// HealthSummary stores a summary of the container's healthcheck results.
type HealthSummary struct {
Status HealthStatus // Status is one of [NoHealthcheck], [Starting], [Healthy] or [Unhealthy].
FailingStreak int // FailingStreak is the number of consecutive failures
}
// HealthcheckResult stores information about a single run of a healthcheck probe
type HealthcheckResult struct {
Start time.Time // Start is the time this check started

View File

@@ -296,9 +296,17 @@ func (v *View) GetAllNames() map[string][]string {
// A lock on the Container is not held because these are immutable deep copies.
func (v *View) transform(ctr *Container) *Snapshot {
health := container.NoHealthcheck
failingStreak := 0
if ctr.Health != nil {
health = ctr.Health.Status()
failingStreak = ctr.Health.FailingStreak
}
healthSummary := &container.HealthSummary{
Status: health,
FailingStreak: failingStreak,
}
snapshot := &Snapshot{
Summary: container.Summary{
ID: ctr.ID,
@@ -308,6 +316,7 @@ func (v *View) transform(ctr *Container) *Snapshot {
Mounts: ctr.GetMountPoints(),
State: ctr.State.StateString(),
Status: ctr.State.String(),
Health: healthSummary,
Created: ctr.Created.Unix(),
},
CreatedAt: ctr.Created,

View File

@@ -119,6 +119,12 @@ func (c *containerRouter) getContainersJSON(ctx context.Context, w http.Response
}
}
if versions.LessThan(version, "1.52") {
for _, c := range containers {
c.Health = nil
}
}
return httputils.WriteJSON(w, http.StatusOK, containers)
}

View File

@@ -1,17 +1,21 @@
package container
import (
"context"
"fmt"
"testing"
"time"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/request"
containertypes "github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/poll"
"gotest.tools/v3/skip"
)
@@ -143,3 +147,57 @@ func TestContainerList_ImageManifestPlatform(t *testing.T) {
assert.Check(t, ctr.ImageManifestDescriptor.Platform.Architecture != "")
}
}
func pollForHealthStatusSummary(ctx context.Context, client client.APIClient, containerID string, healthStatus containertypes.HealthStatus) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
containers, err := client.ContainerList(ctx, containertypes.ListOptions{
All: true,
Filters: filters.NewArgs(filters.Arg("id", containerID)),
})
if err != nil {
return poll.Error(err)
}
total := 0
version := client.ClientVersion()
for _, ctr := range containers {
if ctr.Health == nil && versions.LessThan(version, "1.52") {
total++
} else if ctr.Health != nil && ctr.Health.Status == healthStatus && versions.GreaterThanOrEqualTo(version, "1.52") {
total++
}
}
if total == len(containers) {
return poll.Success()
}
return poll.Continue("waiting for container to become %s", healthStatus)
}
}
func TestContainerList_HealthSummary(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
ctx := setupTest(t)
testcases := []struct {
apiVersion string
}{
{apiVersion: "1.51"},
{apiVersion: "1.52"},
}
for _, tc := range testcases {
t.Run(fmt.Sprintf("run with version v%s", tc.apiVersion), func(t *testing.T) {
apiClient := request.NewAPIClient(t, client.WithVersion(tc.apiVersion))
cID := container.Run(ctx, t, apiClient, container.WithTty(true), container.WithWorkingDir("/foo"), func(c *container.TestContainerConfig) {
c.Config.Healthcheck = &containertypes.HealthConfig{
Test: []string{"CMD-SHELL", "if [ \"$PWD\" = \"/foo\" ]; then exit 0; else exit 1; fi;"},
Interval: 50 * time.Millisecond,
Retries: 3,
}
})
poll.WaitOn(t, pollForHealthStatusSummary(ctx, apiClient, cID, containertypes.Healthy))
})
}
}

View File

@@ -5346,6 +5346,29 @@ definitions:
List of mounts used by the container.
items:
$ref: "#/definitions/MountPoint"
Health:
type: "object"
description: |-
Summary of health status
Added in v1.52, before that version all container summary not include Health.
After this attribute introduced, it includes containers with no health checks configured,
or containers that are not running with none
properties:
Status:
type: "string"
description: |-
the health status of the container
enum:
- "none"
- "starting"
- "healthy"
- "unhealthy"
example: "healthy"
FailingStreak:
description: "FailingStreak is the number of consecutive failures"
type: "integer"
example: 0
Driver:
description: "Driver represents a driver (network, logging, secrets)."

View File

@@ -128,6 +128,7 @@ type Summary struct {
NetworkMode string `json:",omitempty"`
Annotations map[string]string `json:",omitempty"`
}
Health *HealthSummary `json:",omitempty"`
NetworkSettings *NetworkSettingsSummary
Mounts []MountPoint
}

View File

@@ -26,6 +26,12 @@ type Health struct {
Log []*HealthcheckResult // Log contains the last few results (oldest first)
}
// HealthSummary stores a summary of the container's healthcheck results.
type HealthSummary struct {
Status HealthStatus // Status is one of [NoHealthcheck], [Starting], [Healthy] or [Unhealthy].
FailingStreak int // FailingStreak is the number of consecutive failures
}
// HealthcheckResult stores information about a single run of a healthcheck probe
type HealthcheckResult struct {
Start time.Time // Start is the time this check started