container_copy.go 3.2 KB

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