diff --git a/api/client/ps.go b/api/client/ps.go index 97f3207fc4..28b9615639 100644 --- a/api/client/ps.go +++ b/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") 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") - 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) ) cmd.Require(flag.Exact, 0) diff --git a/api/client/ps/custom_test.go b/api/client/ps/custom_test.go index 9882d2bba4..d04c9597db 100644 --- a/api/client/ps/custom_test.go +++ b/api/client/ps/custom_test.go @@ -1,6 +1,8 @@ package ps import ( + "reflect" + "strings" "testing" "time" @@ -26,7 +28,7 @@ func TestContainerContextID(t *testing.T) { {types.Container{Image: ""}, true, "", imageHeader, ctx.Image}, {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{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{SizeRw: 10}, true, "10 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 { ctx = containerContext{c: c.container, trunc: c.trunc} 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) } @@ -52,17 +73,16 @@ func TestContainerContextID(t *testing.T) { sid := ctx.Label("com.docker.swarm.swarm-id") node := ctx.Label("com.docker.swarm.node_name") if sid != "33" { - t.Fatal("Expected 33, was %s\n", sid) + t.Fatalf("Expected 33, was %s\n", sid) } if node != "ubuntu" { - t.Fatal("Expected ubuntu, was %s\n", node) + t.Fatalf("Expected ubuntu, was %s\n", node) } h := ctx.fullHeader() 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) } - } diff --git a/cliconfig/config_test.go b/cliconfig/config_test.go index 93c4519d8b..25fb58a45a 100644 --- a/cliconfig/config_test.go +++ b/cliconfig/config_test.go @@ -158,7 +158,7 @@ func TestNewJson(t *testing.T) { func TestJsonWithPsFormat(t *testing.T) { tmpHome, _ := ioutil.TempDir("", "config-test") - fn := filepath.Join(tmpHome, CONFIGFILE) + fn := filepath.Join(tmpHome, ConfigFileName) js := `{ "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, "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) } - buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) if !strings.Contains(string(buf), `"psFormat":`) || !strings.Contains(string(buf), "{{.ID}}") { t.Fatalf("Should have save in new form: %s", string(buf)) diff --git a/docs/reference/commandline/cli.md b/docs/reference/commandline/cli.md index 69a6e3e31e..c6561cd365 100644 --- a/docs/reference/commandline/cli.md +++ b/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 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 understand these header; it simply puts them into the messages. Docker does 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: { "HttpHeaders: { "MyHeader": "MyValue" - } + }, + "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}" } ## Help diff --git a/docs/reference/commandline/ps.md b/docs/reference/commandline/ps.md index 2c68dd73ff..9511336fc1 100644 --- a/docs/reference/commandline/ps.md +++ b/docs/reference/commandline/ps.md @@ -24,6 +24,7 @@ weight=1 -q, --quiet=false Only display numeric IDs -s, --size=false Display total file sizes --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. @@ -60,5 +61,42 @@ The currently supported filters are: 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 diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 7419b28e31..e279563f4c 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/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) } } + +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) + } + +} diff --git a/man/docker-ps.1.md b/man/docker-ps.1.md index d8c0db68d7..06c105115c 100644 --- a/man/docker-ps.1.md +++ b/man/docker-ps.1.md @@ -16,7 +16,7 @@ docker-ps - List containers [**-q**|**--quiet**[=*false*]] [**-s**|**--size**[=*false*]] [**--since**[=*SINCE*]] -[**-F**|**--format**=*"TEMPLATE"*] +[**--format**=*"TEMPLATE"*] # DESCRIPTION @@ -60,7 +60,7 @@ the running containers. **--since**="" Show only containers created since Id or Name, include non-running ones. -**-F**, **--format**=*"TEMPLATE"* +**--format**=*"TEMPLATE"* Pretty-print containers using a Go template. Valid placeholders: .ID - Container ID