فهرست منبع

Implement docker cp with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
David Calavera 9 سال پیش
والد
کامیت
1b2b91ba43
2فایلهای تغییر یافته به همراه116 افزوده شده و 77 حذف شده
  1. 13 77
      api/client/cp.go
  2. 103 0
      api/client/lib/copy.go

+ 13 - 77
api/client/cp.go

@@ -1,16 +1,13 @@
 package client
 
 import (
-	"encoding/base64"
-	"encoding/json"
 	"fmt"
 	"io"
-	"net/http"
-	"net/url"
 	"os"
 	"path/filepath"
 	"strings"
 
+	"github.com/docker/docker/api/client/lib"
 	"github.com/docker/docker/api/types"
 	Cli "github.com/docker/docker/cli"
 	"github.com/docker/docker/pkg/archive"
@@ -129,38 +126,7 @@ func splitCpArg(arg string) (container, path string) {
 }
 
 func (cli *DockerCli) statContainerPath(containerName, path string) (types.ContainerPathStat, error) {
-	var stat types.ContainerPathStat
-
-	query := make(url.Values, 1)
-	query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
-
-	urlStr := fmt.Sprintf("/containers/%s/archive?%s", containerName, query.Encode())
-
-	response, err := cli.call("HEAD", urlStr, nil, nil)
-	if err != nil {
-		return stat, err
-	}
-	defer response.body.Close()
-
-	if response.statusCode != http.StatusOK {
-		return stat, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
-	}
-
-	return getContainerPathStatFromHeader(response.header)
-}
-
-func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
-	var stat types.ContainerPathStat
-
-	encodedStat := header.Get("X-Docker-Container-Path-Stat")
-	statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
-
-	err := json.NewDecoder(statDecoder).Decode(&stat)
-	if err != nil {
-		err = fmt.Errorf("unable to decode container path stat header: %s", err)
-	}
-
-	return stat, err
+	return cli.client.StatContainerPath(containerName, path)
 }
 
 func resolveLocalPath(localPath string) (absPath string, err error) {
@@ -200,39 +166,19 @@ func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string, c
 
 	}
 
-	query := make(url.Values, 1)
-	query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
-
-	urlStr := fmt.Sprintf("/containers/%s/archive?%s", srcContainer, query.Encode())
-
-	response, err := cli.call("GET", urlStr, nil, nil)
+	content, stat, err := cli.client.CopyFromContainer(srcContainer, srcPath)
 	if err != nil {
 		return err
 	}
-	defer response.body.Close()
-
-	if response.statusCode != http.StatusOK {
-		return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
-	}
+	defer content.Close()
 
 	if dstPath == "-" {
 		// Send the response to STDOUT.
-		_, err = io.Copy(os.Stdout, response.body)
+		_, err = io.Copy(os.Stdout, content)
 
 		return err
 	}
 
-	// In order to get the copy behavior right, we need to know information
-	// about both the source and the destination. The response headers include
-	// stat info about the source that we can use in deciding exactly how to
-	// copy it locally. Along with the stat info about the local destination,
-	// we have everything we need to handle the multiple possibilities there
-	// can be when copying a file/dir from one location to another file/dir.
-	stat, err := getContainerPathStatFromHeader(response.header)
-	if err != nil {
-		return fmt.Errorf("unable to get resource stat from response: %s", err)
-	}
-
 	// Prepare source copy info.
 	srcInfo := archive.CopyInfo{
 		Path:       srcPath,
@@ -241,10 +187,10 @@ func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string, c
 		RebaseName: rebaseName,
 	}
 
-	preArchive := response.body
+	preArchive := content
 	if len(srcInfo.RebaseName) != 0 {
 		_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
-		preArchive = archive.RebaseArchiveEntries(response.body, srcBase, srcInfo.RebaseName)
+		preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
 	}
 	// See comments in the implementation of `archive.CopyTo` for exactly what
 	// goes into deciding how and whether the source archive needs to be
@@ -340,22 +286,12 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string, cpP
 		content = preparedArchive
 	}
 
