Add volume API/CLI
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
parent
7ef08f39ed
commit
b3b7eb2723
49 changed files with 1457 additions and 396 deletions
231
api/client/volume.go
Normal file
231
api/client/volume.go
Normal file
|
@ -0,0 +1,231 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/parsers/filters"
|
||||
)
|
||||
|
||||
// CmdVolume is the parent subcommand for all volume commands
|
||||
//
|
||||
// Usage: docker volume <COMMAND> <OPTS>
|
||||
func (cli *DockerCli) CmdVolume(args ...string) error {
|
||||
description := "Manage Docker volumes\n\nCommands:\n"
|
||||
commands := [][]string{
|
||||
{"create", "Create a volume"},
|
||||
{"inspect", "Return low-level information on a volume"},
|
||||
{"ls", "List volumes"},
|
||||
{"rm", "Remove a volume"},
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
|
||||
}
|
||||
|
||||
description += "\nRun 'docker volume COMMAND --help' for more information on a command."
|
||||
cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, true)
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
return cli.CmdVolumeLs(args...)
|
||||
}
|
||||
|
||||
// CmdVolumeLs outputs a list of Docker volumes.
|
||||
//
|
||||
// Usage: docker volume ls [OPTIONS]
|
||||
func (cli *DockerCli) CmdVolumeLs(args ...string) error {
|
||||
cmd := Cli.Subcmd("volume ls", nil, "List volumes", true)
|
||||
|
||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display volume names")
|
||||
flFilter := opts.NewListOpts(nil)
|
||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
|
||||
|
||||
cmd.Require(flag.Exact, 0)
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
volFilterArgs := filters.Args{}
|
||||
for _, f := range flFilter.GetAll() {
|
||||
var err error
|
||||
volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
if len(volFilterArgs) > 0 {
|
||||
filterJSON, err := filters.ToParam(volFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set("filters", filterJSON)
|
||||
}
|
||||
|
||||
resp, err := cli.call("GET", "/volumes?"+v.Encode(), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var volumes types.VolumesListResponse
|
||||
if err := json.NewDecoder(resp.body).Decode(&volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
|
||||
for _, vol := range volumes.Volumes {
|
||||
if *quiet {
|
||||
fmt.Fprintln(w, vol.Name)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\n", vol.Driver, vol.Name)
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdVolumeInspect displays low-level information on one or more volumes.
|
||||
//
|
||||
// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
|
||||
func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
|
||||
cmd := Cli.Subcmd("volume inspect", []string{"[VOLUME NAME]"}, "Return low-level information on a volume", true)
|
||||
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd.Require(flag.Min, 1)
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var tmpl *template.Template
|
||||
if *tmplStr != "" {
|
||||
var err error
|
||||
tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var status = 0
|
||||
var volumes []*types.Volume
|
||||
for _, name := range cmd.Args() {
|
||||
resp, err := cli.call("GET", "/volumes/"+name, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var volume types.Volume
|
||||
if err := json.NewDecoder(resp.body).Decode(&volume); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
|
||||
if tmpl == nil {
|
||||
volumes = append(volumes, &volume)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(cli.out, &volume); err != nil {
|
||||
if err := tmpl.Execute(cli.out, &volume); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
io.WriteString(cli.out, "\n")
|
||||
}
|
||||
|
||||
if tmpl != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(volumes, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(cli.out, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.WriteString(cli.out, "\n")
|
||||
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdVolumeCreate creates a new container from a given image.
|
||||
//
|
||||
// Usage: docker volume create [OPTIONS]
|
||||
func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
|
||||
cmd := Cli.Subcmd("volume create", nil, "Create a volume", true)
|
||||
flDriver := cmd.String([]string{"d", "-driver"}, "local", "Specify volume driver name")
|
||||
flName := cmd.String([]string{"-name"}, "", "Specify volume name")
|
||||
|
||||
flDriverOpts := opts.NewMapOpts(nil, nil)
|
||||
cmd.Var(flDriverOpts, []string{"o", "-opt"}, "Set driver specific options")
|
||||
|
||||
cmd.Require(flag.Exact, 0)
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
volReq := &types.VolumeCreateRequest{
|
||||
Driver: *flDriver,
|
||||
DriverOpts: flDriverOpts.GetAll(),
|
||||
}
|
||||
|
||||
if *flName != "" {
|
||||
volReq.Name = *flName
|
||||
}
|
||||
|
||||
resp, err := cli.call("POST", "/volumes", volReq, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var vol types.Volume
|
||||
if err := json.NewDecoder(resp.body).Decode(&vol); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.out, "%s\n", vol.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdVolumeRm removes one or more containers.
|
||||
//
|
||||
// Usage: docker volume rm VOLUME [VOLUME...]
|
||||
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
|
||||
cmd := Cli.Subcmd("volume rm", []string{"[NAME]"}, "Remove a volume", true)
|
||||
cmd.Require(flag.Min, 1)
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var status = 0
|
||||
for _, name := range cmd.Args() {
|
||||
_, err := cli.call("DELETE", "/volumes/"+name, nil, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -328,6 +328,8 @@ func createRouter(s *Server) *mux.Router {
|
|||
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
|
||||
"/exec/{id:.*}/json": s.getExecByID,
|
||||
"/containers/{name:.*}/archive": s.getContainersArchive,
|
||||
"/volumes": s.getVolumesList,
|
||||
"/volumes/{name:.*}": s.getVolumeByName,
|
||||
},
|
||||
"POST": {
|
||||
"/auth": s.postAuth,
|
||||
|
@ -352,6 +354,7 @@ func createRouter(s *Server) *mux.Router {
|
|||
"/exec/{name:.*}/start": s.postContainerExecStart,
|
||||
"/exec/{name:.*}/resize": s.postContainerExecResize,
|
||||
"/containers/{name:.*}/rename": s.postContainerRename,
|
||||
"/volumes": s.postVolumesCreate,
|
||||
},
|
||||
"PUT": {
|
||||
"/containers/{name:.*}/archive": s.putContainersArchive,
|
||||
|
@ -359,6 +362,7 @@ func createRouter(s *Server) *mux.Router {
|
|||
"DELETE": {
|
||||
"/containers/{name:.*}": s.deleteContainers,
|
||||
"/images/{name:.*}": s.deleteImages,
|
||||
"/volumes/{name:.*}": s.deleteVolumes,
|
||||
},
|
||||
"OPTIONS": {
|
||||
"": s.optionsHandler,
|
||||
|
|
65
api/server/volume.go
Normal file
65
api/server/volume.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
)
|
||||
|
||||
func (s *Server) getVolumesList(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
|
||||
}
|
||||
|
||||
func (s *Server) getVolumeByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := s.daemon.VolumeInspect(vars["name"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeJSON(w, http.StatusOK, v)
|
||||
}
|
||||
|
||||
func (s *Server) postVolumesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkForJSON(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var req types.VolumeCreateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeJSON(w, http.StatusCreated, volume)
|
||||
}
|
||||
|
||||
func (s *Server) deleteVolumes(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.daemon.VolumeRm(vars["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
|
@ -301,3 +301,24 @@ type MountPoint struct {
|
|||
Mode string
|
||||
RW bool
|
||||
}
|
||||
|
||||
// Volume represents the configuration of a volume for the remote API
|
||||
type Volume struct {
|
||||
Name string // Name is the name of the volume
|
||||
Driver string // Driver is the Driver name used to create the volume
|
||||
Mountpoint string // Mountpoint is the location on disk of the volume
|
||||
}
|
||||
|
||||
// VolumesListResponse contains the response for the remote API:
|
||||
// GET "/volumes"
|
||||
type VolumesListResponse struct {
|
||||
Volumes []*Volume // Volumes is the list of volumes being returned
|
||||
}
|
||||
|
||||
// VolumeCreateRequest contains the response for the remote API:
|
||||
// POST "/volumes"
|
||||
type VolumeCreateRequest struct {
|
||||
Name string // Name is the requested name of the volume
|
||||
Driver string // Driver is the name of the driver that should be used to create the volume
|
||||
DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume.
|
||||
}
|
||||
|
|
|
@ -1197,7 +1197,7 @@ func (container *Container) isDestinationMounted(destination string) bool {
|
|||
func (container *Container) prepareMountPoints() error {
|
||||
for _, config := range container.MountPoints {
|
||||
if len(config.Driver) > 0 {
|
||||
v, err := createVolume(config.Name, config.Driver)
|
||||
v, err := container.daemon.createVolume(config.Name, config.Driver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1207,14 +1207,23 @@ func (container *Container) prepareMountPoints() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) removeMountPoints() error {
|
||||
func (container *Container) removeMountPoints(rm bool) error {
|
||||
var rmErrors []string
|
||||
for _, m := range container.MountPoints {
|
||||
if m.Volume != nil {
|
||||
if err := removeVolume(m.Volume); err != nil {
|
||||
return err
|
||||
if m.Volume == nil {
|
||||
continue
|
||||
}
|
||||
container.daemon.volumes.Decrement(m.Volume)
|
||||
if rm {
|
||||
if err := container.daemon.volumes.Remove(m.Volume); err != nil {
|
||||
rmErrors = append(rmErrors, fmt.Sprintf("%v\n", err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(rmErrors) > 0 {
|
||||
return fmt.Errorf("Error removing volumes:\n%v", rmErrors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ func (container *Container) prepareMountPoints() error {
|
|||
}
|
||||
|
||||
// removeMountPoints is a no-op on Windows.
|
||||
func (container *Container) removeMountPoints() error {
|
||||
func (container *Container) removeMountPoints(_ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/graph/tags"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
|
@ -124,3 +126,17 @@ func (daemon *Daemon) GenerateSecurityOpt(ipcMode runconfig.IpcMode, pidMode run
|
|||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// VolumeCreate creates a volume with the specified name, driver, and opts
|
||||
// This is called directly from the remote API
|
||||
func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]string) (*types.Volume, error) {
|
||||
if name == "" {
|
||||
name = stringid.GenerateNonCryptoID()
|
||||
}
|
||||
|
||||
v, err := daemon.volumes.Create(name, driverName, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumeToAPIType(v), nil
|
||||
}
|
||||
|
|
|
@ -54,10 +54,11 @@ func createContainerPlatformSpecificSettings(container *Container, config *runco
|
|||
}
|
||||
}
|
||||
|
||||
v, err := createVolume(name, volumeDriver)
|
||||
v, err := container.daemon.createVolume(name, volumeDriver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := label.Relabel(v.Path(), container.MountLabel, "z"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@ type Daemon struct {
|
|||
RegistryService *registry.Service
|
||||
EventsService *events.Events
|
||||
netController libnetwork.NetworkController
|
||||
volumes *volumeStore
|
||||
root string
|
||||
shutdown bool
|
||||
}
|
||||
|
@ -653,7 +654,8 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
|
|||
}
|
||||
|
||||
// Configure the volumes driver
|
||||
if err := configureVolumes(config); err != nil {
|
||||
volStore, err := configureVolumes(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -740,6 +742,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
|
|||
d.defaultLogConfig = config.LogConfig
|
||||
d.RegistryService = registryService
|
||||
d.EventsService = eventsService
|
||||
d.volumes = volStore
|
||||
d.root = config.Root
|
||||
go d.execCommandGC()
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/docker/docker/pkg/truncindex"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/local"
|
||||
)
|
||||
|
||||
|
@ -486,12 +486,12 @@ func TestRemoveLocalVolumesFollowingSymlinks(t *testing.T) {
|
|||
}
|
||||
|
||||
m := c.MountPoints["/vol1"]
|
||||
v, err := createVolume(m.Name, m.Driver)
|
||||
_, err = daemon.VolumeCreate(m.Name, m.Driver, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := removeVolume(v); err != nil {
|
||||
if err := daemon.VolumeRm(m.Name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -505,6 +505,7 @@ func initDaemonForVolumesTest(tmp string) (*Daemon, error) {
|
|||
daemon := &Daemon{
|
||||
repository: tmp,
|
||||
root: tmp,
|
||||
volumes: newVolumeStore([]volume.Volume{}),
|
||||
}
|
||||
|
||||
volumesDriver, err := local.New(tmp)
|
||||
|
|
|
@ -256,13 +256,13 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
|
|||
return migrateIfAufs(driver, root)
|
||||
}
|
||||
|
||||
func configureVolumes(config *Config) error {
|
||||
func configureVolumes(config *Config) (*volumeStore, error) {
|
||||
volumesDriver, err := local.New(config.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
volumedrivers.Register(volumesDriver, volumesDriver.Name())
|
||||
return nil
|
||||
return newVolumeStore(volumesDriver.List()), nil
|
||||
}
|
||||
|
||||
func configureSysInit(config *Config) (string, error) {
|
||||
|
|
|
@ -74,9 +74,9 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func configureVolumes(config *Config) error {
|
||||
func configureVolumes(config *Config) (*volumeStore, error) {
|
||||
// Windows does not support volumes at this time
|
||||
return nil
|
||||
return &volumeStore{}, nil
|
||||
}
|
||||
|
||||
func configureSysInit(config *Config) (string, error) {
|
||||
|
|
|
@ -50,9 +50,7 @@ func (daemon *Daemon) ContainerRm(name string, config *ContainerRmConfig) error
|
|||
return fmt.Errorf("Cannot destroy container %s: %v", name, err)
|
||||
}
|
||||
|
||||
if config.RemoveVolume {
|
||||
container.removeMountPoints()
|
||||
}
|
||||
container.removeMountPoints(config.RemoveVolume)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -137,6 +135,19 @@ func (daemon *Daemon) rm(container *Container, forceRemove bool) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) DeleteVolumes(c *Container) error {
|
||||
return c.removeMountPoints()
|
||||
// VolumeRm removes the volume with the given name.
|
||||
// If the volume is referenced by a container it is not removed
|
||||
// This is called directly from the remote API
|
||||
func (daemon *Daemon) VolumeRm(name string) error {
|
||||
v, err := daemon.volumes.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := daemon.volumes.Remove(v); err != nil {
|
||||
if err == ErrVolumeInUse {
|
||||
return fmt.Errorf("Conflict: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -97,3 +97,11 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) {
|
|||
}
|
||||
return eConfig, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
|
||||
v, err := daemon.volumes.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumeToAPIType(v), nil
|
||||
}
|
||||
|
|
|
@ -214,3 +214,32 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container,
|
|||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
|
||||
var volumesOut []*types.Volume
|
||||
volFilters, err := filters.FromParam(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filterUsed := false
|
||||
if i, ok := volFilters["dangling"]; ok {
|
||||
if len(i) > 1 {
|
||||
return nil, fmt.Errorf("Conflict: cannot use more than 1 value for `dangling` filter")
|
||||
}
|
||||
|
||||
filterValue := i[0]
|
||||
if strings.ToLower(filterValue) == "true" || filterValue == "1" {
|
||||
filterUsed = true
|
||||
}
|
||||
}
|
||||
|
||||
volumes := daemon.volumes.List()
|
||||
for _, v := range volumes {
|
||||
if filterUsed && daemon.volumes.Count(v) == 0 {
|
||||
continue
|
||||
}
|
||||
volumesOut = append(volumesOut, volumeToAPIType(v))
|
||||
}
|
||||
return volumesOut, nil
|
||||
}
|
||||
|
|
|
@ -7,15 +7,24 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
)
|
||||
|
||||
// ErrVolumeReadonly is used to signal an error when trying to copy data into
|
||||
// a volume mount that is not writable.
|
||||
var ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
|
||||
var (
|
||||
// ErrVolumeReadonly is used to signal an error when trying to copy data into
|
||||
// a volume mount that is not writable.
|
||||
ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
|
||||
// ErrVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container
|
||||
ErrVolumeInUse = errors.New("volume is in use")
|
||||
// ErrNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
|
||||
ErrNoSuchVolume = errors.New("no such volume")
|
||||
)
|
||||
|
||||
// mountPoint is the intersection point between a volume and a container. It
|
||||
// specifies which volume is to be used and where inside a container it should
|
||||
|
@ -92,3 +101,144 @@ func copyExistingContents(source, destination string) error {
|
|||
}
|
||||
return copyOwnership(source, destination)
|
||||
}
|
||||
|
||||
func newVolumeStore(vols []volume.Volume) *volumeStore {
|
||||
store := &volumeStore{
|
||||
vols: make(map[string]*volumeCounter),
|
||||
}
|
||||
for _, v := range vols {
|
||||
store.vols[v.Name()] = &volumeCounter{v, 0}
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
// volumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
|
||||
type volumeStore struct {
|
||||
vols map[string]*volumeCounter
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type volumeCounter struct {
|
||||
volume.Volume
|
||||
count int
|
||||
}
|
||||
|
||||
func getVolumeDriver(name string) (volume.Driver, error) {
|
||||
if name == "" {
|
||||
name = volume.DefaultDriverName
|
||||
}
|
||||
return volumedrivers.Lookup(name)
|
||||
}
|
||||
|
||||
// Create tries to find an existing volume with the given name or create a new one from the passed in driver
|
||||
func (s *volumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if vc, exists := s.vols[name]; exists {
|
||||
return vc.Volume, nil
|
||||
}
|
||||
|
||||
vd, err := getVolumeDriver(driverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := vd.Create(name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.vols[v.Name()] = &volumeCounter{v, 0}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Get looks if a volume with the given name exists and returns it if so
|
||||
func (s *volumeStore) Get(name string) (volume.Volume, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
vc, exists := s.vols[name]
|
||||
if !exists {
|
||||
return nil, ErrNoSuchVolume
|
||||
}
|
||||
return vc.Volume, nil
|
||||
}
|
||||
|
||||
// Remove removes the requested volume. A volume is not removed if the usage count is > 0
|
||||
func (s *volumeStore) Remove(v volume.Volume) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
name := v.Name()
|
||||
vc, exists := s.vols[name]
|
||||
if !exists {
|
||||
return ErrNoSuchVolume
|
||||
}
|
||||
|
||||
if vc.count != 0 {
|
||||
return ErrVolumeInUse
|
||||
}
|
||||
|
||||
vd, err := getVolumeDriver(vc.DriverName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := vd.Remove(vc.Volume); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(s.vols, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Increment increments the usage count of the passed in volume by 1
|
||||
func (s *volumeStore) Increment(v volume.Volume) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
vc, exists := s.vols[v.Name()]
|
||||
if !exists {
|
||||
s.vols[v.Name()] = &volumeCounter{v, 1}
|
||||
return
|
||||
}
|
||||
vc.count++
|
||||
return
|
||||
}
|
||||
|
||||
// Decrement decrements the usage count of the passed in volume by 1
|
||||
func (s *volumeStore) Decrement(v volume.Volume) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
vc, exists := s.vols[v.Name()]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
vc.count--
|
||||
return
|
||||
}
|
||||
|
||||
// Count returns the usage count of the passed in volume
|
||||
func (s *volumeStore) Count(v volume.Volume) int {
|
||||
vc, exists := s.vols[v.Name()]
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
return vc.count
|
||||
}
|
||||
|
||||
// List returns all the available volumes
|
||||
func (s *volumeStore) List() []volume.Volume {
|
||||
var ls []volume.Volume
|
||||
for _, vc := range s.vols {
|
||||
ls = append(ls, vc.Volume)
|
||||
}
|
||||
return ls
|
||||
}
|
||||
|
||||
// volumeToAPIType converts a volume.Volume to the type used by the remote API
|
||||
func volumeToAPIType(v volume.Volume) *types.Volume {
|
||||
return &types.Volume{
|
||||
Name: v.Name(),
|
||||
Driver: v.DriverName(),
|
||||
Mountpoint: v.Path(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ import (
|
|||
|
||||
type fakeDriver struct{}
|
||||
|
||||
func (fakeDriver) Name() string { return "fake" }
|
||||
func (fakeDriver) Create(name string) (volume.Volume, error) { return nil, nil }
|
||||
func (fakeDriver) Remove(v volume.Volume) error { return nil }
|
||||
func (fakeDriver) Name() string { return "fake" }
|
||||
func (fakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) { return nil, nil }
|
||||
func (fakeDriver) Remove(v volume.Volume) error { return nil }
|
||||
|
||||
func TestGetVolumeDriver(t *testing.T) {
|
||||
_, err := getVolumeDriver("missing")
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/local"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
|
@ -138,7 +138,7 @@ func (m mounts) parts(i int) int {
|
|||
// It preserves the volume json configuration generated pre Docker 1.7 to be able to
|
||||
// downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility.
|
||||
func migrateVolume(id, vfs string) error {
|
||||
l, err := getVolumeDriver(volume.DefaultDriverName)
|
||||
l, err := volumedrivers.Lookup(volume.DefaultDriverName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
|
|||
// Volumes created with a Docker version >= 1.7. We verify integrity in case of data created
|
||||
// with Docker 1.7 RC versions that put the information in
|
||||
// DOCKER_ROOT/volumes/VOLUME_ID rather than DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
|
||||
l, err := getVolumeDriver(volume.DefaultDriverName)
|
||||
l, err := volumedrivers.Lookup(volume.DefaultDriverName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -311,7 +311,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
|||
}
|
||||
|
||||
if len(cp.Source) == 0 {
|
||||
v, err := createVolume(cp.Name, cp.Driver)
|
||||
v, err := daemon.createVolume(cp.Name, cp.Driver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -336,7 +336,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
|||
|
||||
if len(bind.Name) > 0 && len(bind.Driver) > 0 {
|
||||
// create the volume
|
||||
v, err := createVolume(bind.Name, bind.Driver)
|
||||
v, err := daemon.createVolume(bind.Name, bind.Driver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -362,6 +362,11 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
|||
if m.BackwardsCompatible() {
|
||||
bcVolumes[m.Destination] = m.Path()
|
||||
bcVolumesRW[m.Destination] = m.RW
|
||||
|
||||
// This mountpoint is replacing an existing one, so the count needs to be decremented
|
||||
if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
|
||||
daemon.volumes.Decrement(mp.Volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,29 +380,13 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
|||
}
|
||||
|
||||
// createVolume creates a volume.
|
||||
func createVolume(name, driverName string) (volume.Volume, error) {
|
||||
vd, err := getVolumeDriver(driverName)
|
||||
func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
||||
v, err := daemon.volumes.Create(name, driverName, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vd.Create(name)
|
||||
}
|
||||
|
||||
// removeVolume removes a volume.
|
||||
func removeVolume(v volume.Volume) error {
|
||||
vd, err := getVolumeDriver(v.DriverName())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return vd.Remove(v)
|
||||
}
|
||||
|
||||
// getVolumeDriver returns the volume driver for the supplied name.
|
||||
func getVolumeDriver(name string) (volume.Driver, error) {
|
||||
if name == "" {
|
||||
name = volume.DefaultDriverName
|
||||
}
|
||||
return volumedrivers.Lookup(name)
|
||||
daemon.volumes.Increment(v)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// parseVolumeSource parses the origin sources that's mounted into the container.
|
||||
|
|
|
@ -59,5 +59,6 @@ var dockerCommands = []command{
|
|||
{"top", "Display the running processes of a container"},
|
||||
{"unpause", "Unpause all processes within a container"},
|
||||
{"version", "Show the Docker version information"},
|
||||
{"volume", "Manage Docker volumes"},
|
||||
{"wait", "Block until a container stops, then print its exit code"},
|
||||
}
|
||||
|
|
|
@ -52,13 +52,15 @@ containers.
|
|||
**Request**:
|
||||
```
|
||||
{
|
||||
"Name": "volume_name"
|
||||
"Name": "volume_name",
|
||||
"Opts": {}
|
||||
}
|
||||
```
|
||||
|
||||
Instruct the plugin that the user wants to create a volume, given a user
|
||||
specified volume name. The plugin does not need to actually manifest the
|
||||
volume on the filesystem yet (until Mount is called).
|
||||
Opts is a map of driver specific options passed through from the user request.
|
||||
|
||||
**Response**:
|
||||
```
|
||||
|
|
|
@ -10,39 +10,48 @@ parent = "smn_remoteapi"
|
|||
|
||||
# Docker Remote API
|
||||
|
||||
- By default the Docker daemon listens on `unix:///var/run/docker.sock`
|
||||
and the client must have `root` access to interact with the daemon.
|
||||
- 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
|
||||
parameters to `curl` or `wget` when making test API requests:
|
||||
`curl --insecure --cert ~/.docker/cert.pem --key ~/.docker/key.pem https://YOUR_VM_IP:2376/images/json`
|
||||
or
|
||||
`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`
|
||||
- If a group named `docker` exists on your system, docker will apply
|
||||
ownership of the socket to the group.
|
||||
- The API tends to be REST, but for some complex commands, like attach
|
||||
or pull, the HTTP connection is hijacked to transport STDOUT, STDIN,
|
||||
and STDERR.
|
||||
- Since API version 1.2, the auth configuration is now handled client
|
||||
side, so the client has to send the `authConfig` as a `POST` in `/images/(name)/push`.
|
||||
- authConfig, set as the `X-Registry-Auth` header, is currently a Base64
|
||||
encoded (JSON) string with the following structure:
|
||||
`{"username": "string", "password": "string", "email": "string",
|
||||
"serveraddress" : "string", "auth": ""}`. Notice that `auth` is to be left
|
||||
empty, `serveraddress` is a domain/ip without protocol, and that double
|
||||
quotes (instead of single ones) are required.
|
||||
- The Remote API uses an open schema model. In this model, unknown
|
||||
properties in incoming messages will be ignored.
|
||||
Client applications need to take this into account to ensure
|
||||
they will not break when talking to newer Docker daemons.
|
||||
Docker's Remote API uses an open schema model. In this model, unknown
|
||||
properties in incoming messages are ignored. Client applications need to take
|
||||
this behavior into account to ensure they do not break when talking to newer
|
||||
Docker daemons.
|
||||
|
||||
The current version of the API is v1.21
|
||||
The API tends to be REST, but for some complex commands, like attach or pull,
|
||||
the HTTP connection is hijacked to transport STDOUT, STDIN, and STDERR.
|
||||
|
||||
By default the Docker daemon listens on `unix:///var/run/docker.sock` and the
|
||||
client must have `root` access to interact with the daemon. If a group named
|
||||
`docker` exists on your system, `docker` applies ownership of the socket to the
|
||||
group.
|
||||
|
||||
Calling `/info` is the same as calling
|
||||
`/v1.21/info`.
|
||||
|
||||
You can still call an old version of the API using
|
||||
The current version of the API is v1.21 which means calling `/info` is the same
|
||||
as calling `/v1.21/info`. To call an older version of the API use
|
||||
`/v1.20/info`.
|
||||
|
||||
## Authentication
|
||||
|
||||
Since API version 1.2, the auth configuration is now handled client side, so the
|
||||
client has to send the `authConfig` as a `POST` in `/images/(name)/push`. The
|
||||
`authConfig`, set as the `X-Registry-Auth` header, is currently a Base64 encoded
|
||||
(JSON) string with the following structure:
|
||||
|
||||
```
|
||||
{"username": "string", "password": "string", "email": "string",
|
||||
"serveraddress" : "string", "auth": ""}
|
||||
```
|
||||
|
||||
Callers should leave the `auth` empty. The `serveraddress` is a domain/ip
|
||||
without protocol. Throughout this structure, double quotes are required.
|
||||
|
||||
## Using Docker Machine with the API
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
curl --insecure --cert ~/.docker/cert.pem --key ~/.docker/key.pem https://YOUR_VM_IP:2376/images/json
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Docker Events
|
||||
|
||||
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
|
|||
|
||||
> **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/).
|
||||
|
||||
## v1.21
|
||||
## Version history
|
||||
|
||||
### Full documentation
|
||||
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.
|
||||
|
||||
[*Docker Remote API v1.21*](/reference/api/docker_remote_api_v1.21/)
|
||||
### v1.21 API changes
|
||||
|
||||
## v1.20
|
||||
[Docker Remote API v1.21](/reference/api/docker_remote_api_v1.21/) documentation
|
||||
|
||||
### Full documentation
|
||||
* `GET /volumes` lists volumes from all volume drivers.
|
||||
* `POST /volumes` to create a volume.
|
||||
* `GET /volumes/(name)` get low-level information about a volume.
|
||||
* `DELETE /volumes/(name)`remove a volume with the specified name.
|
||||
|
||||
[*Docker Remote API v1.20*](/reference/api/docker_remote_api_v1.20/)
|
||||
|
||||
### What's new
|
||||
### v1.20 API changes
|
||||
|
||||
`GET /containers/(id)/archive`
|
||||
[Docker Remote API v1.20](/reference/api/docker_remote_api_v1.20/) documentation
|
||||
|
||||
**New!**
|
||||
Get an archive of filesystem content from a container.
|
||||
* `GET /containers/(id)/archive` get an archive of filesystem content from a container.
|
||||
* `PUT /containers/(id)/archive` upload an archive of content to be extracted to
|
||||
an existing directory inside a container's filesystem.
|
||||
* `POST /containers/(id)/copy` is deprecated in favor of the above `archive`
|
||||
endpoint which can be used to download files and directories from a container.
|
||||
* The `hostConfig` option now accepts the field `GroupAdd`, which specifies a
|
||||
list of additional groups that the container process will run as.
|
||||
|
||||
`PUT /containers/(id)/archive`
|
||||
### v1.19 API changes
|
||||
|
||||
**New!**
|
||||
Upload an archive of content to be extracted to an
|
||||
existing directory inside a container's filesystem.
|
||||
[Docker Remote API v1.19](/reference/api/docker_remote_api_v1.19/) documentation
|
||||
|
||||
`POST /containers/(id)/copy`
|
||||
|
||||
**Deprecated!**
|
||||
This copy endpoint has been deprecated in favor of the above `archive` endpoint
|
||||
which can be used to download files and directories from a container.
|
||||
|
||||
**New!**
|
||||
The `hostConfig` option now accepts the field `GroupAdd`, which specifies a list of additional
|
||||
groups that the container process will run as.
|
||||
|
||||
## v1.19
|
||||
|
||||
### Full documentation
|
||||
|
||||
[*Docker Remote API v1.19*](/reference/api/docker_remote_api_v1.19/)
|
||||
|
||||
### What's new
|
||||
|
||||
**New!**
|
||||
When the daemon detects a version mismatch with the client, usually when
|
||||
* When the daemon detects a version mismatch with the client, usually when
|
||||
the client is newer than the daemon, an HTTP 400 is now returned instead
|
||||
of a 404.
|
||||
* `GET /containers/(id)/stats` now accepts `stream` bool to get only one set of stats and disconnect.
|
||||
* `GET /containers/(id)/logs` now accepts a `since` timestamp parameter.
|
||||
* `GET /info` The fields `Debug`, `IPv4Forwarding`, `MemoryLimit`, and
|
||||
`SwapLimit` are now returned as boolean instead of as an int. In addition, the
|
||||
end point now returns the new boolean fields `CpuCfsPeriod`, `CpuCfsQuota`, and
|
||||
`OomKillDisable`.
|
||||
|
||||
`GET /containers/(id)/stats`
|
||||
### v1.18 API changes
|
||||
|
||||
**New!**
|
||||
You can now supply a `stream` bool to get only one set of stats and
|
||||
disconnect
|
||||
[Docker Remote API v1.18](/reference/api/docker_remote_api_v1.18/) documentation
|
||||
|
||||
`GET /containers/(id)/logs`
|
||||
* `GET /version` now returns `Os`, `Arch` and `KernelVersion`.
|
||||
* `POST /containers/create` and `POST /containers/(id)/start`allow you to set ulimit settings for use in the container.
|
||||
* `GET /info` now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`.
|
||||
* `GET /images/json` added a `RepoDigests` field to include image digest information.
|
||||
* `POST /build` can now set resource constraints for all containers created for the build.
|
||||
* `CgroupParent` can be passed in the host config to setup container cgroups under a specific cgroup.
|
||||
* `POST /build` closing the HTTP request cancels the build
|
||||
* `POST /containers/(id)/exec` includes `Warnings` field to response.
|
||||
|
||||
**New!**
|
||||
### v1.17 API changes
|
||||
|
||||
This endpoint now accepts a `since` timestamp parameter.
|
||||
[Docker Remote API v1.17](/reference/api/docker_remote_api_v1.17/) documentation
|
||||
|
||||
`GET /info`
|
||||
|
||||
**New!**
|
||||
|
||||
The fields `Debug`, `IPv4Forwarding`, `MemoryLimit`, and `SwapLimit`
|
||||
are now returned as boolean instead of as an int.
|
||||
|
||||
In addition, the end point now returns the new boolean fields
|
||||
`CpuCfsPeriod`, `CpuCfsQuota`, and `OomKillDisable`.
|
||||
|
||||
## v1.18
|
||||
|
||||
### Full documentation
|
||||
|
||||
[*Docker Remote API v1.18*](/reference/api/docker_remote_api_v1.18/)
|
||||
|
||||
### What's new
|
||||
|
||||
`GET /version`
|
||||
|
||||
**New!**
|
||||
This endpoint now returns `Os`, `Arch` and `KernelVersion`.
|
||||
|
||||
`POST /containers/create`
|
||||
|
||||
`POST /containers/(id)/start`
|
||||
|
||||
**New!**
|
||||
You can set ulimit settings to be used within the container.
|
||||
|
||||
`GET /info`
|
||||
|
||||
**New!**
|
||||
This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`.
|
||||
|
||||
`GET /images/json`
|
||||
|
||||
**New!**
|
||||
Added a `RepoDigests` field to include image digest information.
|
||||
|
||||
`POST /build`
|
||||
|
||||
**New!**
|
||||
Builds can now set resource constraints for all containers created for the build.
|
||||
|
||||
**New!**
|
||||
(`CgroupParent`) can be passed in the host config to setup container cgroups under a specific cgroup.
|
||||
|
||||
`POST /build`
|
||||
|
||||
**New!**
|
||||
Closing the HTTP request will now cause the build to be canceled.
|
||||
|
||||
`POST /containers/(id)/exec`
|
||||
|
||||
**New!**
|
||||
Add `Warnings` field to response.
|
||||
|
||||
## v1.17
|
||||
|
||||
### Full documentation
|
||||
|
||||
[*Docker Remote API v1.17*](/reference/api/docker_remote_api_v1.17/)
|
||||
|
||||
### What's new
|
||||
|
||||
The build supports `LABEL` command. Use this to add metadata
|
||||
to an image. For example you could add data describing the content of an image.
|
||||
|
||||
`LABEL "com.example.vendor"="ACME Incorporated"`
|
||||
|
||||
**New!**
|
||||
`POST /containers/(id)/attach` and `POST /exec/(id)/start`
|
||||
|
||||
**New!**
|
||||
Docker client now hints potential proxies about connection hijacking using HTTP Upgrade headers.
|
||||
|
||||
`POST /containers/create`
|
||||
|
||||
**New!**
|
||||
You can set labels on container create describing the container.
|
||||
|
||||
`GET /containers/json`
|
||||
|
||||
**New!**
|
||||
The endpoint returns the labels associated with the containers (`Labels`).
|
||||
|
||||
`GET /containers/(id)/json`
|
||||
|
||||
**New!**
|
||||
This endpoint now returns the list current execs associated with the container (`ExecIDs`).
|
||||
This endpoint now returns the container labels (`Config.Labels`).
|
||||
|
||||
`POST /containers/(id)/rename`
|
||||
|
||||
**New!**
|
||||
New endpoint to rename a container `id` to a new name.
|
||||
|
||||
`POST /containers/create`
|
||||
`POST /containers/(id)/start`
|
||||
|
||||
**New!**
|
||||
(`ReadonlyRootfs`) can be passed in the host config to mount the container's
|
||||
root filesystem as read only.
|
||||
|
||||
`GET /containers/(id)/stats`
|
||||
|
||||
**New!**
|
||||
This endpoint returns a live stream of a container's resource usage statistics.
|
||||
|
||||
`GET /images/json`
|
||||
|
||||
**New!**
|
||||
This endpoint now returns the labels associated with each image (`Labels`).
|
||||
* The build supports `LABEL` command. Use this to add metadata to an image. For
|
||||
example you could add data describing the content of an image. `LABEL
|
||||
"com.example.vendor"="ACME Incorporated"`
|
||||
* `POST /containers/(id)/attach` and `POST /exec/(id)/start`
|
||||
* The Docker client now hints potential proxies about connection hijacking using HTTP Upgrade headers.
|
||||
* `POST /containers/create` sets labels on container create describing the container.
|
||||
* `GET /containers/json` returns the labels associated with the containers (`Labels`).
|
||||
* `GET /containers/(id)/json` returns the list current execs associated with the
|
||||
container (`ExecIDs`). This endpoint now returns the container labels
|
||||
(`Config.Labels`).
|
||||
* `POST /containers/(id)/rename` renames a container `id` to a new name.*
|
||||
* `POST /containers/create` and `POST /containers/(id)/start` callers can pass
|
||||
`ReadonlyRootfs` in the host config to mount the container's root filesystem as
|
||||
read only.
|
||||
* `GET /containers/(id)/stats` returns a live stream of a container's resource usage statistics.
|
||||
* `GET /images/json` returns the labels associated with each image (`Labels`).
|
||||
|
||||
|
||||
## v1.16
|
||||
### v1.16 API changes
|
||||
|
||||
### Full documentation
|
||||
[Docker Remote API v1.16](/reference/api/docker_remote_api_v1.16/)
|
||||
|
||||
[*Docker Remote API v1.16*](/reference/api/docker_remote_api_v1.16/)
|
||||
|
||||
### What's new
|
||||
|
||||
`GET /info`
|
||||
|
||||
**New!**
|
||||
`info` now returns the number of CPUs available on the machine (`NCPU`),
|
||||
* `GET /info` returns the number of CPUs available on the machine (`NCPU`),
|
||||
total memory available (`MemTotal`), a user-friendly name describing the running Docker daemon (`Name`), a unique ID identifying the daemon (`ID`), and
|
||||
a list of daemon labels (`Labels`).
|
||||
* `POST /containers/create` callers can set the new container's MAC address explicitly.
|
||||
* Volumes are now initialized when the container is created.
|
||||
* `POST /containers/(id)/copy` copies data which is contained in a volume.
|
||||
|
||||
`POST /containers/create`
|
||||
### v1.15 API changes
|
||||
|
||||
**New!**
|
||||
You can set the new container's MAC address explicitly.
|
||||
[Docker Remote API v1.15](/reference/api/docker_remote_api_v1.15/) documentation
|
||||
|
||||
**New!**
|
||||
Volumes are now initialized when the container is created.
|
||||
`POST /containers/create` you can set a container's `HostConfig` when creating a
|
||||
container. Previously this was only available when starting a container.
|
||||
|
||||
`POST /containers/(id)/copy`
|
||||
### v1.14 API changes
|
||||
|
||||
**New!**
|
||||
You can now copy data which is contained in a volume.
|
||||
[Docker Remote API v1.14](/reference/api/docker_remote_api_v1.14/) documentation
|
||||
|
||||
## v1.15
|
||||
|
||||
### Full documentation
|
||||
|
||||
[*Docker Remote API v1.15*](/reference/api/docker_remote_api_v1.15/)
|
||||
|
||||
### What's new
|
||||
|
||||
`POST /containers/create`
|
||||
|
||||
**New!**
|
||||
It is now possible to set a container's HostConfig when creating a container.
|
||||
Previously this was only available when starting a container.
|
||||
|
||||
## v1.14
|
||||
|
||||
### Full documentation
|
||||
|
||||
[*Docker Remote API v1.14*](/reference/api/docker_remote_api_v1.14/)
|
||||
|
||||
### What's new
|
||||
|
||||
`DELETE /containers/(id)`
|
||||
|
||||
**New!**
|
||||
When using `force`, the container will be immediately killed with SIGKILL.
|
||||
|
||||
`POST /containers/(id)/start`
|
||||
|
||||
**New!**
|
||||
The `hostConfig` option now accepts the field `CapAdd`, which specifies a list of capabilities
|
||||
* `DELETE /containers/(id)` when using `force`, the container will be immediately killed with SIGKILL.
|
||||
* `POST /containers/(id)/start` the `hostConfig` option accepts the field `CapAdd`, which specifies a list of capabilities
|
||||
to add, and the field `CapDrop`, which specifies a list of capabilities to drop.
|
||||
|
||||
`POST /images/create`
|
||||
|
||||
**New!**
|
||||
The `fromImage` and `repo` parameters now supports the `repo:tag` format.
|
||||
Consequently, the `tag` parameter is now obsolete. Using the new format and
|
||||
the `tag` parameter at the same time will return an error.
|
||||
* `POST /images/create` th `fromImage` and `repo` parameters supportthe
|
||||
`repo:tag` format. Consequently, the `tag` parameter is now obsolete. Using the
|
||||
new format and the `tag` parameter at the same time will return an error.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2245,6 +2245,126 @@ Status Codes:
|
|||
- **404** – no such exec instance
|
||||
- **500** - server error
|
||||
|
||||
## 2.4 Volumes
|
||||
|
||||
### List volumes
|
||||
|
||||
`GET /volumes`
|
||||
|
||||
**Example request**:
|
||||
|
||||
GET /volumes HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Volumes": [
|
||||
{
|
||||
"Name": "tardis",
|
||||
"Driver": "local",
|
||||
"Mountpoint": "/var/lib/docker/volumes/tardis"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Query Parameters:
|
||||
|
||||
- **filter** - JSON encoded value of the filters (a `map[string][]string`) to process on the volumes list. There is one available filter: `dangling=true`
|
||||
|
||||
Status Codes:
|
||||
|
||||
- **200** - no error
|
||||
- **500** - server error
|
||||
|
||||
### Create a volume
|
||||
|
||||
`POST /volumes`
|
||||
|
||||
Create a volume
|
||||
|
||||
**Example request**:
|
||||
|
||||
POST /volumes HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Name": "tardis"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Name": "tardis"
|
||||
"Driver": "local",
|
||||
"Mountpoint": "/var/lib/docker/volumes/tardis"
|
||||
}
|
||||
|
||||
Status Codes:
|
||||
|
||||
- **201** - no error
|
||||
- **500** - server error
|
||||
|
||||
JSON Parameters:
|
||||
|
||||
- **Name** - The new volume's name. If not specified, Docker generates a name.
|
||||
- **Driver** - Name of the volume driver to use. Defaults to `local` for the name.
|
||||
- **DriverOpts** - A mapping of driver options and values. These options are
|
||||
passed directly to the driver and are driver specific.
|
||||
|
||||
### Inspect a volume
|
||||
|
||||
`GET /volumes/(name)`
|
||||
|
||||
Return low-level information on the volume `name`
|
||||
|
||||
**Example request**:
|
||||
|
||||
GET /volumes/tardis
|
||||
|
||||
**Example response**:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Name": "tardis",
|
||||
"Driver": "local",
|
||||
"Mountpoint": "/var/lib/docker/volumes/tardis"
|
||||
}
|
||||
|
||||
Status Codes:
|
||||
|
||||
- **200** - no error
|
||||
- **404** - no such volume
|
||||
- **500** - server error
|
||||
|
||||
### Remove a volume
|
||||
|
||||
`DELETE /volumes/(name)`
|
||||
|
||||
Instruct the driver to remove the volume (`name`).
|
||||
|
||||
**Example request**:
|
||||
|
||||
DELETE /volumes/local/tardis HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
|
||||
Status Codes
|
||||
|
||||
- **204** - no error
|
||||
- **404** - no such volume or volume driver
|
||||
- **409** - volume is in use and cannot be removed
|
||||
- **500** - server error
|
||||
|
||||
# 3. Going further
|
||||
|
||||
## 3.1 Inside `docker run`
|
||||
|
|
|
@ -40,7 +40,7 @@ Running `docker ps --no-trunc` showing 2 linked containers.
|
|||
|
||||
## Filtering
|
||||
|
||||
The filtering flag (`-f` or `--filter)` format is a `key=value` pair. If there is more
|
||||
The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more
|
||||
than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`)
|
||||
|
||||
The currently supported filters are:
|
||||
|
|
42
docs/reference/commandline/volume_create.md
Normal file
42
docs/reference/commandline/volume_create.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
title = "volume create"
|
||||
description = "The volume create command description and usage"
|
||||
keywords = ["volume, create"]
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# volume create
|
||||
|
||||
Usage: docker volume create [OPTIONS]
|
||||
|
||||
Create a volume
|
||||
|
||||
-d, --driver=local Specify volume driver name
|
||||
--help=false Print usage
|
||||
--name= Specify volume name
|
||||
-o, --opt=map[] Set driver specific options
|
||||
|
||||
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:
|
||||
|
||||
$ docker volume create --name hello
|
||||
hello
|
||||
$ docker run -d -v hello:/world busybox ls /world
|
||||
|
||||
The mount is created inside the container's `/src` directory. Docker does not support relative paths for mount points inside the container.
|
||||
|
||||
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.
|
||||
|
||||
## Driver specific options
|
||||
|
||||
Some volume drivers may take options to customize the volume creation. Use the `-o` or `--opt` flags to pass driver options:
|
||||
|
||||
$ docker volume create --driver fake --opt tardis=blue --opt timey=wimey
|
||||
|
||||
These options are passed directly to the volume driver. Options for
|
||||
different volume drivers may do different things (or nothing at all).
|
||||
|
||||
*Note*: The built-in `local` volume driver does not currently accept any options.
|
||||
|
39
docs/reference/commandline/volume_inspect.md
Normal file
39
docs/reference/commandline/volume_inspect.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
title = "volume inspect"
|
||||
description = "The volume inspect command description and usage"
|
||||
keywords = ["volume, inspect"]
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# volume inspect
|
||||
|
||||
Usage: docker volume inspect [OPTIONS] [VOLUME NAME]
|
||||
|
||||
Inspect a volume
|
||||
|
||||
-f, --format= Format the output using the given go template.
|
||||
|
||||
Returns information about a volume. By default, this command renders all results
|
||||
in a JSON array. You can specify an alternate format to execute a given template
|
||||
is executed for each result. Go's
|
||||
[text/template](http://golang.org/pkg/text/template/) package describes all the
|
||||
details of the format.
|
||||
|
||||
Example output:
|
||||
|
||||
$ docker volume create
|
||||
85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d
|
||||
$ docker volume inspect 85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d
|
||||
[
|
||||
{
|
||||
"Name": "85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d",
|
||||
"Driver": "local",
|
||||
"Mountpoint": "/var/lib/docker/volumes/85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d/_data"
|
||||
}
|
||||
]
|
||||
|
||||
$ docker volume inspect --format '{{ .Mountpoint }}' 85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d
|
||||
"/var/lib/docker/volumes/85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d/_data"
|
34
docs/reference/commandline/volume_ls.md
Normal file
34
docs/reference/commandline/volume_ls.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
title = "volume ls"
|
||||
description = "The volume ls command description and usage"
|
||||
keywords = ["volume, list"]
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# volume ls
|
||||
|
||||
Usage: docker volume ls [OPTIONS]
|
||||
|
||||
List volumes
|
||||
|
||||
-f, --filter=[] Provide filter values (i.e. 'dangling=true')
|
||||
--help=false Print usage
|
||||
-q, --quiet=false Only display volume names
|
||||
|
||||
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"`)
|
||||
|
||||
There is a single supported filter `dangling=value` which takes a boolean of `true` or `false`.
|
||||
|
||||
Example output:
|
||||
|
||||
$ docker volume create --name rose
|
||||
rose
|
||||
$docker volume create --name tyler
|
||||
tyler
|
||||
$ docker volume ls
|
||||
DRIVER VOLUME NAME
|
||||
local rose
|
||||
local tyler
|
22
docs/reference/commandline/volume_rm.md
Normal file
22
docs/reference/commandline/volume_rm.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
title = "ps"
|
||||
description = "the volume rm command description and usage"
|
||||
keywords = ["volume, rm"]
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# volume rm
|
||||
|
||||
Usage: docker volume rm [OPTIONS] [VOLUME NAME]
|
||||
|
||||
Remove a volume
|
||||
|
||||
--help=false Print usage
|
||||
|
||||
Removes a volume. You cannot remove a volume that is in use by a container.
|
||||
|
||||
$ docker volume rm hello
|
||||
hello
|
|
@ -20,6 +20,7 @@ type DockerSuite struct {
|
|||
func (s *DockerSuite) TearDownTest(c *check.C) {
|
||||
deleteAllContainers()
|
||||
deleteAllImages()
|
||||
deleteAllVolumes()
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
85
integration-cli/docker_api_volumes_test.go
Normal file
85
integration-cli/docker_api_volumes_test.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
func (s *DockerSuite) TestVolumesApiList(c *check.C) {
|
||||
dockerCmd(c, "run", "-d", "-v", "/foo", "busybox")
|
||||
|
||||
status, b, err := sockRequest("GET", "/volumes", nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(status, check.Equals, http.StatusOK)
|
||||
|
||||
var volumes types.VolumesListResponse
|
||||
c.Assert(json.Unmarshal(b, &volumes), check.IsNil)
|
||||
|
||||
c.Assert(len(volumes.Volumes), check.Equals, 1, check.Commentf("\n%v", volumes.Volumes))
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestVolumesApiCreate(c *check.C) {
|
||||
config := types.VolumeCreateRequest{
|
||||
Name: "test",
|
||||
}
|
||||
status, b, err := sockRequest("POST", "/volumes", config)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(status, check.Equals, http.StatusCreated, check.Commentf(string(b)))
|
||||
|
||||
var vol types.Volume
|
||||
err = json.Unmarshal(b, &vol)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(path.Base(path.Dir(vol.Mountpoint)), check.Equals, config.Name)
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestVolumesApiRemove(c *check.C) {
|
||||
dockerCmd(c, "run", "-d", "-v", "/foo", "--name=test", "busybox")
|
||||
|
||||
status, b, err := sockRequest("GET", "/volumes", nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(status, check.Equals, http.StatusOK)
|
||||
|
||||
var volumes types.VolumesListResponse
|
||||
c.Assert(json.Unmarshal(b, &volumes), check.IsNil)
|
||||
c.Assert(len(volumes.Volumes), check.Equals, 1, check.Commentf("\n%v", volumes.Volumes))
|
||||
|
||||
v := volumes.Volumes[0]
|
||||
status, _, err = sockRequest("DELETE", "/volumes/"+v.Name, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(status, check.Equals, http.StatusConflict, check.Commentf("Should not be able to remove a volume that is in use"))
|
||||
|
||||
dockerCmd(c, "rm", "-f", "test")
|
||||
status, data, err := sockRequest("DELETE", "/volumes/"+v.Name, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(status, check.Equals, http.StatusNoContent, check.Commentf(string(data)))
|
||||
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestVolumesApiInspect(c *check.C) {
|
||||
config := types.VolumeCreateRequest{
|
||||
Name: "test",
|
||||
}
|
||||
status, b, err := sockRequest("POST", "/volumes", config)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(status, check.Equals, http.StatusCreated, check.Commentf(string(b)))
|
||||
|
||||
status, b, err = sockRequest("GET", "/volumes", nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(status, check.Equals, http.StatusOK, check.Commentf(string(b)))
|
||||
|
||||
var volumes types.VolumesListResponse
|
||||
c.Assert(json.Unmarshal(b, &volumes), check.IsNil)
|
||||
c.Assert(len(volumes.Volumes), check.Equals, 1, check.Commentf("\n%v", volumes.Volumes))
|
||||
|
||||
var vol types.Volume
|
||||
status, b, err = sockRequest("GET", "/volumes/"+config.Name, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(status, check.Equals, http.StatusOK, check.Commentf(string(b)))
|
||||
c.Assert(json.Unmarshal(b, &vol), check.IsNil)
|
||||
c.Assert(vol.Name, check.Equals, config.Name)
|
||||
}
|
|
@ -71,6 +71,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) {
|
|||
if err := s.d.Restart(); err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := s.d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
|
@ -1660,5 +1661,28 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPausedContainer(c *check.C) {
|
|||
c.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *DockerDaemonSuite) TestDaemonRestartRmVolumeInUse(c *check.C) {
|
||||
c.Assert(s.d.StartWithBusybox(), check.IsNil)
|
||||
|
||||
out, err := s.d.Cmd("create", "-v", "test:/foo", "busybox")
|
||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
||||
|
||||
c.Assert(s.d.Restart(), check.IsNil)
|
||||
|
||||
out, err = s.d.Cmd("volume", "rm", "test")
|
||||
c.Assert(err, check.Not(check.IsNil), check.Commentf("should not be able to remove in use volume after daemon restart"))
|
||||
c.Assert(strings.Contains(out, "in use"), check.Equals, true)
|
||||
}
|
||||
|
||||
func (s *DockerDaemonSuite) TestDaemonRestartLocalVolumes(c *check.C) {
|
||||
c.Assert(s.d.Start(), check.IsNil)
|
||||
|
||||
_, err := s.d.Cmd("volume", "create", "--name", "test")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(s.d.Restart(), check.IsNil)
|
||||
|
||||
_, err = s.d.Cmd("volume", "inspect", "test")
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
|
|
@ -238,7 +238,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
|||
|
||||
}
|
||||
|
||||
expected := 39
|
||||
expected := 40
|
||||
if isLocalDaemon {
|
||||
expected++ // for the daemon command
|
||||
}
|
||||
|
|
|
@ -2781,3 +2781,13 @@ func (s *DockerSuite) TestRunCreateContainerFailedCleanUp(c *check.C) {
|
|||
containerID, err := inspectField(name, "Id")
|
||||
c.Assert(containerID, check.Equals, "", check.Commentf("Expected not to have this container: %s!", containerID))
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestRunNamedVolume(c *check.C) {
|
||||
dockerCmd(c, "run", "--name=test", "-v", "testing:/foo", "busybox", "sh", "-c", "echo hello > /foo/bar")
|
||||
|
||||
out, _ := dockerCmd(c, "run", "--volumes-from", "test", "busybox", "sh", "-c", "cat /foo/bar")
|
||||
c.Assert(strings.TrimSpace(out), check.Equals, "hello")
|
||||
|
||||
out, _ = dockerCmd(c, "run", "-v", "testing:/foo", "busybox", "sh", "-c", "cat /foo/bar")
|
||||
c.Assert(strings.TrimSpace(out), check.Equals, "hello")
|
||||
}
|
||||
|
|
|
@ -119,11 +119,6 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
|||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
|
||||
p := hostVolumePath(pr.name)
|
||||
if err := os.RemoveAll(p); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
fmt.Fprintln(w, `{}`)
|
||||
})
|
||||
|
@ -152,7 +147,7 @@ func (s *DockerExternalVolumeSuite) TestStartExternalNamedVolumeDriver(c *check.
|
|||
|
||||
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")
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
c.Fatal(out, err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out, s.server.URL) {
|
||||
|
@ -202,20 +197,17 @@ func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverVolumesFrom(c *c
|
|||
c.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
out, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest")
|
||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
||||
|
||||
if _, err := s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp"); err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
out, err = s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp")
|
||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
||||
|
||||
if _, err := s.d.Cmd("rm", "-f", "vol-test1"); err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
out, err = s.d.Cmd("rm", "-fv", "vol-test1")
|
||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
||||
|
||||
c.Assert(s.ec.activations, check.Equals, 1)
|
||||
c.Assert(s.ec.creations, check.Equals, 2)
|
||||
c.Assert(s.ec.creations, check.Equals, 1)
|
||||
c.Assert(s.ec.removals, check.Equals, 1)
|
||||
c.Assert(s.ec.mounts, check.Equals, 2)
|
||||
c.Assert(s.ec.unmounts, check.Equals, 2)
|
||||
|
@ -226,12 +218,12 @@ func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverDeleteContainer(
|
|||
c.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
|
||||
c.Fatal(err)
|
||||
if out, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
|
||||
c.Fatal(out, err)
|
||||
}
|
||||
|
||||
if _, err := s.d.Cmd("rm", "-fv", "vol-test1"); err != nil {
|
||||
c.Fatal(err)
|
||||
if out, err := s.d.Cmd("rm", "-fv", "vol-test1"); err != nil {
|
||||
c.Fatal(out, err)
|
||||
}
|
||||
|
||||
c.Assert(s.ec.activations, check.Equals, 1)
|
||||
|
|
90
integration-cli/docker_cli_volume_test.go
Normal file
90
integration-cli/docker_cli_volume_test.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
func (s *DockerSuite) TestVolumeCliCreate(c *check.C) {
|
||||
dockerCmd(c, "volume", "create")
|
||||
|
||||
_, err := runCommand(exec.Command(dockerBinary, "volume", "create", "-d", "nosuchdriver"))
|
||||
c.Assert(err, check.Not(check.IsNil))
|
||||
|
||||
out, _ := dockerCmd(c, "volume", "create", "--name=test")
|
||||
name := strings.TrimSpace(out)
|
||||
c.Assert(name, check.Equals, "test")
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestVolumeCliInspect(c *check.C) {
|
||||
c.Assert(
|
||||
exec.Command(dockerBinary, "volume", "inspect", "doesntexist").Run(),
|
||||
check.Not(check.IsNil),
|
||||
check.Commentf("volume inspect should error on non-existant volume"),
|
||||
)
|
||||
|
||||
out, _ := dockerCmd(c, "volume", "create")
|
||||
name := strings.TrimSpace(out)
|
||||
out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Name }}'", name)
|
||||
c.Assert(strings.TrimSpace(out), check.Equals, name)
|
||||
|
||||
dockerCmd(c, "volume", "create", "--name", "test")
|
||||
out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Name }}'", "test")
|
||||
c.Assert(strings.TrimSpace(out), check.Equals, "test")
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestVolumeCliLs(c *check.C) {
|
||||
out, _ := dockerCmd(c, "volume", "create")
|
||||
id := strings.TrimSpace(out)
|
||||
|
||||
dockerCmd(c, "volume", "create", "--name", "test")
|
||||
dockerCmd(c, "run", "-v", "/foo", "busybox", "ls", "/")
|
||||
|
||||
out, _ = dockerCmd(c, "volume", "ls")
|
||||
outArr := strings.Split(strings.TrimSpace(out), "\n")
|
||||
c.Assert(len(outArr), check.Equals, 4, check.Commentf("\n%s", out))
|
||||
|
||||
// Since there is no guarentee of ordering of volumes, we just make sure the names are in the output
|
||||
c.Assert(strings.Contains(out, id+"\n"), check.Equals, true)
|
||||
c.Assert(strings.Contains(out, "test\n"), check.Equals, true)
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestVolumeCliRm(c *check.C) {
|
||||
out, _ := dockerCmd(c, "volume", "create")
|
||||
id := strings.TrimSpace(out)
|
||||
|
||||
dockerCmd(c, "volume", "create", "--name", "test")
|
||||
dockerCmd(c, "volume", "rm", id)
|
||||
dockerCmd(c, "volume", "rm", "test")
|
||||
|
||||
out, _ = dockerCmd(c, "volume", "ls")
|
||||
outArr := strings.Split(strings.TrimSpace(out), "\n")
|
||||
c.Assert(len(outArr), check.Equals, 1, check.Commentf("%s\n", out))
|
||||
|
||||
volumeID := "testing"
|
||||
dockerCmd(c, "run", "-v", volumeID+":/foo", "--name=test", "busybox", "sh", "-c", "echo hello > /foo/bar")
|
||||
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "volume", "rm", "testing"))
|
||||
c.Assert(
|
||||
err,
|
||||
check.Not(check.IsNil),
|
||||
check.Commentf("Should not be able to remove volume that is in use by a container\n%s", out))
|
||||
|
||||
out, _ = dockerCmd(c, "run", "--volumes-from=test", "--name=test2", "busybox", "sh", "-c", "cat /foo/bar")
|
||||
c.Assert(strings.TrimSpace(out), check.Equals, "hello")
|
||||
dockerCmd(c, "rm", "-fv", "test2")
|
||||
dockerCmd(c, "volume", "inspect", volumeID)
|
||||
dockerCmd(c, "rm", "-f", "test")
|
||||
|
||||
out, _ = dockerCmd(c, "run", "--name=test2", "-v", volumeID+":/foo", "busybox", "sh", "-c", "cat /foo/bar")
|
||||
c.Assert(strings.TrimSpace(out), check.Equals, "hello", check.Commentf("volume data was removed"))
|
||||
dockerCmd(c, "rm", "test2")
|
||||
|
||||
dockerCmd(c, "volume", "rm", volumeID)
|
||||
c.Assert(
|
||||
exec.Command("volume", "rm", "doesntexist").Run(),
|
||||
check.Not(check.IsNil),
|
||||
check.Commentf("volume rm should fail with non-existant volume"),
|
||||
)
|
||||
}
|
|
@ -445,6 +445,40 @@ func deleteAllContainers() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func deleteAllVolumes() error {
|
||||
volumes, err := getAllVolumes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errors []string
|
||||
for _, v := range volumes {
|
||||
status, b, err := sockRequest("DELETE", "/volumes/"+v.Name, nil)
|
||||
if err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
continue
|
||||
}
|
||||
if status != http.StatusNoContent {
|
||||
errors = append(errors, fmt.Sprintf("error deleting volume %s: %s", v.Name, string(b)))
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf(strings.Join(errors, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAllVolumes() ([]*types.Volume, error) {
|
||||
var volumes types.VolumesListResponse
|
||||
_, b, err := sockRequest("GET", "/volumes", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(b, &volumes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumes.Volumes, nil
|
||||
}
|
||||
|
||||
var protectedImages = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
|
|
51
man/docker-volume-create.1.md
Normal file
51
man/docker-volume-create.1.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
% DOCKER(1) Docker User Manuals
|
||||
% Docker Community
|
||||
% JULY 2015
|
||||
# NAME
|
||||
docker-volume-create - Create a new volume
|
||||
|
||||
# SYNOPSIS
|
||||
**docker volume create**
|
||||
[**-d**|**--driver**[=*local*]]
|
||||
[**--name**[=**]]
|
||||
[**-o**|**--opt**[=**]]
|
||||
|
||||
[OPTIONS]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
$ docker volume create --name hello
|
||||
hello
|
||||
$ docker run -d -v hello:/world busybox ls /world
|
||||
```
|
||||
|
||||
The mount is created inside the container's `/src` directory. Docker doesn't not support relative paths for mount points inside the container.
|
||||
|
||||
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.
|
||||
|
||||
## Driver specific options
|
||||
|
||||
Some volume drivers may take options to customize the volume creation. Use the `-o` or `--opt` flags to pass driver options:
|
||||
|
||||
```
|
||||
$ docker volume create --driver fake --opt tardis=blue --opt timey=wimey
|
||||
```
|
||||
|
||||
These options are passed directly to the volume driver. Options for
|
||||
different volume drivers may do different things (or nothing at all).
|
||||
|
||||
*Note*: The built-in `local` volume driver does not currently accept any options.
|
||||
|
||||
# OPTIONS
|
||||
**-d**, **--driver**=[]
|
||||
Specify volume driver name
|
||||
**--name**=""
|
||||
Specify volume name
|
||||
**-o**, **--opt**=map[]
|
||||
Set driver specific options
|
||||
|
||||
# HISTORY
|
||||
July 2015, created by Brian Goff <cpuguy83@gmail.com>
|
26
man/docker-volume-inspect.1.md
Normal file
26
man/docker-volume-inspect.1.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
% DOCKER(1) Docker User Manuals
|
||||
% Docker Community
|
||||
% JULY 2015
|
||||
# NAME
|
||||
docker-volume-inspect - Get low-level information about a volume
|
||||
|
||||
# SYNOPSIS
|
||||
**docker volume inspect**
|
||||
[**-f**|**--format**[=**]]
|
||||
|
||||
[OPTIONS] [VOLUME NAME]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Returns information about a volume. By default, this command renders all results
|
||||
in a JSON array. You can specify an alternate format to execute a given template
|
||||
is executed for each result. Go's
|
||||
http://golang.org/pkg/text/template/ package describes all the details of the
|
||||
format.
|
||||
|
||||
# OPTIONS
|
||||
**-f**, **--format**=""
|
||||
Format the output using the given go template.
|
||||
|
||||
# HISTORY
|
||||
July 2015, created by Brian Goff <cpuguy83@gmail.com>
|
27
man/docker-volume-ls.1.md
Normal file
27
man/docker-volume-ls.1.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
% DOCKER(1) Docker User Manuals
|
||||
% Docker Community
|
||||
% JULY 2015
|
||||
# NAME
|
||||
docker-volume-ls - List all volumes
|
||||
|
||||
# SYNOPSIS
|
||||
**docker volume ls**
|
||||
[**-f**|**--filter**[=**]]
|
||||
[**-q**|**--quiet**[=**]]
|
||||
|
||||
[OPTIONS]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
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"`)
|
||||
|
||||
There is a single supported filter `dangling=value` which takes a boolean of `true` or `false`.
|
||||
|
||||
# OPTIONS
|
||||
**-f**, **--filter**=""
|
||||
Provide filter values (i.e. 'dangling=true')
|
||||
**-q**, **--quiet**=false
|
||||
Only display volume names
|
||||
|
||||
# HISTORY
|
||||
July 2015, created by Brian Goff <cpuguy83@gmail.com>
|
24
man/docker-volume-rm.1.md
Normal file
24
man/docker-volume-rm.1.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
% DOCKER(1) Docker User Manuals
|
||||
% Docker Community
|
||||
% JULY 2015
|
||||
# NAME
|
||||
docker-volume-rm - Remove a volume
|
||||
|
||||
# SYNOPSIS
|
||||
**docker volume rm**
|
||||
|
||||
[OPTIONS] [VOLUME NAME]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Removes a volume. You cannot remove a volume that is in use by a container.
|
||||
|
||||
```
|
||||
$ docker volume rm hello
|
||||
hello
|
||||
```
|
||||
|
||||
# OPTIONS
|
||||
|
||||
# HISTORY
|
||||
July 2015, created by Brian Goff <cpuguy83@gmail.com>
|
|
@ -131,6 +131,10 @@ func (opts *MapOpts) Set(value string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (opts *MapOpts) GetAll() map[string]string {
|
||||
return opts.values
|
||||
}
|
||||
|
||||
func (opts *MapOpts) String() string {
|
||||
return fmt.Sprintf("%v", map[string]string((opts.values)))
|
||||
}
|
||||
|
|
|
@ -15,10 +15,19 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
versionMimetype = "application/vnd.docker.plugins.v1+json"
|
||||
versionMimetype = "application/vnd.docker.plugins.v1.1+json"
|
||||
defaultTimeOut = 30
|
||||
)
|
||||
|
||||
type remoteError struct {
|
||||
method string
|
||||
err string
|
||||
}
|
||||
|
||||
func (e *remoteError) Error() string {
|
||||
return fmt.Sprintf("Plugin Error: %s, %s", e.err, e.method)
|
||||
}
|
||||
|
||||
// NewClient creates a new plugin client (http).
|
||||
func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
|
||||
tr := &http.Transport{}
|
||||
|
@ -84,9 +93,9 @@ func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret inter
|
|||
if resp.StatusCode != http.StatusOK {
|
||||
remoteErr, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Plugin Error: %s", err)
|
||||
return &remoteError{err.Error(), serviceMethod}
|
||||
}
|
||||
return fmt.Errorf("Plugin Error: %s", remoteErr)
|
||||
return &remoteError{string(remoteErr), serviceMethod}
|
||||
}
|
||||
|
||||
return json.NewDecoder(resp.Body).Decode(&ret)
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrBadReturn = errors.New("found return arg with no name: all args must be named")
|
||||
|
@ -39,7 +38,7 @@ type arg struct {
|
|||
}
|
||||
|
||||
func (a *arg) String() string {
|
||||
return strings.ToLower(a.Name) + " " + strings.ToLower(a.ArgType)
|
||||
return a.Name + " " + a.ArgType
|
||||
}
|
||||
|
||||
// Parses the given file for an interface definition with the given name
|
||||
|
|
|
@ -11,8 +11,8 @@ func (a *volumeDriverAdapter) Name() string {
|
|||
return a.name
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Create(name string) (volume.Volume, error) {
|
||||
err := a.proxy.Create(name)
|
||||
func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volume.Volume, error) {
|
||||
err := a.proxy.Create(name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -33,6 +33,11 @@ type volumeAdapter struct {
|
|||
eMount string // ephemeral host volume path
|
||||
}
|
||||
|
||||
type proxyVolume struct {
|
||||
Name string
|
||||
Mountpoint string
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) Name() string {
|
||||
return a.name
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type VolumeDriver -name VolumeDriver
|
||||
|
||||
package volumedrivers
|
||||
|
||||
import "github.com/docker/docker/volume"
|
||||
|
||||
// NewVolumeDriver returns a driver has the given name mapped on the given client.
|
||||
func NewVolumeDriver(name string, c client) volume.Driver {
|
||||
proxy := &volumeDriverProxy{c}
|
||||
return &volumeDriverAdapter{name, proxy}
|
||||
}
|
||||
|
||||
// VolumeDriver defines the available functions that volume plugins must implement.
|
||||
type VolumeDriver interface {
|
||||
// Create a volume with the given name
|
||||
Create(name string) (err error)
|
||||
// Remove the volume with the given name
|
||||
Remove(name string) (err error)
|
||||
// Get the mountpoint of the given volume
|
||||
Path(name string) (mountpoint string, err error)
|
||||
// Mount the given volume and return the mountpoint
|
||||
Mount(name string) (mountpoint string, err error)
|
||||
// Unmount the given volume
|
||||
Unmount(name string) (err error)
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type VolumeDriver -name VolumeDriver
|
||||
|
||||
package volumedrivers
|
||||
|
||||
import (
|
||||
|
@ -13,6 +15,28 @@ import (
|
|||
|
||||
var drivers = &driverExtpoint{extensions: make(map[string]volume.Driver)}
|
||||
|
||||
// NewVolumeDriver returns a driver has the given name mapped on the given client.
|
||||
func NewVolumeDriver(name string, c client) volume.Driver {
|
||||
proxy := &volumeDriverProxy{c}
|
||||
return &volumeDriverAdapter{name, proxy}
|
||||
}
|
||||
|
||||
type opts map[string]string
|
||||
|
||||
// VolumeDriver defines the available functions that volume plugins must implement.
|
||||
type VolumeDriver interface {
|
||||
// Create a volume with the given name
|
||||
Create(name string, opts opts) (err error)
|
||||
// Remove the volume with the given name
|
||||
Remove(name string) (err error)
|
||||
// Get the mountpoint of the given volume
|
||||
Path(name string) (mountpoint string, err error)
|
||||
// Mount the given volume and return the mountpoint
|
||||
Mount(name string) (mountpoint string, err error)
|
||||
// Unmount the given volume
|
||||
Unmount(name string) (err error)
|
||||
}
|
||||
|
||||
type driverExtpoint struct {
|
||||
extensions map[string]volume.Driver
|
||||
sync.Mutex
|
||||
|
|
|
@ -14,19 +14,21 @@ type volumeDriverProxy struct {
|
|||
|
||||
type volumeDriverProxyCreateRequest struct {
|
||||
Name string
|
||||
Opts opts
|
||||
}
|
||||
|
||||
type volumeDriverProxyCreateResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Create(name string) (err error) {
|
||||
func (pp *volumeDriverProxy) Create(name string, opts opts) (err error) {
|
||||
var (
|
||||
req volumeDriverProxyCreateRequest
|
||||
ret volumeDriverProxyCreateResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
req.Opts = opts
|
||||
if err = pp.Call("VolumeDriver.Create", req, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func TestVolumeRequestError(t *testing.T) {
|
|||
|
||||
driver := volumeDriverProxy{client}
|
||||
|
||||
if err = driver.Create("volume"); err == nil {
|
||||
if err = driver.Create("volume", nil); err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,11 @@ const (
|
|||
volumesPathName = "volumes"
|
||||
)
|
||||
|
||||
var oldVfsDir = filepath.Join("vfs", "dir")
|
||||
var (
|
||||
// ErrNotFound is the typed error returned when the requested volume name can't be found
|
||||
ErrNotFound = errors.New("volume not found")
|
||||
oldVfsDir = filepath.Join("vfs", "dir")
|
||||
)
|
||||
|
||||
// New instantiates a new Root instance with the provided scope. Scope
|
||||
// is the base path that the Root instance uses to store its
|
||||
|
@ -54,6 +58,7 @@ func New(scope string) (*Root, error) {
|
|||
path: r.DataPath(name),
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
|
@ -67,6 +72,15 @@ type Root struct {
|
|||
volumes map[string]*localVolume
|
||||
}
|
||||
|
||||
// List lists all the volumes
|
||||
func (r *Root) List() []volume.Volume {
|
||||
var ls []volume.Volume
|
||||
for _, v := range r.volumes {
|
||||
ls = append(ls, v)
|
||||
}
|
||||
return ls
|
||||
}
|
||||
|
||||
// DataPath returns the constructed path of this volume.
|
||||
func (r *Root) DataPath(volumeName string) string {
|
||||
return filepath.Join(r.path, volumeName, VolumeDataPathName)
|
||||
|
@ -80,27 +94,28 @@ func (r *Root) Name() string {
|
|||
// Create creates a new volume.Volume with the provided name, creating
|
||||
// the underlying directory tree required for this volume in the
|
||||
// process.
|
||||
func (r *Root) Create(name string) (volume.Volume, error) {
|
||||
func (r *Root) Create(name string, _ map[string]string) (volume.Volume, error) {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
v, exists := r.volumes[name]
|
||||
if !exists {
|
||||
path := r.DataPath(name)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
v = &localVolume{
|
||||
driverName: r.Name(),
|
||||
name: name,
|
||||
path: path,
|
||||
}
|
||||
r.volumes[name] = v
|
||||
if exists {
|
||||
return v, nil
|
||||
}
|
||||
v.use()
|
||||
|
||||
path := r.DataPath(name)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
v = &localVolume{
|
||||
driverName: r.Name(),
|
||||
name: name,
|
||||
path: path,
|
||||
}
|
||||
r.volumes[name] = v
|
||||
return v, nil
|
||||
}
|
||||
|
||||
|
@ -115,24 +130,32 @@ func (r *Root) Remove(v volume.Volume) error {
|
|||
if !ok {
|
||||
return errors.New("unknown volume type")
|
||||
}
|
||||
lv.release()
|
||||
if lv.usedCount == 0 {
|
||||
realPath, err := filepath.EvalSymlinks(lv.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.scopedPath(realPath) {
|
||||
return fmt.Errorf("Unable to remove a directory of out the Docker root: %s", realPath)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(realPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(r.volumes, lv.name)
|
||||
return os.RemoveAll(filepath.Dir(lv.path))
|
||||
realPath, err := filepath.EvalSymlinks(lv.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
if !r.scopedPath(realPath) {
|
||||
return fmt.Errorf("Unable to remove a directory of out the Docker root: %s", realPath)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(realPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(r.volumes, lv.name)
|
||||
return os.RemoveAll(filepath.Dir(lv.path))
|
||||
}
|
||||
|
||||
// Get looks up the volume for the given name and returns it if found
|
||||
func (r *Root) Get(name string) (volume.Volume, error) {
|
||||
r.m.Lock()
|
||||
v, exists := r.volumes[name]
|
||||
r.m.Unlock()
|
||||
if !exists {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// scopedPath verifies that the path where the volume is located
|
||||
|
@ -188,15 +211,3 @@ func (v *localVolume) Mount() (string, error) {
|
|||
func (v *localVolume) Unmount() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *localVolume) use() {
|
||||
v.m.Lock()
|
||||
v.usedCount++
|
||||
v.m.Unlock()
|
||||
}
|
||||
|
||||
func (v *localVolume) release() {
|
||||
v.m.Lock()
|
||||
v.usedCount--
|
||||
v.m.Unlock()
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ type Driver interface {
|
|||
// Name returns the name of the volume driver.
|
||||
Name() string
|
||||
// Create makes a new volume with the given id.
|
||||
Create(string) (Volume, error)
|
||||
Create(name string, opts map[string]string) (Volume, error)
|
||||
// Remove deletes the volume.
|
||||
Remove(Volume) error
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue