Skip to content

Commit b3b7eb2

Browse files
committed
Add volume API/CLI
Signed-off-by: Brian Goff <[email protected]>
1 parent 7ef08f3 commit b3b7eb2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1465
-404
lines changed

api/client/volume.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package client
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/url"
9+
"text/tabwriter"
10+
"text/template"
11+
12+
"github.com/docker/docker/api/types"
13+
Cli "github.com/docker/docker/cli"
14+
"github.com/docker/docker/opts"
15+
flag "github.com/docker/docker/pkg/mflag"
16+
"github.com/docker/docker/pkg/parsers/filters"
17+
)
18+
19+
// CmdVolume is the parent subcommand for all volume commands
20+
//
21+
// Usage: docker volume <COMMAND> <OPTS>
22+
func (cli *DockerCli) CmdVolume(args ...string) error {
23+
description := "Manage Docker volumes\n\nCommands:\n"
24+
commands := [][]string{
25+
{"create", "Create a volume"},
26+
{"inspect", "Return low-level information on a volume"},
27+
{"ls", "List volumes"},
28+
{"rm", "Remove a volume"},
29+
}
30+
31+
for _, cmd := range commands {
32+
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
33+
}
34+
35+
description += "\nRun 'docker volume COMMAND --help' for more information on a command."
36+
cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, true)
37+
cmd.ParseFlags(args, true)
38+
39+
return cli.CmdVolumeLs(args...)
40+
}
41+
42+
// CmdVolumeLs outputs a list of Docker volumes.
43+
//
44+
// Usage: docker volume ls [OPTIONS]
45+
func (cli *DockerCli) CmdVolumeLs(args ...string) error {
46+
cmd := Cli.Subcmd("volume ls", nil, "List volumes", true)
47+
48+
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display volume names")
49+
flFilter := opts.NewListOpts(nil)
50+
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
51+
52+
cmd.Require(flag.Exact, 0)
53+
cmd.ParseFlags(args, true)
54+
55+
volFilterArgs := filters.Args{}
56+
for _, f := range flFilter.GetAll() {
57+
var err error
58+
volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
59+
if err != nil {
60+
return err
61+
}
62+
}
63+
64+
v := url.Values{}
65+
if len(volFilterArgs) > 0 {
66+
filterJSON, err := filters.ToParam(volFilterArgs)
67+
if err != nil {
68+
return err
69+
}
70+
v.Set("filters", filterJSON)
71+
}
72+
73+
resp, err := cli.call("GET", "/volumes?"+v.Encode(), nil, nil)
74+
if err != nil {
75+
return err
76+
}
77+
78+
var volumes types.VolumesListResponse
79+
if err := json.NewDecoder(resp.body).Decode(&volumes); err != nil {
80+
return err
81+
}
82+
83+
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
84+
if !*quiet {
85+
fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
86+
fmt.Fprintf(w, "\n")
87+
}
88+
89+
for _, vol := range volumes.Volumes {
90+
if *quiet {
91+
fmt.Fprintln(w, vol.Name)
92+
continue
93+
}
94+
fmt.Fprintf(w, "%s\t%s\n", vol.Driver, vol.Name)
95+
}
96+
w.Flush()
97+
return nil
98+
}
99+
100+
// CmdVolumeInspect displays low-level information on one or more volumes.
101+
//
102+
// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
103+
func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
104+
cmd := Cli.Subcmd("volume inspect", []string{"[VOLUME NAME]"}, "Return low-level information on a volume", true)
105+
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template.")
106+
if err := cmd.Parse(args); err != nil {
107+
return nil
108+
}
109+
110+
cmd.Require(flag.Min, 1)
111+
cmd.ParseFlags(args, true)
112+
113+
var tmpl *template.Template
114+
if *tmplStr != "" {
115+
var err error
116+
tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
117+
if err != nil {
118+
return err
119+
}
120+
}
121+
122+
var status = 0
123+
var volumes []*types.Volume
124+
for _, name := range cmd.Args() {
125+
resp, err := cli.call("GET", "/volumes/"+name, nil, nil)
126+
if err != nil {
127+
return err
128+
}
129+
130+
var volume types.Volume
131+
if err := json.NewDecoder(resp.body).Decode(&volume); err != nil {
132+
fmt.Fprintf(cli.err, "%s\n", err)
133+
status = 1
134+
continue
135+
}
136+
137+
if tmpl == nil {
138+
volumes = append(volumes, &volume)
139+
continue
140+
}
141+
142+
if err := tmpl.Execute(cli.out, &volume); err != nil {
143+
if err := tmpl.Execute(cli.out, &volume); err != nil {
144+
fmt.Fprintf(cli.err, "%s\n", err)
145+
status = 1
146+
continue
147+
}
148+
}
149+
io.WriteString(cli.out, "\n")
150+
}
151+
152+
if tmpl != nil {
153+
return nil
154+
}
155+
156+
b, err := json.MarshalIndent(volumes, "", " ")
157+
if err != nil {
158+
return err
159+
}
160+
_, err = io.Copy(cli.out, bytes.NewReader(b))
161+
if err != nil {
162+
return err
163+
}
164+
io.WriteString(cli.out, "\n")
165+
166+
if status != 0 {
167+
return Cli.StatusError{StatusCode: status}
168+
}
169+
return nil
170+
}
171+
172+
// CmdVolumeCreate creates a new container from a given image.
173+
//
174+
// Usage: docker volume create [OPTIONS]
175+
func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
176+
cmd := Cli.Subcmd("volume create", nil, "Create a volume", true)
177+
flDriver := cmd.String([]string{"d", "-driver"}, "local", "Specify volume driver name")
178+
flName := cmd.String([]string{"-name"}, "", "Specify volume name")
179+
180+
flDriverOpts := opts.NewMapOpts(nil, nil)
181+
cmd.Var(flDriverOpts, []string{"o", "-opt"}, "Set driver specific options")
182+
183+
cmd.Require(flag.Exact, 0)
184+
cmd.ParseFlags(args, true)
185+
186+
volReq := &types.VolumeCreateRequest{
187+
Driver: *flDriver,
188+
DriverOpts: flDriverOpts.GetAll(),
189+
}
190+
191+
if *flName != "" {
192+
volReq.Name = *flName
193+
}
194+
195+
resp, err := cli.call("POST", "/volumes", volReq, nil)
196+
if err != nil {
197+
return err
198+
}
199+
200+
var vol types.Volume
201+
if err := json.NewDecoder(resp.body).Decode(&vol); err != nil {
202+
return err
203+
}
204+
fmt.Fprintf(cli.out, "%s\n", vol.Name)
205+
return nil
206+
}
207+
208+
// CmdVolumeRm removes one or more containers.
209+
//
210+
// Usage: docker volume rm VOLUME [VOLUME...]
211+
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
212+
cmd := Cli.Subcmd("volume rm", []string{"[NAME]"}, "Remove a volume", true)
213+
cmd.Require(flag.Min, 1)
214+
cmd.ParseFlags(args, true)
215+
216+
var status = 0
217+
for _, name := range cmd.Args() {
218+
_, err := cli.call("DELETE", "/volumes/"+name, nil, nil)
219+
if err != nil {
220+
fmt.Fprintf(cli.err, "%s\n", err)
221+
status = 1
222+
continue
223+
}
224+
fmt.Fprintf(cli.out, "%s\n", name)
225+
}
226+
227+
if status != 0 {
228+
return Cli.StatusError{StatusCode: status}
229+
}
230+
return nil
231+
}

