Browse code

Add volume API/CLI

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2015/06/12 22:25:32
Showing 49 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,231 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"fmt"
6
+	"io"
7
+	"net/url"
8
+	"text/tabwriter"
9
+	"text/template"
10
+
11
+	"github.com/docker/docker/api/types"
12
+	Cli "github.com/docker/docker/cli"
13
+	"github.com/docker/docker/opts"
14
+	flag "github.com/docker/docker/pkg/mflag"
15
+	"github.com/docker/docker/pkg/parsers/filters"
16
+)
17
+
18
+// CmdVolume is the parent subcommand for all volume commands
19
+//
20
+// Usage: docker volume <COMMAND> <OPTS>
21
+func (cli *DockerCli) CmdVolume(args ...string) error {
22
+	description := "Manage Docker volumes\n\nCommands:\n"
23
+	commands := [][]string{
24
+		{"create", "Create a volume"},
25
+		{"inspect", "Return low-level information on a volume"},
26
+		{"ls", "List volumes"},
27
+		{"rm", "Remove a volume"},
28
+	}
29
+
30
+	for _, cmd := range commands {
31
+		description += fmt.Sprintf("  %-25.25s%s\n", cmd[0], cmd[1])
32
+	}
33
+
34
+	description += "\nRun 'docker volume COMMAND --help' for more information on a command."
35
+	cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, true)
36
+	cmd.ParseFlags(args, true)
37
+
38
+	return cli.CmdVolumeLs(args...)
39
+}
40
+
41
+// CmdVolumeLs outputs a list of Docker volumes.
42
+//
43
+// Usage: docker volume ls [OPTIONS]
44
+func (cli *DockerCli) CmdVolumeLs(args ...string) error {
45
+	cmd := Cli.Subcmd("volume ls", nil, "List volumes", true)
46
+
47
+	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display volume names")
48
+	flFilter := opts.NewListOpts(nil)
49
+	cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
50
+
51
+	cmd.Require(flag.Exact, 0)
52
+	cmd.ParseFlags(args, true)
53
+
54
+	volFilterArgs := filters.Args{}
55
+	for _, f := range flFilter.GetAll() {
56
+		var err error
57
+		volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
58
+		if err != nil {
59
+			return err
60
+		}
61
+	}
62
+
63
+	v := url.Values{}
64
+	if len(volFilterArgs) > 0 {
65
+		filterJSON, err := filters.ToParam(volFilterArgs)
66
+		if err != nil {
67
+			return err
68
+		}
69
+		v.Set("filters", filterJSON)
70
+	}
71
+
72
+	resp, err := cli.call("GET", "/volumes?"+v.Encode(), nil, nil)
73
+	if err != nil {
74
+		return err
75
+	}
76
+
77
+	var volumes types.VolumesListResponse
78
+	if err := json.NewDecoder(resp.body).Decode(&volumes); err != nil {
79
+		return err
80
+	}
81
+
82
+	w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
83
+	if !*quiet {
84
+		fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
85
+		fmt.Fprintf(w, "\n")
86
+	}
87
+
88
+	for _, vol := range volumes.Volumes {
89
+		if *quiet {
90
+			fmt.Fprintln(w, vol.Name)
91
+			continue
92
+		}
93
+		fmt.Fprintf(w, "%s\t%s\n", vol.Driver, vol.Name)
94
+	}
95
+	w.Flush()
96
+	return nil
97
+}
98
+
99
+// CmdVolumeInspect displays low-level information on one or more volumes.
100
+//
101
+// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
102
+func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
103
+	cmd := Cli.Subcmd("volume inspect", []string{"[VOLUME NAME]"}, "Return low-level information on a volume", true)
104
+	tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template.")
105
+	if err := cmd.Parse(args); err != nil {
106
+		return nil
107
+	}
108
+
109
+	cmd.Require(flag.Min, 1)
110
+	cmd.ParseFlags(args, true)
111
+
112
+	var tmpl *template.Template
113
+	if *tmplStr != "" {
114
+		var err error
115
+		tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
116
+		if err != nil {
117
+			return err
118
+		}
119
+	}
120
+
121
+	var status = 0
122
+	var volumes []*types.Volume
123
+	for _, name := range cmd.Args() {
124
+		resp, err := cli.call("GET", "/volumes/"+name, nil, nil)
125
+		if err != nil {
126
+			return err
127
+		}
128
+
129
+		var volume types.Volume
130
+		if err := json.NewDecoder(resp.body).Decode(&volume); err != nil {
131
+			fmt.Fprintf(cli.err, "%s\n", err)
132
+			status = 1
133
+			continue
134
+		}
135
+
136
+		if tmpl == nil {
137
+			volumes = append(volumes, &volume)
138
+			continue
139
+		}
140
+
141
+		if err := tmpl.Execute(cli.out, &volume); err != nil {
142
+			if err := tmpl.Execute(cli.out, &volume); err != nil {
143
+				fmt.Fprintf(cli.err, "%s\n", err)
144
+				status = 1
145
+				continue
146
+			}
147
+		}
148
+		io.WriteString(cli.out, "\n")
149
+	}
150
+
151
+	if tmpl != nil {
152
+		return nil
153
+	}
154
+
155
+	b, err := json.MarshalIndent(volumes, "", "    ")
156
+	if err != nil {
157
+		return err
158
+	}
159
+	_, err = io.Copy(cli.out, bytes.NewReader(b))
160
+	if err != nil {
161
+		return err
162
+	}
163
+	io.WriteString(cli.out, "\n")
164
+
165
+	if status != 0 {
166
+		return Cli.StatusError{StatusCode: status}
167
+	}
168
+	return nil
169
+}
170
+
171
+// CmdVolumeCreate creates a new container from a given image.
172
+//
173
+// Usage: docker volume create [OPTIONS]
174
+func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
175
+	cmd := Cli.Subcmd("volume create", nil, "Create a volume", true)
176
+	flDriver := cmd.String([]string{"d", "-driver"}, "local", "Specify volume driver name")
177
+	flName := cmd.String([]string{"-name"}, "", "Specify volume name")
178
+
179
+	flDriverOpts := opts.NewMapOpts(nil, nil)
180
+	cmd.Var(flDriverOpts, []string{"o", "-opt"}, "Set driver specific options")
181
+
182
+	cmd.Require(flag.Exact, 0)
183
+	cmd.ParseFlags(args, true)
184
+
185
+	volReq := &types.VolumeCreateRequest{
186
+		Driver:     *flDriver,
187
+		DriverOpts: flDriverOpts.GetAll(),
188
+	}
189
+
190
+	if *flName != "" {
191
+		volReq.Name = *flName
192
+	}
193
+
194
+	resp, err := cli.call("POST", "/volumes", volReq, nil)
195
+	if err != nil {
196
+		return err
197
+	}
198
+
199
+	var vol types.Volume
200
+	if err := json.NewDecoder(resp.body).Decode(&vol); err != nil {
201
+		return err
202
+	}
203
+	fmt.Fprintf(cli.out, "%s\n", vol.Name)
204
+	return nil
205
+}
206
+
207
+// CmdVolumeRm removes one or more containers.
208
+//
209
+// Usage: docker volume rm VOLUME [VOLUME...]
210
+func (cli *DockerCli) CmdVolumeRm(args ...string) error {
211
+	cmd := Cli.Subcmd("volume rm", []string{"[NAME]"}, "Remove a volume", true)
212
+	cmd.Require(flag.Min, 1)
213
+	cmd.ParseFlags(args, true)
214
+
215
+	var status = 0
216
+	for _, name := range cmd.Args() {
217
+		_, err := cli.call("DELETE", "/volumes/"+name, nil, nil)
218
+		if err != nil {
219
+			fmt.Fprintf(cli.err, "%s\n", err)
220
+			status = 1
221
+			continue
222
+		}
223
+		fmt.Fprintf(cli.out, "%s\n", name)
224
+	}
225
+
226
+	if status != 0 {
227
+		return Cli.StatusError{StatusCode: status}
228
+	}
229
+	return nil
230
+}
... ...
@@ -328,6 +328,8 @@ func createRouter(s *Server) *mux.Router {
328 328
 			"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
329 329
 			"/exec/{id:.*}/json":              s.getExecByID,
330 330
 			"/containers/{name:.*}/archive":   s.getContainersArchive,
331
+			"/volumes":                        s.getVolumesList,
332
+			"/volumes/{name:.*}":              s.getVolumeByName,
331 333
 		},
332 334
 		"POST": {
333 335
 			"/auth":                         s.postAuth,
... ...
@@ -352,6 +354,7 @@ func createRouter(s *Server) *mux.Router {
352 352
 			"/exec/{name:.*}/start":         s.postContainerExecStart,
353 353
 			"/exec/{name:.*}/resize":        s.postContainerExecResize,
354 354
 			"/containers/{name:.*}/rename":  s.postContainerRename,
355
+			"/volumes":                      s.postVolumesCreate,
355 356
 		},
356 357
 		"PUT": {
357 358
 			"/containers/{name:.*}/archive": s.putContainersArchive,
... ...
@@ -359,6 +362,7 @@ func createRouter(s *Server) *mux.Router {
359 359
 		"DELETE": {
360 360
 			"/containers/{name:.*}": s.deleteContainers,
361 361
 			"/images/{name:.*}":     s.deleteImages,
362
+			"/volumes/{name:.*}":    s.deleteVolumes,
362 363
 		},
363 364
 		"OPTIONS": {
364 365
 			"": s.optionsHandler,
365 366
new file mode 100644
... ...
@@ -0,0 +1,65 @@
0
+package server
1
+
2
+import (
3
+	"encoding/json"
4
+	"net/http"
5
+
6
+	"github.com/docker/docker/api/types"
7
+	"github.com/docker/docker/pkg/version"
8
+)
9
+
10
+func (s *Server) getVolumesList(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
11
+	if err := parseForm(r); err != nil {
12
+		return err
13
+	}
14
+
15
+	volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
16
+	if err != nil {
17
+		return err
18
+	}
19
+	return writeJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
20
+}
21
+
22
+func (s *Server) getVolumeByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
23
+	if err := parseForm(r); err != nil {
24
+		return err
25
+	}
26
+
27
+	v, err := s.daemon.VolumeInspect(vars["name"])
28
+	if err != nil {
29
+		return err
30
+	}
31
+	return writeJSON(w, http.StatusOK, v)
32
+}
33
+
34
+func (s *Server) postVolumesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
35
+	if err := parseForm(r); err != nil {
36
+		return err
37
+	}
38
+
39
+	if err := checkForJSON(r); err != nil {
40
+		return err
41
+	}
42
+
43
+	var req types.VolumeCreateRequest
44
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
45
+		return err
46
+	}
47
+
48
+	volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
49
+	if err != nil {
50
+		return err
51
+	}
52
+	return writeJSON(w, http.StatusCreated, volume)
53
+}
54
+
55
+func (s *Server) deleteVolumes(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
56
+	if err := parseForm(r); err != nil {
57
+		return err
58
+	}
59
+	if err := s.daemon.VolumeRm(vars["name"]); err != nil {
60
+		return err
61
+	}
62
+	w.WriteHeader(http.StatusNoContent)
63
+	return nil
64
+}
... ...
@@ -301,3 +301,24 @@ type MountPoint struct {
301 301
 	Mode        string
302 302
 	RW          bool
303 303
 }
304
+
305
+// Volume represents the configuration of a volume for the remote API
306
+type Volume struct {
307
+	Name       string // Name is the name of the volume
308
+	Driver     string // Driver is the Driver name used to create the volume
309
+	Mountpoint string // Mountpoint is the location on disk of the volume
310
+}
311
+
312
+// VolumesListResponse contains the response for the remote API:
313
+// GET "/volumes"
314
+type VolumesListResponse struct {
315
+	Volumes []*Volume // Volumes is the list of volumes being returned
316
+}
317
+
318
+// VolumeCreateRequest contains the response for the remote API:
319
+// POST "/volumes"
320
+type VolumeCreateRequest struct {
321
+	Name       string            // Name is the requested name of the volume
322
+	Driver     string            // Driver is the name of the driver that should be used to create the volume
323
+	DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume.
324
+}
... ...
@@ -1197,7 +1197,7 @@ func (container *Container) isDestinationMounted(destination string) bool {
1197 1197
 func (container *Container) prepareMountPoints() error {
1198 1198
 	for _, config := range container.MountPoints {
1199 1199
 		if len(config.Driver) > 0 {
1200
-			v, err := createVolume(config.Name, config.Driver)
1200
+			v, err := container.daemon.createVolume(config.Name, config.Driver, nil)
1201 1201
 			if err != nil {
1202 1202
 				return err
1203 1203
 			}
... ...
@@ -1207,14 +1207,23 @@ func (container *Container) prepareMountPoints() error {
1207 1207
 	return nil
1208 1208
 }
1209 1209
 
1210
-func (container *Container) removeMountPoints() error {
1210
+func (container *Container) removeMountPoints(rm bool) error {
1211
+	var rmErrors []string
1211 1212
 	for _, m := range container.MountPoints {
1212
-		if m.Volume != nil {
1213
-			if err := removeVolume(m.Volume); err != nil {
1214
-				return err
1213
+		if m.Volume == nil {
1214
+			continue
1215
+		}
1216
+		container.daemon.volumes.Decrement(m.Volume)
1217
+		if rm {
1218
+			if err := container.daemon.volumes.Remove(m.Volume); err != nil {
1219
+				rmErrors = append(rmErrors, fmt.Sprintf("%v\n", err))
1220
+				continue
1215 1221
 			}
1216 1222
 		}
1217 1223
 	}
1224
+	if len(rmErrors) > 0 {
1225
+		return fmt.Errorf("Error removing volumes:\n%v", rmErrors)
1226
+	}
1218 1227
 	return nil
1219 1228
 }
1220 1229
 
... ...
@@ -169,7 +169,7 @@ func (container *Container) prepareMountPoints() error {
169 169
 }
170 170
 
171 171
 // removeMountPoints is a no-op on Windows.
172
-func (container *Container) removeMountPoints() error {
172
+func (container *Container) removeMountPoints(_ bool) error {
173 173
 	return nil
174 174
 }
175 175
 
... ...
@@ -4,9 +4,11 @@ import (
4 4
 	"fmt"
5 5
 
6 6
 	"github.com/Sirupsen/logrus"
7
+	"github.com/docker/docker/api/types"
7 8
 	"github.com/docker/docker/graph/tags"
8 9
 	"github.com/docker/docker/image"
9 10
 	"github.com/docker/docker/pkg/parsers"
11
+	"github.com/docker/docker/pkg/stringid"
10 12
 	"github.com/docker/docker/runconfig"
11 13
 	"github.com/opencontainers/runc/libcontainer/label"
12 14
 )
... ...
@@ -124,3 +126,17 @@ func (daemon *Daemon) GenerateSecurityOpt(ipcMode runconfig.IpcMode, pidMode run
124 124
 	}
125 125
 	return nil, nil
126 126
 }
127
+
128
+// VolumeCreate creates a volume with the specified name, driver, and opts
129
+// This is called directly from the remote API
130
+func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]string) (*types.Volume, error) {
131
+	if name == "" {
132
+		name = stringid.GenerateNonCryptoID()
133
+	}
134
+
135
+	v, err := daemon.volumes.Create(name, driverName, opts)
136
+	if err != nil {
137
+		return nil, err
138
+	}
139
+	return volumeToAPIType(v), nil
140
+}
... ...
@@ -54,10 +54,11 @@ func createContainerPlatformSpecificSettings(container *Container, config *runco
54 54
 			}
55 55
 		}
56 56
 
57
-		v, err := createVolume(name, volumeDriver)
57
+		v, err := container.daemon.createVolume(name, volumeDriver, nil)
58 58
 		if err != nil {
59 59
 			return err
60 60
 		}
61
+
61 62
 		if err := label.Relabel(v.Path(), container.MountLabel, "z"); err != nil {
62 63
 			return err
63 64
 		}
... ...
@@ -103,6 +103,7 @@ type Daemon struct {
103 103
 	RegistryService  *registry.Service
104 104
 	EventsService    *events.Events
105 105
 	netController    libnetwork.NetworkController
106
+	volumes          *volumeStore
106 107
 	root             string
107 108
 	shutdown         bool
108 109
 }
... ...
@@ -653,7 +654,8 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
653 653
 	}
654 654
 
655 655
 	// Configure the volumes driver
656
-	if err := configureVolumes(config); err != nil {
656
+	volStore, err := configureVolumes(config)
657
+	if err != nil {
657 658
 		return nil, err
658 659
 	}
659 660
 
... ...
@@ -740,6 +742,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
740 740
 	d.defaultLogConfig = config.LogConfig
741 741
 	d.RegistryService = registryService
742 742
 	d.EventsService = eventsService
743
+	d.volumes = volStore
743 744
 	d.root = config.Root
744 745
 	go d.execCommandGC()
745 746
 
... ...
@@ -13,7 +13,7 @@ import (
13 13
 	"github.com/docker/docker/pkg/truncindex"
14 14
 	"github.com/docker/docker/runconfig"
15 15
 	"github.com/docker/docker/volume"
16
-	"github.com/docker/docker/volume/drivers"
16
+	volumedrivers "github.com/docker/docker/volume/drivers"
17 17
 	"github.com/docker/docker/volume/local"
18 18
 )
19 19
 
... ...
@@ -486,12 +486,12 @@ func TestRemoveLocalVolumesFollowingSymlinks(t *testing.T) {
486 486
 	}
487 487
 
488 488
 	m := c.MountPoints["/vol1"]
489
-	v, err := createVolume(m.Name, m.Driver)
489
+	_, err = daemon.VolumeCreate(m.Name, m.Driver, nil)
490 490
 	if err != nil {
491 491
 		t.Fatal(err)
492 492
 	}
493 493
 
494
-	if err := removeVolume(v); err != nil {
494
+	if err := daemon.VolumeRm(m.Name); err != nil {
495 495
 		t.Fatal(err)
496 496
 	}
497 497
 
... ...
@@ -505,6 +505,7 @@ func initDaemonForVolumesTest(tmp string) (*Daemon, error) {
505 505
 	daemon := &Daemon{
506 506
 		repository: tmp,
507 507
 		root:       tmp,
508
+		volumes:    newVolumeStore([]volume.Volume{}),
508 509
 	}
509 510
 
510 511
 	volumesDriver, err := local.New(tmp)
... ...
@@ -256,13 +256,13 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
256 256
 	return migrateIfAufs(driver, root)
257 257
 }
258 258
 
259
-func configureVolumes(config *Config) error {
259
+func configureVolumes(config *Config) (*volumeStore, error) {
260 260
 	volumesDriver, err := local.New(config.Root)
261 261
 	if err != nil {
262
-		return err
262
+		return nil, err
263 263
 	}
264 264
 	volumedrivers.Register(volumesDriver, volumesDriver.Name())
265
-	return nil
265
+	return newVolumeStore(volumesDriver.List()), nil
266 266
 }
267 267
 
268 268
 func configureSysInit(config *Config) (string, error) {
... ...
@@ -74,9 +74,9 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
74 74
 	return nil
75 75
 }
76 76
 
77
-func configureVolumes(config *Config) error {
77
+func configureVolumes(config *Config) (*volumeStore, error) {
78 78
 	// Windows does not support volumes at this time
79
-	return nil
79
+	return &volumeStore{}, nil
80 80
 }
81 81
 
82 82
 func configureSysInit(config *Config) (string, error) {
... ...
@@ -50,9 +50,7 @@ func (daemon *Daemon) ContainerRm(name string, config *ContainerRmConfig) error
50 50
 		return fmt.Errorf("Cannot destroy container %s: %v", name, err)
51 51
 	}
52 52
 
53
-	if config.RemoveVolume {
54
-		container.removeMountPoints()
55
-	}
53
+	container.removeMountPoints(config.RemoveVolume)
56 54
 	return nil
57 55
 }
58 56
 
... ...
@@ -137,6 +135,19 @@ func (daemon *Daemon) rm(container *Container, forceRemove bool) (err error) {
137 137
 	return nil
138 138
 }
139 139
 
140
-func (daemon *Daemon) DeleteVolumes(c *Container) error {
141
-	return c.removeMountPoints()
140
+// VolumeRm removes the volume with the given name.
141
+// If the volume is referenced by a container it is not removed
142
+// This is called directly from the remote API
143
+func (daemon *Daemon) VolumeRm(name string) error {
144
+	v, err := daemon.volumes.Get(name)
145
+	if err != nil {
146
+		return err
147
+	}
148
+	if err := daemon.volumes.Remove(v); err != nil {
149
+		if err == ErrVolumeInUse {
150
+			return fmt.Errorf("Conflict: %v", err)
151
+		}
152
+		return err
153
+	}
154
+	return nil
142 155
 }
... ...
@@ -97,3 +97,11 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) {
97 97
 	}
98 98
 	return eConfig, nil
99 99
 }
100
+
101
+func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
102
+	v, err := daemon.volumes.Get(name)
103
+	if err != nil {
104
+		return nil, err
105
+	}
106
+	return volumeToAPIType(v), nil
107
+}
... ...
@@ -214,3 +214,32 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container,
214 214
 	}
215 215
 	return containers, nil
216 216
 }
217
+
218
+func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
219
+	var volumesOut []*types.Volume
220
+	volFilters, err := filters.FromParam(filter)
221
+	if err != nil {
222
+		return nil, err
223
+	}
224
+
225
+	filterUsed := false
226
+	if i, ok := volFilters["dangling"]; ok {
227
+		if len(i) > 1 {
228
+			return nil, fmt.Errorf("Conflict: cannot use more than 1 value for `dangling` filter")
229
+		}
230
+
231
+		filterValue := i[0]
232
+		if strings.ToLower(filterValue) == "true" || filterValue == "1" {
233
+			filterUsed = true
234
+		}
235
+	}
236
+
237
+	volumes := daemon.volumes.List()
238
+	for _, v := range volumes {
239
+		if filterUsed && daemon.volumes.Count(v) == 0 {
240
+			continue
241
+		}
242
+		volumesOut = append(volumesOut, volumeToAPIType(v))
243
+	}
244
+	return volumesOut, nil
245
+}
... ...
@@ -7,15 +7,24 @@ import (
7 7
 	"os"
8 8
 	"path/filepath"
9 9
 	"strings"
10
+	"sync"
10 11
 
12
+	"github.com/docker/docker/api/types"
11 13
 	"github.com/docker/docker/pkg/chrootarchive"
12 14
 	"github.com/docker/docker/pkg/system"
13 15
 	"github.com/docker/docker/volume"
16
+	"github.com/docker/docker/volume/drivers"
14 17
 )
15 18
 
16
-// ErrVolumeReadonly is used to signal an error when trying to copy data into
17
-// a volume mount that is not writable.
18
-var ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
19
+var (
20
+	// ErrVolumeReadonly is used to signal an error when trying to copy data into
21
+	// a volume mount that is not writable.
22
+	ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
23
+	// ErrVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container
24
+	ErrVolumeInUse = errors.New("volume is in use")
25
+	// ErrNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
26
+	ErrNoSuchVolume = errors.New("no such volume")
27
+)
19 28
 
20 29
 // mountPoint is the intersection point between a volume and a container. It
21 30
 // specifies which volume is to be used and where inside a container it should
... ...
@@ -92,3 +101,144 @@ func copyExistingContents(source, destination string) error {
92 92
 	}
93 93
 	return copyOwnership(source, destination)
94 94
 }
95
+
96
+func newVolumeStore(vols []volume.Volume) *volumeStore {
97
+	store := &volumeStore{
98
+		vols: make(map[string]*volumeCounter),
99
+	}
100
+	for _, v := range vols {
101
+		store.vols[v.Name()] = &volumeCounter{v, 0}
102
+	}
103
+	return store
104
+}
105
+
106
+// volumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
107
+type volumeStore struct {
108
+	vols map[string]*volumeCounter
109
+	mu   sync.Mutex
110
+}
111
+
112
+type volumeCounter struct {
113
+	volume.Volume
114
+	count int
115
+}
116
+
117
+func getVolumeDriver(name string) (volume.Driver, error) {
118
+	if name == "" {
119
+		name = volume.DefaultDriverName
120
+	}
121
+	return volumedrivers.Lookup(name)
122
+}
123
+
124
+// Create tries to find an existing volume with the given name or create a new one from the passed in driver
125
+func (s *volumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
126
+	s.mu.Lock()
127
+	defer s.mu.Unlock()
128
+
129
+	if vc, exists := s.vols[name]; exists {
130
+		return vc.Volume, nil
131
+	}
132
+
133
+	vd, err := getVolumeDriver(driverName)
134
+	if err != nil {
135
+		return nil, err
136
+	}
137
+
138
+	v, err := vd.Create(name, opts)
139
+	if err != nil {
140
+		return nil, err
141
+	}
142
+
143
+	s.vols[v.Name()] = &volumeCounter{v, 0}
144
+	return v, nil
145
+}
146
+
147
+// Get looks if a volume with the given name exists and returns it if so
148
+func (s *volumeStore) Get(name string) (volume.Volume, error) {
149
+	s.mu.Lock()
150
+	defer s.mu.Unlock()
151
+	vc, exists := s.vols[name]
152
+	if !exists {
153
+		return nil, ErrNoSuchVolume
154
+	}
155
+	return vc.Volume, nil
156
+}
157
+
158
+// Remove removes the requested volume. A volume is not removed if the usage count is > 0
159
+func (s *volumeStore) Remove(v volume.Volume) error {
160
+	s.mu.Lock()
161
+	defer s.mu.Unlock()
162
+	name := v.Name()
163
+	vc, exists := s.vols[name]
164
+	if !exists {
165
+		return ErrNoSuchVolume
166
+	}
167
+
168
+	if vc.count != 0 {
169
+		return ErrVolumeInUse
170
+	}
171
+
172
+	vd, err := getVolumeDriver(vc.DriverName())
173
+	if err != nil {
174
+		return err
175
+	}
176
+	if err := vd.Remove(vc.Volume); err != nil {
177
+		return err
178
+	}
179
+	delete(s.vols, name)
180
+	return nil
181
+}
182
+
183
+// Increment increments the usage count of the passed in volume by 1
184
+func (s *volumeStore) Increment(v volume.Volume) {
185
+	s.mu.Lock()
186
+	defer s.mu.Unlock()
187
+
188
+	vc, exists := s.vols[v.Name()]
189
+	if !exists {
190
+		s.vols[v.Name()] = &volumeCounter{v, 1}
191
+		return
192
+	}
193
+	vc.count++
194
+	return
195
+}
196
+
197
+// Decrement decrements the usage count of the passed in volume by 1
198
+func (s *volumeStore) Decrement(v volume.Volume) {
199
+	s.mu.Lock()
200
+	defer s.mu.Unlock()
201
+
202
+	vc, exists := s.vols[v.Name()]
203
+	if !exists {
204
+		return
205
+	}
206
+	vc.count--
207
+	return
208
+}
209
+
210
+// Count returns the usage count of the passed in volume
211
+func (s *volumeStore) Count(v volume.Volume) int {
212
+	vc, exists := s.vols[v.Name()]
213
+	if !exists {
214
+		return 0
215
+	}
216
+	return vc.count
217
+}
218
+
219
+// List returns all the available volumes
220
+func (s *volumeStore) List() []volume.Volume {
221
+	var ls []volume.Volume
222
+	for _, vc := range s.vols {
223
+		ls = append(ls, vc.Volume)
224
+	}
225
+	return ls
226
+}
227
+
228
+// volumeToAPIType converts a volume.Volume to the type used by the remote API
229
+func volumeToAPIType(v volume.Volume) *types.Volume {
230
+	return &types.Volume{
231
+		Name:       v.Name(),
232
+		Driver:     v.DriverName(),
233
+		Mountpoint: v.Path(),
234
+	}
235
+}
... ...
@@ -12,9 +12,9 @@ import (
12 12
 
13 13
 type fakeDriver struct{}
14 14
 
15
-func (fakeDriver) Name() string                              { return "fake" }
16
-func (fakeDriver) Create(name string) (volume.Volume, error) { return nil, nil }
17
-func (fakeDriver) Remove(v volume.Volume) error              { return nil }
15
+func (fakeDriver) Name() string                                                      { return "fake" }
16
+func (fakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) { return nil, nil }
17
+func (fakeDriver) Remove(v volume.Volume) error                                      { return nil }
18 18
 
19 19
 func TestGetVolumeDriver(t *testing.T) {
20 20
 	_, err := getVolumeDriver("missing")
... ...
@@ -15,7 +15,7 @@ import (
15 15
 	"github.com/docker/docker/pkg/system"
16 16
 	"github.com/docker/docker/runconfig"
17 17
 	"github.com/docker/docker/volume"
18
-	"github.com/docker/docker/volume/drivers"
18
+	volumedrivers "github.com/docker/docker/volume/drivers"
19 19
 	"github.com/docker/docker/volume/local"
20 20
 	"github.com/opencontainers/runc/libcontainer/label"
21 21
 )
... ...
@@ -138,7 +138,7 @@ func (m mounts) parts(i int) int {
138 138
 // It preserves the volume json configuration generated pre Docker 1.7 to be able to
139 139
 // downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility.
140 140
 func migrateVolume(id, vfs string) error {
141
-	l, err := getVolumeDriver(volume.DefaultDriverName)
141
+	l, err := volumedrivers.Lookup(volume.DefaultDriverName)
142 142
 	if err != nil {
143 143
 		return err
144 144
 	}
... ...
@@ -209,7 +209,7 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
209 209
 		// Volumes created with a Docker version >= 1.7. We verify integrity in case of data created
210 210
 		// with Docker 1.7 RC versions that put the information in
211 211
 		// DOCKER_ROOT/volumes/VOLUME_ID rather than DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
212
-		l, err := getVolumeDriver(volume.DefaultDriverName)
212
+		l, err := volumedrivers.Lookup(volume.DefaultDriverName)
213 213
 		if err != nil {
214 214
 			return err
215 215
 		}
... ...
@@ -311,7 +311,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
311 311
 			}
312 312
 
313 313
 			if len(cp.Source) == 0 {
314
-				v, err := createVolume(cp.Name, cp.Driver)
314
+				v, err := daemon.createVolume(cp.Name, cp.Driver, nil)
315 315
 				if err != nil {
316 316
 					return err
317 317
 				}
... ...
@@ -336,7 +336,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
336 336
 
337 337
 		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
338 338
 			// create the volume
339
-			v, err := createVolume(bind.Name, bind.Driver)
339
+			v, err := daemon.createVolume(bind.Name, bind.Driver, nil)
340 340
 			if err != nil {
341 341
 				return err
342 342
 			}
... ...
@@ -362,6 +362,11 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
362 362
 		if m.BackwardsCompatible() {
363 363
 			bcVolumes[m.Destination] = m.Path()
364 364
 			bcVolumesRW[m.Destination] = m.RW
365
+
366
+			// This mountpoint is replacing an existing one, so the count needs to be decremented
367
+			if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
368
+				daemon.volumes.Decrement(mp.Volume)
369
+			}
365 370
 		}
366 371
 	}
367 372
 
... ...
@@ -375,29 +380,13 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
375 375
 }
376 376
 
377 377
 // createVolume creates a volume.
378
-func createVolume(name, driverName string) (volume.Volume, error) {
379
-	vd, err := getVolumeDriver(driverName)
378
+func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
379
+	v, err := daemon.volumes.Create(name, driverName, opts)
380 380
 	if err != nil {
381 381
 		return nil, err
382 382
 	}
383
-	return vd.Create(name)
384
-}
385
-
386
-// removeVolume removes a volume.
387
-func removeVolume(v volume.Volume) error {
388
-	vd, err := getVolumeDriver(v.DriverName())
389
-	if err != nil {
390
-		return nil
391
-	}
392
-	return vd.Remove(v)
393
-}
394
-
395
-// getVolumeDriver returns the volume driver for the supplied name.
396
-func getVolumeDriver(name string) (volume.Driver, error) {
397
-	if name == "" {
398
-		name = volume.DefaultDriverName
399
-	}
400
-	return volumedrivers.Lookup(name)
383
+	daemon.volumes.Increment(v)
384
+	return v, nil
401 385
 }
402 386
 
403 387
 // parseVolumeSource parses the origin sources that's mounted into the container.
... ...
@@ -59,5 +59,6 @@ var dockerCommands = []command{
59 59
 	{"top", "Display the running processes of a container"},
60 60
 	{"unpause", "Unpause all processes within a container"},
61 61
 	{"version", "Show the Docker version information"},
62
+	{"volume", "Manage Docker volumes"},
62 63
 	{"wait", "Block until a container stops, then print its exit code"},
63 64
 }
... ...
@@ -52,13 +52,15 @@ containers.
52 52
 **Request**:
53 53
 ```
54 54
 {
55
-    "Name": "volume_name"
55
+    "Name": "volume_name",
56
+    "Opts": {}
56 57
 }
57 58
 ```
58 59
 
59 60
 Instruct the plugin that the user wants to create a volume, given a user
60 61
 specified volume name.  The plugin does not need to actually manifest the
61 62
 volume on the filesystem yet (until Mount is called).
63
+Opts is a map of driver specific options passed through from the user request.
62 64
 
63 65
 **Response**:
64 66
 ```
... ...
@@ -10,39 +10,48 @@ parent = "smn_remoteapi"
10 10
 
11 11
 # Docker Remote API
12 12
 
13
- - By default the Docker daemon listens on `unix:///var/run/docker.sock`
14
-   and the client must have `root` access to interact with the daemon.
15
- - If you are using `docker-machine`, the Docker daemon is on a virtual host that uses an encrypted TCP socket. In this situation, you need to add extra
16
-   parameters to `curl` or `wget` when making test API requests:
17
-   `curl --insecure --cert ~/.docker/cert.pem --key ~/.docker/key.pem https://YOUR_VM_IP:2376/images/json`
18
-   or 
19
-   `wget --no-check-certificate --certificate=$DOCKER_CERT_PATH/cert.pem --private-key=$DOCKER_CERT_PATH/key.pem https://your_vm_ip:2376/images/json -O - -q`
20
- - If a group named `docker` exists on your system, docker will apply
21
-   ownership of the socket to the group.
22
- - The API tends to be REST, but for some complex commands, like attach
23
-   or pull, the HTTP connection is hijacked to transport STDOUT, STDIN,
24
-   and STDERR.
25
- - Since API version 1.2, the auth configuration is now handled client
26
-   side, so the client has to send the `authConfig` as a `POST` in `/images/(name)/push`.
27
- - authConfig, set as the `X-Registry-Auth` header, is currently a Base64
28
-   encoded (JSON) string with the following structure:
29
-   `{"username": "string", "password": "string", "email": "string",
30
-   "serveraddress" : "string", "auth": ""}`. Notice that `auth` is to be left
31
-   empty, `serveraddress` is a domain/ip without protocol, and that double
32
-   quotes (instead of single ones) are required.
33
- - The Remote API uses an open schema model.  In this model, unknown 
34
-   properties in incoming messages will be ignored.
35
-   Client applications need to take this into account to ensure
36
-   they will not break when talking to newer Docker daemons.
37
-
38
-The current version of the API is v1.21
39
-
40
-Calling `/info` is the same as calling
41
-`/v1.21/info`.
42
-
43
-You can still call an old version of the API using
13
+Docker's Remote API uses an open schema model.  In this model, unknown 
14
+properties in incoming messages are ignored. Client applications need to take
15
+this behavior into account to ensure they do not break when talking to newer
16
+Docker daemons.
17
+
18
+The API tends to be REST, but for some complex commands, like attach or pull,
19
+the HTTP connection is hijacked to transport STDOUT, STDIN, and STDERR.
20
+   
21
+By default the Docker daemon listens on `unix:///var/run/docker.sock` and the
22
+client must have `root` access to interact with the daemon. If a group named
23
+`docker` exists on your system, `docker` applies ownership of the socket to the
24
+group.
25
+
26
+The current version of the API is v1.21 which means calling `/info` is the same
27
+as calling `/v1.21/info`. To call an older version of the API use
44 28
 `/v1.20/info`.
45 29
 
30
+## Authentication
31
+
32
+Since API version 1.2, the auth configuration is now handled client side, so the
33
+client has to send the `authConfig` as a `POST` in `/images/(name)/push`. The
34
+`authConfig`, set as the `X-Registry-Auth` header, is currently a Base64 encoded
35
+(JSON) string with the following structure:
36
+
37
+```
38
+{"username": "string", "password": "string", "email": "string",
39
+   "serveraddress" : "string", "auth": ""}
40
+```
41
+   
42
+Callers should leave the `auth` empty. The `serveraddress` is a domain/ip
43
+without protocol. Throughout this structure, double quotes are required.
44
+
45
+## Using Docker Machine with the API
46
+
47
+If you are using `docker-machine`, the Docker daemon is on a virtual host that uses an encrypted TCP socket. This means, for Docker Machine users, you need to add extra parameters to `curl` or `wget` when making test API requests, for example:
48
+
49
+```
50
+curl --insecure --cert ~/.docker/cert.pem --key ~/.docker/key.pem https://YOUR_VM_IP:2376/images/json
51
+
52
+wget --no-check-certificate --certificate=$DOCKER_CERT_PATH/cert.pem --private-key=$DOCKER_CERT_PATH/key.pem https://your_vm_ip:2376/images/json -O - -q
53
+```
54
+
46 55
 ## Docker Events
47 56
 
48 57
 The following diagram depicts the container states accessible through the API.
... ...
@@ -59,248 +68,109 @@ Running `docker rmi` emits an **untag** event when removing an image name.  The
59 59
 
60 60
 > **Acknowledgement**: This diagram and the accompanying text were used with the permission of Matt Good and Gilder Labs. See Matt's original blog post [Docker Events Explained](http://gliderlabs.com/blog/2015/04/14/docker-events-explained/).
61 61
 
62
-## v1.21
63
-
64
-### Full documentation
65
-
66
-[*Docker Remote API v1.21*](/reference/api/docker_remote_api_v1.21/)
67
-
68
-## v1.20
69
-
70
-### Full documentation
62
+## Version history
71 63
 
72
-[*Docker Remote API v1.20*](/reference/api/docker_remote_api_v1.20/)
64
+This section lists each version from latest to oldest.  Each listing includes a link to the full documentation set and the changes relevant in that release.
73 65
 
74
-### What's new
66
+### v1.21 API changes
75 67
 
76
-`GET /containers/(id)/archive`
68
+[Docker Remote API v1.21](/reference/api/docker_remote_api_v1.21/) documentation
77 69
 
78
-**New!**
79
-Get an archive of filesystem content from a container.
70
+* `GET /volumes` lists volumes from all volume drivers.
71
+* `POST /volumes` to create a volume.
72
+* `GET /volumes/(name)` get low-level information about a volume.
73
+* `DELETE /volumes/(name)`remove a volume with the specified name.
80 74
 
81
-`PUT /containers/(id)/archive`
82 75
 
83
-**New!**
84
-Upload an archive of content to be extracted to an
85
-existing directory inside a container's filesystem.
76
+### v1.20 API changes
86 77
 
87
-`POST /containers/(id)/copy`
78
+[Docker Remote API v1.20](/reference/api/docker_remote_api_v1.20/) documentation
88 79
 
89
-**Deprecated!**
90
-This copy endpoint has been deprecated in favor of the above `archive` endpoint
91
-which can be used to download files and directories from a container.
80
+* `GET /containers/(id)/archive` get an archive of filesystem content from a container.
81
+* `PUT /containers/(id)/archive` upload an archive of content to be extracted to
82
+an existing directory inside a container's filesystem.
83
+* `POST /containers/(id)/copy` is deprecated in favor of the above `archive`
84
+endpoint which can be used to download files and directories from a container.
85
+* The `hostConfig` option now accepts the field `GroupAdd`, which specifies a
86
+list of additional groups that the container process will run as.
92 87
 
93
-**New!**
94
-The `hostConfig` option now accepts the field `GroupAdd`, which specifies a list of additional
95
-groups that the container process will run as.
88
+### v1.19 API changes
96 89
 
97
-## v1.19
90
+[Docker Remote API v1.19](/reference/api/docker_remote_api_v1.19/) documentation
98 91
 
99
-### Full documentation
100
-
101
-[*Docker Remote API v1.19*](/reference/api/docker_remote_api_v1.19/)
102
-
103
-### What's new
104
-
105
-**New!**
106
-When the daemon detects a version mismatch with the client, usually when
92
+* When the daemon detects a version mismatch with the client, usually when
107 93
 the client is newer than the daemon, an HTTP 400 is now returned instead
108 94
 of a 404.
109
-
110
-`GET /containers/(id)/stats`
111
-
112
-**New!**
113
-You can now supply a `stream` bool to get only one set of stats and
114
-disconnect
115
-
116
-`GET /containers/(id)/logs`
117
-
118
-**New!**
119
-
120
-This endpoint now accepts a `since` timestamp parameter.
121
-
122
-`GET /info`
123
-
124
-**New!**
125
-
126
-The fields `Debug`, `IPv4Forwarding`, `MemoryLimit`, and `SwapLimit`
127
-are now returned as boolean instead of as an int.
128
-
129
-In addition, the end point now returns the new boolean fields
130
-`CpuCfsPeriod`, `CpuCfsQuota`, and `OomKillDisable`.
131
-
132
-## v1.18
133
-
134
-### Full documentation
135
-
136
-[*Docker Remote API v1.18*](/reference/api/docker_remote_api_v1.18/)
137
-
138
-### What's new
139
-
140
-`GET /version`
141
-
142
-**New!**
143
-This endpoint now returns `Os`, `Arch` and `KernelVersion`.
144
-
145
-`POST /containers/create`
146
-
147
-`POST /containers/(id)/start`
148
-
149
-**New!**
150
-You can set ulimit settings to be used within the container.
151
-
152
-`GET /info`
153
-
154
-**New!**
155
-This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`.
156
-
157
-`GET /images/json`
158
-
159
-**New!**
160
-Added a `RepoDigests` field to include image digest information.
161
-
162
-`POST /build`
163
-
164
-**New!**
165
-Builds can now set resource constraints for all containers created for the build.
166
-
167
-**New!**
168
-(`CgroupParent`) can be passed in the host config to setup container cgroups under a specific cgroup.
169
-
170
-`POST /build`
171
-
172
-**New!**
173
-Closing the HTTP request will now cause the build to be canceled.
174
-
175
-`POST /containers/(id)/exec`
176
-
177
-**New!**
178
-Add `Warnings` field to response.
179
-
180
-## v1.17
181
-
182
-### Full documentation
183
-
184
-[*Docker Remote API v1.17*](/reference/api/docker_remote_api_v1.17/)
185
-
186
-### What's new
187
-
188
-The build supports `LABEL` command. Use this to add metadata
189
-to an image. For example you could add data describing the content of an image.
190
-
191
-`LABEL "com.example.vendor"="ACME Incorporated"`
192
-
193
-**New!**
194
-`POST /containers/(id)/attach` and `POST /exec/(id)/start`
195
-
196
-**New!**
197
-Docker client now hints potential proxies about connection hijacking using HTTP Upgrade headers.
198
-
199
-`POST /containers/create`
200
-
201
-**New!**
202
-You can set labels on container create describing the container.
203
-
204
-`GET /containers/json`
205
-
206
-**New!**
207
-The endpoint returns the labels associated with the containers (`Labels`).
208
-
209
-`GET /containers/(id)/json`
210
-
211
-**New!**
212
-This endpoint now returns the list current execs associated with the container (`ExecIDs`).
213
-This endpoint now returns the container labels (`Config.Labels`).
214
-
215
-`POST /containers/(id)/rename`
216
-
217
-**New!**
218
-New endpoint to rename a container `id` to a new name.
219
-
220
-`POST /containers/create`
221
-`POST /containers/(id)/start`
222
-
223
-**New!**
224
-(`ReadonlyRootfs`) can be passed in the host config to mount the container's
225
-root filesystem as read only.
226
-
227
-`GET /containers/(id)/stats`
228
-
229
-**New!**
230
-This endpoint returns a live stream of a container's resource usage statistics.
231
-
232
-`GET /images/json`
233
-
234
-**New!**
235
-This endpoint now returns the labels associated with each image (`Labels`).
236
-
237
-
238
-## v1.16
239
-
240
-### Full documentation
241
-
242
-[*Docker Remote API v1.16*](/reference/api/docker_remote_api_v1.16/)
243
-
244
-### What's new
245
-
246
-`GET /info`
247
-
248
-**New!**
249
-`info` now returns the number of CPUs available on the machine (`NCPU`),
95
+* `GET /containers/(id)/stats` now accepts `stream` bool to get only one set of stats and disconnect.
96
+* `GET /containers/(id)/logs` now accepts a `since` timestamp parameter.
97
+* `GET /info` The fields `Debug`, `IPv4Forwarding`, `MemoryLimit`, and
98
+`SwapLimit` are now returned as boolean instead of as an int. In addition, the
99
+end point now returns the new boolean fields `CpuCfsPeriod`, `CpuCfsQuota`, and
100
+`OomKillDisable`.
101
+
102
+### v1.18 API changes
103
+
104
+[Docker Remote API v1.18](/reference/api/docker_remote_api_v1.18/) documentation
105
+
106
+* `GET /version` now returns `Os`, `Arch` and `KernelVersion`.
107
+* `POST /containers/create` and `POST /containers/(id)/start`allow you to  set ulimit settings for use in the container.
108
+* `GET /info` now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`.
109
+* `GET /images/json` added a `RepoDigests` field to include image digest information.
110
+* `POST /build` can now set resource constraints for all containers created for the build.
111
+* `CgroupParent` can be passed in the host config to setup container cgroups under a specific cgroup.
112
+* `POST /build` closing the HTTP request cancels the build
113
+* `POST /containers/(id)/exec` includes `Warnings` field to response.
114
+
115
+### v1.17 API changes
116
+
117
+[Docker Remote API v1.17](/reference/api/docker_remote_api_v1.17/) documentation
118
+
119
+* The build supports `LABEL` command. Use this to add metadata to an image. For
120
+example you could add data describing the content of an image. `LABEL
121
+"com.example.vendor"="ACME Incorporated"`
122
+* `POST /containers/(id)/attach` and `POST /exec/(id)/start`
123
+* The Docker client now hints potential proxies about connection hijacking using HTTP Upgrade headers.
124
+* `POST /containers/create` sets labels on container create describing the container.
125
+* `GET /containers/json` returns the labels associated with the containers (`Labels`).
126
+* `GET /containers/(id)/json` returns the list current execs associated with the
127
+container (`ExecIDs`). This endpoint now returns the container labels
128
+(`Config.Labels`).
129
+* `POST /containers/(id)/rename` renames a container `id` to a new name.* 
130
+* `POST /containers/create` and `POST /containers/(id)/start` callers can pass
131
+`ReadonlyRootfs` in the host config to mount the container's root filesystem as
132
+read only.
133
+* `GET /containers/(id)/stats` returns a live stream of a container's resource usage statistics.
134
+* `GET /images/json` returns the labels associated with each image (`Labels`).
135
+
136
+
137
+### v1.16 API changes
138
+
139
+[Docker Remote API v1.16](/reference/api/docker_remote_api_v1.16/)
140
+
141
+* `GET /info` returns the number of CPUs available on the machine (`NCPU`),
250 142
 total memory available (`MemTotal`), a user-friendly name describing the running Docker daemon (`Name`), a unique ID identifying the daemon (`ID`), and
251 143
 a list of daemon labels (`Labels`).
144
+* `POST /containers/create` callers can set the new container's MAC address explicitly.
145
+* Volumes are now initialized when the container is created.
146
+* `POST /containers/(id)/copy` copies data which is contained in a volume.
252 147
 
253
-`POST /containers/create`
254
-
255
-**New!**
256
-You can set the new container's MAC address explicitly.
257
-
258
-**New!**
259
-Volumes are now initialized when the container is created.
260
-
261
-`POST /containers/(id)/copy`
262
-
263
-**New!**
264
-You can now copy data which is contained in a volume.
148
+### v1.15 API changes
265 149
 
266
-## v1.15
150
+[Docker Remote API v1.15](/reference/api/docker_remote_api_v1.15/) documentation
267 151
 
268
-### Full documentation
152
+`POST /containers/create` you can set a container's `HostConfig` when creating a
153
+container. Previously this was only available when starting a container.
269 154
 
270
-[*Docker Remote API v1.15*](/reference/api/docker_remote_api_v1.15/)
155
+### v1.14 API changes
271 156
 
272
-### What's new
157
+[Docker Remote API v1.14](/reference/api/docker_remote_api_v1.14/) documentation
273 158
 
274
-`POST /containers/create`
275
-
276
-**New!**
277
-It is now possible to set a container's HostConfig when creating a container.
278
-Previously this was only available when starting a container.
279
-
280
-## v1.14
281
-
282
-### Full documentation
283
-
284
-[*Docker Remote API v1.14*](/reference/api/docker_remote_api_v1.14/)
285
-
286
-### What's new
287
-
288
-`DELETE /containers/(id)`
289
-
290
-**New!**
291
-When using `force`, the container will be immediately killed with SIGKILL.
292
-
293
-`POST /containers/(id)/start`
294
-
295
-**New!**
296
-The `hostConfig` option now accepts the field `CapAdd`, which specifies a list of capabilities
159
+* `DELETE /containers/(id)` when using `force`, the container will be immediately killed with SIGKILL.
160
+* `POST /containers/(id)/start` the `hostConfig` option accepts the field `CapAdd`, which specifies a list of capabilities
297 161
 to add, and the field `CapDrop`, which specifies a list of capabilities to drop.
162
+* `POST /images/create` th `fromImage` and `repo` parameters supportthe
163
+`repo:tag` format. Consequently,  the `tag` parameter is now obsolete. Using the
164
+new format and the `tag` parameter at the same time will return an error.
298 165
 
299
-`POST /images/create`
300
-
301
-**New!**
302
-The `fromImage` and `repo` parameters now supports the `repo:tag` format.
303
-Consequently,  the `tag` parameter is now obsolete. Using the new format and
304
-the `tag` parameter at the same time will return an error.
305 166
 
306 167
 
... ...
@@ -2245,6 +2245,126 @@ Status Codes:
2245 2245
 -   **404** – no such exec instance
2246 2246
 -   **500** - server error
2247 2247
 
2248
+## 2.4 Volumes
2249
+
2250
+### List volumes
2251
+
2252
+`GET /volumes`
2253
+
2254
+**Example request**:
2255
+
2256
+  GET /volumes HTTP/1.1
2257
+
2258
+**Example response**:
2259
+
2260
+  HTTP/1.1 200 OK
2261
+  Content-Type: application/json
2262
+
2263
+  {
2264
+    "Volumes": [
2265
+      {
2266
+        "Name": "tardis",
2267
+        "Driver": "local",
2268
+        "Mountpoint": "/var/lib/docker/volumes/tardis"
2269
+      }
2270
+    ]
2271
+  }
2272
+
2273
+Query Parameters:
2274
+
2275
+- **filter** - JSON encoded value of the filters (a `map[string][]string`) to process on the volumes list. There is one available filter: `dangling=true`
2276
+
2277
+Status Codes:
2278
+
2279
+-   **200** - no error
2280
+-   **500** - server error
2281
+
2282
+### Create a volume
2283
+
2284
+`POST /volumes`
2285
+
2286
+Create a volume
2287
+
2288
+**Example request**:
2289
+
2290
+  POST /volumes HTTP/1.1
2291
+  Content-Type: application/json
2292
+
2293
+  {
2294
+    "Name": "tardis"
2295
+  }
2296
+
2297
+**Example response**:
2298
+
2299
+  HTTP/1.1 201 Created
2300
+  Content-Type: application/json
2301
+
2302
+  {
2303
+    "Name": "tardis"
2304
+    "Driver": "local",
2305
+    "Mountpoint": "/var/lib/docker/volumes/tardis"
2306
+  }
2307
+
2308
+Status Codes:
2309
+
2310
+- **201** - no error
2311
+- **500**  - server error
2312
+
2313
+JSON Parameters:
2314
+
2315
+- **Name** - The new volume's name. If not specified, Docker generates a name.
2316
+- **Driver** - Name of the volume driver to use. Defaults to `local` for the name.
2317
+- **DriverOpts** - A mapping of driver options and values. These options are
2318
+    passed directly to the driver and are driver specific.
2319
+
2320
+### Inspect a volume
2321
+
2322
+`GET /volumes/(name)`
2323
+
2324
+Return low-level information on the volume `name`
2325
+
2326
+**Example request**:
2327
+
2328
+    GET /volumes/tardis
2329
+
2330
+**Example response**:
2331
+
2332
+  HTTP/1.1 200 OK
2333
+  Content-Type: application/json
2334
+
2335
+  {
2336
+    "Name": "tardis",
2337
+    "Driver": "local",
2338
+    "Mountpoint": "/var/lib/docker/volumes/tardis"
2339
+  }
2340
+
2341
+Status Codes:
2342
+
2343
+-   **200** - no error
2344
+-   **404** - no such volume
2345
+-   **500** - server error
2346
+
2347
+### Remove a volume
2348
+
2349
+`DELETE /volumes/(name)`
2350
+
2351
+Instruct the driver to remove the volume (`name`).
2352
+
2353
+**Example request**:
2354
+
2355
+  DELETE /volumes/local/tardis HTTP/1.1
2356
+
2357
+**Example response**:
2358
+
2359
+  HTTP/1.1 204 No Content
2360
+
2361
+Status Codes
2362
+
2363
+-   **204** - no error
2364
+-   **404** - no such volume or volume driver
2365
+-   **409** - volume is in use and cannot be removed
2366
+-   **500** - server error
2367
+
2248 2368
 # 3. Going further
2249 2369
 
2250 2370
 ## 3.1 Inside `docker run`
... ...
@@ -40,7 +40,7 @@ Running `docker ps --no-trunc` showing 2 linked containers.
40 40
 
41 41
 ## Filtering
42 42
 
43
-The filtering flag (`-f` or `--filter)` format is a `key=value` pair. If there is more
43
+The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more
44 44
 than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`)
45 45
 
46 46
 The currently supported filters are:
47 47
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+<!--[metadata]>
1
+title = "volume create"
2
+description = "The volume create command description and usage"
3
+keywords = ["volume, create"]
4
+[menu.main]
5
+parent = "smn_cli"
6
+<![end-metadata]-->
7
+
8
+# volume create
9
+
10
+    Usage: docker volume create [OPTIONS]
11
+
12
+    Create a volume
13
+
14
+    -d, --driver=local    Specify volume driver name
15
+    --help=false          Print usage
16
+    --name=               Specify volume name
17
+    -o, --opt=map[]       Set driver specific options
18
+
19
+Creates a new volume that containers can can consume and store data in. If a name is not specified, Docker generates a random name. You create a volume and then configure the container to use it, for example:
20
+
21
+  $ docker volume create --name hello
22
+  hello
23
+  $ docker run -d -v hello:/world busybox ls /world
24
+
25
+The mount is created inside the container's `/src` directory. Docker does not support relative paths for mount points inside the container. 
26
+
27
+Multiple containers can use the same volume in the same time period. This is useful if two containers need access to shared data. For example, if one container writes and the other reads the data.
28
+
29
+## Driver specific options
30
+
31
+Some volume drivers may take options to customize the volume creation. Use the `-o` or `--opt` flags to pass driver options:
32
+
33
+  $ docker volume create --driver fake --opt tardis=blue --opt timey=wimey
34
+
35
+These options are passed directly to the volume driver. Options for
36
+different volume drivers may do different things (or nothing at all).
37
+
38
+*Note*: The built-in `local` volume driver does not currently accept any options.
39
+
0 40
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+<!--[metadata]>
1
+title = "volume inspect"
2
+description = "The volume inspect command description and usage"
3
+keywords = ["volume, inspect"]
4
+[menu.main]
5
+parent = "smn_cli"
6
+<![end-metadata]-->
7
+
8
+# volume inspect
9
+
10
+    Usage: docker volume inspect [OPTIONS] [VOLUME NAME]
11
+
12
+    Inspect a volume
13
+
14
+    -f, --format=       Format the output using the given go template.
15
+
16
+Returns information about a volume. By default, this command renders all results
17
+in a JSON array. You can specify an alternate format to execute a given template
18
+is executed for each result. Go's
19
+[text/template](http://golang.org/pkg/text/template/) package describes all the
20
+details of the format.
21
+
22
+Example output:
23
+
24
+    $ docker volume create
25
+    85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d
26
+    $ docker volume inspect 85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d
27
+    [
28
+      {
29
+          "Name": "85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d",
30
+          "Driver": "local",
31
+          "Mountpoint": "/var/lib/docker/volumes/85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d/_data"
32
+      }
33
+    ]
34
+
35
+    $ docker volume inspect --format '{{ .Mountpoint }}' 85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d
36
+    "/var/lib/docker/volumes/85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d/_data"
0 37
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+<!--[metadata]>
1
+title = "volume ls"
2
+description = "The volume ls command description and usage"
3
+keywords = ["volume, list"]
4
+[menu.main]
5
+parent = "smn_cli"
6
+<![end-metadata]-->
7
+
8
+# volume ls
9
+
10
+    Usage: docker volume ls [OPTIONS]
11
+
12
+    List volumes
13
+
14
+    -f, --filter=[]      Provide filter values (i.e. 'dangling=true')
15
+    --help=false         Print usage
16
+    -q, --quiet=false    Only display volume names
17
+
18
+Lists all the volumes Docker knows about. You can filter using the `-f` or `--filter` flag. The filtering format is a `key=value` pair. To specify more than one filter,  pass multiple flags (for example,  `--filter "foo=bar" --filter "bif=baz"`)
19
+
20
+There is a single supported filter `dangling=value` which takes a boolean of `true` or `false`.
21
+
22
+Example output:
23
+
24
+    $ docker volume create --name rose
25
+    rose
26
+    $docker volume create --name tyler
27
+    tyler
28
+    $ docker volume ls
29
+    DRIVER              VOLUME NAME
30
+    local               rose
31
+    local               tyler
0 32
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+<!--[metadata]>
1
+title = "ps"
2
+description = "the volume rm command description and usage"
3
+keywords = ["volume, rm"]
4
+[menu.main]
5
+parent = "smn_cli"
6
+<![end-metadata]-->
7
+
8
+# volume rm
9
+
10
+    Usage: docker volume rm [OPTIONS] [VOLUME NAME]
11
+
12
+    Remove a volume
13
+
14
+    --help=false       Print usage
15
+
16
+Removes a volume. You cannot remove a volume that is in use by a container.
17
+
18
+  $ docker volume rm hello
19
+  hello
... ...
@@ -20,6 +20,7 @@ type DockerSuite struct {
20 20
 func (s *DockerSuite) TearDownTest(c *check.C) {
21 21
 	deleteAllContainers()
22 22
 	deleteAllImages()
23
+	deleteAllVolumes()
23 24
 }
24 25
 
25 26
 func init() {
26 27
new file mode 100644
... ...
@@ -0,0 +1,85 @@
0
+package main
1
+
2
+import (
3
+	"encoding/json"
4
+	"net/http"
5
+	"path"
6
+
7
+	"github.com/docker/docker/api/types"
8
+	"github.com/go-check/check"
9
+)
10
+
11
+func (s *DockerSuite) TestVolumesApiList(c *check.C) {
12
+	dockerCmd(c, "run", "-d", "-v", "/foo", "busybox")
13
+
14
+	status, b, err := sockRequest("GET", "/volumes", nil)
15
+	c.Assert(err, check.IsNil)
16
+	c.Assert(status, check.Equals, http.StatusOK)
17
+
18
+	var volumes types.VolumesListResponse
19
+	c.Assert(json.Unmarshal(b, &volumes), check.IsNil)
20
+
21
+	c.Assert(len(volumes.Volumes), check.Equals, 1, check.Commentf("\n%v", volumes.Volumes))
22
+}
23
+
24
+func (s *DockerSuite) TestVolumesApiCreate(c *check.C) {
25
+	config := types.VolumeCreateRequest{
26
+		Name: "test",
27
+	}
28
+	status, b, err := sockRequest("POST", "/volumes", config)
29
+	c.Assert(err, check.IsNil)
30
+	c.Assert(status, check.Equals, http.StatusCreated, check.Commentf(string(b)))
31
+
32
+	var vol types.Volume
33
+	err = json.Unmarshal(b, &vol)
34
+	c.Assert(err, check.IsNil)
35
+
36
+	c.Assert(path.Base(path.Dir(vol.Mountpoint)), check.Equals, config.Name)
37
+}
38
+
39
+func (s *DockerSuite) TestVolumesApiRemove(c *check.C) {
40
+	dockerCmd(c, "run", "-d", "-v", "/foo", "--name=test", "busybox")
41
+
42
+	status, b, err := sockRequest("GET", "/volumes", nil)
43
+	c.Assert(err, check.IsNil)
44
+	c.Assert(status, check.Equals, http.StatusOK)
45
+
46
+	var volumes types.VolumesListResponse
47
+	c.Assert(json.Unmarshal(b, &volumes), check.IsNil)
48
+	c.Assert(len(volumes.Volumes), check.Equals, 1, check.Commentf("\n%v", volumes.Volumes))
49
+
50
+	v := volumes.Volumes[0]
51
+	status, _, err = sockRequest("DELETE", "/volumes/"+v.Name, nil)
52
+	c.Assert(err, check.IsNil)
53
+	c.Assert(status, check.Equals, http.StatusConflict, check.Commentf("Should not be able to remove a volume that is in use"))
54
+
55
+	dockerCmd(c, "rm", "-f", "test")
56
+	status, data, err := sockRequest("DELETE", "/volumes/"+v.Name, nil)
57
+	c.Assert(err, check.IsNil)
58
+	c.Assert(status, check.Equals, http.StatusNoContent, check.Commentf(string(data)))
59
+
60
+}
61
+
62
+func (s *DockerSuite) TestVolumesApiInspect(c *check.C) {
63
+	config := types.VolumeCreateRequest{
64
+		Name: "test",
65
+	}
66
+	status, b, err := sockRequest("POST", "/volumes", config)
67
+	c.Assert(err, check.IsNil)
68
+	c.Assert(status, check.Equals, http.StatusCreated, check.Commentf(string(b)))
69
+
70
+	status, b, err = sockRequest("GET", "/volumes", nil)
71
+	c.Assert(err, check.IsNil)
72
+	c.Assert(status, check.Equals, http.StatusOK, check.Commentf(string(b)))
73
+
74
+	var volumes types.VolumesListResponse
75
+	c.Assert(json.Unmarshal(b, &volumes), check.IsNil)
76
+	c.Assert(len(volumes.Volumes), check.Equals, 1, check.Commentf("\n%v", volumes.Volumes))
77
+
78
+	var vol types.Volume
79
+	status, b, err = sockRequest("GET", "/volumes/"+config.Name, nil)
80
+	c.Assert(err, check.IsNil)
81
+	c.Assert(status, check.Equals, http.StatusOK, check.Commentf(string(b)))
82
+	c.Assert(json.Unmarshal(b, &vol), check.IsNil)
83
+	c.Assert(vol.Name, check.Equals, config.Name)
84
+}
... ...
@@ -71,6 +71,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) {
71 71
 	if err := s.d.Restart(); err != nil {
72 72
 		c.Fatal(err)
73 73
 	}
74
+
74 75
 	if _, err := s.d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil {
75 76
 		c.Fatal(err)
76 77
 	}
... ...
@@ -1660,5 +1661,28 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPausedContainer(c *check.C) {
1660 1660
 			c.Fatal(err)
1661 1661
 		}
1662 1662
 	}
1663
+}
1664
+
1665
+func (s *DockerDaemonSuite) TestDaemonRestartRmVolumeInUse(c *check.C) {
1666
+	c.Assert(s.d.StartWithBusybox(), check.IsNil)
1667
+
1668
+	out, err := s.d.Cmd("create", "-v", "test:/foo", "busybox")
1669
+	c.Assert(err, check.IsNil, check.Commentf(out))
1670
+
1671
+	c.Assert(s.d.Restart(), check.IsNil)
1663 1672
 
1673
+	out, err = s.d.Cmd("volume", "rm", "test")
1674
+	c.Assert(err, check.Not(check.IsNil), check.Commentf("should not be able to remove in use volume after daemon restart"))
1675
+	c.Assert(strings.Contains(out, "in use"), check.Equals, true)
1676
+}
1677
+
1678
+func (s *DockerDaemonSuite) TestDaemonRestartLocalVolumes(c *check.C) {
1679
+	c.Assert(s.d.Start(), check.IsNil)
1680
+
1681
+	_, err := s.d.Cmd("volume", "create", "--name", "test")
1682
+	c.Assert(err, check.IsNil)
1683
+	c.Assert(s.d.Restart(), check.IsNil)
1684
+
1685
+	_, err = s.d.Cmd("volume", "inspect", "test")
1686
+	c.Assert(err, check.IsNil)
1664 1687
 }
... ...
@@ -238,7 +238,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
238 238
 
239 239
 		}
240 240
 
241
-		expected := 39
241
+		expected := 40
242 242
 		if isLocalDaemon {
243 243
 			expected++ // for the daemon command
244 244
 		}
... ...
@@ -2781,3 +2781,13 @@ func (s *DockerSuite) TestRunCreateContainerFailedCleanUp(c *check.C) {
2781 2781
 	containerID, err := inspectField(name, "Id")
2782 2782
 	c.Assert(containerID, check.Equals, "", check.Commentf("Expected not to have this container: %s!", containerID))
2783 2783
 }
2784
+
2785
+func (s *DockerSuite) TestRunNamedVolume(c *check.C) {
2786
+	dockerCmd(c, "run", "--name=test", "-v", "testing:/foo", "busybox", "sh", "-c", "echo hello > /foo/bar")
2787
+
2788
+	out, _ := dockerCmd(c, "run", "--volumes-from", "test", "busybox", "sh", "-c", "cat /foo/bar")
2789
+	c.Assert(strings.TrimSpace(out), check.Equals, "hello")
2790
+
2791
+	out, _ = dockerCmd(c, "run", "-v", "testing:/foo", "busybox", "sh", "-c", "cat /foo/bar")
2792
+	c.Assert(strings.TrimSpace(out), check.Equals, "hello")
2793
+}
... ...
@@ -119,11 +119,6 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
119 119
 			http.Error(w, err.Error(), 500)
120 120
 		}
121 121
 
122
-		p := hostVolumePath(pr.name)
123
-		if err := os.RemoveAll(p); err != nil {
124
-			http.Error(w, err.Error(), 500)
125
-		}
126
-
127 122
 		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
128 123
 		fmt.Fprintln(w, `{}`)
129 124
 	})
... ...
@@ -152,7 +147,7 @@ func (s *DockerExternalVolumeSuite) TestStartExternalNamedVolumeDriver(c *check.
152 152
 
153 153
 	out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
154 154
 	if err != nil {
155
-		c.Fatal(err)
155
+		c.Fatal(out, err)
156 156
 	}
157 157
 
158 158
 	if !strings.Contains(out, s.server.URL) {
... ...
@@ -202,20 +197,17 @@ func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverVolumesFrom(c *c
202 202
 		c.Fatal(err)
203 203
 	}
204 204
 
205
-	if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
206
-		c.Fatal(err)
207
-	}
205
+	out, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest")
206
+	c.Assert(err, check.IsNil, check.Commentf(out))
208 207
 
209
-	if _, err := s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp"); err != nil {
210
-		c.Fatal(err)
211
-	}
208
+	out, err = s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp")
209
+	c.Assert(err, check.IsNil, check.Commentf(out))
212 210
 
213
-	if _, err := s.d.Cmd("rm", "-f", "vol-test1"); err != nil {
214
-		c.Fatal(err)
215
-	}
211
+	out, err = s.d.Cmd("rm", "-fv", "vol-test1")
212
+	c.Assert(err, check.IsNil, check.Commentf(out))
216 213
 
217 214
 	c.Assert(s.ec.activations, check.Equals, 1)
218
-	c.Assert(s.ec.creations, check.Equals, 2)
215
+	c.Assert(s.ec.creations, check.Equals, 1)
219 216
 	c.Assert(s.ec.removals, check.Equals, 1)
220 217
 	c.Assert(s.ec.mounts, check.Equals, 2)
221 218
 	c.Assert(s.ec.unmounts, check.Equals, 2)
... ...
@@ -226,12 +218,12 @@ func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverDeleteContainer(
226 226
 		c.Fatal(err)
227 227
 	}
228 228
 
229
-	if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
230
-		c.Fatal(err)
229
+	if out, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
230
+		c.Fatal(out, err)
231 231
 	}
232 232
 
233
-	if _, err := s.d.Cmd("rm", "-fv", "vol-test1"); err != nil {
234
-		c.Fatal(err)
233
+	if out, err := s.d.Cmd("rm", "-fv", "vol-test1"); err != nil {
234
+		c.Fatal(out, err)
235 235
 	}
236 236
 
237 237
 	c.Assert(s.ec.activations, check.Equals, 1)
238 238
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+package main
1
+
2
+import (
3
+	"os/exec"
4
+	"strings"
5
+
6
+	"github.com/go-check/check"
7
+)
8
+
9
+func (s *DockerSuite) TestVolumeCliCreate(c *check.C) {
10
+	dockerCmd(c, "volume", "create")
11
+
12
+	_, err := runCommand(exec.Command(dockerBinary, "volume", "create", "-d", "nosuchdriver"))
13
+	c.Assert(err, check.Not(check.IsNil))
14
+
15
+	out, _ := dockerCmd(c, "volume", "create", "--name=test")
16
+	name := strings.TrimSpace(out)
17
+	c.Assert(name, check.Equals, "test")
18
+}
19
+
20
+func (s *DockerSuite) TestVolumeCliInspect(c *check.C) {
21
+	c.Assert(
22
+		exec.Command(dockerBinary, "volume", "inspect", "doesntexist").Run(),
23
+		check.Not(check.IsNil),
24
+		check.Commentf("volume inspect should error on non-existant volume"),
25
+	)
26
+
27
+	out, _ := dockerCmd(c, "volume", "create")
28
+	name := strings.TrimSpace(out)
29
+	out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Name }}'", name)
30
+	c.Assert(strings.TrimSpace(out), check.Equals, name)
31
+
32
+	dockerCmd(c, "volume", "create", "--name", "test")
33
+	out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Name }}'", "test")
34
+	c.Assert(strings.TrimSpace(out), check.Equals, "test")
35
+}
36
+
37
+func (s *DockerSuite) TestVolumeCliLs(c *check.C) {
38
+	out, _ := dockerCmd(c, "volume", "create")
39
+	id := strings.TrimSpace(out)
40
+
41
+	dockerCmd(c, "volume", "create", "--name", "test")
42
+	dockerCmd(c, "run", "-v", "/foo", "busybox", "ls", "/")
43
+
44
+	out, _ = dockerCmd(c, "volume", "ls")
45
+	outArr := strings.Split(strings.TrimSpace(out), "\n")
46
+	c.Assert(len(outArr), check.Equals, 4, check.Commentf("\n%s", out))
47
+
48
+	// Since there is no guarentee of ordering of volumes, we just make sure the names are in the output
49
+	c.Assert(strings.Contains(out, id+"\n"), check.Equals, true)
50
+	c.Assert(strings.Contains(out, "test\n"), check.Equals, true)
51
+}
52
+
53
+func (s *DockerSuite) TestVolumeCliRm(c *check.C) {
54
+	out, _ := dockerCmd(c, "volume", "create")
55
+	id := strings.TrimSpace(out)
56
+
57
+	dockerCmd(c, "volume", "create", "--name", "test")
58
+	dockerCmd(c, "volume", "rm", id)
59
+	dockerCmd(c, "volume", "rm", "test")
60
+
61
+	out, _ = dockerCmd(c, "volume", "ls")
62
+	outArr := strings.Split(strings.TrimSpace(out), "\n")
63
+	c.Assert(len(outArr), check.Equals, 1, check.Commentf("%s\n", out))
64
+
65
+	volumeID := "testing"
66
+	dockerCmd(c, "run", "-v", volumeID+":/foo", "--name=test", "busybox", "sh", "-c", "echo hello > /foo/bar")
67
+	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "volume", "rm", "testing"))
68
+	c.Assert(
69
+		err,
70
+		check.Not(check.IsNil),
71
+		check.Commentf("Should not be able to remove volume that is in use by a container\n%s", out))
72
+
73
+	out, _ = dockerCmd(c, "run", "--volumes-from=test", "--name=test2", "busybox", "sh", "-c", "cat /foo/bar")
74
+	c.Assert(strings.TrimSpace(out), check.Equals, "hello")
75
+	dockerCmd(c, "rm", "-fv", "test2")
76
+	dockerCmd(c, "volume", "inspect", volumeID)
77
+	dockerCmd(c, "rm", "-f", "test")
78
+
79
+	out, _ = dockerCmd(c, "run", "--name=test2", "-v", volumeID+":/foo", "busybox", "sh", "-c", "cat /foo/bar")
80
+	c.Assert(strings.TrimSpace(out), check.Equals, "hello", check.Commentf("volume data was removed"))
81
+	dockerCmd(c, "rm", "test2")
82
+
83
+	dockerCmd(c, "volume", "rm", volumeID)
84
+	c.Assert(
85
+		exec.Command("volume", "rm", "doesntexist").Run(),
86
+		check.Not(check.IsNil),
87
+		check.Commentf("volume rm should fail with non-existant volume"),
88
+	)
89
+}
... ...
@@ -445,6 +445,40 @@ func deleteAllContainers() error {
445 445
 	return nil
446 446
 }
447 447
 
448
+func deleteAllVolumes() error {
449
+	volumes, err := getAllVolumes()
450
+	if err != nil {
451
+		return err
452
+	}
453
+	var errors []string
454
+	for _, v := range volumes {
455
+		status, b, err := sockRequest("DELETE", "/volumes/"+v.Name, nil)
456
+		if err != nil {
457
+			errors = append(errors, err.Error())
458
+			continue
459
+		}
460
+		if status != http.StatusNoContent {
461
+			errors = append(errors, fmt.Sprintf("error deleting volume %s: %s", v.Name, string(b)))
462
+		}
463
+	}
464
+	if len(errors) > 0 {
465
+		return fmt.Errorf(strings.Join(errors, "\n"))
466
+	}
467
+	return nil
468
+}
469
+
470
+func getAllVolumes() ([]*types.Volume, error) {
471
+	var volumes types.VolumesListResponse
472
+	_, b, err := sockRequest("GET", "/volumes", nil)
473
+	if err != nil {
474
+		return nil, err
475
+	}
476
+	if err := json.Unmarshal(b, &volumes); err != nil {
477
+		return nil, err
478
+	}
479
+	return volumes.Volumes, nil
480
+}
481
+
448 482
 var protectedImages = map[string]struct{}{}
449 483
 
450 484
 func init() {
451 485
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+% DOCKER(1) Docker User Manuals
1
+% Docker Community
2
+% JULY 2015
3
+# NAME
4
+docker-volume-create - Create a new volume
5
+
6
+# SYNOPSIS
7
+**docker volume create**
8
+[**-d**|**--driver**[=*local*]]
9
+[**--name**[=**]]
10
+[**-o**|**--opt**[=**]]
11
+
12
+[OPTIONS]
13
+
14
+# DESCRIPTION
15
+
16
+Creates a new volume that containers can consume and store data in. If a name is not specified, Docker generates a random name. You create a volume and then configure the container to use it, for example:
17
+
18
+  ```
19
+  $ docker volume create --name hello
20
+  hello
21
+  $ docker run -d -v hello:/world busybox ls /world
22
+  ```
23
+
24
+The mount is created inside the container's `/src` directory. Docker doesn't not support relative paths for mount points inside the container. 
25
+
26
+Multiple containers can use the same volume in the same time period. This is useful if two containers need access to shared data. For example, if one container writes and the other reads the data.
27
+
28
+## Driver specific options
29
+
30
+Some volume drivers may take options to customize the volume creation. Use the `-o` or `--opt` flags to pass driver options:
31
+
32
+  ```
33
+  $ docker volume create --driver fake --opt tardis=blue --opt timey=wimey
34
+  ```
35
+
36
+These options are passed directly to the volume driver. Options for
37
+different volume drivers may do different things (or nothing at all).
38
+
39
+*Note*: The built-in `local` volume driver does not currently accept any options.
40
+
41
+# OPTIONS
42
+**-d**, **--driver**=[]
43
+   Specify volume driver name
44
+**--name**=""
45
+  Specify volume name
46
+**-o**, **--opt**=map[]
47
+  Set driver specific options
48
+
49
+# HISTORY
50
+July 2015, created by Brian Goff <cpuguy83@gmail.com>
0 51
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+% DOCKER(1) Docker User Manuals
1
+% Docker Community
2
+% JULY 2015
3
+# NAME
4
+docker-volume-inspect - Get low-level information about a volume
5
+
6
+# SYNOPSIS
7
+**docker volume inspect**
8
+[**-f**|**--format**[=**]]
9
+
10
+[OPTIONS] [VOLUME NAME]
11
+
12
+# DESCRIPTION
13
+
14
+Returns information about a volume. By default, this command renders all results
15
+in a JSON array. You can specify an alternate format to execute a given template
16
+is executed for each result. Go's
17
+http://golang.org/pkg/text/template/ package describes all the details of the
18
+format.
19
+
20
+# OPTIONS
21
+**-f**, **--format**=""
22
+   Format the output using the given go template.
23
+
24
+# HISTORY
25
+July 2015, created by Brian Goff <cpuguy83@gmail.com>
0 26
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+% DOCKER(1) Docker User Manuals
1
+% Docker Community
2
+% JULY 2015
3
+# NAME
4
+docker-volume-ls - List all volumes
5
+
6
+# SYNOPSIS
7
+**docker volume ls**
8
+[**-f**|**--filter**[=**]]
9
+[**-q**|**--quiet**[=**]]
10
+
11
+[OPTIONS]
12
+
13
+# DESCRIPTION
14
+
15
+Lists all the volumes Docker knows about. You can filter using the `-f` or `--filter` flag. The filtering format is a `key=value` pair. To specify more than one filter,  pass multiple flags (for example,  `--filter "foo=bar" --filter "bif=baz"`)
16
+
17
+There is a single supported filter `dangling=value` which takes a boolean of `true` or `false`.
18
+
19
+# OPTIONS
20
+**-f**, **--filter**=""
21
+   Provide filter values (i.e. 'dangling=true')
22
+**-q**, **--quiet**=false
23
+  Only display volume names
24
+
25
+# HISTORY
26
+July 2015, created by Brian Goff <cpuguy83@gmail.com>
0 27
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+% DOCKER(1) Docker User Manuals
1
+% Docker Community
2
+% JULY 2015
3
+# NAME
4
+docker-volume-rm - Remove a volume
5
+
6
+# SYNOPSIS
7
+**docker volume rm**
8
+
9
+[OPTIONS] [VOLUME NAME]
10
+
11
+# DESCRIPTION
12
+
13
+Removes a volume. You cannot remove a volume that is in use by a container.
14
+
15
+    ```
16
+    $ docker volume rm hello
17
+    hello
18
+    ```
19
+
20
+# OPTIONS
21
+
22
+# HISTORY
23
+July 2015, created by Brian Goff <cpuguy83@gmail.com>
... ...
@@ -131,6 +131,10 @@ func (opts *MapOpts) Set(value string) error {
131 131
 	return nil
132 132
 }
133 133
 
134
+func (opts *MapOpts) GetAll() map[string]string {
135
+	return opts.values
136
+}
137
+
134 138
 func (opts *MapOpts) String() string {
135 139
 	return fmt.Sprintf("%v", map[string]string((opts.values)))
136 140
 }
... ...
@@ -15,10 +15,19 @@ import (
15 15
 )
16 16
 
17 17
 const (
18
-	versionMimetype = "application/vnd.docker.plugins.v1+json"
18
+	versionMimetype = "application/vnd.docker.plugins.v1.1+json"
19 19
 	defaultTimeOut  = 30
20 20
 )
21 21
 
22
+type remoteError struct {
23
+	method string
24
+	err    string
25
+}
26
+
27
+func (e *remoteError) Error() string {
28
+	return fmt.Sprintf("Plugin Error: %s, %s", e.err, e.method)
29
+}
30
+
22 31
 // NewClient creates a new plugin client (http).
23 32
 func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
24 33
 	tr := &http.Transport{}
... ...
@@ -84,9 +93,9 @@ func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret inter
84 84
 		if resp.StatusCode != http.StatusOK {
85 85
 			remoteErr, err := ioutil.ReadAll(resp.Body)
86 86
 			if err != nil {
87
-				return fmt.Errorf("Plugin Error: %s", err)
87
+				return &remoteError{err.Error(), serviceMethod}
88 88
 			}
89
-			return fmt.Errorf("Plugin Error: %s", remoteErr)
89
+			return &remoteError{string(remoteErr), serviceMethod}
90 90
 		}
91 91
 
92 92
 		return json.NewDecoder(resp.Body).Decode(&ret)
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"go/parser"
8 8
 	"go/token"
9 9
 	"reflect"
10
-	"strings"
11 10
 )
12 11
 
13 12
 var ErrBadReturn = errors.New("found return arg with no name: all args must be named")
... ...
@@ -39,7 +38,7 @@ type arg struct {
39 39
 }
40 40
 
41 41
 func (a *arg) String() string {
42
-	return strings.ToLower(a.Name) + " " + strings.ToLower(a.ArgType)
42
+	return a.Name + " " + a.ArgType
43 43
 }
44 44
 
45 45
 // Parses the given file for an interface definition with the given name
... ...
@@ -11,8 +11,8 @@ func (a *volumeDriverAdapter) Name() string {
11 11
 	return a.name
12 12
 }
13 13
 
14
-func (a *volumeDriverAdapter) Create(name string) (volume.Volume, error) {
15
-	err := a.proxy.Create(name)
14
+func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volume.Volume, error) {
15
+	err := a.proxy.Create(name, opts)
16 16
 	if err != nil {
17 17
 		return nil, err
18 18
 	}
... ...
@@ -33,6 +33,11 @@ type volumeAdapter struct {
33 33
 	eMount     string // ephemeral host volume path
34 34
 }
35 35
 
36
+type proxyVolume struct {
37
+	Name       string
38
+	Mountpoint string
39
+}
40
+
36 41
 func (a *volumeAdapter) Name() string {
37 42
 	return a.name
38 43
 }
39 44
deleted file mode 100644
... ...
@@ -1,25 +0,0 @@
1
-//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type VolumeDriver -name VolumeDriver
2
-
3
-package volumedrivers
4
-
5
-import "github.com/docker/docker/volume"
6
-
7
-// NewVolumeDriver returns a driver has the given name mapped on the given client.
8
-func NewVolumeDriver(name string, c client) volume.Driver {
9
-	proxy := &volumeDriverProxy{c}
10
-	return &volumeDriverAdapter{name, proxy}
11
-}
12
-
13
-// VolumeDriver defines the available functions that volume plugins must implement.
14
-type VolumeDriver interface {
15
-	// Create a volume with the given name
16
-	Create(name string) (err error)
17
-	// Remove the volume with the given name
18
-	Remove(name string) (err error)
19
-	// Get the mountpoint of the given volume
20
-	Path(name string) (mountpoint string, err error)
21
-	// Mount the given volume and return the mountpoint
22
-	Mount(name string) (mountpoint string, err error)
23
-	// Unmount the given volume
24
-	Unmount(name string) (err error)
25
-}
... ...
@@ -1,3 +1,5 @@
1
+//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type VolumeDriver -name VolumeDriver
2
+
1 3
 package volumedrivers
2 4
 
3 5
 import (
... ...
@@ -13,6 +15,28 @@ import (
13 13
 
14 14
 var drivers = &driverExtpoint{extensions: make(map[string]volume.Driver)}
15 15
 
16
+// NewVolumeDriver returns a driver has the given name mapped on the given client.
17
+func NewVolumeDriver(name string, c client) volume.Driver {
18
+	proxy := &volumeDriverProxy{c}
19
+	return &volumeDriverAdapter{name, proxy}
20
+}
21
+
22
+type opts map[string]string
23
+
24
+// VolumeDriver defines the available functions that volume plugins must implement.
25
+type VolumeDriver interface {
26
+	// Create a volume with the given name
27
+	Create(name string, opts opts) (err error)
28
+	// Remove the volume with the given name
29
+	Remove(name string) (err error)
30
+	// Get the mountpoint of the given volume
31
+	Path(name string) (mountpoint string, err error)
32
+	// Mount the given volume and return the mountpoint
33
+	Mount(name string) (mountpoint string, err error)
34
+	// Unmount the given volume
35
+	Unmount(name string) (err error)
36
+}
37
+
16 38
 type driverExtpoint struct {
17 39
 	extensions map[string]volume.Driver
18 40
 	sync.Mutex
... ...
@@ -14,19 +14,21 @@ type volumeDriverProxy struct {
14 14
 
15 15
 type volumeDriverProxyCreateRequest struct {
16 16
 	Name string
17
+	Opts opts
17 18
 }
18 19
 
19 20
 type volumeDriverProxyCreateResponse struct {
20 21
 	Err string
21 22
 }
22 23
 
23
-func (pp *volumeDriverProxy) Create(name string) (err error) {
24
+func (pp *volumeDriverProxy) Create(name string, opts opts) (err error) {
24 25
 	var (
25 26
 		req volumeDriverProxyCreateRequest
26 27
 		ret volumeDriverProxyCreateResponse
27 28
 	)
28 29
 
29 30
 	req.Name = name
31
+	req.Opts = opts
30 32
 	if err = pp.Call("VolumeDriver.Create", req, &ret); err != nil {
31 33
 		return
32 34
 	}
... ...
@@ -50,7 +50,7 @@ func TestVolumeRequestError(t *testing.T) {
50 50
 
51 51
 	driver := volumeDriverProxy{client}
52 52
 
53
-	if err = driver.Create("volume"); err == nil {
53
+	if err = driver.Create("volume", nil); err == nil {
54 54
 		t.Fatal("Expected error, was nil")
55 55
 	}
56 56
 
... ...
@@ -23,7 +23,11 @@ const (
23 23
 	volumesPathName    = "volumes"
24 24
 )
25 25
 
26
-var oldVfsDir = filepath.Join("vfs", "dir")
26
+var (
27
+	// ErrNotFound is the typed error returned when the requested volume name can't be found
28
+	ErrNotFound = errors.New("volume not found")
29
+	oldVfsDir   = filepath.Join("vfs", "dir")
30
+)
27 31
 
28 32
 // New instantiates a new Root instance with the provided scope. Scope
29 33
 // is the base path that the Root instance uses to store its
... ...
@@ -54,6 +58,7 @@ func New(scope string) (*Root, error) {
54 54
 			path:       r.DataPath(name),
55 55
 		}
56 56
 	}
57
+
57 58
 	return r, nil
58 59
 }
59 60
 
... ...
@@ -67,6 +72,15 @@ type Root struct {
67 67
 	volumes map[string]*localVolume
68 68
 }
69 69
 
70
+// List lists all the volumes
71
+func (r *Root) List() []volume.Volume {
72
+	var ls []volume.Volume
73
+	for _, v := range r.volumes {
74
+		ls = append(ls, v)
75
+	}
76
+	return ls
77
+}
78
+
70 79
 // DataPath returns the constructed path of this volume.
71 80
 func (r *Root) DataPath(volumeName string) string {
72 81
 	return filepath.Join(r.path, volumeName, VolumeDataPathName)
... ...
@@ -80,27 +94,28 @@ func (r *Root) Name() string {
80 80
 // Create creates a new volume.Volume with the provided name, creating
81 81
 // the underlying directory tree required for this volume in the
82 82
 // process.
83
-func (r *Root) Create(name string) (volume.Volume, error) {
83
+func (r *Root) Create(name string, _ map[string]string) (volume.Volume, error) {
84 84
 	r.m.Lock()
85 85
 	defer r.m.Unlock()
86 86
 
87 87
 	v, exists := r.volumes[name]
88
-	if !exists {
89
-		path := r.DataPath(name)
90
-		if err := os.MkdirAll(path, 0755); err != nil {
91
-			if os.IsExist(err) {
92
-				return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
93
-			}
94
-			return nil, err
95
-		}
96
-		v = &localVolume{
97
-			driverName: r.Name(),
98
-			name:       name,
99
-			path:       path,
88
+	if exists {
89
+		return v, nil
90
+	}
91
+
92
+	path := r.DataPath(name)
93
+	if err := os.MkdirAll(path, 0755); err != nil {
94
+		if os.IsExist(err) {
95
+			return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
100 96
 		}
101
-		r.volumes[name] = v
97
+		return nil, err
102 98
 	}
103
-	v.use()
99
+	v = &localVolume{
100
+		driverName: r.Name(),
101
+		name:       name,
102
+		path:       path,
103
+	}
104
+	r.volumes[name] = v
104 105
 	return v, nil
105 106
 }
106 107
 
... ...
@@ -115,24 +130,32 @@ func (r *Root) Remove(v volume.Volume) error {
115 115
 	if !ok {
116 116
 		return errors.New("unknown volume type")
117 117
 	}
118
-	lv.release()
119
-	if lv.usedCount == 0 {
120
-		realPath, err := filepath.EvalSymlinks(lv.path)
121
-		if err != nil {
122
-			return err
123
-		}
124
-		if !r.scopedPath(realPath) {
125
-			return fmt.Errorf("Unable to remove a directory of out the Docker root: %s", realPath)
126
-		}
127 118
 
128
-		if err := os.RemoveAll(realPath); err != nil {
129
-			return err
130
-		}
119
+	realPath, err := filepath.EvalSymlinks(lv.path)
120
+	if err != nil {
121
+		return err
122
+	}
123
+	if !r.scopedPath(realPath) {
124
+		return fmt.Errorf("Unable to remove a directory of out the Docker root: %s", realPath)
125
+	}
131 126
 
132
-		delete(r.volumes, lv.name)
133
-		return os.RemoveAll(filepath.Dir(lv.path))
127
+	if err := os.RemoveAll(realPath); err != nil {
128
+		return err
134 129
 	}
135
-	return nil
130
+
131
+	delete(r.volumes, lv.name)
132
+	return os.RemoveAll(filepath.Dir(lv.path))
133
+}
134
+
135
+// Get looks up the volume for the given name and returns it if found
136
+func (r *Root) Get(name string) (volume.Volume, error) {
137
+	r.m.Lock()
138
+	v, exists := r.volumes[name]
139
+	r.m.Unlock()
140
+	if !exists {
141
+		return nil, ErrNotFound
142
+	}
143
+	return v, nil
136 144
 }
137 145
 
138 146
 // scopedPath verifies that the path where the volume is located
... ...
@@ -188,15 +211,3 @@ func (v *localVolume) Mount() (string, error) {
188 188
 func (v *localVolume) Unmount() error {
189 189
 	return nil
190 190
 }
191
-
192
-func (v *localVolume) use() {
193
-	v.m.Lock()
194
-	v.usedCount++
195
-	v.m.Unlock()
196
-}
197
-
198
-func (v *localVolume) release() {
199
-	v.m.Lock()
200
-	v.usedCount--
201
-	v.m.Unlock()
202
-}
... ...
@@ -9,7 +9,7 @@ type Driver interface {
9 9
 	// Name returns the name of the volume driver.
10 10
 	Name() string
11 11
 	// Create makes a new volume with the given id.
12
-	Create(string) (Volume, error)
12
+	Create(name string, opts map[string]string) (Volume, error)
13 13
 	// Remove deletes the volume.
14 14
 	Remove(Volume) error
15 15
 }