add support for attributes exposed by docker ps

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2023-08-24 09:08:32 +02:00 committed by Nicolas De loof
parent 1054792b47
commit 41682acc77
5 changed files with 181 additions and 34 deletions

View File

@ -17,6 +17,9 @@
package formatter package formatter
import ( import (
"fmt"
"strconv"
"strings"
"time" "time"
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
@ -141,6 +144,22 @@ func (c *ContainerContext) Name() string {
return c.c.Name return c.c.Name
} }
// Names returns a comma-separated string of the container's names, with their
// slash (/) prefix stripped. Additional names for the container (related to the
// legacy `--link` feature) are omitted.
func (c *ContainerContext) Names() string {
names := formatter.StripNamePrefix(c.c.Names)
if c.trunc {
for _, name := range names {
if len(strings.Split(name, "/")) == 1 {
names = []string{name}
break
}
}
}
return strings.Join(names, ",")
}
func (c *ContainerContext) Service() string { func (c *ContainerContext) Service() string {
return c.c.Service return c.c.Service
} }
@ -150,7 +169,11 @@ func (c *ContainerContext) Image() string {
} }
func (c *ContainerContext) Command() string { func (c *ContainerContext) Command() string {
return c.c.Command command := c.c.Command
if c.trunc {
command = formatter.Ellipsis(command, 20)
}
return strconv.Quote(command)
} }
func (c *ContainerContext) CreatedAt() string { func (c *ContainerContext) CreatedAt() string {
@ -194,3 +217,65 @@ func (c *ContainerContext) Ports() string {
} }
return formatter.DisplayablePorts(ports) return formatter.DisplayablePorts(ports)
} }
// Labels returns a comma-separated string of labels present on the container.
func (c *ContainerContext) Labels() string {
if c.c.Labels == nil {
return ""
}
var joinLabels []string
for k, v := range c.c.Labels {
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(joinLabels, ",")
}
// Label returns the value of the label with the given name or an empty string
// if the given label does not exist.
func (c *ContainerContext) Label(name string) string {
if c.c.Labels == nil {
return ""
}
return c.c.Labels[name]
}
// Mounts returns a comma-separated string of mount names present on the container.
// If the trunc option is set, names can be truncated (ellipsized).
func (c *ContainerContext) Mounts() string {
var mounts []string
for _, name := range c.c.Mounts {
if c.trunc {
name = formatter.Ellipsis(name, 15)
}
mounts = append(mounts, name)
}
return strings.Join(mounts, ",")
}
// LocalVolumes returns the number of volumes using the "local" volume driver.
func (c *ContainerContext) LocalVolumes() string {
return fmt.Sprintf("%d", c.c.LocalVolumes)
}
// Networks returns a comma-separated string of networks that the container is
// attached to.
func (c *ContainerContext) Networks() string {
return strings.Join(c.c.Networks, ",")
}
// Size returns the container's size and virtual size (e.g. "2B (virtual 21.5MB)")
func (c *ContainerContext) Size() string {
if c.FieldsUsed == nil {
c.FieldsUsed = map[string]interface{}{}
}
c.FieldsUsed["Size"] = struct{}{}
srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)
sv := units.HumanSizeWithPrecision(float64(c.c.SizeRootFs), 3)
sf := srw
if c.c.SizeRootFs > 0 {
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
}
return sf
}

View File

