package docker import ( "errors" "io" "io/ioutil" "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 }