| package docker |
| |
| import ( |
| "errors" |
| "io" |
| "io/ioutil" |
| "os" |
| "os/exec" |
| ) |
| |
| type Archive io.Reader |
| |
| type Compression uint32 |
| |
| const ( |
| Uncompressed Compression = iota |
| Bzip2 |
| Gzip |
| Xz |
| ) |
| |
| func (compression *Compression) Flag() string { |
| switch *compression { |
| case Bzip2: |
| return "j" |
| case Gzip: |
| return "z" |
| case Xz: |
| return "J" |
| } |
| return "" |
| } |
| |
| func Tar(path string, compression Compression) (io.Reader, error) { |
| cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".") |
| return CmdStream(cmd) |
| } |
| |
| func Untar(archive io.Reader, path string) error { |
| cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x") |
| cmd.Stdin = archive |
| output, err := cmd.CombinedOutput() |
| if err != nil { |
| return errors.New(err.Error() + ": " + string(output)) |
| } |
| return nil |
| } |
| |
| // CmdStream executes a command, and returns its stdout as a stream. |
| // If the command fails to run or doesn't complete successfully, an error |
| // will be returned, including anything written on stderr. |
| func CmdStream(cmd *exec.Cmd) (io.Reader, error) { |
| stdout, err := cmd.StdoutPipe() |
| if err != nil { |
| return nil, err |
| } |
| stderr, err := cmd.StderrPipe() |
| if err != nil { |
| return nil, err |
| } |
| pipeR, pipeW := io.Pipe() |
| errChan := make(chan []byte) |
| // Collect stderr, we will use it in case of an error |
| go func() { |
| errText, e := ioutil.ReadAll(stderr) |
| if e != nil { |
| errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")") |
| } |
| errChan <- errText |
| }() |
| // Copy stdout to the returned pipe |
| go func() { |
| _, err := io.Copy(pipeW, stdout) |
| if err != nil { |
| pipeW.CloseWithError(err) |
| } |
| errText := <-errChan |
| if err := cmd.Wait(); err != nil { |
| pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText))) |
| } else { |
| pipeW.Close() |
| } |
| }() |
| // Run the command and return the pipe |
| if err := cmd.Start(); err != nil { |
| return nil, err |
| } |
| return pipeR, nil |
| } |
| |
| // NewTempArchive reads the content of src into a temporary file, and returns the contents |
| // of that file as an archive. The archive can only be read once - as soon as reading completes, |
| // the file will be deleted. |
| func NewTempArchive(src Archive, dir string) (*TempArchive, error) { |
| f, err := ioutil.TempFile(dir, "") |
| if err != nil { |
| return nil, err |
| } |
| if _, err := io.Copy(f, src); err != nil { |
| return nil, err |
| } |
| if _, err := f.Seek(0, 0); err != nil { |
| return nil, err |
| } |
| st, err := f.Stat() |
| if err != nil { |
| return nil, err |
| } |
| size := st.Size() |
| return &TempArchive{f, size}, nil |
| } |
| |
| type TempArchive struct { |
| *os.File |
| Size int64 // Pre-computed from Stat().Size() as a convenience |
| } |
| |
| func (archive *TempArchive) Read(data []byte) (int, error) { |
| n, err := archive.File.Read(data) |
| if err != nil { |
| os.Remove(archive.File.Name()) |
| } |
| return n, err |
| } |