-	query := make(url.Values, 2)
-	query.Set("path", filepath.ToSlash(resolvedDstPath)) // Normalize the paths used in the API.
-	// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
-	query.Set("noOverwriteDirNonDir", "true")
-
-	urlStr := fmt.Sprintf("/containers/%s/archive?%s", dstContainer, query.Encode())
-
-	response, err := cli.stream("PUT", urlStr, &streamOpts{in: content})
-	if err != nil {
-		return err
-	}
-	defer response.body.Close()
-
-	if response.statusCode != http.StatusOK {
-		return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
+	options := lib.CopyToContainerOptions{
+		ContainerID:               dstContainer,
+		Path:                      resolvedDstPath,
+		Content:                   content,
+		AllowOverwriteDirWithFile: false,
 	}
 
-	return nil
+	return cli.client.CopyToContainer(options)
 }

+ 103 - 0
api/client/lib/copy.go

@@ -0,0 +1,103 @@
+package lib
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"path/filepath"
+	"strings"
+
+	"github.com/docker/docker/api/types"
+)
+
+// CopyToContainerOptions holds information
+// about files to copy into a container
+type CopyToContainerOptions struct {
+	ContainerID               string
+	Path                      string
+	Content                   io.Reader
+	AllowOverwriteDirWithFile bool
+}
+
+// StatContainerPath returns Stat information about a path inside the container filesystem.
+func (cli *Client) StatContainerPath(containerID, path string) (types.ContainerPathStat, error) {
+	query := make(url.Values, 1)
+	query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
+
+	urlStr := fmt.Sprintf("/containers/%s/archive", containerID)
+	response, err := cli.HEAD(urlStr, query, nil)
+	if err != nil {
+		return types.ContainerPathStat{}, err
+	}
+	defer ensureReaderClosed(response)
+	return getContainerPathStatFromHeader(response.header)
+}
+
+// CopyToContainer copies content into the container filesystem.
+func (cli *Client) CopyToContainer(options CopyToContainerOptions) error {
+	var query url.Values
+	query.Set("path", filepath.ToSlash(options.Path)) // Normalize the paths used in the API.
+	// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
+	if !options.AllowOverwriteDirWithFile {
+		query.Set("noOverwriteDirNonDir", "true")
+	}
+
+	path := fmt.Sprintf("/containers/%s/archive", options.ContainerID)
+
+	response, err := cli.PUT(path, query, options.Content, nil)
+	if err != nil {
+		return err
+	}
+
+	if response.statusCode != http.StatusOK {
+		return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
+	}
+
+	return nil
+}
+
+// CopyFromContainer get the content from the container and return it as a Reader
+// to manipulate it in the host. It's up to the caller to close the reader.
+func (cli *Client) CopyFromContainer(containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
+	query := make(url.Values, 1)
+	query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
+
+	apiPath := fmt.Sprintf("/containers/%s/archive", containerID)
+	response, err := cli.GET(apiPath, query, nil)
+	if err != nil {
+		return nil, types.ContainerPathStat{}, err
+	}
+
+	if response.statusCode != http.StatusOK {
+		return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
+	}
+
+	// In order to get the copy behavior right, we need to know information
+	// about both the source and the destination. The response headers include
+	// stat info about the source that we can use in deciding exactly how to
+	// copy it locally. Along with the stat info about the local destination,
+	// we have everything we need to handle the multiple possibilities there
+	// can be when copying a file/dir from one location to another file/dir.
+	stat, err := getContainerPathStatFromHeader(response.header)
+	if err != nil {
+		return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
+	}
+	return response.body, stat, err
+}
+
+func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
+	var stat types.ContainerPathStat
+
+	encodedStat := header.Get("X-Docker-Container-Path-Stat")
+	statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
+
+	err := json.NewDecoder(statDecoder).Decode(&stat)
+	if err != nil {
+		err = fmt.Errorf("unable to decode container path stat header: %s", err)
+	}
+
+	return stat, err
+}