copy.go 3.3 KB

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