Browse code

Merge pull request #13711 from calavera/version_volumes_inspect

Expose new mount points structs in inspect.

Sebastiaan van Stijn authored on 2015/07/22 16:02:00
Showing 14 changed files
... ...
@@ -1193,8 +1193,8 @@ func (s *Server) getContainersByName(version version.Version, w http.ResponseWri
1193 1193
 		return fmt.Errorf("Missing parameter")
1194 1194
 	}
1195 1195
 
1196
-	if version.LessThan("1.19") {
1197
-		containerJSONRaw, err := s.daemon.ContainerInspectRaw(vars["name"])
1196
+	if version.LessThan("1.20") {
1197
+		containerJSONRaw, err := s.daemon.ContainerInspectPre120(vars["name"])
1198 1198
 		if err != nil {
1199 1199
 			return err
1200 1200
 		}
... ...
@@ -238,8 +238,6 @@ type ContainerJSONBase struct {
238 238
 	ExecDriver      string
239 239
 	MountLabel      string
240 240
 	ProcessLabel    string
241
-	Volumes         map[string]string
242
-	VolumesRW       map[string]bool
243 241
 	AppArmorProfile string
244 242
 	ExecIDs         []string
245 243
 	HostConfig      *runconfig.HostConfig
... ...
@@ -248,13 +246,16 @@ type ContainerJSONBase struct {
248 248
 
249 249
 type ContainerJSON struct {
250 250
 	*ContainerJSONBase
251
+	Mounts []MountPoint
251 252
 	Config *runconfig.Config
252 253
 }
253 254
 
254 255
 // backcompatibility struct along with ContainerConfig
255
-type ContainerJSONRaw struct {
256
+type ContainerJSONPre120 struct {
256 257
 	*ContainerJSONBase
257
-	Config *ContainerConfig
258
+	Volumes   map[string]string
259
+	VolumesRW map[string]bool
260
+	Config    *ContainerConfig
258 261
 }
259 262
 
260 263
 type ContainerConfig struct {
... ...
@@ -266,3 +267,13 @@ type ContainerConfig struct {
266 266
 	CpuShares  int64
267 267
 	Cpuset     string
268 268
 }
269
+
270
+// MountPoint represents a mount point configuration inside the container.
271
+type MountPoint struct {
272
+	Name        string `json:",omitempty"`
273
+	Source      string
274
+	Destination string
275
+	Driver      string `json:",omitempty"`
276
+	Mode        string // this is internally named `Relabel`
277
+	RW          bool
278
+}
... ...
@@ -20,10 +20,22 @@ func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error
20 20
 		return nil, err
21 21
 	}
22 22
 
23
-	return &types.ContainerJSON{base, container.Config}, nil
23
+	mountPoints := make([]types.MountPoint, 0, len(container.MountPoints))
24
+	for _, m := range container.MountPoints {
25
+		mountPoints = append(mountPoints, types.MountPoint{
26
+			Name:        m.Name,
27
+			Source:      m.Path(),
28
+			Destination: m.Destination,
29
+			Driver:      m.Driver,
30
+			Mode:        m.Relabel,
31
+			RW:          m.RW,
32
+		})
33
+	}
34
+
35
+	return &types.ContainerJSON{base, mountPoints, container.Config}, nil
24 36
 }
25 37
 
26
-func (daemon *Daemon) ContainerInspectRaw(name string) (*types.ContainerJSONRaw, error) {
38
+func (daemon *Daemon) ContainerInspectPre120(name string) (*types.ContainerJSONPre120, error) {
27 39
 	container, err := daemon.Get(name)
28 40
 	if err != nil {
29 41
 		return nil, err
... ...
@@ -37,6 +49,13 @@ func (daemon *Daemon) ContainerInspectRaw(name string) (*types.ContainerJSONRaw,
37 37
 		return nil, err
38 38
 	}
39 39
 
40
+	volumes := make(map[string]string)
41
+	volumesRW := make(map[string]bool)
42
+	for _, m := range container.MountPoints {
43
+		volumes[m.Destination] = m.Path()
44
+		volumesRW[m.Destination] = m.RW
45
+	}
46
+
40 47
 	config := &types.ContainerConfig{
41 48
 		container.Config,
42 49
 		container.hostConfig.Memory,
... ...
@@ -45,7 +64,7 @@ func (daemon *Daemon) ContainerInspectRaw(name string) (*types.ContainerJSONRaw,
45 45
 		container.hostConfig.CpusetCpus,
46 46
 	}
47 47
 
48
-	return &types.ContainerJSONRaw{base, config}, nil
48
+	return &types.ContainerJSONPre120{base, volumes, volumesRW, config}, nil
49 49
 }
50 50
 
51 51
 func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSONBase, error) {
... ...
@@ -76,14 +95,6 @@ func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSON
76 76
 		FinishedAt: container.State.FinishedAt,
77 77
 	}
78 78
 
79
-	volumes := make(map[string]string)
80
-	volumesRW := make(map[string]bool)
81
-
82
-	for _, m := range container.MountPoints {
83
-		volumes[m.Destination] = m.Path()
84
-		volumesRW[m.Destination] = m.RW
85
-	}
86
-
87 79
 	contJSONBase := &types.ContainerJSONBase{
88 80
 		Id:              container.ID,
89 81
 		Created:         container.Created,
... ...
@@ -102,8 +113,6 @@ func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSON
102 102
 		ExecDriver:      container.ExecDriver,
103 103
 		MountLabel:      container.MountLabel,
104 104
 		ProcessLabel:    container.ProcessLabel,
105
-		Volumes:         volumes,
106
-		VolumesRW:       volumesRW,
107 105
 		AppArmorProfile: container.AppArmorProfile,
108 106
 		ExecIDs:         container.GetExecIDs(),
109 107
 		HostConfig:      &hostConfig,
... ...
@@ -144,9 +144,14 @@ Create a container
144 144
                    "com.example.license": "GPL",
145 145
                    "com.example.version": "1.0"
146 146
            },
147
-           "Volumes": {
148
-                   "/tmp": {}
149
-           },
147
+           "Mounts": [
148
+             {
149
+               "Source": "/data",
150
+               "Destination": "/data",
151
+               "Mode": "ro,Z",
152
+               "RW": false
153
+             }
154
+           ],
150 155
            "WorkingDir": "",
151 156
            "NetworkDisabled": false,
152 157
            "MacAddress": "12:34:56:78:9a:bc",
... ...
@@ -227,8 +232,7 @@ Json Parameters:
227 227
 -   **Entrypoint** - Set the entry point for the container as a string or an array
228 228
       of strings.
229 229
 -   **Image** - A string specifying the image name to use for the container.
230
--   **Volumes** – An object mapping mount point paths (strings) inside the
231
-      container to empty objects.
230
+-   **Mounts** - An array of mount points in the container.
232 231
 -   **WorkingDir** - A string specifying the working directory for commands to
233 232
       run in.
234 233
 -   **NetworkDisabled** - Boolean value, when true disables networking for the
... ...
@@ -424,8 +428,14 @@ Return low-level information on the container `id`
424 424
 			"Running": false,
425 425
 			"StartedAt": "2015-01-06T15:47:32.072697474Z"
426 426
 		},
427
-		"Volumes": {},
428
-		"VolumesRW": {}
427
+		"Mounts": [
428
+			{
429
+				"Source": "/data",
430
+				"Destination": "/data",
431
+				"Mode": "ro,Z",
432
+				"RW": false
433
+			}
434
+		]
429 435
 	}
430 436
 
431 437
 Status Codes:
... ...
@@ -1814,9 +1824,14 @@ Create a new image from a container's changes
1814 1814
          "Cmd": [
1815 1815
                  "date"
1816 1816
          ],
1817
-         "Volumes": {
1818
-                 "/tmp": {}
1819
-         },
1817
+         "Mounts": [
1818
+           {
1819
+             "Source": "/data",
1820
+             "Destination": "/data",
1821
+             "Mode": "ro,Z",
1822
+             "RW": false
1823
+           }
1824
+         ],
1820 1825
          "Labels": {
1821 1826
                  "key1": "value1",
1822 1827
                  "key2": "value2"
... ...
@@ -2202,8 +2217,7 @@ Return low-level information about the `exec` command `id`.
2202 2202
         "ProcessLabel" : "",
2203 2203
         "AppArmorProfile" : "",
2204 2204
         "RestartCount" : 0,
2205
-        "Volumes" : {},
2206
-        "VolumesRW" : {}
2205
+        "Mounts" : [],
2207 2206
       }
2208 2207
     }
2209 2208
 
... ...
@@ -190,7 +190,7 @@ func (s *DockerSuite) TestContainerApiStartVolumeBinds(c *check.C) {
190 190
 	c.Assert(err, check.IsNil)
191 191
 	c.Assert(status, check.Equals, http.StatusNoContent)
192 192
 
193
-	pth, err := inspectFieldMap(name, "Volumes", "/tmp")
193
+	pth, err := inspectMountSourceField(name, "/tmp")
194 194
 	if err != nil {
195 195
 		c.Fatal(err)
196 196
 	}
... ...
@@ -233,7 +233,7 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {
233 233
 
234 234
 	dockerCmd(c, "run", "-d", "--name", volName, "-v", volPath, "busybox")
235 235
 
236
-	name := "TestContainerApiStartDupVolumeBinds"
236
+	name := "TestContainerApiStartVolumesFrom"
237 237
 	config := map[string]interface{}{
238 238
 		"Image":   "busybox",
239 239
 		"Volumes": map[string]struct{}{volPath: {}},
... ...
@@ -250,11 +250,11 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {
250 250
 	c.Assert(err, check.IsNil)
251 251
 	c.Assert(status, check.Equals, http.StatusNoContent)
252 252
 
253
-	pth, err := inspectFieldMap(name, "Volumes", volPath)
253
+	pth, err := inspectMountSourceField(name, volPath)
254 254
 	if err != nil {
255 255
 		c.Fatal(err)
256 256
 	}
257
-	pth2, err := inspectFieldMap(volName, "Volumes", volPath)
257
+	pth2, err := inspectMountSourceField(volName, volPath)
258 258
 	if err != nil {
259 259
 		c.Fatal(err)
260 260
 	}
... ...
@@ -705,7 +705,7 @@ func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) {
705 705
 func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) {
706 706
 	dockerCmd(c, "create", "-v", "/foo", "--name=one", "busybox")
707 707
 
708
-	fooDir, err := inspectFieldMap("one", "Volumes", "/foo")
708
+	fooDir, err := inspectMountSourceField("one", "/foo")
709 709
 	if err != nil {
710 710
 		c.Fatal(err)
711 711
 	}
... ...
@@ -717,7 +717,7 @@ func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) {
717 717
 	c.Assert(err, check.IsNil)
718 718
 	c.Assert(status, check.Equals, http.StatusNoContent)
719 719
 
720
-	fooDir2, err := inspectFieldMap("two", "Volumes", "/foo")
720
+	fooDir2, err := inspectMountSourceField("two", "/foo")
721 721
 	if err != nil {
722 722
 		c.Fatal(err)
723 723
 	}
... ...
@@ -1493,17 +1493,15 @@ func (s *DockerSuite) TestContainerApiDeleteRemoveVolume(c *check.C) {
1493 1493
 	id := strings.TrimSpace(out)
1494 1494
 	c.Assert(waitRun(id), check.IsNil)
1495 1495
 
1496
-	vol, err := inspectFieldMap(id, "Volumes", "/testvolume")
1497
-	c.Assert(err, check.IsNil)
1498
-
1499
-	_, err = os.Stat(vol)
1496
+	source, err := inspectMountSourceField(id, "/testvolume")
1497
+	_, err = os.Stat(source)
1500 1498
 	c.Assert(err, check.IsNil)
1501 1499
 
1502 1500
 	status, _, err := sockRequest("DELETE", "/containers/"+id+"?v=1&force=1", nil)
1503 1501
 	c.Assert(err, check.IsNil)
1504 1502
 	c.Assert(status, check.Equals, http.StatusNoContent)
1505 1503
 
1506
-	if _, err := os.Stat(vol); !os.IsNotExist(err) {
1504
+	if _, err := os.Stat(source); !os.IsNotExist(err) {
1507 1505
 		c.Fatalf("expected to get ErrNotExist error, got %v", err)
1508 1506
 	}
1509 1507
 }
... ...
@@ -2,6 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"fmt"
5 6
 	"net/http"
6 7
 	"strings"
7 8
 
... ...
@@ -12,28 +13,38 @@ func (s *DockerSuite) TestInspectApiContainerResponse(c *check.C) {
12 12
 	out, _ := dockerCmd(c, "run", "-d", "busybox", "true")
13 13
 
14 14
 	cleanedContainerID := strings.TrimSpace(out)
15
+	keysBase := []string{"Id", "State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings",
16
+		"ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "GraphDriver"}
17
+
18
+	cases := []struct {
19
+		version string
20
+		keys    []string
21
+	}{
22
+		{"1.20", append(keysBase, "Mounts")},
23
+		{"1.19", append(keysBase, "Volumes", "VolumesRW")},
24
+	}
15 25
 
16
-	endpoint := "/containers/" + cleanedContainerID + "/json"
17
-	status, body, err := sockRequest("GET", endpoint, nil)
18
-	c.Assert(status, check.Equals, http.StatusOK)
19
-	c.Assert(err, check.IsNil)
26
+	for _, cs := range cases {
27
+		endpoint := fmt.Sprintf("/v%s/containers/%s/json", cs.version, cleanedContainerID)
20 28
 
21
-	var inspectJSON map[string]interface{}
22
-	if err = json.Unmarshal(body, &inspectJSON); err != nil {
23
-		c.Fatalf("unable to unmarshal body for latest version: %v", err)
24
-	}
29
+		status, body, err := sockRequest("GET", endpoint, nil)
30
+		c.Assert(status, check.Equals, http.StatusOK)
31
+		c.Assert(err, check.IsNil)
25 32
 
26
-	keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW", "GraphDriver"}
33
+		var inspectJSON map[string]interface{}
34
+		if err = json.Unmarshal(body, &inspectJSON); err != nil {
35
+			c.Fatalf("unable to unmarshal body for version %s: %v", cs.version, err)
36
+		}
27 37
 
28
-	keys = append(keys, "Id")
38
+		for _, key := range cs.keys {
39
+			if _, ok := inspectJSON[key]; !ok {
40
+				c.Fatalf("%s does not exist in response for version %s", key, cs.version)
41
+			}
42
+		}
29 43
 
30
-	for _, key := range keys {
31
-		if _, ok := inspectJSON[key]; !ok {
32
-			c.Fatalf("%s does not exist in response for latest version", key)
44
+		//Issue #6830: type not properly converted to JSON/back
45
+		if _, ok := inspectJSON["Path"].(bool); ok {
46
+			c.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling")
33 47
 		}
34 48
 	}
35
-	//Issue #6830: type not properly converted to JSON/back
36
-	if _, ok := inspectJSON["Path"].(bool); ok {
37
-		c.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling")
38
-	}
39 49
 }
... ...
@@ -184,7 +184,7 @@ func (s *DockerSuite) TestCreateVolumesCreated(c *check.C) {
184 184
 	name := "test_create_volume"
185 185
 	dockerCmd(c, "create", "--name", name, "-v", "/foo", "busybox")
186 186
 
187
-	dir, err := inspectFieldMap(name, "Volumes", "/foo")
187
+	dir, err := inspectMountSourceField(name, "/foo")
188 188
 	if err != nil {
189 189
 		c.Fatalf("Error getting volume host path: %q", err)
190 190
 	}
... ...
@@ -67,23 +67,23 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) {
67 67
 	if out, err := s.d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil {
68 68
 		c.Fatal(err, out)
69 69
 	}
70
+
70 71
 	if err := s.d.Restart(); err != nil {
71 72
 		c.Fatal(err)
72 73
 	}
73 74
 	if _, err := s.d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil {
74 75
 		c.Fatal(err)
75 76
 	}
77
+
76 78
 	if out, err := s.d.Cmd("rm", "-fv", "volrestarttest2"); err != nil {
77 79
 		c.Fatal(err, out)
78 80
 	}
79
-	v, err := s.d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1")
80
-	if err != nil {
81
-		c.Fatal(err)
82
-	}
83
-	volumes := make(map[string]string)
84
-	json.Unmarshal([]byte(v), &volumes)
85
-	if _, err := os.Stat(volumes["/foo"]); err != nil {
86
-		c.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err)
81
+
82
+	out, err := s.d.Cmd("inspect", "-f", "{{json .Mounts}}", "volrestarttest1")
83
+	c.Assert(err, check.IsNil)
84
+
85
+	if _, err := inspectMountPointJSON(out, "/foo"); err != nil {
86
+		c.Fatalf("Expected volume to exist: /foo, error: %v\n", err)
87 87
 	}
88 88
 }
89 89
 
90 90
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+// +build experimental
1
+
2
+package main
3
+
4
+import (
5
+	"github.com/docker/docker/api/types"
6
+	"github.com/go-check/check"
7
+)
8
+
9
+func (s *DockerSuite) TestInspectNamedMountPoint(c *check.C) {
10
+	dockerCmd(c, "run", "-d", "--name", "test", "-v", "data:/data", "busybox", "cat")
11
+
12
+	vol, err := inspectFieldJSON("test", "Mounts")
13
+	c.Assert(err, check.IsNil)
14
+
15
+	var mp []types.MountPoint
16
+	err = unmarshalJSON([]byte(vol), &mp)
17
+	c.Assert(err, check.IsNil)
18
+
19
+	if len(mp) != 1 {
20
+		c.Fatalf("Expected 1 mount point, was %v\n", len(mp))
21
+	}
22
+
23
+	m := mp[0]
24
+	if m.Name != "data" {
25
+		c.Fatalf("Expected name data, was %s\n", m.Name)
26
+	}
27
+
28
+	if m.Driver != "local" {
29
+		c.Fatalf("Expected driver local, was %s\n", m.Driver)
30
+	}
31
+
32
+	if m.Source == "" {
33
+		c.Fatalf("Expected source to not be empty")
34
+	}
35
+
36
+	if m.RW != true {
37
+		c.Fatalf("Expected rw to be true")
38
+	}
39
+
40
+	if m.Destination != "/data" {
41
+		c.Fatalf("Expected destination /data, was %s\n", m.Destination)
42
+	}
43
+}
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"strconv"
7 7
 	"strings"
8 8
 
9
+	"github.com/docker/docker/api/types"
9 10
 	"github.com/go-check/check"
10 11
 )
11 12
 
... ...
@@ -218,3 +219,44 @@ func (s *DockerSuite) TestInspectContainerGraphDriver(c *check.C) {
218 218
 		c.Fatalf("failed to inspect DeviceSize of the image: %s, %v", deviceSize, err)
219 219
 	}
220 220
 }
221
+
222
+func (s *DockerSuite) TestInspectBindMountPoint(c *check.C) {
223
+	dockerCmd(c, "run", "-d", "--name", "test", "-v", "/data:/data:ro,z", "busybox", "cat")
224
+
225
+	vol, err := inspectFieldJSON("test", "Mounts")
226
+	c.Assert(err, check.IsNil)
227
+
228
+	var mp []types.MountPoint
229
+	err = unmarshalJSON([]byte(vol), &mp)
230
+	c.Assert(err, check.IsNil)
231
+
232
+	if len(mp) != 1 {
233
+		c.Fatalf("Expected 1 mount point, was %v\n", len(mp))
234
+	}
235
+
236
+	m := mp[0]
237
+
238
+	if m.Name != "" {
239
+		c.Fatal("Expected name to be empty")
240
+	}
241
+
242
+	if m.Driver != "" {
243
+		c.Fatal("Expected driver to be empty")
244
+	}
245
+
246
+	if m.Source != "/data" {
247
+		c.Fatalf("Expected source /data, was %s\n", m.Source)
248
+	}
249
+
250
+	if m.Destination != "/data" {
251
+		c.Fatalf("Expected destination /data, was %s\n", m.Destination)
252
+	}
253
+
254
+	if m.Mode != "ro,z" {
255
+		c.Fatalf("Expected mode `ro,z`, was %s\n", m.Mode)
256
+	}
257
+
258
+	if m.RW != false {
259
+		c.Fatalf("Expected rw to be false")
260
+	}
261
+}
... ...
@@ -54,27 +54,27 @@ func (s *DockerSuite) TestRestartWithVolumes(c *check.C) {
54 54
 	out, _ := dockerCmd(c, "run", "-d", "-v", "/test", "busybox", "top")
55 55
 
56 56
 	cleanedContainerID := strings.TrimSpace(out)
57
-	out, _ = dockerCmd(c, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID)
57
+	out, _ = dockerCmd(c, "inspect", "--format", "{{ len .Mounts }}", cleanedContainerID)
58 58
 
59 59
 	if out = strings.Trim(out, " \n\r"); out != "1" {
60 60
 		c.Errorf("expect 1 volume received %s", out)
61 61
 	}
62 62
 
63
-	volumes, err := inspectField(cleanedContainerID, "Volumes")
63
+	source, err := inspectMountSourceField(cleanedContainerID, "/test")
64 64
 	c.Assert(err, check.IsNil)
65 65
 
66 66
 	dockerCmd(c, "restart", cleanedContainerID)
67 67
 
68
-	out, _ = dockerCmd(c, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID)
68
+	out, _ = dockerCmd(c, "inspect", "--format", "{{ len .Mounts }}", cleanedContainerID)
69 69
 	if out = strings.Trim(out, " \n\r"); out != "1" {
70 70
 		c.Errorf("expect 1 volume after restart received %s", out)
71 71
 	}
72 72
 
73
-	volumesAfterRestart, err := inspectField(cleanedContainerID, "Volumes")
73
+	sourceAfterRestart, err := inspectMountSourceField(cleanedContainerID, "/test")
74 74
 	c.Assert(err, check.IsNil)
75 75
 
76
-	if volumes != volumesAfterRestart {
77
-		c.Errorf("expected volume path: %s Actual path: %s", volumes, volumesAfterRestart)
76
+	if source != sourceAfterRestart {
77
+		c.Errorf("expected volume path: %s Actual path: %s", source, sourceAfterRestart)
78 78
 	}
79 79
 }
80 80
 
... ...
@@ -1824,24 +1824,23 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
1824 1824
 
1825 1825
 	dockerCmd(c, "run", "-v", "/foo", "-v", "/bar/", "--name", "dark_helmet", "run_volumes_clean_paths")
1826 1826
 
1827
-	out, err := inspectFieldMap("dark_helmet", "Volumes", "/foo/")
1828
-	c.Assert(err, check.IsNil)
1829
-	if out != "" {
1827
+	out, err := inspectMountSourceField("dark_helmet", "/foo/")
1828
+	if err != mountNotFound {
1830 1829
 		c.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out)
1831 1830
 	}
1832 1831
 
1833
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/foo")
1832
+	out, err = inspectMountSourceField("dark_helmet", "/foo")
1834 1833
 	c.Assert(err, check.IsNil)
1835 1834
 	if !strings.Contains(out, volumesConfigPath) {
1836 1835
 		c.Fatalf("Volume was not defined for /foo\n%q", out)
1837 1836
 	}
1838 1837
 
1839
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar/")
1840
-	c.Assert(err, check.IsNil)
1841
-	if out != "" {
1838
+	out, err = inspectMountSourceField("dark_helmet", "/bar/")
1839
+	if err != mountNotFound {
1842 1840
 		c.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out)
1843 1841
 	}
1844
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar")
1842
+
1843
+	out, err = inspectMountSourceField("dark_helmet", "/bar")
1845 1844
 	c.Assert(err, check.IsNil)
1846 1845
 	if !strings.Contains(out, volumesConfigPath) {
1847 1846
 		c.Fatalf("Volume was not defined for /bar\n%q", out)
... ...
@@ -2483,14 +2482,15 @@ func (s *DockerSuite) TestVolumeFromMixedRWOptions(c *check.C) {
2483 2483
 	dockerCmd(c, "run", "--volumes-from", "parent:ro", "--name", "test-volumes-1", "busybox", "true")
2484 2484
 	dockerCmd(c, "run", "--volumes-from", "parent:rw", "--name", "test-volumes-2", "busybox", "true")
2485 2485
 
2486
-	testRO, err := inspectFieldMap("test-volumes-1", ".VolumesRW", "/test")
2486
+	mRO, err := inspectMountPoint("test-volumes-1", "/test")
2487 2487
 	c.Assert(err, check.IsNil)
2488
-	if testRO != "false" {
2488
+	if mRO.RW {
2489 2489
 		c.Fatalf("Expected RO volume was RW")
2490 2490
 	}
2491
-	testRW, err := inspectFieldMap("test-volumes-2", ".VolumesRW", "/test")
2491
+
2492
+	mRW, err := inspectMountPoint("test-volumes-2", "/test")
2492 2493
 	c.Assert(err, check.IsNil)
2493
-	if testRW != "true" {
2494
+	if !mRW.RW {
2494 2495
 		c.Fatalf("Expected RW volume was RO")
2495 2496
 	}
2496 2497
 }
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"strings"
21 21
 	"time"
22 22
 
23
+	"github.com/docker/docker/api/types"
23 24
 	"github.com/docker/docker/opts"
24 25
 	"github.com/docker/docker/pkg/ioutils"
25 26
 	"github.com/docker/docker/pkg/stringutils"
... ...
@@ -880,6 +881,46 @@ func inspectFieldMap(name, path, field string) (string, error) {
880 880
 	return inspectFilter(name, fmt.Sprintf("index .%s %q", path, field))
881 881
 }
882 882
 
883
+func inspectMountSourceField(name, destination string) (string, error) {
884
+	m, err := inspectMountPoint(name, destination)
885
+	if err != nil {
886
+		return "", err
887
+	}
888
+	return m.Source, nil
889
+}
890
+
891
+func inspectMountPoint(name, destination string) (types.MountPoint, error) {
892
+	out, err := inspectFieldJSON(name, "Mounts")
893
+	if err != nil {
894
+		return types.MountPoint{}, err
895
+	}
896
+
897
+	return inspectMountPointJSON(out, destination)
898
+}
899
+
900
+var mountNotFound = errors.New("mount point not found")
901
+
902
+func inspectMountPointJSON(j, destination string) (types.MountPoint, error) {
903
+	var mp []types.MountPoint
904
+	if err := unmarshalJSON([]byte(j), &mp); err != nil {
905
+		return types.MountPoint{}, err
906
+	}
907
+
908
+	var m *types.MountPoint
909
+	for _, c := range mp {
910
+		if c.Destination == destination {
911
+			m = &c
912
+			break
913
+		}
914
+	}
915
+
916
+	if m == nil {
917
+		return types.MountPoint{}, mountNotFound
918
+	}
919
+
920
+	return *m, nil
921
+}
922
+
883 923
 func getIDByName(name string) (string, error) {
884 924
 	return inspectField(name, "Id")
885 925
 }
... ...
@@ -95,8 +95,14 @@ To get information on a container use its ID or instance name:
95 95
     "ExecDriver": "native-0.2",
96 96
     "MountLabel": "",
97 97
     "ProcessLabel": "",
98
-    "Volumes": {},
99
-    "VolumesRW": {},
98
+    "Mounts": [
99
+      {
100
+        "Source": "/data",
101
+        "Destination": "/data",
102
+        "Mode": "ro,Z",
103
+        "RW": false
104
+      }
105
+    ],
100 106
     "AppArmorProfile": "",
101 107
     "ExecIDs": null,
102 108
     "HostConfig": {