diff --git a/cmd/formatter/container.go b/cmd/formatter/container.go index 872635ad4c..1381b52f8b 100644 --- a/cmd/formatter/container.go +++ b/cmd/formatter/container.go @@ -17,6 +17,9 @@ package formatter import ( + "fmt" + "strconv" + "strings" "time" "github.com/docker/cli/cli/command/formatter" @@ -141,6 +144,22 @@ func (c *ContainerContext) Name() string { 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 { return c.c.Service } @@ -150,7 +169,11 @@ func (c *ContainerContext) Image() 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 { @@ -194,3 +217,65 @@ func (c *ContainerContext) Ports() string { } 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 +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 16e3b3a581..dfe4628c9e 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -390,18 +390,25 @@ type PortPublisher struct { // ContainerSummary hold high-level description of a container type ContainerSummary struct { - ID string - Name string - Image string - Command string - Project string - Service string - Created int64 - State string - Status string - Health string - ExitCode int - Publishers PortPublishers + ID string + Name string + Names []string + Image string + Command string + Project string + Service string + Created int64 + State string + Status string + Health string + 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 diff --git a/pkg/compose/ps.go b/pkg/compose/ps.go index 2b2a0b9504..b4f619f668 100644 --- a/pkg/compose/ps.go +++ b/pkg/compose/ps.go @@ -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{ - ID: container.ID, - Name: getCanonicalContainerName(container), - Image: container.Image, - Project: container.Labels[api.ProjectLabel], - Service: container.Labels[api.ServiceLabel], - Command: container.Command, - State: container.State, - Status: container.Status, - Created: container.Created, - Health: health, - ExitCode: exitCode, - Publishers: publishers, + ID: container.ID, + Name: getCanonicalContainerName(container), + Names: container.Names, + Image: container.Image, + Project: container.Labels[api.ProjectLabel], + Service: container.Labels[api.ServiceLabel], + Command: container.Command, + State: container.State, + Status: container.Status, + Created: container.Created, + Labels: container.Labels, + SizeRw: container.SizeRw, + SizeRootFs: container.SizeRootFs, + Mounts: mounts, + LocalVolumes: local, + Networks: networks, + Health: health, + ExitCode: exitCode, + Publishers: publishers, } return nil }) diff --git a/pkg/compose/ps_test.go b/pkg/compose/ps_test.go index 015660a290..5c2e534539 100644 --- a/pkg/compose/ps_test.go +++ b/pkg/compose/ps_test.go @@ -54,13 +54,34 @@ func TestPs(t *testing.T) { containers, err := tested.Ps(ctx, strings.ToLower(testProject), compose.PsOptions{}) expected := []compose.ContainerSummary{ - {ID: "123", Name: "123", Image: "foo", Project: strings.ToLower(testProject), Service: "service1", - State: "running", Health: "healthy", Publishers: nil}, - {ID: "456", Name: "456", 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, + 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: "", - Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}}}, - {ID: "789", Name: "789", Image: "foo", Project: strings.ToLower(testProject), Service: "service2", - State: "exited", Health: "", ExitCode: 130, Publishers: nil}, + Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}}, + 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: "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.DeepEqual(t, containers, expected) diff --git a/pkg/e2e/ps_test.go b/pkg/e2e/ps_test.go index ccaa3c2b61..11a9b87155 100644 --- a/pkg/e2e/ps_test.go +++ b/pkg/e2e/ps_test.go @@ -62,10 +62,15 @@ func TestPs(t *testing.T) { t.Run("json", func(t *testing.T) { res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps", "--format", "json") - var output []api.ContainerSummary - dec := json.NewDecoder(strings.NewReader(res.Stdout())) + type element struct { + Name string + Publishers api.PortPublishers + } + var output []element + out := res.Stdout() + dec := json.NewDecoder(strings.NewReader(out)) for dec.More() { - var s api.ContainerSummary + var s element require.NoError(t, dec.Decode(&s), "Failed to unmarshal ps JSON output") output = append(output, s) }