瀏覽代碼

Merge pull request #13711 from calavera/version_volumes_inspect

Expose new mount points structs in inspect.
Sebastiaan van Stijn 10 年之前
父節點
當前提交
50d2597e49

+ 2 - 2
api/server/server.go

@@ -1193,8 +1193,8 @@ func (s *Server) getContainersByName(version version.Version, w http.ResponseWri
 		return fmt.Errorf("Missing parameter")
 	}
 
-	if version.LessThan("1.19") {
-		containerJSONRaw, err := s.daemon.ContainerInspectRaw(vars["name"])
+	if version.LessThan("1.20") {
+		containerJSONRaw, err := s.daemon.ContainerInspectPre120(vars["name"])
 		if err != nil {
 			return err
 		}

+ 15 - 4
api/types/types.go

@@ -238,8 +238,6 @@ type ContainerJSONBase struct {
 	ExecDriver      string
 	MountLabel      string
 	ProcessLabel    string
-	Volumes         map[string]string
-	VolumesRW       map[string]bool
 	AppArmorProfile string
 	ExecIDs         []string
 	HostConfig      *runconfig.HostConfig
@@ -248,13 +246,16 @@ type ContainerJSONBase struct {
 
 type ContainerJSON struct {
 	*ContainerJSONBase
+	Mounts []MountPoint
 	Config *runconfig.Config
 }
 
 // backcompatibility struct along with ContainerConfig
-type ContainerJSONRaw struct {
+type ContainerJSONPre120 struct {
 	*ContainerJSONBase
-	Config *ContainerConfig
+	Volumes   map[string]string
+	VolumesRW map[string]bool
+	Config    *ContainerConfig
 }
 
 type ContainerConfig struct {
@@ -266,3 +267,13 @@ type ContainerConfig struct {
 	CpuShares  int64
 	Cpuset     string
 }
+
+// MountPoint represents a mount point configuration inside the container.
+type MountPoint struct {
+	Name        string `json:",omitempty"`
+	Source      string
+	Destination string
+	Driver      string `json:",omitempty"`
+	Mode        string // this is internally named `Relabel`
+	RW          bool
+}

+ 22 - 13
daemon/inspect.go

@@ -20,10 +20,22 @@ func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error
 		return nil, err
 	}
 
-	return &types.ContainerJSON{base, container.Config}, nil
+	mountPoints := make([]types.MountPoint, 0, len(container.MountPoints))
+	for _, m := range container.MountPoints {
+		mountPoints = append(mountPoints, types.MountPoint{
+			Name:        m.Name,
+			Source:      m.Path(),
+			Destination: m.Destination,
+			Driver:      m.Driver,
+			Mode:        m.Relabel,
+			RW:          m.RW,
+		})
+	}
+
+	return &types.ContainerJSON{base, mountPoints, container.Config}, nil
 }
 
-func (daemon *Daemon) ContainerInspectRaw(name string) (*types.ContainerJSONRaw, error) {
+func (daemon *Daemon) ContainerInspectPre120(name string) (*types.ContainerJSONPre120, error) {
 	container, err := daemon.Get(name)
 	if err != nil {
 		return nil, err
@@ -37,6 +49,13 @@ func (daemon *Daemon) ContainerInspectRaw(name string) (*types.ContainerJSONRaw,
 		return nil, err
 	}
 
+	volumes := make(map[string]string)
+	volumesRW := make(map[string]bool)
+	for _, m := range container.MountPoints {
+		volumes[m.Destination] = m.Path()
+		volumesRW[m.Destination] = m.RW
+	}
+
 	config := &types.ContainerConfig{
 		container.Config,
 		container.hostConfig.Memory,
@@ -45,7 +64,7 @@ func (daemon *Daemon) ContainerInspectRaw(name string) (*types.ContainerJSONRaw,
 		container.hostConfig.CpusetCpus,
 	}
 
-	return &types.ContainerJSONRaw{base, config}, nil
+	return &types.ContainerJSONPre120{base, volumes, volumesRW, config}, nil
 }
 
 func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSONBase, error) {
@@ -76,14 +95,6 @@ func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSON
 		FinishedAt: container.State.FinishedAt,
 	}
 
-	volumes := make(map[string]string)
-	volumesRW := make(map[string]bool)
-
-	for _, m := range container.MountPoints {
-		volumes[m.Destination] = m.Path()
-		volumesRW[m.Destination] = m.RW
-	}
-
 	contJSONBase := &types.ContainerJSONBase{
 		Id:              container.ID,
 		Created:         container.Created,
@@ -102,8 +113,6 @@ func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSON
 		ExecDriver:      container.ExecDriver,
 		MountLabel:      container.MountLabel,
 		ProcessLabel:    container.ProcessLabel,
-		Volumes:         volumes,
-		VolumesRW:       volumesRW,
 		AppArmorProfile: container.AppArmorProfile,
 		ExecIDs:         container.GetExecIDs(),
 		HostConfig:      &hostConfig,

+ 26 - 12
docs/reference/api/docker_remote_api_v1.20.md

@@ -144,9 +144,14 @@ Create a container
                    "com.example.license": "GPL",
                    "com.example.version": "1.0"
            },
-           "Volumes": {
-                   "/tmp": {}
-           },
+           "Mounts": [
+             {
+               "Source": "/data",
+               "Destination": "/data",
+               "Mode": "ro,Z",
+               "RW": false
+             }
+           ],
            "WorkingDir": "",
            "NetworkDisabled": false,
            "MacAddress": "12:34:56:78:9a:bc",
@@ -227,8 +232,7 @@ Json Parameters:
 -   **Entrypoint** - Set the entry point for the container as a string or an array
       of strings.
 -   **Image** - A string specifying the image name to use for the container.
--   **Volumes** – An object mapping mount point paths (strings) inside the
-      container to empty objects.
+-   **Mounts** - An array of mount points in the container.
 -   **WorkingDir** - A string specifying the working directory for commands to
       run in.
 -   **NetworkDisabled** - Boolean value, when true disables networking for the
@@ -424,8 +428,14 @@ Return low-level information on the container `id`
 			"Running": false,
 			"StartedAt": "2015-01-06T15:47:32.072697474Z"
 		},
-		"Volumes": {},
-		"VolumesRW": {}
+		"Mounts": [
+			{
+				"Source": "/data",
+				"Destination": "/data",
+				"Mode": "ro,Z",
+				"RW": false
+			}
+		]
 	}
 
 Status Codes:
@@ -1814,9 +1824,14 @@ Create a new image from a container's changes
          "Cmd": [
                  "date"
          ],
-         "Volumes": {
-                 "/tmp": {}
-         },
+         "Mounts": [
+           {
+             "Source": "/data",
+             "Destination": "/data",
+             "Mode": "ro,Z",
+             "RW": false
+           }
+         ],
          "Labels": {
                  "key1": "value1",
                  "key2": "value2"
@@ -2202,8 +2217,7 @@ Return low-level information about the `exec` command `id`.
         "ProcessLabel" : "",
         "AppArmorProfile" : "",
         "RestartCount" : 0,
-        "Volumes" : {},
-        "VolumesRW" : {}
+        "Mounts" : [],
       }
     }
 

+ 9 - 11
integration-cli/docker_api_containers_test.go

@@ -190,7 +190,7 @@ func (s *DockerSuite) TestContainerApiStartVolumeBinds(c *check.C) {
 	c.Assert(err, check.IsNil)
 	c.Assert(status, check.Equals, http.StatusNoContent)
 
-	pth, err := inspectFieldMap(name, "Volumes", "/tmp")
+	pth, err := inspectMountSourceField(name, "/tmp")
 	if err != nil {
 		c.Fatal(err)
 	}
@@ -233,7 +233,7 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {
 
 	dockerCmd(c, "run", "-d", "--name", volName, "-v", volPath, "busybox")
 
-	name := "TestContainerApiStartDupVolumeBinds"
+	name := "TestContainerApiStartVolumesFrom"
 	config := map[string]interface{}{
 		"Image":   "busybox",
 		"Volumes": map[string]struct{}{volPath: {}},
@@ -250,11 +250,11 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {
 	c.Assert(err, check.IsNil)
 	c.Assert(status, check.Equals, http.StatusNoContent)
 
-	pth, err := inspectFieldMap(name, "Volumes", volPath)
+	pth, err := inspectMountSourceField(name, volPath)
 	if err != nil {
 		c.Fatal(err)
 	}
-	pth2, err := inspectFieldMap(volName, "Volumes", volPath)
+	pth2, err := inspectMountSourceField(volName, volPath)
 	if err != nil {
 		c.Fatal(err)
 	}
@@ -705,7 +705,7 @@ func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) {
 func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) {
 	dockerCmd(c, "create", "-v", "/foo", "--name=one", "busybox")
 
-	fooDir, err := inspectFieldMap("one", "Volumes", "/foo")
+	fooDir, err := inspectMountSourceField("one", "/foo")
 	if err != nil {
 		c.Fatal(err)
 	}
@@ -717,7 +717,7 @@ func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) {
 	c.Assert(err, check.IsNil)
 	c.Assert(status, check.Equals, http.StatusNoContent)
 
-	fooDir2, err := inspectFieldMap("two", "Volumes", "/foo")
+	fooDir2, err := inspectMountSourceField("two", "/foo")
 	if err != nil {
 		c.Fatal(err)
 	}
@@ -1493,17 +1493,15 @@ func (s *DockerSuite) TestContainerApiDeleteRemoveVolume(c *check.C) {
 	id := strings.TrimSpace(out)
 	c.Assert(waitRun(id), check.IsNil)
 
-	vol, err := inspectFieldMap(id, "Volumes", "/testvolume")
-	c.Assert(err, check.IsNil)
-
-	_, err = os.Stat(vol)
+	source, err := inspectMountSourceField(id, "/testvolume")
+	_, err = os.Stat(source)
 	c.Assert(err, check.IsNil)
 
 	status, _, err := sockRequest("DELETE", "/containers/"+id+"?v=1&force=1", nil)
 	c.Assert(err, check.IsNil)
 	c.Assert(status, check.Equals, http.StatusNoContent)
 
-	if _, err := os.Stat(vol); !os.IsNotExist(err) {
+	if _, err := os.Stat(source); !os.IsNotExist(err) {
 		c.Fatalf("expected to get ErrNotExist error, got %v", err)
 	}
 }

+ 28 - 17
integration-cli/docker_api_inspect_test.go

@@ -2,6 +2,7 @@ package main
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"strings"
 
@@ -12,28 +13,38 @@ func (s *DockerSuite) TestInspectApiContainerResponse(c *check.C) {
 	out, _ := dockerCmd(c, "run", "-d", "busybox", "true")
 
 	cleanedContainerID := strings.TrimSpace(out)
+	keysBase := []string{"Id", "State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings",
+		"ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "GraphDriver"}
+
+	cases := []struct {
+		version string
+		keys    []string
+	}{
+		{"1.20", append(keysBase, "Mounts")},
+		{"1.19", append(keysBase, "Volumes", "VolumesRW")},
+	}
 
-	endpoint := "/containers/" + cleanedContainerID + "/json"
-	status, body, err := sockRequest("GET", endpoint, nil)
-	c.Assert(status, check.Equals, http.StatusOK)
-	c.Assert(err, check.IsNil)
+	for _, cs := range cases {
+		endpoint := fmt.Sprintf("/v%s/containers/%s/json", cs.version, cleanedContainerID)
 
-	var inspectJSON map[string]interface{}
-	if err = json.Unmarshal(body, &inspectJSON); err != nil {
-		c.Fatalf("unable to unmarshal body for latest version: %v", err)
-	}
+		status, body, err := sockRequest("GET", endpoint, nil)
+		c.Assert(status, check.Equals, http.StatusOK)
+		c.Assert(err, check.IsNil)
 
-	keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW", "GraphDriver"}
+		var inspectJSON map[string]interface{}
+		if err = json.Unmarshal(body, &inspectJSON); err != nil {
+			c.Fatalf("unable to unmarshal body for version %s: %v", cs.version, err)
+		}
 
-	keys = append(keys, "Id")
+		for _, key := range cs.keys {
+			if _, ok := inspectJSON[key]; !ok {
+				c.Fatalf("%s does not exist in response for version %s", key, cs.version)
+			}
+		}
 
-	for _, key := range keys {
-		if _, ok := inspectJSON[key]; !ok {
-			c.Fatalf("%s does not exist in response for latest version", key)
+		//Issue #6830: type not properly converted to JSON/back
+		if _, ok := inspectJSON["Path"].(bool); ok {
+			c.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling")
 		}
 	}
-	//Issue #6830: type not properly converted to JSON/back
-	if _, ok := inspectJSON["Path"].(bool); ok {
-		c.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling")
-	}
 }

+ 1 - 1
integration-cli/docker_cli_create_test.go

@@ -184,7 +184,7 @@ func (s *DockerSuite) TestCreateVolumesCreated(c *check.C) {
 	name := "test_create_volume"
 	dockerCmd(c, "create", "--name", name, "-v", "/foo", "busybox")
 
-	dir, err := inspectFieldMap(name, "Volumes", "/foo")
+	dir, err := inspectMountSourceField(name, "/foo")
 	if err != nil {
 		c.Fatalf("Error getting volume host path: %q", err)
 	}

+ 8 - 8
integration-cli/docker_cli_daemon_test.go

@@ -67,23 +67,23 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) {
 	if out, err := s.d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil {
 		c.Fatal(err, out)
 	}
+
 	if err := s.d.Restart(); err != nil {
 		c.Fatal(err)
 	}
 	if _, err := s.d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil {
 		c.Fatal(err)
 	}
+
 	if out, err := s.d.Cmd("rm", "-fv", "volrestarttest2"); err != nil {
 		c.Fatal(err, out)
 	}
-	v, err := s.d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1")
-	if err != nil {
-		c.Fatal(err)
-	}
-	volumes := make(map[string]string)
-	json.Unmarshal([]byte(v), &volumes)
-	if _, err := os.Stat(volumes["/foo"]); err != nil {
-		c.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err)
+
+	out, err := s.d.Cmd("inspect", "-f", "{{json .Mounts}}", "volrestarttest1")
+	c.Assert(err, check.IsNil)
+
+	if _, err := inspectMountPointJSON(out, "/foo"); err != nil {
+		c.Fatalf("Expected volume to exist: /foo, error: %v\n", err)
 	}
 }
 

+ 44 - 0
integration-cli/docker_cli_inspect_experimental_test.go

@@ -0,0 +1,44 @@
+// +build experimental
+
+package main
+
+import (
+	"github.com/docker/docker/api/types"
+	"github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestInspectNamedMountPoint(c *check.C) {
+	dockerCmd(c, "run", "-d", "--name", "test", "-v", "data:/data", "busybox", "cat")
+
+	vol, err := inspectFieldJSON("test", "Mounts")
+	c.Assert(err, check.IsNil)
+
+	var mp []types.MountPoint
+	err = unmarshalJSON([]byte(vol), &mp)
+	c.Assert(err, check.IsNil)
+
+	if len(mp) != 1 {
+		c.Fatalf("Expected 1 mount point, was %v\n", len(mp))
+	}
+
+	m := mp[0]
+	if m.Name != "data" {
+		c.Fatalf("Expected name data, was %s\n", m.Name)
+	}
+
+	if m.Driver != "local" {
+		c.Fatalf("Expected driver local, was %s\n", m.Driver)
+	}
+
+	if m.Source == "" {
+		c.Fatalf("Expected source to not be empty")
+	}
+
+	if m.RW != true {
+		c.Fatalf("Expected rw to be true")
+	}
+
+	if m.Destination != "/data" {
+		c.Fatalf("Expected destination /data, was %s\n", m.Destination)
+	}
+}

+ 42 - 0
integration-cli/docker_cli_inspect_test.go

@@ -6,6 +6,7 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/docker/docker/api/types"
 	"github.com/go-check/check"
 )
 
@@ -218,3 +219,44 @@ func (s *DockerSuite) TestInspectContainerGraphDriver(c *check.C) {
 		c.Fatalf("failed to inspect DeviceSize of the image: %s, %v", deviceSize, err)
 	}
 }
+
+func (s *DockerSuite) TestInspectBindMountPoint(c *check.C) {
+	dockerCmd(c, "run", "-d", "--name", "test", "-v", "/data:/data:ro,z", "busybox", "cat")
+
+	vol, err := inspectFieldJSON("test", "Mounts")
+	c.Assert(err, check.IsNil)
+
+	var mp []types.MountPoint
+	err = unmarshalJSON([]byte(vol), &mp)
+	c.Assert(err, check.IsNil)
+
+	if len(mp) != 1 {
+		c.Fatalf("Expected 1 mount point, was %v\n", len(mp))
+	}
+
+	m := mp[0]
+
+	if m.Name != "" {
+		c.Fatal("Expected name to be empty")
+	}
+
+	if m.Driver != "" {
+		c.Fatal("Expected driver to be empty")
+	}
+
+	if m.Source != "/data" {
+		c.Fatalf("Expected source /data, was %s\n", m.Source)
+	}
+
+	if m.Destination != "/data" {
+		c.Fatalf("Expected destination /data, was %s\n", m.Destination)
+	}
+
+	if m.Mode != "ro,z" {
+		c.Fatalf("Expected mode `ro,z`, was %s\n", m.Mode)
+	}
+
+	if m.RW != false {
+		c.Fatalf("Expected rw to be false")
+	}
+}

+ 6 - 6
integration-cli/docker_cli_restart_test.go

@@ -54,27 +54,27 @@ func (s *DockerSuite) TestRestartWithVolumes(c *check.C) {
 	out, _ := dockerCmd(c, "run", "-d", "-v", "/test", "busybox", "top")
 
 	cleanedContainerID := strings.TrimSpace(out)
-	out, _ = dockerCmd(c, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID)
+	out, _ = dockerCmd(c, "inspect", "--format", "{{ len .Mounts }}", cleanedContainerID)
 
 	if out = strings.Trim(out, " \n\r"); out != "1" {
 		c.Errorf("expect 1 volume received %s", out)
 	}
 
-	volumes, err := inspectField(cleanedContainerID, "Volumes")
+	source, err := inspectMountSourceField(cleanedContainerID, "/test")
 	c.Assert(err, check.IsNil)
 
 	dockerCmd(c, "restart", cleanedContainerID)
 
-	out, _ = dockerCmd(c, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID)
+	out, _ = dockerCmd(c, "inspect", "--format", "{{ len .Mounts }}", cleanedContainerID)
 	if out = strings.Trim(out, " \n\r"); out != "1" {
 		c.Errorf("expect 1 volume after restart received %s", out)
 	}
 
-	volumesAfterRestart, err := inspectField(cleanedContainerID, "Volumes")
+	sourceAfterRestart, err := inspectMountSourceField(cleanedContainerID, "/test")
 	c.Assert(err, check.IsNil)
 
-	if volumes != volumesAfterRestart {
-		c.Errorf("expected volume path: %s Actual path: %s", volumes, volumesAfterRestart)
+	if source != sourceAfterRestart {
+		c.Errorf("expected volume path: %s Actual path: %s", source, sourceAfterRestart)
 	}
 }
 

+ 12 - 12
integration-cli/docker_cli_run_test.go

@@ -1824,24 +1824,23 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
 
 	dockerCmd(c, "run", "-v", "/foo", "-v", "/bar/", "--name", "dark_helmet", "run_volumes_clean_paths")
 
-	out, err := inspectFieldMap("dark_helmet", "Volumes", "/foo/")
-	c.Assert(err, check.IsNil)
-	if out != "" {
+	out, err := inspectMountSourceField("dark_helmet", "/foo/")
+	if err != mountNotFound {
 		c.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out)
 	}
 
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/foo")
+	out, err = inspectMountSourceField("dark_helmet", "/foo")
 	c.Assert(err, check.IsNil)
 	if !strings.Contains(out, volumesConfigPath) {
 		c.Fatalf("Volume was not defined for /foo\n%q", out)
 	}
 
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar/")
-	c.Assert(err, check.IsNil)
-	if out != "" {
+	out, err = inspectMountSourceField("dark_helmet", "/bar/")
+	if err != mountNotFound {
 		c.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out)
 	}
-	out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar")
+
+	out, err = inspectMountSourceField("dark_helmet", "/bar")
 	c.Assert(err, check.IsNil)
 	if !strings.Contains(out, volumesConfigPath) {
 		c.Fatalf("Volume was not defined for /bar\n%q", out)
@@ -2483,14 +2482,15 @@ func (s *DockerSuite) TestVolumeFromMixedRWOptions(c *check.C) {
 	dockerCmd(c, "run", "--volumes-from", "parent:ro", "--name", "test-volumes-1", "busybox", "true")
 	dockerCmd(c, "run", "--volumes-from", "parent:rw", "--name", "test-volumes-2", "busybox", "true")
 
-	testRO, err := inspectFieldMap("test-volumes-1", ".VolumesRW", "/test")
+	mRO, err := inspectMountPoint("test-volumes-1", "/test")
 	c.Assert(err, check.IsNil)
-	if testRO != "false" {
+	if mRO.RW {
 		c.Fatalf("Expected RO volume was RW")
 	}
-	testRW, err := inspectFieldMap("test-volumes-2", ".VolumesRW", "/test")
+
+	mRW, err := inspectMountPoint("test-volumes-2", "/test")
 	c.Assert(err, check.IsNil)
-	if testRW != "true" {
+	if !mRW.RW {
 		c.Fatalf("Expected RW volume was RO")
 	}
 }

+ 41 - 0
integration-cli/docker_utils.go

@@ -20,6 +20,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/stringutils"
@@ -880,6 +881,46 @@ func inspectFieldMap(name, path, field string) (string, error) {
 	return inspectFilter(name, fmt.Sprintf("index .%s %q", path, field))
 }
 
+func inspectMountSourceField(name, destination string) (string, error) {
+	m, err := inspectMountPoint(name, destination)
+	if err != nil {
+		return "", err
+	}
+	return m.Source, nil
+}
+
+func inspectMountPoint(name, destination string) (types.MountPoint, error) {
+	out, err := inspectFieldJSON(name, "Mounts")
+	if err != nil {
+		return types.MountPoint{}, err
+	}
+
+	return inspectMountPointJSON(out, destination)
+}
+
+var mountNotFound = errors.New("mount point not found")
+
+func inspectMountPointJSON(j, destination string) (types.MountPoint, error) {
+	var mp []types.MountPoint
+	if err := unmarshalJSON([]byte(j), &mp); err != nil {
+		return types.MountPoint{}, err
+	}
+
+	var m *types.MountPoint
+	for _, c := range mp {
+		if c.Destination == destination {
+			m = &c
+			break
+		}
+	}
+
+	if m == nil {
+		return types.MountPoint{}, mountNotFound
+	}
+
+	return *m, nil
+}
+
 func getIDByName(name string) (string, error) {
 	return inspectField(name, "Id")
 }

+ 8 - 2
man/docker-inspect.1.md

@@ -95,8 +95,14 @@ To get information on a container use its ID or instance name:
     "ExecDriver": "native-0.2",
     "MountLabel": "",
     "ProcessLabel": "",
-    "Volumes": {},
-    "VolumesRW": {},
+    "Mounts": [
+      {
+        "Source": "/data",
+        "Destination": "/data",
+        "Mode": "ro,Z",
+        "RW": false
+      }
+    ],
     "AppArmorProfile": "",
     "ExecIDs": null,
     "HostConfig": {