diff --git a/api/client/formatter/custom.go b/api/client/formatter/custom.go index 8a680705ca..9ac457a414 100644 --- a/api/client/formatter/custom.go +++ b/api/client/formatter/custom.go @@ -31,6 +31,7 @@ const ( repositoryHeader = "REPOSITORY" tagHeader = "TAG" digestHeader = "DIGEST" + mountsHeader = "MOUNTS" ) type containerContext struct { @@ -142,6 +143,20 @@ func (c *containerContext) Label(name string) string { return c.c.Labels[name] } +func (c *containerContext) Mounts() string { + c.addHeader(mountsHeader) + + var mounts []string + for _, m := range c.c.Mounts { + name := m.Name + if c.trunc { + name = stringutils.Truncate(name, 15) + } + mounts = append(mounts, name) + } + return strings.Join(mounts, ",") +} + type imageContext struct { baseSubContext trunc bool diff --git a/daemon/list.go b/daemon/list.go index 5cce6132f1..e6f4ab3b6c 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -9,6 +9,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/container" "github.com/docker/docker/image" + "github.com/docker/docker/volume" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" networktypes "github.com/docker/engine-api/types/network" @@ -306,6 +307,27 @@ func includeContainerInList(container *container.Container, ctx *listContext) it return excludeContainer } + if ctx.filters.Include("volume") { + volumesByName := make(map[string]*volume.MountPoint) + for _, m := range container.MountPoints { + volumesByName[m.Name] = m + } + + volumeExist := fmt.Errorf("volume mounted in container") + err := ctx.filters.WalkValues("volume", func(value string) error { + if _, exist := container.MountPoints[value]; exist { + return volumeExist + } + if _, exist := volumesByName[value]; exist { + return volumeExist + } + return nil + }) + if err != volumeExist { + return excludeContainer + } + } + if ctx.ancestorFilter { if len(ctx.images) == 0 { return excludeContainer @@ -419,6 +441,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li newC.SizeRootFs = sizeRootFs } newC.Labels = container.Config.Labels + newC.Mounts = addMountPoints(container) return newC, nil } diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index 30871d0875..4689fade52 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -116,6 +116,7 @@ This section lists each version from latest to oldest. Each listing includes a [Docker Remote API v1.23](docker_remote_api_v1.23.md) documentation * `GET /containers/json` returns the state of the container, one of `created`, `restarting`, `running`, `paused`, `exited` or `dead`. +* `GET /containers/json` returns the mount points for the container. * `GET /networks/(name)` now returns an `Internal` field showing whether the network is internal or not. diff --git a/docs/reference/api/docker_remote_api_v1.23.md b/docs/reference/api/docker_remote_api_v1.23.md index 422920f083..a21cfe1902 100644 --- a/docs/reference/api/docker_remote_api_v1.23.md +++ b/docs/reference/api/docker_remote_api_v1.23.md @@ -73,7 +73,18 @@ List containers "MacAddress": "02:42:ac:11:00:02" } } - } + }, + "Mounts": [ + { + "Name": "fac362...80535", + "Source": "/data", + "Destination": "/data", + "Driver": "local", + "Mode": "ro,Z", + "RW": false, + "Propagation": "" + } + ] }, { "Id": "9cd87474be90", @@ -102,8 +113,8 @@ List containers "MacAddress": "02:42:ac:11:00:08" } } - } - + }, + "Mounts": [] }, { "Id": "3176a2479c92", @@ -132,8 +143,8 @@ List containers "MacAddress": "02:42:ac:11:00:06" } } - } - + }, + "Mounts": [] }, { "Id": "4cb07b47f9fb", @@ -162,8 +173,8 @@ List containers "MacAddress": "02:42:ac:11:00:05" } } - } - + }, + "Mounts": [] } ] @@ -184,6 +195,10 @@ Query Parameters: - `status=`(`created`|`restarting`|`running`|`paused`|`exited`|`dead`) - `label=key` or `label="key=value"` of a container label - `isolation=`(`default`|`process`|`hyperv`) (Windows daemon only) + - `ancestor`=(`[:]`, `` or ``) + - `before`=(`` or ``) + - `since`=(`` or ``) + - `volume`=(`` or ``) Status Codes: diff --git a/docs/reference/commandline/ps.md b/docs/reference/commandline/ps.md index 0ec16cf2c1..af82322e87 100644 --- a/docs/reference/commandline/ps.md +++ b/docs/reference/commandline/ps.md @@ -60,6 +60,7 @@ The currently supported filters are: * before (container's id or name) - filters containers created before given id or name * since (container's id or name) - filters containers created since given id or name * isolation (default|process|hyperv) (Windows daemon only) +* volume (volume name or mount point) - filters containers that mount volumes. #### Label @@ -193,6 +194,18 @@ with the same containers as in `before` filter: 9c3527ed70ce busybox "top" 10 minutes ago Up 10 minutes desperate_dubinsky 4aace5031105 busybox "top" 10 minutes ago Up 10 minutes focused_hamilton +#### Volume + +The `volume` filter shows only containers that mount a specific volume or have a volume mounted in a specific path: + + $ docker ps --filter volume=remote-volume --format "table {{.ID}}\t{{.Mounts}}" + CONTAINER ID MOUNTS + 9c3527ed70ce remote-volume + + $ docker ps --filter volume=/data --format "table {{.ID}}\t{{.Mounts}}" + CONTAINER ID MOUNTS + 9c3527ed70ce remote-volume + ## Formatting @@ -213,6 +226,7 @@ Placeholder | Description `.Names` | Container names. `.Labels` | All labels assigned to the container. `.Label` | Value of a specific label for this container. For example `{{.Label "com.docker.swarm.cpu"}}` +`.Mounts` | Names of the volumes mounted in this container. 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. diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index f72bf6b21d..065c39c23f 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -734,3 +734,56 @@ func (s *DockerSuite) TestPsNotShowPortsOfStoppedContainer(c *check.C) { fields = strings.Fields(lines[1]) c.Assert(fields[len(fields)-2], checker.Not(checker.Equals), expected, check.Commentf("Should not got %v", expected)) } + +func (s *DockerSuite) TestPsShowMounts(c *check.C) { + prefix, slash := getPrefixAndSlashFromDaemonPlatform() + + mp := prefix + slash + "test" + + dockerCmd(c, "volume", "create", "--name", "ps-volume-test") + runSleepingContainer(c, "--name=volume-test-1", "--volume", "ps-volume-test:"+mp) + c.Assert(waitRun("volume-test-1"), checker.IsNil) + runSleepingContainer(c, "--name=volume-test-2", "--volume", mp) + c.Assert(waitRun("volume-test-2"), checker.IsNil) + + out, _ := dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}") + + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + c.Assert(lines, checker.HasLen, 2) + + fields := strings.Fields(lines[0]) + c.Assert(fields, checker.HasLen, 2) + + annonymounsVolumeID := fields[1] + + fields = strings.Fields(lines[1]) + c.Assert(fields[1], checker.Equals, "ps-volume-test") + + // filter by volume name + out, _ = dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}", "--filter", "volume=ps-volume-test") + + lines = strings.Split(strings.TrimSpace(string(out)), "\n") + c.Assert(lines, checker.HasLen, 1) + + fields = strings.Fields(lines[0]) + c.Assert(fields[1], checker.Equals, "ps-volume-test") + + // empty results filtering by unknown volume + out, _ = dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}", "--filter", "volume=this-volume-should-not-exist") + c.Assert(strings.TrimSpace(string(out)), checker.HasLen, 0) + + // filter by mount destination + out, _ = dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}", "--filter", "volume="+mp) + + lines = strings.Split(strings.TrimSpace(string(out)), "\n") + c.Assert(lines, checker.HasLen, 2) + + fields = strings.Fields(lines[0]) + c.Assert(fields[1], checker.Equals, annonymounsVolumeID) + fields = strings.Fields(lines[1]) + c.Assert(fields[1], checker.Equals, "ps-volume-test") + + // empty results filtering by unknown mount point + out, _ = dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}", "--filter", "volume="+prefix+slash+"this-path-was-never-mounted") + c.Assert(strings.TrimSpace(string(out)), checker.HasLen, 0) +} diff --git a/man/docker-ps.1.md b/man/docker-ps.1.md index 6e1fe9a6c0..f567966487 100644 --- a/man/docker-ps.1.md +++ b/man/docker-ps.1.md @@ -35,6 +35,7 @@ the running containers. - before=(|) - since=(|) - ancestor=([:tag]||) - containers created from an image or a descendant. + - volume=(|) **--format**="*TEMPLATE*" Pretty-print containers using a Go template. @@ -50,6 +51,7 @@ the running containers. .Names - Container names. .Labels - All labels assigned to the container. .Label - Value of a specific label for this container. For example `{{.Label "com.docker.swarm.cpu"}}` + .Mounts - Names of the volumes mounted in this container. **--help** Print usage statement @@ -118,6 +120,18 @@ the running containers. c1d3b0166030 debian 41d50ecd2f57 fedora +# Display containers with `remote-volume` mounted + + $ docker ps --filter volume=remote-volume --format "table {{.ID}}\t{{.Mounts}}" + CONTAINER ID MOUNTS + 9c3527ed70ce remote-volume + +# Display containers with a volume mounted in `/data` + + $ docker ps --filter volume=/data --format "table {{.ID}}\t{{.Mounts}}" + CONTAINER ID MOUNTS + 9c3527ed70ce remote-volume + # HISTORY April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work.