ef711962 |
package docker |
97a82094 |
import (
"errors" |
54db1862 |
"fmt" |
97a82094 |
"io"
"io/ioutil" |
baacae83 |
"os" |
97a82094 |
"os/exec"
)
|
33f6a0aa |
type Archive io.Reader
|
97a82094 |
type Compression uint32
const (
Uncompressed Compression = iota
Bzip2
Gzip |
3c5d2e46 |
Xz |
97a82094 |
)
func (compression *Compression) Flag() string {
switch *compression {
case Bzip2:
return "j"
case Gzip:
return "z" |
3c5d2e46 |
case Xz:
return "J" |
97a82094 |
}
return ""
}
|
54db1862 |
func (compression *Compression) Extension() string {
switch *compression {
case Uncompressed:
return "tar"
case Bzip2:
return "tar.bz2"
case Gzip:
return "tar.gz"
case Xz:
return "tar.xz"
}
return ""
}
|
97a82094 |
func Tar(path string, compression Compression) (io.Reader, error) {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
return CmdStream(cmd)
}
|
5b828761 |
// FIXME: specify behavior when target path exists vs. doesn't exist. |
97a82094 |
func Untar(archive io.Reader, path string) error {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
cmd.Stdin = archive |
561ceac5 |
// Hardcode locale environment for predictable outcome regardless of host configuration.
// (see https://github.com/dotcloud/docker/issues/355)
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"} |
97a82094 |
output, err := cmd.CombinedOutput()
if err != nil { |
54db1862 |
return fmt.Errorf("%s: %s", err, output) |
97a82094 |
}
return nil
}
|
5b828761 |
// UntarPath is a convenience function which looks for an archive
// at filesystem path `src`, and unpacks it at `dst`.
func UntarPath(src, dst string) error {
if archive, err := os.Open(src); err != nil {
return err
} else if err := Untar(archive, dst); err != nil {
return err
}
return nil
}
// CopyWithTar creates a tar archive of filesystem path `src`, and
// unpacks it at filesystem path `dst`.
// The archive is streamed directly with fixed buffering and no
// intermediary disk IO.
//
func CopyWithTar(src, dst string) error {
archive, err := Tar(src, Uncompressed)
if err != nil {
return err
}
return Untar(archive, dst)
}
|
f85e6548 |
// 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. |
97a82094 |
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() |
58befe30 |
errChan := make(chan []byte) |
6ede6bc8 |
// Collect stderr, we will use it in case of an error |
97a82094 |
go func() {
errText, e := ioutil.ReadAll(stderr)
if e != nil {
errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
} |
58befe30 |
errChan <- errText
}() |
6ede6bc8 |
// Copy stdout to the returned pipe |
58befe30 |
go func() {
_, err := io.Copy(pipeW, stdout)
if err != nil {
pipeW.CloseWithError(err)
}
errText := <-errChan |
97a82094 |
if err := cmd.Wait(); err != nil {
pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
} else {
pipeW.Close()
}
}() |
6ede6bc8 |
// Run the command and return the pipe |
97a82094 |
if err := cmd.Start(); err != nil {
return nil, err
}
return pipeR, nil
} |
baacae83 |
// 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
} |