archive.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package docker
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "os/exec"
  9. )
  10. type Archive io.Reader
  11. type Compression uint32
  12. const (
  13. Uncompressed Compression = iota
  14. Bzip2
  15. Gzip
  16. Xz
  17. )
  18. func (compression *Compression) Flag() string {
  19. switch *compression {
  20. case Bzip2:
  21. return "j"
  22. case Gzip:
  23. return "z"
  24. case Xz:
  25. return "J"
  26. }
  27. return ""
  28. }
  29. func (compression *Compression) Extension() string {
  30. switch *compression {
  31. case Uncompressed:
  32. return "tar"
  33. case Bzip2:
  34. return "tar.bz2"
  35. case Gzip:
  36. return "tar.gz"
  37. case Xz:
  38. return "tar.xz"
  39. }
  40. return ""
  41. }
  42. func Tar(path string, compression Compression) (io.Reader, error) {
  43. cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
  44. return CmdStream(cmd)
  45. }
  46. func Untar(archive io.Reader, path string) error {
  47. cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
  48. cmd.Stdin = archive
  49. // Hardcode locale environment for predictable outcome regardless of host configuration.
  50. // (see https://github.com/dotcloud/docker/issues/355)
  51. cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
  52. output, err := cmd.CombinedOutput()
  53. if err != nil {
  54. return fmt.Errorf("%s: %s", err, output)
  55. }
  56. return nil
  57. }
  58. // CmdStream executes a command, and returns its stdout as a stream.
  59. // If the command fails to run or doesn't complete successfully, an error
  60. // will be returned, including anything written on stderr.
  61. func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
  62. stdout, err := cmd.StdoutPipe()
  63. if err != nil {
  64. return nil, err
  65. }
  66. stderr, err := cmd.StderrPipe()
  67. if err != nil {
  68. return nil, err
  69. }
  70. pipeR, pipeW := io.Pipe()
  71. errChan := make(chan []byte)
  72. // Collect stderr, we will use it in case of an error
  73. go func() {
  74. errText, e := ioutil.ReadAll(stderr)
  75. if e != nil {
  76. errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
  77. }
  78. errChan <- errText
  79. }()
  80. // Copy stdout to the returned pipe
  81. go func() {
  82. _, err := io.Copy(pipeW, stdout)
  83. if err != nil {
  84. pipeW.CloseWithError(err)
  85. }
  86. errText := <-errChan
  87. if err := cmd.Wait(); err != nil {
  88. pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
  89. } else {
  90. pipeW.Close()
  91. }
  92. }()
  93. // Run the command and return the pipe
  94. if err := cmd.Start(); err != nil {
  95. return nil, err
  96. }
  97. return pipeR, nil
  98. }
  99. // NewTempArchive reads the content of src into a temporary file, and returns the contents
  100. // of that file as an archive. The archive can only be read once - as soon as reading completes,
  101. // the file will be deleted.
  102. func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
  103. f, err := ioutil.TempFile(dir, "")
  104. if err != nil {
  105. return nil, err
  106. }
  107. if _, err := io.Copy(f, src); err != nil {
  108. return nil, err
  109. }
  110. if _, err := f.Seek(0, 0); err != nil {
  111. return nil, err
  112. }
  113. st, err := f.Stat()
  114. if err != nil {
  115. return nil, err
  116. }
  117. size := st.Size()
  118. return &TempArchive{f, size}, nil
  119. }
  120. type TempArchive struct {
  121. *os.File
  122. Size int64 // Pre-computed from Stat().Size() as a convenience
  123. }
  124. func (archive *TempArchive) Read(data []byte) (int, error) {
  125. n, err := archive.File.Read(data)
  126. if err != nil {
  127. os.Remove(archive.File.Name())
  128. }
  129. return n, err
  130. }