Browse Source

ps --format: Add config.js doc, fix gofmt, add integration tests

Re-add the docs from @calavera's PR to the moved cli cmd reference docs.
Fix gofmt and vet issues from carried commits
Add integration test for using format with --no-trunc and multi-names
Fix custom_test map order dependency on expected value check
Add docs to reference/commandline/ps.md
Remove "-F" flag option from original carried PR content

Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com> (github: estesp)
Phil Estes 10 years ago
parent
commit
542b58d8f7

+ 1 - 1
api/client/ps.go

@@ -31,7 +31,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 		since    = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
 		since    = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
 		before   = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
 		before   = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
 		last     = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
 		last     = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
-		format   = cmd.String([]string{"F", "-format"}, "", "Pretty-print containers using a Go template")
+		format   = cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
 		flFilter = opts.NewListOpts(nil)
 		flFilter = opts.NewListOpts(nil)
 	)
 	)
 	cmd.Require(flag.Exact, 0)
 	cmd.Require(flag.Exact, 0)

+ 26 - 6
api/client/ps/custom_test.go

@@ -1,6 +1,8 @@
 package ps
 package ps
 
 
 import (
 import (
+	"reflect"
+	"strings"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
@@ -26,7 +28,7 @@ func TestContainerContextID(t *testing.T) {
 		{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
 		{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
 		{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
 		{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
 		{types.Container{Created: int(unix)}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
 		{types.Container{Created: int(unix)}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
-		{types.Container{Ports: []types.Port{types.Port{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
+		{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
 		{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
 		{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
 		{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
 		{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
 		{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
 		{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
@@ -36,7 +38,26 @@ func TestContainerContextID(t *testing.T) {
 	for _, c := range cases {
 	for _, c := range cases {
 		ctx = containerContext{c: c.container, trunc: c.trunc}
 		ctx = containerContext{c: c.container, trunc: c.trunc}
 		v := c.call()
 		v := c.call()
-		if v != c.expValue {
+		if strings.Contains(v, ",") {
+			// comma-separated values means probably a map input, which won't
+			// be guaranteed to have the same order as our expected value
+			// We'll create maps and use reflect.DeepEquals to check instead:
+			entriesMap := make(map[string]string)
+			expMap := make(map[string]string)
+			entries := strings.Split(v, ",")
+			expectedEntries := strings.Split(c.expValue, ",")
+			for _, entry := range entries {
+				keyval := strings.Split(entry, "=")
+				entriesMap[keyval[0]] = keyval[1]
+			}
+			for _, expected := range expectedEntries {
+				keyval := strings.Split(expected, "=")
+				expMap[keyval[0]] = keyval[1]
+			}
+			if !reflect.DeepEqual(expMap, entriesMap) {
+				t.Fatalf("Expected entries: %v, got: %v", c.expValue, v)
+			}
+		} else if v != c.expValue {
 			t.Fatalf("Expected %s, was %s\n", c.expValue, v)
 			t.Fatalf("Expected %s, was %s\n", c.expValue, v)
 		}
 		}
 
 
@@ -52,17 +73,16 @@ func TestContainerContextID(t *testing.T) {
 	sid := ctx.Label("com.docker.swarm.swarm-id")
 	sid := ctx.Label("com.docker.swarm.swarm-id")
 	node := ctx.Label("com.docker.swarm.node_name")
 	node := ctx.Label("com.docker.swarm.node_name")
 	if sid != "33" {
 	if sid != "33" {
-		t.Fatal("Expected 33, was %s\n", sid)
+		t.Fatalf("Expected 33, was %s\n", sid)
 	}
 	}
 
 
 	if node != "ubuntu" {
 	if node != "ubuntu" {
-		t.Fatal("Expected ubuntu, was %s\n", node)
+		t.Fatalf("Expected ubuntu, was %s\n", node)
 	}
 	}
 
 
 	h := ctx.fullHeader()
 	h := ctx.fullHeader()
 	if h != "SWARM ID\tNODE NAME" {
 	if h != "SWARM ID\tNODE NAME" {
-		t.Fatal("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
+		t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
 
 
 	}
 	}
-
 }
 }

+ 2 - 2
cliconfig/config_test.go

@@ -158,7 +158,7 @@ func TestNewJson(t *testing.T) {
 
 
 func TestJsonWithPsFormat(t *testing.T) {
 func TestJsonWithPsFormat(t *testing.T) {
 	tmpHome, _ := ioutil.TempDir("", "config-test")
 	tmpHome, _ := ioutil.TempDir("", "config-test")
-	fn := filepath.Join(tmpHome, CONFIGFILE)
+	fn := filepath.Join(tmpHome, ConfigFileName)
 	js := `{
 	js := `{
 		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
 		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
 		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
 		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
@@ -180,7 +180,7 @@ func TestJsonWithPsFormat(t *testing.T) {
 		t.Fatalf("Failed to save: %q", err)
 		t.Fatalf("Failed to save: %q", err)
 	}
 	}
 
 
-	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
+	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
 	if !strings.Contains(string(buf), `"psFormat":`) ||
 	if !strings.Contains(string(buf), `"psFormat":`) ||
 		!strings.Contains(string(buf), "{{.ID}}") {
 		!strings.Contains(string(buf), "{{.ID}}") {
 		t.Fatalf("Should have save in new form: %s", string(buf))
 		t.Fatalf("Should have save in new form: %s", string(buf))

+ 11 - 3
docs/reference/commandline/cli.md

@@ -85,18 +85,26 @@ mechanisms, you must keep in mind the order of precedence among them. Command
 line options override environment variables and environment variables override
 line options override environment variables and environment variables override
 properties you specify in a `config.json` file.
 properties you specify in a `config.json` file.
 
 
-The `config.json` file stores a JSON encoding of a single `HttpHeaders`
-property. The property specifies a set of headers to include in all messages
+The `config.json` file stores a JSON encoding of several properties:
+
+The property `HttpHeaders` specifies a set of headers to include in all messages
 sent from the Docker client to the daemon. Docker does not try to interpret or
 sent from the Docker client to the daemon. Docker does not try to interpret or
 understand these header; it simply puts them into the messages. Docker does
 understand these header; it simply puts them into the messages. Docker does
 not allow these headers to change any headers it sets for itself.
 not allow these headers to change any headers it sets for itself.
 
 
+The property `psFormat` specifies the default format for `docker ps` output.
+When the `--format` flag is not provided with the `docker ps` command,
+Docker's client uses this property. If this property is not set, the client
+falls back to the default table format. For a list of supported formatting
+directives, see the [**Formatting** section in the `docker ps` documentation](../ps)
+
 Following is a sample `config.json` file:
 Following is a sample `config.json` file:
 
 
     {
     {
       "HttpHeaders: {
       "HttpHeaders: {
         "MyHeader": "MyValue"
         "MyHeader": "MyValue"
-      }
+      },
+      "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}"
     }
     }
 
 
 ## Help
 ## Help

+ 40 - 2
docs/reference/commandline/ps.md

@@ -24,6 +24,7 @@ weight=1
       -q, --quiet=false     Only display numeric IDs
       -q, --quiet=false     Only display numeric IDs
       -s, --size=false      Display total file sizes
       -s, --size=false      Display total file sizes
       --since=""            Show created since Id or Name, include non-running
       --since=""            Show created since Id or Name, include non-running
+      --format=[]       Pretty-print containers using a Go template
 
 
 Running `docker ps --no-trunc` showing 2 linked containers.
 Running `docker ps --no-trunc` showing 2 linked containers.
 
 
@@ -60,5 +61,42 @@ The currently supported filters are:
 
 
 This shows all the containers that have exited with status of '0'
 This shows all the containers that have exited with status of '0'
 
 
-
-
+## Formatting
+
+The formatting option (`--format`) will pretty-print container output using a Go template.
+
+Valid placeholders for the Go template are listed below:
+
+Placeholder | Description
+---- | ----
+`.ID` | Container ID
+`.Image` | Image ID
+`.Command` | Quoted command
+`.CreatedAt` | Time when the container was created.
+`.RunningFor` | Elapsed time since the container was started.
+`.Ports` | Exposed ports.
+`.Status` | Container status.
+`.Size` | Container disk size.
+`.Labels` | All labels asigned to the container.
+`.Label` | Value of a specific label for this container. For example `{{.Label "com.docker.swarm.cpu"}}`
+
+When using the `--format` option, the `ps` command will either output the data exactly as the template
+declares or, when using the `table` directive, will include column headers as well.
+
+The following example uses a template without headers and outputs the `ID` and `Command`
+entries separated by a colon for all running containers:
+
+    $ docker ps --format "{{.ID}}: {{.Command}}"
+    a87ecb4f327c: /bin/sh -c #(nop) MA
+    01946d9d34d8: /bin/sh -c #(nop) MA
+    c1d3b0166030: /bin/sh -c yum -y up
+    41d50ecd2f57: /bin/sh -c #(nop) MA
+
+To list all running containers with their labels in a table format you can use:
+
+    $ docker ps --format "table {{.ID}}\t{{.Labels}}"
+    CONTAINER ID        LABELS
+    a87ecb4f327c        com.docker.swarm.node=ubuntu,com.docker.swarm.storage=ssd
+    01946d9d34d8
+    c1d3b0166030        com.docker.swarm.node=debian,com.docker.swarm.cpu=6
+    41d50ecd2f57        com.docker.swarm.node=fedora,com.docker.swarm.cpu=3,com.docker.swarm.storage=ssd

+ 31 - 0
integration-cli/docker_cli_ps_test.go

@@ -508,3 +508,34 @@ func (s *DockerSuite) TestPsListContainersFilterCreated(c *check.C) {
 		c.Fatalf("Expected id %s, got %s for filter, out: %s", cID, containerOut, out)
 		c.Fatalf("Expected id %s, got %s for filter, out: %s", cID, containerOut, out)
 	}
 	}
 }
 }
+
+func (s *DockerSuite) TestPsFormatMultiNames(c *check.C) {
+	//create 2 containers and link them
+	dockerCmd(c, "run", "--name=child", "-d", "busybox", "top")
+	dockerCmd(c, "run", "--name=parent", "--link=child:linkedone", "-d", "busybox", "top")
+
+	//use the new format capabilities to only list the names and --no-trunc to get all names
+	out, _ := dockerCmd(c, "ps", "--format", "{{.Names}}", "--no-trunc")
+	lines := strings.Split(strings.TrimSpace(string(out)), "\n")
+	expected := []string{"parent", "child,parent/linkedone"}
+	var names []string
+	for _, l := range lines {
+		names = append(names, l)
+	}
+	if !reflect.DeepEqual(expected, names) {
+		c.Fatalf("Expected array with non-truncated names: %v, got: %v", expected, names)
+	}
+
+	//now list without turning off truncation and make sure we only get the non-link names
+	out, _ = dockerCmd(c, "ps", "--format", "{{.Names}}")
+	lines = strings.Split(strings.TrimSpace(string(out)), "\n")
+	expected = []string{"parent", "child"}
+	var truncNames []string
+	for _, l := range lines {
+		truncNames = append(truncNames, l)
+	}
+	if !reflect.DeepEqual(expected, truncNames) {
+		c.Fatalf("Expected array with truncated names: %v, got: %v", expected, truncNames)
+	}
+
+}

+ 2 - 2
man/docker-ps.1.md

@@ -16,7 +16,7 @@ docker-ps - List containers
 [**-q**|**--quiet**[=*false*]]
 [**-q**|**--quiet**[=*false*]]
 [**-s**|**--size**[=*false*]]
 [**-s**|**--size**[=*false*]]
 [**--since**[=*SINCE*]]
 [**--since**[=*SINCE*]]
-[**-F**|**--format**=*"TEMPLATE"*]
+[**--format**=*"TEMPLATE"*]
 
 
 
 
 # DESCRIPTION
 # DESCRIPTION
@@ -60,7 +60,7 @@ the running containers.
 **--since**=""
 **--since**=""
    Show only containers created since Id or Name, include non-running ones.
    Show only containers created since Id or Name, include non-running ones.
 
 
-**-F**, **--format**=*"TEMPLATE"*
+**--format**=*"TEMPLATE"*
    Pretty-print containers using a Go template.
    Pretty-print containers using a Go template.
    Valid placeholders:
    Valid placeholders:
       .ID - Container ID
       .ID - Container ID