mirror of https://github.com/docker/docs.git
Merge pull request #8509 from cpuguy83/make_copy_support_volumes
Make container.Copy support volumes
This commit is contained in:
commit
a0781f3ea9
|
@ -826,19 +826,25 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var filter []string
|
|
||||||
|
|
||||||
basePath, err := container.getResourcePath(resource)
|
basePath, err := container.getResourcePath(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
container.Unmount()
|
container.Unmount()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is actually in a volume
|
||||||
|
for _, mnt := range container.VolumeMounts() {
|
||||||
|
if len(mnt.MountToPath) > 0 && strings.HasPrefix(resource, mnt.MountToPath[1:]) {
|
||||||
|
return mnt.Export(resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stat, err := os.Stat(basePath)
|
stat, err := os.Stat(basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
container.Unmount()
|
container.Unmount()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var filter []string
|
||||||
if !stat.IsDir() {
|
if !stat.IsDir() {
|
||||||
d, f := path.Split(basePath)
|
d, f := path.Split(basePath)
|
||||||
basePath = d
|
basePath = d
|
||||||
|
|
|
@ -2,6 +2,7 @@ package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -24,6 +25,18 @@ type Mount struct {
|
||||||
copyData bool
|
copyData bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
|
||||||
|
var name string
|
||||||
|
if resource == mnt.MountToPath[1:] {
|
||||||
|
name = filepath.Base(resource)
|
||||||
|
}
|
||||||
|
path, err := filepath.Rel(mnt.MountToPath[1:], resource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mnt.volume.Export(path, name)
|
||||||
|
}
|
||||||
|
|
||||||
func (container *Container) prepareVolumes() error {
|
func (container *Container) prepareVolumes() error {
|
||||||
if container.Volumes == nil || len(container.Volumes) == 0 {
|
if container.Volumes == nil || len(container.Volumes) == 0 {
|
||||||
container.Volumes = make(map[string]string)
|
container.Volumes = make(map[string]string)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -371,3 +372,109 @@ func TestCpUnprivilegedUser(t *testing.T) {
|
||||||
|
|
||||||
logDone("cp - unprivileged user")
|
logDone("cp - unprivileged user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCpVolumePath(t *testing.T) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "cp-test-volumepath")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
outDir, err := ioutil.TempDir("", "cp-test-volumepath-out")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(outDir)
|
||||||
|
_, err = os.Create(tmpDir + "/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, exitCode, err := cmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar")
|
||||||
|
if err != nil || exitCode != 0 {
|
||||||
|
t.Fatal("failed to create a container", out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanedContainerID := stripTrailingCharacters(out)
|
||||||
|
defer deleteContainer(cleanedContainerID)
|
||||||
|
|
||||||
|
out, _, err = cmd(t, "wait", cleanedContainerID)
|
||||||
|
if err != nil || stripTrailingCharacters(out) != "0" {
|
||||||
|
t.Fatal("failed to set up container", out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy actual volume path
|
||||||
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/foo", outDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err)
|
||||||
|
}
|
||||||
|
stat, err := os.Stat(outDir + "/foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !stat.IsDir() {
|
||||||
|
t.Fatal("expected copied content to be dir")
|
||||||
|
}
|
||||||
|
stat, err = os.Stat(outDir + "/foo/bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if stat.IsDir() {
|
||||||
|
t.Fatal("Expected file `bar` to be a file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy file nested in volume
|
||||||
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/foo/bar", outDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err)
|
||||||
|
}
|
||||||
|
stat, err = os.Stat(outDir + "/bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if stat.IsDir() {
|
||||||
|
t.Fatal("Expected file `bar` to be a file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy Bind-mounted dir
|
||||||
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/baz", outDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't copy from bind-mounted volume path: %s:%s %v", cleanedContainerID, "/baz", err)
|
||||||
|
}
|
||||||
|
stat, err = os.Stat(outDir + "/baz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !stat.IsDir() {
|
||||||
|
t.Fatal("Expected `baz` to be a dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy file nested in bind-mounted dir
|
||||||
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/baz/test", outDir)
|
||||||
|
fb, err := ioutil.ReadFile(outDir + "/baz/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fb2, err := ioutil.ReadFile(tmpDir + "/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(fb, fb2) {
|
||||||
|
t.Fatalf("Expected copied file to be duplicate of bind-mounted file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy bind-mounted file
|
||||||
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/test", outDir)
|
||||||
|
fb, err = ioutil.ReadFile(outDir + "/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fb2, err = ioutil.ReadFile(tmpDir + "/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(fb, fb2) {
|
||||||
|
t.Fatalf("Expected copied file to be duplicate of bind-mounted file")
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("cp - volume path")
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ type (
|
||||||
Excludes []string
|
Excludes []string
|
||||||
Compression Compression
|
Compression Compression
|
||||||
NoLchown bool
|
NoLchown bool
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -359,6 +360,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
||||||
twBuf := pools.BufioWriter32KPool.Get(nil)
|
twBuf := pools.BufioWriter32KPool.Get(nil)
|
||||||
defer pools.BufioWriter32KPool.Put(twBuf)
|
defer pools.BufioWriter32KPool.Put(twBuf)
|
||||||
|
|
||||||
|
var renamedRelFilePath string // For when tar.Options.Name is set
|
||||||
for _, include := range options.Includes {
|
for _, include := range options.Includes {
|
||||||
filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
|
filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -384,6 +386,15 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename the base resource
|
||||||
|
if options.Name != "" && filePath == srcPath+"/"+filepath.Base(relFilePath) {
|
||||||
|
renamedRelFilePath = relFilePath
|
||||||
|
}
|
||||||
|
// Set this to make sure the items underneath also get renamed
|
||||||
|
if options.Name != "" {
|
||||||
|
relFilePath = strings.Replace(relFilePath, renamedRelFilePath, options.Name, 1)
|
||||||
|
}
|
||||||
|
|
||||||
if err := addTarFile(filePath, relFilePath, tw, twBuf); err != nil {
|
if err := addTarFile(filePath, relFilePath, tw, twBuf); err != nil {
|
||||||
log.Debugf("Can't add file %s to tar: %s", srcPath, err)
|
log.Debugf("Can't add file %s to tar: %s", srcPath, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,14 @@ package volumes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,6 +24,35 @@ type Volume struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Volume) Export(resource, name string) (io.ReadCloser, error) {
|
||||||
|
if v.IsBindMount && filepath.Base(resource) == name {
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
basePath, err := v.getResourcePath(resource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stat, err := os.Stat(basePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var filter []string
|
||||||
|
if !stat.IsDir() {
|
||||||
|
d, f := path.Split(basePath)
|
||||||
|
basePath = d
|
||||||
|
filter = []string{f}
|
||||||
|
} else {
|
||||||
|
filter = []string{path.Base(basePath)}
|
||||||
|
basePath = path.Dir(basePath)
|
||||||
|
}
|
||||||
|
return archive.TarWithOptions(basePath, &archive.TarOptions{
|
||||||
|
Compression: archive.Uncompressed,
|
||||||
|
Name: name,
|
||||||
|
Includes: filter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Volume) IsDir() (bool, error) {
|
func (v *Volume) IsDir() (bool, error) {
|
||||||
stat, err := os.Stat(v.Path)
|
stat, err := os.Stat(v.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -137,3 +169,8 @@ func (v *Volume) getRootResourcePath(path string) (string, error) {
|
||||||
cleanPath := filepath.Join("/", path)
|
cleanPath := filepath.Join("/", path)
|
||||||
return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath)
|
return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Volume) getResourcePath(path string) (string, error) {
|
||||||
|
cleanPath := filepath.Join("/", path)
|
||||||
|
return symlink.FollowSymlinkInScope(filepath.Join(v.Path, cleanPath), v.Path)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue