From 221f420ce210f09ee8ddac8b7c3c0c51846e8492 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 17 May 2021 09:04:52 +0200 Subject: [PATCH] ps shows healthcheck only for running container Signed-off-by: Nicolas De Loof --- api/compose/api.go | 1 + cli/cmd/compose/ps.go | 4 +++- local/compose/ps.go | 24 +++++++++++++++++++----- local/compose/ps_test.go | 40 ++++++++-------------------------------- 4 files changed, 31 insertions(+), 38 deletions(-) diff --git a/api/compose/api.go b/api/compose/api.go index 63ab12711..fca9ba510 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -284,6 +284,7 @@ type ContainerSummary struct { Service string State string Health string + ExitCode int Publishers []PortPublisher } diff --git a/cli/cmd/compose/ps.go b/cli/cmd/compose/ps.go index c67ea580b..1379bce21 100644 --- a/cli/cmd/compose/ps.go +++ b/cli/cmd/compose/ps.go @@ -103,8 +103,10 @@ func runPs(ctx context.Context, backend compose.Service, services []string, opts } } status := container.State - if container.Health != "" { + if status == "running" && container.Health != "" { status = fmt.Sprintf("%s (%s)", container.State, container.Health) + } else if status == "exited" || status == "dead" { + status = fmt.Sprintf("%s (%d)", container.State, container.ExitCode) } _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", container.Name, container.Service, status, strings.Join(ports, ", ")) } diff --git a/local/compose/ps.go b/local/compose/ps.go index 99cd6c139..95b0f4ddd 100644 --- a/local/compose/ps.go +++ b/local/compose/ps.go @@ -19,10 +19,10 @@ package compose import ( "context" "fmt" - - "golang.org/x/sync/errgroup" + "sort" "github.com/docker/compose-cli/api/compose" + "golang.org/x/sync/errgroup" ) func (s *composeService) Ps(ctx context.Context, projectName string, options compose.PsOptions) ([]compose.ContainerSummary, error) { @@ -42,6 +42,9 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options com i := i eg.Go(func() error { var publishers []compose.PortPublisher + sort.Slice(container.Ports, func(i, j int) bool { + return container.Ports[i].PrivatePort < container.Ports[j].PrivatePort + }) for _, p := range container.Ports { var url string if p.PublicPort != 0 { @@ -60,9 +63,19 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options com return err } - var health string - if inspect.State != nil && inspect.State.Health != nil { - health = inspect.State.Health.Status + var ( + health string + exitCode int + ) + if inspect.State != nil { + switch inspect.State.Status { + case "running": + if inspect.State.Health != nil { + health = inspect.State.Health.Status + } + case "exited", "dead": + exitCode = inspect.State.ExitCode + } } summary[i] = compose.ContainerSummary{ @@ -72,6 +85,7 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options com Service: container.Labels[serviceLabel], State: container.State, Health: health, + ExitCode: exitCode, Publishers: publishers, } return nil diff --git a/local/compose/ps_test.go b/local/compose/ps_test.go index 3ea5faac1..c21de7b57 100644 --- a/local/compose/ps_test.go +++ b/local/compose/ps_test.go @@ -40,10 +40,10 @@ func TestPs(t *testing.T) { args := filters.NewArgs(projectFilter(testProject)) args.Add("label", "com.docker.compose.oneoff=False") listOpts := apitypes.ContainerListOptions{Filters: args, All: true} - c1, inspect1 := containerDetails("service1", "123", "Running", "healthy") - c2, inspect2 := containerDetails("service1", "456", "Running", "") + c1, inspect1 := containerDetails("service1", "123", "running", "healthy", 0) + c2, inspect2 := containerDetails("service1", "456", "running", "", 0) c2.Ports = []apitypes.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}} - c3, inspect3 := containerDetails("service2", "789", "Running", "") + c3, inspect3 := containerDetails("service2", "789", "exited", "", 130) api.EXPECT().ContainerList(ctx, listOpts).Return([]apitypes.Container{c1, c2, c3}, nil) api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil) api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil) @@ -52,45 +52,21 @@ func TestPs(t *testing.T) { containers, err := tested.Ps(ctx, testProject, compose.PsOptions{}) expected := []compose.ContainerSummary{ - {ID: "123", Name: "123", Project: testProject, Service: "service1", State: "Running", Health: "healthy", Publishers: nil}, - {ID: "456", Name: "456", Project: testProject, Service: "service1", State: "Running", Health: "", Publishers: []compose.PortPublisher{{URL: "localhost:80", TargetPort: 90, PublishedPort: 80}}}, - {ID: "789", Name: "789", Project: testProject, Service: "service2", State: "Running", Health: "", Publishers: nil}, + {ID: "123", Name: "123", Project: testProject, Service: "service1", State: "running", Health: "healthy", Publishers: nil}, + {ID: "456", Name: "456", Project: testProject, Service: "service1", State: "running", Health: "", Publishers: []compose.PortPublisher{{URL: "localhost:80", TargetPort: 90, PublishedPort: 80}}}, + {ID: "789", Name: "789", Project: testProject, Service: "service2", State: "exited", Health: "", ExitCode: 130, Publishers: nil}, } assert.NilError(t, err) assert.DeepEqual(t, containers, expected) } -func TestPsAll(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - api := mocks.NewMockAPIClient(mockCtrl) - tested.apiClient = api - - ctx := context.Background() - listOpts := apitypes.ContainerListOptions{Filters: filters.NewArgs(projectFilter(testProject)), All: true} - c1, inspect1 := containerDetails("service1", "123", "Running", "healthy") - c2, inspect2 := containerDetails("service1", "456", "Stopped", "") - api.EXPECT().ContainerList(ctx, listOpts).Return([]apitypes.Container{c1, c2}, nil) - api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil) - api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil) - - containers, err := tested.Ps(ctx, testProject, compose.PsOptions{All: true}) - - expected := []compose.ContainerSummary{ - {ID: "123", Name: "123", Project: testProject, Service: "service1", State: "Running", Health: "healthy", Publishers: nil}, - {ID: "456", Name: "456", Project: testProject, Service: "service1", State: "Stopped", Health: "", Publishers: nil}, - } - assert.NilError(t, err) - assert.DeepEqual(t, containers, expected) -} - -func containerDetails(service string, id string, status string, health string) (apitypes.Container, apitypes.ContainerJSON) { +func containerDetails(service string, id string, status string, health string, exitCode int) (apitypes.Container, apitypes.ContainerJSON) { container := apitypes.Container{ ID: id, Names: []string{"/" + id}, Labels: containerLabels(service), State: status, } - inspect := apitypes.ContainerJSON{ContainerJSONBase: &apitypes.ContainerJSONBase{State: &apitypes.ContainerState{Status: status, Health: &apitypes.Health{Status: health}}}} + inspect := apitypes.ContainerJSON{ContainerJSONBase: &apitypes.ContainerJSONBase{State: &apitypes.ContainerState{Status: status, Health: &apitypes.Health{Status: health}, ExitCode: exitCode}}} return container, inspect }