github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/client/container_copy.go (about) 1 package client // import "github.com/docker/docker/client" 2 3 import ( 4 "context" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "net/url" 11 "path/filepath" 12 "strings" 13 14 "github.com/docker/docker/api/types" 15 ) 16 17 // ContainerStatPath returns Stat information about a path inside the container filesystem. 18 func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) { 19 query := url.Values{} 20 query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. 21 22 urlStr := "/containers/" + containerID + "/archive" 23 response, err := cli.head(ctx, urlStr, query, nil) 24 defer ensureReaderClosed(response) 25 if err != nil { 26 return types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+path) 27 } 28 return getContainerPathStatFromHeader(response.header) 29 } 30 31 // CopyToContainer copies content into the container filesystem. 32 // Note that `content` must be a Reader for a TAR archive 33 func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error { 34 query := url.Values{} 35 query.Set("path", filepath.ToSlash(dstPath)) // 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 if options.CopyUIDGID { 42 query.Set("copyUIDGID", "true") 43 } 44 45 apiPath := "/containers/" + containerID + "/archive" 46 47 response, err := cli.putRaw(ctx, apiPath, query, content, nil) 48 defer ensureReaderClosed(response) 49 if err != nil { 50 return wrapResponseError(err, response, "container:path", containerID+":"+dstPath) 51 } 52 53 // TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior 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 // for a TAR archive to manipulate it in the host. It's up to the caller to close the reader. 63 func (cli *Client) CopyFromContainer(ctx context.Context, containerID, 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/" + containerID + "/archive" 68 response, err := cli.get(ctx, apiPath, query, nil) 69 if err != nil { 70 return nil, types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+srcPath) 71 } 72 73 // TODO this code converts non-error status-codes (e.g., "204 No Content") into an error; verify if this is the desired behavior 74 if response.statusCode != http.StatusOK { 75 return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) 76 } 77 78 // In order to get the copy behavior right, we need to know information 79 // about both the source and the destination. The response headers include 80 // stat info about the source that we can use in deciding exactly how to 81 // copy it locally. Along with the stat info about the local destination, 82 // we have everything we need to handle the multiple possibilities there 83 // can be when copying a file/dir from one location to another file/dir. 84 stat, err := getContainerPathStatFromHeader(response.header) 85 if err != nil { 86 return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err) 87 } 88 return response.body, stat, err 89 } 90 91 func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) { 92 var stat types.ContainerPathStat 93 94 encodedStat := header.Get("X-Docker-Container-Path-Stat") 95 statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat)) 96 97 err := json.NewDecoder(statDecoder).Decode(&stat) 98 if err != nil { 99 err = fmt.Errorf("unable to decode container path stat header: %s", err) 100 } 101 102 return stat, err 103 }