Initial commit
This commit is contained in:
104
vendor/github.com/hyperhq/hypercli/volume/drivers/adapter.go
generated
vendored
Normal file
104
vendor/github.com/hyperhq/hypercli/volume/drivers/adapter.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package volumedrivers
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
type volumeDriverAdapter struct {
|
||||
name string
|
||||
proxy *volumeDriverProxy
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Name() string {
|
||||
return a.name
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volume.Volume, error) {
|
||||
err := a.proxy.Create(name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &volumeAdapter{
|
||||
proxy: a.proxy,
|
||||
name: name,
|
||||
driverName: a.name}, 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,
|
||||
driverName: a.name,
|
||||
eMount: vp.Mountpoint,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
|
||||
v, err := a.proxy.Get(name)
|
||||
if err != nil {
|
||||
// TODO: remove this hack. Allows back compat with volume drivers that don't support this call
|
||||
if !plugins.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
return a.Create(name, nil)
|
||||
}
|
||||
|
||||
return &volumeAdapter{
|
||||
proxy: a.proxy,
|
||||
name: v.Name,
|
||||
driverName: a.Name(),
|
||||
eMount: v.Mountpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type volumeAdapter struct {
|
||||
proxy *volumeDriverProxy
|
||||
name string
|
||||
driverName string
|
||||
eMount string // ephemeral host volume path
|
||||
}
|
||||
|
||||
type proxyVolume struct {
|
||||
Name string
|
||||
Mountpoint string
|
||||
}
|
||||
|
||||
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 {
|
||||
return a.eMount
|
||||
}
|
||||
m, _ := a.proxy.Path(a.name)
|
||||
return m
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) Mount() (string, error) {
|
||||
var err error
|
||||
a.eMount, err = a.proxy.Mount(a.name)
|
||||
return a.eMount, err
|
||||
}
|
||||
|
||||
func (a *volumeAdapter) Unmount() error {
|
||||
return a.proxy.Unmount(a.name)
|
||||
}
|
||||
164
vendor/github.com/hyperhq/hypercli/volume/drivers/extpoint.go
generated
vendored
Normal file
164
vendor/github.com/hyperhq/hypercli/volume/drivers/extpoint.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type volumeDriver -name VolumeDriver
|
||||
|
||||
package volumedrivers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/pkg/locker"
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
// currently created by hand. generation tool would generate this like:
|
||||
// $ extpoint-gen Driver > volume/extpoint.go
|
||||
|
||||
var drivers = &driverExtpoint{extensions: make(map[string]volume.Driver), driverLock: &locker.Locker{}}
|
||||
|
||||
const extName = "VolumeDriver"
|
||||
|
||||
// NewVolumeDriver returns a driver has the given name mapped on the given client.
|
||||
func NewVolumeDriver(name string, c client) volume.Driver {
|
||||
proxy := &volumeDriverProxy{c}
|
||||
return &volumeDriverAdapter{name, proxy}
|
||||
}
|
||||
|
||||
type opts map[string]string
|
||||
type list []*proxyVolume
|
||||
|
||||
// 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.
|
||||
type volumeDriver interface {
|
||||
// Create a volume with the given name
|
||||
Create(name string, opts opts) (err error)
|
||||
// Remove the volume with the given name
|
||||
Remove(name string) (err error)
|
||||
// Get the mountpoint of the given volume
|
||||
Path(name string) (mountpoint string, err error)
|
||||
// Mount the given volume and return the mountpoint
|
||||
Mount(name string) (mountpoint string, err error)
|
||||
// Unmount the given volume
|
||||
Unmount(name string) (err error)
|
||||
// List lists all the volumes known to the driver
|
||||
List() (volumes list, err error)
|
||||
// Get retreives the volume with the requested name
|
||||
Get(name string) (volume *proxyVolume, err error)
|
||||
}
|
||||
|
||||
type driverExtpoint struct {
|
||||
extensions map[string]volume.Driver
|
||||
sync.Mutex
|
||||
driverLock *locker.Locker
|
||||
}
|
||||
|
||||
// Register associates the given driver to the given name, checking if
|
||||
// the name is already associated
|
||||
func Register(extension volume.Driver, name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
drivers.Lock()
|
||||
defer drivers.Unlock()
|
||||
|
||||
_, exists := drivers.extensions[name]
|
||||
if exists {
|
||||
return false
|
||||
}
|
||||
drivers.extensions[name] = extension
|
||||
return true
|
||||
}
|
||||
|
||||
// Unregister dissociates the name from it's driver, if the association exists.
|
||||
func Unregister(name string) bool {
|
||||
drivers.Lock()
|
||||
defer drivers.Unlock()
|
||||
|
||||
_, exists := drivers.extensions[name]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
delete(drivers.extensions, name)
|
||||
return true
|
||||
}
|
||||
|
||||
// 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 Lookup(name string) (volume.Driver, error) {
|
||||
drivers.driverLock.Lock(name)
|
||||
defer drivers.driverLock.Unlock(name)
|
||||
|
||||
drivers.Lock()
|
||||
ext, ok := drivers.extensions[name]
|
||||
drivers.Unlock()
|
||||
if ok {
|
||||
return ext, nil
|
||||
}
|
||||
|
||||
pl, err := plugins.Get(name, extName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
|
||||
}
|
||||
|
||||
drivers.Lock()
|
||||
defer drivers.Unlock()
|
||||
if ext, ok := drivers.extensions[name]; ok {
|
||||
return ext, nil
|
||||
}
|
||||
|
||||
d := NewVolumeDriver(name, pl.Client)
|
||||
drivers.extensions[name] = d
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// GetDriver returns a volume driver by it's name.
|
||||
// If the driver is empty, it looks for the local driver.
|
||||
func GetDriver(name string) (volume.Driver, error) {
|
||||
if name == "" {
|
||||
name = volume.DefaultDriverName
|
||||
}
|
||||
return Lookup(name)
|
||||
}
|
||||
|
||||
// GetDriverList returns list of volume drivers registered.
|
||||
// If no driver is registered, empty string list will be returned.
|
||||
func GetDriverList() []string {
|
||||
var driverList []string
|
||||
drivers.Lock()
|
||||
for driverName := range drivers.extensions {
|
||||
driverList = append(driverList, driverName)
|
||||
}
|
||||
drivers.Unlock()
|
||||
return driverList
|
||||
}
|
||||
|
||||
// GetAllDrivers lists all the registered drivers
|
||||
func GetAllDrivers() ([]volume.Driver, error) {
|
||||
plugins, err := plugins.GetAll(extName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ds []volume.Driver
|
||||
|
||||
drivers.Lock()
|
||||
defer drivers.Unlock()
|
||||
|
||||
for _, d := range drivers.extensions {
|
||||
ds = append(ds, d)
|
||||
}
|
||||
|
||||
for _, p := range plugins {
|
||||
ext, ok := drivers.extensions[p.Name]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ext = NewVolumeDriver(p.Name, p.Client)
|
||||
drivers.extensions[p.Name] = ext
|
||||
ds = append(ds, ext)
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
23
vendor/github.com/hyperhq/hypercli/volume/drivers/extpoint_test.go
generated
vendored
Normal file
23
vendor/github.com/hyperhq/hypercli/volume/drivers/extpoint_test.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package volumedrivers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/volume/testutils"
|
||||
)
|
||||
|
||||
func TestGetDriver(t *testing.T) {
|
||||
_, err := GetDriver("missing")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
||||
d, err := GetDriver("fake")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.Name() != "fake" {
|
||||
t.Fatalf("Expected fake driver, got %s\n", d.Name())
|
||||
}
|
||||
}
|
||||
207
vendor/github.com/hyperhq/hypercli/volume/drivers/proxy.go
generated
vendored
Normal file
207
vendor/github.com/hyperhq/hypercli/volume/drivers/proxy.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
// generated code - DO NOT EDIT
|
||||
|
||||
package volumedrivers
|
||||
|
||||
import "errors"
|
||||
|
||||
type client interface {
|
||||
Call(string, interface{}, interface{}) error
|
||||
}
|
||||
|
||||
type volumeDriverProxy struct {
|
||||
client
|
||||
}
|
||||
|
||||
type volumeDriverProxyCreateRequest struct {
|
||||
Name string
|
||||
Opts opts
|
||||
}
|
||||
|
||||
type volumeDriverProxyCreateResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Create(name string, opts opts) (err error) {
|
||||
var (
|
||||
req volumeDriverProxyCreateRequest
|
||||
ret volumeDriverProxyCreateResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
req.Opts = opts
|
||||
if err = pp.Call("VolumeDriver.Create", req, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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.Call("VolumeDriver.Remove", req, &ret); 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.Call("VolumeDriver.Path", req, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mountpoint = ret.Mountpoint
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyMountRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type volumeDriverProxyMountResponse struct {
|
||||
Mountpoint string
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Mount(name string) (mountpoint string, err error) {
|
||||
var (
|
||||
req volumeDriverProxyMountRequest
|
||||
ret volumeDriverProxyMountResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
if err = pp.Call("VolumeDriver.Mount", req, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mountpoint = ret.Mountpoint
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyUnmountRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type volumeDriverProxyUnmountResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) Unmount(name string) (err error) {
|
||||
var (
|
||||
req volumeDriverProxyUnmountRequest
|
||||
ret volumeDriverProxyUnmountResponse
|
||||
)
|
||||
|
||||
req.Name = name
|
||||
if err = pp.Call("VolumeDriver.Unmount", req, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type volumeDriverProxyListRequest struct {
|
||||
}
|
||||
|
||||
type volumeDriverProxyListResponse struct {
|
||||
Volumes list
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *volumeDriverProxy) List() (volumes list, err error) {
|
||||
var (
|
||||
req volumeDriverProxyListRequest
|
||||
ret volumeDriverProxyListResponse
|
||||
)
|
||||
|
||||
if err = pp.Call("VolumeDriver.List", req, &ret); 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.Call("VolumeDriver.Get", req, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
volume = ret.Volume
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
122
vendor/github.com/hyperhq/hypercli/volume/drivers/proxy_test.go
generated
vendored
Normal file
122
vendor/github.com/hyperhq/hypercli/volume/drivers/proxy_test.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
package volumedrivers
|
||||
|
||||
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"}`)
|
||||
})
|
||||
|
||||
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")
|
||||
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")
|
||||
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)
|
||||
}
|
||||
}
|
||||
232
vendor/github.com/hyperhq/hypercli/volume/local/local.go
generated
vendored
Normal file
232
vendor/github.com/hyperhq/hypercli/volume/local/local.go
generated
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
// 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 = errors.New("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 = utils.RestrictedVolumeNamePattern
|
||||
)
|
||||
|
||||
// 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, rootUID, rootGID int) (*Root, error) {
|
||||
rootDirectory := filepath.Join(scope, volumesPathName)
|
||||
|
||||
if err := idtools.MkdirAllAs(rootDirectory, 0700, rootUID, rootGID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &Root{
|
||||
scope: scope,
|
||||
path: rootDirectory,
|
||||
volumes: make(map[string]*localVolume),
|
||||
rootUID: rootUID,
|
||||
rootGID: rootGID,
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(rootDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range dirs {
|
||||
name := filepath.Base(d.Name())
|
||||
r.volumes[name] = &localVolume{
|
||||
driverName: r.Name(),
|
||||
name: name,
|
||||
path: r.DataPath(name),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
rootUID int
|
||||
rootGID int
|
||||
}
|
||||
|
||||
// List lists all the volumes
|
||||
func (r *Root) List() ([]volume.Volume, error) {
|
||||
var ls []volume.Volume
|
||||
for _, v := range r.volumes {
|
||||
ls = append(ls, v)
|
||||
}
|
||||
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, _ 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.MkdirAllAs(path, 0755, r.rootUID, r.rootGID); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
v = &localVolume{
|
||||
driverName: r.Name(),
|
||||
name: name,
|
||||
path: path,
|
||||
}
|
||||
r.volumes[name] = v
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// 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 errors.New("unknown volume type")
|
||||
}
|
||||
|
||||
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 fmt.Errorf("Unable to remove a directory of out the Docker 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 err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (r *Root) validateName(name string) error {
|
||||
if !volumeNameRegex.MatchString(name) {
|
||||
return derr.ErrorCodeVolumeName.WithArgs(name, utils.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
|
||||
usedCount int
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Mount implements the localVolume interface, returning the data location.
|
||||
func (v *localVolume) Mount() (string, error) {
|
||||
return v.path, nil
|
||||
}
|
||||
|
||||
// Umount is for satisfying the localVolume interface and does not do anything in this driver.
|
||||
func (v *localVolume) Unmount() error {
|
||||
return nil
|
||||
}
|
||||
147
vendor/github.com/hyperhq/hypercli/volume/local/local_test.go
generated
vendored
Normal file
147
vendor/github.com/hyperhq/hypercli/volume/local/local_test.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
rootDir, err := ioutil.TempDir("", "local-volume-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
r, err := New(rootDir, 0, 0)
|
||||
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, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vol, err := r.Create("testing", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r, err = New(rootDir, 0, 0)
|
||||
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, 0, 0)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateName(t *testing.T) {
|
||||
r := &Root{}
|
||||
names := map[string]bool{
|
||||
"/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)
|
||||
}
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/hyperhq/hypercli/volume/local/local_unix.go
generated
vendored
Normal file
29
vendor/github.com/hyperhq/hypercli/volume/local/local_unix.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// +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 (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var oldVfsDir = filepath.Join("vfs", "dir")
|
||||
|
||||
// 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
|
||||
}
|
||||
18
vendor/github.com/hyperhq/hypercli/volume/local/local_windows.go
generated
vendored
Normal file
18
vendor/github.com/hyperhq/hypercli/volume/local/local_windows.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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 (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
74
vendor/github.com/hyperhq/hypercli/volume/store/errors.go
generated
vendored
Normal file
74
vendor/github.com/hyperhq/hypercli/volume/store/errors.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// errVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container
|
||||
errVolumeInUse = errors.New("volume is in use")
|
||||
// errNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
|
||||
errNoSuchVolume = errors.New("no such volume")
|
||||
// errInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform
|
||||
errInvalidName = errors.New("volume name is not valid on this platform")
|
||||
// errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver
|
||||
errNameConflict = errors.New("conflict: volume name must be unique")
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func isErr(err error, expected error) bool {
|
||||
switch pe := err.(type) {
|
||||
case nil:
|
||||
return false
|
||||
case *OpErr:
|
||||
err = pe.Err
|
||||
}
|
||||
return err == expected
|
||||
}
|
||||
366
vendor/github.com/hyperhq/hypercli/volume/store/store.go
generated
vendored
Normal file
366
vendor/github.com/hyperhq/hypercli/volume/store/store.go
generated
vendored
Normal file
@@ -0,0 +1,366 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/locker"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
)
|
||||
|
||||
// New initializes a VolumeStore to keep
|
||||
// reference counting of volumes in the system.
|
||||
func New() *VolumeStore {
|
||||
return &VolumeStore{
|
||||
locks: &locker.Locker{},
|
||||
names: make(map[string]volume.Volume),
|
||||
refs: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VolumeStore) getNamed(name string) (volume.Volume, bool) {
|
||||
s.globalLock.Lock()
|
||||
v, exists := s.names[name]
|
||||
s.globalLock.Unlock()
|
||||
return v, exists
|
||||
}
|
||||
|
||||
func (s *VolumeStore) setNamed(v volume.Volume, ref string) {
|
||||
s.globalLock.Lock()
|
||||
s.names[v.Name()] = v
|
||||
if len(ref) > 0 {
|
||||
s.refs[v.Name()] = append(s.refs[v.Name()], ref)
|
||||
}
|
||||
s.globalLock.Unlock()
|
||||
}
|
||||
|
||||
func (s *VolumeStore) purge(name string) {
|
||||
s.globalLock.Lock()
|
||||
delete(s.names, name)
|
||||
delete(s.refs, name)
|
||||
s.globalLock.Unlock()
|
||||
}
|
||||
|
||||
// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
|
||||
type VolumeStore struct {
|
||||
locks *locker.Locker
|
||||
globalLock sync.Mutex
|
||||
// names stores the volume name -> driver name 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][]string
|
||||
}
|
||||
|
||||
// List proxies to all registered volume drivers to get the full list of volumes
|
||||
// If a driver returns a volume that has name which conflicts with a another volume from a different driver,
|
||||
// the first volume is chosen and the conflicting volume is dropped.
|
||||
func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
|
||||
vols, warnings, err := s.list()
|
||||
if err != nil {
|
||||
return nil, nil, &OpErr{Err: err, Op: "list"}
|
||||
}
|
||||
var out []volume.Volume
|
||||
|
||||
for _, v := range vols {
|
||||
name := normaliseVolumeName(v.Name())
|
||||
|
||||
s.locks.Lock(name)
|
||||
storedV, exists := s.getNamed(name)
|
||||
if !exists {
|
||||
s.setNamed(v, "")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// list goes through each volume driver and asks for its list of volumes.
|
||||
func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
|
||||
drivers, err := volumedrivers.GetAllDrivers()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var (
|
||||
ls []volume.Volume
|
||||
warnings []string
|
||||
)
|
||||
|
||||
type vols struct {
|
||||
vols []volume.Volume
|
||||
err error
|
||||
driverName string
|
||||
}
|
||||
chVols := make(chan vols, len(drivers))
|
||||
|
||||
for _, vd := range drivers {
|
||||
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
|
||||
}
|
||||
chVols <- vols{vols: vs}
|
||||
}(vd)
|
||||
}
|
||||
|
||||
badDrivers := make(map[string]struct{})
|
||||
for i := 0; i < len(drivers); i++ {
|
||||
vs := <-chVols
|
||||
|
||||
if vs.err != nil {
|
||||
warnings = append(warnings, vs.err.Error())
|
||||
badDrivers[vs.driverName] = struct{}{}
|
||||
logrus.Warn(vs.err)
|
||||
}
|
||||
ls = append(ls, vs.vols...)
|
||||
}
|
||||
|
||||
if len(badDrivers) > 0 {
|
||||
for _, v := range s.names {
|
||||
if _, exists := badDrivers[v.DriverName()]; exists {
|
||||
ls = append(ls, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ls, warnings, nil
|
||||
}
|
||||
|
||||
// CreateWithRef creates a volume with the given name and driver and stores the ref
|
||||
// This is just like Create() except we store the reference while holding the lock.
|
||||
// This ensures there's no race between creating a volume and then storing a reference.
|
||||
func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts map[string]string) (volume.Volume, error) {
|
||||
name = normaliseVolumeName(name)
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
v, err := s.create(name, driverName, opts)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
||||
}
|
||||
|
||||
s.setNamed(v, ref)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Create creates a volume with the given name and driver.
|
||||
func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
||||
name = normaliseVolumeName(name)
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
v, err := s.create(name, driverName, opts)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
||||
}
|
||||
s.setNamed(v, "")
|
||||
return v, 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.
|
||||
// It is expected that callers of this function hold any neccessary locks.
|
||||
func (s *VolumeStore) create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
||||
// Validate the name in a platform-specific manner
|
||||
valid, err := volume.IsVolumeNameValid(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !valid {
|
||||
return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"}
|
||||
}
|
||||
|
||||
if v, exists := s.getNamed(name); exists {
|
||||
if v.DriverName() != driverName && driverName != "" && driverName != volume.DefaultDriverName {
|
||||
return nil, errNameConflict
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Registering new volume reference: driver %s, name %s", driverName, name)
|
||||
vd, err := volumedrivers.GetDriver(driverName)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Op: "create", Name: name, Err: err}
|
||||
}
|
||||
|
||||
if v, err := vd.Get(name); err == nil {
|
||||
return v, nil
|
||||
}
|
||||
return vd.Create(name, opts)
|
||||
}
|
||||
|
||||
// GetWithRef gets a volume with the given name from the passed in driver and stores the ref
|
||||
// This is just like Get(), but we store the reference while holding the lock.
|
||||
// This makes sure there are no races between checking for the existance of a volume and adding a reference for it
|
||||
func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, error) {
|
||||
name = normaliseVolumeName(name)
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
vd, err := volumedrivers.GetDriver(driverName)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
||||
}
|
||||
|
||||
v, err := vd.Get(name)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
||||
}
|
||||
|
||||
s.setNamed(v, ref)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Get looks if a volume with the given name exists and returns it if so
|
||||
func (s *VolumeStore) Get(name string) (volume.Volume, error) {
|
||||
name = normaliseVolumeName(name)
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
v, err := s.getVolume(name)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
||||
}
|
||||
s.setNamed(v, "")
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// get requests the volume, if the driver info is stored it just access 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 neccessary locks
|
||||
func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
||||
logrus.Debugf("Getting volume reference for name: %s", name)
|
||||
if v, exists := s.names[name]; exists {
|
||||
vd, err := volumedrivers.GetDriver(v.DriverName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vd.Get(name)
|
||||
}
|
||||
|
||||
logrus.Debugf("Probing all drivers for volume with name: %s", name)
|
||||
drivers, err := volumedrivers.GetAllDrivers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range drivers {
|
||||
v, err := d.Get(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
return nil, errNoSuchVolume
|
||||
}
|
||||
|
||||
// Remove removes the requested volume. A volume is not removed if it has any refs
|
||||
func (s *VolumeStore) Remove(v volume.Volume) error {
|
||||
name := normaliseVolumeName(v.Name())
|
||||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
if refs, exists := s.refs[name]; exists && len(refs) > 0 {
|
||||
return &OpErr{Err: errVolumeInUse, Name: v.Name(), Op: "remove", Refs: refs}
|
||||
}
|
||||
|
||||
vd, err := volumedrivers.GetDriver(v.DriverName())
|
||||
if err != nil {
|
||||
return &OpErr{Err: err, Name: vd.Name(), Op: "remove"}
|
||||
}
|
||||
|
||||
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
||||
if err := vd.Remove(v); err != nil {
|
||||
return &OpErr{Err: err, Name: name, Op: "remove"}
|
||||
}
|
||||
|
||||
s.purge(name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dereference removes the specified reference to the volume
|
||||
func (s *VolumeStore) Dereference(v volume.Volume, ref string) {
|
||||
s.locks.Lock(v.Name())
|
||||
defer s.locks.Unlock(v.Name())
|
||||
|
||||
s.globalLock.Lock()
|
||||
defer s.globalLock.Unlock()
|
||||
refs, exists := s.refs[v.Name()]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
for i, r := range refs {
|
||||
if r == ref {
|
||||
s.refs[v.Name()] = append(s.refs[v.Name()][:i], s.refs[v.Name()][i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refs gets the current list of refs for the given volume
|
||||
func (s *VolumeStore) Refs(v volume.Volume) []string {
|
||||
s.locks.Lock(v.Name())
|
||||
defer s.locks.Unlock(v.Name())
|
||||
|
||||
s.globalLock.Lock()
|
||||
defer s.globalLock.Unlock()
|
||||
refs, exists := s.refs[v.Name()]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
refsOut := make([]string, len(refs))
|
||||
copy(refsOut, refs)
|
||||
return refsOut
|
||||
}
|
||||
|
||||
// FilterByDriver returns the available volumes filtered by driver name
|
||||
func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) {
|
||||
vd, err := volumedrivers.GetDriver(name)
|
||||
if err != nil {
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "list"}
|
||||
}
|
||||
ls, err := vd.List()
|
||||
if err != nil {
|
||||
return nil, &OpErr{Err: err, Name: name, Op: "list"}
|
||||
}
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
// FilterByUsed returns the available volumes filtered by if they are in use or not.
|
||||
// `used=true` returns only volumes that are being used, while `used=false` returns
|
||||
// only volumes that are not being used.
|
||||
func (s *VolumeStore) FilterByUsed(vols []volume.Volume, used bool) []volume.Volume {
|
||||
return s.filter(vols, func(v volume.Volume) bool {
|
||||
s.locks.Lock(v.Name())
|
||||
l := len(s.refs[v.Name()])
|
||||
s.locks.Unlock(v.Name())
|
||||
if (used && l > 0) || (!used && l == 0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// filterFunc defines a function to allow filter volumes in the store
|
||||
type filterFunc func(vol volume.Volume) bool
|
||||
|
||||
// filter returns the available volumes filtered by a filterFunc function
|
||||
func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume {
|
||||
var ls []volume.Volume
|
||||
for _, v := range vols {
|
||||
if f(v) {
|
||||
ls = append(ls, v)
|
||||
}
|
||||
}
|
||||
return ls
|
||||
}
|
||||
159
vendor/github.com/hyperhq/hypercli/volume/store/store_test.go
generated
vendored
Normal file
159
vendor/github.com/hyperhq/hypercli/volume/store/store_test.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
vt "github.com/docker/docker/volume/testutils"
|
||||
)
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
||||
defer volumedrivers.Unregister("fake")
|
||||
s := New()
|
||||
v, err := s.Create("fake1", "fake", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Name() != "fake1" {
|
||||
t.Fatalf("Expected fake1 volume, got %v", v)
|
||||
}
|
||||
if l, _, _ := s.List(); len(l) != 1 {
|
||||
t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
|
||||
}
|
||||
|
||||
if _, err := s.Create("none", "none", nil); err == nil {
|
||||
t.Fatalf("Expected unknown driver error, got nil")
|
||||
}
|
||||
|
||||
_, err = s.Create("fakeerror", "fake", 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) {
|
||||
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
||||
volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
|
||||
defer volumedrivers.Unregister("fake")
|
||||
defer volumedrivers.Unregister("noop")
|
||||
s := New()
|
||||
|
||||
// doing string compare here since this error comes directly from the driver
|
||||
expected := "no such volume"
|
||||
if err := s.Remove(vt.NoopVolume{}); err == nil || !strings.Contains(err.Error(), expected) {
|
||||
t.Fatalf("Expected error %q, got %v", expected, err)
|
||||
}
|
||||
|
||||
v, err := s.CreateWithRef("fake1", "fake", "fake", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := s.Remove(v); !IsInUse(err) {
|
||||
t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
|
||||
}
|
||||
s.Dereference(v, "fake")
|
||||
if err := s.Remove(v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l, _, _ := s.List(); len(l) != 0 {
|
||||
t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
||||
volumedrivers.Register(vt.NewFakeDriver("fake2"), "fake2")
|
||||
defer volumedrivers.Unregister("fake")
|
||||
defer volumedrivers.Unregister("fake2")
|
||||
|
||||
s := New()
|
||||
if _, err := s.Create("test", "fake", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.Create("test2", "fake2", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ls, _, err := s.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ls) != 2 {
|
||||
t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
||||
}
|
||||
|
||||
// and again with a new store
|
||||
s = New()
|
||||
ls, _, err = s.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ls) != 2 {
|
||||
t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterByDriver(t *testing.T) {
|
||||
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
||||
volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
|
||||
defer volumedrivers.Unregister("fake")
|
||||
defer volumedrivers.Unregister("noop")
|
||||
s := New()
|
||||
|
||||
if _, err := s.Create("fake1", "fake", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.Create("fake2", "fake", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.Create("fake3", "noop", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if l, _ := s.FilterByDriver("fake"); len(l) != 2 {
|
||||
t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
|
||||
}
|
||||
|
||||
if l, _ := s.FilterByDriver("noop"); len(l) != 1 {
|
||||
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterByUsed(t *testing.T) {
|
||||
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
||||
volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
|
||||
|
||||
s := New()
|
||||
if _, err := s.CreateWithRef("fake1", "fake", "volReference", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.Create("fake2", "fake", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vols, _, err := s.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dangling := s.FilterByUsed(vols, false)
|
||||
if len(dangling) != 1 {
|
||||
t.Fatalf("expected 1 danging volume, got %v", len(dangling))
|
||||
}
|
||||
if dangling[0].Name() != "fake2" {
|
||||
t.Fatalf("expected danging volume fake2, got %s", dangling[0].Name())
|
||||
}
|
||||
|
||||
used := s.FilterByUsed(vols, true)
|
||||
if len(used) != 1 {
|
||||
t.Fatalf("expected 1 used volume, got %v", len(used))
|
||||
}
|
||||
if used[0].Name() != "fake1" {
|
||||
t.Fatalf("expected used volume fake1, got %s", used[0].Name())
|
||||
}
|
||||
}
|
||||
9
vendor/github.com/hyperhq/hypercli/volume/store/store_unix.go
generated
vendored
Normal file
9
vendor/github.com/hyperhq/hypercli/volume/store/store_unix.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package store
|
||||
|
||||
// normaliseVolumeName is a platform specific function to normalise the name
|
||||
// of a volume. This is a no-op on Unix-like platforms
|
||||
func normaliseVolumeName(name string) string {
|
||||
return name
|
||||
}
|
||||
12
vendor/github.com/hyperhq/hypercli/volume/store/store_windows.go
generated
vendored
Normal file
12
vendor/github.com/hyperhq/hypercli/volume/store/store_windows.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package store
|
||||
|
||||
import "strings"
|
||||
|
||||
// normaliseVolumeName is a platform specific function to normalise 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 normaliseVolumeName(name string) string {
|
||||
return strings.ToLower(name)
|
||||
}
|
||||
104
vendor/github.com/hyperhq/hypercli/volume/testutils/testutils.go
generated
vendored
Normal file
104
vendor/github.com/hyperhq/hypercli/volume/testutils/testutils.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package volumetestutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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, error) { return "noop", nil }
|
||||
|
||||
// Unmount unmounts the volume from the container
|
||||
func (NoopVolume) Unmount() error { return nil }
|
||||
|
||||
// FakeVolume is a fake volume with a random name
|
||||
type FakeVolume struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// NewFakeVolume creates a new fake volume for testing
|
||||
func NewFakeVolume(name string) volume.Volume {
|
||||
return FakeVolume{name: name}
|
||||
}
|
||||
|
||||
// Name is the name of the volume
|
||||
func (f FakeVolume) Name() string { return f.name }
|
||||
|
||||
// DriverName is the name of the driver
|
||||
func (FakeVolume) DriverName() string { return "fake" }
|
||||
|
||||
// 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, error) { return "fake", nil }
|
||||
|
||||
// Unmount unmounts the volume from the container
|
||||
func (FakeVolume) Unmount() error { return 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.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")
|
||||
}
|
||||
120
vendor/github.com/hyperhq/hypercli/volume/volume.go
generated
vendored
Normal file
120
vendor/github.com/hyperhq/hypercli/volume/volume.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
package volume
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// DefaultDriverName is the driver name used for the driver
|
||||
// implemented in the local package.
|
||||
const DefaultDriverName string = "local"
|
||||
|
||||
// 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 id.
|
||||
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 retreives the volume with the requested name
|
||||
Get(name string) (Volume, error)
|
||||
}
|
||||
|
||||
// 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() (string, error)
|
||||
// Unmount unmounts the volume when it is no longer in use.
|
||||
Unmount() error
|
||||
}
|
||||
|
||||
// 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.
|
||||
type MountPoint struct {
|
||||
Source string // Container host directory
|
||||
Destination string // Inside the container
|
||||
RW bool // True if writable
|
||||
Name string // Name set by user
|
||||
Driver string // Volume driver to use
|
||||
Volume Volume `json:"-"`
|
||||
|
||||
// Note Mode is not used on Windows
|
||||
Mode string `json:"Relabel"` // Originally field was `Relabel`"
|
||||
|
||||
// Note Propagation is not used on Windows
|
||||
Propagation string // Mount propagation string
|
||||
Named bool // specifies if the mountpoint was specified by name
|
||||
}
|
||||
|
||||
// Setup sets up a mount point by either mounting the volume if it is
|
||||
// configured, or creating the source directory if supplied.
|
||||
func (m *MountPoint) Setup() (string, error) {
|
||||
if m.Volume != nil {
|
||||
return m.Volume.Mount()
|
||||
}
|
||||
if len(m.Source) > 0 {
|
||||
if _, err := os.Stat(m.Source); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
if runtime.GOOS != "windows" { // Windows does not have deprecation issues here
|
||||
logrus.Warnf("Auto-creating non-existent volume host path %s, this is deprecated and will be removed soon", m.Source)
|
||||
if err := system.MkdirAll(m.Source, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.Source, nil
|
||||
}
|
||||
return "", derr.ErrorCodeMountSetup
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ParseVolumesFrom ensure that the supplied volumes-from is valid.
|
||||
func ParseVolumesFrom(spec string) (string, string, error) {
|
||||
if len(spec) == 0 {
|
||||
return "", "", derr.ErrorCodeVolumeFromBlank.WithArgs(spec)
|
||||
}
|
||||
|
||||
specParts := strings.SplitN(spec, ":", 2)
|
||||
id := specParts[0]
|
||||
mode := "rw"
|
||||
|
||||
if len(specParts) == 2 {
|
||||
mode = specParts[1]
|
||||
if !ValidMountMode(mode) {
|
||||
return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(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 HasPropagation(mode) {
|
||||
return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(mode)
|
||||
}
|
||||
}
|
||||
return id, mode, nil
|
||||
}
|
||||
44
vendor/github.com/hyperhq/hypercli/volume/volume_propagation_linux.go
generated
vendored
Normal file
44
vendor/github.com/hyperhq/hypercli/volume/volume_propagation_linux.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// +build linux
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultPropagationMode defines what propagation mode should be used by
|
||||
// default if user has not specified one explicitly.
|
||||
const DefaultPropagationMode string = "rprivate"
|
||||
|
||||
// propagation modes
|
||||
var propagationModes = map[string]bool{
|
||||
"private": true,
|
||||
"rprivate": true,
|
||||
"slave": true,
|
||||
"rslave": true,
|
||||
"shared": true,
|
||||
"rshared": true,
|
||||
}
|
||||
|
||||
// GetPropagation extracts and returns the mount propagation mode. If there
|
||||
// are no specifications, then by default it is "private".
|
||||
func GetPropagation(mode string) string {
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
if propagationModes[o] {
|
||||
return o
|
||||
}
|
||||
}
|
||||
return DefaultPropagationMode
|
||||
}
|
||||
|
||||
// HasPropagation checks if there is a valid propagation mode present in
|
||||
// passed string. Returns true if a valid propagatio mode specifier is
|
||||
// present, false otherwise.
|
||||
func HasPropagation(mode string) bool {
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
if propagationModes[o] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
65
vendor/github.com/hyperhq/hypercli/volume/volume_propagation_linux_test.go
generated
vendored
Normal file
65
vendor/github.com/hyperhq/hypercli/volume/volume_propagation_linux_test.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// +build linux
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseMountSpecPropagation(t *testing.T) {
|
||||
var (
|
||||
valid []string
|
||||
invalid map[string]string
|
||||
)
|
||||
|
||||
valid = []string{
|
||||
"/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{
|
||||
"/path:/path:ro,rshared,rslave": `invalid mode: "ro,rshared,rslave"`,
|
||||
"/path:/path:ro,z,rshared,rslave": `invalid mode: "ro,z,rshared,rslave"`,
|
||||
"/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",
|
||||
}
|
||||
|
||||
for _, path := range valid {
|
||||
if _, err := ParseMountSpec(path, "local"); err != nil {
|
||||
t.Fatalf("ParseMountSpec(`%q`) should succeed: error %q", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
for path, expectedError := range invalid {
|
||||
if _, err := ParseMountSpec(path, "local"); err == nil {
|
||||
t.Fatalf("ParseMountSpec(`%q`) should have failed validation. Err %v", path, err)
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("ParseMountSpec(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
vendor/github.com/hyperhq/hypercli/volume/volume_propagation_unsupported.go
generated
vendored
Normal file
22
vendor/github.com/hyperhq/hypercli/volume/volume_propagation_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// +build !linux
|
||||
|
||||
package volume
|
||||
|
||||
// DefaultPropagationMode is used only in linux. In other cases it returns
|
||||
// empty string.
|
||||
const DefaultPropagationMode string = ""
|
||||
|
||||
// propagation modes not supported on this platform.
|
||||
var propagationModes = map[string]bool{}
|
||||
|
||||
// GetPropagation is not supported. Return empty string.
|
||||
func GetPropagation(mode string) string {
|
||||
return DefaultPropagationMode
|
||||
}
|
||||
|
||||
// HasPropagation checks if there is a valid propagation mode present in
|
||||
// passed string. Returns true if a valid propagatio mode specifier is
|
||||
// present, false otherwise.
|
||||
func HasPropagation(mode string) bool {
|
||||
return false
|
||||
}
|
||||
212
vendor/github.com/hyperhq/hypercli/volume/volume_test.go
generated
vendored
Normal file
212
vendor/github.com/hyperhq/hypercli/volume/volume_test.go
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
package volume
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseMountSpec(t *testing.T) {
|
||||
var (
|
||||
valid []string
|
||||
invalid map[string]string
|
||||
)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
valid = []string{
|
||||
`d:\`,
|
||||
`d:`,
|
||||
`d:\path`,
|
||||
`d:\path with space`,
|
||||
// TODO Windows post TP4 - readonly support `d:\pathandmode:ro`,
|
||||
`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`,
|
||||
// TODO Windows post TP4 - readonly support `name:D::RO`,
|
||||
`c:/:d:/forward/slashes/are/good/too`,
|
||||
// TODO Windows post TP4 - readonly support `c:/:d:/including with/spaces:ro`,
|
||||
`c:\Windows`, // With capital
|
||||
`c:\Program Files (x86)`, // With capitals and brackets
|
||||
}
|
||||
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:`: `The system cannot find the file specified`,
|
||||
`c:\windows\system32\ntdll.dll:d:`: `Source 'c:\windows\system32\ntdll.dll' is not 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`,
|
||||
`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`,
|
||||
}
|
||||
|
||||
} else {
|
||||
valid = []string{
|
||||
"/home",
|
||||
"/home:/home",
|
||||
"/home:/something/else",
|
||||
"/with space",
|
||||
"/home:/with space",
|
||||
"relative:/absolute-path",
|
||||
"hostPath:/containerPath:ro",
|
||||
"/hostPath:/containerPath:rw",
|
||||
"/rw:/ro",
|
||||
}
|
||||
invalid = map[string]string{
|
||||
"": "Invalid volume specification",
|
||||
"./": "Invalid volume destination",
|
||||
"../": "Invalid volume destination",
|
||||
"/:../": "Invalid volume destination",
|
||||
"/:path": "Invalid volume destination",
|
||||
":": "Invalid volume specification",
|
||||
"/tmp:": "Invalid volume destination",
|
||||
":test": "Invalid volume specification",
|
||||
":/test": "Invalid volume specification",
|
||||
"tmp:": "Invalid volume destination",
|
||||
":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: "sw"`,
|
||||
"/path:/path:rwz": `invalid mode: "rwz"`,
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range valid {
|
||||
if _, err := ParseMountSpec(path, "local"); err != nil {
|
||||
t.Fatalf("ParseMountSpec(`%q`) should succeed: error %q", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
for path, expectedError := range invalid {
|
||||
if _, err := ParseMountSpec(path, "local"); err == nil {
|
||||
t.Fatalf("ParseMountSpec(`%q`) should have failed validation. Err %v", path, err)
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("ParseMountSpec(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testParseMountSpec is a structure used by TestParseMountSpecSplit for
|
||||
// specifying test cases for the ParseMountSpec() function.
|
||||
type testParseMountSpec struct {
|
||||
bind string
|
||||
driver string
|
||||
expDest string
|
||||
expSource string
|
||||
expName string
|
||||
expDriver string
|
||||
expRW bool
|
||||
fail bool
|
||||
}
|
||||
|
||||
func TestParseMountSpecSplit(t *testing.T) {
|
||||
var cases []testParseMountSpec
|
||||
if runtime.GOOS == "windows" {
|
||||
cases = []testParseMountSpec{
|
||||
{`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false},
|
||||
{`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false},
|
||||
// TODO Windows post TP4 - Add readonly support {`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false},
|
||||
{`c:\:d:\:rw`, "local", `d:\`, `c:\`, ``, "", true, false},
|
||||
{`c:\:d:\:foo`, "local", `d:\`, `c:\`, ``, "", false, true},
|
||||
{`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
|
||||
{`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
|
||||
// TODO Windows post TP4 - Add readonly support {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
|
||||
{`name:c:`, "", ``, ``, ``, "", true, true},
|
||||
{`driver/name:c:`, "", ``, ``, ``, "", true, true},
|
||||
}
|
||||
} else {
|
||||
cases = []testParseMountSpec{
|
||||
{"/tmp:/tmp1", "", "/tmp1", "/tmp", "", "", true, false},
|
||||
{"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false},
|
||||
{"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false},
|
||||
{"/tmp:/tmp4:foo", "", "", "", "", "", false, true},
|
||||
{"name:/named1", "", "/named1", "", "name", "local", true, false},
|
||||
{"name:/named2", "external", "/named2", "", "name", "external", true, false},
|
||||
{"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
|
||||
{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false},
|
||||
{"/tmp:tmp", "", "", "", "", "", true, true},
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
m, err := ParseMountSpec(c.bind, c.driver)
|
||||
if c.fail {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if m == nil || err != nil {
|
||||
t.Fatalf("ParseMountSpec failed for spec %s driver %s error %v\n", c.bind, c.driver, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Destination != c.expDest {
|
||||
t.Fatalf("Expected destination %s, was %s, for spec %s\n", c.expDest, m.Destination, c.bind)
|
||||
}
|
||||
|
||||
if m.Source != c.expSource {
|
||||
t.Fatalf("Expected source %s, was %s, for spec %s\n", c.expSource, m.Source, c.bind)
|
||||
}
|
||||
|
||||
if m.Name != c.expName {
|
||||
t.Fatalf("Expected name %s, was %s for spec %s\n", c.expName, m.Name, c.bind)
|
||||
}
|
||||
|
||||
if m.Driver != c.expDriver {
|
||||
t.Fatalf("Expected driver %s, was %s, for spec %s\n", c.expDriver, m.Driver, c.bind)
|
||||
}
|
||||
|
||||
if m.RW != c.expRW {
|
||||
t.Fatalf("Expected RW %v, was %v for spec %s\n", c.expRW, m.RW, c.bind)
|
||||
}
|
||||
}
|
||||
}
|
||||
182
vendor/github.com/hyperhq/hypercli/volume/volume_unix.go
generated
vendored
Normal file
182
vendor/github.com/hyperhq/hypercli/volume/volume_unix.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// +build linux freebsd darwin solaris
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
derr "github.com/docker/docker/errors"
|
||||
)
|
||||
|
||||
// read-write modes
|
||||
var rwModes = map[string]bool{
|
||||
"rw": true,
|
||||
"ro": true,
|
||||
}
|
||||
|
||||
// label modes
|
||||
var labelModes = map[string]bool{
|
||||
"Z": true,
|
||||
"z": true,
|
||||
}
|
||||
|
||||
// BackwardsCompatible decides whether this mount point can be
|
||||
// used in old versions of Docker or not.
|
||||
// Only bind mounts and local volumes can be used in old versions of Docker.
|
||||
func (m *MountPoint) BackwardsCompatible() bool {
|
||||
return len(m.Source) > 0 || m.Driver == DefaultDriverName
|
||||
}
|
||||
|
||||
// HasResource checks whether the given absolute path for a container is in
|
||||
// this mount point. If the relative path starts with `../` then the resource
|
||||
// is outside of this mount point, but we can't simply check for this prefix
|
||||
// because it misses `..` which is also outside of the mount, so check both.
|
||||
func (m *MountPoint) HasResource(absolutePath string) bool {
|
||||
relPath, err := filepath.Rel(m.Destination, absolutePath)
|
||||
return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
|
||||
}
|
||||
|
||||
// ParseMountSpec validates the configuration of mount information is valid.
|
||||
func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
|
||||
spec = filepath.ToSlash(spec)
|
||||
|
||||
mp := &MountPoint{
|
||||
RW: true,
|
||||
Propagation: DefaultPropagationMode,
|
||||
}
|
||||
if strings.Count(spec, ":") > 2 {
|
||||
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
|
||||
}
|
||||
|
||||
arr := strings.SplitN(spec, ":", 3)
|
||||
if arr[0] == "" {
|
||||
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
|
||||
}
|
||||
|
||||
switch len(arr) {
|
||||
case 1:
|
||||
// Just a destination path in the container
|
||||
mp.Destination = filepath.Clean(arr[0])
|
||||
case 2:
|
||||
if isValid := ValidMountMode(arr[1]); isValid {
|
||||
// Destination + Mode is not a valid volume - volumes
|
||||
// cannot include a mode. eg /foo:rw
|
||||
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
|
||||
}
|
||||
// Host Source Path or Name + Destination
|
||||
mp.Source = arr[0]
|
||||
mp.Destination = arr[1]
|
||||
case 3:
|
||||
// HostSourcePath+DestinationPath+Mode
|
||||
mp.Source = arr[0]
|
||||
mp.Destination = arr[1]
|
||||
mp.Mode = arr[2] // Mode field is used by SELinux to decide whether to apply label
|
||||
if !ValidMountMode(mp.Mode) {
|
||||
return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mp.Mode)
|
||||
}
|
||||
mp.RW = ReadWrite(mp.Mode)
|
||||
mp.Propagation = GetPropagation(mp.Mode)
|
||||
default:
|
||||
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
|
||||
}
|
||||
|
||||
//validate the volumes destination path
|
||||
mp.Destination = filepath.Clean(mp.Destination)
|
||||
if !filepath.IsAbs(mp.Destination) {
|
||||
return nil, derr.ErrorCodeVolumeAbs.WithArgs(mp.Destination)
|
||||
}
|
||||
|
||||
// Destination cannot be "/"
|
||||
if mp.Destination == "/" {
|
||||
return nil, derr.ErrorCodeVolumeSlash.WithArgs(spec)
|
||||
}
|
||||
|
||||
name, source := ParseVolumeSource(mp.Source)
|
||||
if len(source) == 0 {
|
||||
mp.Source = "" // Clear it out as we previously assumed it was not a name
|
||||
mp.Driver = volumeDriver
|
||||
if len(mp.Driver) == 0 {
|
||||
mp.Driver = DefaultDriverName
|
||||
}
|
||||
// Named volumes can't have propagation properties specified.
|
||||
// Their defaults will be decided by docker. This is just a
|
||||
// safeguard. Don't want to get into situations where named
|
||||
// volumes were mounted as '[r]shared' inside container and
|
||||
// container does further mounts under that volume and these
|
||||
// mounts become visible on host and later original volume
|
||||
// cleanup becomes an issue if container does not unmount
|
||||
// submounts explicitly.
|
||||
if HasPropagation(mp.Mode) {
|
||||
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
|
||||
}
|
||||
} else {
|
||||
mp.Source = filepath.Clean(source)
|
||||
}
|
||||
|
||||
mp.Name = name
|
||||
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// ParseVolumeSource parses the origin sources that's mounted into the container.
|
||||
// It returns a name and a source. It looks to see if the spec passed in
|
||||
// is an absolute file. If it is, it assumes the spec is a source. If not,
|
||||
// it assumes the spec is a name.
|
||||
func ParseVolumeSource(spec string) (string, string) {
|
||||
if !filepath.IsAbs(spec) {
|
||||
return spec, ""
|
||||
}
|
||||
return "", spec
|
||||
}
|
||||
|
||||
// IsVolumeNameValid checks a volume name in a platform specific manner.
|
||||
func IsVolumeNameValid(name string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ValidMountMode will make sure the mount mode is valid.
|
||||
// returns if it's a valid mount mode or not.
|
||||
func ValidMountMode(mode string) bool {
|
||||
rwModeCount := 0
|
||||
labelModeCount := 0
|
||||
propagationModeCount := 0
|
||||
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
if rwModes[o] {
|
||||
rwModeCount++
|
||||
continue
|
||||
} else if labelModes[o] {
|
||||
labelModeCount++
|
||||
continue
|
||||
} else if propagationModes[o] {
|
||||
propagationModeCount++
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Only one string for each mode is allowed.
|
||||
if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadWrite tells you if a mode string is a valid read-write mode or not.
|
||||
// If there are no specifications w.r.t read write mode, then by default
|
||||
// it returs true.
|
||||
func ReadWrite(mode string) bool {
|
||||
if !ValidMountMode(mode) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, o := range strings.Split(mode, ",") {
|
||||
if o == "ro" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
199
vendor/github.com/hyperhq/hypercli/volume/volume_windows.go
generated
vendored
Normal file
199
vendor/github.com/hyperhq/hypercli/volume/volume_windows.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
package volume
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
derr "github.com/docker/docker/errors"
|
||||
)
|
||||
|
||||
// read-write modes
|
||||
var rwModes = map[string]bool{
|
||||
"rw": true,
|
||||
}
|
||||
|
||||
// read-only modes
|
||||
var roModes = map[string]bool{
|
||||
"ro": true,
|
||||
}
|
||||
|
||||
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])`
|
||||
|
||||
// RXSource is the combined possibilities for a source
|
||||
RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `))):)?`
|
||||
|
||||
// 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]+)*\\?))`
|
||||
// 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
|
||||
RXMode = `(:(?P<mode>(?i)rw))?`
|
||||
// Temporarily for TP4, disabling the use of ro as it's not supported yet
|
||||
// in the platform. TODO Windows: `(:(?P<mode>(?i)ro|rw))?`
|
||||
// mode (optional)
|
||||
// - Hopefully self explanatory in comparison to above.
|
||||
// - Colon is not in the capture group
|
||||
//
|
||||
)
|
||||
|
||||
// BackwardsCompatible decides whether this mount point can be
|
||||
// used in old versions of Docker or not.
|
||||
// Windows volumes are never backwards compatible.
|
||||
func (m *MountPoint) BackwardsCompatible() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ParseMountSpec validates the configuration of mount information is valid.
|
||||
func ParseMountSpec(spec string, volumeDriver string) (*MountPoint, error) {
|
||||
var specExp = regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
|
||||
|
||||
// Ensure in platform semantics for matching. The CLI will send in Unix semantics.
|
||||
match := specExp.FindStringSubmatch(filepath.FromSlash(strings.ToLower(spec)))
|
||||
|
||||
// Must have something back
|
||||
if len(match) == 0 {
|
||||
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
|
||||
}
|
||||
|
||||
// Pull out the sub expressions from the named capture groups
|
||||
matchgroups := make(map[string]string)
|
||||
for i, name := range specExp.SubexpNames() {
|
||||
matchgroups[name] = strings.ToLower(match[i])
|
||||
}
|
||||
|
||||
mp := &MountPoint{
|
||||
Source: matchgroups["source"],
|
||||
Destination: matchgroups["destination"],
|
||||
RW: true,
|
||||
}
|
||||
if strings.ToLower(matchgroups["mode"]) == "ro" {
|
||||
mp.RW = false
|
||||
}
|
||||
|
||||
// Volumes cannot include an explicitly supplied mode eg c:\path:rw
|
||||
if mp.Source == "" && mp.Destination != "" && matchgroups["mode"] != "" {
|
||||
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
|
||||
}
|
||||
|
||||
// Note: No need to check if destination is absolute as it must be by
|
||||
// definition of matching the regex.
|
||||
|
||||
if filepath.VolumeName(mp.Destination) == mp.Destination {
|
||||
// Ensure the destination path, if a drive letter, is not the c drive
|
||||
if strings.ToLower(mp.Destination) == "c:" {
|
||||
return nil, derr.ErrorCodeVolumeDestIsC.WithArgs(spec)
|
||||
}
|
||||
} else {
|
||||
// So we know the destination is a path, not drive letter. Clean it up.
|
||||
mp.Destination = filepath.Clean(mp.Destination)
|
||||
// Ensure the destination path, if a path, is not the c root directory
|
||||
if strings.ToLower(mp.Destination) == `c:\` {
|
||||
return nil, derr.ErrorCodeVolumeDestIsCRoot.WithArgs(spec)
|
||||
}
|
||||
}
|
||||
|
||||
// See if the source is a name instead of a host directory
|
||||
if len(mp.Source) > 0 {
|
||||
validName, err := IsVolumeNameValid(mp.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if validName {
|
||||
// OK, so the source is a name.
|
||||
mp.Name = mp.Source
|
||||
mp.Source = ""
|
||||
|
||||
// Set the driver accordingly
|
||||
mp.Driver = volumeDriver
|
||||
if len(mp.Driver) == 0 {
|
||||
mp.Driver = DefaultDriverName
|
||||
}
|
||||
} else {
|
||||
// OK, so the source must be a host directory. Make sure it's clean.
|
||||
mp.Source = filepath.Clean(mp.Source)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the host path source, if supplied, exists and is a directory
|
||||
if len(mp.Source) > 0 {
|
||||
var fi os.FileInfo
|
||||
var err error
|
||||
if fi, err = os.Stat(mp.Source); err != nil {
|
||||
return nil, derr.ErrorCodeVolumeSourceNotFound.WithArgs(mp.Source, err)
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return nil, derr.ErrorCodeVolumeSourceNotDirectory.WithArgs(mp.Source)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("MP: Source '%s', Dest '%s', RW %t, Name '%s', Driver '%s'", mp.Source, mp.Destination, mp.RW, mp.Name, mp.Driver)
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// IsVolumeNameValid checks a volume name in a platform specific manner.
|
||||
func IsVolumeNameValid(name string) (bool, error) {
|
||||
nameExp := regexp.MustCompile(`^` + RXName + `$`)
|
||||
if !nameExp.MatchString(name) {
|
||||
return false, nil
|
||||
}
|
||||
nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`)
|
||||
if nameExp.MatchString(name) {
|
||||
return false, derr.ErrorCodeVolumeNameReservedWord.WithArgs(name)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ValidMountMode will make sure the mount mode is valid.
|
||||
// returns if it's a valid mount mode or not.
|
||||
func ValidMountMode(mode string) bool {
|
||||
return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
|
||||
}
|
||||
|
||||
// ReadWrite tells you if a mode string is a valid read-write mode or not.
|
||||
func ReadWrite(mode string) bool {
|
||||
return rwModes[strings.ToLower(mode)]
|
||||
}
|
||||
Reference in New Issue
Block a user