@ -390,18 +390,25 @@ type PortPublisher struct {
// ContainerSummary hold high-level description of a container // ContainerSummary hold high-level description of a container
type ContainerSummary struct { type ContainerSummary struct {
ID string ID string
Name string Name string
Image string Names []string
Command string Image string
Project string Command string
Service string Project string
Created int64 Service string
State string Created int64
Status string State string
Health string Status string
ExitCode int Health string
Publishers PortPublishers ExitCode int
Publishers PortPublishers
Labels map[string]string
SizeRw int64 `json:",omitempty"`
SizeRootFs int64 `json:",omitempty"`
Mounts []string
Networks []string
LocalVolumes int
} }
// PortPublishers is a slice of PortPublisher // PortPublishers is a slice of PortPublisher

View File

@ -78,19 +78,48 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
} }
} }
var (
local int
mounts []string
)
for _, m := range container.Mounts {
name := m.Name
if name == "" {
name = m.Source
}
if m.Driver == "local" {
local++
}
mounts = append(mounts, name)
}
var networks []string
if container.NetworkSettings != nil {
for k := range container.NetworkSettings.Networks {
networks = append(networks, k)
}
}
summary[i] = api.ContainerSummary{ summary[i] = api.ContainerSummary{
ID: container.ID, ID: container.ID,
Name: getCanonicalContainerName(container), Name: getCanonicalContainerName(container),
Image: container.Image, Names: container.Names,
Project: container.Labels[api.ProjectLabel], Image: container.Image,
Service: container.Labels[api.ServiceLabel], Project: container.Labels[api.ProjectLabel],
Command: container.Command, Service: container.Labels[api.ServiceLabel],
State: container.State, Command: container.Command,
Status: container.Status, State: container.State,
Created: container.Created, Status: container.Status,
Health: health, Created: container.Created,
ExitCode: exitCode, Labels: container.Labels,
Publishers: publishers, SizeRw: container.SizeRw,
SizeRootFs: container.SizeRootFs,
Mounts: mounts,
LocalVolumes: local,
Networks: networks,
Health: health,
ExitCode: exitCode,
Publishers: publishers,
} }
return nil return nil
}) })

View File

@ -54,13 +54,34 @@ func TestPs(t *testing.T) {
containers, err := tested.Ps(ctx, strings.ToLower(testProject), compose.PsOptions{}) containers, err := tested.Ps(ctx, strings.ToLower(testProject), compose.PsOptions{})
expected := []compose.ContainerSummary{ expected := []compose.ContainerSummary{
{ID: "123", Name: "123", Image: "foo", Project: strings.ToLower(testProject), Service: "service1", {ID: "123", Name: "123", Names: []string{"/123"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
State: "running", Health: "healthy", Publishers: nil}, State: "running", Health: "healthy", Publishers: nil,
{ID: "456", Name: "456", Image: "foo", Project: strings.ToLower(testProject), Service: "service1", Labels: map[string]string{
compose.ProjectLabel: strings.ToLower(testProject),
compose.ConfigFilesLabel: "/src/pkg/compose/testdata/compose.yaml",
compose.WorkingDirLabel: "/src/pkg/compose/testdata",
compose.ServiceLabel: "service1",
},
},
{ID: "456", Name: "456", Names: []string{"/456"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
State: "running", Health: "", State: "running", Health: "",
Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}}}, Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}},
{ID: "789", Name: "789", Image: "foo", Project: strings.ToLower(testProject), Service: "service2", Labels: map[string]string{
State: "exited", Health: "", ExitCode: 130, Publishers: nil}, compose.ProjectLabel: strings.ToLower(testProject),
compose.ConfigFilesLabel: "/src/pkg/compose/testdata/compose.yaml",
compose.WorkingDirLabel: "/src/pkg/compose/testdata",
compose.ServiceLabel: "service1",
},
},
{ID: "789", Name: "789", Names: []string{"/789"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service2",
State: "exited", Health: "", ExitCode: 130, Publishers: nil,
Labels: map[string]string{
compose.ProjectLabel: strings.ToLower(testProject),
compose.ConfigFilesLabel: "/src/pkg/compose/testdata/compose.yaml",
compose.WorkingDirLabel: "/src/pkg/compose/testdata",
compose.ServiceLabel: "service2",
},
},
} }
assert.NilError(t, err) assert.NilError(t, err)
assert.DeepEqual(t, containers, expected) assert.DeepEqual(t, containers, expected)

View File

@ -62,10 +62,15 @@ func TestPs(t *testing.T) {
t.Run("json", func(t *testing.T) { t.Run("json", func(t *testing.T) {
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps", res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps",
"--format", "json") "--format", "json")
var output []api.ContainerSummary type element struct {
dec := json.NewDecoder(strings.NewReader(res.Stdout())) Name string
Publishers api.PortPublishers
}
var output []element
out := res.Stdout()
dec := json.NewDecoder(strings.NewReader(out))
for dec.More() { for dec.More() {
var s api.ContainerSummary var s element
require.NoError(t, dec.Decode(&s), "Failed to unmarshal ps JSON output") require.NoError(t, dec.Decode(&s), "Failed to unmarshal ps JSON output")
output = append(output, s) output = append(output, s)
} }