瀏覽代碼

Merge pull request #8509 from cpuguy83/make_copy_support_volumes

Make container.Copy support volumes
Alexandr Morozov 10 年之前
父節點
當前提交
a0781f3ea9
共有 5 個文件被更改,包括 176 次插入2 次删除
  1. 8 2
      daemon/container.go
  2. 13 0
      daemon/volumes.go
  3. 107 0
      integration-cli/docker_cli_cp_test.go
  4. 11 0
      pkg/archive/archive.go
  5. 37 0
      volumes/volume.go

+ 8 - 2
daemon/container.go

@@ -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

+ 13 - 0
daemon/volumes.go

@@ -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)

+ 107 - 0
integration-cli/docker_cli_cp_test.go

@@ -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")
+}

+ 11 - 0
pkg/archive/archive.go

@@ -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)
 				}

+ 37 - 0
volumes/volume.go

@@ -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)
+}