Browse code

Expose new mount points structs in inspect.

Keep old hashes around for old api version calls.

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

David Calavera authored on 2015/06/04 04:21:38
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
 		}
... ...
@@ -225,8 +225,6 @@ type ContainerJSONBase struct {
225 225
 	ExecDriver      string
226 226
 	MountLabel      string
227 227
 	ProcessLabel    string
228
-	Volumes         map[string]string
229
-	VolumesRW       map[string]bool
230 228
 	AppArmorProfile string
231 229
 	ExecIDs         []string
232 230
 	HostConfig      *runconfig.HostConfig
... ...
@@ -235,13 +233,16 @@ type ContainerJSONBase struct {
235 235
 
236 236
 type ContainerJSON struct {
237 237
 	*ContainerJSONBase
238
+	Mounts []MountPoint
238 239
 	Config *runconfig.Config
239 240
 }
240 241
 
241 242
 // backcompatibility struct along with ContainerConfig
242
-type ContainerJSONRaw struct {
243
+type ContainerJSONPre120 struct {
243 244
 	*ContainerJSONBase
244
-	Config *ContainerConfig
245
+	Volumes   map[string]string
246
+	VolumesRW map[string]bool
247
+	Config    *ContainerConfig
245 248
 }
246 249
 
247 250
 type ContainerConfig struct {
... ...
@@ -253,3 +254,13 @@ type ContainerConfig struct {
253 253
 	CpuShares  int64
254 254
 	Cpuset     string
255 255
 }
256
+
257
+// MountPoint represents a mount point configuration inside the container.
258
+type MountPoint struct {
259
+	Name        string `json:",omitempty"`
260
+	Source      string
261
+	Destination string
262
+	Driver      string `json:",omitempty"`
263
+	Mode        string // this is internally named `Relabel`
264
+	RW          bool
265
+}
... ...
@@ -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,
... ...
@@ -140,9 +140,14 @@ Create a container
140 140
                    "com.example.license": "GPL",
141 141
                    "com.example.version": "1.0"
142 142
            },
143
-           "Volumes": {
144
-                   "/tmp": {}
145
-           },
143
+           "Mounts": [
144
+             {
145
+               "Source": "/data",
146
+               "Destination": "/data",
147
+               "Mode": "ro,Z",
148
+               "RW": false
149
+             }
150
+           ],
146 151
            "WorkingDir": "",
147 152
            "NetworkDisabled": false,
148 153
            "MacAddress": "12:34:56:78:9a:bc",
... ...
@@ -223,8 +228,7 @@ Json Parameters:
223 223
 -   **Entrypoint** - Set the entry point for the container as a string or an array
224 224
       of strings.
225 225
 -   **Image** - A string specifying the image name to use for the container.
226
--   **Volumes** – An object mapping mount point paths (strings) inside the
227
-      container to empty objects.
226
+-   **Mounts** - An array of mount points in the container.
228 227
 -   **WorkingDir** - A string specifying the working directory for commands to
229 228
       run in.
230 229
 -   **NetworkDisabled** - Boolean value, when true disables networking for the
... ...
@@ -420,8 +424,14 @@ Return low-level information on the container `id`
420 420
 			"Running": false,
421 421
 			"StartedAt": "2015-01-06T15:47:32.072697474Z"
422 422
 		},
423
-		"Volumes": {},
424
-		"VolumesRW": {}
423
+		"Mounts": [
424
+			{
425
+				"Source": "/data",
426
+				"Destination": "/data",
427
+				"Mode": "ro,Z",
428
+				"RW": false
429
+			}
430
+		]
425 431
 	}
426 432
 
427 433
 Status Codes:
... ...
@@ -1694,9 +1704,14 @@ Create a new image from a container's changes
1694 1694
          "Cmd": [
1695 1695
                  "date"
1696 1696
          ],
1697
-         "Volumes": {
1698
-                 "/tmp": {}
1699
-         },
1697
+         "Mounts": [
1698
+           {
1699
+             "Source": "/data",
1700
+             "Destination": "/data",
1701
+             "Mode": "ro,Z",
1702
+             "RW": false
1703
+           }
1704
+         ],
1700 1705
          "Labels": {
1701 1706
                  "key1": "value1",
1702 1707
                  "key2": "value2"
... ...
@@ -2082,8 +2097,7 @@ Return low-level information about the `exec` command `id`.
2082 2082
         "ProcessLabel" : "",
2083 2083
         "AppArmorProfile" : "",
2084 2084
         "RestartCount" : 0,
2085
-        "Volumes" : {},
2086
-        "VolumesRW" : {}
2085
+        "Mounts" : [],
2087 2086
       }
2088 2087
     }
