github.com/ph/moby@v1.13.1/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 := fmt.Sprintf("/containers/%s/archive", containerID) 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 func (cli *Client) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error { 34 query := url.Values{} 35 query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. 36 // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. 37 if !options.AllowOverwriteDirWithFile { 38 query.Set("noOverwriteDirNonDir", "true") 39 } 40 41 apiPath := fmt.Sprintf("/containers/%s/archive", container) 42 43 response, err := cli.putRaw(ctx, apiPath, query, content, nil) 44 if err != nil { 45 return err 46 } 47 defer ensureReaderClosed(response) 48 49 if response.statusCode != http.StatusOK { 50 return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) 51 } 52 53 return nil 54 } 55 56 // CopyFromContainer gets the content from the container and returns it as a Reader 57 // to manipulate it in the host. It's up to the caller to close the reader. 58 func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { 59 query := make(url.Values, 1) 60 query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. 61 62 apiPath := fmt.Sprintf("/containers/%s/archive", container) 63 response, err := cli.get(ctx, apiPath, query, nil) 64 if err != nil { 65 return nil, types.ContainerPathStat{}, err 66 } 67 68 if response.statusCode != http.StatusOK { 69 return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) 70 } 71 72 // In order to get the copy behavior right, we need to know information 73 // about both the source and the destination. The response headers include 74 // stat info about the source that we can use in deciding exactly how to 75 // copy it locally. Along with the stat info about the local destination, 76 // we have everything we need to handle the multiple possibilities there 77 // can be when copying a file/dir from one location to another file/dir. 78 stat, err := getContainerPathStatFromHeader(response.header) 79 if err != nil { 80 return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err) 81 } 82 return response.body, stat, err 83 } 84 85 func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) { 86 var stat types.ContainerPathStat 87 88 encodedStat := header.Get("X-Docker-Container-Path-Stat") 89 statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat)) 90 91 err := json.NewDecoder(statDecoder).Decode(&stat) 92 if err != nil { 93 err = fmt.Errorf("unable to decode container path stat header: %s", err) 94 } 95 96 return stat, err 97 }