1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Implement docker cp with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
David Calavera 2015-12-02 23:56:03 -05:00
parent 8c9ad7b818
commit 1b2b91ba43
2 changed files with 116 additions and 77 deletions

View file

@ -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
api/client/lib/copy.go Normal file
View file

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