2089 2088
 
... ...
@@ -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
 	}
... ...
@@ -1467,17 +1467,15 @@ func (s *DockerSuite) TestContainerApiDeleteRemoveVolume(c *check.C) {
1467 1467
 	id := strings.TrimSpace(out)
1468 1468
 	c.Assert(waitRun(id), check.IsNil)
1469 1469
 
1470
-	vol, err := inspectFieldMap(id, "Volumes", "/testvolume")
1471
-	c.Assert(err, check.IsNil)
1472
-
1473
-	_, err = os.Stat(vol)
1470
+	source, err := inspectMountSourceField(id, "/testvolume")
1471
+	_, err = os.Stat(source)
1474 1472
 	c.Assert(err, check.IsNil)
1475 1473
 
1476 1474
 	status, _, err := sockRequest("DELETE", "/containers/"+id+"?v=1&force=1", nil)
1477 1475
 	c.Assert(err, check.IsNil)
1478 1476
 	c.Assert(status, check.Equals, http.StatusNoContent)
1479 1477
 
1480
-	if _, err := os.Stat(vol); !os.IsNotExist(err) {
1478
+	if _, err := os.Stat(source); !os.IsNotExist(err) {
1481 1479
 		c.Fatalf("expected to get ErrNotExist error, got %v", err)
1482 1480
 	}
1483 1481
 }
... ...
@@ -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
 
... ...
@@ -18,7 +19,6 @@ func (s *DockerSuite) TestInspectImage(c *check.C) {
18 18
 	if id != imageTestID {
19 19
 		c.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id)
20 20
 	}
21
-
22 21
 }
23 22
 
24 23
 func (s *DockerSuite) TestInspectInt64(c *check.C) {
... ...
@@ -265,3 +265,44 @@ func (s *DockerSuite) TestInspectContainerGraphDriver(c *check.C) {
265 265
 		c.Fatalf("failed to inspect DeviceSize of the image: %s, %v", deviceSize, err)
266 266
 	}
267 267
 }
268
+
269
+func (s *DockerSuite) TestInspectBindMountPoint(c *check.C) {
270
+	dockerCmd(c, "run", "-d", "--name", "test", "-v", "/data:/data:ro,z", "busybox", "cat")
271
+
272
+	vol, err := inspectFieldJSON("test", "Mounts")
273
+	c.Assert(err, check.IsNil)
274
+
275
+	var mp []types.MountPoint
276
+	err = unmarshalJSON([]byte(vol), &mp)
277
+	c.Assert(err, check.IsNil)
278
+
279
+	if len(mp) != 1 {
280
+		c.Fatalf("Expected 1 mount point, was %v\n", len(mp))
281
+	}
282
+
283
+	m := mp[0]
284
+
285
+	if m.Name != "" {
286
+		c.Fatal("Expected name to be empty")
287
+	}
288
+
289
+	if m.Driver != "" {
290
+		c.Fatal("Expected driver to be empty")
291
+	}
292
+
293
+	if m.Source != "/data" {
294
+		c.Fatalf("Expected source /data, was %s\n", m.Source)
295
+	}
296
+
297
+	if m.Destination != "/data" {
298
+		c.Fatalf("Expected destination /data, was %s\n", m.Destination)
299
+	}
300
+
301
+	if m.Mode != "ro,z" {
302
+		c.Fatalf("Expected mode `ro,z`, was %s\n", m.Mode)
303
+	}
304
+
305
+	if m.RW != false {
306
+		c.Fatalf("Expected rw to be false")
307
+	}
308
+}
... ...
@@ -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
 
... ...
@@ -2302,24 +2302,23 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
2302 2302
 		c.Fatal(err, out)
2303 2303
 	}
2304 2304
 
2305
-	out, err := inspectFieldMap("dark_helmet", "Volumes", "/foo/")
2306
-	c.Assert(err, check.IsNil)
2307
-	if out != "" {
2305
+	out, err := inspectMountSourceField("dark_helmet", "/foo/")
2306
+	if err != mountNotFound {
2308 2307
 		c.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out)
2309 2308
 	}
2310 2309
 
2311
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/foo")
2310
+	out, err = inspectMountSourceField("dark_helmet", "/foo")
2312 2311
 	c.Assert(err, check.IsNil)
2313 2312
 	if !strings.Contains(out, volumesConfigPath) {
2314 2313
 		c.Fatalf("Volume was not defined for /foo\n%q", out)
2315 2314
 	}
2316 2315
 
2317
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar/")
2318
-	c.Assert(err, check.IsNil)
2319
-	if out != "" {
2316
+	out, err = inspectMountSourceField("dark_helmet", "/bar/")
2317
+	if err != mountNotFound {
2320 2318
 		c.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out)
2321 2319
 	}
2322
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar")
2320
+
2321
+	out, err = inspectMountSourceField("dark_helmet", "/bar")
2323 2322
 	c.Assert(err, check.IsNil)
2324 2323
 	if !strings.Contains(out, volumesConfigPath) {
2325 2324
 		c.Fatalf("Volume was not defined for /bar\n%q", out)
... ...
@@ -3107,14 +3106,15 @@ func (s *DockerSuite) TestVolumeFromMixedRWOptions(c *check.C) {
3107 3107
 	dockerCmd(c, "run", "--volumes-from", "parent:ro", "--name", "test-volumes-1", "busybox", "true")
3108 3108
 	dockerCmd(c, "run", "--volumes-from", "parent:rw", "--name", "test-volumes-2", "busybox", "true")
3109 3109
 
3110
-	testRO, err := inspectFieldMap("test-volumes-1", ".VolumesRW", "/test")
3110
+	mRO, err := inspectMountPoint("test-volumes-1", "/test")
3111 3111
 	c.Assert(err, check.IsNil)
3112
-	if testRO != "false" {
3112
+	if mRO.RW {
3113 3113
 		c.Fatalf("Expected RO volume was RW")
3114 3114
 	}
3115
-	testRW, err := inspectFieldMap("test-volumes-2", ".VolumesRW", "/test")
3115
+
3116
+	mRW, err := inspectMountPoint("test-volumes-2", "/test")
3116 3117
 	c.Assert(err, check.IsNil)
3117
-	if testRW != "true" {
3118
+	if !mRW.RW {
3118 3119
 		c.Fatalf("Expected RW volume was RO")
3119 3120
 	}
3120 3121
 }
... ...
@@ -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"
... ...
@@ -874,6 +875,46 @@ func inspectFieldMap(name, path, field string) (string, error) {
874 874
 	return inspectFilter(name, fmt.Sprintf("index .%s %q", path, field))
875 875
 }
876 876
 
877
+func inspectMountSourceField(name, destination string) (string, error) {
878
+	m, err := inspectMountPoint(name, destination)
879
+	if err != nil {
880
+		return "", err
881
+	}
882
+	return m.Source, nil
883
+}
884
+
885
+func inspectMountPoint(name, destination string) (types.MountPoint, error) {
886
+	out, err := inspectFieldJSON(name, "Mounts")
887
+	if err != nil {
888
+		return types.MountPoint{}, err
889
+	}
890
+
891
+	return inspectMountPointJSON(out, destination)
892
+}
893
+
894
+var mountNotFound = errors.New("mount point not found")
895
+
896
+func inspectMountPointJSON(j, destination string) (types.MountPoint, error) {
897
+	var mp []types.MountPoint
898
+	if err := unmarshalJSON([]byte(j), &mp); err != nil {
899
+		return types.MountPoint{}, err
900
+	}
901
+
902
+	var m *types.MountPoint
903
+	for _, c := range mp {
904
+		if c.Destination == destination {
905
+			m = &c
906
+			break
907
+		}
908
+	}
909
+
910
+	if m == nil {
911
+		return types.MountPoint{}, mountNotFound
912
+	}
913
+
914
+	return *m, nil
915
+}
916
+
877 917
 func getIDByName(name string) (string, error) {
878 918
 	return inspectField(name, "Id")
879 919
 }
... ...
@@ -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": {