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>
This commit is contained in:
David Calavera 2015-06-03 12:21:38 -07:00
parent 48a01a317c
commit 1c3cb2d31e
14 changed files with 264 additions and 89 deletions

View File

@ -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
}

View File

@ -225,8 +225,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
@ -235,13 +233,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 {
@ -253,3 +254,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
}

View File

@ -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,

View File

@ -140,9 +140,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",
@ -223,8 +228,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
@ -420,8 +424,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:
@ -1694,9 +1704,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"
@ -2082,8 +2097,7 @@ Return low-level information about the `exec` command `id`.
"ProcessLabel" : "",
"AppArmorProfile" : "",
"RestartCount" : 0,
"Volumes" : {},
"VolumesRW" : {}
"Mounts" : [],
}
}

View File

@ -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)
}
@ -1467,17 +1467,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)
}
}

View File

@ -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"}
endpoint := "/containers/" + cleanedContainerID + "/json"
status, body, err := sockRequest("GET", endpoint, nil)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
var inspectJSON map[string]interface{}
if err = json.Unmarshal(body, &inspectJSON); err != nil {
c.Fatalf("unable to unmarshal body for latest version: %v", err)
cases := []struct {
version string
keys []string
}{
{"1.20", append(keysBase, "Mounts")},
{"1.19", append(keysBase, "Volumes", "VolumesRW")},
}
keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW", "GraphDriver"}
for _, cs := range cases {
endpoint := fmt.Sprintf("/v%s/containers/%s/json", cs.version, cleanedContainerID)
keys = append(keys, "Id")
status, body, err := sockRequest("GET", endpoint, nil)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
for _, key := range keys {
if _, ok := inspectJSON[key]; !ok {
c.Fatalf("%s does not exist in response for latest version", key)
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)
}
for _, key := range cs.keys {
if _, ok := inspectJSON[key]; !ok {
c.Fatalf("%s does not exist in response for version %s", key, cs.version)
}
}
//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")
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -6,6 +6,7 @@ import (
"strconv"
"strings"
"github.com/docker/docker/api/types"
"github.com/go-check/check"
)
@ -18,7 +19,6 @@ func (s *DockerSuite) TestInspectImage(c *check.C) {
if id != imageTestID {
c.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id)
}
}
func (s *DockerSuite) TestInspectInt64(c *check.C) {
@ -265,3 +265,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")
}
}

View File

@ -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)
}
}

View File

@ -2302,24 +2302,23 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
c.Fatal(err, out)
}
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)
@ -3107,14 +3106,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")
}
}

View File

@ -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"
@ -874,6 +875,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")
}

View File

@ -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": {