Browse code

Add mounts to docker ps.

- Allow to filter containers by volume with `--filter volume=name` and `filter volume=/dest`.
- Show their names in the list with the custom format `{{ .Mounts }}`.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2016/02/04 07:46:01
Showing 7 changed files
... ...
@@ -31,6 +31,7 @@ const (
31 31
 	repositoryHeader   = "REPOSITORY"
32 32
 	tagHeader          = "TAG"
33 33
 	digestHeader       = "DIGEST"
34
+	mountsHeader       = "MOUNTS"
34 35
 )
35 36
 
36 37
 type containerContext struct {
... ...
@@ -142,6 +143,20 @@ func (c *containerContext) Label(name string) string {
142 142
 	return c.c.Labels[name]
143 143
 }
144 144
 
145
+func (c *containerContext) Mounts() string {
146
+	c.addHeader(mountsHeader)
147
+
148
+	var mounts []string
149
+	for _, m := range c.c.Mounts {
150
+		name := m.Name
151
+		if c.trunc {
152
+			name = stringutils.Truncate(name, 15)
153
+		}
154
+		mounts = append(mounts, name)
155
+	}
156
+	return strings.Join(mounts, ",")
157
+}
158
+
145 159
 type imageContext struct {
146 160
 	baseSubContext
147 161
 	trunc  bool
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"github.com/Sirupsen/logrus"
10 10
 	"github.com/docker/docker/container"
11 11
 	"github.com/docker/docker/image"
12
+	"github.com/docker/docker/volume"
12 13
 	"github.com/docker/engine-api/types"
13 14
 	"github.com/docker/engine-api/types/filters"
14 15
 	networktypes "github.com/docker/engine-api/types/network"
... ...
@@ -306,6 +307,27 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
306 306
 		return excludeContainer
307 307
 	}
308 308
 
309
+	if ctx.filters.Include("volume") {
310
+		volumesByName := make(map[string]*volume.MountPoint)
311
+		for _, m := range container.MountPoints {
312
+			volumesByName[m.Name] = m
313
+		}
314
+
315
+		volumeExist := fmt.Errorf("volume mounted in container")
316
+		err := ctx.filters.WalkValues("volume", func(value string) error {
317
+			if _, exist := container.MountPoints[value]; exist {
318
+				return volumeExist
319
+			}
320
+			if _, exist := volumesByName[value]; exist {
321
+				return volumeExist
322
+			}
323
+			return nil
324
+		})
325
+		if err != volumeExist {
326
+			return excludeContainer
327
+		}
328
+	}
329
+
309 330
 	if ctx.ancestorFilter {
310 331
 		if len(ctx.images) == 0 {
311 332
 			return excludeContainer
... ...
@@ -419,6 +441,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
419 419
 		newC.SizeRootFs = sizeRootFs
420 420
 	}
421 421
 	newC.Labels = container.Config.Labels
422
+	newC.Mounts = addMountPoints(container)
422 423
 
423 424
 	return newC, nil
424 425
 }
... ...
@@ -116,6 +116,7 @@ This section lists each version from latest to oldest.  Each listing includes a
116 116
 [Docker Remote API v1.23](docker_remote_api_v1.23.md) documentation
117 117
 
118 118
 * `GET /containers/json` returns the state of the container, one of `created`, `restarting`, `running`, `paused`, `exited` or `dead`.
119
+* `GET /containers/json` returns the mount points for the container.
119 120
 * `GET /networks/(name)` now returns an `Internal` field showing whether the network is internal or not.
120 121
 
121 122
 
... ...
@@ -73,7 +73,18 @@ List containers
73 73
                                           "MacAddress": "02:42:ac:11:00:02"
74 74
                                   }
75 75
                          }
76
-                 }
76
+                 },
77
+                 "Mounts": [
78
+                         {
79
+                                  "Name": "fac362...80535",
80
+                                  "Source": "/data",
81
+                                  "Destination": "/data",
82
+                                  "Driver": "local",
83
+                                  "Mode": "ro,Z",
84
+                                  "RW": false,
85
+                                  "Propagation": ""
86
+                         }
87
+                 ]
77 88
          },
78 89
          {
79 90
                  "Id": "9cd87474be90",
... ...
@@ -102,8 +113,8 @@ List containers
102 102
                                           "MacAddress": "02:42:ac:11:00:08"
103 103
                                   }
104 104
                          }
105
-                 }
106
-
105
+                 },
106
+                 "Mounts": []
107 107
          },
108 108
          {
109 109
                  "Id": "3176a2479c92",
... ...
@@ -132,8 +143,8 @@ List containers
132 132
                                           "MacAddress": "02:42:ac:11:00:06"
133 133
                                   }
134 134
                          }
135
-                 }
136
-
135
+                 },
136
+                 "Mounts": []
137 137
          },
138 138
          {
139 139
                  "Id": "4cb07b47f9fb",
... ...
@@ -162,8 +173,8 @@ List containers
162 162
                                           "MacAddress": "02:42:ac:11:00:05"
163 163
                                   }
164 164
                          }
165
-                 }
166
-
165
+                 },
166
+                 "Mounts": []
167 167
          }
168 168
     ]
169 169
 
... ...
@@ -184,6 +195,10 @@ Query Parameters:
184 184
   -   `status=`(`created`|`restarting`|`running`|`paused`|`exited`|`dead`)
185 185
   -   `label=key` or `label="key=value"` of a container label
186 186
   -   `isolation=`(`default`|`process`|`hyperv`)   (Windows daemon only)
187
+  -   `ancestor`=(`<image-name>[:<tag>]`,  `<image id>` or `<image@digest>`)
188
+  -   `before`=(`<container id>` or `<container name>`)
189
+  -   `since`=(`<container id>` or `<container name>`)
190
+  -   `volume`=(`<volume name>` or `<mount point destination>`)
187 191
 
188 192
 Status Codes:
189 193
 
