Merge pull request #8509 from cpuguy83/make_copy_support_volumes
Make container.Copy support volumes
This commit is contained in:
commit
a0781f3ea9
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…
Add table
Reference in a new issue