Browse code

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 authored on 2015/07/17 13:03:16
Showing 7 changed files
... ...
@@ -31,7 +31,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
31 31
 		since    = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
32 32
 		before   = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
33 33
 		last     = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
34
-		format   = cmd.String([]string{"F", "-format"}, "", "Pretty-print containers using a Go template")
34
+		format   = cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
35 35
 		flFilter = opts.NewListOpts(nil)
36 36
 	)
37 37
 	cmd.Require(flag.Exact, 0)
... ...
@@ -1,6 +1,8 @@
1 1
 package ps
2 2
 
3 3
 import (
4
+	"reflect"
5
+	"strings"
4 6
 	"testing"
5 7
 	"time"
6 8
 
... ...
@@ -26,7 +28,7 @@ func TestContainerContextID(t *testing.T) {
26 26
 		{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
27 27
 		{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
28 28
 		{types.Container{Created: int(unix)}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
29
-		{types.Container{Ports: []types.Port{types.Port{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
29
+		{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
30 30
 		{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
31 31
 		{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
32 32
 		{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
... ...
@@ -36,7 +38,26 @@ func TestContainerContextID(t *testing.T) {
36 36
 	for _, c := range cases {
37 37
 		ctx = containerContext{c: c.container, trunc: c.trunc}
38 38
 		v := c.call()
39
-		if v != c.expValue {
39
+		if strings.Contains(v, ",") {
40
+			// comma-separated values means probably a map input, which won't
41
+			// be guaranteed to have the same order as our expected value
42
+			// We'll create maps and use reflect.DeepEquals to check instead:
43
+			entriesMap := make(map[string]string)
44
+			expMap := make(map[string]string)
45
+			entries := strings.Split(v, ",")
46
+			expectedEntries := strings.Split(c.expValue, ",")
47
+			for _, entry := range entries {
48
+				keyval := strings.Split(entry, "=")
49
+				entriesMap[keyval[0]] = keyval[1]
50
+			}
51
+			for _, expected := range expectedEntries {
52
+				keyval := strings.Split(expected, "=")
53
+				expMap[keyval[0]] = keyval[1]
54
+			}
55
+			if !reflect.DeepEqual(expMap, entriesMap) {
56
+				t.Fatalf("Expected entries: %v, got: %v", c.expValue, v)
57
+			}
58
+		} else if v != c.expValue {
40 59
 			t.Fatalf("Expected %s, was %s\n", c.expValue, v)
41 60
 		}
42 61
 
... ...
@@ -52,17 +73,16 @@ func TestContainerContextID(t *testing.T) {
52 52
 	sid := ctx.Label("com.docker.swarm.swarm-id")
53 53
 	node := ctx.Label("com.docker.swarm.node_name")
54 54
 	if sid != "33" {
55
-		t.Fatal("Expected 33, was %s\n", sid)
55
+		t.Fatalf("Expected 33, was %s\n", sid)
56 56
 	}
57 57
 
58 58
 	if node != "ubuntu" {
59
-		t.Fatal("Expected ubuntu, was %s\n", node)
59
+		t.Fatalf("Expected ubuntu, was %s\n", node)
60 60
 	}
61 61
 
62 62
 	h := ctx.fullHeader()
63 63
 	if h != "SWARM ID\tNODE NAME" {
64
-		t.Fatal("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
64
+		t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
65 65
 
66 66
 	}
67
-
68 67
 }
... ...
@@ -158,7 +158,7 @@ func TestNewJson(t *testing.T) {
158 158
 
159 159
 func TestJsonWithPsFormat(t *testing.T) {
160 160
 	tmpHome, _ := ioutil.TempDir("", "config-test")
161
-	fn := filepath.Join(tmpHome, CONFIGFILE)
161
+	fn := filepath.Join(tmpHome, ConfigFileName)
162 162
 	js := `{
163 163
 		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
164 164
 		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
... ...
@@ -180,7 +180,7 @@ func TestJsonWithPsFormat(t *testing.T) {
180 180
 		t.Fatalf("Failed to save: %q", err)
181 181
 	}
182 182
 
183
-	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE))
183
+	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
184 184
 	if !strings.Contains(string(buf), `"psFormat":`) ||
185 185
 		!strings.Contains(string(buf), "{{.ID}}") {
186 186
 		t.Fatalf("Should have save in new form: %s", string(buf))
... ...
@@ -85,18 +85,26 @@ mechanisms, you must keep in mind the order of precedence among them. Command
85 85
 line options override environment variables and environment variables override
86 86
 properties you specify in a `config.json` file.
87 87
 
88
-The `config.json` file stores a JSON encoding of a single `HttpHeaders`
89
-property. The property specifies a set of headers to include in all messages
88
+The `config.json` file stores a JSON encoding of several properties:
89
+
90
+The property `HttpHeaders` specifies a set of headers to include in all messages
90 91
 sent from the Docker client to the daemon. Docker does not try to interpret or
91 92
 understand these header; it simply puts them into the messages. Docker does
92 93
 not allow these headers to change any headers it sets for itself.
93 94
 
95
+The property `psFormat` specifies the default format for `docker ps` output.
96
+When the `--format` flag is not provided with the `docker ps` command,
97
+Docker's client uses this property. If this property is not set, the client
98
+falls back to the default table format. For a list of supported formatting
99
+directives, see the [**Formatting** section in the `docker ps` documentation](../ps)
100
+
94 101
 Following is a sample `config.json` file:
95 102
 
96 103
     {
97 104
       "HttpHeaders: {
98 105
         "MyHeader": "MyValue"
99
-      }
106
+      },
107
+      "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}"
100 108
     }
101 109
 
102 110
 ## Help
... ...
@@ -24,6 +24,7 @@ weight=1
24 24
       -q, --quiet=false     Only display numeric IDs
25 25
       -s, --size=false      Display total file sizes
26 26
       --since=""            Show created since Id or Name, include non-running
27
+      --format=[]       Pretty-print containers using a Go template
27 28
 
28 29
 Running `docker ps --no-trunc` showing 2 linked containers.
29 30
 
... ...
@@ -60,5 +61,42 @@ The currently supported filters are:
60 60
 
61 61
 This shows all the containers that have exited with status of '0'
62 62
 
63
-
64
-
63
+## Formatting
64
+
65
+The formatting option (`--format`) will pretty-print container output using a Go template.
66
+
67
+Valid placeholders for the Go template are listed below:
68
+
69
+Placeholder | Description
70
+---- | ----
71
+`.ID` | Container ID
72
+`.Image` | Image ID
73
+`.Command` | Quoted command
74
+`.CreatedAt` | Time when the container was created.
75
+`.RunningFor` | Elapsed time since the container was started.
76
+`.Ports` | Exposed ports.
77
+`.Status` | Container status.
78
+`.Size` | Container disk size.
79
+`.Labels` | All labels asigned to the container.
80
+`.Label` | Value of a specific label for this container. For example `{{.Label "com.docker.swarm.cpu"}}`
81
+
82
+When using the `--format` option, the `ps` command will either output the data exactly as the template
83
+declares or, when using the `table` directive, will include column headers as well.
84
+
85
+The following example uses a template without headers and outputs the `ID` and `Command`
86
+entries separated by a colon for all running containers:
87
+
88
+    $ docker ps --format "{{.ID}}: {{.Command}}"
89
+    a87ecb4f327c: /bin/sh -c #(nop) MA
90
+    01946d9d34d8: /bin/sh -c #(nop) MA
91
+    c1d3b0166030: /bin/sh -c yum -y up
92
+    41d50ecd2f57: /bin/sh -c #(nop) MA
93
+
94
+To list all running containers with their labels in a table format you can use:
95
+
96
+    $ docker ps --format "table {{.ID}}\t{{.Labels}}"
97
+    CONTAINER ID        LABELS
98
+    a87ecb4f327c        com.docker.swarm.node=ubuntu,com.docker.swarm.storage=ssd
99
+    01946d9d34d8
100
+    c1d3b0166030        com.docker.swarm.node=debian,com.docker.swarm.cpu=6
101
+    41d50ecd2f57        com.docker.swarm.node=fedora,com.docker.swarm.cpu=3,com.docker.swarm.storage=ssd
... ...
@@ -508,3 +508,34 @@ func (s *DockerSuite) TestPsListContainersFilterCreated(c *check.C) {
508 508
 		c.Fatalf("Expected id %s, got %s for filter, out: %s", cID, containerOut, out)
509 509
 	}
510 510
 }
511
+
512
+func (s *DockerSuite) TestPsFormatMultiNames(c *check.C) {
513
+	//create 2 containers and link them
514
+	dockerCmd(c, "run", "--name=child", "-d", "busybox", "top")
515
+	dockerCmd(c, "run", "--name=parent", "--link=child:linkedone", "-d", "busybox", "top")
516
+
517
+	//use the new format capabilities to only list the names and --no-trunc to get all names
518
+	out, _ := dockerCmd(c, "ps", "--format", "{{.Names}}", "--no-trunc")
519
+	lines := strings.Split(strings.TrimSpace(string(out)), "\n")
520
+	expected := []string{"parent", "child,parent/linkedone"}
521
+	var names []string
522
+	for _, l := range lines {
523
+		names = append(names, l)
524
+	}
525
+	if !reflect.DeepEqual(expected, names) {
526
+		c.Fatalf("Expected array with non-truncated names: %v, got: %v", expected, names)
527
+	}
528
+
529
+	//now list without turning off truncation and make sure we only get the non-link names
530
+	out, _ = dockerCmd(c, "ps", "--format", "{{.Names}}")
531
+	lines = strings.Split(strings.TrimSpace(string(out)), "\n")
532
+	expected = []string{"parent", "child"}
533
+	var truncNames []string
534
+	for _, l := range lines {
535
+		truncNames = append(truncNames, l)
536
+	}
537
+	if !reflect.DeepEqual(expected, truncNames) {
538
+		c.Fatalf("Expected array with truncated names: %v, got: %v", expected, truncNames)
539
+	}
540
+
541
+}
... ...
@@ -16,7 +16,7 @@ docker-ps - List containers
16 16
 [**-q**|**--quiet**[=*false*]]
17 17
 [**-s**|**--size**[=*false*]]
18 18
 [**--since**[=*SINCE*]]
19
-[**-F**|**--format**=*"TEMPLATE"*]
19
+[**--format**=*"TEMPLATE"*]
20 20
 
21 21
 
22 22
 # DESCRIPTION
... ...
@@ -60,7 +60,7 @@ the running containers.
60 60
 **--since**=""
61 61
    Show only containers created since Id or Name, include non-running ones.
62 62
 
63
-**-F**, **--format**=*"TEMPLATE"*
63
+**--format**=*"TEMPLATE"*
64 64
    Pretty-print containers using a Go template.
65 65
    Valid placeholders:
66 66
       .ID - Container ID