update vendor
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
parent
19a32db84d
commit
94d1cfbfbf
10501 changed files with 2307943 additions and 29279 deletions
176
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/adapter.go
generated
vendored
Normal file
176
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/adapter.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
package drivers // import "github.com/docker/docker/volume/drivers"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoSuchVolume = errors.New("no such volume")
|
||||
)
|
||||
|
||||
type volumeDriverAdapter struct {
|
||||
name string
|
||||
scopePath func(s string) string
|
||||
capabilities *volume.Capability
|
||||
proxy volumeDriver
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Name() string {
|
||||
return a.name
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volume.Volume, error) {
|
||||
if err := a.proxy.Create(name, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &volumeAdapter{
|
||||
proxy: a.proxy,
|
||||
name: name,
|
||||
driverName: a.name,
|
||||
scopePath: a.scopePath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
|
||||
return a.proxy.Remove(v.Name())
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
|
||||
ls, err := a.proxy.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out []volume.Volume
|
||||
for _, vp := range ls {
|
||||
out = append(out, &volumeAdapter{
|
||||
proxy: a.proxy,
|
||||
name: vp.Name,
|
||||
scopePath: a.scopePath,
|
||||
driverName: a.name,
|
||||
eMount: a.scopePath(vp.Mountpoint),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
|
||||
v, err := a.proxy.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// plugin may have returned no volume and no error
|
||||
if v == nil {
|
||||
return nil, errNoSuchVolume
|
||||
}
|
||||
|
||||
return &volumeAdapter{
|
||||
proxy: a.proxy,
|
||||
name: v.Name,
|
||||
driverName: a.Name(),
|
||||
eMount: v.Mountpoint,
|
||||
createdAt: v.CreatedAt,
|
||||
status: v.Status,
|
||||
scopePath: a.scopePath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Scope() string {
|
||||
cap := a.getCapabilities()
|
||||
return cap.Scope
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
|
||||
if a.capabilities != nil {
|
||||
return *a.capabilities
|
||||
}
|
||||
cap, err := a.proxy.Capabilities()
|
||||
if err != nil {
|
||||
// `GetCapabilities` is a not a required endpoint.
|
||||
// On error assume it's a local-only driver
|
||||
logrus.WithError(err).WithField("driver", a.name).Debug("Volume driver returned an error while trying to query its capabilities, using default capabilities")
|
||||
return volume.Capability{Scope: volume.LocalScope}
|
||||
}
|
||||
|
||||
// don't spam the warn log below just because the plugin didn't provide a scope
|
||||
if len(cap.Scope) == 0 {
|
||||
cap.Scope = volume.LocalScope
|
||||
}
|
||||
|
||||
cap.Scope = strings.ToLower(cap.Scope)
|
||||
if cap.Scope != volume.LocalScope && cap.Scope != volume.GlobalScope {
|
||||
logrus.WithField("driver", a.Name()).WithField("scope", a.Scope).Warn("Volume driver returned an invalid scope")
|
||||
cap.Scope = volume.LocalScope
|
||||
}
|
||||
|
||||
a.capabilities = &cap
|
||||
return cap
|
||||
}
|
||||
|
||||
type volumeAdapter struct {
|
||||
proxy volumeDriver
|
||||
name string
|
||||
scopePath func(string) string
|
||||
driverName string
|
||||
eMount string // ephemeral host volume path
|
||||
createdAt time.Time // time the directory was created
|
||||
status map[string]interface{}
|
||||
}
|
||||
|
||||
type proxyVolume struct {
|
||||
Name string
|
||||
Mountpoint string
|
||||
CreatedAt time.Time
|
||||
Status map[string]interface{}
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) Name() string {
|
||||
return a.name
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) DriverName() string {
|
||||
return a.driverName
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) Path() string {
|
||||
if len(a.eMount) == 0 {
|
||||
mountpoint, _ := a.proxy.Path(a.name)
|
||||
a.eMount = a.scopePath(mountpoint)
|
||||
}
|
||||
return a.eMount
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) CachedPath() string {
|
||||
return a.eMount
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) Mount(id string) (string, error) {
|
||||
mountpoint, err := a.proxy.Mount(a.name, id)
|
||||
a.eMount = a.scopePath(mountpoint)
|
||||
return a.eMount, err
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) Unmount(id string) error {
|
||||
err := a.proxy.Unmount(a.name, id)
|
||||
if err == nil {
|
||||
a.eMount = ""
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) CreatedAt() (time.Time, error) {
|
||||
return a.createdAt, nil
|
||||
}
|
||||
func (a *volumeAdapter) Status() map[string]interface{} {
|
||||
out := make(map[string]interface{}, len(a.status))
|
||||
for k, v := range a.status {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
235
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/extpoint.go
generated
vendored
Normal file
235
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/extpoint.go
generated
vendored
Normal file
|
@ -0,0 +1,235 @@
|
|||
//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type volumeDriver -name VolumeDriver
|
||||
|
||||
package drivers // import "github.com/docker/docker/volume/drivers"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/locker"
|
||||
getter "github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const extName = "VolumeDriver"
|
||||
|
||||
// volumeDriver defines the available functions that volume plugins must implement.
|
||||
// This interface is only defined to generate the proxy objects.
|
||||
// It's not intended to be public or reused.
|
||||
// nolint: deadcode
|
||||
type volumeDriver interface {
|
||||
// Create a volume with the given name
|
||||
Create(name string, opts map[string]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, id string) (mountpoint string, err error)
|
||||
// Unmount the given volume
|
||||
Unmount(name, id string) (err error)
|
||||
// List lists all the volumes known to the driver
|
||||
List() (volumes []*proxyVolume, err error)
|
||||
// Get retrieves the volume with the requested name
|
||||
Get(name string) (volume *proxyVolume, err error)
|
||||
// Capabilities gets the list of capabilities of the driver
|
||||
Capabilities() (capabilities volume.Capability, err error)
|
||||
}
|
||||
|
||||
// Store is an in-memory store for volume drivers
|
||||
type Store struct {
|
||||
extensions map[string]volume.Driver
|
||||
mu sync.Mutex
|
||||
driverLock *locker.Locker
|
||||
pluginGetter getter.PluginGetter
|
||||
}
|
||||
|
||||
// NewStore creates a new volume driver store
|
||||
func NewStore(pg getter.PluginGetter) *Store {
|
||||
return &Store{
|
||||
extensions: make(map[string]volume.Driver),
|
||||
driverLock: locker.New(),
|
||||
pluginGetter: pg,
|
||||
}
|
||||
}
|
||||
|
||||
type driverNotFoundError string
|
||||
|
||||
func (e driverNotFoundError) Error() string {
|
||||
return "volume driver not found: " + string(e)
|
||||
}
|
||||
|
||||
func (driverNotFoundError) NotFound() {}
|
||||
|
||||
// lookup returns the driver associated with the given name. If a
|
||||
// driver with the given name has not been registered it checks if
|
||||
// there is a VolumeDriver plugin available with the given name.
|
||||
func (s *Store) lookup(name string, mode int) (volume.Driver, error) {
|
||||
if name == "" {
|
||||
return nil, errdefs.InvalidParameter(errors.New("driver name cannot be empty"))
|
||||
}
|
||||
s.driverLock.Lock(name)
|
||||
defer s.driverLock.Unlock(name)
|
||||
|
||||
s.mu.Lock()
|
||||
ext, ok := s.extensions[name]
|
||||
s.mu.Unlock()
|
||||
if ok {
|
||||
return ext, nil
|
||||
}
|
||||
if s.pluginGetter != nil {
|
||||
p, err := s.pluginGetter.Get(name, extName, mode)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error looking up volume plugin "+name)
|
||||
}
|
||||
|
||||
d, err := makePluginAdapter(p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error making plugin client")
|
||||
}
|
||||
if err := validateDriver(d); err != nil {
|
||||
if mode > 0 {
|
||||
// Undo any reference count changes from the initial `Get`
|
||||
if _, err := s.pluginGetter.Get(name, extName, mode*-1); err != nil {
|
||||
logrus.WithError(err).WithField("action", "validate-driver").WithField("plugin", name).Error("error releasing reference to plugin")
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.IsV1() {
|
||||
s.mu.Lock()
|
||||
s.extensions[name] = d
|
||||
s.mu.Unlock()
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
return nil, driverNotFoundError(name)
|
||||
}
|
||||
|
||||
func validateDriver(vd volume.Driver) error {
|
||||
scope := vd.Scope()
|
||||
if scope != volume.LocalScope && scope != volume.GlobalScope {
|
||||
return fmt.Errorf("Driver %q provided an invalid capability scope: %s", vd.Name(), scope)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register associates the given driver to the given name, checking if
|
||||
// the name is already associated
|
||||
func (s *Store) Register(d volume.Driver, name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if _, exists := s.extensions[name]; exists {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := validateDriver(d); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
s.extensions[name] = d
|
||||
return true
|
||||
}
|
||||
|
||||
// GetDriver returns a volume driver by its name.
|
||||
// If the driver is empty, it looks for the local driver.
|
||||
func (s *Store) GetDriver(name string) (volume.Driver, error) {
|
||||
return s.lookup(name, getter.Lookup)
|
||||
}
|
||||
|
||||
// CreateDriver returns a volume driver by its name and increments RefCount.
|
||||
// If the driver is empty, it looks for the local driver.
|
||||
func (s *Store) CreateDriver(name string) (volume.Driver, error) {
|
||||
return s.lookup(name, getter.Acquire)
|
||||
}
|
||||
|
||||
// ReleaseDriver returns a volume driver by its name and decrements RefCount..
|
||||
// If the driver is empty, it looks for the local driver.
|
||||
func (s *Store) ReleaseDriver(name string) (volume.Driver, error) {
|
||||
return s.lookup(name, getter.Release)
|
||||
}
|
||||
|
||||
// GetDriverList returns list of volume drivers registered.
|
||||
// If no driver is registered, empty string list will be returned.
|
||||
func (s *Store) GetDriverList() []string {
|
||||
var driverList []string
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for driverName := range s.extensions {
|
||||
driverList = append(driverList, driverName)
|
||||
}
|
||||
sort.Strings(driverList)
|
||||
return driverList
|
||||
}
|
||||
|
||||
// GetAllDrivers lists all the registered drivers
|
||||
func (s *Store) GetAllDrivers() ([]volume.Driver, error) {
|
||||
var plugins []getter.CompatPlugin
|
||||
if s.pluginGetter != nil {
|
||||
var err error
|
||||
plugins, err = s.pluginGetter.GetAllByCap(extName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing plugins: %v", err)
|
||||
}
|
||||
}
|
||||
var ds []volume.Driver
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for _, d := range s.extensions {
|
||||
ds = append(ds, d)
|
||||
}
|
||||
|
||||
for _, p := range plugins {
|
||||
name := p.Name()
|
||||
|
||||
if _, ok := s.extensions[name]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ext, err := makePluginAdapter(p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error making plugin client")
|
||||
}
|
||||
if p.IsV1() {
|
||||
s.extensions[name] = ext
|
||||
}
|
||||
ds = append(ds, ext)
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
func makePluginAdapter(p getter.CompatPlugin) (*volumeDriverAdapter, error) {
|
||||
if pc, ok := p.(getter.PluginWithV1Client); ok {
|
||||
return &volumeDriverAdapter{name: p.Name(), scopePath: p.ScopedPath, proxy: &volumeDriverProxy{pc.Client()}}, nil
|
||||
}
|
||||
|
||||
pa, ok := p.(getter.PluginAddr)
|
||||
if !ok {
|
||||
return nil, errdefs.System(errors.Errorf("got unknown plugin instance %T", p))
|
||||
}
|
||||
|
||||
if pa.Protocol() != plugins.ProtocolSchemeHTTPV1 {
|
||||
return nil, errors.Errorf("plugin protocol not supported: %s", p)
|
||||
}
|
||||
|
||||
addr := pa.Addr()
|
||||
client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating plugin client")
|
||||
}
|
||||
|
||||
return &volumeDriverAdapter{name: p.Name(), scopePath: p.ScopedPath, proxy: &volumeDriverProxy{client}}, nil
|
||||
}
|
24
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/extpoint_test.go
generated
vendored
Normal file
24
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/extpoint_test.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
package drivers // import "github.com/docker/docker/volume/drivers"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
volumetestutils "github.com/docker/docker/volume/testutils"
|
||||
)
|
||||
|
||||
func TestGetDriver(t *testing.T) {
|
||||
s := NewStore(nil)
|
||||
_, err := s.GetDriver("missing")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
s.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
||||
|
||||
d, err := s.GetDriver("fake")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.Name() != "fake" {
|
||||
t.Fatalf("Expected fake driver, got %s\n", d.Name())
|
||||
}
|
||||
}
|
255
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/proxy.go
generated
vendored
Normal file
255
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/proxy.go
generated
vendored
Normal file
|
@ -0,0 +1,255 @@
|
|||
// generated code - DO NOT EDIT
|
||||
|
||||
package drivers // import "github.com/docker/docker/volume/drivers"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
const (
|
||||
longTimeout = 2 * time.Minute
|
||||
shortTimeout = 1 * time.Minute
|
||||
)
|
||||
|
||||
type client interface {
|
||||
CallWithOptions(string, interface{}, interface{}, ...func(*plugins.RequestOpts)) error
|
||||
}
|
||||
|
||||
type volumeDriverProxy struct {
|
||||
client
|
||||
}
|
||||
|
||||
type volumeDriverProxyCreateRequest struct {
|
||||
Name string
|
||||
Opts map[string]string
|
||||
}
|
||||
|
||||
type volumeDriverProxyCreateResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Create(name string, opts map[string]string) (err error) {
|
||||
var (
|
||||
req volumeDriverProxyCreateRequest
|
||||
ret volumeDriverProxyCreateResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
req.Opts = opts
|
||||
|
||||
if err = pp.CallWithOptions("VolumeDriver.Create", req, &ret, plugins.WithRequestTimeout(longTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyRemoveRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type volumeDriverProxyRemoveResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Remove(name string) (err error) {
|
||||
var (
|
||||
req volumeDriverProxyRemoveRequest
|
||||
ret volumeDriverProxyRemoveResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
|
||||
if err = pp.CallWithOptions("VolumeDriver.Remove", req, &ret, plugins.WithRequestTimeout(shortTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyPathRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type volumeDriverProxyPathResponse struct {
|
||||
Mountpoint string
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Path(name string) (mountpoint string, err error) {
|
||||
var (
|
||||
req volumeDriverProxyPathRequest
|
||||
ret volumeDriverProxyPathResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
|
||||
if err = pp.CallWithOptions("VolumeDriver.Path", req, &ret, plugins.WithRequestTimeout(shortTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mountpoint = ret.Mountpoint
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyMountRequest struct {
|
||||
Name string
|
||||
ID string
|
||||
}
|
||||
|
||||
type volumeDriverProxyMountResponse struct {
|
||||
Mountpoint string
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Mount(name string, id string) (mountpoint string, err error) {
|
||||
var (
|
||||
req volumeDriverProxyMountRequest
|
||||
ret volumeDriverProxyMountResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
req.ID = id
|
||||
|
||||
if err = pp.CallWithOptions("VolumeDriver.Mount", req, &ret, plugins.WithRequestTimeout(longTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mountpoint = ret.Mountpoint
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyUnmountRequest struct {
|
||||
Name string
|
||||
ID string
|
||||
}
|
||||
|
||||
type volumeDriverProxyUnmountResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Unmount(name string, id string) (err error) {
|
||||
var (
|
||||
req volumeDriverProxyUnmountRequest
|
||||
ret volumeDriverProxyUnmountResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
req.ID = id
|
||||
|
||||
if err = pp.CallWithOptions("VolumeDriver.Unmount", req, &ret, plugins.WithRequestTimeout(shortTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyListRequest struct {
|
||||
}
|
||||
|
||||
type volumeDriverProxyListResponse struct {
|
||||
Volumes []*proxyVolume
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) List() (volumes []*proxyVolume, err error) {
|
||||
var (
|
||||
req volumeDriverProxyListRequest
|
||||
ret volumeDriverProxyListResponse
|
||||
)
|
||||
|
||||
if err = pp.CallWithOptions("VolumeDriver.List", req, &ret, plugins.WithRequestTimeout(shortTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
volumes = ret.Volumes
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyGetRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type volumeDriverProxyGetResponse struct {
|
||||
Volume *proxyVolume
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Get(name string) (volume *proxyVolume, err error) {
|
||||
var (
|
||||
req volumeDriverProxyGetRequest
|
||||
ret volumeDriverProxyGetResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
|
||||
if err = pp.CallWithOptions("VolumeDriver.Get", req, &ret, plugins.WithRequestTimeout(shortTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
volume = ret.Volume
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyCapabilitiesRequest struct {
|
||||
}
|
||||
|
||||
type volumeDriverProxyCapabilitiesResponse struct {
|
||||
Capabilities volume.Capability
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Capabilities() (capabilities volume.Capability, err error) {
|
||||
var (
|
||||
req volumeDriverProxyCapabilitiesRequest
|
||||
ret volumeDriverProxyCapabilitiesResponse
|
||||
)
|
||||
|
||||
if err = pp.CallWithOptions("VolumeDriver.Capabilities", req, &ret, plugins.WithRequestTimeout(shortTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
capabilities = ret.Capabilities
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
132
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/proxy_test.go
generated
vendored
Normal file
132
vendor/github.com/docker/docker-ce/components/engine/volume/drivers/proxy_test.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
package drivers // import "github.com/docker/docker/volume/drivers"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
func TestVolumeRequestError(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
fmt.Fprintln(w, `{"Err": "Cannot create volume"}`)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
fmt.Fprintln(w, `{"Err": "Cannot remove volume"}`)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
fmt.Fprintln(w, `{"Err": "Cannot mount volume"}`)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
fmt.Fprintln(w, `{"Err": "Cannot unmount volume"}`)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
fmt.Fprintln(w, `{"Err": "Unknown volume"}`)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
fmt.Fprintln(w, `{"Err": "Cannot list volumes"}`)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
fmt.Fprintln(w, `{"Err": "Cannot get volume"}`)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Capabilities", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
||||
http.Error(w, "error", 500)
|
||||
})
|
||||
|
||||
u, _ := url.Parse(server.URL)
|
||||
client, err := plugins.NewClient("tcp://"+u.Host, &tlsconfig.Options{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
driver := volumeDriverProxy{client}
|
||||
|
||||
if err = driver.Create("volume", nil); err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "Cannot create volume") {
|
||||
t.Fatalf("Unexpected error: %v\n", err)
|
||||
}
|
||||
|
||||
_, err = driver.Mount("volume", "123")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "Cannot mount volume") {
|
||||
t.Fatalf("Unexpected error: %v\n", err)
|
||||
}
|
||||
|
||||
err = driver.Unmount("volume", "123")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "Cannot unmount volume") {
|
||||
t.Fatalf("Unexpected error: %v\n", err)
|
||||
}
|
||||
|
||||
err = driver.Remove("volume")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "Cannot remove volume") {
|
||||
t.Fatalf("Unexpected error: %v\n", err)
|
||||
}
|
||||
|
||||
_, err = driver.Path("volume")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "Unknown volume") {
|
||||
t.Fatalf("Unexpected error: %v\n", err)
|
||||
}
|
||||
|
||||
_, err = driver.List()
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Cannot list volumes") {
|
||||
t.Fatalf("Unexpected error: %v\n", err)
|
||||
}
|
||||
|
||||
_, err = driver.Get("volume")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Cannot get volume") {
|
||||
t.Fatalf("Unexpected error: %v\n", err)
|
||||
}
|
||||
|
||||
_, err = driver.Capabilities()
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
378
vendor/github.com/docker/docker-ce/components/engine/volume/local/local.go
generated
vendored
Normal file
378
vendor/github.com/docker/docker-ce/components/engine/volume/local/local.go
generated
vendored
Normal file
|
@ -0,0 +1,378 @@
|
|||
// Package local provides the default implementation for volumes. It
|
||||
// is used to mount data volume containers and directories local to
|
||||
// the host server.
|
||||
package local // import "github.com/docker/docker/volume/local"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/daemon/names"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// VolumeDataPathName is the name of the directory where the volume data is stored.
|
||||
// It uses a very distinctive name to avoid collisions migrating data between
|
||||
// Docker versions.
|
||||
const (
|
||||
VolumeDataPathName = "_data"
|
||||
volumesPathName = "volumes"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound is the typed error returned when the requested volume name can't be found
|
||||
ErrNotFound = fmt.Errorf("volume not found")
|
||||
// volumeNameRegex ensures the name assigned for the volume is valid.
|
||||
// This name is used to create the bind directory, so we need to avoid characters that
|
||||
// would make the path to escape the root directory.
|
||||
volumeNameRegex = names.RestrictedNamePattern
|
||||
)
|
||||
|
||||
type activeMount struct {
|
||||
count uint64
|
||||
mounted bool
|
||||
}
|
||||
|
||||
// New instantiates a new Root instance with the provided scope. Scope
|
||||
// is the base path that the Root instance uses to store its
|
||||
// volumes. The base path is created here if it does not exist.
|
||||
func New(scope string, rootIdentity idtools.Identity) (*Root, error) {
|
||||
rootDirectory := filepath.Join(scope, volumesPathName)
|
||||
|
||||
if err := idtools.MkdirAllAndChown(rootDirectory, 0700, rootIdentity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &Root{
|
||||
scope: scope,
|
||||
path: rootDirectory,
|
||||
volumes: make(map[string]*localVolume),
|
||||
rootIdentity: rootIdentity,
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(rootDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range dirs {
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := filepath.Base(d.Name())
|
||||
v := &localVolume{
|
||||
driverName: r.Name(),
|
||||
name: name,
|
||||
path: r.DataPath(name),
|
||||
}
|
||||
r.volumes[name] = v
|
||||
optsFilePath := filepath.Join(rootDirectory, name, "opts.json")
|
||||
if b, err := ioutil.ReadFile(optsFilePath); err == nil {
|
||||
opts := optsConfig{}
|
||||
if err := json.Unmarshal(b, &opts); err != nil {
|
||||
return nil, errors.Wrapf(err, "error while unmarshaling volume options for volume: %s", name)
|
||||
}
|
||||
// Make sure this isn't an empty optsConfig.
|
||||
// This could be empty due to buggy behavior in older versions of Docker.
|
||||
if !reflect.DeepEqual(opts, optsConfig{}) {
|
||||
v.opts = &opts
|
||||
}
|
||||
|
||||
// unmount anything that may still be mounted (for example, from an unclean shutdown)
|
||||
mount.Unmount(v.path)
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Root implements the Driver interface for the volume package and
|
||||
// manages the creation/removal of volumes. It uses only standard vfs
|
||||
// commands to create/remove dirs within its provided scope.
|
||||
type Root struct {
|
||||
m sync.Mutex
|
||||
scope string
|
||||
path string
|
||||
volumes map[string]*localVolume
|
||||
rootIdentity idtools.Identity
|
||||
}
|
||||
|
||||
// List lists all the volumes
|
||||
func (r *Root) List() ([]volume.Volume, error) {
|
||||
var ls []volume.Volume
|
||||
r.m.Lock()
|
||||
for _, v := range r.volumes {
|
||||
ls = append(ls, v)
|
||||
}
|
||||
r.m.Unlock()
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
// DataPath returns the constructed path of this volume.
|
||||
func (r *Root) DataPath(volumeName string) string {
|
||||
return filepath.Join(r.path, volumeName, VolumeDataPathName)
|
||||
}
|
||||
|
||||
// Name returns the name of Root, defined in the volume package in the DefaultDriverName constant.
|
||||
func (r *Root) Name() string {
|
||||
return volume.DefaultDriverName
|
||||
}
|
||||
|
||||
// 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, opts map[string]string) (volume.Volume, error) {
|
||||
if err := r.validateName(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
v, exists := r.volumes[name]
|
||||
if exists {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
path := r.DataPath(name)
|
||||
if err := idtools.MkdirAllAndChown(path, 0755, r.rootIdentity); err != nil {
|
||||
return nil, errors.Wrapf(errdefs.System(err), "error while creating volume path '%s'", path)
|
||||
}
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.RemoveAll(filepath.Dir(path))
|
||||
}
|
||||
}()
|
||||
|
||||
v = &localVolume{
|
||||
driverName: r.Name(),
|
||||
name: name,
|
||||
path: path,
|
||||
}
|
||||
|
||||
if len(opts) != 0 {
|
||||
if err = setOpts(v, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var b []byte
|
||||
b, err = json.Marshal(v.opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = ioutil.WriteFile(filepath.Join(filepath.Dir(path), "opts.json"), b, 600); err != nil {
|
||||
return nil, errdefs.System(errors.Wrap(err, "error while persisting volume options"))
|
||||
}
|
||||
}
|
||||
|
||||
r.volumes[name] = v
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Remove removes the specified volume and all underlying data. If the
|
||||
// given volume does not belong to this driver and an error is
|
||||
// returned. The volume is reference counted, if all references are
|
||||
// not released then the volume is not removed.
|
||||
func (r *Root) Remove(v volume.Volume) error {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
lv, ok := v.(*localVolume)
|
||||
if !ok {
|
||||
return errdefs.System(errors.Errorf("unknown volume type %T", v))
|
||||
}
|
||||
|
||||
if lv.active.count > 0 {
|
||||
return errdefs.System(errors.Errorf("volume has active mounts"))
|
||||
}
|
||||
|
||||
if err := lv.unmount(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
realPath, err := filepath.EvalSymlinks(lv.path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
realPath = filepath.Dir(lv.path)
|
||||
}
|
||||
|
||||
if !r.scopedPath(realPath) {
|
||||
return errdefs.System(errors.Errorf("Unable to remove a directory outside of the local volume root %s: %s", r.scope, realPath))
|
||||
}
|
||||
|
||||
if err := removePath(realPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(r.volumes, lv.name)
|
||||
return removePath(filepath.Dir(lv.path))
|
||||
}
|
||||
|
||||
func removePath(path string) error {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return errdefs.System(errors.Wrapf(err, "error removing volume path '%s'", path))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Scope returns the local volume scope
|
||||
func (r *Root) Scope() string {
|
||||
return volume.LocalScope
|
||||
}
|
||||
|
||||
type validationError string
|
||||
|
||||
func (e validationError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e validationError) InvalidParameter() {}
|
||||
|
||||
func (r *Root) validateName(name string) error {
|
||||
if len(name) == 1 {
|
||||
return validationError("volume name is too short, names should be at least two alphanumeric characters")
|
||||
}
|
||||
if !volumeNameRegex.MatchString(name) {
|
||||
return validationError(fmt.Sprintf("%q includes invalid characters for a local volume name, only %q are allowed. If you intended to pass a host directory, use absolute path", name, names.RestrictedNameChars))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// localVolume implements the Volume interface from the volume package and
|
||||
// represents the volumes created by Root.
|
||||
type localVolume struct {
|
||||
m sync.Mutex
|
||||
// unique name of the volume
|
||||
name string
|
||||
// path is the path on the host where the data lives
|
||||
path string
|
||||
// driverName is the name of the driver that created the volume.
|
||||
driverName string
|
||||
// opts is the parsed list of options used to create the volume
|
||||
opts *optsConfig
|
||||
// active refcounts the active mounts
|
||||
active activeMount
|
||||
}
|
||||
|
||||
// Name returns the name of the given Volume.
|
||||
func (v *localVolume) Name() string {
|
||||
return v.name
|
||||
}
|
||||
|
||||
// DriverName returns the driver that created the given Volume.
|
||||
func (v *localVolume) DriverName() string {
|
||||
return v.driverName
|
||||
}
|
||||
|
||||
// Path returns the data location.
|
||||
func (v *localVolume) Path() string {
|
||||
return v.path
|
||||
}
|
||||
|
||||
// CachedPath returns the data location
|
||||
func (v *localVolume) CachedPath() string {
|
||||
return v.path
|
||||
}
|
||||
|
||||
// Mount implements the localVolume interface, returning the data location.
|
||||
// If there are any provided mount options, the resources will be mounted at this point
|
||||
func (v *localVolume) Mount(id string) (string, error) {
|
||||
v.m.Lock()
|
||||
defer v.m.Unlock()
|
||||
if v.opts != nil {
|
||||
if !v.active.mounted {
|
||||
if err := v.mount(); err != nil {
|
||||
return "", errdefs.System(err)
|
||||
}
|
||||
v.active.mounted = true
|
||||
}
|
||||
v.active.count++
|
||||
}
|
||||
return v.path, nil
|
||||
}
|
||||
|
||||
// Unmount dereferences the id, and if it is the last reference will unmount any resources
|
||||
// that were previously mounted.
|
||||
func (v *localVolume) Unmount(id string) error {
|
||||
v.m.Lock()
|
||||
defer v.m.Unlock()
|
||||
|
||||
// Always decrement the count, even if the unmount fails
|
||||
// Essentially docker doesn't care if this fails, it will send an error, but
|
||||
// ultimately there's nothing that can be done. If we don't decrement the count
|
||||
// this volume can never be removed until a daemon restart occurs.
|
||||
if v.opts != nil {
|
||||
v.active.count--
|
||||
}
|
||||
|
||||
if v.active.count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return v.unmount()
|
||||
}
|
||||
|
||||
func (v *localVolume) unmount() error {
|
||||
if v.opts != nil {
|
||||
if err := mount.Unmount(v.path); err != nil {
|
||||
if mounted, mErr := mount.Mounted(v.path); mounted || mErr != nil {
|
||||
return errdefs.System(errors.Wrapf(err, "error while unmounting volume path '%s'", v.path))
|
||||
}
|
||||
}
|
||||
v.active.mounted = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateOpts(opts map[string]string) error {
|
||||
for opt := range opts {
|
||||
if !validOpts[opt] {
|
||||
return validationError(fmt.Sprintf("invalid option key: %q", opt))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *localVolume) Status() map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAddress finds out address/hostname from options
|
||||
func getAddress(opts string) string {
|
||||
optsList := strings.Split(opts, ",")
|
||||
for i := 0; i < len(optsList); i++ {
|
||||
if strings.HasPrefix(optsList[i], "addr=") {
|
||||
addr := strings.SplitN(optsList[i], "=", 2)[1]
|
||||
return addr
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
335
vendor/github.com/docker/docker-ce/components/engine/volume/local/local_test.go
generated
vendored
Normal file
335
vendor/github.com/docker/docker-ce/components/engine/volume/local/local_test.go
generated
vendored
Normal file
|
@ -0,0 +1,335 @@
|
|||
package local // import "github.com/docker/docker/volume/local"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"gotest.tools/skip"
|
||||
)
|
||||
|
||||
func TestGetAddress(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"addr=11.11.11.1": "11.11.11.1",
|
||||
" ": "",
|
||||
"addr=": "",
|
||||
"addr=2001:db8::68": "2001:db8::68",
|
||||
}
|
||||
for name, success := range cases {
|
||||
v := getAddress(name)
|
||||
if v != success {
|
||||
t.Errorf("Test case failed for %s actual: %s expected : %s", name, v, success)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
skip.If(t, runtime.GOOS == "windows", "FIXME: investigate why this test fails on CI")
|
||||
rootDir, err := ioutil.TempDir("", "local-volume-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vol, err := r.Create("testing", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.Remove(vol); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vol, err = r.Create("testing2", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.RemoveAll(vol.Path()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.Remove(vol); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(vol.Path()); err != nil && !os.IsNotExist(err) {
|
||||
t.Fatal("volume dir not removed")
|
||||
}
|
||||
|
||||
if l, _ := r.List(); len(l) != 0 {
|
||||
t.Fatal("expected there to be no volumes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitializeWithVolumes(t *testing.T) {
|
||||
rootDir, err := ioutil.TempDir("", "local-volume-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vol, err := r.Create("testing", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err = New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v, err := r.Get(vol.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if v.Path() != vol.Path() {
|
||||
t.Fatal("expected to re-initialize root with existing volumes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
rootDir, err := ioutil.TempDir("", "local-volume-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cases := map[string]bool{
|
||||
"name": true,
|
||||
"name-with-dash": true,
|
||||
"name_with_underscore": true,
|
||||
"name/with/slash": false,
|
||||
"name/with/../../slash": false,
|
||||
"./name": false,
|
||||
"../name": false,
|
||||
"./": false,
|
||||
"../": false,
|
||||
"~": false,
|
||||
".": false,
|
||||
"..": false,
|
||||
"...": false,
|
||||
}
|
||||
|
||||
for name, success := range cases {
|
||||
v, err := r.Create(name, nil)
|
||||
if success {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Name() != name {
|
||||
t.Fatalf("Expected volume with name %s, got %s", name, v.Name())
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error creating volume with name %s, got nil", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r, err = New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateName(t *testing.T) {
|
||||
r := &Root{}
|
||||
names := map[string]bool{
|
||||
"x": false,
|
||||
"/testvol": false,
|
||||
"thing.d": true,
|
||||
"hello-world": true,
|
||||
"./hello": false,
|
||||
".hello": false,
|
||||
}
|
||||
|
||||
for vol, expected := range names {
|
||||
err := r.validateName(vol)
|
||||
if expected && err != nil {
|
||||
t.Fatalf("expected %s to be valid got %v", vol, err)
|
||||
}
|
||||
if !expected && err == nil {
|
||||
t.Fatalf("expected %s to be invalid", vol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateWithOpts(t *testing.T) {
|
||||
skip.If(t, runtime.GOOS == "windows")
|
||||
skip.If(t, os.Getuid() != 0, "requires mounts")
|
||||
rootDir, err := ioutil.TempDir("", "local-volume-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := r.Create("test", map[string]string{"invalidopt": "notsupported"}); err == nil {
|
||||
t.Fatal("expected invalid opt to cause error")
|
||||
}
|
||||
|
||||
vol, err := r.Create("test", map[string]string{"device": "tmpfs", "type": "tmpfs", "o": "size=1m,uid=1000"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v := vol.(*localVolume)
|
||||
|
||||
dir, err := v.Mount("1234")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := v.Unmount("1234"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
mountInfos, err := mount.GetMounts(mount.SingleEntryFilter(dir))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(mountInfos) != 1 {
|
||||
t.Fatalf("expected 1 mount, found %d: %+v", len(mountInfos), mountInfos)
|
||||
}
|
||||
|
||||
info := mountInfos[0]
|
||||
t.Logf("%+v", info)
|
||||
if info.Fstype != "tmpfs" {
|
||||
t.Fatalf("expected tmpfs mount, got %q", info.Fstype)
|
||||
}
|
||||
if info.Source != "tmpfs" {
|
||||
t.Fatalf("expected tmpfs mount, got %q", info.Source)
|
||||
}
|
||||
if !strings.Contains(info.VfsOpts, "uid=1000") {
|
||||
t.Fatalf("expected mount info to have uid=1000: %q", info.VfsOpts)
|
||||
}
|
||||
if !strings.Contains(info.VfsOpts, "size=1024k") {
|
||||
t.Fatalf("expected mount info to have size=1024k: %q", info.VfsOpts)
|
||||
}
|
||||
|
||||
if v.active.count != 1 {
|
||||
t.Fatalf("Expected active mount count to be 1, got %d", v.active.count)
|
||||
}
|
||||
|
||||
// test double mount
|
||||
if _, err := v.Mount("1234"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.active.count != 2 {
|
||||
t.Fatalf("Expected active mount count to be 2, got %d", v.active.count)
|
||||
}
|
||||
|
||||
if err := v.Unmount("1234"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.active.count != 1 {
|
||||
t.Fatalf("Expected active mount count to be 1, got %d", v.active.count)
|
||||
}
|
||||
|
||||
mounted, err := mount.Mounted(v.path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !mounted {
|
||||
t.Fatal("expected mount to still be active")
|
||||
}
|
||||
|
||||
r, err = New(rootDir, idtools.Identity{UID: 0, GID: 0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v2, exists := r.volumes["test"]
|
||||
if !exists {
|
||||
t.Fatal("missing volume on restart")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(v.opts, v2.opts) {
|
||||
t.Fatal("missing volume options on restart")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelaodNoOpts(t *testing.T) {
|
||||
rootDir, err := ioutil.TempDir("", "volume-test-reload-no-opts")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := r.Create("test1", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := r.Create("test2", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// make sure a file with `null` (.e.g. empty opts map from older daemon) is ok
|
||||
if err := ioutil.WriteFile(filepath.Join(rootDir, "test2"), []byte("null"), 600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := r.Create("test3", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// make sure an empty opts file doesn't break us too
|
||||
if err := ioutil.WriteFile(filepath.Join(rootDir, "test3"), nil, 600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := r.Create("test4", map[string]string{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err = New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, name := range []string{"test1", "test2", "test3", "test4"} {
|
||||
v, err := r.Get(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lv, ok := v.(*localVolume)
|
||||
if !ok {
|
||||
t.Fatalf("expected *localVolume got: %v", reflect.TypeOf(v))
|
||||
}
|
||||
if lv.opts != nil {
|
||||
t.Fatalf("expected opts to be nil, got: %v", lv.opts)
|
||||
}
|
||||
if _, err := lv.Mount("1234"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
99
vendor/github.com/docker/docker-ce/components/engine/volume/local/local_unix.go
generated
vendored
Normal file
99
vendor/github.com/docker/docker-ce/components/engine/volume/local/local_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
// +build linux freebsd
|
||||
|
||||
// Package local provides the default implementation for volumes. It
|
||||
// is used to mount data volume containers and directories local to
|
||||
// the host server.
|
||||
package local // import "github.com/docker/docker/volume/local"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
)
|
||||
|
||||
var (
|
||||
oldVfsDir = filepath.Join("vfs", "dir")
|
||||
|
||||
validOpts = map[string]bool{
|
||||
"type": true, // specify the filesystem type for mount, e.g. nfs
|
||||
"o": true, // generic mount options
|
||||
"device": true, // device to mount from
|
||||
}
|
||||
)
|
||||
|
||||
type optsConfig struct {
|
||||
MountType string
|
||||
MountOpts string
|
||||
MountDevice string
|
||||
}
|
||||
|
||||
func (o *optsConfig) String() string {
|
||||
return fmt.Sprintf("type='%s' device='%s' o='%s'", o.MountType, o.MountDevice, o.MountOpts)
|
||||
}
|
||||
|
||||
// scopedPath verifies that the path where the volume is located
|
||||
// is under Docker's root and the valid local paths.
|
||||
func (r *Root) scopedPath(realPath string) bool {
|
||||
// Volumes path for Docker version >= 1.7
|
||||
if strings.HasPrefix(realPath, filepath.Join(r.scope, volumesPathName)) && realPath != filepath.Join(r.scope, volumesPathName) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Volumes path for Docker version < 1.7
|
||||
if strings.HasPrefix(realPath, filepath.Join(r.scope, oldVfsDir)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func setOpts(v *localVolume, opts map[string]string) error {
|
||||
if len(opts) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := validateOpts(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.opts = &optsConfig{
|
||||
MountType: opts["type"],
|
||||
MountOpts: opts["o"],
|
||||
MountDevice: opts["device"],
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *localVolume) mount() error {
|
||||
if v.opts.MountDevice == "" {
|
||||
return fmt.Errorf("missing device in volume options")
|
||||
}
|
||||
mountOpts := v.opts.MountOpts
|
||||
if v.opts.MountType == "nfs" {
|
||||
if addrValue := getAddress(v.opts.MountOpts); addrValue != "" && net.ParseIP(addrValue).To4() == nil {
|
||||
ipAddr, err := net.ResolveIPAddr("ip", addrValue)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error resolving passed in nfs address")
|
||||
}
|
||||
mountOpts = strings.Replace(mountOpts, "addr="+addrValue, "addr="+ipAddr.String(), 1)
|
||||
}
|
||||
}
|
||||
err := mount.Mount(v.opts.MountDevice, v.path, v.opts.MountType, mountOpts)
|
||||
return errors.Wrapf(err, "error while mounting volume with options: %s", v.opts)
|
||||
}
|
||||
|
||||
func (v *localVolume) CreatedAt() (time.Time, error) {
|
||||
fileInfo, err := os.Stat(v.path)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
sec, nsec := fileInfo.Sys().(*syscall.Stat_t).Ctim.Unix()
|
||||
return time.Unix(sec, nsec), nil
|
||||
}
|
46
vendor/github.com/docker/docker-ce/components/engine/volume/local/local_windows.go
generated
vendored
Normal file
46
vendor/github.com/docker/docker-ce/components/engine/volume/local/local_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Package local provides the default implementation for volumes. It
|
||||
// is used to mount data volume containers and directories local to
|
||||
// the host server.
|
||||
package local // import "github.com/docker/docker/volume/local"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type optsConfig struct{}
|
||||
|
||||
var validOpts map[string]bool
|
||||
|
||||
// scopedPath verifies that the path where the volume is located
|
||||
// is under Docker's root and the valid local paths.
|
||||
func (r *Root) scopedPath(realPath string) bool {
|
||||
if strings.HasPrefix(realPath, filepath.Join(r.scope, volumesPathName)) && realPath != filepath.Join(r.scope, volumesPathName) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setOpts(v *localVolume, opts map[string]string) error {
|
||||
if len(opts) > 0 {
|
||||
return fmt.Errorf("options are not supported on this platform")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *localVolume) mount() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *localVolume) CreatedAt() (time.Time, error) {
|
||||
fileInfo, err := os.Stat(v.path)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
ft := fileInfo.Sys().(*syscall.Win32FileAttributeData).CreationTime
|
||||
return time.Unix(0, ft.Nanoseconds()), nil
|
||||
}
|
34
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/lcow_parser.go
generated
vendored
Normal file
34
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/lcow_parser.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
var lcowSpecificValidators mountValidator = func(m *mount.Mount) error {
|
||||
if path.Clean(m.Target) == "/" {
|
||||
return ErrVolumeTargetIsRoot
|
||||
}
|
||||
if m.Type == mount.TypeNamedPipe {
|
||||
return errors.New("Linux containers on Windows do not support named pipe mounts")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type lcowParser struct {
|
||||
windowsParser
|
||||
}
|
||||
|
||||
func (p *lcowParser) ValidateMountConfig(mnt *mount.Mount) error {
|
||||
return p.validateMountConfigReg(mnt, rxLCOWDestination, lcowSpecificValidators)
|
||||
}
|
||||
|
||||
func (p *lcowParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
||||
return p.parseMountRaw(raw, volumeDriver, rxLCOWDestination, false, lcowSpecificValidators)
|
||||
}
|
||||
|
||||
func (p *lcowParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
||||
return p.parseMountSpec(cfg, rxLCOWDestination, false, lcowSpecificValidators)
|
||||
}
|
417
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/linux_parser.go
generated
vendored
Normal file
417
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/linux_parser.go
generated
vendored
Normal file
|
@ -0,0 +1,417 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
type linuxParser struct {
|
||||
}
|
||||
|
||||
func linuxSplitRawSpec(raw string) ([]string, error) {
|
||||
if strings.Count(raw, ":") > 2 {
|
||||
return nil, errInvalidSpec(raw)
|
||||
}
|
||||
|
||||
arr := strings.SplitN(raw, ":", 3)
|
||||
if arr[0] == "" {
|
||||
return nil, errInvalidSpec(raw)
|
||||
}
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
func linuxValidateNotRoot(p string) error {
|
||||
p = path.Clean(strings.Replace(p, `\`, `/`, -1))
|
||||
if p == "/" {
|
||||
return ErrVolumeTargetIsRoot
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func linuxValidateAbsolute(p string) error {
|
||||
p = strings.Replace(p, `\`, `/`, -1)
|
||||
if path.IsAbs(p) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
||||
}
|
||||
func (p *linuxParser) ValidateMountConfig(mnt *mount.Mount) error {
|
||||
// there was something looking like a bug in existing codebase:
|
||||
// - validateMountConfig on linux was called with options skipping bind source existence when calling ParseMountRaw
|
||||
// - but not when calling ParseMountSpec directly... nor when the unit test called it directly
|
||||
return p.validateMountConfigImpl(mnt, true)
|
||||
}
|
||||
func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error {
|
||||
if len(mnt.Target) == 0 {
|
||||
return &errMountConfig{mnt, errMissingField("Target")}
|
||||
}
|
||||
|
||||
if err := linuxValidateNotRoot(mnt.Target); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
|
||||
if err := linuxValidateAbsolute(mnt.Target); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
|
||||
switch mnt.Type {
|
||||
case mount.TypeBind:
|
||||
if len(mnt.Source) == 0 {
|
||||
return &errMountConfig{mnt, errMissingField("Source")}
|
||||
}
|
||||
// Don't error out just because the propagation mode is not supported on the platform
|
||||
if opts := mnt.BindOptions; opts != nil {
|
||||
if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 {
|
||||
if _, ok := linuxPropagationModes[opts.Propagation]; !ok {
|
||||
return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
|
||||
}
|
||||
}
|
||||
}
|
||||
if mnt.VolumeOptions != nil {
|
||||
return &errMountConfig{mnt, errExtraField("VolumeOptions")}
|
||||
}
|
||||
|
||||
if err := linuxValidateAbsolute(mnt.Source); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
|
||||
if validateBindSourceExists {
|
||||
exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source)
|
||||
if !exists {
|
||||
return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
|
||||
}
|
||||
}
|
||||
|
||||
case mount.TypeVolume:
|
||||
if mnt.BindOptions != nil {
|
||||
return &errMountConfig{mnt, errExtraField("BindOptions")}
|
||||
}
|
||||
|
||||
if len(mnt.Source) == 0 && mnt.ReadOnly {
|
||||
return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
||||
}
|
||||
case mount.TypeTmpfs:
|
||||
if len(mnt.Source) != 0 {
|
||||
return &errMountConfig{mnt, errExtraField("Source")}
|
||||
}
|
||||
if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
default:
|
||||
return &errMountConfig{mnt, errors.New("mount type unknown")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// read-write modes
|
||||
var rwModes = map[string]bool{
|
||||
"rw": true,
|
||||
"ro": true,
|
||||
}
|
||||
|
||||
// label modes
|
||||
var linuxLabelModes = map[string]bool{
|
||||
"Z": true,
|
||||
"z": true,
|
||||
}
|
||||
|
||||
// consistency modes
|
||||
var linuxConsistencyModes = map[mount.Consistency]bool{
|
||||
mount.ConsistencyFull: true,
|
||||
mount.ConsistencyCached: true,
|
||||
mount.ConsistencyDelegated: true,
|
||||
}
|
||||
var linuxPropagationModes = map[mount.Propagation]bool{
|
||||
mount.PropagationPrivate: true,
|
||||
mount.PropagationRPrivate: true,
|
||||
mount.PropagationSlave: true,
|
||||
mount.PropagationRSlave: true,
|
||||
mount.PropagationShared: true,
|
||||
mount.PropagationRShared: true,
|
||||
}
|
||||
|
||||
const linuxDefaultPropagationMode = mount.PropagationRPrivate
|
||||
|
||||
func linuxGetPropagation(mode string) mount.Propagation {
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
prop := mount.Propagation(o)
|
||||
if linuxPropagationModes[prop] {
|
||||
return prop
|
||||
}
|
||||
}
|
||||
return linuxDefaultPropagationMode
|
||||
}
|
||||
|
||||
func linuxHasPropagation(mode string) bool {
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
if linuxPropagationModes[mount.Propagation(o)] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func linuxValidMountMode(mode string) bool {
|
||||
if mode == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
rwModeCount := 0
|
||||
labelModeCount := 0
|
||||
propagationModeCount := 0
|
||||
copyModeCount := 0
|
||||
consistencyModeCount := 0
|
||||
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
switch {
|
||||
case rwModes[o]:
|
||||
rwModeCount++
|
||||
case linuxLabelModes[o]:
|
||||
labelModeCount++
|
||||
case linuxPropagationModes[mount.Propagation(o)]:
|
||||
propagationModeCount++
|
||||
case copyModeExists(o):
|
||||
copyModeCount++
|
||||
case linuxConsistencyModes[mount.Consistency(o)]:
|
||||
consistencyModeCount++
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Only one string for each mode is allowed.
|
||||
if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *linuxParser) ReadWrite(mode string) bool {
|
||||
if !linuxValidMountMode(mode) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
if o == "ro" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
||||
arr, err := linuxSplitRawSpec(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var spec mount.Mount
|
||||
var mode string
|
||||
switch len(arr) {
|
||||
case 1:
|
||||
// Just a destination path in the container
|
||||
spec.Target = arr[0]
|
||||
case 2:
|
||||
if linuxValidMountMode(arr[1]) {
|
||||
// Destination + Mode is not a valid volume - volumes
|
||||
// cannot include a mode. e.g. /foo:rw
|
||||
return nil, errInvalidSpec(raw)
|
||||
}
|
||||
// Host Source Path or Name + Destination
|
||||
spec.Source = arr[0]
|
||||
spec.Target = arr[1]
|
||||
case 3:
|
||||
// HostSourcePath+DestinationPath+Mode
|
||||
spec.Source = arr[0]
|
||||
spec.Target = arr[1]
|
||||
mode = arr[2]
|
||||
default:
|
||||
return nil, errInvalidSpec(raw)
|
||||
}
|
||||
|
||||
if !linuxValidMountMode(mode) {
|
||||
return nil, errInvalidMode(mode)
|
||||
}
|
||||
|
||||
if path.IsAbs(spec.Source) {
|
||||
spec.Type = mount.TypeBind
|
||||
} else {
|
||||
spec.Type = mount.TypeVolume
|
||||
}
|
||||
|
||||
spec.ReadOnly = !p.ReadWrite(mode)
|
||||
|
||||
// cannot assume that if a volume driver is passed in that we should set it
|
||||
if volumeDriver != "" && spec.Type == mount.TypeVolume {
|
||||
spec.VolumeOptions = &mount.VolumeOptions{
|
||||
DriverConfig: &mount.Driver{Name: volumeDriver},
|
||||
}
|
||||
}
|
||||
|
||||
if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
||||
if spec.VolumeOptions == nil {
|
||||
spec.VolumeOptions = &mount.VolumeOptions{}
|
||||
}
|
||||
spec.VolumeOptions.NoCopy = !copyData
|
||||
}
|
||||
if linuxHasPropagation(mode) {
|
||||
spec.BindOptions = &mount.BindOptions{
|
||||
Propagation: linuxGetPropagation(mode),
|
||||
}
|
||||
}
|
||||
|
||||
mp, err := p.parseMountSpec(spec, false)
|
||||
if mp != nil {
|
||||
mp.Mode = mode
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
|
||||
}
|
||||
return mp, err
|
||||
}
|
||||
func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
||||
return p.parseMountSpec(cfg, true)
|
||||
}
|
||||
func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) {
|
||||
if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp := &MountPoint{
|
||||
RW: !cfg.ReadOnly,
|
||||
Destination: path.Clean(filepath.ToSlash(cfg.Target)),
|
||||
Type: cfg.Type,
|
||||
Spec: cfg,
|
||||
}
|
||||
|
||||
switch cfg.Type {
|
||||
case mount.TypeVolume:
|
||||
if cfg.Source == "" {
|
||||
mp.Name = stringid.GenerateNonCryptoID()
|
||||
} else {
|
||||
mp.Name = cfg.Source
|
||||
}
|
||||
mp.CopyData = p.DefaultCopyMode()
|
||||
|
||||
if cfg.VolumeOptions != nil {
|
||||
if cfg.VolumeOptions.DriverConfig != nil {
|
||||
mp.Driver = cfg.VolumeOptions.DriverConfig.Name
|
||||
}
|
||||
if cfg.VolumeOptions.NoCopy {
|
||||
mp.CopyData = false
|
||||
}
|
||||
}
|
||||
case mount.TypeBind:
|
||||
mp.Source = path.Clean(filepath.ToSlash(cfg.Source))
|
||||
if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
|
||||
mp.Propagation = cfg.BindOptions.Propagation
|
||||
} else {
|
||||
// If user did not specify a propagation mode, get
|
||||
// default propagation mode.
|
||||
mp.Propagation = linuxDefaultPropagationMode
|
||||
}
|
||||
case mount.TypeTmpfs:
|
||||
// NOP
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
|
||||
if len(spec) == 0 {
|
||||
return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
|
||||
}
|
||||
|
||||
specParts := strings.SplitN(spec, ":", 2)
|
||||
id := specParts[0]
|
||||
mode := "rw"
|
||||
|
||||
if len(specParts) == 2 {
|
||||
mode = specParts[1]
|
||||
if !linuxValidMountMode(mode) {
|
||||
return "", "", errInvalidMode(mode)
|
||||
}
|
||||
// For now don't allow propagation properties while importing
|
||||
// volumes from data container. These volumes will inherit
|
||||
// the same propagation property as of the original volume
|
||||
// in data container. This probably can be relaxed in future.
|
||||
if linuxHasPropagation(mode) {
|
||||
return "", "", errInvalidMode(mode)
|
||||
}
|
||||
// Do not allow copy modes on volumes-from
|
||||
if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
||||
return "", "", errInvalidMode(mode)
|
||||
}
|
||||
}
|
||||
return id, mode, nil
|
||||
}
|
||||
|
||||
func (p *linuxParser) DefaultPropagationMode() mount.Propagation {
|
||||
return linuxDefaultPropagationMode
|
||||
}
|
||||
|
||||
func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
|
||||
var rawOpts []string
|
||||
if readOnly {
|
||||
rawOpts = append(rawOpts, "ro")
|
||||
}
|
||||
|
||||
if opt != nil && opt.Mode != 0 {
|
||||
rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
|
||||
}
|
||||
|
||||
if opt != nil && opt.SizeBytes != 0 {
|
||||
// calculate suffix here, making this linux specific, but that is
|
||||
// okay, since API is that way anyways.
|
||||
|
||||
// we do this by finding the suffix that divides evenly into the
|
||||
// value, returning the value itself, with no suffix, if it fails.
|
||||
//
|
||||
// For the most part, we don't enforce any semantic to this values.
|
||||
// The operating system will usually align this and enforce minimum
|
||||
// and maximums.
|
||||
var (
|
||||
size = opt.SizeBytes
|
||||
suffix string
|
||||
)
|
||||
for _, r := range []struct {
|
||||
suffix string
|
||||
divisor int64
|
||||
}{
|
||||
{"g", 1 << 30},
|
||||
{"m", 1 << 20},
|
||||
{"k", 1 << 10},
|
||||
} {
|
||||
if size%r.divisor == 0 {
|
||||
size = size / r.divisor
|
||||
suffix = r.suffix
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
|
||||
}
|
||||
return strings.Join(rawOpts, ","), nil
|
||||
}
|
||||
|
||||
func (p *linuxParser) DefaultCopyMode() bool {
|
||||
return true
|
||||
}
|
||||
func (p *linuxParser) ValidateVolumeName(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool {
|
||||
return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName
|
||||
}
|
||||
|
||||
func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error {
|
||||
if err := linuxValidateNotRoot(dest); err != nil {
|
||||
return err
|
||||
}
|
||||
return linuxValidateAbsolute(dest)
|
||||
}
|
181
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/mounts.go
generated
vendored
Normal file
181
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/mounts.go
generated
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// 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
|
||||
// be mounted.
|
||||
//
|
||||
// Note that this type is embedded in `container.Container` object and persisted to disk.
|
||||
// Changes to this struct need to by synced with on disk state.
|
||||
type MountPoint struct {
|
||||
// Source is the source path of the mount.
|
||||
// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
|
||||
Source string
|
||||
// Destination is the path relative to the container root (`/`) to the mount point
|
||||
// It is where the `Source` is mounted to
|
||||
Destination string
|
||||
// RW is set to true when the mountpoint should be mounted as read-write
|
||||
RW bool
|
||||
// Name is the name reference to the underlying data defined by `Source`
|
||||
// e.g., the volume name
|
||||
Name string
|
||||
// Driver is the volume driver used to create the volume (if it is a volume)
|
||||
Driver string
|
||||
// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
|
||||
Type mounttypes.Type `json:",omitempty"`
|
||||
// Volume is the volume providing data to this mountpoint.
|
||||
// This is nil unless `Type` is set to `TypeVolume`
|
||||
Volume volume.Volume `json:"-"`
|
||||
|
||||
// Mode is the comma separated list of options supplied by the user when creating
|
||||
// the bind/volume mount.
|
||||
// Note Mode is not used on Windows
|
||||
Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
|
||||
|
||||
// Propagation describes how the mounts are propagated from the host into the
|
||||
// mount point, and vice-versa.
|
||||
// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
|
||||
// Note Propagation is not used on Windows
|
||||
Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
|
||||
|
||||
// Specifies if data should be copied from the container before the first mount
|
||||
// Use a pointer here so we can tell if the user set this value explicitly
|
||||
// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
|
||||
CopyData bool `json:"-"`
|
||||
// ID is the opaque ID used to pass to the volume driver.
|
||||
// This should be set by calls to `Mount` and unset by calls to `Unmount`
|
||||
ID string `json:",omitempty"`
|
||||
|
||||
// Sepc is a copy of the API request that created this mount.
|
||||
Spec mounttypes.Mount
|
||||
|
||||
// Some bind mounts should not be automatically created.
|
||||
// (Some are auto-created for backwards-compatibility)
|
||||
// This is checked on the API but setting this here prevents race conditions.
|
||||
// where a bind dir existed during validation was removed before reaching the setup code.
|
||||
SkipMountpointCreation bool
|
||||
|
||||
// Track usage of this mountpoint
|
||||
// Specifically needed for containers which are running and calls to `docker cp`
|
||||
// because both these actions require mounting the volumes.
|
||||
active int
|
||||
}
|
||||
|
||||
// Cleanup frees resources used by the mountpoint
|
||||
func (m *MountPoint) Cleanup() error {
|
||||
if m.Volume == nil || m.ID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := m.Volume.Unmount(m.ID); err != nil {
|
||||
return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
|
||||
}
|
||||
|
||||
m.active--
|
||||
if m.active == 0 {
|
||||
m.ID = ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup sets up a mount point by either mounting the volume if it is
|
||||
// configured, or creating the source directory if supplied.
|
||||
// The, optional, checkFun parameter allows doing additional checking
|
||||
// before creating the source directory on the host.
|
||||
func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun func(m *MountPoint) error) (path string, err error) {
|
||||
if m.SkipMountpointCreation {
|
||||
return m.Source, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil || !label.RelabelNeeded(m.Mode) {
|
||||
return
|
||||
}
|
||||
|
||||
var sourcePath string
|
||||
sourcePath, err = filepath.EvalSymlinks(m.Source)
|
||||
if err != nil {
|
||||
path = ""
|
||||
err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
|
||||
return
|
||||
}
|
||||
err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
|
||||
if err == syscall.ENOTSUP {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
path = ""
|
||||
err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
|
||||
}
|
||||
}()
|
||||
|
||||
if m.Volume != nil {
|
||||
id := m.ID
|
||||
if id == "" {
|
||||
id = stringid.GenerateNonCryptoID()
|
||||
}
|
||||
path, err := m.Volume.Mount(id)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
|
||||
}
|
||||
|
||||
m.ID = id
|
||||
m.active++
|
||||
return path, nil
|
||||
}
|
||||
|
||||
if len(m.Source) == 0 {
|
||||
return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
||||
}
|
||||
|
||||
if m.Type == mounttypes.TypeBind {
|
||||
// Before creating the source directory on the host, invoke checkFun if it's not nil. One of
|
||||
// the use case is to forbid creating the daemon socket as a directory if the daemon is in
|
||||
// the process of shutting down.
|
||||
if checkFun != nil {
|
||||
if err := checkFun(m); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
|
||||
// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
|
||||
if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
|
||||
if perr, ok := err.(*os.PathError); ok {
|
||||
if perr.Err != syscall.ENOTDIR {
|
||||
return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.Source, nil
|
||||
}
|
||||
|
||||
// Path returns the path of a volume in a mount point.
|
||||
func (m *MountPoint) Path() string {
|
||||
if m.Volume != nil {
|
||||
return m.Volume.Path()
|
||||
}
|
||||
return m.Source
|
||||
}
|
||||
|
||||
func errInvalidMode(mode string) error {
|
||||
return errors.Errorf("invalid mode: %v", mode)
|
||||
}
|
||||
|
||||
func errInvalidSpec(spec string) error {
|
||||
return errors.Errorf("invalid volume specification: '%s'", spec)
|
||||
}
|
47
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/parser.go
generated
vendored
Normal file
47
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
const (
|
||||
// OSLinux is the same as runtime.GOOS on linux
|
||||
OSLinux = "linux"
|
||||
// OSWindows is the same as runtime.GOOS on windows
|
||||
OSWindows = "windows"
|
||||
)
|
||||
|
||||
// ErrVolumeTargetIsRoot is returned when the target destination is root.
|
||||
// It's used by both LCOW and Linux parsers.
|
||||
var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")
|
||||
|
||||
// Parser represents a platform specific parser for mount expressions
|
||||
type Parser interface {
|
||||
ParseMountRaw(raw, volumeDriver string) (*MountPoint, error)
|
||||
ParseMountSpec(cfg mount.Mount) (*MountPoint, error)
|
||||
ParseVolumesFrom(spec string) (string, string, error)
|
||||
DefaultPropagationMode() mount.Propagation
|
||||
ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error)
|
||||
DefaultCopyMode() bool
|
||||
ValidateVolumeName(name string) error
|
||||
ReadWrite(mode string) bool
|
||||
IsBackwardCompatible(m *MountPoint) bool
|
||||
HasResource(m *MountPoint, absPath string) bool
|
||||
ValidateTmpfsMountDestination(dest string) error
|
||||
ValidateMountConfig(mt *mount.Mount) error
|
||||
}
|
||||
|
||||
// NewParser creates a parser for a given container OS, depending on the current host OS (linux on a windows host will resolve to an lcowParser)
|
||||
func NewParser(containerOS string) Parser {
|
||||
switch containerOS {
|
||||
case OSWindows:
|
||||
return &windowsParser{}
|
||||
}
|
||||
if runtime.GOOS == OSWindows {
|
||||
return &lcowParser{}
|
||||
}
|
||||
return &linuxParser{}
|
||||
}
|
480
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/parser_test.go
generated
vendored
Normal file
480
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/parser_test.go
generated
vendored
Normal file
|
@ -0,0 +1,480 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
type parseMountRawTestSet struct {
|
||||
valid []string
|
||||
invalid map[string]string
|
||||
}
|
||||
|
||||
func TestConvertTmpfsOptions(t *testing.T) {
|
||||
type testCase struct {
|
||||
opt mount.TmpfsOptions
|
||||
readOnly bool
|
||||
expectedSubstrings []string
|
||||
unexpectedSubstrings []string
|
||||
}
|
||||
cases := []testCase{
|
||||
{
|
||||
opt: mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
|
||||
readOnly: false,
|
||||
expectedSubstrings: []string{"size=1m", "mode=700"},
|
||||
unexpectedSubstrings: []string{"ro"},
|
||||
},
|
||||
{
|
||||
opt: mount.TmpfsOptions{},
|
||||
readOnly: true,
|
||||
expectedSubstrings: []string{"ro"},
|
||||
unexpectedSubstrings: []string{},
|
||||
},
|
||||
}
|
||||
p := &linuxParser{}
|
||||
for _, c := range cases {
|
||||
data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly)
|
||||
if err != nil {
|
||||
t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
|
||||
c.opt, c.readOnly, err)
|
||||
}
|
||||
t.Logf("data=%q", data)
|
||||
for _, s := range c.expectedSubstrings {
|
||||
if !strings.Contains(data, s) {
|
||||
t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
|
||||
}
|
||||
}
|
||||
for _, s := range c.unexpectedSubstrings {
|
||||
if strings.Contains(data, s) {
|
||||
t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mockFiProvider struct{}
|
||||
|
||||
func (mockFiProvider) fileInfo(path string) (exists, isDir bool, err error) {
|
||||
dirs := map[string]struct{}{
|
||||
`c:\`: {},
|
||||
`c:\windows\`: {},
|
||||
`c:\windows`: {},
|
||||
`c:\program files`: {},
|
||||
`c:\Windows`: {},
|
||||
`c:\Program Files (x86)`: {},
|
||||
`\\?\c:\windows\`: {},
|
||||
}
|
||||
files := map[string]struct{}{
|
||||
`c:\windows\system32\ntdll.dll`: {},
|
||||
}
|
||||
if _, ok := dirs[path]; ok {
|
||||
return true, true, nil
|
||||
}
|
||||
if _, ok := files[path]; ok {
|
||||
return true, false, nil
|
||||
}
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
func TestParseMountRaw(t *testing.T) {
|
||||
|
||||
previousProvider := currentFileInfoProvider
|
||||
defer func() { currentFileInfoProvider = previousProvider }()
|
||||
currentFileInfoProvider = mockFiProvider{}
|
||||
windowsSet := parseMountRawTestSet{
|
||||
valid: []string{
|
||||
`d:\`,
|
||||
`d:`,
|
||||
`d:\path`,
|
||||
`d:\path with space`,
|
||||
`c:\:d:\`,
|
||||
`c:\windows\:d:`,
|
||||
`c:\windows:d:\s p a c e`,
|
||||
`c:\windows:d:\s p a c e:RW`,
|
||||
`c:\program files:d:\s p a c e i n h o s t d i r`,
|
||||
`0123456789name:d:`,
|
||||
`MiXeDcAsEnAmE:d:`,
|
||||
`name:D:`,
|
||||
`name:D::rW`,
|
||||
`name:D::RW`,
|
||||
`name:D::RO`,
|
||||
`c:/:d:/forward/slashes/are/good/too`,
|
||||
`c:/:d:/including with/spaces:ro`,
|
||||
`c:\Windows`, // With capital
|
||||
`c:\Program Files (x86)`, // With capitals and brackets
|
||||
`\\?\c:\windows\:d:`, // Long path handling (source)
|
||||
`c:\windows\:\\?\d:\`, // Long path handling (target)
|
||||
`\\.\pipe\foo:\\.\pipe\foo`, // named pipe
|
||||
`//./pipe/foo://./pipe/foo`, // named pipe forward slashes
|
||||
},
|
||||
invalid: map[string]string{
|
||||
``: "invalid volume specification: ",
|
||||
`.`: "invalid volume specification: ",
|
||||
`..\`: "invalid volume specification: ",
|
||||
`c:\:..\`: "invalid volume specification: ",
|
||||
`c:\:d:\:xyzzy`: "invalid volume specification: ",
|
||||
`c:`: "cannot be `c:`",
|
||||
`c:\`: "cannot be `c:`",
|
||||
`c:\notexist:d:`: `bind mount source path does not exist: c:\notexist`,
|
||||
`c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`,
|
||||
`name<:d:`: `invalid volume specification`,
|
||||
`name>:d:`: `invalid volume specification`,
|
||||
`name::d:`: `invalid volume specification`,
|
||||
`name":d:`: `invalid volume specification`,
|
||||
`name\:d:`: `invalid volume specification`,
|
||||
`name*:d:`: `invalid volume specification`,
|
||||
`name|:d:`: `invalid volume specification`,
|
||||
`name?:d:`: `invalid volume specification`,
|
||||
`name/:d:`: `invalid volume specification`,
|
||||
`d:\pathandmode:rw`: `invalid volume specification`,
|
||||
`d:\pathandmode:ro`: `invalid volume specification`,
|
||||
`con:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`PRN:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`aUx:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`nul:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com1:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com2:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com3:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com4:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com5:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com6:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com7:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com8:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`com9:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt1:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt2:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt3:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt4:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt5:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt6:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt7:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt8:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt9:d:`: `cannot be a reserved word for Windows filenames`,
|
||||
`c:\windows\system32\ntdll.dll`: `Only directories can be mapped on this platform`,
|
||||
`\\.\pipe\foo:c:\pipe`: `'c:\pipe' is not a valid pipe path`,
|
||||
},
|
||||
}
|
||||
lcowSet := parseMountRawTestSet{
|
||||
valid: []string{
|
||||
`/foo`,
|
||||
`/foo/`,
|
||||
`/foo bar`,
|
||||
`c:\:/foo`,
|
||||
`c:\windows\:/foo`,
|
||||
`c:\windows:/s p a c e`,
|
||||
`c:\windows:/s p a c e:RW`,
|
||||
`c:\program files:/s p a c e i n h o s t d i r`,
|
||||
`0123456789name:/foo`,
|
||||
`MiXeDcAsEnAmE:/foo`,
|
||||
`name:/foo`,
|
||||
`name:/foo:rW`,
|
||||
`name:/foo:RW`,
|
||||
`name:/foo:RO`,
|
||||
`c:/:/forward/slashes/are/good/too`,
|
||||
`c:/:/including with/spaces:ro`,
|
||||
`/Program Files (x86)`, // With capitals and brackets
|
||||
},
|
||||
invalid: map[string]string{
|
||||
``: "invalid volume specification: ",
|
||||
`.`: "invalid volume specification: ",
|
||||
`c:`: "invalid volume specification: ",
|
||||
`c:\`: "invalid volume specification: ",
|
||||
`../`: "invalid volume specification: ",
|
||||
`c:\:../`: "invalid volume specification: ",
|
||||
`c:\:/foo:xyzzy`: "invalid volume specification: ",
|
||||
`/`: "destination can't be '/'",
|
||||
`/..`: "destination can't be '/'",
|
||||
`c:\notexist:/foo`: `bind mount source path does not exist: c:\notexist`,
|
||||
`c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`,
|
||||
`name<:/foo`: `invalid volume specification`,
|
||||
`name>:/foo`: `invalid volume specification`,
|
||||
`name::/foo`: `invalid volume specification`,
|
||||
`name":/foo`: `invalid volume specification`,
|
||||
`name\:/foo`: `invalid volume specification`,
|
||||
`name*:/foo`: `invalid volume specification`,
|
||||
`name|:/foo`: `invalid volume specification`,
|
||||
`name?:/foo`: `invalid volume specification`,
|
||||
`name/:/foo`: `invalid volume specification`,
|
||||
`/foo:rw`: `invalid volume specification`,
|
||||
`/foo:ro`: `invalid volume specification`,
|
||||
`con:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`PRN:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`aUx:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`nul:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com1:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com2:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com3:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com4:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com5:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com6:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com7:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com8:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`com9:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt1:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt2:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt3:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt4:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt5:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt6:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt7:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt8:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`lpt9:/foo`: `cannot be a reserved word for Windows filenames`,
|
||||
`\\.\pipe\foo:/foo`: `Linux containers on Windows do not support named pipe mounts`,
|
||||
},
|
||||
}
|
||||
linuxSet := parseMountRawTestSet{
|
||||
valid: []string{
|
||||
"/home",
|
||||
"/home:/home",
|
||||
"/home:/something/else",
|
||||
"/with space",
|
||||
"/home:/with space",
|
||||
"relative:/absolute-path",
|
||||
"hostPath:/containerPath:ro",
|
||||
"/hostPath:/containerPath:rw",
|
||||
"/rw:/ro",
|
||||
"/hostPath:/containerPath:shared",
|
||||
"/hostPath:/containerPath:rshared",
|
||||
"/hostPath:/containerPath:slave",
|
||||
"/hostPath:/containerPath:rslave",
|
||||
"/hostPath:/containerPath:private",
|
||||
"/hostPath:/containerPath:rprivate",
|
||||
"/hostPath:/containerPath:ro,shared",
|
||||
"/hostPath:/containerPath:ro,slave",
|
||||
"/hostPath:/containerPath:ro,private",
|
||||
"/hostPath:/containerPath:ro,z,shared",
|
||||
"/hostPath:/containerPath:ro,Z,slave",
|
||||
"/hostPath:/containerPath:Z,ro,slave",
|
||||
"/hostPath:/containerPath:slave,Z,ro",
|
||||
"/hostPath:/containerPath:Z,slave,ro",
|
||||
"/hostPath:/containerPath:slave,ro,Z",
|
||||
"/hostPath:/containerPath:rslave,ro,Z",
|
||||
"/hostPath:/containerPath:ro,rshared,Z",
|
||||
"/hostPath:/containerPath:ro,Z,rprivate",
|
||||
},
|
||||
invalid: map[string]string{
|
||||
"": "invalid volume specification",
|
||||
"./": "mount path must be absolute",
|
||||
"../": "mount path must be absolute",
|
||||
"/:../": "mount path must be absolute",
|
||||
"/:path": "mount path must be absolute",
|
||||
":": "invalid volume specification",
|
||||
"/tmp:": "invalid volume specification",
|
||||
":test": "invalid volume specification",
|
||||
":/test": "invalid volume specification",
|
||||
"tmp:": "invalid volume specification",
|
||||
":test:": "invalid volume specification",
|
||||
"::": "invalid volume specification",
|
||||
":::": "invalid volume specification",
|
||||
"/tmp:::": "invalid volume specification",
|
||||
":/tmp::": "invalid volume specification",
|
||||
"/path:rw": "invalid volume specification",
|
||||
"/path:ro": "invalid volume specification",
|
||||
"/rw:rw": "invalid volume specification",
|
||||
"path:ro": "invalid volume specification",
|
||||
"/path:/path:sw": `invalid mode`,
|
||||
"/path:/path:rwz": `invalid mode`,
|
||||
"/path:/path:ro,rshared,rslave": `invalid mode`,
|
||||
"/path:/path:ro,z,rshared,rslave": `invalid mode`,
|
||||
"/path:shared": "invalid volume specification",
|
||||
"/path:slave": "invalid volume specification",
|
||||
"/path:private": "invalid volume specification",
|
||||
"name:/absolute-path:shared": "invalid volume specification",
|
||||
"name:/absolute-path:rshared": "invalid volume specification",
|
||||
"name:/absolute-path:slave": "invalid volume specification",
|
||||
"name:/absolute-path:rslave": "invalid volume specification",
|
||||
"name:/absolute-path:private": "invalid volume specification",
|
||||
"name:/absolute-path:rprivate": "invalid volume specification",
|
||||
},
|
||||
}
|
||||
|
||||
linParser := &linuxParser{}
|
||||
winParser := &windowsParser{}
|
||||
lcowParser := &lcowParser{}
|
||||
tester := func(parser Parser, set parseMountRawTestSet) {
|
||||
|
||||
for _, path := range set.valid {
|
||||
|
||||
if _, err := parser.ParseMountRaw(path, "local"); err != nil {
|
||||
t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
for path, expectedError := range set.invalid {
|
||||
if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
|
||||
t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tester(linParser, linuxSet)
|
||||
tester(winParser, windowsSet)
|
||||
tester(lcowParser, lcowSet)
|
||||
|
||||
}
|
||||
|
||||
// testParseMountRaw is a structure used by TestParseMountRawSplit for
|
||||
// specifying test cases for the ParseMountRaw() function.
|
||||
type testParseMountRaw struct {
|
||||
bind string
|
||||
driver string
|
||||
expType mount.Type
|
||||
expDest string
|
||||
expSource string
|
||||
expName string
|
||||
expDriver string
|
||||
expRW bool
|
||||
fail bool
|
||||
}
|
||||
|
||||
func TestParseMountRawSplit(t *testing.T) {
|
||||
previousProvider := currentFileInfoProvider
|
||||
defer func() { currentFileInfoProvider = previousProvider }()
|
||||
currentFileInfoProvider = mockFiProvider{}
|
||||
windowsCases := []testParseMountRaw{
|
||||
{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
|
||||
{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
|
||||
{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
|
||||
{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
|
||||
{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
|
||||
{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
|
||||
{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
|
||||
{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
|
||||
{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
||||
{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
||||
{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
|
||||
{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
||||
{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
||||
}
|
||||
lcowCases := []testParseMountRaw{
|
||||
{`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
|
||||
{`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false},
|
||||
{`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
|
||||
{`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true},
|
||||
{`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
|
||||
{`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
|
||||
{`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false},
|
||||
{`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
||||
{`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
||||
{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true},
|
||||
{`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
||||
{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
||||
}
|
||||
linuxCases := []testParseMountRaw{
|
||||
{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
|
||||
{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
|
||||
{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
|
||||
{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
|
||||
{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
|
||||
{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
|
||||
{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
|
||||
{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
|
||||
{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
|
||||
}
|
||||
linParser := &linuxParser{}
|
||||
winParser := &windowsParser{}
|
||||
lcowParser := &lcowParser{}
|
||||
tester := func(parser Parser, cases []testParseMountRaw) {
|
||||
for i, c := range cases {
|
||||
t.Logf("case %d", i)
|
||||
m, err := parser.ParseMountRaw(c.bind, c.driver)
|
||||
if c.fail {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, was nil, for spec %s\n", c.bind)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if m == nil || err != nil {
|
||||
t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Destination != c.expDest {
|
||||
t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
|
||||
}
|
||||
|
||||
if m.Source != c.expSource {
|
||||
t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
|
||||
}
|
||||
|
||||
if m.Name != c.expName {
|
||||
t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
|
||||
}
|
||||
|
||||
if m.Driver != c.expDriver {
|
||||
t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
|
||||
}
|
||||
|
||||
if m.RW != c.expRW {
|
||||
t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
|
||||
}
|
||||
if m.Type != c.expType {
|
||||
t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tester(linParser, linuxCases)
|
||||
tester(winParser, windowsCases)
|
||||
tester(lcowParser, lcowCases)
|
||||
}
|
||||
|
||||
func TestParseMountSpec(t *testing.T) {
|
||||
type c struct {
|
||||
input mount.Mount
|
||||
expected MountPoint
|
||||
}
|
||||
testDir, err := ioutil.TempDir("", "test-mount-config")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(testDir)
|
||||
parser := NewParser(runtime.GOOS)
|
||||
cases := []c{
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}},
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
t.Logf("case %d", i)
|
||||
mp, err := parser.ParseMountSpec(c.input)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if c.expected.Type != mp.Type {
|
||||
t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
|
||||
}
|
||||
if c.expected.Destination != mp.Destination {
|
||||
t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
|
||||
}
|
||||
if c.expected.Source != mp.Source {
|
||||
t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
|
||||
}
|
||||
if c.expected.RW != mp.RW {
|
||||
t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
|
||||
}
|
||||
if c.expected.Propagation != mp.Propagation {
|
||||
t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
|
||||
}
|
||||
if c.expected.Driver != mp.Driver {
|
||||
t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
|
||||
}
|
||||
if c.expected.CopyData != mp.CopyData {
|
||||
t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
|
||||
}
|
||||
}
|
||||
}
|
28
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/validate.go
generated
vendored
Normal file
28
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/validate.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type errMountConfig struct {
|
||||
mount *mount.Mount
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *errMountConfig) Error() string {
|
||||
return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
|
||||
}
|
||||
|
||||
func errBindSourceDoesNotExist(path string) error {
|
||||
return errors.Errorf("bind mount source path does not exist: %s", path)
|
||||
}
|
||||
|
||||
func errExtraField(name string) error {
|
||||
return errors.Errorf("field %s must not be specified", name)
|
||||
}
|
||||
func errMissingField(name string) error {
|
||||
return errors.Errorf("field %s must not be empty", name)
|
||||
}
|
73
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/validate_test.go
generated
vendored
Normal file
73
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/validate_test.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
func TestValidateMount(t *testing.T) {
|
||||
testDir, err := ioutil.TempDir("", "test-validate-mount")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
cases := []struct {
|
||||
input mount.Mount
|
||||
expected error
|
||||
}{
|
||||
{mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath, Source: "hello"}, nil},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, nil},
|
||||
{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
|
||||
{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath}, errMissingField("Source")},
|
||||
{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath, Source: testSourcePath, VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
|
||||
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil},
|
||||
{mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")},
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindSourceDoesNotExist(testSourcePath)},
|
||||
}
|
||||
|
||||
lcowCases := []struct {
|
||||
input mount.Mount
|
||||
expected error
|
||||
}{
|
||||
{mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: "/foo", Source: "hello"}, nil},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: "/foo"}, nil},
|
||||
{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
|
||||
{mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")},
|
||||
{mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
|
||||
{mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindSourceDoesNotExist("c:\\foo")},
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil},
|
||||
{mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")},
|
||||
}
|
||||
parser := NewParser(runtime.GOOS)
|
||||
for i, x := range cases {
|
||||
err := parser.ValidateMountConfig(&x.input)
|
||||
if err == nil && x.expected == nil {
|
||||
continue
|
||||
}
|
||||
if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
|
||||
t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
|
||||
}
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
parser = &lcowParser{}
|
||||
for i, x := range lcowCases {
|
||||
err := parser.ValidateMountConfig(&x.input)
|
||||
if err == nil && x.expected == nil {
|
||||
continue
|
||||
}
|
||||
if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
|
||||
t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/validate_unix_test.go
generated
vendored
Normal file
8
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/validate_unix_test.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
// +build !windows
|
||||
|
||||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
var (
|
||||
testDestinationPath = "/foo"
|
||||
testSourcePath = "/foo"
|
||||
)
|
6
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/validate_windows_test.go
generated
vendored
Normal file
6
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/validate_windows_test.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
var (
|
||||
testDestinationPath = `c:\foo`
|
||||
testSourcePath = `c:\foo`
|
||||
)
|
23
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/volume_copy.go
generated
vendored
Normal file
23
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/volume_copy.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import "strings"
|
||||
|
||||
// {<copy mode>=isEnabled}
|
||||
var copyModes = map[string]bool{
|
||||
"nocopy": false,
|
||||
}
|
||||
|
||||
func copyModeExists(mode string) bool {
|
||||
_, exists := copyModes[mode]
|
||||
return exists
|
||||
}
|
||||
|
||||
// GetCopyMode gets the copy mode from the mode string for mounts
|
||||
func getCopyMode(mode string, def bool) (bool, bool) {
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
if isEnabled, exists := copyModes[o]; exists {
|
||||
return isEnabled, true
|
||||
}
|
||||
}
|
||||
return def, false
|
||||
}
|
18
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/volume_unix.go
generated
vendored
Normal file
18
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/volume_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
// +build linux freebsd darwin
|
||||
|
||||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
|
||||
relPath, err := filepath.Rel(m.Destination, absolutePath)
|
||||
return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
|
||||
}
|
||||
|
||||
func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
|
||||
return false
|
||||
}
|
8
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/volume_windows.go
generated
vendored
Normal file
8
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/volume_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
|
||||
return false
|
||||
}
|
||||
func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
|
||||
return false
|
||||
}
|
456
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/windows_parser.go
generated
vendored
Normal file
456
vendor/github.com/docker/docker-ce/components/engine/volume/mounts/windows_parser.go
generated
vendored
Normal file
|
@ -0,0 +1,456 @@
|
|||
package mounts // import "github.com/docker/docker/volume/mounts"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
type windowsParser struct {
|
||||
}
|
||||
|
||||
const (
|
||||
// Spec should be in the format [source:]destination[:mode]
|
||||
//
|
||||
// Examples: c:\foo bar:d:rw
|
||||
// c:\foo:d:\bar
|
||||
// myname:d:
|
||||
// d:\
|
||||
//
|
||||
// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
|
||||
// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
|
||||
// test is https://regex-golang.appspot.com/assets/html/index.html
|
||||
//
|
||||
// Useful link for referencing named capturing groups:
|
||||
// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
|
||||
//
|
||||
// There are three match groups: source, destination and mode.
|
||||
//
|
||||
|
||||
// rxHostDir is the first option of a source
|
||||
rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*`
|
||||
// rxName is the second option of a source
|
||||
rxName = `[^\\/:*?"<>|\r\n]+`
|
||||
|
||||
// RXReservedNames are reserved names not possible on Windows
|
||||
rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
|
||||
|
||||
// rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
|
||||
rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
|
||||
// rxSource is the combined possibilities for a source
|
||||
rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?`
|
||||
|
||||
// Source. Can be either a host directory, a name, or omitted:
|
||||
// HostDir:
|
||||
// - Essentially using the folder solution from
|
||||
// https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
|
||||
// but adding case insensitivity.
|
||||
// - Must be an absolute path such as c:\path
|
||||
// - Can include spaces such as `c:\program files`
|
||||
// - And then followed by a colon which is not in the capture group
|
||||
// - And can be optional
|
||||
// Name:
|
||||
// - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
|
||||
// - And then followed by a colon which is not in the capture group
|
||||
// - And can be optional
|
||||
|
||||
// rxDestination is the regex expression for the mount destination
|
||||
rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))`
|
||||
|
||||
rxLCOWDestination = `(?P<destination>/(?:[^\\/:*?"<>\r\n]+[/]?)*)`
|
||||
// Destination (aka container path):
|
||||
// - Variation on hostdir but can be a drive followed by colon as well
|
||||
// - If a path, must be absolute. Can include spaces
|
||||
// - Drive cannot be c: (explicitly checked in code, not RegEx)
|
||||
|
||||
// rxMode is the regex expression for the mode of the mount
|
||||
// Mode (optional):
|
||||
// - Hopefully self explanatory in comparison to above regex's.
|
||||
// - Colon is not in the capture group
|
||||
rxMode = `(:(?P<mode>(?i)ro|rw))?`
|
||||
)
|
||||
|
||||
type mountValidator func(mnt *mount.Mount) error
|
||||
|
||||
func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
|
||||
specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`)
|
||||
match := specExp.FindStringSubmatch(strings.ToLower(raw))
|
||||
|
||||
// Must have something back
|
||||
if len(match) == 0 {
|
||||
return nil, errInvalidSpec(raw)
|
||||
}
|
||||
|
||||
var split []string
|
||||
matchgroups := make(map[string]string)
|
||||
// Pull out the sub expressions from the named capture groups
|
||||
for i, name := range specExp.SubexpNames() {
|
||||
matchgroups[name] = strings.ToLower(match[i])
|
||||
}
|
||||
if source, exists := matchgroups["source"]; exists {
|
||||
if source != "" {
|
||||
split = append(split, source)
|
||||
}
|
||||
}
|
||||
if destination, exists := matchgroups["destination"]; exists {
|
||||
if destination != "" {
|
||||
split = append(split, destination)
|
||||
}
|
||||
}
|
||||
if mode, exists := matchgroups["mode"]; exists {
|
||||
if mode != "" {
|
||||
split = append(split, mode)
|
||||
}
|
||||
}
|
||||
// Fix #26329. If the destination appears to be a file, and the source is null,
|
||||
// it may be because we've fallen through the possible naming regex and hit a
|
||||
// situation where the user intention was to map a file into a container through
|
||||
// a local volume, but this is not supported by the platform.
|
||||
if matchgroups["source"] == "" && matchgroups["destination"] != "" {
|
||||
volExp := regexp.MustCompile(`^` + rxName + `$`)
|
||||
reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`)
|
||||
|
||||
if volExp.MatchString(matchgroups["destination"]) {
|
||||
if reservedNameExp.MatchString(matchgroups["destination"]) {
|
||||
return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
|
||||
}
|
||||
} else {
|
||||
|
||||
exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"])
|
||||
if exists && !isDir {
|
||||
return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return split, nil
|
||||
}
|
||||
|
||||
func windowsValidMountMode(mode string) bool {
|
||||
if mode == "" {
|
||||
return true
|
||||
}
|
||||
return rwModes[strings.ToLower(mode)]
|
||||
}
|
||||
func windowsValidateNotRoot(p string) error {
|
||||
p = strings.ToLower(strings.Replace(p, `/`, `\`, -1))
|
||||
if p == "c:" || p == `c:\` {
|
||||
return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var windowsSpecificValidators mountValidator = func(mnt *mount.Mount) error {
|
||||
return windowsValidateNotRoot(mnt.Target)
|
||||
}
|
||||
|
||||
func windowsValidateRegex(p, r string) error {
|
||||
if regexp.MustCompile(`^` + r + `$`).MatchString(strings.ToLower(p)) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid mount path: '%s'", p)
|
||||
}
|
||||
func windowsValidateAbsolute(p string) error {
|
||||
if err := windowsValidateRegex(p, rxDestination); err != nil {
|
||||
return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func windowsDetectMountType(p string) mount.Type {
|
||||
if strings.HasPrefix(p, `\\.\pipe\`) {
|
||||
return mount.TypeNamedPipe
|
||||
} else if regexp.MustCompile(`^` + rxHostDir + `$`).MatchString(p) {
|
||||
return mount.TypeBind
|
||||
} else {
|
||||
return mount.TypeVolume
|
||||
}
|
||||
}
|
||||
|
||||
func (p *windowsParser) ReadWrite(mode string) bool {
|
||||
return strings.ToLower(mode) != "ro"
|
||||
}
|
||||
|
||||
// IsVolumeNameValid checks a volume name in a platform specific manner.
|
||||
func (p *windowsParser) ValidateVolumeName(name string) error {
|
||||
nameExp := regexp.MustCompile(`^` + rxName + `$`)
|
||||
if !nameExp.MatchString(name) {
|
||||
return errors.New("invalid volume name")
|
||||
}
|
||||
nameExp = regexp.MustCompile(`^` + rxReservedNames + `$`)
|
||||
if nameExp.MatchString(name) {
|
||||
return fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (p *windowsParser) ValidateMountConfig(mnt *mount.Mount) error {
|
||||
return p.validateMountConfigReg(mnt, rxDestination, windowsSpecificValidators)
|
||||
}
|
||||
|
||||
type fileInfoProvider interface {
|
||||
fileInfo(path string) (exist, isDir bool, err error)
|
||||
}
|
||||
|
||||
type defaultFileInfoProvider struct {
|
||||
}
|
||||
|
||||
func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return false, false, err
|
||||
}
|
||||
return false, false, nil
|
||||
}
|
||||
return true, fi.IsDir(), nil
|
||||
}
|
||||
|
||||
var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}
|
||||
|
||||
func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, destRegex string, additionalValidators ...mountValidator) error {
|
||||
|
||||
for _, v := range additionalValidators {
|
||||
if err := v(mnt); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
}
|
||||
if len(mnt.Target) == 0 {
|
||||
return &errMountConfig{mnt, errMissingField("Target")}
|
||||
}
|
||||
|
||||
if err := windowsValidateRegex(mnt.Target, destRegex); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
|
||||
switch mnt.Type {
|
||||
case mount.TypeBind:
|
||||
if len(mnt.Source) == 0 {
|
||||
return &errMountConfig{mnt, errMissingField("Source")}
|
||||
}
|
||||
// Don't error out just because the propagation mode is not supported on the platform
|
||||
if opts := mnt.BindOptions; opts != nil {
|
||||
if len(opts.Propagation) > 0 {
|
||||
return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
|
||||
}
|
||||
}
|
||||
if mnt.VolumeOptions != nil {
|
||||
return &errMountConfig{mnt, errExtraField("VolumeOptions")}
|
||||
}
|
||||
|
||||
if err := windowsValidateAbsolute(mnt.Source); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
|
||||
exists, isdir, err := currentFileInfoProvider.fileInfo(mnt.Source)
|
||||
if err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
if !exists {
|
||||
return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
|
||||
}
|
||||
if !isdir {
|
||||
return &errMountConfig{mnt, fmt.Errorf("source path must be a directory")}
|
||||
}
|
||||
|
||||
case mount.TypeVolume:
|
||||
if mnt.BindOptions != nil {
|
||||
return &errMountConfig{mnt, errExtraField("BindOptions")}
|
||||
}
|
||||
|
||||
if len(mnt.Source) == 0 && mnt.ReadOnly {
|
||||
return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
||||
}
|
||||
|
||||
if len(mnt.Source) != 0 {
|
||||
if err := p.ValidateVolumeName(mnt.Source); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
}
|
||||
case mount.TypeNamedPipe:
|
||||
if len(mnt.Source) == 0 {
|
||||
return &errMountConfig{mnt, errMissingField("Source")}
|
||||
}
|
||||
|
||||
if mnt.BindOptions != nil {
|
||||
return &errMountConfig{mnt, errExtraField("BindOptions")}
|
||||
}
|
||||
|
||||
if mnt.ReadOnly {
|
||||
return &errMountConfig{mnt, errExtraField("ReadOnly")}
|
||||
}
|
||||
|
||||
if windowsDetectMountType(mnt.Source) != mount.TypeNamedPipe {
|
||||
return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
|
||||
}
|
||||
|
||||
if windowsDetectMountType(mnt.Target) != mount.TypeNamedPipe {
|
||||
return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
|
||||
}
|
||||
default:
|
||||
return &errMountConfig{mnt, errors.New("mount type unknown")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (p *windowsParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
||||
return p.parseMountRaw(raw, volumeDriver, rxDestination, true, windowsSpecificValidators)
|
||||
}
|
||||
|
||||
func (p *windowsParser) parseMountRaw(raw, volumeDriver, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
|
||||
arr, err := windowsSplitRawSpec(raw, destRegex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var spec mount.Mount
|
||||
var mode string
|
||||
switch len(arr) {
|
||||
case 1:
|
||||
// Just a destination path in the container
|
||||
spec.Target = arr[0]
|
||||
case 2:
|
||||
if windowsValidMountMode(arr[1]) {
|
||||
// Destination + Mode is not a valid volume - volumes
|
||||
// cannot include a mode. e.g. /foo:rw
|
||||
return nil, errInvalidSpec(raw)
|
||||
}
|
||||
// Host Source Path or Name + Destination
|
||||
spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
|
||||
spec.Target = arr[1]
|
||||
case 3:
|
||||
// HostSourcePath+DestinationPath+Mode
|
||||
spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
|
||||
spec.Target = arr[1]
|
||||
mode = arr[2]
|
||||
default:
|
||||
return nil, errInvalidSpec(raw)
|
||||
}
|
||||
if convertTargetToBackslash {
|
||||
spec.Target = strings.Replace(spec.Target, `/`, `\`, -1)
|
||||
}
|
||||
|
||||
if !windowsValidMountMode(mode) {
|
||||
return nil, errInvalidMode(mode)
|
||||
}
|
||||
|
||||
spec.Type = windowsDetectMountType(spec.Source)
|
||||
spec.ReadOnly = !p.ReadWrite(mode)
|
||||
|
||||
// cannot assume that if a volume driver is passed in that we should set it
|
||||
if volumeDriver != "" && spec.Type == mount.TypeVolume {
|
||||
spec.VolumeOptions = &mount.VolumeOptions{
|
||||
DriverConfig: &mount.Driver{Name: volumeDriver},
|
||||
}
|
||||
}
|
||||
|
||||
if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
||||
if spec.VolumeOptions == nil {
|
||||
spec.VolumeOptions = &mount.VolumeOptions{}
|
||||
}
|
||||
spec.VolumeOptions.NoCopy = !copyData
|
||||
}
|
||||
|
||||
mp, err := p.parseMountSpec(spec, destRegex, convertTargetToBackslash, additionalValidators...)
|
||||
if mp != nil {
|
||||
mp.Mode = mode
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
|
||||
}
|
||||
return mp, err
|
||||
}
|
||||
|
||||
func (p *windowsParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
||||
return p.parseMountSpec(cfg, rxDestination, true, windowsSpecificValidators)
|
||||
}
|
||||
func (p *windowsParser) parseMountSpec(cfg mount.Mount, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
|
||||
if err := p.validateMountConfigReg(&cfg, destRegex, additionalValidators...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp := &MountPoint{
|
||||
RW: !cfg.ReadOnly,
|
||||
Destination: cfg.Target,
|
||||
Type: cfg.Type,
|
||||
Spec: cfg,
|
||||
}
|
||||
if convertTargetToBackslash {
|
||||
mp.Destination = strings.Replace(cfg.Target, `/`, `\`, -1)
|
||||
}
|
||||
|
||||
switch cfg.Type {
|
||||
case mount.TypeVolume:
|
||||
if cfg.Source == "" {
|
||||
mp.Name = stringid.GenerateNonCryptoID()
|
||||
} else {
|
||||
mp.Name = cfg.Source
|
||||
}
|
||||
mp.CopyData = p.DefaultCopyMode()
|
||||
|
||||
if cfg.VolumeOptions != nil {
|
||||
if cfg.VolumeOptions.DriverConfig != nil {
|
||||
mp.Driver = cfg.VolumeOptions.DriverConfig.Name
|
||||
}
|
||||
if cfg.VolumeOptions.NoCopy {
|
||||
mp.CopyData = false
|
||||
}
|
||||
}
|
||||
case mount.TypeBind:
|
||||
mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
|
||||
case mount.TypeNamedPipe:
|
||||
mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
|
||||
}
|
||||
// cleanup trailing `\` except for paths like `c:\`
|
||||
if len(mp.Source) > 3 && mp.Source[len(mp.Source)-1] == '\\' {
|
||||
mp.Source = mp.Source[:len(mp.Source)-1]
|
||||
}
|
||||
if len(mp.Destination) > 3 && mp.Destination[len(mp.Destination)-1] == '\\' {
|
||||
mp.Destination = mp.Destination[:len(mp.Destination)-1]
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) {
|
||||
if len(spec) == 0 {
|
||||
return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
|
||||
}
|
||||
|
||||
specParts := strings.SplitN(spec, ":", 2)
|
||||
id := specParts[0]
|
||||
mode := "rw"
|
||||
|
||||
if len(specParts) == 2 {
|
||||
mode = specParts[1]
|
||||
if !windowsValidMountMode(mode) {
|
||||
return "", "", errInvalidMode(mode)
|
||||
}
|
||||
|
||||
// Do not allow copy modes on volumes-from
|
||||
if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
||||
return "", "", errInvalidMode(mode)
|
||||
}
|
||||
}
|
||||
return id, mode, nil
|
||||
}
|
||||
|
||||
func (p *windowsParser) DefaultPropagationMode() mount.Propagation {
|
||||
return mount.Propagation("")
|
||||
}
|
||||
|
||||
func (p *windowsParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
|
||||
return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
|
||||
}
|
||||
func (p *windowsParser) DefaultCopyMode() bool {
|
||||
return false
|
||||
}
|
||||
func (p *windowsParser) IsBackwardCompatible(m *MountPoint) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsParser) ValidateTmpfsMountDestination(dest string) error {
|
||||
return errors.New("Platform does not support tmpfs")
|
||||
}
|
89
vendor/github.com/docker/docker-ce/components/engine/volume/service/by.go
generated
vendored
Normal file
89
vendor/github.com/docker/docker-ce/components/engine/volume/service/by.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
// By is an interface which is used to implement filtering on volumes.
|
||||
type By interface {
|
||||
isBy()
|
||||
}
|
||||
|
||||
// ByDriver is `By` that filters based on the driver names that are passed in
|
||||
func ByDriver(drivers ...string) By {
|
||||
return byDriver(drivers)
|
||||
}
|
||||
|
||||
type byDriver []string
|
||||
|
||||
func (byDriver) isBy() {}
|
||||
|
||||
// ByReferenced is a `By` that filters based on if the volume has references
|
||||
type ByReferenced bool
|
||||
|
||||
func (ByReferenced) isBy() {}
|
||||
|
||||
// And creates a `By` combining all the passed in bys using AND logic.
|
||||
func And(bys ...By) By {
|
||||
and := make(andCombinator, 0, len(bys))
|
||||
for _, by := range bys {
|
||||
and = append(and, by)
|
||||
}
|
||||
return and
|
||||
}
|
||||
|
||||
type andCombinator []By
|
||||
|
||||
func (andCombinator) isBy() {}
|
||||
|
||||
// Or creates a `By` combining all the passed in bys using OR logic.
|
||||
func Or(bys ...By) By {
|
||||
or := make(orCombinator, 0, len(bys))
|
||||
for _, by := range bys {
|
||||
or = append(or, by)
|
||||
}
|
||||
return or
|
||||
}
|
||||
|
||||
type orCombinator []By
|
||||
|
||||
func (orCombinator) isBy() {}
|
||||
|
||||
// CustomFilter is a `By` that is used by callers to provide custom filtering
|
||||
// logic.
|
||||
type CustomFilter filterFunc
|
||||
|
||||
func (CustomFilter) isBy() {}
|
||||
|
||||
// FromList returns a By which sets the initial list of volumes to use
|
||||
func FromList(ls *[]volume.Volume, by By) By {
|
||||
return &fromList{by: by, ls: ls}
|
||||
}
|
||||
|
||||
type fromList struct {
|
||||
by By
|
||||
ls *[]volume.Volume
|
||||
}
|
||||
|
||||
func (fromList) isBy() {}
|
||||
|
||||
func byLabelFilter(filter filters.Args) By {
|
||||
return CustomFilter(func(v volume.Volume) bool {
|
||||
dv, ok := v.(volume.DetailedVolume)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
labels := dv.Labels()
|
||||
if !filter.MatchKVList("label", labels) {
|
||||
return false
|
||||
}
|
||||
if filter.Contains("label!") {
|
||||
if filter.MatchKVList("label!", labels) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
132
vendor/github.com/docker/docker-ce/components/engine/volume/service/convert.go
generated
vendored
Normal file
132
vendor/github.com/docker/docker-ce/components/engine/volume/service/convert.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/pkg/directory"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// convertOpts are used to pass options to `volumeToAPI`
|
||||
type convertOpt interface {
|
||||
isConvertOpt()
|
||||
}
|
||||
|
||||
type useCachedPath bool
|
||||
|
||||
func (useCachedPath) isConvertOpt() {}
|
||||
|
||||
type calcSize bool
|
||||
|
||||
func (calcSize) isConvertOpt() {}
|
||||
|
||||
type pathCacher interface {
|
||||
CachedPath() string
|
||||
}
|
||||
|
||||
func (s *VolumesService) volumesToAPI(ctx context.Context, volumes []volume.Volume, opts ...convertOpt) []*types.Volume {
|
||||
var (
|
||||
out = make([]*types.Volume, 0, len(volumes))
|
||||
getSize bool
|
||||
cachedPath bool
|
||||
)
|
||||
|
||||
for _, o := range opts {
|
||||
switch t := o.(type) {
|
||||
case calcSize:
|
||||
getSize = bool(t)
|
||||
case useCachedPath:
|
||||
cachedPath = bool(t)
|
||||
}
|
||||
}
|
||||
for _, v := range volumes {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
apiV := volumeToAPIType(v)
|
||||
|
||||
if cachedPath {
|
||||
if vv, ok := v.(pathCacher); ok {
|
||||
apiV.Mountpoint = vv.CachedPath()
|
||||
}
|
||||
} else {
|
||||
apiV.Mountpoint = v.Path()
|
||||
}
|
||||
|
||||
if getSize {
|
||||
p := v.Path()
|
||||
if apiV.Mountpoint == "" {
|
||||
apiV.Mountpoint = p
|
||||
}
|
||||
sz, err := directory.Size(ctx, p)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("volume", v.Name()).Warnf("Failed to determine size of volume")
|
||||
sz = -1
|
||||
}
|
||||
apiV.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(s.vs.CountReferences(v))}
|
||||
}
|
||||
|
||||
out = append(out, &apiV)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func volumeToAPIType(v volume.Volume) types.Volume {
|
||||
createdAt, _ := v.CreatedAt()
|
||||
tv := types.Volume{
|
||||
Name: v.Name(),
|
||||
Driver: v.DriverName(),
|
||||
CreatedAt: createdAt.Format(time.RFC3339),
|
||||
}
|
||||
if v, ok := v.(volume.DetailedVolume); ok {
|
||||
tv.Labels = v.Labels()
|
||||
tv.Options = v.Options()
|
||||
tv.Scope = v.Scope()
|
||||
}
|
||||
if cp, ok := v.(pathCacher); ok {
|
||||
tv.Mountpoint = cp.CachedPath()
|
||||
}
|
||||
return tv
|
||||
}
|
||||
|
||||
func filtersToBy(filter filters.Args, acceptedFilters map[string]bool) (By, error) {
|
||||
if err := filter.Validate(acceptedFilters); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var bys []By
|
||||
if drivers := filter.Get("driver"); len(drivers) > 0 {
|
||||
bys = append(bys, ByDriver(drivers...))
|
||||
}
|
||||
if filter.Contains("name") {
|
||||
bys = append(bys, CustomFilter(func(v volume.Volume) bool {
|
||||
return filter.Match("name", v.Name())
|
||||
}))
|
||||
}
|
||||
bys = append(bys, byLabelFilter(filter))
|
||||
|
||||
if filter.Contains("dangling") {
|
||||
var dangling bool
|
||||
if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
|
||||
dangling = true
|
||||
} else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
|
||||
return nil, invalidFilter{"dangling", filter.Get("dangling")}
|
||||
}
|
||||
bys = append(bys, ByReferenced(!dangling))
|
||||
}
|
||||
|
||||
var by By
|
||||
switch len(bys) {
|
||||
case 0:
|
||||
case 1:
|
||||
by = bys[0]
|
||||
default:
|
||||
by = And(bys...)
|
||||
}
|
||||
return by, nil
|
||||
}
|
95
vendor/github.com/docker/docker-ce/components/engine/volume/service/db.go
generated
vendored
Normal file
95
vendor/github.com/docker/docker-ce/components/engine/volume/service/db.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var volumeBucketName = []byte("volumes")
|
||||
|
||||
type volumeMetadata struct {
|
||||
Name string
|
||||
Driver string
|
||||
Labels map[string]string
|
||||
Options map[string]string
|
||||
}
|
||||
|
||||
func (s *VolumeStore) setMeta(name string, meta volumeMetadata) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
return setMeta(tx, name, meta)
|
||||
})
|
||||
}
|
||||
|
||||
func setMeta(tx *bolt.Tx, name string, meta volumeMetadata) error {
|
||||
metaJSON, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := tx.CreateBucketIfNotExists(volumeBucketName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating volume bucket")
|
||||
}
|
||||
return errors.Wrap(b.Put([]byte(name), metaJSON), "error setting volume metadata")
|
||||
}
|
||||
|
||||
func (s *VolumeStore) getMeta(name string) (volumeMetadata, error) {
|
||||
var meta volumeMetadata
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
return getMeta(tx, name, &meta)
|
||||
})
|
||||
return meta, err
|
||||
}
|
||||
|
||||
func getMeta(tx *bolt.Tx, name string, meta *volumeMetadata) error {
|
||||
b := tx.Bucket(volumeBucketName)
|
||||
if b == nil {
|
||||
return errdefs.NotFound(errors.New("volume bucket does not exist"))
|
||||
}
|
||||
val := b.Get([]byte(name))
|
||||
if len(val) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := json.Unmarshal(val, meta); err != nil {
|
||||
return errors.Wrap(err, "error unmarshaling volume metadata")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *VolumeStore) removeMeta(name string) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
return removeMeta(tx, name)
|
||||
})
|
||||
}
|
||||
|
||||
func removeMeta(tx *bolt.Tx, name string) error {
|
||||
b := tx.Bucket(volumeBucketName)
|
||||
return errors.Wrap(b.Delete([]byte(name)), "error removing volume metadata")
|
||||
}
|
||||
|
||||
// listMeta is used during restore to get the list of volume metadata
|
||||
// from the on-disk database.
|
||||
// Any errors that occur are only logged.
|
||||
func listMeta(tx *bolt.Tx) []volumeMetadata {
|
||||
var ls []volumeMetadata
|
||||
b := tx.Bucket(volumeBucketName)
|
||||
b.ForEach(func(k, v []byte) error {
|
||||
if len(v) == 0 {
|
||||
// don't try to unmarshal an empty value
|
||||
return nil
|
||||
}
|
||||
|
||||
var m volumeMetadata
|
||||
if err := json.Unmarshal(v, &m); err != nil {
|
||||
// Just log the error
|
||||
logrus.Errorf("Error while reading volume metadata for volume %q: %v", string(k), err)
|
||||
return nil
|
||||
}
|
||||
ls = append(ls, m)
|
||||
return nil
|
||||
})
|
||||
return ls
|
||||
}
|
52
vendor/github.com/docker/docker-ce/components/engine/volume/service/db_test.go
generated
vendored
Normal file
52
vendor/github.com/docker/docker-ce/components/engine/volume/service/db_test.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"gotest.tools/assert"
|
||||
is "gotest.tools/assert/cmp"
|
||||
)
|
||||
|
||||
func TestSetGetMeta(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir, err := ioutil.TempDir("", "test-set-get")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
db, err := bolt.Open(filepath.Join(dir, "db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
assert.NilError(t, err)
|
||||
|
||||
store := &VolumeStore{db: db}
|
||||
|
||||
_, err = store.getMeta("test")
|
||||
assert.Assert(t, is.ErrorContains(err, ""))
|
||||
|
||||
err = db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucket(volumeBucketName)
|
||||
return err
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
meta, err := store.getMeta("test")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, volumeMetadata{}, meta)
|
||||
|
||||
testMeta := volumeMetadata{
|
||||
Name: "test",
|
||||
Driver: "fake",
|
||||
Labels: map[string]string{"a": "1", "b": "2"},
|
||||
Options: map[string]string{"foo": "bar"},
|
||||
}
|
||||
err = store.setMeta("test", testMeta)
|
||||
assert.NilError(t, err)
|
||||
|
||||
meta, err = store.getMeta("test")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, testMeta, meta)
|
||||
}
|
21
vendor/github.com/docker/docker-ce/components/engine/volume/service/default_driver.go
generated
vendored
Normal file
21
vendor/github.com/docker/docker-ce/components/engine/volume/service/default_driver.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// +build linux windows
|
||||
|
||||
package service // import "github.com/docker/docker/volume/service"
|
||||
import (
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/local"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func setupDefaultDriver(store *drivers.Store, root string, rootIDs idtools.Identity) error {
|
||||
d, err := local.New(root, rootIDs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up default driver")
|
||||
}
|
||||
if !store.Register(d, volume.DefaultDriverName) {
|
||||
return errors.New("local volume driver could not be registered")
|
||||
}
|
||||
return nil
|
||||
}
|
10
vendor/github.com/docker/docker-ce/components/engine/volume/service/default_driver_stubs.go
generated
vendored
Normal file
10
vendor/github.com/docker/docker-ce/components/engine/volume/service/default_driver_stubs.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
// +build !linux,!windows
|
||||
|
||||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
)
|
||||
|
||||
func setupDefaultDriver(_ *drivers.Store, _ string, _ idtools.Identity) error { return nil }
|
111
vendor/github.com/docker/docker-ce/components/engine/volume/service/errors.go
generated
vendored
Normal file
111
vendor/github.com/docker/docker-ce/components/engine/volume/service/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// errVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container
|
||||
errVolumeInUse conflictError = "volume is in use"
|
||||
// errNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
|
||||
errNoSuchVolume notFoundError = "no such volume"
|
||||
// errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver
|
||||
errNameConflict conflictError = "volume name must be unique"
|
||||
)
|
||||
|
||||
type conflictError string
|
||||
|
||||
func (e conflictError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
func (conflictError) Conflict() {}
|
||||
|
||||
type notFoundError string
|
||||
|
||||
func (e notFoundError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (notFoundError) NotFound() {}
|
||||
|
||||
// OpErr is the error type returned by functions in the store package. It describes
|
||||
// the operation, volume name, and error.
|
||||
type OpErr struct {
|
||||
// Err is the error that occurred during the operation.
|
||||
Err error
|
||||
// Op is the operation which caused the error, such as "create", or "list".
|
||||
Op string
|
||||
// Name is the name of the resource being requested for this op, typically the volume name or the driver name.
|
||||
Name string
|
||||
// Refs is the list of references associated with the resource.
|
||||
Refs []string
|
||||
}
|
||||
|
||||
// Error satisfies the built-in error interface type.
|
||||
func (e *OpErr) Error() string {
|
||||
if e == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
s := e.Op
|
||||
if e.Name != "" {
|
||||
s = s + " " + e.Name
|
||||
}
|
||||
|
||||
s = s + ": " + e.Err.Error()
|
||||
if len(e.Refs) > 0 {
|
||||
s = s + " - " + "[" + strings.Join(e.Refs, ", ") + "]"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Cause returns the error the caused this error
|
||||
func (e *OpErr) Cause() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// IsInUse returns a boolean indicating whether the error indicates that a
|
||||
// volume is in use
|
||||
func IsInUse(err error) bool {
|
||||
return isErr(err, errVolumeInUse)
|
||||
}
|
||||
|
||||
// IsNotExist returns a boolean indicating whether the error indicates that the volume does not exist
|
||||
func IsNotExist(err error) bool {
|
||||
return isErr(err, errNoSuchVolume)
|
||||
}
|
||||
|
||||
// IsNameConflict returns a boolean indicating whether the error indicates that a
|
||||
// volume name is already taken
|
||||
func IsNameConflict(err error) bool {
|
||||
return isErr(err, errNameConflict)
|
||||
}
|
||||
|
||||
type causal interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
func isErr(err error, expected error) bool {
|
||||
switch pe := err.(type) {
|
||||
case nil:
|
||||
return false
|
||||
case causal:
|
||||
return isErr(pe.Cause(), expected)
|
||||
}
|
||||
return err == expected
|
||||
}
|
||||
|
||||
type invalidFilter struct {
|
||||
filter string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (e invalidFilter) Error() string {
|
||||
msg := "Invalid filter '" + e.filter
|
||||
if e.value != nil {
|
||||
msg += fmt.Sprintf("=%s", e.value)
|
||||
}
|
||||
return msg + "'"
|
||||
}
|
||||
|
||||
func (e invalidFilter) InvalidParameter() {}
|
89
vendor/github.com/docker/docker-ce/components/engine/volume/service/opts/opts.go
generated
vendored
Normal file
89
vendor/github.com/docker/docker-ce/components/engine/volume/service/opts/opts.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
package opts
|
||||
|
||||
// CreateOption is used to pass options in when creating a volume
|
||||
type CreateOption func(*CreateConfig)
|
||||
|
||||
// CreateConfig is the set of config options that can be set when creating
|
||||
// a volume
|
||||
type CreateConfig struct {
|
||||
Options map[string]string
|
||||
Labels map[string]string
|
||||
Reference string
|
||||
}
|
||||
|
||||
// WithCreateLabels creates a CreateOption which sets the labels to the
|
||||
// passed in value
|
||||
func WithCreateLabels(labels map[string]string) CreateOption {
|
||||
return func(cfg *CreateConfig) {
|
||||
cfg.Labels = labels
|
||||
}
|
||||
}
|
||||
|
||||
// WithCreateOptions creates a CreateOption which sets the options passed
|
||||
// to the volume driver when creating a volume to the options passed in.
|
||||
func WithCreateOptions(opts map[string]string) CreateOption {
|
||||
return func(cfg *CreateConfig) {
|
||||
cfg.Options = opts
|
||||
}
|
||||
}
|
||||
|
||||
// WithCreateReference creats a CreateOption which sets a reference to use
|
||||
// when creating a volume. This ensures that the volume is created with a reference
|
||||
// already attached to it to prevent race conditions with Create and volume cleanup.
|
||||
func WithCreateReference(ref string) CreateOption {
|
||||
return func(cfg *CreateConfig) {
|
||||
cfg.Reference = ref
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfig is used with `GetOption` to set options for the volumes service's
|
||||
// `Get` implementation.
|
||||
type GetConfig struct {
|
||||
Driver string
|
||||
Reference string
|
||||
ResolveStatus bool
|
||||
}
|
||||
|
||||
// GetOption is passed to the service `Get` add extra details on the get request
|
||||
type GetOption func(*GetConfig)
|
||||
|
||||
// WithGetDriver provides the driver to get the volume from
|
||||
// If no driver is provided to `Get`, first the available metadata is checked
|
||||
// to see which driver it belongs to, if that is not available all drivers are
|
||||
// probed to find the volume.
|
||||
func WithGetDriver(name string) GetOption {
|
||||
return func(o *GetConfig) {
|
||||
o.Driver = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithGetReference indicates to `Get` to increment the reference count for the
|
||||
// retreived volume with the provided reference ID.
|
||||
func WithGetReference(ref string) GetOption {
|
||||
return func(o *GetConfig) {
|
||||
o.Reference = ref
|
||||
}
|
||||
}
|
||||
|
||||
// WithGetResolveStatus indicates to `Get` to also fetch the volume status.
|
||||
// This can cause significant overhead in the volume lookup.
|
||||
func WithGetResolveStatus(cfg *GetConfig) {
|
||||
cfg.ResolveStatus = true
|
||||
}
|
||||
|
||||
// RemoveConfig is used by `RemoveOption` to store config options for remove
|
||||
type RemoveConfig struct {
|
||||
PurgeOnError bool
|
||||
}
|
||||
|
||||
// RemoveOption is used to pass options to the volumes service `Remove` implementation
|
||||
type RemoveOption func(*RemoveConfig)
|
||||
|
||||
// WithPurgeOnError is an option passed to `Remove` which will purge all cached
|
||||
// data about a volume even if there was an error while attempting to remove the
|
||||
// volume.
|
||||
func WithPurgeOnError(b bool) RemoveOption {
|
||||
return func(o *RemoveConfig) {
|
||||
o.PurgeOnError = b
|
||||
}
|
||||
}
|
85
vendor/github.com/docker/docker-ce/components/engine/volume/service/restore.go
generated
vendored
Normal file
85
vendor/github.com/docker/docker-ce/components/engine/volume/service/restore.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// restore is called when a new volume store is created.
|
||||
// It's primary purpose is to ensure that all drivers' refcounts are set based
|
||||
// on known volumes after a restart.
|
||||
// This only attempts to track volumes that are actually stored in the on-disk db.
|
||||
// It does not probe the available drivers to find anything that may have been added
|
||||
// out of band.
|
||||
func (s *VolumeStore) restore() {
|
||||
var ls []volumeMetadata
|
||||
s.db.View(func(tx *bolt.Tx) error {
|
||||
ls = listMeta(tx)
|
||||
return nil
|
||||
})
|
||||
ctx := context.Background()
|
||||
|
||||
chRemove := make(chan *volumeMetadata, len(ls))
|
||||
var wg sync.WaitGroup
|
||||
for _, meta := range ls {
|
||||
wg.Add(1)
|
||||
// this is potentially a very slow operation, so do it in a goroutine
|
||||
go func(meta volumeMetadata) {
|
||||
defer wg.Done()
|
||||
|
||||
var v volume.Volume
|
||||
var err error
|
||||
if meta.Driver != "" {
|
||||
v, err = lookupVolume(ctx, s.drivers, meta.Driver, meta.Name)
|
||||
if err != nil && err != errNoSuchVolume {
|
||||
logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
|
||||
return
|
||||
}
|
||||
if v == nil {
|
||||
// doesn't exist in the driver, remove it from the db
|
||||
chRemove <- &meta
|
||||
return
|
||||
}
|
||||
} else {
|
||||
v, err = s.getVolume(ctx, meta.Name, meta.Driver)
|
||||
if err != nil {
|
||||
if err == errNoSuchVolume {
|
||||
chRemove <- &meta
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
meta.Driver = v.DriverName()
|
||||
if err := s.setMeta(v.Name(), meta); err != nil {
|
||||
logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", v.Name()).Warn("Error updating volume metadata on restore")
|
||||
}
|
||||
}
|
||||
|
||||
// increment driver refcount
|
||||
s.drivers.CreateDriver(meta.Driver)
|
||||
|
||||
// cache the volume
|
||||
s.globalLock.Lock()
|
||||
s.options[v.Name()] = meta.Options
|
||||
s.labels[v.Name()] = meta.Labels
|
||||
s.names[v.Name()] = v
|
||||
s.refs[v.Name()] = make(map[string]struct{})
|
||||
s.globalLock.Unlock()
|
||||
}(meta)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(chRemove)
|
||||
s.db.Update(func(tx *bolt.Tx) error {
|
||||
for meta := range chRemove {
|
||||
if err := removeMeta(tx, meta.Name); err != nil {
|
||||
logrus.WithField("volume", meta.Name).Warnf("Error removing stale entry from volume db: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
58
vendor/github.com/docker/docker-ce/components/engine/volume/service/restore_test.go
generated
vendored
Normal file
58
vendor/github.com/docker/docker-ce/components/engine/volume/service/restore_test.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/volume"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/service/opts"
|
||||
volumetestutils "github.com/docker/docker/volume/testutils"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestRestore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir, err := ioutil.TempDir("", "test-restore")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
drivers := volumedrivers.NewStore(nil)
|
||||
driverName := "test-restore"
|
||||
drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
||||
|
||||
s, err := NewStore(dir, drivers)
|
||||
assert.NilError(t, err)
|
||||
defer s.Shutdown()
|
||||
|
||||
ctx := context.Background()
|
||||
_, err = s.Create(ctx, "test1", driverName)
|
||||
assert.NilError(t, err)
|
||||
|
||||
testLabels := map[string]string{"a": "1"}
|
||||
testOpts := map[string]string{"foo": "bar"}
|
||||
_, err = s.Create(ctx, "test2", driverName, opts.WithCreateOptions(testOpts), opts.WithCreateLabels(testLabels))
|
||||
assert.NilError(t, err)
|
||||
|
||||
s.Shutdown()
|
||||
|
||||
s, err = NewStore(dir, drivers)
|
||||
assert.NilError(t, err)
|
||||
|
||||
v, err := s.Get(ctx, "test1")
|
||||
assert.NilError(t, err)
|
||||
|
||||
dv := v.(volume.DetailedVolume)
|
||||
var nilMap map[string]string
|
||||
assert.DeepEqual(t, nilMap, dv.Options())
|
||||
assert.DeepEqual(t, nilMap, dv.Labels())
|
||||
|
||||
v, err = s.Get(ctx, "test2")
|
||||
assert.NilError(t, err)
|
||||
dv = v.(volume.DetailedVolume)
|
||||
assert.DeepEqual(t, testOpts, dv.Options())
|
||||
assert.DeepEqual(t, testLabels, dv.Labels())
|
||||
}
|
243
vendor/github.com/docker/docker-ce/components/engine/volume/service/service.go
generated
vendored
Normal file
243
vendor/github.com/docker/docker-ce/components/engine/volume/service/service.go
generated
vendored
Normal file
|
@ -0,0 +1,243 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/directory"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/service/opts"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ds interface {
|
||||
GetDriverList() []string
|
||||
}
|
||||
|
||||
type volumeEventLogger interface {
|
||||
LogVolumeEvent(volumeID, action string, attributes map[string]string)
|
||||
}
|
||||
|
||||
// VolumesService manages access to volumes
|
||||
type VolumesService struct {
|
||||
vs *VolumeStore
|
||||
ds ds
|
||||
pruneRunning int32
|
||||
eventLogger volumeEventLogger
|
||||
}
|
||||
|
||||
// NewVolumeService creates a new volume service
|
||||
func NewVolumeService(root string, pg plugingetter.PluginGetter, rootIDs idtools.Identity, logger volumeEventLogger) (*VolumesService, error) {
|
||||
ds := drivers.NewStore(pg)
|
||||
if err := setupDefaultDriver(ds, root, rootIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vs, err := NewStore(root, ds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &VolumesService{vs: vs, ds: ds, eventLogger: logger}, nil
|
||||
}
|
||||
|
||||
// GetDriverList gets the list of registered volume drivers
|
||||
func (s *VolumesService) GetDriverList() []string {
|
||||
return s.ds.GetDriverList()
|
||||
}
|
||||
|
||||
// Create creates a volume
|
||||
func (s *VolumesService) Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error) {
|
||||
if name == "" {
|
||||
name = stringid.GenerateNonCryptoID()
|
||||
}
|
||||
v, err := s.vs.Create(ctx, name, driverName, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.eventLogger.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
|
||||
apiV := volumeToAPIType(v)
|
||||
return &apiV, nil
|
||||
}
|
||||
|
||||
// Get gets a volume
|
||||
func (s *VolumesService) Get(ctx context.Context, name string, getOpts ...opts.GetOption) (*types.Volume, error) {
|
||||
v, err := s.vs.Get(ctx, name, getOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vol := volumeToAPIType(v)
|
||||
|
||||
var cfg opts.GetConfig
|
||||
for _, o := range getOpts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
if cfg.ResolveStatus {
|
||||
vol.Status = v.Status()
|
||||
}
|
||||
return &vol, nil
|
||||
}
|
||||
|
||||
// Mount mounts the volume
|
||||
func (s *VolumesService) Mount(ctx context.Context, vol *types.Volume, ref string) (string, error) {
|
||||
v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver))
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
err = errdefs.NotFound(err)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return v.Mount(ref)
|
||||
}
|
||||
|
||||
// Unmount unmounts the volume.
|
||||
// Note that depending on the implementation, the volume may still be mounted due to other resources using it.
|
||||
func (s *VolumesService) Unmount(ctx context.Context, vol *types.Volume, ref string) error {
|
||||
v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver))
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
err = errdefs.NotFound(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return v.Unmount(ref)
|
||||
}
|
||||
|
||||
// Release releases a volume reference
|
||||
func (s *VolumesService) Release(ctx context.Context, name string, ref string) error {
|
||||
return s.vs.Release(ctx, name, ref)
|
||||
}
|
||||
|
||||
// Remove removes a volume
|
||||
func (s *VolumesService) Remove(ctx context.Context, name string, rmOpts ...opts.RemoveOption) error {
|
||||
var cfg opts.RemoveConfig
|
||||
for _, o := range rmOpts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
v, err := s.vs.Get(ctx, name)
|
||||
if err != nil {
|
||||
if IsNotExist(err) && cfg.PurgeOnError {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.vs.Remove(ctx, v, rmOpts...)
|
||||
if IsNotExist(err) {
|
||||
err = nil
|
||||
} else if IsInUse(err) {
|
||||
err = errdefs.Conflict(err)
|
||||
} else if IsNotExist(err) && cfg.PurgeOnError {
|
||||
err = nil
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
s.eventLogger.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var acceptedPruneFilters = map[string]bool{
|
||||
"label": true,
|
||||
"label!": true,
|
||||
}
|
||||
|
||||
var acceptedListFilters = map[string]bool{
|
||||
"dangling": true,
|
||||
"name": true,
|
||||
"driver": true,
|
||||
"label": true,
|
||||
}
|
||||
|
||||
// LocalVolumesSize gets all local volumes and fetches their size on disk
|
||||
// Note that this intentionally skips volumes which have mount options. Typically
|
||||
// volumes with mount options are not really local even if they are using the
|
||||
// local driver.
|
||||
func (s *VolumesService) LocalVolumesSize(ctx context.Context) ([]*types.Volume, error) {
|
||||
ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), CustomFilter(func(v volume.Volume) bool {
|
||||
dv, ok := v.(volume.DetailedVolume)
|
||||
return ok && len(dv.Options()) == 0
|
||||
})))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.volumesToAPI(ctx, ls, calcSize(true)), nil
|
||||
}
|
||||
|
||||
// Prune removes (local) volumes which match the past in filter arguments.
|
||||
// Note that this intentionally skips volumes with mount options as there would
|
||||
// be no space reclaimed in this case.
|
||||
func (s *VolumesService) Prune(ctx context.Context, filter filters.Args) (*types.VolumesPruneReport, error) {
|
||||
if !atomic.CompareAndSwapInt32(&s.pruneRunning, 0, 1) {
|
||||
return nil, errdefs.Conflict(errors.New("a prune operation is already running"))
|
||||
}
|
||||
defer atomic.StoreInt32(&s.pruneRunning, 0)
|
||||
|
||||
by, err := filtersToBy(filter, acceptedPruneFilters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), ByReferenced(false), by, CustomFilter(func(v volume.Volume) bool {
|
||||
dv, ok := v.(volume.DetailedVolume)
|
||||
return ok && len(dv.Options()) == 0
|
||||
})))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rep := &types.VolumesPruneReport{VolumesDeleted: make([]string, 0, len(ls))}
|
||||
for _, v := range ls {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err := ctx.Err()
|
||||
if err == context.Canceled {
|
||||
err = nil
|
||||
}
|
||||
return rep, err
|
||||
default:
|
||||
}
|
||||
|
||||
vSize, err := directory.Size(ctx, v.Path())
|
||||
if err != nil {
|
||||
logrus.WithField("volume", v.Name()).WithError(err).Warn("could not determine size of volume")
|
||||
}
|
||||
if err := s.vs.Remove(ctx, v); err != nil {
|
||||
logrus.WithError(err).WithField("volume", v.Name()).Warnf("Could not determine size of volume")
|
||||
continue
|
||||
}
|
||||
rep.SpaceReclaimed += uint64(vSize)
|
||||
rep.VolumesDeleted = append(rep.VolumesDeleted, v.Name())
|
||||
}
|
||||
return rep, nil
|
||||
}
|
||||
|
||||
// List gets the list of volumes which match the past in filters
|
||||
// If filters is nil or empty all volumes are returned.
|
||||
func (s *VolumesService) List(ctx context.Context, filter filters.Args) (volumesOut []*types.Volume, warnings []string, err error) {
|
||||
by, err := filtersToBy(filter, acceptedListFilters)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
volumes, warnings, err := s.vs.Find(ctx, by)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return s.volumesToAPI(ctx, volumes, useCachedPath(true)), warnings, nil
|
||||
}
|
||||
|
||||
// Shutdown shuts down the image service and dependencies
|
||||
func (s *VolumesService) Shutdown() error {
|
||||
return s.vs.Shutdown()
|
||||
}
|
66
vendor/github.com/docker/docker-ce/components/engine/volume/service/service_linux_test.go
generated
vendored
Normal file
66
vendor/github.com/docker/docker-ce/components/engine/volume/service/service_linux_test.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/volume"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/local"
|
||||
"github.com/docker/docker/volume/service/opts"
|
||||
"github.com/docker/docker/volume/testutils"
|
||||
"gotest.tools/assert"
|
||||
is "gotest.tools/assert/cmp"
|
||||
)
|
||||
|
||||
func TestLocalVolumeSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ds := volumedrivers.NewStore(nil)
|
||||
dir, err := ioutil.TempDir("", t.Name())
|
||||
assert.Assert(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
l, err := local.New(dir, idtools.Identity{UID: os.Getuid(), GID: os.Getegid()})
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, ds.Register(l, volume.DefaultDriverName))
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("fake"), "fake"))
|
||||
|
||||
service, cleanup := newTestService(t, ds)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
v1, err := service.Create(ctx, "test1", volume.DefaultDriverName, opts.WithCreateReference("foo"))
|
||||
assert.Assert(t, err)
|
||||
v2, err := service.Create(ctx, "test2", volume.DefaultDriverName)
|
||||
assert.Assert(t, err)
|
||||
_, err = service.Create(ctx, "test3", "fake")
|
||||
assert.Assert(t, err)
|
||||
|
||||
data := make([]byte, 1024)
|
||||
err = ioutil.WriteFile(filepath.Join(v1.Mountpoint, "data"), data, 0644)
|
||||
assert.Assert(t, err)
|
||||
err = ioutil.WriteFile(filepath.Join(v2.Mountpoint, "data"), data[:1], 0644)
|
||||
assert.Assert(t, err)
|
||||
|
||||
ls, err := service.LocalVolumesSize(ctx)
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(ls, 2))
|
||||
|
||||
for _, v := range ls {
|
||||
switch v.Name {
|
||||
case "test1":
|
||||
assert.Assert(t, is.Equal(v.UsageData.Size, int64(len(data))))
|
||||
assert.Assert(t, is.Equal(v.UsageData.RefCount, int64(1)))
|
||||
case "test2":
|
||||
assert.Assert(t, is.Equal(v.UsageData.Size, int64(len(data[:1]))))
|
||||
assert.Assert(t, is.Equal(v.UsageData.RefCount, int64(0)))
|
||||
default:
|
||||
t.Fatalf("got unexpected volume: %+v", v)
|
||||
}
|
||||
}
|
||||
}
|
253
vendor/github.com/docker/docker-ce/components/engine/volume/service/service_test.go
generated
vendored
Normal file
253
vendor/github.com/docker/docker-ce/components/engine/volume/service/service_test.go
generated
vendored
Normal file
|
@ -0,0 +1,253 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/volume"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/service/opts"
|
||||
"github.com/docker/docker/volume/testutils"
|
||||
"gotest.tools/assert"
|
||||
is "gotest.tools/assert/cmp"
|
||||
)
|
||||
|
||||
func TestServiceCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ds := volumedrivers.NewStore(nil)
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
|
||||
|
||||
ctx := context.Background()
|
||||
service, cleanup := newTestService(t, ds)
|
||||
defer cleanup()
|
||||
|
||||
_, err := service.Create(ctx, "v1", "notexist")
|
||||
assert.Assert(t, errdefs.IsNotFound(err), err)
|
||||
|
||||
v, err := service.Create(ctx, "v1", "d1")
|
||||
assert.Assert(t, err)
|
||||
|
||||
vCopy, err := service.Create(ctx, "v1", "d1")
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.DeepEqual(v, vCopy))
|
||||
|
||||
_, err = service.Create(ctx, "v1", "d2")
|
||||
assert.Check(t, IsNameConflict(err), err)
|
||||
assert.Check(t, errdefs.IsConflict(err), err)
|
||||
|
||||
assert.Assert(t, service.Remove(ctx, "v1"))
|
||||
_, err = service.Create(ctx, "v1", "d2")
|
||||
assert.Assert(t, err)
|
||||
_, err = service.Create(ctx, "v1", "d2")
|
||||
assert.Assert(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestServiceList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ds := volumedrivers.NewStore(nil)
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
|
||||
|
||||
service, cleanup := newTestService(t, ds)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := service.Create(ctx, "v1", "d1")
|
||||
assert.Assert(t, err)
|
||||
_, err = service.Create(ctx, "v2", "d1")
|
||||
assert.Assert(t, err)
|
||||
_, err = service.Create(ctx, "v3", "d2")
|
||||
assert.Assert(t, err)
|
||||
|
||||
ls, _, err := service.List(ctx, filters.NewArgs(filters.Arg("driver", "d1")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 2))
|
||||
|
||||
ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("driver", "d2")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 1))
|
||||
|
||||
ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("driver", "notexist")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 0))
|
||||
|
||||
ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 3))
|
||||
ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 0))
|
||||
|
||||
_, err = service.Get(ctx, "v1", opts.WithGetReference("foo"))
|
||||
assert.Assert(t, err)
|
||||
ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 2))
|
||||
ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 1))
|
||||
|
||||
ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false"), filters.Arg("driver", "d2")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 0))
|
||||
ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true"), filters.Arg("driver", "d2")))
|
||||
assert.Assert(t, err)
|
||||
assert.Check(t, is.Len(ls, 1))
|
||||
}
|
||||
|
||||
func TestServiceRemove(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ds := volumedrivers.NewStore(nil)
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
|
||||
|
||||
service, cleanup := newTestService(t, ds)
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := service.Create(ctx, "test", "d1")
|
||||
assert.Assert(t, err)
|
||||
|
||||
assert.Assert(t, service.Remove(ctx, "test"))
|
||||
assert.Assert(t, service.Remove(ctx, "test", opts.WithPurgeOnError(true)))
|
||||
}
|
||||
|
||||
func TestServiceGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ds := volumedrivers.NewStore(nil)
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
|
||||
|
||||
service, cleanup := newTestService(t, ds)
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
|
||||
v, err := service.Get(ctx, "notexist")
|
||||
assert.Assert(t, IsNotExist(err))
|
||||
assert.Check(t, v == nil)
|
||||
|
||||
created, err := service.Create(ctx, "test", "d1")
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, created != nil)
|
||||
|
||||
v, err = service.Get(ctx, "test")
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.DeepEqual(created, v))
|
||||
|
||||
v, err = service.Get(ctx, "test", opts.WithGetResolveStatus)
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(v.Status, 1), v.Status)
|
||||
|
||||
v, err = service.Get(ctx, "test", opts.WithGetDriver("notarealdriver"))
|
||||
assert.Assert(t, errdefs.IsConflict(err), err)
|
||||
v, err = service.Get(ctx, "test", opts.WithGetDriver("d1"))
|
||||
assert.Assert(t, err == nil)
|
||||
assert.Assert(t, is.DeepEqual(created, v))
|
||||
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
|
||||
v, err = service.Get(ctx, "test", opts.WithGetDriver("d2"))
|
||||
assert.Assert(t, errdefs.IsConflict(err), err)
|
||||
}
|
||||
|
||||
func TestServicePrune(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ds := volumedrivers.NewStore(nil)
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver(volume.DefaultDriverName), volume.DefaultDriverName))
|
||||
assert.Assert(t, ds.Register(testutils.NewFakeDriver("other"), "other"))
|
||||
|
||||
service, cleanup := newTestService(t, ds)
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := service.Create(ctx, "test", volume.DefaultDriverName)
|
||||
assert.Assert(t, err)
|
||||
_, err = service.Create(ctx, "test2", "other")
|
||||
assert.Assert(t, err)
|
||||
|
||||
pr, err := service.Prune(ctx, filters.NewArgs(filters.Arg("label", "banana")))
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
|
||||
|
||||
pr, err = service.Prune(ctx, filters.NewArgs())
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
|
||||
assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
|
||||
|
||||
_, err = service.Get(ctx, "test")
|
||||
assert.Assert(t, IsNotExist(err), err)
|
||||
|
||||
v, err := service.Get(ctx, "test2")
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Equal(v.Driver, "other"))
|
||||
|
||||
_, err = service.Create(ctx, "test", volume.DefaultDriverName)
|
||||
assert.Assert(t, err)
|
||||
|
||||
pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana")))
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
|
||||
assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
|
||||
v, err = service.Get(ctx, "test2")
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Equal(v.Driver, "other"))
|
||||
|
||||
_, err = service.Create(ctx, "test", volume.DefaultDriverName, opts.WithCreateLabels(map[string]string{"banana": ""}))
|
||||
assert.Assert(t, err)
|
||||
pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana")))
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
|
||||
|
||||
_, err = service.Create(ctx, "test3", volume.DefaultDriverName, opts.WithCreateLabels(map[string]string{"banana": "split"}))
|
||||
assert.Assert(t, err)
|
||||
pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana=split")))
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
|
||||
assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
|
||||
|
||||
pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label", "banana=split")))
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
|
||||
assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test3"))
|
||||
|
||||
v, err = service.Create(ctx, "test", volume.DefaultDriverName, opts.WithCreateReference(t.Name()))
|
||||
assert.Assert(t, err)
|
||||
|
||||
pr, err = service.Prune(ctx, filters.NewArgs())
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
|
||||
assert.Assert(t, service.Release(ctx, v.Name, t.Name()))
|
||||
|
||||
pr, err = service.Prune(ctx, filters.NewArgs())
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
|
||||
assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
|
||||
}
|
||||
|
||||
func newTestService(t *testing.T, ds *volumedrivers.Store) (*VolumesService, func()) {
|
||||
t.Helper()
|
||||
|
||||
dir, err := ioutil.TempDir("", t.Name())
|
||||
assert.Assert(t, err)
|
||||
|
||||
store, err := NewStore(dir, ds)
|
||||
assert.Assert(t, err)
|
||||
s := &VolumesService{vs: store, eventLogger: dummyEventLogger{}}
|
||||
return s, func() {
|
||||
assert.Check(t, s.Shutdown())
|
||||
assert.Check(t, os.RemoveAll(dir))
|
||||
}
|
||||
}
|
||||
|
||||
type dummyEventLogger struct{}
|
||||
|
||||
func (dummyEventLogger) LogVolumeEvent(_, _ string, _ map[string]string) {}
|
858
vendor/github.com/docker/docker-ce/components/engine/volume/service/store.go
generated
vendored
Normal file
858
vendor/github.com/docker/docker-ce/components/engine/volume/service/store.go
generated
vendored
Normal file
|
@ -0,0 +1,858 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/locker"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
volumemounts "github.com/docker/docker/volume/mounts"
|
||||
"github.com/docker/docker/volume/service/opts"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
volumeDataDir = "volumes"
|
||||
)
|
||||
|
||||
type volumeWrapper struct {
|
||||
volume.Volume
|
||||
labels map[string]string
|
||||
scope string
|
||||
options map[string]string
|
||||
}
|
||||
|
||||
func (v volumeWrapper) Options() map[string]string {
|
||||
if v.options == nil {
|
||||
return nil
|
||||
}
|
||||
options := make(map[string]string, len(v.options))
|
||||
for key, value := range v.options {
|
||||
options[key] = value
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (v volumeWrapper) Labels() map[string]string {
|
||||
if v.labels == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
labels := make(map[string]string, len(v.labels))
|
||||
for key, value := range v.labels {
|
||||
labels[key] = value
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
func (v volumeWrapper) Scope() string {
|
||||
return v.scope
|
||||
}
|
||||
|
||||
func (v volumeWrapper) CachedPath() string {
|
||||
if vv, ok := v.Volume.(interface {
|
||||
CachedPath() string
|
||||
}); ok {
|
||||
return vv.CachedPath()
|
||||
}
|
||||
return v.Volume.Path()
|
||||
}
|
||||
|
||||
// NewStore creates a new volume store at the given path
|
||||
func NewStore(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
|
||||
vs := &VolumeStore{
|
||||
locks: &locker.Locker{},
|
||||
names: make(map[string]volume.Volume),
|
||||
refs: make(map[string]map[string]struct{}),
|
||||
labels: make(map[string]map[string]string),
|
||||
options: make(map[string]map[string]string),
|
||||
drivers: drivers,
|
||||
}
|
||||
|
||||
if rootPath != "" {
|
||||
// initialize metadata store
|
||||
volPath := filepath.Join(rootPath, volumeDataDir)
|
||||
if err := os.MkdirAll(volPath, 0750); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
vs.db, err = bolt.Open(filepath.Join(volPath, "metadata.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error while opening volume store metadata database")
|
||||
}
|
||||
|
||||
// initialize volumes bucket
|
||||
if err := vs.db.Update(func(tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucketIfNotExists(volumeBucketName); err != nil {
|
||||
return errors.Wrap(err, "error while setting up volume store metadata database")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
vs.restore()
|
||||
|
||||
return vs, nil
|
||||
}
|
||||
|
||||
func (s *VolumeStore) getNamed(name string) (volume.Volume, bool) {
|
||||
s.globalLock.RLock()
|
||||
v, exists := s.names[name]
|
||||
s.globalLock.RUnlock()
|
||||
return v, exists
|
||||
}
|
||||
|
||||
func (s *VolumeStore) setNamed(v volume.Volume, ref string) {
|
||||
name := v.Name()
|
||||
|
||||
s.globalLock.Lock()
|
||||
s.names[name] = v
|
||||
if len(ref) > 0 {
|
||||
if s.refs[name] == nil {
|
||||
s.refs[name] = make(map[string]struct{})
|
||||
}
|
||||
s.refs[name][ref] = struct{}{}
|
||||
}
|
||||
s.globalLock.Unlock()
|
||||
}
|
||||
|
||||
// hasRef returns true if the given name has at least one ref.
|
||||
// Callers of this function are expected to hold the name lock.
|
||||
func (s *VolumeStore) hasRef(name string) bool {
|
||||
s.globalLock.RLock()
|
||||
l := len(s.refs[name])
|
||||
s.globalLock.RUnlock()
|
||||
return l > 0
|
||||
}
|
||||
|
||||
// getRefs gets the list of refs for a given name
|
||||
// Callers of this function are expected to hold the name lock.
|
||||
func (s *VolumeStore) getRefs(name string) []string {
|
||||
s.globalLock.RLock()
|
||||
defer s.globalLock.RUnlock()
|
||||
|
||||
refs := make([]string, 0, len(s.refs[name]))
|
||||
for r := range s.refs[name] {
|
||||
refs = append(refs, r)
|
||||
}
|
||||
|
||||
return refs
|
||||
}
|
||||
|
||||
// purge allows the cleanup of internal data on docker in case
|
||||
// the internal data is out of sync with volumes driver plugins.
|
||||
func (s *VolumeStore) purge(ctx context.Context, name string) error {
|
||||
s.globalLock.Lock()
|
||||
defer s.globalLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
v, exists := s.names[name]
|
||||
if exists {
|
||||
driverName := v.DriverName()
|
||||
if _, err := s.drivers.ReleaseDriver(driverName); err != nil {
|
||||
logrus.WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver")
|
||||
}
|
||||
}
|
||||
if err := s.removeMeta(name); err != nil {
|
||||
logrus.Errorf("Error removing volume metadata for volume %q: %v", name, err)
|
||||
}
|
||||
delete(s.names, name)
|
||||
delete(s.refs, name)
|
||||
delete(s.labels, name)
|
||||
delete(s.options, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
|
||||
type VolumeStore struct {
|
||||
// locks ensures that only one action is being performed on a particular volume at a time without locking the entire store
|
||||
// since actions on volumes can be quite slow, this ensures the store is free to handle requests for other volumes.
|
||||
locks *locker.Locker
|
||||
drivers *drivers.Store
|
||||
// globalLock is used to protect access to mutable structures used by the store object
|
||||
globalLock sync.RWMutex
|
||||
// names stores the volume name -> volume relationship.
|
||||
// This is used for making lookups faster so we don't have to probe all drivers
|
||||
names map[string]volume.Volume
|
||||
// refs stores the volume name and the list of things referencing it
|
||||
refs map[string]map[string]struct{}
|
||||
// labels stores volume labels for each volume
|
||||
labels map[string]map[string]string
|
||||
// options stores volume options for each volume
|
||||
options map[string]map[string]string
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
func filterByDriver(names []string) filterFunc {
|
||||
return func(v volume.Volume) bool {
|
||||
for _, name := range names {
|
||||
if name == v.DriverName() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VolumeStore) byReferenced(referenced bool) filterFunc {
|
||||
return func(v volume.Volume) bool {
|
||||
return s.hasRef(v.Name()) == referenced
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VolumeStore) filter(ctx context.Context, vols *[]volume.Volume, by By) (warnings []string, err error) {
|
||||
// note that this specifically does not support the `FromList` By type.
|
||||
switch f := by.(type) {
|
||||
case nil:
|
||||
if *vols == nil {
|
||||
var ls []volume.Volume
|
||||
ls, warnings, err = s.list(ctx)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
*vols = ls
|
||||
}
|
||||
case byDriver:
|
||||
if *vols != nil {
|
||||
filter(vols, filterByDriver([]string(f)))
|
||||
return nil, nil
|
||||
}
|
||||
var ls []volume.Volume
|
||||
ls, warnings, err = s.list(ctx, []string(f)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*vols = ls
|
||||
case ByReferenced:
|
||||
// TODO(@cpuguy83): It would be nice to optimize this by looking at the list
|
||||
// of referenced volumes, however the locking strategy makes this difficult
|
||||
// without either providing inconsistent data or deadlocks.
|
||||
if *vols == nil {
|
||||
var ls []volume.Volume
|
||||
ls, warnings, err = s.list(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*vols = ls
|
||||
}
|
||||
filter(vols, s.byReferenced(bool(f)))
|
||||
case andCombinator:
|
||||
for _, by := range f {
|
||||
w, err := s.filter(ctx, vols, by)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
warnings = append(warnings, w...)
|
||||
}
|
||||
case orCombinator:
|
||||
for _, by := range f {
|
||||
switch by.(type) {
|
||||
case byDriver:
|
||||
var ls []volume.Volume
|
||||
w, err := s.filter(ctx, &ls, by)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
warnings = append(warnings, w...)
|
||||
default:
|
||||
ls, w, err := s.list(ctx)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
warnings = append(warnings, w...)
|
||||
w, err = s.filter(ctx, &ls, by)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
warnings = append(warnings, w...)
|
||||
*vols = append(*vols, ls...)
|
||||
}
|
||||
}
|
||||
unique(vols)
|
||||
case CustomFilter:
|
||||
if *vols == nil {
|
||||
var ls []volume.Volume
|
||||
ls, warnings, err = s.list(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*vols = ls
|
||||
}
|
||||
filter(vols, filterFunc(f))
|
||||
default:
|
||||
return nil, errdefs.InvalidParameter(errors.Errorf("unsupported filter: %T", f))
|
||||
}
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
func unique(ls *[]volume.Volume) {
|
||||
names := make(map[string]bool, len(*ls))
|
||||
filter(ls, func(v volume.Volume) bool {
|
||||
if names[v.Name()] {
|
||||
return false
|
||||
}
|
||||
names[v.Name()] = true
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Find lists volumes filtered by the past in filter.
|
||||
// If a driver returns a volume that has name which conflicts with another volume from a different driver,
|
||||
// the first volume is chosen and the conflicting volume is dropped.
|
||||
func (s *VolumeStore) Find(ctx context.Context, by By) (vols []volume.Volume, warnings []string, err error) {
|
||||
logrus.WithField("ByType", fmt.Sprintf("%T", by)).WithField("ByValue", fmt.Sprintf("%+v", by)).Debug("VolumeStore.Find")
|
||||
switch f := by.(type) {
|
||||
case nil, orCombinator, andCombinator, byDriver, ByReferenced, CustomFilter:
|
||||
warnings, err = s.filter(ctx, &vols, by)
|
||||
case fromList:
|
||||
warnings, err = s.filter(ctx, f.ls, f.by)
|
||||
default:
|
||||
// Really shouldn't be possible, but makes sure that any new By's are added to this check.
|
||||
err = errdefs.InvalidParameter(errors.Errorf("unsupported filter type: %T", f))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, &OpErr{Err: err, Op: "list"}
|
||||
}
|
||||
|
||||
var out []volume.Volume
|
||||
|
||||
for _, v := range vols {
|
||||
name := normalizeVolumeName(v.Name())
|
||||
|
||||
s.locks.Lock(name)
|
||||
storedV, exists := s.getNamed(name)
|
||||
// Note: it's not safe to populate the cache here because the volume may have been
|
||||
// deleted before we acquire a lock on its name
|
||||
if exists && storedV.DriverName() != v.DriverName() {
|
||||
logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), storedV.DriverName(), v.DriverName())
|
||||
s.locks.Unlock(v.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, v)
|
||||
s.locks.Unlock(v.Name())
|
||||
}
|
||||
return out, warnings, nil
|
||||
}
|
||||
|
||||
type filterFunc func(volume.Volume) bool
|
||||
|
||||
func filter(vols *[]volume.Volume, fn filterFunc) {
|
||||
var evict []int
|
||||
for i, v := range *vols {
|
||||
if !fn(v) {
|
||||
evict = append(evict, i)
|
||||
}
|
||||
}
|
||||
|
||||
for n, i := range evict {
|
||||
copy((*vols)[i-n:], (*vols)[i-n+1:])
|
||||
(*vols)[len(*vols)-1] = nil
|
||||
*vols = (*vols)[:len(*vols)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// list goes through each volume driver and asks for its list of volumes.
|
||||
// TODO(@cpuguy83): plumb context through
|
||||
func (s *VolumeStore) list(ctx context.Context, driverNames ...string) ([]volume.Volume, []string, error) {
|
||||
var (
|
||||
ls = []volume.Volume{} // do not return a nil value as this affects filtering
|
||||
warnings []string
|
||||
)
|
||||
|
||||
var dls []volume.Driver
|
||||
|
||||
all, err := s.drivers.GetAllDrivers()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(driverNames) == 0 {
|
||||
dls = all
|
||||
} else {
|
||||
idx := make(map[string]bool, len(driverNames))
|
||||
for _, name := range driverNames {
|
||||
idx[name] = true
|
||||
}
|
||||
for _, d := range all {
|
||||
if idx[d.Name()] {
|
||||
dls = append(dls, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type vols struct {
|
||||
vols []volume.Volume
|
||||
err error
|
||||
driverName string
|
||||
}
|
||||
chVols := make(chan vols, len(dls))
|
||||
|
||||
for _, vd := range dls {
|
||||
go func(d volume.Driver) {
|
||||
vs, err := d.List()
|
||||
if err != nil {
|
||||
chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
|
||||
return
|
||||
}
|
||||
for i, v := range vs {
|
||||
s.globalLock.RLock()
|
||||
vs[i] = volumeWrapper{v, s.labels[v.Name()], d.Scope(), s.options[v.Name()]}
|
||||
s.globalLock.RUnlock()
|
||||
}
|
||||
|
||||
chVols <- vols{vols: vs}
|
||||
}(vd)
|
||||
}
|
||||
|
||||
badDrivers := make(map[string]struct{})
|
||||
for i := 0; i < len(dls); i++ {
|
||||
vs := <-chVols
|
||||
|
||||
if vs.err != nil {
|
||||
warnings = append(warnings, vs.err.Error())
|
||||
badDrivers[vs.driverName] = struct{}{}
|
||||
}
|
||||
ls = append(ls, vs.vols...)
|
||||
}
|
||||
|
||||
if len(badDrivers) > 0 {
|
||||
s.globalLock.RLock()
|
||||
for _, v := range s.names {
|
||||
if _, exists := badDrivers[v.DriverName()]; exists {
|
||||
ls = append(ls, v)
|
||||
}
|
||||
}
|
||||
s.globalLock.RUnlock()
|
||||
}
|
||||
return ls, warnings, nil
|
||||
}
|
||||
|
||||
// Create creates a volume with the given name and driver
|
||||
// If the volume needs to be created with a reference to prevent race conditions
|
||||
// with volume cleanup, make sure to use the `CreateWithReference` option.
|
||||
func (s *VolumeStore) Create(ctx context.Context, name, driverName string, createOpts ...opts.CreateOption) (volume.Volume, error) {
|
||||
var cfg opts.CreateConfig
|
||||
for _, o := range createOpts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
name = normalizeVolumeName(name)
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
v, err := s.create(ctx, name, driverName, cfg.Options, cfg.Labels)
|
||||
if err != nil {
|
||||
if _, ok := err.(*OpErr); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
||||
}
|
||||
|
||||
s.setNamed(v, cfg.Reference)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// checkConflict checks the local cache for name collisions with the passed in name,
|
||||
// for existing volumes with the same name but in a different driver.
|
||||
// This is used by `Create` as a best effort to prevent name collisions for volumes.
|
||||
// If a matching volume is found that is not a conflict that is returned so the caller
|
||||
// does not need to perform an additional lookup.
|
||||
// When no matching volume is found, both returns will be nil
|
||||
//
|
||||
// Note: This does not probe all the drivers for name collisions because v1 plugins
|
||||
// are very slow, particularly if the plugin is down, and cause other issues,
|
||||
// particularly around locking the store.
|
||||
// TODO(cpuguy83): With v2 plugins this shouldn't be a problem. Could also potentially
|
||||
// use a connect timeout for this kind of check to ensure we aren't blocking for a
|
||||
// long time.
|
||||
func (s *VolumeStore) checkConflict(ctx context.Context, name, driverName string) (volume.Volume, error) {
|
||||
// check the local cache
|
||||
v, _ := s.getNamed(name)
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
vDriverName := v.DriverName()
|
||||
var conflict bool
|
||||
if driverName != "" {
|
||||
// Retrieve canonical driver name to avoid inconsistencies (for example
|
||||
// "plugin" vs. "plugin:latest")
|
||||
vd, err := s.drivers.GetDriver(driverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vDriverName != vd.Name() {
|
||||
conflict = true
|
||||
}
|
||||
}
|
||||
|
||||
// let's check if the found volume ref
|
||||
// is stale by checking with the driver if it still exists
|
||||
exists, err := volumeExists(ctx, s.drivers, v)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(errNameConflict, "found reference to volume '%s' in driver '%s', but got an error while checking the driver: %v", name, vDriverName, err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
if conflict {
|
||||
return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
if s.hasRef(v.Name()) {
|
||||
// Containers are referencing this volume but it doesn't seem to exist anywhere.
|
||||
// Return a conflict error here, the user can fix this with `docker volume rm -f`
|
||||
return nil, errors.Wrapf(errNameConflict, "found references to volume '%s' in driver '%s' but the volume was not found in the driver -- you may need to remove containers referencing this volume or force remove the volume to re-create it", name, vDriverName)
|
||||
}
|
||||
|
||||
// doesn't exist, so purge it from the cache
|
||||
s.purge(ctx, name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// volumeExists returns if the volume is still present in the driver.
|
||||
// An error is returned if there was an issue communicating with the driver.
|
||||
func volumeExists(ctx context.Context, store *drivers.Store, v volume.Volume) (bool, error) {
|
||||
exists, err := lookupVolume(ctx, store, v.DriverName(), v.Name())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return exists != nil, nil
|
||||
}
|
||||
|
||||
// create asks the given driver to create a volume with the name/opts.
|
||||
// If a volume with the name is already known, it will ask the stored driver for the volume.
|
||||
// If the passed in driver name does not match the driver name which is stored
|
||||
// for the given volume name, an error is returned after checking if the reference is stale.
|
||||
// If the reference is stale, it will be purged and this create can continue.
|
||||
// It is expected that callers of this function hold any necessary locks.
|
||||
func (s *VolumeStore) create(ctx context.Context, name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
|
||||
// Validate the name in a platform-specific manner
|
||||
|
||||
// volume name validation is specific to the host os and not on container image
|
||||
// windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS
|
||||
parser := volumemounts.NewParser(runtime.GOOS)
|
||||
err := parser.ValidateVolumeName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := s.checkConflict(ctx, name, driverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v != nil {
|
||||
// there is an existing volume, if we already have this stored locally, return it.
|
||||
// TODO: there could be some inconsistent details such as labels here
|
||||
if vv, _ := s.getNamed(v.Name()); vv != nil {
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Since there isn't a specified driver name, let's see if any of the existing drivers have this volume name
|
||||
if driverName == "" {
|
||||
v, _ = s.getVolume(ctx, name, "")
|
||||
if v != nil {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
if driverName == "" {
|
||||
driverName = volume.DefaultDriverName
|
||||
}
|
||||
vd, err := s.drivers.CreateDriver(driverName)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Op: "create", Name: name, Err: err}
|
||||
}
|
||||
|
||||
logrus.Debugf("Registering new volume reference: driver %q, name %q", vd.Name(), name)
|
||||
if v, _ = vd.Get(name); v == nil {
|
||||
v, err = vd.Create(name, opts)
|
||||
if err != nil {
|
||||
if _, err := s.drivers.ReleaseDriver(driverName); err != nil {
|
||||
logrus.WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
s.globalLock.Lock()
|
||||
s.labels[name] = labels
|
||||
s.options[name] = opts
|
||||
s.refs[name] = make(map[string]struct{})
|
||||
s.globalLock.Unlock()
|
||||
|
||||
metadata := volumeMetadata{
|
||||
Name: name,
|
||||
Driver: vd.Name(),
|
||||
Labels: labels,
|
||||
Options: opts,
|
||||
}
|
||||
|
||||
if err := s.setMeta(name, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumeWrapper{v, labels, vd.Scope(), opts}, nil
|
||||
}
|
||||
|
||||
// Get looks if a volume with the given name exists and returns it if so
|
||||
func (s *VolumeStore) Get(ctx context.Context, name string, getOptions ...opts.GetOption) (volume.Volume, error) {
|
||||
var cfg opts.GetConfig
|
||||
for _, o := range getOptions {
|
||||
o(&cfg)
|
||||
}
|
||||
name = normalizeVolumeName(name)
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
v, err := s.getVolume(ctx, name, cfg.Driver)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
||||
}
|
||||
if cfg.Driver != "" && v.DriverName() != cfg.Driver {
|
||||
return nil, &OpErr{Name: name, Op: "get", Err: errdefs.Conflict(errors.New("found volume driver does not match passed in driver"))}
|
||||
}
|
||||
s.setNamed(v, cfg.Reference)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// getVolume requests the volume, if the driver info is stored it just accesses that driver,
|
||||
// if the driver is unknown it probes all drivers until it finds the first volume with that name.
|
||||
// it is expected that callers of this function hold any necessary locks
|
||||
func (s *VolumeStore) getVolume(ctx context.Context, name, driverName string) (volume.Volume, error) {
|
||||
var meta volumeMetadata
|
||||
meta, err := s.getMeta(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if driverName != "" {
|
||||
if meta.Driver == "" {
|
||||
meta.Driver = driverName
|
||||
}
|
||||
if driverName != meta.Driver {
|
||||
return nil, errdefs.Conflict(errors.New("provided volume driver does not match stored driver"))
|
||||
}
|
||||
}
|
||||
|
||||
if driverName == "" {
|
||||
driverName = meta.Driver
|
||||
}
|
||||
if driverName == "" {
|
||||
s.globalLock.RLock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.globalLock.RUnlock()
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
v, exists := s.names[name]
|
||||
s.globalLock.RUnlock()
|
||||
if exists {
|
||||
meta.Driver = v.DriverName()
|
||||
if err := s.setMeta(name, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if meta.Driver != "" {
|
||||
vol, err := lookupVolume(ctx, s.drivers, meta.Driver, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if vol == nil {
|
||||
s.purge(ctx, name)
|
||||
return nil, errNoSuchVolume
|
||||
}
|
||||
|
||||
var scope string
|
||||
vd, err := s.drivers.GetDriver(meta.Driver)
|
||||
if err == nil {
|
||||
scope = vd.Scope()
|
||||
}
|
||||
return volumeWrapper{vol, meta.Labels, scope, meta.Options}, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Probing all drivers for volume with name: %s", name)
|
||||
drivers, err := s.drivers.GetAllDrivers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range drivers {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
v, err := d.Get(name)
|
||||
if err != nil || v == nil {
|
||||
continue
|
||||
}
|
||||
meta.Driver = v.DriverName()
|
||||
if err := s.setMeta(name, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumeWrapper{v, meta.Labels, d.Scope(), meta.Options}, nil
|
||||
}
|
||||
return nil, errNoSuchVolume
|
||||
}
|
||||
|
||||
// lookupVolume gets the specified volume from the specified driver.
|
||||
// This will only return errors related to communications with the driver.
|
||||
// If the driver returns an error that is not communication related the
|
||||
// error is logged but not returned.
|
||||
// If the volume is not found it will return `nil, nil``
|
||||
// TODO(@cpuguy83): plumb through the context to lower level components
|
||||
func lookupVolume(ctx context.Context, store *drivers.Store, driverName, volumeName string) (volume.Volume, error) {
|
||||
if driverName == "" {
|
||||
driverName = volume.DefaultDriverName
|
||||
}
|
||||
vd, err := store.GetDriver(driverName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName)
|
||||
}
|
||||
v, err := vd.Get(volumeName)
|
||||
if err != nil {
|
||||
err = errors.Cause(err)
|
||||
if _, ok := err.(net.Error); ok {
|
||||
if v != nil {
|
||||
volumeName = v.Name()
|
||||
driverName = v.DriverName()
|
||||
}
|
||||
return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName)
|
||||
}
|
||||
|
||||
// At this point, the error could be anything from the driver, such as "no such volume"
|
||||
// Let's not check an error here, and instead check if the driver returned a volume
|
||||
logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Debug("Error while looking up volume")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Remove removes the requested volume. A volume is not removed if it has any refs
|
||||
func (s *VolumeStore) Remove(ctx context.Context, v volume.Volume, rmOpts ...opts.RemoveOption) error {
|
||||
var cfg opts.RemoveConfig
|
||||
for _, o := range rmOpts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
name := v.Name()
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
if s.hasRef(name) {
|
||||
return &OpErr{Err: errVolumeInUse, Name: name, Op: "remove", Refs: s.getRefs(name)}
|
||||
}
|
||||
|
||||
v, err := s.getVolume(ctx, name, v.DriverName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vd, err := s.drivers.GetDriver(v.DriverName())
|
||||
if err != nil {
|
||||
return &OpErr{Err: err, Name: v.DriverName(), Op: "remove"}
|
||||
}
|
||||
|
||||
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
||||
vol := unwrapVolume(v)
|
||||
|
||||
err = vd.Remove(vol)
|
||||
if err != nil {
|
||||
err = &OpErr{Err: err, Name: name, Op: "remove"}
|
||||
}
|
||||
|
||||
if err == nil || cfg.PurgeOnError {
|
||||
if e := s.purge(ctx, name); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Release releases the specified reference to the volume
|
||||
func (s *VolumeStore) Release(ctx context.Context, name string, ref string) error {
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
s.globalLock.Lock()
|
||||
defer s.globalLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
if s.refs[name] != nil {
|
||||
delete(s.refs[name], ref)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountReferences gives a count of all references for a given volume.
|
||||
func (s *VolumeStore) CountReferences(v volume.Volume) int {
|
||||
name := normalizeVolumeName(v.Name())
|
||||
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
s.globalLock.Lock()
|
||||
defer s.globalLock.Unlock()
|
||||
|
||||
return len(s.refs[name])
|
||||
}
|
||||
|
||||
func unwrapVolume(v volume.Volume) volume.Volume {
|
||||
if vol, ok := v.(volumeWrapper); ok {
|
||||
return vol.Volume
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Shutdown releases all resources used by the volume store
|
||||
// It does not make any changes to volumes, drivers, etc.
|
||||
func (s *VolumeStore) Shutdown() error {
|
||||
return s.db.Close()
|
||||
}
|
421
vendor/github.com/docker/docker-ce/components/engine/volume/service/store_test.go
generated
vendored
Normal file
421
vendor/github.com/docker/docker-ce/components/engine/volume/service/store_test.go
generated
vendored
Normal file
|
@ -0,0 +1,421 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/volume"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/service/opts"
|
||||
volumetestutils "github.com/docker/docker/volume/testutils"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gotest.tools/assert"
|
||||
is "gotest.tools/assert/cmp"
|
||||
)
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
||||
|
||||
ctx := context.Background()
|
||||
v, err := s.Create(ctx, "fake1", "fake")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Name() != "fake1" {
|
||||
t.Fatalf("Expected fake1 volume, got %v", v)
|
||||
}
|
||||
if l, _, _ := s.Find(ctx, nil); len(l) != 1 {
|
||||
t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
|
||||
}
|
||||
|
||||
if _, err := s.Create(ctx, "none", "none"); err == nil {
|
||||
t.Fatalf("Expected unknown driver error, got nil")
|
||||
}
|
||||
|
||||
_, err = s.Create(ctx, "fakeerror", "fake", opts.WithCreateOptions(map[string]string{"error": "create error"}))
|
||||
expected := &OpErr{Op: "create", Name: "fakeerror", Err: errors.New("create error")}
|
||||
if err != nil && err.Error() != expected.Error() {
|
||||
t.Fatalf("Expected create fakeError: create error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// doing string compare here since this error comes directly from the driver
|
||||
expected := "no such volume"
|
||||
var v volume.Volume = volumetestutils.NoopVolume{}
|
||||
if err := s.Remove(ctx, v); err == nil || !strings.Contains(err.Error(), expected) {
|
||||
t.Fatalf("Expected error %q, got %v", expected, err)
|
||||
}
|
||||
|
||||
v, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("fake"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := s.Remove(ctx, v); !IsInUse(err) {
|
||||
t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
|
||||
}
|
||||
s.Release(ctx, v.Name(), "fake")
|
||||
if err := s.Remove(ctx, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l, _, _ := s.Find(ctx, nil); len(l) != 0 {
|
||||
t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir, err := ioutil.TempDir("", "test-list")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
drivers := volumedrivers.NewStore(nil)
|
||||
drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
||||
drivers.Register(volumetestutils.NewFakeDriver("fake2"), "fake2")
|
||||
|
||||
s, err := NewStore(dir, drivers)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
if _, err := s.Create(ctx, "test", "fake"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.Create(ctx, "test2", "fake2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ls, _, err := s.Find(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ls) != 2 {
|
||||
t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
||||
}
|
||||
if err := s.Shutdown(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// and again with a new store
|
||||
s, err = NewStore(dir, drivers)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ls, _, err = s.Find(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ls) != 2 {
|
||||
t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindByDriver(t *testing.T) {
|
||||
t.Parallel()
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
|
||||
assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake"))
|
||||
assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop"))
|
||||
|
||||
ctx := context.Background()
|
||||
_, err := s.Create(ctx, "fake1", "fake")
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = s.Create(ctx, "fake2", "fake")
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = s.Create(ctx, "fake3", "noop")
|
||||
assert.NilError(t, err)
|
||||
|
||||
l, _, err := s.Find(ctx, ByDriver("fake"))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, len(l), 2)
|
||||
|
||||
l, _, err = s.Find(ctx, ByDriver("noop"))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, len(l), 1)
|
||||
|
||||
l, _, err = s.Find(ctx, ByDriver("nosuchdriver"))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, len(l), 0)
|
||||
}
|
||||
|
||||
func TestFindByReferenced(t *testing.T) {
|
||||
t.Parallel()
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
||||
|
||||
ctx := context.Background()
|
||||
if _, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("volReference")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.Create(ctx, "fake2", "fake"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dangling, _, err := s.Find(ctx, ByReferenced(false))
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, len(dangling) == 1)
|
||||
assert.Check(t, dangling[0].Name() == "fake2")
|
||||
|
||||
used, _, err := s.Find(ctx, ByReferenced(true))
|
||||
assert.Assert(t, err)
|
||||
assert.Assert(t, len(used) == 1)
|
||||
assert.Check(t, used[0].Name() == "fake1")
|
||||
}
|
||||
|
||||
func TestDerefMultipleOfSameRef(t *testing.T) {
|
||||
t.Parallel()
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
||||
|
||||
ctx := context.Background()
|
||||
v, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("volReference"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := s.Get(ctx, "fake1", opts.WithGetDriver("fake"), opts.WithGetReference("volReference")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s.Release(ctx, v.Name(), "volReference")
|
||||
if err := s.Remove(ctx, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateKeepOptsLabelsWhenExistsRemotely(t *testing.T) {
|
||||
t.Parallel()
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
|
||||
vd := volumetestutils.NewFakeDriver("fake")
|
||||
s.drivers.Register(vd, "fake")
|
||||
|
||||
// Create a volume in the driver directly
|
||||
if _, err := vd.Create("foo", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
v, err := s.Create(ctx, "foo", "fake", opts.WithCreateLabels(map[string]string{"hello": "world"}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch dv := v.(type) {
|
||||
case volume.DetailedVolume:
|
||||
if dv.Labels()["hello"] != "world" {
|
||||
t.Fatalf("labels don't match")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("got unexpected type: %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefererencePluginOnCreateError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
l net.Listener
|
||||
err error
|
||||
)
|
||||
|
||||
for i := 32768; l == nil && i < 40000; i++ {
|
||||
l, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", i))
|
||||
}
|
||||
if l == nil {
|
||||
t.Fatalf("could not create listener: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
|
||||
d := volumetestutils.NewFakeDriver("TestDefererencePluginOnCreateError")
|
||||
p, err := volumetestutils.MakeFakePlugin(d, l)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pg := volumetestutils.NewFakePluginGetter(p)
|
||||
s.drivers = volumedrivers.NewStore(pg)
|
||||
|
||||
ctx := context.Background()
|
||||
// create a good volume so we have a plugin reference
|
||||
_, err = s.Create(ctx, "fake1", d.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now create another one expecting an error
|
||||
_, err = s.Create(ctx, "fake2", d.Name(), opts.WithCreateOptions(map[string]string{"error": "some error"}))
|
||||
if err == nil || !strings.Contains(err.Error(), "some error") {
|
||||
t.Fatalf("expected an error on create: %v", err)
|
||||
}
|
||||
|
||||
// There should be only 1 plugin reference
|
||||
if refs := volumetestutils.FakeRefs(p); refs != 1 {
|
||||
t.Fatalf("expected 1 plugin reference, got: %d", refs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefDerefRemove(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
driverName := "test-ref-deref-remove"
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
||||
|
||||
ctx := context.Background()
|
||||
v, err := s.Create(ctx, "test", driverName, opts.WithCreateReference("test-ref"))
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = s.Remove(ctx, v)
|
||||
assert.Assert(t, is.ErrorContains(err, ""))
|
||||
assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
|
||||
|
||||
s.Release(ctx, v.Name(), "test-ref")
|
||||
err = s.Remove(ctx, v)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
driverName := "test-get"
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
||||
|
||||
ctx := context.Background()
|
||||
_, err := s.Get(ctx, "not-exist")
|
||||
assert.Assert(t, is.ErrorContains(err, ""))
|
||||
assert.Equal(t, errNoSuchVolume, err.(*OpErr).Err)
|
||||
|
||||
v1, err := s.Create(ctx, "test", driverName, opts.WithCreateLabels(map[string]string{"a": "1"}))
|
||||
assert.NilError(t, err)
|
||||
|
||||
v2, err := s.Get(ctx, "test")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, v1, v2, cmpVolume)
|
||||
|
||||
dv := v2.(volume.DetailedVolume)
|
||||
assert.Equal(t, "1", dv.Labels()["a"])
|
||||
|
||||
err = s.Remove(ctx, v1)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestGetWithReference(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
driverName := "test-get-with-ref"
|
||||
s, cleanup := setupTest(t)
|
||||
defer cleanup()
|
||||
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
||||
|
||||
ctx := context.Background()
|
||||
_, err := s.Get(ctx, "not-exist", opts.WithGetDriver(driverName), opts.WithGetReference("test-ref"))
|
||||
assert.Assert(t, is.ErrorContains(err, ""))
|
||||
|
||||
v1, err := s.Create(ctx, "test", driverName, opts.WithCreateLabels(map[string]string{"a": "1"}))
|
||||
assert.NilError(t, err)
|
||||
|
||||
v2, err := s.Get(ctx, "test", opts.WithGetDriver(driverName), opts.WithGetReference("test-ref"))
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, v1, v2, cmpVolume)
|
||||
|
||||
err = s.Remove(ctx, v2)
|
||||
assert.Assert(t, is.ErrorContains(err, ""))
|
||||
assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
|
||||
|
||||
s.Release(ctx, v2.Name(), "test-ref")
|
||||
err = s.Remove(ctx, v2)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
var cmpVolume = cmp.AllowUnexported(volumetestutils.FakeVolume{}, volumeWrapper{})
|
||||
|
||||
func setupTest(t *testing.T) (*VolumeStore, func()) {
|
||||
t.Helper()
|
||||
|
||||
dirName := strings.Replace(t.Name(), string(os.PathSeparator), "_", -1)
|
||||
dir, err := ioutil.TempDir("", dirName)
|
||||
assert.NilError(t, err)
|
||||
|
||||
cleanup := func() {
|
||||
t.Helper()
|
||||
err := os.RemoveAll(dir)
|
||||
assert.Check(t, err)
|
||||
}
|
||||
|
||||
s, err := NewStore(dir, volumedrivers.NewStore(nil))
|
||||
assert.Check(t, err)
|
||||
return s, func() {
|
||||
s.Shutdown()
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterFunc(t *testing.T) {
|
||||
testDriver := volumetestutils.NewFakeDriver("test")
|
||||
testVolume, err := testDriver.Create("test", nil)
|
||||
assert.NilError(t, err)
|
||||
testVolume2, err := testDriver.Create("test2", nil)
|
||||
assert.NilError(t, err)
|
||||
testVolume3, err := testDriver.Create("test3", nil)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, test := range []struct {
|
||||
vols []volume.Volume
|
||||
fn filterFunc
|
||||
desc string
|
||||
expect []volume.Volume
|
||||
}{
|
||||
{desc: "test nil list", vols: nil, expect: nil, fn: func(volume.Volume) bool { return true }},
|
||||
{desc: "test empty list", vols: []volume.Volume{}, expect: []volume.Volume{}, fn: func(volume.Volume) bool { return true }},
|
||||
{desc: "test filter non-empty to empty", vols: []volume.Volume{testVolume}, expect: []volume.Volume{}, fn: func(volume.Volume) bool { return false }},
|
||||
{desc: "test nothing to fitler non-empty list", vols: []volume.Volume{testVolume}, expect: []volume.Volume{testVolume}, fn: func(volume.Volume) bool { return true }},
|
||||
{desc: "test filter some", vols: []volume.Volume{testVolume, testVolume2}, expect: []volume.Volume{testVolume}, fn: func(v volume.Volume) bool { return v.Name() == testVolume.Name() }},
|
||||
{desc: "test filter middle", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume, testVolume3}, fn: func(v volume.Volume) bool { return v.Name() != testVolume2.Name() }},
|
||||
{desc: "test filter middle and last", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume}, fn: func(v volume.Volume) bool { return v.Name() != testVolume2.Name() && v.Name() != testVolume3.Name() }},
|
||||
{desc: "test filter first and last", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume2}, fn: func(v volume.Volume) bool { return v.Name() != testVolume.Name() && v.Name() != testVolume3.Name() }},
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
test := test
|
||||
t.Parallel()
|
||||
|
||||
filter(&test.vols, test.fn)
|
||||
assert.DeepEqual(t, test.vols, test.expect, cmpVolume)
|
||||
})
|
||||
}
|
||||
}
|
9
vendor/github.com/docker/docker-ce/components/engine/volume/service/store_unix.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker-ce/components/engine/volume/service/store_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build linux freebsd darwin
|
||||
|
||||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
// normalizeVolumeName is a platform specific function to normalize the name
|
||||
// of a volume. This is a no-op on Unix-like platforms
|
||||
func normalizeVolumeName(name string) string {
|
||||
return name
|
||||
}
|
12
vendor/github.com/docker/docker-ce/components/engine/volume/service/store_windows.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker-ce/components/engine/volume/service/store_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
package service // import "github.com/docker/docker/volume/service"
|
||||
|
||||
import "strings"
|
||||
|
||||
// normalizeVolumeName is a platform specific function to normalize the name
|
||||
// of a volume. On Windows, as NTFS is case insensitive, under
|
||||
// c:\ProgramData\Docker\Volumes\, the folders John and john would be synonymous.
|
||||
// Hence we can't allow the volume "John" and "john" to be created as separate
|
||||
// volumes.
|
||||
func normalizeVolumeName(name string) string {
|
||||
return strings.ToLower(name)
|
||||
}
|
230
vendor/github.com/docker/docker-ce/components/engine/volume/testutils/testutils.go
generated
vendored
Normal file
230
vendor/github.com/docker/docker-ce/components/engine/volume/testutils/testutils.go
generated
vendored
Normal file
|
@ -0,0 +1,230 @@
|
|||
package testutils // import "github.com/docker/docker/volume/testutils"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
// NoopVolume is a volume that doesn't perform any operation
|
||||
type NoopVolume struct{}
|
||||
|
||||
// Name is the name of the volume
|
||||
func (NoopVolume) Name() string { return "noop" }
|
||||
|
||||
// DriverName is the name of the driver
|
||||
func (NoopVolume) DriverName() string { return "noop" }
|
||||
|
||||
// Path is the filesystem path to the volume
|
||||
func (NoopVolume) Path() string { return "noop" }
|
||||
|
||||
// Mount mounts the volume in the container
|
||||
func (NoopVolume) Mount(_ string) (string, error) { return "noop", nil }
|
||||
|
||||
// Unmount unmounts the volume from the container
|
||||
func (NoopVolume) Unmount(_ string) error { return nil }
|
||||
|
||||
// Status provides low-level details about the volume
|
||||
func (NoopVolume) Status() map[string]interface{} { return nil }
|
||||
|
||||
// CreatedAt provides the time the volume (directory) was created at
|
||||
func (NoopVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
|
||||
|
||||
// FakeVolume is a fake volume with a random name
|
||||
type FakeVolume struct {
|
||||
name string
|
||||
driverName string
|
||||
createdAt time.Time
|
||||
}
|
||||
|
||||
// NewFakeVolume creates a new fake volume for testing
|
||||
func NewFakeVolume(name string, driverName string) volume.Volume {
|
||||
return FakeVolume{name: name, driverName: driverName, createdAt: time.Now()}
|
||||
}
|
||||
|
||||
// Name is the name of the volume
|
||||
func (f FakeVolume) Name() string { return f.name }
|
||||
|
||||
// DriverName is the name of the driver
|
||||
func (f FakeVolume) DriverName() string { return f.driverName }
|
||||
|
||||
// Path is the filesystem path to the volume
|
||||
func (FakeVolume) Path() string { return "fake" }
|
||||
|
||||
// Mount mounts the volume in the container
|
||||
func (FakeVolume) Mount(_ string) (string, error) { return "fake", nil }
|
||||
|
||||
// Unmount unmounts the volume from the container
|
||||
func (FakeVolume) Unmount(_ string) error { return nil }
|
||||
|
||||
// Status provides low-level details about the volume
|
||||
func (FakeVolume) Status() map[string]interface{} {
|
||||
return map[string]interface{}{"datakey": "datavalue"}
|
||||
}
|
||||
|
||||
// CreatedAt provides the time the volume (directory) was created at
|
||||
func (f FakeVolume) CreatedAt() (time.Time, error) {
|
||||
return f.createdAt, nil
|
||||
}
|
||||
|
||||
// FakeDriver is a driver that generates fake volumes
|
||||
type FakeDriver struct {
|
||||
name string
|
||||
vols map[string]volume.Volume
|
||||
}
|
||||
|
||||
// NewFakeDriver creates a new FakeDriver with the specified name
|
||||
func NewFakeDriver(name string) volume.Driver {
|
||||
return &FakeDriver{
|
||||
name: name,
|
||||
vols: make(map[string]volume.Volume),
|
||||
}
|
||||
}
|
||||
|
||||
// Name is the name of the driver
|
||||
func (d *FakeDriver) Name() string { return d.name }
|
||||
|
||||
// Create initializes a fake volume.
|
||||
// It returns an error if the options include an "error" key with a message
|
||||
func (d *FakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) {
|
||||
if opts != nil && opts["error"] != "" {
|
||||
return nil, fmt.Errorf(opts["error"])
|
||||
}
|
||||
v := NewFakeVolume(name, d.name)
|
||||
d.vols[name] = v
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Remove deletes a volume.
|
||||
func (d *FakeDriver) Remove(v volume.Volume) error {
|
||||
if _, exists := d.vols[v.Name()]; !exists {
|
||||
return fmt.Errorf("no such volume")
|
||||
}
|
||||
delete(d.vols, v.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
// List lists the volumes
|
||||
func (d *FakeDriver) List() ([]volume.Volume, error) {
|
||||
var vols []volume.Volume
|
||||
for _, v := range d.vols {
|
||||
vols = append(vols, v)
|
||||
}
|
||||
return vols, nil
|
||||
}
|
||||
|
||||
// Get gets the volume
|
||||
func (d *FakeDriver) Get(name string) (volume.Volume, error) {
|
||||
if v, exists := d.vols[name]; exists {
|
||||
return v, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no such volume")
|
||||
}
|
||||
|
||||
// Scope returns the local scope
|
||||
func (*FakeDriver) Scope() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
type fakePlugin struct {
|
||||
client *plugins.Client
|
||||
name string
|
||||
refs int
|
||||
}
|
||||
|
||||
// MakeFakePlugin creates a fake plugin from the passed in driver
|
||||
// Note: currently only "Create" is implemented because that's all that's needed
|
||||
// so far. If you need it to test something else, add it here, but probably you
|
||||
// shouldn't need to use this except for very specific cases with v2 plugin handling.
|
||||
func MakeFakePlugin(d volume.Driver, l net.Listener) (plugingetter.CompatPlugin, error) {
|
||||
c, err := plugins.NewClient(l.Addr().Network()+"://"+l.Addr().String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
|
||||
createReq := struct {
|
||||
Name string
|
||||
Opts map[string]string
|
||||
}{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&createReq); err != nil {
|
||||
fmt.Fprintf(w, `{"Err": "%s"}`, err.Error())
|
||||
return
|
||||
}
|
||||
_, err := d.Create(createReq.Name, createReq.Opts)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, `{"Err": "%s"}`, err.Error())
|
||||
return
|
||||
}
|
||||
w.Write([]byte("{}"))
|
||||
})
|
||||
|
||||
go http.Serve(l, mux)
|
||||
return &fakePlugin{client: c, name: d.Name()}, nil
|
||||
}
|
||||
|
||||
func (p *fakePlugin) Client() *plugins.Client {
|
||||
return p.client
|
||||
}
|
||||
|
||||
func (p *fakePlugin) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
func (p *fakePlugin) IsV1() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *fakePlugin) ScopedPath(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
type fakePluginGetter struct {
|
||||
plugins map[string]plugingetter.CompatPlugin
|
||||
}
|
||||
|
||||
// NewFakePluginGetter returns a plugin getter for fake plugins
|
||||
func NewFakePluginGetter(pls ...plugingetter.CompatPlugin) plugingetter.PluginGetter {
|
||||
idx := make(map[string]plugingetter.CompatPlugin, len(pls))
|
||||
for _, p := range pls {
|
||||
idx[p.Name()] = p
|
||||
}
|
||||
return &fakePluginGetter{plugins: idx}
|
||||
}
|
||||
|
||||
// This ignores the second argument since we only care about volume drivers here,
|
||||
// there shouldn't be any other kind of plugin in here
|
||||
func (g *fakePluginGetter) Get(name, _ string, mode int) (plugingetter.CompatPlugin, error) {
|
||||
p, ok := g.plugins[name]
|
||||
if !ok {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
p.(*fakePlugin).refs += mode
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (g *fakePluginGetter) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
|
||||
panic("GetAllByCap shouldn't be called")
|
||||
}
|
||||
|
||||
func (g *fakePluginGetter) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin {
|
||||
panic("GetAllManagedPluginsByCap should not be called")
|
||||
}
|
||||
|
||||
func (g *fakePluginGetter) Handle(capability string, callback func(string, *plugins.Client)) {
|
||||
panic("Handle should not be called")
|
||||
}
|
||||
|
||||
// FakeRefs checks ref count on a fake plugin.
|
||||
func FakeRefs(p plugingetter.CompatPlugin) int {
|
||||
// this should panic if something other than a `*fakePlugin` is passed in
|
||||
return p.(*fakePlugin).refs
|
||||
}
|
69
vendor/github.com/docker/docker-ce/components/engine/volume/volume.go
generated
vendored
Normal file
69
vendor/github.com/docker/docker-ce/components/engine/volume/volume.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
package volume // import "github.com/docker/docker/volume"
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultDriverName is the driver name used for the driver
|
||||
// implemented in the local package.
|
||||
const DefaultDriverName = "local"
|
||||
|
||||
// Scopes define if a volume has is cluster-wide (global) or local only.
|
||||
// Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume
|
||||
const (
|
||||
LocalScope = "local"
|
||||
GlobalScope = "global"
|
||||
)
|
||||
|
||||
// Driver is for creating and removing volumes.
|
||||
type Driver interface {
|
||||
// Name returns the name of the volume driver.
|
||||
Name() string
|
||||
// Create makes a new volume with the given name.
|
||||
Create(name string, opts map[string]string) (Volume, error)
|
||||
// Remove deletes the volume.
|
||||
Remove(vol Volume) (err error)
|
||||
// List lists all the volumes the driver has
|
||||
List() ([]Volume, error)
|
||||
// Get retrieves the volume with the requested name
|
||||
Get(name string) (Volume, error)
|
||||
// Scope returns the scope of the driver (e.g. `global` or `local`).
|
||||
// Scope determines how the driver is handled at a cluster level
|
||||
Scope() string
|
||||
}
|
||||
|
||||
// Capability defines a set of capabilities that a driver is able to handle.
|
||||
type Capability struct {
|
||||
// Scope is the scope of the driver, `global` or `local`
|
||||
// A `global` scope indicates that the driver manages volumes across the cluster
|
||||
// A `local` scope indicates that the driver only manages volumes resources local to the host
|
||||
// Scope is declared by the driver
|
||||
Scope string
|
||||
}
|
||||
|
||||
// Volume is a place to store data. It is backed by a specific driver, and can be mounted.
|
||||
type Volume interface {
|
||||
// Name returns the name of the volume
|
||||
Name() string
|
||||
// DriverName returns the name of the driver which owns this volume.
|
||||
DriverName() string
|
||||
// Path returns the absolute path to the volume.
|
||||
Path() string
|
||||
// Mount mounts the volume and returns the absolute path to
|
||||
// where it can be consumed.
|
||||
Mount(id string) (string, error)
|
||||
// Unmount unmounts the volume when it is no longer in use.
|
||||
Unmount(id string) error
|
||||
// CreatedAt returns Volume Creation time
|
||||
CreatedAt() (time.Time, error)
|
||||
// Status returns low-level status information about a volume
|
||||
Status() map[string]interface{}
|
||||
}
|
||||
|
||||
// DetailedVolume wraps a Volume with user-defined labels, options, and cluster scope (e.g., `local` or `global`)
|
||||
type DetailedVolume interface {
|
||||
Labels() map[string]string
|
||||
Options() map[string]string
|
||||
Scope() string
|
||||
Volume
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue