github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/client/container_copy.go (about) 1 package client 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "net/url" 10 "path/filepath" 11 "strings" 12 13 "golang.org/x/net/context" 14 15 "github.com/docker/docker/api/types" 16 ) 17 18 // ContainerStatPath returns Stat information about a path inside the container filesystem. 19 func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) { 20 query := url.Values{} 21 query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. 22 23 urlStr := "/containers/" + containerID + "/archive" 24 response, err := cli.head(ctx, urlStr, query, nil) 25 if err != nil { 26 return types.ContainerPathStat{}, err 27 } 28 defer ensureReaderClosed(response) 29 return getContainerPathStatFromHeader(response.header) 30 } 31 32 // CopyToContainer copies content into the container filesystem. 33 // Note that `content` must be a Reader for a TAR 34 func (cli *Client) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error { 35 query := url.Values{} 36 query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. 37 // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. 38 if !options.AllowOverwriteDirWithFile { 39 query.Set("noOverwriteDirNonDir", "true") 40 } 41 42 if options.CopyUIDGID { 43 query.Set("copyUIDGID", "true") 44 } 45 46 apiPath := "/containers/" + container + "/archive" 47 48 response, err := cli.putRaw(ctx, apiPath, query, content, nil) 49 if err != nil { 50 return err 51 } 52 defer ensureReaderClosed(response) 53 54 if response.statusCode != http.StatusOK { 55 return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) 56 } 57 58 return nil 59 } 60 61 // CopyFromContainer gets the content from the container and returns it as a Reader 62 // to manipulate it in the host. It's up to the caller to close the reader. 63 func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { 64 query := make(url.Values, 1) 65 query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. 66 67 apiPath := "/containers/" + container + "/archive" 68 response, err := cli.get(ctx, apiPath, query, nil) 69 if err != nil { 70 return nil, types.ContainerPathStat{}, err 71 } 72 73 if response.statusCode != http.StatusOK { 74 return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) 75 } 76 77 // In order to get the copy behavior right, we need to know information 78 // about both the source and the destination. The response headers include 79 // stat info about the source that we can use in deciding exactly how to 80 // copy it locally. Along with the stat info about the local destination, 81 // we have everything we need to handle the multiple possibilities there 82 // can be when copying a file/dir from one location to another file/dir. 83 stat, err := getContainerPathStatFromHeader(response.header) 84 if err != nil { 85 return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err) 86 } 87 return response.body, stat, err 88 } 89 90 func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) { 91 var stat types.ContainerPathStat 92 93 encodedStat := header.Get("X-Docker-Container-Path-Stat") 94 statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat)) 95 96 err := json.NewDecoder(statDecoder).Decode(&stat) 97 if err != nil { 98 err = fmt.Errorf("unable to decode container path stat header: %s", err) 99 } 100 101 return stat, err 102 }