Initial commit
This commit is contained in:
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
|
||||
}
|
||||
Reference in New Issue
Block a user