api/server/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,8 @@ func createRouter(s *Server) *mux.Router {
328328
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
329329
"/exec/{id:.*}/json": s.getExecByID,
330330
"/containers/{name:.*}/archive": s.getContainersArchive,
331+
"/volumes": s.getVolumesList,
332+
"/volumes/{name:.*}": s.getVolumeByName,
331333
},
332334
"POST": {
333335
"/auth": s.postAuth,
@@ -352,13 +354,15 @@ func createRouter(s *Server) *mux.Router {
352354
"/exec/{name:.*}/start": s.postContainerExecStart,
353355
"/exec/{name:.*}/resize": s.postContainerExecResize,
354356
"/containers/{name:.*}/rename": s.postContainerRename,
357+
"/volumes": s.postVolumesCreate,
355358
},
356359
"PUT": {
357360
"/containers/{name:.*}/archive": s.putContainersArchive,
358361
},
359362
"DELETE": {
360363
"/containers/{name:.*}": s.deleteContainers,
361364
"/images/{name:.*}": s.deleteImages,
365+
"/volumes/{name:.*}": s.deleteVolumes,
362366
},
363367
"OPTIONS": {
364368
"": s.optionsHandler,

api/server/volume.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package server
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
7+
"github.com/docker/docker/api/types"
8+
"github.com/docker/docker/pkg/version"
9+
)
10+
11+
func (s *Server) getVolumesList(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
12+
if err := parseForm(r); err != nil {
13+
return err
14+
}
15+
16+
volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
17+
if err != nil {
18+
return err
19+
}
20+
return writeJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
21+
}
22+
23+
func (s *Server) getVolumeByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
24+
if err := parseForm(r); err != nil {
25+
return err
26+
}
27+
28+
v, err := s.daemon.VolumeInspect(vars["name"])
29+
if err != nil {
30+
return err
31+
}
32+
return writeJSON(w, http.StatusOK, v)
33+
}
34+
35+
func (s *Server) postVolumesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
36+
if err := parseForm(r); err != nil {
37+
return err
38+
}
39+
40+
if err := checkForJSON(r); err != nil {
41+
return err
42+
}
43+
44+
var req types.VolumeCreateRequest
45+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
46+
return err
47+
}
48+
49+
volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
50+
if err != nil {
51+
return err
52+
}
53+
return writeJSON(w, http.StatusCreated, volume)
54+
}
55+
56+
func (s *Server) deleteVolumes(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
57+
if err := parseForm(r); err != nil {
58+
return err
59+
}
60+
if err := s.daemon.VolumeRm(vars["name"]); err != nil {
61+
return err
62+
}
63+
w.WriteHeader(http.StatusNoContent)
64+
return nil
65+
}

api/types/types.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,24 @@ type MountPoint struct {
301301
Mode string
302302
RW bool
303303
}
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+
}

daemon/container_unix.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ func (container *Container) isDestinationMounted(destination string) bool {
11971197
func (container *Container) prepareMountPoints() error {
11981198
for _, config := range container.MountPoints {
11991199
if len(config.Driver) > 0 {
1200-
v, err := createVolume(config.Name, config.Driver)
1200+
v, err := container.daemon.createVolume(config.Name, config.Driver, nil)
12011201
if err != nil {
12021202
return err
12031203
}
@@ -1207,14 +1207,23 @@ func (container *Container) prepareMountPoints() error {
12071207
return nil
12081208
}
12091209

1210-
func (container *Container) removeMountPoints() error {
1210+
func (container *Container) removeMountPoints(rm bool) error {
1211+
var rmErrors []string
12111212
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
12151221
}
12161222
}
12171223
}
1224+
if len(rmErrors) > 0 {
1225+
return fmt.Errorf("Error removing volumes:\n%v", rmErrors)
1226+
}
12181227
return nil
12191228
}
12201229

daemon/container_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func (container *Container) prepareMountPoints() error {
169169
}
170170

171171
// removeMountPoints is a no-op on Windows.
172-
func (container *Container) removeMountPoints() error {
172+
func (container *Container) removeMountPoints(_ bool) error {
173173
return nil
174174
}
175175

0 commit comments

Comments
 (0)