Make container.Copy support volumes
Fixes #1992 Right now when you `docker cp` a path which is in a volume, the cp itself works, however you end up getting files that are in the container's fs rather than the files in the volume (which is not in the container's fs). This makes it so when you `docker cp` a path that is in a volume it follows the volume to the real path on the host. archive.go has been modified so that when you do `docker cp mydata:/foo .`, and /foo is the volume, the outputed folder is called "foo" instead of the volume ID (because we are telling it to tar up `/var/lib/docker/vfs/dir/<some id>` and not "foo", but the user would be expecting "foo", not the ID Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
parent
af52f8edcf
commit
ef98fe0763
5 changed files with 176 additions and 2 deletions
|
@ -826,19 +826,25 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var filter []string
|
||||
|
||||
basePath, err := container.getResourcePath(resource)
|
||||
if err != nil {
|
||||
container.Unmount()
|
||||
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)
|
||||
if err != nil {
|
||||
container.Unmount()
|
||||
return nil, err
|
||||
}
|
||||
var filter []string
|
||||
if !stat.IsDir() {
|
||||
d, f := path.Split(basePath)
|
||||
basePath = d
|
||||
|
|
|
@ -2,6 +2,7 @@ package daemon
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -24,6 +25,18 @@ type Mount struct {
|
|||
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 {
|
||||
if container.Volumes == nil || len(container.Volumes) == 0 {
|
||||
container.Volumes = make(map[string]string)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -371,3 +372,109 @@ func TestCpUnprivilegedUser(t *testing.T) {
|
|||
|
||||
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
|
||||
Compression Compression
|
||||
NoLchown bool
|
||||
Name string
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -359,6 +360,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
twBuf := pools.BufioWriter32KPool.Get(nil)
|
||||
defer pools.BufioWriter32KPool.Put(twBuf)
|
||||
|
||||
var renamedRelFilePath string // For when tar.Options.Name is set
|
||||
for _, include := range options.Includes {
|
||||
filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
|
@ -384,6 +386,15 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
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 {
|
||||
log.Debugf("Can't add file %s to tar: %s", srcPath, err)
|
||||
}
|
||||
|
|
|
@ -2,11 +2,14 @@ package volumes
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
)
|
||||
|
||||
|
@ -21,6 +24,35 @@ type Volume struct {
|
|||
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) {
|
||||
stat, err := os.Stat(v.Path)
|
||||
if err != nil {
|
||||
|
@ -137,3 +169,8 @@ func (v *Volume) getRootResourcePath(path string) (string, error) {
|
|||
cleanPath := filepath.Join("/", path)
|
||||
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 a new issue