浏览代码

add support for attributes exposed by `docker ps`

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 2 年之前
父节点
当前提交
41682acc77
共有 5 个文件被更改,包括 181 次插入34 次删除
  1. 86 1
      cmd/formatter/container.go
  2. 19 12
      pkg/api/api.go
  3. 41 12
      pkg/compose/ps.go
  4. 27 6
      pkg/compose/ps_test.go
  5. 8 3
      pkg/e2e/ps_test.go

+ 86 - 1
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
+}

+ 19 - 12
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

+ 41 - 12
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
 		})

+ 27 - 6
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)

+ 8 - 3
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)
 		}