... ...
@@ -60,6 +60,7 @@ The currently supported filters are:
60 60
 * before (container's id or name) - filters containers created before given id or name
61 61
 * since (container's id or name) - filters containers created since given id or name
62 62
 * isolation (default|process|hyperv)   (Windows daemon only)
63
+* volume (volume name or mount point) - filters containers that mount volumes.
63 64
 
64 65
 
65 66
 #### Label
... ...
@@ -193,6 +194,18 @@ with the same containers as in `before` filter:
193 193
     9c3527ed70ce        busybox     "top"         10 minutes ago      Up 10 minutes                           desperate_dubinsky
194 194
     4aace5031105        busybox     "top"         10 minutes ago      Up 10 minutes                           focused_hamilton
195 195
 
196
+#### Volume
197
+
198
+The `volume` filter shows only containers that mount a specific volume or have a volume mounted in a specific path:
199
+
200
+    $ docker ps --filter volume=remote-volume --format "table {{.ID}}\t{{.Mounts}}"
201
+    CONTAINER ID        MOUNTS
202
+    9c3527ed70ce        remote-volume
203
+
204
+    $ docker ps --filter volume=/data --format "table {{.ID}}\t{{.Mounts}}"
205
+    CONTAINER ID        MOUNTS
206
+    9c3527ed70ce        remote-volume
207
+
196 208
 
197 209
 ## Formatting
198 210
 
... ...
@@ -213,6 +226,7 @@ Placeholder | Description
213 213
 `.Names` | Container names.
214 214
 `.Labels` | All labels assigned to the container.
215 215
 `.Label` | Value of a specific label for this container. For example `{{.Label "com.docker.swarm.cpu"}}`
216
+`.Mounts` | Names of the volumes mounted in this container.
216 217
 
217 218
 When using the `--format` option, the `ps` command will either output the data exactly as the template
218 219
 declares or, when using the `table` directive, will include column headers as well.
... ...
@@ -734,3 +734,56 @@ func (s *DockerSuite) TestPsNotShowPortsOfStoppedContainer(c *check.C) {
734 734
 	fields = strings.Fields(lines[1])
735 735
 	c.Assert(fields[len(fields)-2], checker.Not(checker.Equals), expected, check.Commentf("Should not got %v", expected))
736 736
 }
737
+
738
+func (s *DockerSuite) TestPsShowMounts(c *check.C) {
739
+	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
740
+
741
+	mp := prefix + slash + "test"
742
+
743
+	dockerCmd(c, "volume", "create", "--name", "ps-volume-test")
744
+	runSleepingContainer(c, "--name=volume-test-1", "--volume", "ps-volume-test:"+mp)
745
+	c.Assert(waitRun("volume-test-1"), checker.IsNil)
746
+	runSleepingContainer(c, "--name=volume-test-2", "--volume", mp)
747
+	c.Assert(waitRun("volume-test-2"), checker.IsNil)
748
+
749
+	out, _ := dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}")
750
+
751
+	lines := strings.Split(strings.TrimSpace(string(out)), "\n")
752
+	c.Assert(lines, checker.HasLen, 2)
753
+
754
+	fields := strings.Fields(lines[0])
755
+	c.Assert(fields, checker.HasLen, 2)
756
+
757
+	annonymounsVolumeID := fields[1]
758
+
759
+	fields = strings.Fields(lines[1])
760
+	c.Assert(fields[1], checker.Equals, "ps-volume-test")
761
+
762
+	// filter by volume name
763
+	out, _ = dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}", "--filter", "volume=ps-volume-test")
764
+
765
+	lines = strings.Split(strings.TrimSpace(string(out)), "\n")
766
+	c.Assert(lines, checker.HasLen, 1)
767
+
768
+	fields = strings.Fields(lines[0])
769
+	c.Assert(fields[1], checker.Equals, "ps-volume-test")
770
+
771
+	// empty results filtering by unknown volume
772
+	out, _ = dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}", "--filter", "volume=this-volume-should-not-exist")
773
+	c.Assert(strings.TrimSpace(string(out)), checker.HasLen, 0)
774
+
775
+	// filter by mount destination
776
+	out, _ = dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}", "--filter", "volume="+mp)
777
+
778
+	lines = strings.Split(strings.TrimSpace(string(out)), "\n")
779
+	c.Assert(lines, checker.HasLen, 2)
780
+
781
+	fields = strings.Fields(lines[0])
782
+	c.Assert(fields[1], checker.Equals, annonymounsVolumeID)
783
+	fields = strings.Fields(lines[1])
784
+	c.Assert(fields[1], checker.Equals, "ps-volume-test")
785
+
786
+	// empty results filtering by unknown mount point
787
+	out, _ = dockerCmd(c, "ps", "--format", "{{.Names}} {{.Mounts}}", "--filter", "volume="+prefix+slash+"this-path-was-never-mounted")
788
+	c.Assert(strings.TrimSpace(string(out)), checker.HasLen, 0)
789
+}
... ...
@@ -35,6 +35,7 @@ the running containers.
35 35
    - before=(<container-name>|<container-id>)
36 36
    - since=(<container-name>|<container-id>)
37 37
    - ancestor=(<image-name>[:tag]|<image-id>|<image@digest>) - containers created from an image or a descendant.
38
+   - volume=(<volume-name>|<mount-point-destination>)
38 39
 
39 40
 **--format**="*TEMPLATE*"
40 41
    Pretty-print containers using a Go template.
... ...
@@ -50,6 +51,7 @@ the running containers.
50 50
       .Names - Container names.
51 51
       .Labels - All labels assigned to the container.
52 52
       .Label - Value of a specific label for this container. For example `{{.Label "com.docker.swarm.cpu"}}`
53
+      .Mounts - Names of the volumes mounted in this container.
53 54
 
54 55
 **--help**
55 56
   Print usage statement
... ...
@@ -118,6 +120,18 @@ the running containers.
118 118
     c1d3b0166030        debian
119 119
     41d50ecd2f57        fedora
120 120
 
121
+# Display containers with `remote-volume` mounted
122
+
123
+    $ docker ps --filter volume=remote-volume --format "table {{.ID}}\t{{.Mounts}}"
124
+    CONTAINER ID        MOUNTS
125
+    9c3527ed70ce        remote-volume
126
+
127
+# Display containers with a volume mounted in `/data`
128
+
129
+    $ docker ps --filter volume=/data --format "table {{.ID}}\t{{.Mounts}}"
130
+    CONTAINER ID        MOUNTS
131
+    9c3527ed70ce        remote-volume
132
+
121 133
 # HISTORY
122 134
 April 2014, Originally compiled by William Henry (whenry at redhat dot com)
123 135
 based on docker.com source material and internal work.