container_copy.go 3.4 KB

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