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)
... | ... |
@@ -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 |