Merge pull request #669 from vmarmol/labels
Support for container labels
This commit is contained in:
commit
9c92d8ffb3
51
Godeps/Godeps.json
generated
51
Godeps/Godeps.json
generated
@ -55,16 +55,51 @@
|
||||
"Comment": "v2-27-g97e243d",
|
||||
"Rev": "97e243d21a8e232e9d8af38ba2366dfcfceebeba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/archive",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/fileutils",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/ioutils",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/mount",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/pools",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/promise",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/system",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/units",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
||||
"Comment": "v1.4.1-1379-g8e107a9",
|
||||
"Rev": "8e107a93210c54f22ec1354d969c771b1abfbe05"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/libcontainer",
|
||||
"Comment": "v1.4.0-412-g4ea9039",
|
||||
@ -72,8 +107,8 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fsouza/go-dockerclient",
|
||||
"Comment": "0.2.1-251-g2e21eae",
|
||||
"Rev": "2e21eaef5e7d46f002e259eb7cde39ed3680a7b4"
|
||||
"Comment": "0.2.1-461-g09334c5",
|
||||
"Rev": "09334c56c63bab2cd6c4ccab924d89e2419a361f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/godbus/dbus",
|
||||
@ -158,18 +193,6 @@
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
"Rev": "ca8a464d23d55afd32571475db223e065ffd8a52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/internal",
|
||||
"Rev": "ca8a464d23d55afd32571475db223e065ffd8a52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/jwt",
|
||||
"Rev": "ca8a464d23d55afd32571475db223e065ffd8a52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/jws",
|
||||
"Rev": "ca8a464d23d55afd32571475db223e065ffd8a52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/cloud/internal",
|
||||
"Rev": "542bfb014d8e28df6e27be847dfdc40c510dab1a"
|
||||
|
2
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/MAINTAINERS
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/MAINTAINERS
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
Cristian Staretu <cristian.staretu@gmail.com> (@unclejack)
|
||||
Tibor Vass <teabee89@gmail.com> (@tiborvass)
|
1
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/README.md
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/README.md
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
This code provides helper functions for dealing with archive files.
|
832
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive.go
generated
vendored
Normal file
832
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive.go
generated
vendored
Normal file
@ -0,0 +1,832 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
type (
|
||||
Archive io.ReadCloser
|
||||
ArchiveReader io.Reader
|
||||
Compression int
|
||||
TarOptions struct {
|
||||
IncludeFiles []string
|
||||
ExcludePatterns []string
|
||||
Compression Compression
|
||||
NoLchown bool
|
||||
Name string
|
||||
}
|
||||
|
||||
// Archiver allows the reuse of most utility functions of this package
|
||||
// with a pluggable Untar function.
|
||||
Archiver struct {
|
||||
Untar func(io.Reader, string, *TarOptions) error
|
||||
}
|
||||
|
||||
// breakoutError is used to differentiate errors related to breaking out
|
||||
// When testing archive breakout in the unit tests, this error is expected
|
||||
// in order for the test to pass.
|
||||
breakoutError error
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotImplemented = errors.New("Function not implemented")
|
||||
defaultArchiver = &Archiver{Untar}
|
||||
)
|
||||
|
||||
const (
|
||||
Uncompressed Compression = iota
|
||||
Bzip2
|
||||
Gzip
|
||||
Xz
|
||||
)
|
||||
|
||||
func IsArchive(header []byte) bool {
|
||||
compression := DetectCompression(header)
|
||||
if compression != Uncompressed {
|
||||
return true
|
||||
}
|
||||
r := tar.NewReader(bytes.NewBuffer(header))
|
||||
_, err := r.Next()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func DetectCompression(source []byte) Compression {
|
||||
for compression, m := range map[Compression][]byte{
|
||||
Bzip2: {0x42, 0x5A, 0x68},
|
||||
Gzip: {0x1F, 0x8B, 0x08},
|
||||
Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
|
||||
} {
|
||||
if len(source) < len(m) {
|
||||
log.Debugf("Len too short")
|
||||
continue
|
||||
}
|
||||
if bytes.Compare(m, source[:len(m)]) == 0 {
|
||||
return compression
|
||||
}
|
||||
}
|
||||
return Uncompressed
|
||||
}
|
||||
|
||||
func xzDecompress(archive io.Reader) (io.ReadCloser, error) {
|
||||
args := []string{"xz", "-d", "-c", "-q"}
|
||||
|
||||
return CmdStream(exec.Command(args[0], args[1:]...), archive)
|
||||
}
|
||||
|
||||
func DecompressStream(archive io.Reader) (io.ReadCloser, error) {
|
||||
p := pools.BufioReader32KPool
|
||||
buf := p.Get(archive)
|
||||
bs, err := buf.Peek(10)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compression := DetectCompression(bs)
|
||||
switch compression {
|
||||
case Uncompressed:
|
||||
readBufWrapper := p.NewReadCloserWrapper(buf, buf)
|
||||
return readBufWrapper, nil
|
||||
case Gzip:
|
||||
gzReader, err := gzip.NewReader(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readBufWrapper := p.NewReadCloserWrapper(buf, gzReader)
|
||||
return readBufWrapper, nil
|
||||
case Bzip2:
|
||||
bz2Reader := bzip2.NewReader(buf)
|
||||
readBufWrapper := p.NewReadCloserWrapper(buf, bz2Reader)
|
||||
return readBufWrapper, nil
|
||||
case Xz:
|
||||
xzReader, err := xzDecompress(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readBufWrapper := p.NewReadCloserWrapper(buf, xzReader)
|
||||
return readBufWrapper, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
|
||||
}
|
||||
}
|
||||
|
||||
func CompressStream(dest io.WriteCloser, compression Compression) (io.WriteCloser, error) {
|
||||
p := pools.BufioWriter32KPool
|
||||
buf := p.Get(dest)
|
||||
switch compression {
|
||||
case Uncompressed:
|
||||
writeBufWrapper := p.NewWriteCloserWrapper(buf, buf)
|
||||
return writeBufWrapper, nil
|
||||
case Gzip:
|
||||
gzWriter := gzip.NewWriter(dest)
|
||||
writeBufWrapper := p.NewWriteCloserWrapper(buf, gzWriter)
|
||||
return writeBufWrapper, nil
|
||||
case Bzip2, Xz:
|
||||
// archive/bzip2 does not support writing, and there is no xz support at all
|
||||
// However, this is not a problem as docker only currently generates gzipped tars
|
||||
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
|
||||
}
|
||||
}
|
||||
|
||||
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 ""
|
||||
}
|
||||
|
||||
type tarAppender struct {
|
||||
TarWriter *tar.Writer
|
||||
Buffer *bufio.Writer
|
||||
|
||||
// for hardlink mapping
|
||||
SeenFiles map[uint64]string
|
||||
}
|
||||
|
||||
// canonicalTarName provides a platform-independent and consistent posix-style
|
||||
//path for files and directories to be archived regardless of the platform.
|
||||
func canonicalTarName(name string, isDir bool) (string, error) {
|
||||
name, err := CanonicalTarNameForPath(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// suffix with '/' for directories
|
||||
if isDir && !strings.HasSuffix(name, "/") {
|
||||
name += "/"
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (ta *tarAppender) addTarFile(path, name string) error {
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
link := ""
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
if link, err = os.Readlink(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hdr, err := tar.FileInfoHeader(fi, link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name, err = canonicalTarName(name, fi.IsDir())
|
||||
if err != nil {
|
||||
return fmt.Errorf("tar: cannot canonicalize path: %v", err)
|
||||
}
|
||||
hdr.Name = name
|
||||
|
||||
nlink, inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if it's a regular file and has more than 1 link,
|
||||
// it's hardlinked, so set the type flag accordingly
|
||||
if fi.Mode().IsRegular() && nlink > 1 {
|
||||
// a link should have a name that it links too
|
||||
// and that linked name should be first in the tar archive
|
||||
if oldpath, ok := ta.SeenFiles[inode]; ok {
|
||||
hdr.Typeflag = tar.TypeLink
|
||||
hdr.Linkname = oldpath
|
||||
hdr.Size = 0 // This Must be here for the writer math to add up!
|
||||
} else {
|
||||
ta.SeenFiles[inode] = name
|
||||
}
|
||||
}
|
||||
|
||||
capability, _ := system.Lgetxattr(path, "security.capability")
|
||||
if capability != nil {
|
||||
hdr.Xattrs = make(map[string]string)
|
||||
hdr.Xattrs["security.capability"] = string(capability)
|
||||
}
|
||||
|
||||
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hdr.Typeflag == tar.TypeReg {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ta.Buffer.Reset(ta.TarWriter)
|
||||
defer ta.Buffer.Reset(nil)
|
||||
_, err = io.Copy(ta.Buffer, file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ta.Buffer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool) error {
|
||||
// hdr.Mode is in linux format, which we can use for sycalls,
|
||||
// but for os.Foo() calls we need the mode converted to os.FileMode,
|
||||
// so use hdrInfo.Mode() (they differ for e.g. setuid bits)
|
||||
hdrInfo := hdr.FileInfo()
|
||||
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeDir:
|
||||
// Create directory unless it exists as a directory already.
|
||||
// In that case we just want to merge the two
|
||||
if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
|
||||
if err := os.Mkdir(path, hdrInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case tar.TypeReg, tar.TypeRegA:
|
||||
// Source is regular file
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(file, reader); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
file.Close()
|
||||
|
||||
case tar.TypeBlock, tar.TypeChar, tar.TypeFifo:
|
||||
mode := uint32(hdr.Mode & 07777)
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeBlock:
|
||||
mode |= syscall.S_IFBLK
|
||||
case tar.TypeChar:
|
||||
mode |= syscall.S_IFCHR
|
||||
case tar.TypeFifo:
|
||||
mode |= syscall.S_IFIFO
|
||||
}
|
||||
|
||||
if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeLink:
|
||||
targetPath := filepath.Join(extractDir, hdr.Linkname)
|
||||
// check for hardlink breakout
|
||||
if !strings.HasPrefix(targetPath, extractDir) {
|
||||
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname))
|
||||
}
|
||||
if err := os.Link(targetPath, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeSymlink:
|
||||
// path -> hdr.Linkname = targetPath
|
||||
// e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file
|
||||
targetPath := filepath.Join(filepath.Dir(path), hdr.Linkname)
|
||||
|
||||
// the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because
|
||||
// that symlink would first have to be created, which would be caught earlier, at this very check:
|
||||
if !strings.HasPrefix(targetPath, extractDir) {
|
||||
return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname))
|
||||
}
|
||||
if err := os.Symlink(hdr.Linkname, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeXGlobalHeader:
|
||||
log.Debugf("PAX Global Extended Headers found and ignored")
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag)
|
||||
}
|
||||
|
||||
if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, value := range hdr.Xattrs {
|
||||
if err := system.Lsetxattr(path, key, []byte(value), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||
// must happen after chown, as that can modify the file mode
|
||||
if hdr.Typeflag != tar.TypeSymlink {
|
||||
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
|
||||
// syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
|
||||
if hdr.Typeflag != tar.TypeSymlink {
|
||||
if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := system.LUtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tar creates an archive from the directory at `path`, and returns it as a
|
||||
// stream of bytes.
|
||||
func Tar(path string, compression Compression) (io.ReadCloser, error) {
|
||||
return TarWithOptions(path, &TarOptions{Compression: compression})
|
||||
}
|
||||
|
||||
func escapeName(name string) string {
|
||||
escaped := make([]byte, 0)
|
||||
for i, c := range []byte(name) {
|
||||
if i == 0 && c == '/' {
|
||||
continue
|
||||
}
|
||||
// all printable chars except "-" which is 0x2d
|
||||
if (0x20 <= c && c <= 0x7E) && c != 0x2d {
|
||||
escaped = append(escaped, c)
|
||||
} else {
|
||||
escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
|
||||
}
|
||||
}
|
||||
return string(escaped)
|
||||
}
|
||||
|
||||
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
|
||||
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
|
||||
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
compressWriter, err := CompressStream(pipeWriter, options.Compression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
ta := &tarAppender{
|
||||
TarWriter: tar.NewWriter(compressWriter),
|
||||
Buffer: pools.BufioWriter32KPool.Get(nil),
|
||||
SeenFiles: make(map[uint64]string),
|
||||
}
|
||||
// this buffer is needed for the duration of this piped stream
|
||||
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
||||
|
||||
// In general we log errors here but ignore them because
|
||||
// during e.g. a diff operation the container can continue
|
||||
// mutating the filesystem and we can see transient errors
|
||||
// from this
|
||||
|
||||
if options.IncludeFiles == nil {
|
||||
options.IncludeFiles = []string{"."}
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
|
||||
var renamedRelFilePath string // For when tar.Options.Name is set
|
||||
for _, include := range options.IncludeFiles {
|
||||
filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Debugf("Tar: Can't stat file %s to tar: %s", srcPath, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
relFilePath, err := filepath.Rel(srcPath, filePath)
|
||||
if err != nil || (relFilePath == "." && f.IsDir()) {
|
||||
// Error getting relative path OR we are looking
|
||||
// at the root path. Skip in both situations.
|
||||
return nil
|
||||
}
|
||||
|
||||
skip := false
|
||||
|
||||
// If "include" is an exact match for the current file
|
||||
// then even if there's an "excludePatterns" pattern that
|
||||
// matches it, don't skip it. IOW, assume an explicit 'include'
|
||||
// is asking for that file no matter what - which is true
|
||||
// for some files, like .dockerignore and Dockerfile (sometimes)
|
||||
if include != relFilePath {
|
||||
skip, err = fileutils.Matches(relFilePath, options.ExcludePatterns)
|
||||
if err != nil {
|
||||
log.Debugf("Error matching %s", relFilePath, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if skip {
|
||||
if f.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if seen[relFilePath] {
|
||||
return nil
|
||||
}
|
||||
seen[relFilePath] = true
|
||||
|
||||
// Rename the base resource
|
||||
if options.Name != "" && filePath == srcPath+"/"+filepath.Base(relFilePath) {
|
||||
renamedRelFilePath = relFilePath
|
||||
}
|
||||
// Set this to make sure the items underneath also get renamed
|
||||
if options.Name != "" {
|
||||
relFilePath = strings.Replace(relFilePath, renamedRelFilePath, options.Name, 1)
|
||||
}
|
||||
|
||||
if err := ta.addTarFile(filePath, relFilePath); err != nil {
|
||||
log.Debugf("Can't add file %s to tar: %s", filePath, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
if err := ta.TarWriter.Close(); err != nil {
|
||||
log.Debugf("Can't close tar writer: %s", err)
|
||||
}
|
||||
if err := compressWriter.Close(); err != nil {
|
||||
log.Debugf("Can't close compress writer: %s", err)
|
||||
}
|
||||
if err := pipeWriter.Close(); err != nil {
|
||||
log.Debugf("Can't close pipe writer: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return pipeReader, nil
|
||||
}
|
||||
|
||||
func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error {
|
||||
tr := tar.NewReader(decompressedArchive)
|
||||
trBuf := pools.BufioReader32KPool.Get(nil)
|
||||
defer pools.BufioReader32KPool.Put(trBuf)
|
||||
|
||||
var dirs []*tar.Header
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
loop:
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Normalize name, for safety and for a simple is-root check
|
||||
// This keeps "../" as-is, but normalizes "/../" to "/"
|
||||
hdr.Name = filepath.Clean(hdr.Name)
|
||||
|
||||
for _, exclude := range options.ExcludePatterns {
|
||||
if strings.HasPrefix(hdr.Name, exclude) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(hdr.Name, "/") {
|
||||
// Not the root directory, ensure that the parent directory exists
|
||||
parent := filepath.Dir(hdr.Name)
|
||||
parentPath := filepath.Join(dest, parent)
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
err = os.MkdirAll(parentPath, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
rel, err := filepath.Rel(dest, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(rel, "../") {
|
||||
return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
||||
}
|
||||
|
||||
// If path exits we almost always just want to remove and replace it
|
||||
// The only exception is when it is a directory *and* the file from
|
||||
// the layer is also a directory. Then we want to merge them (i.e.
|
||||
// just apply the metadata from the layer).
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
if fi.IsDir() && hdr.Name == "." {
|
||||
continue
|
||||
}
|
||||
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
trBuf.Reset(tr)
|
||||
if err := createTarFile(path, dest, hdr, trBuf, !options.NoLchown); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Directory mtimes must be handled at the end to avoid further
|
||||
// file creation in them to modify the directory mtime
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
dirs = append(dirs, hdr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, hdr := range dirs {
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
|
||||
if err := syscall.UtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `dest`.
|
||||
// The archive may be compressed with one of the following algorithms:
|
||||
// identity (uncompressed), gzip, bzip2, xz.
|
||||
// FIXME: specify behavior when target path exists vs. doesn't exist.
|
||||
func Untar(archive io.Reader, dest string, options *TarOptions) error {
|
||||
if archive == nil {
|
||||
return fmt.Errorf("Empty archive")
|
||||
}
|
||||
dest = filepath.Clean(dest)
|
||||
if options == nil {
|
||||
options = &TarOptions{}
|
||||
}
|
||||
if options.ExcludePatterns == nil {
|
||||
options.ExcludePatterns = []string{}
|
||||
}
|
||||
decompressedArchive, err := DecompressStream(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer decompressedArchive.Close()
|
||||
return Unpack(decompressedArchive, dest, options)
|
||||
}
|
||||
|
||||
func (archiver *Archiver) TarUntar(src, dst string) error {
|
||||
log.Debugf("TarUntar(%s %s)", src, dst)
|
||||
archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer archive.Close()
|
||||
return archiver.Untar(archive, dst, nil)
|
||||
}
|
||||
|
||||
// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
|
||||
// If either Tar or Untar fails, TarUntar aborts and returns the error.
|
||||
func TarUntar(src, dst string) error {
|
||||
return defaultArchiver.TarUntar(src, dst)
|
||||
}
|
||||
|
||||
func (archiver *Archiver) UntarPath(src, dst string) error {
|
||||
archive, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer archive.Close()
|
||||
if err := archiver.Untar(archive, dst, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return defaultArchiver.UntarPath(src, dst)
|
||||
}
|
||||
|
||||
func (archiver *Archiver) CopyWithTar(src, dst string) error {
|
||||
srcSt, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !srcSt.IsDir() {
|
||||
return archiver.CopyFileWithTar(src, dst)
|
||||
}
|
||||
// Create dst, copy src's content into it
|
||||
log.Debugf("Creating dest directory: %s", dst)
|
||||
if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Calling TarUntar(%s, %s)", src, dst)
|
||||
return archiver.TarUntar(src, dst)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return defaultArchiver.CopyWithTar(src, dst)
|
||||
}
|
||||
|
||||
func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
|
||||
log.Debugf("CopyFileWithTar(%s, %s)", src, dst)
|
||||
srcSt, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if srcSt.IsDir() {
|
||||
return fmt.Errorf("Can't copy a directory")
|
||||
}
|
||||
// Clean up the trailing /
|
||||
if dst[len(dst)-1] == '/' {
|
||||
dst = path.Join(dst, filepath.Base(src))
|
||||
}
|
||||
// Create the holding directory if necessary
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
r, w := io.Pipe()
|
||||
errC := promise.Go(func() error {
|
||||
defer w.Close()
|
||||
|
||||
srcF, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcF.Close()
|
||||
|
||||
hdr, err := tar.FileInfoHeader(srcSt, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = filepath.Base(dst)
|
||||
tw := tar.NewWriter(w)
|
||||
defer tw.Close()
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(tw, srcF); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
defer func() {
|
||||
if er := <-errC; err != nil {
|
||||
err = er
|
||||
}
|
||||
}()
|
||||
return archiver.Untar(r, filepath.Dir(dst), nil)
|
||||
}
|
||||
|
||||
// CopyFileWithTar emulates the behavior of the 'cp' command-line
|
||||
// for a single file. It copies a regular file from path `src` to
|
||||
// path `dst`, and preserves all its metadata.
|
||||
//
|
||||
// If `dst` ends with a trailing slash '/', the final destination path
|
||||
// will be `dst/base(src)`.
|
||||
func CopyFileWithTar(src, dst string) (err error) {
|
||||
return defaultArchiver.CopyFileWithTar(src, dst)
|
||||
}
|
||||
|
||||
// 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, input io.Reader) (io.ReadCloser, error) {
|
||||
if input != nil {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write stdin if any
|
||||
go func() {
|
||||
io.Copy(stdin, input)
|
||||
stdin.Close()
|
||||
}()
|
||||
}
|
||||
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(fmt.Errorf("%s: %s", err, 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.Sync(); 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{File: f, Size: size}, nil
|
||||
}
|
||||
|
||||
type TempArchive struct {
|
||||
*os.File
|
||||
Size int64 // Pre-computed from Stat().Size() as a convenience
|
||||
read int64
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Close closes the underlying file if it's still open, or does a no-op
|
||||
// to allow callers to try to close the TempArchive multiple times safely.
|
||||
func (archive *TempArchive) Close() error {
|
||||
if archive.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
archive.closed = true
|
||||
|
||||
return archive.File.Close()
|
||||
}
|
||||
|
||||
func (archive *TempArchive) Read(data []byte) (int, error) {
|
||||
n, err := archive.File.Read(data)
|
||||
archive.read += int64(n)
|
||||
if err != nil || archive.read == archive.Size {
|
||||
archive.Close()
|
||||
os.Remove(archive.File.Name())
|
||||
}
|
||||
return n, err
|
||||
}
|
625
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_test.go
generated
vendored
Normal file
625
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_test.go
generated
vendored
Normal file
@ -0,0 +1,625 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
)
|
||||
|
||||
func TestCmdStreamLargeStderr(t *testing.T) {
|
||||
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
|
||||
out, err := CmdStream(cmd, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
_, err := io.Copy(ioutil.Discard, out)
|
||||
errCh <- err
|
||||
}()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
t.Fatalf("Command should not have failed (err=%.100s...)", err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdStreamBad(t *testing.T) {
|
||||
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
|
||||
out, err := CmdStream(badCmd, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
if output, err := ioutil.ReadAll(out); err == nil {
|
||||
t.Fatalf("Command should have failed")
|
||||
} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
|
||||
t.Fatalf("Wrong error value (%s)", err)
|
||||
} else if s := string(output); s != "hello\n" {
|
||||
t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdStreamGood(t *testing.T) {
|
||||
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
|
||||
out, err := CmdStream(cmd, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if output, err := ioutil.ReadAll(out); err != nil {
|
||||
t.Fatalf("Command should not have failed (err=%s)", err)
|
||||
} else if s := string(output); s != "hello\n" {
|
||||
t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTarFiles(t *testing.T) {
|
||||
// try without hardlinks
|
||||
if err := checkNoChanges(1000, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// try with hardlinks
|
||||
if err := checkNoChanges(1000, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkNoChanges(fileNum int, hardlinks bool) error {
|
||||
srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(srcDir)
|
||||
|
||||
destDir, err := ioutil.TempDir("", "docker-test-destDir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(destDir)
|
||||
|
||||
_, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = TarUntar(srcDir, destDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changes, err := ChangesDirs(destDir, srcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(changes) > 0 {
|
||||
return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
|
||||
archive, err := TarWithOptions(origin, options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
buf := make([]byte, 10)
|
||||
if _, err := archive.Read(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wrap := io.MultiReader(bytes.NewReader(buf), archive)
|
||||
|
||||
detectedCompression := DetectCompression(buf)
|
||||
compression := options.Compression
|
||||
if detectedCompression.Extension() != compression.Extension() {
|
||||
return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "docker-test-untar")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
if err := Untar(wrap, tmp, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := os.Stat(tmp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ChangesDirs(origin, tmp)
|
||||
}
|
||||
|
||||
func TestTarUntar(t *testing.T) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, c := range []Compression{
|
||||
Uncompressed,
|
||||
Gzip,
|
||||
} {
|
||||
changes, err := tarUntar(t, origin, &TarOptions{
|
||||
Compression: c,
|
||||
ExcludePatterns: []string{"3"},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
|
||||
}
|
||||
|
||||
if len(changes) != 1 || changes[0].Path != "/3" {
|
||||
t.Fatalf("Unexpected differences after tarUntar: %v", changes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTarWithOptions(t *testing.T) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
opts *TarOptions
|
||||
numChanges int
|
||||
}{
|
||||
{&TarOptions{IncludeFiles: []string{"1"}}, 1},
|
||||
{&TarOptions{ExcludePatterns: []string{"2"}}, 1},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
changes, err := tarUntar(t, origin, testCase.opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
|
||||
}
|
||||
if len(changes) != testCase.numChanges {
|
||||
t.Errorf("Expected %d changes, got %d for %+v:",
|
||||
testCase.numChanges, len(changes), testCase.opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
|
||||
// use PAX Global Extended Headers.
|
||||
// Failing prevents the archives from being uncompressed during ADD
|
||||
func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
|
||||
hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
|
||||
tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
|
||||
// Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
|
||||
func TestUntarUstarGnuConflict(t *testing.T) {
|
||||
f, err := os.Open("testdata/broken.tar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
found := false
|
||||
tr := tar.NewReader(f)
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTarWithHardLink(t *testing.T) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Link(path.Join(origin, "1"), path.Join(origin, "2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var i1, i2 uint64
|
||||
if i1, err = getNlink(path.Join(origin, "1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// sanity check that we can hardlink
|
||||
if i1 != 2 {
|
||||
t.Skipf("skipping since hardlinks don't work here; expected 2 links, got %d", i1)
|
||||
}
|
||||
|
||||
dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dest)
|
||||
|
||||
// we'll do this in two steps to separate failure
|
||||
fh, err := Tar(origin, Uncompressed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// ensure we can read the whole thing with no error, before writing back out
|
||||
buf, err := ioutil.ReadAll(fh)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bRdr := bytes.NewReader(buf)
|
||||
err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if i1, err = getInode(path.Join(dest, "1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i2, err = getInode(path.Join(dest, "2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if i1 != i2 {
|
||||
t.Errorf("expected matching inodes, but got %d and %d", i1, i2)
|
||||
}
|
||||
}
|
||||
|
||||
func getNlink(path string) (uint64, error) {
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
statT, ok := stat.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
|
||||
}
|
||||
return statT.Nlink, nil
|
||||
}
|
||||
|
||||
func getInode(path string) (uint64, error) {
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
statT, ok := stat.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
|
||||
}
|
||||
return statT.Ino, nil
|
||||
}
|
||||
|
||||
func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
|
||||
fileData := []byte("fooo")
|
||||
for n := 0; n < numberOfFiles; n++ {
|
||||
fileName := fmt.Sprintf("file-%d", n)
|
||||
if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if makeLinks {
|
||||
if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
totalSize := numberOfFiles * len(fileData)
|
||||
return totalSize, nil
|
||||
}
|
||||
|
||||
func BenchmarkTarUntar(b *testing.B) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
target := path.Join(tempDir, "dest")
|
||||
n, err := prepareUntarSourceDirectory(100, origin, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(n))
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := TarUntar(origin, target)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
os.RemoveAll(target)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTarUntarWithLinks(b *testing.B) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
target := path.Join(tempDir, "dest")
|
||||
n, err := prepareUntarSourceDirectory(100, origin, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(n))
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := TarUntar(origin, target)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
os.RemoveAll(target)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntarInvalidFilenames(t *testing.T) {
|
||||
for i, headers := range [][]*tar.Header{
|
||||
{
|
||||
{
|
||||
Name: "../victim/dotdot",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
// Note the leading slash
|
||||
Name: "/../victim/slash-dotdot",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil {
|
||||
t.Fatalf("i=%d. %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntarInvalidHardlink(t *testing.T) {
|
||||
for i, headers := range [][]*tar.Header{
|
||||
{ // try reading victim/hello (../)
|
||||
{
|
||||
Name: "dotdot",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (/../)
|
||||
{
|
||||
Name: "slash-dotdot",
|
||||
Typeflag: tar.TypeLink,
|
||||
// Note the leading slash
|
||||
Linkname: "/../victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try writing victim/file
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "loophole-victim/file",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (hardlink, symlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "symlink",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "loophole-victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // Try reading victim/hello (hardlink, hardlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "hardlink",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "loophole-victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // Try removing victim directory (hardlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil {
|
||||
t.Fatalf("i=%d. %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntarInvalidSymlink(t *testing.T) {
|
||||
for i, headers := range [][]*tar.Header{
|
||||
{ // try reading victim/hello (../)
|
||||
{
|
||||
Name: "dotdot",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (/../)
|
||||
{
|
||||
Name: "slash-dotdot",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
// Note the leading slash
|
||||
Linkname: "/../victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try writing victim/file
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "loophole-victim/file",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (symlink, symlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "symlink",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "loophole-victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (symlink, hardlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "hardlink",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "loophole-victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try removing victim directory (symlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try writing to victim/newdir/newfile with a symlink in the path
|
||||
{
|
||||
// this header needs to be before the next one, or else there is an error
|
||||
Name: "dir/loophole",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "dir/loophole/newdir/newfile",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil {
|
||||
t.Fatalf("i=%d. %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTempArchiveCloseMultipleTimes(t *testing.T) {
|
||||
reader := ioutil.NopCloser(strings.NewReader("hello"))
|
||||
tempArchive, err := NewTempArchive(reader, "")
|
||||
buf := make([]byte, 10)
|
||||
n, err := tempArchive.Read(buf)
|
||||
if n != 5 {
|
||||
t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
if err = tempArchive.Close(); err != nil {
|
||||
t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
46
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_unix.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_unix.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
// +build !windows
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
)
|
||||
|
||||
// canonicalTarNameForPath returns platform-specific filepath
|
||||
// to canonical posix-style path for tar archival. p is relative
|
||||
// path.
|
||||
func CanonicalTarNameForPath(p string) (string, error) {
|
||||
return p, nil // already unix-style
|
||||
}
|
||||
|
||||
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
|
||||
s, ok := stat.(*syscall.Stat_t)
|
||||
|
||||
if !ok {
|
||||
err = errors.New("cannot convert stat value to syscall.Stat_t")
|
||||
return
|
||||
}
|
||||
|
||||
nlink = uint32(s.Nlink)
|
||||
inode = uint64(s.Ino)
|
||||
|
||||
// Currently go does not fil in the major/minors
|
||||
if s.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
|
||||
s.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
|
||||
hdr.Devmajor = int64(major(uint64(s.Rdev)))
|
||||
hdr.Devminor = int64(minor(uint64(s.Rdev)))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func major(device uint64) uint64 {
|
||||
return (device >> 8) & 0xfff
|
||||
}
|
||||
|
||||
func minor(device uint64) uint64 {
|
||||
return (device & 0xff) | ((device >> 12) & 0xfff00)
|
||||
}
|
42
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_unix_test.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_unix_test.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
// +build !windows
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCanonicalTarNameForPath(t *testing.T) {
|
||||
cases := []struct{ in, expected string }{
|
||||
{"foo", "foo"},
|
||||
{"foo/bar", "foo/bar"},
|
||||
{"foo/dir/", "foo/dir/"},
|
||||
}
|
||||
for _, v := range cases {
|
||||
if out, err := CanonicalTarNameForPath(v.in); err != nil {
|
||||
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
|
||||
} else if out != v.expected {
|
||||
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalTarName(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
isDir bool
|
||||
expected string
|
||||
}{
|
||||
{"foo", false, "foo"},
|
||||
{"foo", true, "foo/"},
|
||||
{"foo/bar", false, "foo/bar"},
|
||||
{"foo/bar", true, "foo/bar/"},
|
||||
}
|
||||
for _, v := range cases {
|
||||
if out, err := canonicalTarName(v.in, v.isDir); err != nil {
|
||||
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
|
||||
} else if out != v.expected {
|
||||
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
|
||||
}
|
||||
}
|
||||
}
|
29
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_windows.go
generated
vendored
Normal file
29
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_windows.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
// +build windows
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
)
|
||||
|
||||
// canonicalTarNameForPath returns platform-specific filepath
|
||||
// to canonical posix-style path for tar archival. p is relative
|
||||
// path.
|
||||
func CanonicalTarNameForPath(p string) (string, error) {
|
||||
// windows: convert windows style relative path with backslashes
|
||||
// into forward slashes. since windows does not allow '/' or '\'
|
||||
// in file names, it is mostly safe to replace however we must
|
||||
// check just in case
|
||||
if strings.Contains(p, "/") {
|
||||
return "", fmt.Errorf("windows path contains forward slash: %s", p)
|
||||
}
|
||||
return strings.Replace(p, "\\", "/", -1), nil
|
||||
}
|
||||
|
||||
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
|
||||
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
|
||||
return
|
||||
}
|
48
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_windows_test.go
generated
vendored
Normal file
48
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_windows_test.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
// +build windows
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCanonicalTarNameForPath(t *testing.T) {
|
||||
cases := []struct {
|
||||
in, expected string
|
||||
shouldFail bool
|
||||
}{
|
||||
{"foo", "foo", false},
|
||||
{"foo/bar", "___", true}, // unix-styled windows path must fail
|
||||
{`foo\bar`, "foo/bar", false},
|
||||
{`foo\bar`, "foo/bar/", false},
|
||||
}
|
||||
for _, v := range cases {
|
||||
if out, err := CanonicalTarNameForPath(v.in); err != nil && !v.shouldFail {
|
||||
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
|
||||
} else if v.shouldFail && err == nil {
|
||||
t.Fatalf("canonical path call should have pailed with error. in=%s out=%s", v.in, out)
|
||||
} else if !v.shouldFail && out != v.expected {
|
||||
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalTarName(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
isDir bool
|
||||
expected string
|
||||
}{
|
||||
{"foo", false, "foo"},
|
||||
{"foo", true, "foo/"},
|
||||
{`foo\bar`, false, "foo/bar"},
|
||||
{`foo\bar`, true, "foo/bar/"},
|
||||
}
|
||||
for _, v := range cases {
|
||||
if out, err := canonicalTarName(v.in, v.isDir); err != nil {
|
||||
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
|
||||
} else if out != v.expected {
|
||||
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
|
||||
}
|
||||
}
|
||||
}
|
423
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes.go
generated
vendored
Normal file
423
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes.go
generated
vendored
Normal file
@ -0,0 +1,423 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
ChangeModify = iota
|
||||
ChangeAdd
|
||||
ChangeDelete
|
||||
)
|
||||
|
||||
type Change struct {
|
||||
Path string
|
||||
Kind ChangeType
|
||||
}
|
||||
|
||||
func (change *Change) String() string {
|
||||
var kind string
|
||||
switch change.Kind {
|
||||
case ChangeModify:
|
||||
kind = "C"
|
||||
case ChangeAdd:
|
||||
kind = "A"
|
||||
case ChangeDelete:
|
||||
kind = "D"
|
||||
}
|
||||
return fmt.Sprintf("%s %s", kind, change.Path)
|
||||
}
|
||||
|
||||
// for sort.Sort
|
||||
type changesByPath []Change
|
||||
|
||||
func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path }
|
||||
func (c changesByPath) Len() int { return len(c) }
|
||||
func (c changesByPath) Swap(i, j int) { c[j], c[i] = c[i], c[j] }
|
||||
|
||||
// Gnu tar and the go tar writer don't have sub-second mtime
|
||||
// precision, which is problematic when we apply changes via tar
|
||||
// files, we handle this by comparing for exact times, *or* same
|
||||
// second count and either a or b having exactly 0 nanoseconds
|
||||
func sameFsTime(a, b time.Time) bool {
|
||||
return a == b ||
|
||||
(a.Unix() == b.Unix() &&
|
||||
(a.Nanosecond() == 0 || b.Nanosecond() == 0))
|
||||
}
|
||||
|
||||
func sameFsTimeSpec(a, b syscall.Timespec) bool {
|
||||
return a.Sec == b.Sec &&
|
||||
(a.Nsec == b.Nsec || a.Nsec == 0 || b.Nsec == 0)
|
||||
}
|
||||
|
||||
// Changes walks the path rw and determines changes for the files in the path,
|
||||
// with respect to the parent layers
|
||||
func Changes(layers []string, rw string) ([]Change, error) {
|
||||
var changes []Change
|
||||
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(rw, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = filepath.Join("/", path)
|
||||
|
||||
// Skip root
|
||||
if path == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip AUFS metadata
|
||||
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
||||
return err
|
||||
}
|
||||
|
||||
change := Change{
|
||||
Path: path,
|
||||
}
|
||||
|
||||
// Find out what kind of modification happened
|
||||
file := filepath.Base(path)
|
||||
// If there is a whiteout, then the file was removed
|
||||
if strings.HasPrefix(file, ".wh.") {
|
||||
originalFile := file[len(".wh."):]
|
||||
change.Path = filepath.Join(filepath.Dir(path), originalFile)
|
||||
change.Kind = ChangeDelete
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
change.Kind = ChangeAdd
|
||||
|
||||
// ...Unless it already existed in a top layer, in which case, it's a modification
|
||||
for _, layer := range layers {
|
||||
stat, err := os.Stat(filepath.Join(layer, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the top layer, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
if stat.IsDir() && f.IsDir() {
|
||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
|
||||
// Both directories are the same, don't record the change
|
||||
return nil
|
||||
}
|
||||
}
|
||||
change.Kind = ChangeModify
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record change
|
||||
changes = append(changes, change)
|
||||
return nil
|
||||
})
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
parent *FileInfo
|
||||
name string
|
||||
stat *system.Stat
|
||||
children map[string]*FileInfo
|
||||
capability []byte
|
||||
added bool
|
||||
}
|
||||
|
||||
func (root *FileInfo) LookUp(path string) *FileInfo {
|
||||
parent := root
|
||||
if path == "/" {
|
||||
return root
|
||||
}
|
||||
|
||||
pathElements := strings.Split(path, "/")
|
||||
for _, elem := range pathElements {
|
||||
if elem != "" {
|
||||
child := parent.children[elem]
|
||||
if child == nil {
|
||||
return nil
|
||||
}
|
||||
parent = child
|
||||
}
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
func (info *FileInfo) path() string {
|
||||
if info.parent == nil {
|
||||
return "/"
|
||||
}
|
||||
return filepath.Join(info.parent.path(), info.name)
|
||||
}
|
||||
|
||||
func (info *FileInfo) isDir() bool {
|
||||
return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR == syscall.S_IFDIR
|
||||
}
|
||||
|
||||
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
||||
|
||||
sizeAtEntry := len(*changes)
|
||||
|
||||
if oldInfo == nil {
|
||||
// add
|
||||
change := Change{
|
||||
Path: info.path(),
|
||||
Kind: ChangeAdd,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
info.added = true
|
||||
}
|
||||
|
||||
// We make a copy so we can modify it to detect additions
|
||||
// also, we only recurse on the old dir if the new info is a directory
|
||||
// otherwise any previous delete/change is considered recursive
|
||||
oldChildren := make(map[string]*FileInfo)
|
||||
if oldInfo != nil && info.isDir() {
|
||||
for k, v := range oldInfo.children {
|
||||
oldChildren[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for name, newChild := range info.children {
|
||||
oldChild, _ := oldChildren[name]
|
||||
if oldChild != nil {
|
||||
// change?
|
||||
oldStat := oldChild.stat
|
||||
newStat := newChild.stat
|
||||
// Note: We can't compare inode or ctime or blocksize here, because these change
|
||||
// when copying a file into a container. However, that is not generally a problem
|
||||
// because any content change will change mtime, and any status change should
|
||||
// be visible when actually comparing the stat fields. The only time this
|
||||
// breaks down is if some code intentionally hides a change by setting
|
||||
// back mtime
|
||||
if oldStat.Mode() != newStat.Mode() ||
|
||||
oldStat.Uid() != newStat.Uid() ||
|
||||
oldStat.Gid() != newStat.Gid() ||
|
||||
oldStat.Rdev() != newStat.Rdev() ||
|
||||
// Don't look at size for dirs, its not a good measure of change
|
||||
(oldStat.Size() != newStat.Size() && oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR) ||
|
||||
!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) ||
|
||||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
||||
change := Change{
|
||||
Path: newChild.path(),
|
||||
Kind: ChangeModify,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
newChild.added = true
|
||||
}
|
||||
|
||||
// Remove from copy so we can detect deletions
|
||||
delete(oldChildren, name)
|
||||
}
|
||||
|
||||
newChild.addChanges(oldChild, changes)
|
||||
}
|
||||
for _, oldChild := range oldChildren {
|
||||
// delete
|
||||
change := Change{
|
||||
Path: oldChild.path(),
|
||||
Kind: ChangeDelete,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
}
|
||||
|
||||
// If there were changes inside this directory, we need to add it, even if the directory
|
||||
// itself wasn't changed. This is needed to properly save and restore filesystem permissions.
|
||||
if len(*changes) > sizeAtEntry && info.isDir() && !info.added && info.path() != "/" {
|
||||
change := Change{
|
||||
Path: info.path(),
|
||||
Kind: ChangeModify,
|
||||
}
|
||||
// Let's insert the directory entry before the recently added entries located inside this dir
|
||||
*changes = append(*changes, change) // just to resize the slice, will be overwritten
|
||||
copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:])
|
||||
(*changes)[sizeAtEntry] = change
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
|
||||
var changes []Change
|
||||
|
||||
info.addChanges(oldInfo, &changes)
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
func newRootFileInfo() *FileInfo {
|
||||
root := &FileInfo{
|
||||
name: "/",
|
||||
children: make(map[string]*FileInfo),
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
||||
root := newRootFileInfo()
|
||||
|
||||
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
relPath, err := filepath.Rel(sourceDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relPath = filepath.Join("/", relPath)
|
||||
|
||||
if relPath == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
parent := root.LookUp(filepath.Dir(relPath))
|
||||
if parent == nil {
|
||||
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
|
||||
}
|
||||
|
||||
info := &FileInfo{
|
||||
name: filepath.Base(relPath),
|
||||
children: make(map[string]*FileInfo),
|
||||
parent: parent,
|
||||
}
|
||||
|
||||
s, err := system.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.stat = s
|
||||
|
||||
info.capability, _ = system.Lgetxattr(path, "security.capability")
|
||||
|
||||
parent.children[info.name] = info
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// ChangesDirs compares two directories and generates an array of Change objects describing the changes.
|
||||
// If oldDir is "", then all files in newDir will be Add-Changes.
|
||||
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
|
||||
var (
|
||||
oldRoot, newRoot *FileInfo
|
||||
err1, err2 error
|
||||
errs = make(chan error, 2)
|
||||
)
|
||||
go func() {
|
||||
if oldDir != "" {
|
||||
oldRoot, err1 = collectFileInfo(oldDir)
|
||||
}
|
||||
errs <- err1
|
||||
}()
|
||||
go func() {
|
||||
newRoot, err2 = collectFileInfo(newDir)
|
||||
errs <- err2
|
||||
}()
|
||||
|
||||
// block until both routines have returned
|
||||
for i := 0; i < 2; i++ {
|
||||
if err := <-errs; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newRoot.Changes(oldRoot), nil
|
||||
}
|
||||
|
||||
// ChangesSize calculates the size in bytes of the provided changes, based on newDir.
|
||||
func ChangesSize(newDir string, changes []Change) int64 {
|
||||
var size int64
|
||||
for _, change := range changes {
|
||||
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
|
||||
file := filepath.Join(newDir, change.Path)
|
||||
fileInfo, _ := os.Lstat(file)
|
||||
if fileInfo != nil && !fileInfo.IsDir() {
|
||||
size += fileInfo.Size()
|
||||
}
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// ExportChanges produces an Archive from the provided changes, relative to dir.
|
||||
func ExportChanges(dir string, changes []Change) (Archive, error) {
|
||||
reader, writer := io.Pipe()
|
||||
go func() {
|
||||
ta := &tarAppender{
|
||||
TarWriter: tar.NewWriter(writer),
|
||||
Buffer: pools.BufioWriter32KPool.Get(nil),
|
||||
SeenFiles: make(map[uint64]string),
|
||||
}
|
||||
// this buffer is needed for the duration of this piped stream
|
||||
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
||||
|
||||
sort.Sort(changesByPath(changes))
|
||||
|
||||
// In general we log errors here but ignore them because
|
||||
// during e.g. a diff operation the container can continue
|
||||
// mutating the filesystem and we can see transient errors
|
||||
// from this
|
||||
for _, change := range changes {
|
||||
if change.Kind == ChangeDelete {
|
||||
whiteOutDir := filepath.Dir(change.Path)
|
||||
whiteOutBase := filepath.Base(change.Path)
|
||||
whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase)
|
||||
timestamp := time.Now()
|
||||
hdr := &tar.Header{
|
||||
Name: whiteOut[1:],
|
||||
Size: 0,
|
||||
ModTime: timestamp,
|
||||
AccessTime: timestamp,
|
||||
ChangeTime: timestamp,
|
||||
}
|
||||
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
|
||||
log.Debugf("Can't write whiteout header: %s", err)
|
||||
}
|
||||
} else {
|
||||
path := filepath.Join(dir, change.Path)
|
||||
if err := ta.addTarFile(path, change.Path[1:]); err != nil {
|
||||
log.Debugf("Can't add file %s to tar: %s", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
if err := ta.TarWriter.Close(); err != nil {
|
||||
log.Debugf("Can't close layer: %s", err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
log.Debugf("failed close Changes writer: %s", err)
|
||||
}
|
||||
}()
|
||||
return reader, nil
|
||||
}
|
127
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_posix_test.go
generated
vendored
Normal file
127
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_posix_test.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHardLinkOrder(t *testing.T) {
|
||||
names := []string{"file1.txt", "file2.txt", "file3.txt"}
|
||||
msg := []byte("Hey y'all")
|
||||
|
||||
// Create dir
|
||||
src, err := ioutil.TempDir("", "docker-hardlink-test-src-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//defer os.RemoveAll(src)
|
||||
for _, name := range names {
|
||||
func() {
|
||||
fh, err := os.Create(path.Join(src, name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fh.Close()
|
||||
if _, err = fh.Write(msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
// Create dest, with changes that includes hardlinks
|
||||
dest, err := ioutil.TempDir("", "docker-hardlink-test-dest-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.RemoveAll(dest) // we just want the name, at first
|
||||
if err := copyDir(src, dest); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dest)
|
||||
for _, name := range names {
|
||||
for i := 0; i < 5; i++ {
|
||||
if err := os.Link(path.Join(dest, name), path.Join(dest, fmt.Sprintf("%s.link%d", name, i))); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get changes
|
||||
changes, err := ChangesDirs(dest, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// sort
|
||||
sort.Sort(changesByPath(changes))
|
||||
|
||||
// ExportChanges
|
||||
ar, err := ExportChanges(dest, changes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hdrs, err := walkHeaders(ar)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// reverse sort
|
||||
sort.Sort(sort.Reverse(changesByPath(changes)))
|
||||
// ExportChanges
|
||||
arRev, err := ExportChanges(dest, changes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hdrsRev, err := walkHeaders(arRev)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// line up the two sets
|
||||
sort.Sort(tarHeaders(hdrs))
|
||||
sort.Sort(tarHeaders(hdrsRev))
|
||||
|
||||
// compare Size and LinkName
|
||||
for i := range hdrs {
|
||||
if hdrs[i].Name != hdrsRev[i].Name {
|
||||
t.Errorf("headers - expected name %q; but got %q", hdrs[i].Name, hdrsRev[i].Name)
|
||||
}
|
||||
if hdrs[i].Size != hdrsRev[i].Size {
|
||||
t.Errorf("headers - %q expected size %d; but got %d", hdrs[i].Name, hdrs[i].Size, hdrsRev[i].Size)
|
||||
}
|
||||
if hdrs[i].Typeflag != hdrsRev[i].Typeflag {
|
||||
t.Errorf("headers - %q expected type %d; but got %d", hdrs[i].Name, hdrs[i].Typeflag, hdrsRev[i].Typeflag)
|
||||
}
|
||||
if hdrs[i].Linkname != hdrsRev[i].Linkname {
|
||||
t.Errorf("headers - %q expected linkname %q; but got %q", hdrs[i].Name, hdrs[i].Linkname, hdrsRev[i].Linkname)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type tarHeaders []tar.Header
|
||||
|
||||
func (th tarHeaders) Len() int { return len(th) }
|
||||
func (th tarHeaders) Swap(i, j int) { th[j], th[i] = th[i], th[j] }
|
||||
func (th tarHeaders) Less(i, j int) bool { return th[i].Name < th[j].Name }
|
||||
|
||||
func walkHeaders(r io.Reader) ([]tar.Header, error) {
|
||||
t := tar.NewReader(r)
|
||||
headers := []tar.Header{}
|
||||
for {
|
||||
hdr, err := t.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return headers, err
|
||||
}
|
||||
headers = append(headers, *hdr)
|
||||
}
|
||||
return headers, nil
|
||||
}
|
294
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_test.go
generated
vendored
Normal file
294
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_test.go
generated
vendored
Normal file
@ -0,0 +1,294 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func max(x, y int) int {
|
||||
if x >= y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func copyDir(src, dst string) error {
|
||||
cmd := exec.Command("cp", "-a", src, dst)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FileType uint32
|
||||
|
||||
const (
|
||||
Regular FileType = iota
|
||||
Dir
|
||||
Symlink
|
||||
)
|
||||
|
||||
type FileData struct {
|
||||
filetype FileType
|
||||
path string
|
||||
contents string
|
||||
permissions os.FileMode
|
||||
}
|
||||
|
||||
func createSampleDir(t *testing.T, root string) {
|
||||
files := []FileData{
|
||||
{Regular, "file1", "file1\n", 0600},
|
||||
{Regular, "file2", "file2\n", 0666},
|
||||
{Regular, "file3", "file3\n", 0404},
|
||||
{Regular, "file4", "file4\n", 0600},
|
||||
{Regular, "file5", "file5\n", 0600},
|
||||
{Regular, "file6", "file6\n", 0600},
|
||||
{Regular, "file7", "file7\n", 0600},
|
||||
{Dir, "dir1", "", 0740},
|
||||
{Regular, "dir1/file1-1", "file1-1\n", 01444},
|
||||
{Regular, "dir1/file1-2", "file1-2\n", 0666},
|
||||
{Dir, "dir2", "", 0700},
|
||||
{Regular, "dir2/file2-1", "file2-1\n", 0666},
|
||||
{Regular, "dir2/file2-2", "file2-2\n", 0666},
|
||||
{Dir, "dir3", "", 0700},
|
||||
{Regular, "dir3/file3-1", "file3-1\n", 0666},
|
||||
{Regular, "dir3/file3-2", "file3-2\n", 0666},
|
||||
{Dir, "dir4", "", 0700},
|
||||
{Regular, "dir4/file3-1", "file4-1\n", 0666},
|
||||
{Regular, "dir4/file3-2", "file4-2\n", 0666},
|
||||
{Symlink, "symlink1", "target1", 0666},
|
||||
{Symlink, "symlink2", "target2", 0666},
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, info := range files {
|
||||
p := path.Join(root, info.path)
|
||||
if info.filetype == Dir {
|
||||
if err := os.MkdirAll(p, info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Regular {
|
||||
if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Symlink {
|
||||
if err := os.Symlink(info.contents, p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if info.filetype != Symlink {
|
||||
// Set a consistent ctime, atime for all files and dirs
|
||||
if err := os.Chtimes(p, now, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an directory, copy it, make sure we report no changes between the two
|
||||
func TestChangesDirsEmpty(t *testing.T) {
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(changes) != 0 {
|
||||
t.Fatalf("Reported changes for identical dirs: %v", changes)
|
||||
}
|
||||
os.RemoveAll(src)
|
||||
os.RemoveAll(dst)
|
||||
}
|
||||
|
||||
func mutateSampleDir(t *testing.T, root string) {
|
||||
// Remove a regular file
|
||||
if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Remove a directory
|
||||
if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Remove a symlink
|
||||
if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Rewrite a file
|
||||
if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace a file
|
||||
if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Touch file
|
||||
if err := os.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace file with dir
|
||||
if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create new file
|
||||
if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create new dir
|
||||
if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a new symlink
|
||||
if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Change a symlink
|
||||
if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace dir with file
|
||||
if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Touch dir
|
||||
if err := os.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangesDirsMutated(t *testing.T) {
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(src)
|
||||
defer os.RemoveAll(dst)
|
||||
|
||||
mutateSampleDir(t, dst)
|
||||
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sort.Sort(changesByPath(changes))
|
||||
|
||||
expectedChanges := []Change{
|
||||
{"/dir1", ChangeDelete},
|
||||
{"/dir2", ChangeModify},
|
||||
{"/dir3", ChangeModify},
|
||||
{"/dirnew", ChangeAdd},
|
||||
{"/file1", ChangeDelete},
|
||||
{"/file2", ChangeModify},
|
||||
{"/file3", ChangeModify},
|
||||
{"/file4", ChangeModify},
|
||||
{"/file5", ChangeModify},
|
||||
{"/filenew", ChangeAdd},
|
||||
{"/symlink1", ChangeDelete},
|
||||
{"/symlink2", ChangeModify},
|
||||
{"/symlinknew", ChangeAdd},
|
||||
}
|
||||
|
||||
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
|
||||
if i >= len(expectedChanges) {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
}
|
||||
if i >= len(changes) {
|
||||
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
|
||||
}
|
||||
if changes[i].Path == expectedChanges[i].Path {
|
||||
if changes[i] != expectedChanges[i] {
|
||||
t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
|
||||
}
|
||||
} else if changes[i].Path < expectedChanges[i].Path {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
} else {
|
||||
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyLayer(t *testing.T) {
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
defer os.RemoveAll(src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mutateSampleDir(t, dst)
|
||||
defer os.RemoveAll(dst)
|
||||
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer, err := ExportChanges(dst, changes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layerCopy, err := NewTempArchive(layer, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ApplyLayer(src, layerCopy); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes2, err := ChangesDirs(src, dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(changes2) != 0 {
|
||||
t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2)
|
||||
}
|
||||
}
|
169
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/diff.go
generated
vendored
Normal file
169
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/diff.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
|
||||
tr := tar.NewReader(layer)
|
||||
trBuf := pools.BufioReader32KPool.Get(tr)
|
||||
defer pools.BufioReader32KPool.Put(trBuf)
|
||||
|
||||
var dirs []*tar.Header
|
||||
|
||||
aufsTempdir := ""
|
||||
aufsHardlinks := make(map[string]*tar.Header)
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
size += hdr.Size
|
||||
|
||||
// Normalize name, for safety and for a simple is-root check
|
||||
hdr.Name = filepath.Clean(hdr.Name)
|
||||
|
||||
if !strings.HasSuffix(hdr.Name, "/") {
|
||||
// Not the root directory, ensure that the parent directory exists.
|
||||
// This happened in some tests where an image had a tarfile without any
|
||||
// parent directories.
|
||||
parent := filepath.Dir(hdr.Name)
|
||||
parentPath := filepath.Join(dest, parent)
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
err = os.MkdirAll(parentPath, 0600)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip AUFS metadata dirs
|
||||
if strings.HasPrefix(hdr.Name, ".wh..wh.") {
|
||||
// Regular files inside /.wh..wh.plnk can be used as hardlink targets
|
||||
// We don't want this directory, but we need the files in them so that
|
||||
// such hardlinks can be resolved.
|
||||
if strings.HasPrefix(hdr.Name, ".wh..wh.plnk") && hdr.Typeflag == tar.TypeReg {
|
||||
basename := filepath.Base(hdr.Name)
|
||||
aufsHardlinks[basename] = hdr
|
||||
if aufsTempdir == "" {
|
||||
if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer os.RemoveAll(aufsTempdir)
|
||||
}
|
||||
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
rel, err := filepath.Rel(dest, path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if strings.HasPrefix(rel, "../") {
|
||||
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
||||
}
|
||||
base := filepath.Base(path)
|
||||
|
||||
if strings.HasPrefix(base, ".wh.") {
|
||||
originalBase := base[len(".wh."):]
|
||||
originalPath := filepath.Join(filepath.Dir(path), originalBase)
|
||||
if err := os.RemoveAll(originalPath); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
// If path exits we almost always just want to remove and replace it.
|
||||
// The only exception is when it is a directory *and* the file from
|
||||
// the layer is also a directory. Then we want to merge them (i.e.
|
||||
// just apply the metadata from the layer).
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trBuf.Reset(tr)
|
||||
srcData := io.Reader(trBuf)
|
||||
srcHdr := hdr
|
||||
|
||||
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
|
||||
// we manually retarget these into the temporary files we extracted them into
|
||||
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") {
|
||||
linkBasename := filepath.Base(hdr.Linkname)
|
||||
srcHdr = aufsHardlinks[linkBasename]
|
||||
if srcHdr == nil {
|
||||
return 0, fmt.Errorf("Invalid aufs hardlink")
|
||||
}
|
||||
tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
srcData = tmpFile
|
||||
}
|
||||
|
||||
if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Directory mtimes must be handled at the end to avoid further
|
||||
// file creation in them to modify the directory mtime
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
dirs = append(dirs, hdr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, hdr := range dirs {
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
|
||||
if err := syscall.UtimesNano(path, ts); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// ApplyLayer parses a diff in the standard layer format from `layer`, and
|
||||
// applies it to the directory `dest`. Returns the size in bytes of the
|
||||
// contents of the layer.
|
||||
func ApplyLayer(dest string, layer ArchiveReader) (int64, error) {
|
||||
dest = filepath.Clean(dest)
|
||||
|
||||
// We need to be able to set any perms
|
||||
oldmask, err := system.Umask(0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
|
||||
|
||||
layer, err = DecompressStream(layer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return UnpackLayer(dest, layer)
|
||||
}
|
191
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/diff_test.go
generated
vendored
Normal file
191
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/diff_test.go
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
)
|
||||
|
||||
func TestApplyLayerInvalidFilenames(t *testing.T) {
|
||||
for i, headers := range [][]*tar.Header{
|
||||
{
|
||||
{
|
||||
Name: "../victim/dotdot",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
// Note the leading slash
|
||||
Name: "/../victim/slash-dotdot",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidFilenames", headers); err != nil {
|
||||
t.Fatalf("i=%d. %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyLayerInvalidHardlink(t *testing.T) {
|
||||
for i, headers := range [][]*tar.Header{
|
||||
{ // try reading victim/hello (../)
|
||||
{
|
||||
Name: "dotdot",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (/../)
|
||||
{
|
||||
Name: "slash-dotdot",
|
||||
Typeflag: tar.TypeLink,
|
||||
// Note the leading slash
|
||||
Linkname: "/../victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try writing victim/file
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "loophole-victim/file",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (hardlink, symlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "symlink",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "loophole-victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // Try reading victim/hello (hardlink, hardlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "hardlink",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "loophole-victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // Try removing victim directory (hardlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidHardlink", headers); err != nil {
|
||||
t.Fatalf("i=%d. %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyLayerInvalidSymlink(t *testing.T) {
|
||||
for i, headers := range [][]*tar.Header{
|
||||
{ // try reading victim/hello (../)
|
||||
{
|
||||
Name: "dotdot",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (/../)
|
||||
{
|
||||
Name: "slash-dotdot",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
// Note the leading slash
|
||||
Linkname: "/../victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try writing victim/file
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "loophole-victim/file",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (symlink, symlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "symlink",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "loophole-victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try reading victim/hello (symlink, hardlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "hardlink",
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: "loophole-victim/hello",
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
{ // try removing victim directory (symlink)
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: "../victim",
|
||||
Mode: 0755,
|
||||
},
|
||||
{
|
||||
Name: "loophole-victim",
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0644,
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidSymlink", headers); err != nil {
|
||||
t.Fatalf("i=%d. %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
97
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/example_changes.go
generated
vendored
Normal file
97
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/example_changes.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
// +build ignore
|
||||
|
||||
// Simple tool to create an archive stream from an old and new directory
|
||||
//
|
||||
// By default it will stream the comparison of two temporary directories with junk files
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
var (
|
||||
flDebug = flag.Bool("D", false, "debugging output")
|
||||
flNewDir = flag.String("newdir", "", "")
|
||||
flOldDir = flag.String("olddir", "", "")
|
||||
log = logrus.New()
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Produce a tar from comparing two directory paths. By default a demo tar is created of around 200 files (including hardlinks)")
|
||||
fmt.Printf("%s [OPTIONS]\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
log.Out = os.Stderr
|
||||
if (len(os.Getenv("DEBUG")) > 0) || *flDebug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
var newDir, oldDir string
|
||||
|
||||
if len(*flNewDir) == 0 {
|
||||
var err error
|
||||
newDir, err = ioutil.TempDir("", "docker-test-newDir")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(newDir)
|
||||
if _, err := prepareUntarSourceDirectory(100, newDir, true); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
newDir = *flNewDir
|
||||
}
|
||||
|
||||
if len(*flOldDir) == 0 {
|
||||
oldDir, err := ioutil.TempDir("", "docker-test-oldDir")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(oldDir)
|
||||
} else {
|
||||
oldDir = *flOldDir
|
||||
}
|
||||
|
||||
changes, err := archive.ChangesDirs(newDir, oldDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
a, err := archive.ExportChanges(newDir, changes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer a.Close()
|
||||
|
||||
i, err := io.Copy(os.Stdout, a)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "wrote archive of %d bytes", i)
|
||||
}
|
||||
|
||||
func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
|
||||
fileData := []byte("fooo")
|
||||
for n := 0; n < numberOfFiles; n++ {
|
||||
fileName := fmt.Sprintf("file-%d", n)
|
||||
if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if makeLinks {
|
||||
if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
totalSize := numberOfFiles * len(fileData)
|
||||
return totalSize, nil
|
||||
}
|
BIN
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/testdata/broken.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/testdata/broken.tar
generated
vendored
Normal file
Binary file not shown.
16
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/time_linux.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/time_linux.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
||||
if time.IsZero() {
|
||||
// Return UTIME_OMIT special value
|
||||
ts.Sec = 0
|
||||
ts.Nsec = ((1 << 30) - 2)
|
||||
return
|
||||
}
|
||||
return syscall.NsecToTimespec(time.UnixNano())
|
||||
}
|
16
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/time_unsupported.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/time_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// +build !linux
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
||||
nsec := int64(0)
|
||||
if !time.IsZero() {
|
||||
nsec = time.UnixNano()
|
||||
}
|
||||
return syscall.NsecToTimespec(nsec)
|
||||
}
|
167
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/utils_test.go
generated
vendored
Normal file
167
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/utils_test.go
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
)
|
||||
|
||||
var testUntarFns = map[string]func(string, io.Reader) error{
|
||||
"untar": func(dest string, r io.Reader) error {
|
||||
return Untar(r, dest, nil)
|
||||
},
|
||||
"applylayer": func(dest string, r io.Reader) error {
|
||||
_, err := ApplyLayer(dest, ArchiveReader(r))
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
// testBreakout is a helper function that, within the provided `tmpdir` directory,
|
||||
// creates a `victim` folder with a generated `hello` file in it.
|
||||
// `untar` extracts to a directory named `dest`, the tar file created from `headers`.
|
||||
//
|
||||
// Here are the tested scenarios:
|
||||
// - removed `victim` folder (write)
|
||||
// - removed files from `victim` folder (write)
|
||||
// - new files in `victim` folder (write)
|
||||
// - modified files in `victim` folder (write)
|
||||
// - file in `dest` with same content as `victim/hello` (read)
|
||||
//
|
||||
// When using testBreakout make sure you cover one of the scenarios listed above.
|
||||
func testBreakout(untarFn string, tmpdir string, headers []*tar.Header) error {
|
||||
tmpdir, err := ioutil.TempDir("", tmpdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
dest := filepath.Join(tmpdir, "dest")
|
||||
if err := os.Mkdir(dest, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
victim := filepath.Join(tmpdir, "victim")
|
||||
if err := os.Mkdir(victim, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
hello := filepath.Join(victim, "hello")
|
||||
helloData, err := time.Now().MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(hello, helloData, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
helloStat, err := os.Stat(hello)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reader, writer := io.Pipe()
|
||||
go func() {
|
||||
t := tar.NewWriter(writer)
|
||||
for _, hdr := range headers {
|
||||
t.WriteHeader(hdr)
|
||||
}
|
||||
t.Close()
|
||||
}()
|
||||
|
||||
untar := testUntarFns[untarFn]
|
||||
if untar == nil {
|
||||
return fmt.Errorf("could not find untar function %q in testUntarFns", untarFn)
|
||||
}
|
||||
if err := untar(dest, reader); err != nil {
|
||||
if _, ok := err.(breakoutError); !ok {
|
||||
// If untar returns an error unrelated to an archive breakout,
|
||||
// then consider this an unexpected error and abort.
|
||||
return err
|
||||
}
|
||||
// Here, untar detected the breakout.
|
||||
// Let's move on verifying that indeed there was no breakout.
|
||||
fmt.Printf("breakoutError: %v\n", err)
|
||||
}
|
||||
|
||||
// Check victim folder
|
||||
f, err := os.Open(victim)
|
||||
if err != nil {
|
||||
// codepath taken if victim folder was removed
|
||||
return fmt.Errorf("archive breakout: error reading %q: %v", victim, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Check contents of victim folder
|
||||
//
|
||||
// We are only interested in getting 2 files from the victim folder, because if all is well
|
||||
// we expect only one result, the `hello` file. If there is a second result, it cannot
|
||||
// hold the same name `hello` and we assume that a new file got created in the victim folder.
|
||||
// That is enough to detect an archive breakout.
|
||||
names, err := f.Readdirnames(2)
|
||||
if err != nil {
|
||||
// codepath taken if victim is not a folder
|
||||
return fmt.Errorf("archive breakout: error reading directory content of %q: %v", victim, err)
|
||||
}
|
||||
for _, name := range names {
|
||||
if name != "hello" {
|
||||
// codepath taken if new file was created in victim folder
|
||||
return fmt.Errorf("archive breakout: new file %q", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Check victim/hello
|
||||
f, err = os.Open(hello)
|
||||
if err != nil {
|
||||
// codepath taken if read permissions were removed
|
||||
return fmt.Errorf("archive breakout: could not lstat %q: %v", hello, err)
|
||||
}
|
||||
defer f.Close()
|
||||
b, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if helloStat.IsDir() != fi.IsDir() ||
|
||||
// TODO: cannot check for fi.ModTime() change
|
||||
helloStat.Mode() != fi.Mode() ||
|
||||
helloStat.Size() != fi.Size() ||
|
||||
!bytes.Equal(helloData, b) {
|
||||
// codepath taken if hello has been modified
|
||||
return fmt.Errorf("archive breakout: file %q has been modified. Contents: expected=%q, got=%q. FileInfo: expected=%#v, got=%#v.", hello, helloData, b, helloStat, fi)
|
||||
}
|
||||
|
||||
// Check that nothing in dest/ has the same content as victim/hello.
|
||||
// Since victim/hello was generated with time.Now(), it is safe to assume
|
||||
// that any file whose content matches exactly victim/hello, managed somehow
|
||||
// to access victim/hello.
|
||||
return filepath.Walk(dest, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
if err != nil {
|
||||
// skip directory if error
|
||||
return filepath.SkipDir
|
||||
}
|
||||
// enter directory
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
// skip file if error
|
||||
return nil
|
||||
}
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
// Houston, we have a problem. Aborting (space)walk.
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(helloData, b) {
|
||||
return fmt.Errorf("archive breakout: file %q has been accessed via %q", hello, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
59
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/wrap.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/wrap.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Generate generates a new archive from the content provided
|
||||
// as input.
|
||||
//
|
||||
// `files` is a sequence of path/content pairs. A new file is
|
||||
// added to the archive for each pair.
|
||||
// If the last pair is incomplete, the file is created with an
|
||||
// empty content. For example:
|
||||
//
|
||||
// Generate("foo.txt", "hello world", "emptyfile")
|
||||
//
|
||||
// The above call will return an archive with 2 files:
|
||||
// * ./foo.txt with content "hello world"
|
||||
// * ./empty with empty content
|
||||
//
|
||||
// FIXME: stream content instead of buffering
|
||||
// FIXME: specify permissions and other archive metadata
|
||||
func Generate(input ...string) (Archive, error) {
|
||||
files := parseStringPairs(input...)
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, file := range files {
|
||||
name, content := file[0], file[1]
|
||||
hdr := &tar.Header{
|
||||
Name: name,
|
||||
Size: int64(len(content)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tw.Write([]byte(content)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.NopCloser(buf), nil
|
||||
}
|
||||
|
||||
func parseStringPairs(input ...string) (output [][2]string) {
|
||||
output = make([][2]string, 0, len(input)/2+1)
|
||||
for i := 0; i < len(input); i += 2 {
|
||||
var pair [2]string
|
||||
pair[0] = input[i]
|
||||
if i+1 < len(input) {
|
||||
pair[1] = input[i+1]
|
||||
}
|
||||
output = append(output, pair)
|
||||
}
|
||||
return
|
||||
}
|
26
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Matches returns true if relFilePath matches any of the patterns
|
||||
func Matches(relFilePath string, patterns []string) (bool, error) {
|
||||
for _, exclude := range patterns {
|
||||
matched, err := filepath.Match(exclude, relFilePath)
|
||||
if err != nil {
|
||||
log.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude)
|
||||
return false, err
|
||||
}
|
||||
if matched {
|
||||
if filepath.Clean(relFilePath) == "." {
|
||||
log.Errorf("Can't exclude whole path, excluding pattern: %s", exclude)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Skipping excluded path: %s", relFilePath)
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
114
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers.go
generated
vendored
Normal file
114
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers.go
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type readCloserWrapper struct {
|
||||
io.Reader
|
||||
closer func() error
|
||||
}
|
||||
|
||||
func (r *readCloserWrapper) Close() error {
|
||||
return r.closer()
|
||||
}
|
||||
|
||||
func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser {
|
||||
return &readCloserWrapper{
|
||||
Reader: r,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
||||
|
||||
type readerErrWrapper struct {
|
||||
reader io.Reader
|
||||
closer func()
|
||||
}
|
||||
|
||||
func (r *readerErrWrapper) Read(p []byte) (int, error) {
|
||||
n, err := r.reader.Read(p)
|
||||
if err != nil {
|
||||
r.closer()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
|
||||
return &readerErrWrapper{
|
||||
reader: r,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
||||
|
||||
type bufReader struct {
|
||||
sync.Mutex
|
||||
buf *bytes.Buffer
|
||||
reader io.Reader
|
||||
err error
|
||||
wait sync.Cond
|
||||
drainBuf []byte
|
||||
}
|
||||
|
||||
func NewBufReader(r io.Reader) *bufReader {
|
||||
reader := &bufReader{
|
||||
buf: &bytes.Buffer{},
|
||||
drainBuf: make([]byte, 1024),
|
||||
reader: r,
|
||||
}
|
||||
reader.wait.L = &reader.Mutex
|
||||
go reader.drain()
|
||||
return reader
|
||||
}
|
||||
|
||||
func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer *bytes.Buffer) *bufReader {
|
||||
reader := &bufReader{
|
||||
buf: buffer,
|
||||
drainBuf: drainBuffer,
|
||||
reader: r,
|
||||
}
|
||||
reader.wait.L = &reader.Mutex
|
||||
go reader.drain()
|
||||
return reader
|
||||
}
|
||||
|
||||
func (r *bufReader) drain() {
|
||||
for {
|
||||
n, err := r.reader.Read(r.drainBuf)
|
||||
r.Lock()
|
||||
if err != nil {
|
||||
r.err = err
|
||||
} else {
|
||||
r.buf.Write(r.drainBuf[0:n])
|
||||
}
|
||||
r.wait.Signal()
|
||||
r.Unlock()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *bufReader) Read(p []byte) (n int, err error) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
for {
|
||||
n, err = r.buf.Read(p)
|
||||
if n > 0 {
|
||||
return n, err
|
||||
}
|
||||
if r.err != nil {
|
||||
return 0, r.err
|
||||
}
|
||||
r.wait.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *bufReader) Close() error {
|
||||
closer, ok := r.reader.(io.ReadCloser)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return closer.Close()
|
||||
}
|
34
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go
generated
vendored
Normal file
34
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBufReader(t *testing.T) {
|
||||
reader, writer := io.Pipe()
|
||||
bufreader := NewBufReader(reader)
|
||||
|
||||
// Write everything down to a Pipe
|
||||
// Usually, a pipe should block but because of the buffered reader,
|
||||
// the writes will go through
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
writer.Write([]byte("hello world"))
|
||||
writer.Close()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
// Drain the reader *after* everything has been written, just to verify
|
||||
// it is indeed buffering
|
||||
<-done
|
||||
output, err := ioutil.ReadAll(bufreader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(output, []byte("hello world")) {
|
||||
t.Error(string(output))
|
||||
}
|
||||
}
|
39
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package ioutils
|
||||
|
||||
import "io"
|
||||
|
||||
type NopWriter struct{}
|
||||
|
||||
func (*NopWriter) Write(buf []byte) (int, error) {
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
type nopWriteCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w *nopWriteCloser) Close() error { return nil }
|
||||
|
||||
func NopWriteCloser(w io.Writer) io.WriteCloser {
|
||||
return &nopWriteCloser{w}
|
||||
}
|
||||
|
||||
type NopFlusher struct{}
|
||||
|
||||
func (f *NopFlusher) Flush() {}
|
||||
|
||||
type writeCloserWrapper struct {
|
||||
io.Writer
|
||||
closer func() error
|
||||
}
|
||||
|
||||
func (r *writeCloserWrapper) Close() error {
|
||||
return r.closer()
|
||||
}
|
||||
|
||||
func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser {
|
||||
return &writeCloserWrapper{
|
||||
Writer: r,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
111
Godeps/_workspace/src/github.com/docker/docker/pkg/pools/pools.go
generated
vendored
Normal file
111
Godeps/_workspace/src/github.com/docker/docker/pkg/pools/pools.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
// +build go1.3
|
||||
|
||||
// Package pools provides a collection of pools which provide various
|
||||
// data types with buffers. These can be used to lower the number of
|
||||
// memory allocations and reuse buffers.
|
||||
//
|
||||
// New pools should be added to this package to allow them to be
|
||||
// shared across packages.
|
||||
//
|
||||
// Utility functions which operate on pools should be added to this
|
||||
// package to allow them to be reused.
|
||||
package pools
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
)
|
||||
|
||||
var (
|
||||
// Pool which returns bufio.Reader with a 32K buffer
|
||||
BufioReader32KPool *BufioReaderPool
|
||||
// Pool which returns bufio.Writer with a 32K buffer
|
||||
BufioWriter32KPool *BufioWriterPool
|
||||
)
|
||||
|
||||
const buffer32K = 32 * 1024
|
||||
|
||||
type BufioReaderPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func init() {
|
||||
BufioReader32KPool = newBufioReaderPoolWithSize(buffer32K)
|
||||
BufioWriter32KPool = newBufioWriterPoolWithSize(buffer32K)
|
||||
}
|
||||
|
||||
// newBufioReaderPoolWithSize is unexported because new pools should be
|
||||
// added here to be shared where required.
|
||||
func newBufioReaderPoolWithSize(size int) *BufioReaderPool {
|
||||
pool := sync.Pool{
|
||||
New: func() interface{} { return bufio.NewReaderSize(nil, size) },
|
||||
}
|
||||
return &BufioReaderPool{pool: pool}
|
||||
}
|
||||
|
||||
// Get returns a bufio.Reader which reads from r. The buffer size is that of the pool.
|
||||
func (bufPool *BufioReaderPool) Get(r io.Reader) *bufio.Reader {
|
||||
buf := bufPool.pool.Get().(*bufio.Reader)
|
||||
buf.Reset(r)
|
||||
return buf
|
||||
}
|
||||
|
||||
// Put puts the bufio.Reader back into the pool.
|
||||
func (bufPool *BufioReaderPool) Put(b *bufio.Reader) {
|
||||
b.Reset(nil)
|
||||
bufPool.pool.Put(b)
|
||||
}
|
||||
|
||||
// NewReadCloserWrapper returns a wrapper which puts the bufio.Reader back
|
||||
// into the pool and closes the reader if it's an io.ReadCloser.
|
||||
func (bufPool *BufioReaderPool) NewReadCloserWrapper(buf *bufio.Reader, r io.Reader) io.ReadCloser {
|
||||
return ioutils.NewReadCloserWrapper(r, func() error {
|
||||
if readCloser, ok := r.(io.ReadCloser); ok {
|
||||
readCloser.Close()
|
||||
}
|
||||
bufPool.Put(buf)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type BufioWriterPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// newBufioWriterPoolWithSize is unexported because new pools should be
|
||||
// added here to be shared where required.
|
||||
func newBufioWriterPoolWithSize(size int) *BufioWriterPool {
|
||||
pool := sync.Pool{
|
||||
New: func() interface{} { return bufio.NewWriterSize(nil, size) },
|
||||
}
|
||||
return &BufioWriterPool{pool: pool}
|
||||
}
|
||||
|
||||
// Get returns a bufio.Writer which writes to w. The buffer size is that of the pool.
|
||||
func (bufPool *BufioWriterPool) Get(w io.Writer) *bufio.Writer {
|
||||
buf := bufPool.pool.Get().(*bufio.Writer)
|
||||
buf.Reset(w)
|
||||
return buf
|
||||
}
|
||||
|
||||
// Put puts the bufio.Writer back into the pool.
|
||||
func (bufPool *BufioWriterPool) Put(b *bufio.Writer) {
|
||||
b.Reset(nil)
|
||||
bufPool.pool.Put(b)
|
||||
}
|
||||
|
||||
// NewWriteCloserWrapper returns a wrapper which puts the bufio.Writer back
|
||||
// into the pool and closes the writer if it's an io.Writecloser.
|
||||
func (bufPool *BufioWriterPool) NewWriteCloserWrapper(buf *bufio.Writer, w io.Writer) io.WriteCloser {
|
||||
return ioutils.NewWriteCloserWrapper(w, func() error {
|
||||
buf.Flush()
|
||||
if writeCloser, ok := w.(io.WriteCloser); ok {
|
||||
writeCloser.Close()
|
||||
}
|
||||
bufPool.Put(buf)
|
||||
return nil
|
||||
})
|
||||
}
|
73
Godeps/_workspace/src/github.com/docker/docker/pkg/pools/pools_nopool.go
generated
vendored
Normal file
73
Godeps/_workspace/src/github.com/docker/docker/pkg/pools/pools_nopool.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
// +build !go1.3
|
||||
|
||||
package pools
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
)
|
||||
|
||||
var (
|
||||
BufioReader32KPool *BufioReaderPool
|
||||
BufioWriter32KPool *BufioWriterPool
|
||||
)
|
||||
|
||||
const buffer32K = 32 * 1024
|
||||
|
||||
type BufioReaderPool struct {
|
||||
size int
|
||||
}
|
||||
|
||||
func init() {
|
||||
BufioReader32KPool = newBufioReaderPoolWithSize(buffer32K)
|
||||
BufioWriter32KPool = newBufioWriterPoolWithSize(buffer32K)
|
||||
}
|
||||
|
||||
func newBufioReaderPoolWithSize(size int) *BufioReaderPool {
|
||||
return &BufioReaderPool{size: size}
|
||||
}
|
||||
|
||||
func (bufPool *BufioReaderPool) Get(r io.Reader) *bufio.Reader {
|
||||
return bufio.NewReaderSize(r, bufPool.size)
|
||||
}
|
||||
|
||||
func (bufPool *BufioReaderPool) Put(b *bufio.Reader) {
|
||||
b.Reset(nil)
|
||||
}
|
||||
|
||||
func (bufPool *BufioReaderPool) NewReadCloserWrapper(buf *bufio.Reader, r io.Reader) io.ReadCloser {
|
||||
return ioutils.NewReadCloserWrapper(r, func() error {
|
||||
if readCloser, ok := r.(io.ReadCloser); ok {
|
||||
return readCloser.Close()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type BufioWriterPool struct {
|
||||
size int
|
||||
}
|
||||
|
||||
func newBufioWriterPoolWithSize(size int) *BufioWriterPool {
|
||||
return &BufioWriterPool{size: size}
|
||||
}
|
||||
|
||||
func (bufPool *BufioWriterPool) Get(w io.Writer) *bufio.Writer {
|
||||
return bufio.NewWriterSize(w, bufPool.size)
|
||||
}
|
||||
|
||||
func (bufPool *BufioWriterPool) Put(b *bufio.Writer) {
|
||||
b.Reset(nil)
|
||||
}
|
||||
|
||||
func (bufPool *BufioWriterPool) NewWriteCloserWrapper(buf *bufio.Writer, w io.Writer) io.WriteCloser {
|
||||
return ioutils.NewWriteCloserWrapper(w, func() error {
|
||||
buf.Flush()
|
||||
if writeCloser, ok := w.(io.WriteCloser); ok {
|
||||
return writeCloser.Close()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
11
Godeps/_workspace/src/github.com/docker/docker/pkg/promise/promise.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/docker/docker/pkg/promise/promise.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package promise
|
||||
|
||||
// Go is a basic promise implementation: it wraps calls a function in a goroutine,
|
||||
// and returns a channel which will later return the function's return value.
|
||||
func Go(f func() error) chan error {
|
||||
ch := make(chan error, 1)
|
||||
go func() {
|
||||
ch <- f()
|
||||
}()
|
||||
return ch
|
||||
}
|
2
Godeps/_workspace/src/github.com/docker/docker/pkg/system/MAINTAINERS
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/docker/docker/pkg/system/MAINTAINERS
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
Victor Vieux <vieux@docker.com> (@vieux)
|
9
Godeps/_workspace/src/github.com/docker/docker/pkg/system/errors.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/docker/docker/pkg/system/errors.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
|
||||
)
|
16
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// +build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Lstat(path string) (*Stat, error) {
|
||||
s := &syscall.Stat_t{}
|
||||
err := syscall.Lstat(path, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromStatT(s)
|
||||
}
|
27
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_test.go
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_test.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLstat(t *testing.T) {
|
||||
file, invalid, _, dir := prepareFiles(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
statFile, err := Lstat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if statFile == nil {
|
||||
t.Fatal("returned empty stat for existing file")
|
||||
}
|
||||
|
||||
statInvalid, err := Lstat(invalid)
|
||||
if err == nil {
|
||||
t.Fatal("did not return error for non-existing file")
|
||||
}
|
||||
if statInvalid != nil {
|
||||
t.Fatal("returned non-nil stat for non-existing file")
|
||||
}
|
||||
}
|
8
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_windows.go
generated
vendored
Normal file
8
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_windows.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
func Lstat(path string) (*Stat, error) {
|
||||
// should not be called on cli code path
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
17
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo.go
generated
vendored
Normal file
17
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package system
|
||||
|
||||
// MemInfo contains memory statistics of the host system.
|
||||
type MemInfo struct {
|
||||
// Total usable RAM (i.e. physical RAM minus a few reserved bits and the
|
||||
// kernel binary code).
|
||||
MemTotal int64
|
||||
|
||||
// Amount of free memory.
|
||||
MemFree int64
|
||||
|
||||
// Total amount of swap space available.
|
||||
SwapTotal int64
|
||||
|
||||
// Amount of swap space that is currently unused.
|
||||
SwapFree int64
|
||||
}
|
67
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/units"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMalformed = errors.New("malformed file")
|
||||
)
|
||||
|
||||
// Retrieve memory statistics of the host system and parse them into a MemInfo
|
||||
// type.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
file, err := os.Open("/proc/meminfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return parseMemInfo(file)
|
||||
}
|
||||
|
||||
func parseMemInfo(reader io.Reader) (*MemInfo, error) {
|
||||
meminfo := &MemInfo{}
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
// Expected format: ["MemTotal:", "1234", "kB"]
|
||||
parts := strings.Fields(scanner.Text())
|
||||
|
||||
// Sanity checks: Skip malformed entries.
|
||||
if len(parts) < 3 || parts[2] != "kB" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert to bytes.
|
||||
size, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
bytes := int64(size) * units.KiB
|
||||
|
||||
switch parts[0] {
|
||||
case "MemTotal:":
|
||||
meminfo.MemTotal = bytes
|
||||
case "MemFree:":
|
||||
meminfo.MemFree = bytes
|
||||
case "SwapTotal:":
|
||||
meminfo.SwapTotal = bytes
|
||||
case "SwapFree:":
|
||||
meminfo.SwapFree = bytes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle errors that may have occurred during the reading of the file.
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return meminfo, nil
|
||||
}
|
37
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux_test.go
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux_test.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/units"
|
||||
)
|
||||
|
||||
func TestMemInfo(t *testing.T) {
|
||||
const input = `
|
||||
MemTotal: 1 kB
|
||||
MemFree: 2 kB
|
||||
SwapTotal: 3 kB
|
||||
SwapFree: 4 kB
|
||||
Malformed1:
|
||||
Malformed2: 1
|
||||
Malformed3: 2 MB
|
||||
Malformed4: X kB
|
||||
`
|
||||
meminfo, err := parseMemInfo(strings.NewReader(input))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if meminfo.MemTotal != 1*units.KiB {
|
||||
t.Fatalf("Unexpected MemTotal: %d", meminfo.MemTotal)
|
||||
}
|
||||
if meminfo.MemFree != 2*units.KiB {
|
||||
t.Fatalf("Unexpected MemFree: %d", meminfo.MemFree)
|
||||
}
|
||||
if meminfo.SwapTotal != 3*units.KiB {
|
||||
t.Fatalf("Unexpected SwapTotal: %d", meminfo.SwapTotal)
|
||||
}
|
||||
if meminfo.SwapFree != 4*units.KiB {
|
||||
t.Fatalf("Unexpected SwapFree: %d", meminfo.SwapFree)
|
||||
}
|
||||
}
|
7
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unsupported.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// +build !linux
|
||||
|
||||
package system
|
||||
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
18
Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod.go
generated
vendored
Normal file
18
Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// +build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Mknod(path string, mode uint32, dev int) error {
|
||||
return syscall.Mknod(path, mode, dev)
|
||||
}
|
||||
|
||||
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
|
||||
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
|
||||
// then the top 12 bits of the minor
|
||||
func Mkdev(major int64, minor int64) uint32 {
|
||||
return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
|
||||
}
|
12
Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod_windows.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod_windows.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
func Mknod(path string, mode uint32, dev int) error {
|
||||
// should not be called on cli code path
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
||||
|
||||
func Mkdev(major int64, minor int64) uint32 {
|
||||
panic("Mkdev not implemented on windows, should not be called on cli code")
|
||||
}
|
42
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Stat struct {
|
||||
mode uint32
|
||||
uid uint32
|
||||
gid uint32
|
||||
rdev uint64
|
||||
size int64
|
||||
mtim syscall.Timespec
|
||||
}
|
||||
|
||||
func (s Stat) Mode() uint32 {
|
||||
return s.mode
|
||||
}
|
||||
|
||||
func (s Stat) Uid() uint32 {
|
||||
return s.uid
|
||||
}
|
||||
|
||||
func (s Stat) Gid() uint32 {
|
||||
return s.gid
|
||||
}
|
||||
|
||||
func (s Stat) Rdev() uint64 {
|
||||
return s.rdev
|
||||
}
|
||||
|
||||
func (s Stat) Size() int64 {
|
||||
return s.size
|
||||
}
|
||||
|
||||
func (s Stat) Mtim() syscall.Timespec {
|
||||
return s.mtim
|
||||
}
|
||||
|
||||
func (s Stat) GetLastModification() syscall.Timespec {
|
||||
return s.Mtim()
|
||||
}
|
14
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_linux.go
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_linux.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func fromStatT(s *syscall.Stat_t) (*Stat, error) {
|
||||
return &Stat{size: s.Size,
|
||||
mode: s.Mode,
|
||||
uid: s.Uid,
|
||||
gid: s.Gid,
|
||||
rdev: s.Rdev,
|
||||
mtim: s.Mtim}, nil
|
||||
}
|
36
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_test.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_test.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFromStatT(t *testing.T) {
|
||||
file, _, _, dir := prepareFiles(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
stat := &syscall.Stat_t{}
|
||||
err := syscall.Lstat(file, stat)
|
||||
|
||||
s, err := fromStatT(stat)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if stat.Mode != s.Mode() {
|
||||
t.Fatal("got invalid mode")
|
||||
}
|
||||
if stat.Uid != s.Uid() {
|
||||
t.Fatal("got invalid uid")
|
||||
}
|
||||
if stat.Gid != s.Gid() {
|
||||
t.Fatal("got invalid gid")
|
||||
}
|
||||
if stat.Rdev != s.Rdev() {
|
||||
t.Fatal("got invalid rdev")
|
||||
}
|
||||
if stat.Mtim != s.Mtim() {
|
||||
t.Fatal("got invalid mtim")
|
||||
}
|
||||
}
|
16
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_unsupported.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// +build !linux,!windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func fromStatT(s *syscall.Stat_t) (*Stat, error) {
|
||||
return &Stat{size: s.Size,
|
||||
mode: uint32(s.Mode),
|
||||
uid: s.Uid,
|
||||
gid: s.Gid,
|
||||
rdev: uint64(s.Rdev),
|
||||
mtim: s.Mtimespec}, nil
|
||||
}
|
12
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_windows.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_windows.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func fromStatT(s *syscall.Win32FileAttributeData) (*Stat, error) {
|
||||
return nil, errors.New("fromStatT should not be called on windows path")
|
||||
}
|
11
Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// +build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Umask(newmask int) (oldmask int, err error) {
|
||||
return syscall.Umask(newmask), nil
|
||||
}
|
8
Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask_windows.go
generated
vendored
Normal file
8
Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask_windows.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
func Umask(newmask int) (oldmask int, err error) {
|
||||
// should not be called on cli code path
|
||||
return 0, ErrNotSupportedPlatform
|
||||
}
|
11
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_darwin.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_darwin.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package system
|
||||
|
||||
import "syscall"
|
||||
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
||||
|
||||
func UtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return syscall.UtimesNano(path, ts)
|
||||
}
|
24
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_freebsd.go
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
var _path *byte
|
||||
_path, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_LUTIMES, uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), 0); err != 0 && err != syscall.ENOSYS {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return syscall.UtimesNano(path, ts)
|
||||
}
|
28
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_linux.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_linux.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
// These are not currently available in syscall
|
||||
AT_FDCWD := -100
|
||||
AT_SYMLINK_NOFOLLOW := 0x100
|
||||
|
||||
var _path *byte
|
||||
_path, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(AT_FDCWD), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(AT_SYMLINK_NOFOLLOW), 0, 0); err != 0 && err != syscall.ENOSYS {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return syscall.UtimesNano(path, ts)
|
||||
}
|
65
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_test.go
generated
vendored
Normal file
65
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_test.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func prepareFiles(t *testing.T) (string, string, string, string) {
|
||||
dir, err := ioutil.TempDir("", "docker-system-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
file := filepath.Join(dir, "exist")
|
||||
if err := ioutil.WriteFile(file, []byte("hello"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
invalid := filepath.Join(dir, "doesnt-exist")
|
||||
|
||||
symlink := filepath.Join(dir, "symlink")
|
||||
if err := os.Symlink(file, symlink); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return file, invalid, symlink, dir
|
||||
}
|
||||
|
||||
func TestLUtimesNano(t *testing.T) {
|
||||
file, invalid, symlink, dir := prepareFiles(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
before, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ts := []syscall.Timespec{{0, 0}, {0, 0}}
|
||||
if err := LUtimesNano(symlink, ts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
symlinkInfo, err := os.Lstat(symlink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if before.ModTime().Unix() == symlinkInfo.ModTime().Unix() {
|
||||
t.Fatal("The modification time of the symlink should be different")
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if before.ModTime().Unix() != fileInfo.ModTime().Unix() {
|
||||
t.Fatal("The modification time of the file should be same")
|
||||
}
|
||||
|
||||
if err := LUtimesNano(invalid, ts); err == nil {
|
||||
t.Fatal("Doesn't return an error on a non-existing file")
|
||||
}
|
||||
}
|
13
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_unsupported.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// +build !linux,!freebsd,!darwin
|
||||
|
||||
package system
|
||||
|
||||
import "syscall"
|
||||
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
||||
|
||||
func UtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
59
Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_linux.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_linux.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Returns a nil slice and nil error if the xattr is not set
|
||||
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
pathBytes, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrBytes, err := syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dest := make([]byte, 128)
|
||||
destBytes := unsafe.Pointer(&dest[0])
|
||||
sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
|
||||
if errno == syscall.ENODATA {
|
||||
return nil, nil
|
||||
}
|
||||
if errno == syscall.ERANGE {
|
||||
dest = make([]byte, sz)
|
||||
destBytes := unsafe.Pointer(&dest[0])
|
||||
sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
|
||||
}
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
return dest[:sz], nil
|
||||
}
|
||||
|
||||
var _zero uintptr
|
||||
|
||||
func Lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||
pathBytes, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrBytes, err := syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dataBytes unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
dataBytes = unsafe.Pointer(&data[0])
|
||||
} else {
|
||||
dataBytes = unsafe.Pointer(&_zero)
|
||||
}
|
||||
_, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
11
Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_unsupported.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// +build !linux
|
||||
|
||||
package system
|
||||
|
||||
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
||||
|
||||
func Lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
305
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go
generated
vendored
Normal file
305
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go
generated
vendored
Normal file
@ -0,0 +1,305 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tar implements access to tar archives.
|
||||
// It aims to cover most of the variations, including those produced
|
||||
// by GNU and BSD tars.
|
||||
//
|
||||
// References:
|
||||
// http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
|
||||
// http://www.gnu.org/software/tar/manual/html_node/Standard.html
|
||||
// http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
|
||||
package tar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
blockSize = 512
|
||||
|
||||
// Types
|
||||
TypeReg = '0' // regular file
|
||||
TypeRegA = '\x00' // regular file
|
||||
TypeLink = '1' // hard link
|
||||
TypeSymlink = '2' // symbolic link
|
||||
TypeChar = '3' // character device node
|
||||
TypeBlock = '4' // block device node
|
||||
TypeDir = '5' // directory
|
||||
TypeFifo = '6' // fifo node
|
||||
TypeCont = '7' // reserved
|
||||
TypeXHeader = 'x' // extended header
|
||||
TypeXGlobalHeader = 'g' // global extended header
|
||||
TypeGNULongName = 'L' // Next file has a long name
|
||||
TypeGNULongLink = 'K' // Next file symlinks to a file w/ a long name
|
||||
TypeGNUSparse = 'S' // sparse file
|
||||
)
|
||||
|
||||
// A Header represents a single header in a tar archive.
|
||||
// Some fields may not be populated.
|
||||
type Header struct {
|
||||
Name string // name of header file entry
|
||||
Mode int64 // permission and mode bits
|
||||
Uid int // user id of owner
|
||||
Gid int // group id of owner
|
||||
Size int64 // length in bytes
|
||||
ModTime time.Time // modified time
|
||||
Typeflag byte // type of header entry
|
||||
Linkname string // target name of link
|
||||
Uname string // user name of owner
|
||||
Gname string // group name of owner
|
||||
Devmajor int64 // major number of character or block device
|
||||
Devminor int64 // minor number of character or block device
|
||||
AccessTime time.Time // access time
|
||||
ChangeTime time.Time // status change time
|
||||
Xattrs map[string]string
|
||||
}
|
||||
|
||||
// File name constants from the tar spec.
|
||||
const (
|
||||
fileNameSize = 100 // Maximum number of bytes in a standard tar name.
|
||||
fileNamePrefixSize = 155 // Maximum number of ustar extension bytes.
|
||||
)
|
||||
|
||||
// FileInfo returns an os.FileInfo for the Header.
|
||||
func (h *Header) FileInfo() os.FileInfo {
|
||||
return headerFileInfo{h}
|
||||
}
|
||||
|
||||
// headerFileInfo implements os.FileInfo.
|
||||
type headerFileInfo struct {
|
||||
h *Header
|
||||
}
|
||||
|
||||
func (fi headerFileInfo) Size() int64 { return fi.h.Size }
|
||||
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
||||
func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
|
||||
func (fi headerFileInfo) Sys() interface{} { return fi.h }
|
||||
|
||||
// Name returns the base name of the file.
|
||||
func (fi headerFileInfo) Name() string {
|
||||
if fi.IsDir() {
|
||||
return path.Base(path.Clean(fi.h.Name))
|
||||
}
|
||||
return path.Base(fi.h.Name)
|
||||
}
|
||||
|
||||
// Mode returns the permission and mode bits for the headerFileInfo.
|
||||
func (fi headerFileInfo) Mode() (mode os.FileMode) {
|
||||
// Set file permission bits.
|
||||
mode = os.FileMode(fi.h.Mode).Perm()
|
||||
|
||||
// Set setuid, setgid and sticky bits.
|
||||
if fi.h.Mode&c_ISUID != 0 {
|
||||
// setuid
|
||||
mode |= os.ModeSetuid
|
||||
}
|
||||
if fi.h.Mode&c_ISGID != 0 {
|
||||
// setgid
|
||||
mode |= os.ModeSetgid
|
||||
}
|
||||
if fi.h.Mode&c_ISVTX != 0 {
|
||||
// sticky
|
||||
mode |= os.ModeSticky
|
||||
}
|
||||
|
||||
// Set file mode bits.
|
||||
// clear perm, setuid, setgid and sticky bits.
|
||||
m := os.FileMode(fi.h.Mode) &^ 07777
|
||||
if m == c_ISDIR {
|
||||
// directory
|
||||
mode |= os.ModeDir
|
||||
}
|
||||
if m == c_ISFIFO {
|
||||
// named pipe (FIFO)
|
||||
mode |= os.ModeNamedPipe
|
||||
}
|
||||
if m == c_ISLNK {
|
||||
// symbolic link
|
||||
mode |= os.ModeSymlink
|
||||
}
|
||||
if m == c_ISBLK {
|
||||
// device file
|
||||
mode |= os.ModeDevice
|
||||
}
|
||||
if m == c_ISCHR {
|
||||
// Unix character device
|
||||
mode |= os.ModeDevice
|
||||
mode |= os.ModeCharDevice
|
||||
}
|
||||
if m == c_ISSOCK {
|
||||
// Unix domain socket
|
||||
mode |= os.ModeSocket
|
||||
}
|
||||
|
||||
switch fi.h.Typeflag {
|
||||
case TypeLink, TypeSymlink:
|
||||
// hard link, symbolic link
|
||||
mode |= os.ModeSymlink
|
||||
case TypeChar:
|
||||
// character device node
|
||||
mode |= os.ModeDevice
|
||||
mode |= os.ModeCharDevice
|
||||
case TypeBlock:
|
||||
// block device node
|
||||
mode |= os.ModeDevice
|
||||
case TypeDir:
|
||||
// directory
|
||||
mode |= os.ModeDir
|
||||
case TypeFifo:
|
||||
// fifo node
|
||||
mode |= os.ModeNamedPipe
|
||||
}
|
||||
|
||||
return mode
|
||||
}
|
||||
|
||||
// sysStat, if non-nil, populates h from system-dependent fields of fi.
|
||||
var sysStat func(fi os.FileInfo, h *Header) error
|
||||
|
||||
// Mode constants from the tar spec.
|
||||
const (
|
||||
c_ISUID = 04000 // Set uid
|
||||
c_ISGID = 02000 // Set gid
|
||||
c_ISVTX = 01000 // Save text (sticky bit)
|
||||
c_ISDIR = 040000 // Directory
|
||||
c_ISFIFO = 010000 // FIFO
|
||||
c_ISREG = 0100000 // Regular file
|
||||
c_ISLNK = 0120000 // Symbolic link
|
||||
c_ISBLK = 060000 // Block special file
|
||||
c_ISCHR = 020000 // Character special file
|
||||
c_ISSOCK = 0140000 // Socket
|
||||
)
|
||||
|
||||
// Keywords for the PAX Extended Header
|
||||
const (
|
||||
paxAtime = "atime"
|
||||
paxCharset = "charset"
|
||||
paxComment = "comment"
|
||||
paxCtime = "ctime" // please note that ctime is not a valid pax header.
|
||||
paxGid = "gid"
|
||||
paxGname = "gname"
|
||||
paxLinkpath = "linkpath"
|
||||
paxMtime = "mtime"
|
||||
paxPath = "path"
|
||||
paxSize = "size"
|
||||
paxUid = "uid"
|
||||
paxUname = "uname"
|
||||
paxXattr = "SCHILY.xattr."
|
||||
paxNone = ""
|
||||
)
|
||||
|
||||
// FileInfoHeader creates a partially-populated Header from fi.
|
||||
// If fi describes a symlink, FileInfoHeader records link as the link target.
|
||||
// If fi describes a directory, a slash is appended to the name.
|
||||
// Because os.FileInfo's Name method returns only the base name of
|
||||
// the file it describes, it may be necessary to modify the Name field
|
||||
// of the returned header to provide the full path name of the file.
|
||||
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
||||
if fi == nil {
|
||||
return nil, errors.New("tar: FileInfo is nil")
|
||||
}
|
||||
fm := fi.Mode()
|
||||
h := &Header{
|
||||
Name: fi.Name(),
|
||||
ModTime: fi.ModTime(),
|
||||
Mode: int64(fm.Perm()), // or'd with c_IS* constants later
|
||||
}
|
||||
switch {
|
||||
case fm.IsRegular():
|
||||
h.Mode |= c_ISREG
|
||||
h.Typeflag = TypeReg
|
||||
h.Size = fi.Size()
|
||||
case fi.IsDir():
|
||||
h.Typeflag = TypeDir
|
||||
h.Mode |= c_ISDIR
|
||||
h.Name += "/"
|
||||
case fm&os.ModeSymlink != 0:
|
||||
h.Typeflag = TypeSymlink
|
||||
h.Mode |= c_ISLNK
|
||||
h.Linkname = link
|
||||
case fm&os.ModeDevice != 0:
|
||||
if fm&os.ModeCharDevice != 0 {
|
||||
h.Mode |= c_ISCHR
|
||||
h.Typeflag = TypeChar
|
||||
} else {
|
||||
h.Mode |= c_ISBLK
|
||||
h.Typeflag = TypeBlock
|
||||
}
|
||||
case fm&os.ModeNamedPipe != 0:
|
||||
h.Typeflag = TypeFifo
|
||||
h.Mode |= c_ISFIFO
|
||||
case fm&os.ModeSocket != 0:
|
||||
h.Mode |= c_ISSOCK
|
||||
default:
|
||||
return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
|
||||
}
|
||||
if fm&os.ModeSetuid != 0 {
|
||||
h.Mode |= c_ISUID
|
||||
}
|
||||
if fm&os.ModeSetgid != 0 {
|
||||
h.Mode |= c_ISGID
|
||||
}
|
||||
if fm&os.ModeSticky != 0 {
|
||||
h.Mode |= c_ISVTX
|
||||
}
|
||||
if sysStat != nil {
|
||||
return h, sysStat(fi, h)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
var zeroBlock = make([]byte, blockSize)
|
||||
|
||||
// POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values.
|
||||
// We compute and return both.
|
||||
func checksum(header []byte) (unsigned int64, signed int64) {
|
||||
for i := 0; i < len(header); i++ {
|
||||
if i == 148 {
|
||||
// The chksum field (header[148:156]) is special: it should be treated as space bytes.
|
||||
unsigned += ' ' * 8
|
||||
signed += ' ' * 8
|
||||
i += 7
|
||||
continue
|
||||
}
|
||||
unsigned += int64(header[i])
|
||||
signed += int64(int8(header[i]))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type slicer []byte
|
||||
|
||||
func (sp *slicer) next(n int) (b []byte) {
|
||||
s := *sp
|
||||
b, *sp = s[0:n], s[n:]
|
||||
return
|
||||
}
|
||||
|
||||
func isASCII(s string) bool {
|
||||
for _, c := range s {
|
||||
if c >= 0x80 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toASCII(s string) string {
|
||||
if isASCII(s) {
|
||||
return s
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for _, c := range s {
|
||||
if c < 0x80 {
|
||||
buf.WriteByte(byte(c))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
79
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go
generated
vendored
Normal file
79
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tar_test
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
// Create a buffer to write our archive to.
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Create a new tar archive.
|
||||
tw := tar.NewWriter(buf)
|
||||
|
||||
// Add some files to the archive.
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"readme.txt", "This archive contains some text files."},
|
||||
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
|
||||
{"todo.txt", "Get animal handling licence."},
|
||||
}
|
||||
for _, file := range files {
|
||||
hdr := &tar.Header{
|
||||
Name: file.Name,
|
||||
Size: int64(len(file.Body)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
// Make sure to check the error on Close.
|
||||
if err := tw.Close(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Open the tar archive for reading.
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
tr := tar.NewReader(r)
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Printf("Contents of %s:\n", hdr.Name)
|
||||
if _, err := io.Copy(os.Stdout, tr); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Contents of readme.txt:
|
||||
// This archive contains some text files.
|
||||
// Contents of gopher.txt:
|
||||
// Gopher names:
|
||||
// George
|
||||
// Geoffrey
|
||||
// Gonzo
|
||||
// Contents of todo.txt:
|
||||
// Get animal handling licence.
|
||||
}
|
820
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go
generated
vendored
Normal file
820
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go
generated
vendored
Normal file
@ -0,0 +1,820 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tar
|
||||
|
||||
// TODO(dsymonds):
|
||||
// - pax extensions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrHeader = errors.New("archive/tar: invalid tar header")
|
||||
)
|
||||
|
||||
const maxNanoSecondIntSize = 9
|
||||
|
||||
// A Reader provides sequential access to the contents of a tar archive.
|
||||
// A tar archive consists of a sequence of files.
|
||||
// The Next method advances to the next file in the archive (including the first),
|
||||
// and then it can be treated as an io.Reader to access the file's data.
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
err error
|
||||
pad int64 // amount of padding (ignored) after current file entry
|
||||
curr numBytesReader // reader for current file entry
|
||||
hdrBuff [blockSize]byte // buffer to use in readHeader
|
||||
}
|
||||
|
||||
// A numBytesReader is an io.Reader with a numBytes method, returning the number
|
||||
// of bytes remaining in the underlying encoded data.
|
||||
type numBytesReader interface {
|
||||
io.Reader
|
||||
numBytes() int64
|
||||
}
|
||||
|
||||
// A regFileReader is a numBytesReader for reading file data from a tar archive.
|
||||
type regFileReader struct {
|
||||
r io.Reader // underlying reader
|
||||
nb int64 // number of unread bytes for current file entry
|
||||
}
|
||||
|
||||
// A sparseFileReader is a numBytesReader for reading sparse file data from a tar archive.
|
||||
type sparseFileReader struct {
|
||||
rfr *regFileReader // reads the sparse-encoded file data
|
||||
sp []sparseEntry // the sparse map for the file
|
||||
pos int64 // keeps track of file position
|
||||
tot int64 // total size of the file
|
||||
}
|
||||
|
||||
// Keywords for GNU sparse files in a PAX extended header
|
||||
const (
|
||||
paxGNUSparseNumBlocks = "GNU.sparse.numblocks"
|
||||
paxGNUSparseOffset = "GNU.sparse.offset"
|
||||
paxGNUSparseNumBytes = "GNU.sparse.numbytes"
|
||||
paxGNUSparseMap = "GNU.sparse.map"
|
||||
paxGNUSparseName = "GNU.sparse.name"
|
||||
paxGNUSparseMajor = "GNU.sparse.major"
|
||||
paxGNUSparseMinor = "GNU.sparse.minor"
|
||||
paxGNUSparseSize = "GNU.sparse.size"
|
||||
paxGNUSparseRealSize = "GNU.sparse.realsize"
|
||||
)
|
||||
|
||||
// Keywords for old GNU sparse headers
|
||||
const (
|
||||
oldGNUSparseMainHeaderOffset = 386
|
||||
oldGNUSparseMainHeaderIsExtendedOffset = 482
|
||||
oldGNUSparseMainHeaderNumEntries = 4
|
||||
oldGNUSparseExtendedHeaderIsExtendedOffset = 504
|
||||
oldGNUSparseExtendedHeaderNumEntries = 21
|
||||
oldGNUSparseOffsetSize = 12
|
||||
oldGNUSparseNumBytesSize = 12
|
||||
)
|
||||
|
||||
// NewReader creates a new Reader reading from r.
|
||||
func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
|
||||
|
||||
// Next advances to the next entry in the tar archive.
|
||||
func (tr *Reader) Next() (*Header, error) {
|
||||
var hdr *Header
|
||||
if tr.err == nil {
|
||||
tr.skipUnread()
|
||||
}
|
||||
if tr.err != nil {
|
||||
return hdr, tr.err
|
||||
}
|
||||
hdr = tr.readHeader()
|
||||
if hdr == nil {
|
||||
return hdr, tr.err
|
||||
}
|
||||
// Check for PAX/GNU header.
|
||||
switch hdr.Typeflag {
|
||||
case TypeXHeader:
|
||||
// PAX extended header
|
||||
headers, err := parsePAX(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We actually read the whole file,
|
||||
// but this skips alignment padding
|
||||
tr.skipUnread()
|
||||
hdr = tr.readHeader()
|
||||
mergePAX(hdr, headers)
|
||||
|
||||
// Check for a PAX format sparse file
|
||||
sp, err := tr.checkForGNUSparsePAXHeaders(hdr, headers)
|
||||
if err != nil {
|
||||
tr.err = err
|
||||
return nil, err
|
||||
}
|
||||
if sp != nil {
|
||||
// Current file is a PAX format GNU sparse file.
|
||||
// Set the current file reader to a sparse file reader.
|
||||
tr.curr = &sparseFileReader{rfr: tr.curr.(*regFileReader), sp: sp, tot: hdr.Size}
|
||||
}
|
||||
return hdr, nil
|
||||
case TypeGNULongName:
|
||||
// We have a GNU long name header. Its contents are the real file name.
|
||||
realname, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr, err := tr.Next()
|
||||
hdr.Name = cString(realname)
|
||||
return hdr, err
|
||||
case TypeGNULongLink:
|
||||
// We have a GNU long link header.
|
||||
realname, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr, err := tr.Next()
|
||||
hdr.Linkname = cString(realname)
|
||||
return hdr, err
|
||||
}
|
||||
return hdr, tr.err
|
||||
}
|
||||
|
||||
// checkForGNUSparsePAXHeaders checks the PAX headers for GNU sparse headers. If they are found, then
|
||||
// this function reads the sparse map and returns it. Unknown sparse formats are ignored, causing the file to
|
||||
// be treated as a regular file.
|
||||
func (tr *Reader) checkForGNUSparsePAXHeaders(hdr *Header, headers map[string]string) ([]sparseEntry, error) {
|
||||
var sparseFormat string
|
||||
|
||||
// Check for sparse format indicators
|
||||
major, majorOk := headers[paxGNUSparseMajor]
|
||||
minor, minorOk := headers[paxGNUSparseMinor]
|
||||
sparseName, sparseNameOk := headers[paxGNUSparseName]
|
||||
_, sparseMapOk := headers[paxGNUSparseMap]
|
||||
sparseSize, sparseSizeOk := headers[paxGNUSparseSize]
|
||||
sparseRealSize, sparseRealSizeOk := headers[paxGNUSparseRealSize]
|
||||
|
||||
// Identify which, if any, sparse format applies from which PAX headers are set
|
||||
if majorOk && minorOk {
|
||||
sparseFormat = major + "." + minor
|
||||
} else if sparseNameOk && sparseMapOk {
|
||||
sparseFormat = "0.1"
|
||||
} else if sparseSizeOk {
|
||||
sparseFormat = "0.0"
|
||||
} else {
|
||||
// Not a PAX format GNU sparse file.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Check for unknown sparse format
|
||||
if sparseFormat != "0.0" && sparseFormat != "0.1" && sparseFormat != "1.0" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Update hdr from GNU sparse PAX headers
|
||||
if sparseNameOk {
|
||||
hdr.Name = sparseName
|
||||
}
|
||||
if sparseSizeOk {
|
||||
realSize, err := strconv.ParseInt(sparseSize, 10, 0)
|
||||
if err != nil {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
hdr.Size = realSize
|
||||
} else if sparseRealSizeOk {
|
||||
realSize, err := strconv.ParseInt(sparseRealSize, 10, 0)
|
||||
if err != nil {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
hdr.Size = realSize
|
||||
}
|
||||
|
||||
// Set up the sparse map, according to the particular sparse format in use
|
||||
var sp []sparseEntry
|
||||
var err error
|
||||
switch sparseFormat {
|
||||
case "0.0", "0.1":
|
||||
sp, err = readGNUSparseMap0x1(headers)
|
||||
case "1.0":
|
||||
sp, err = readGNUSparseMap1x0(tr.curr)
|
||||
}
|
||||
return sp, err
|
||||
}
|
||||
|
||||
// mergePAX merges well known headers according to PAX standard.
|
||||
// In general headers with the same name as those found
|
||||
// in the header struct overwrite those found in the header
|
||||
// struct with higher precision or longer values. Esp. useful
|
||||
// for name and linkname fields.
|
||||
func mergePAX(hdr *Header, headers map[string]string) error {
|
||||
for k, v := range headers {
|
||||
switch k {
|
||||
case paxPath:
|
||||
hdr.Name = v
|
||||
case paxLinkpath:
|
||||
hdr.Linkname = v
|
||||
case paxGname:
|
||||
hdr.Gname = v
|
||||
case paxUname:
|
||||
hdr.Uname = v
|
||||
case paxUid:
|
||||
uid, err := strconv.ParseInt(v, 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Uid = int(uid)
|
||||
case paxGid:
|
||||
gid, err := strconv.ParseInt(v, 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Gid = int(gid)
|
||||
case paxAtime:
|
||||
t, err := parsePAXTime(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.AccessTime = t
|
||||
case paxMtime:
|
||||
t, err := parsePAXTime(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.ModTime = t
|
||||
case paxCtime:
|
||||
t, err := parsePAXTime(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.ChangeTime = t
|
||||
case paxSize:
|
||||
size, err := strconv.ParseInt(v, 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Size = int64(size)
|
||||
default:
|
||||
if strings.HasPrefix(k, paxXattr) {
|
||||
if hdr.Xattrs == nil {
|
||||
hdr.Xattrs = make(map[string]string)
|
||||
}
|
||||
hdr.Xattrs[k[len(paxXattr):]] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parsePAXTime takes a string of the form %d.%d as described in
|
||||
// the PAX specification.
|
||||
func parsePAXTime(t string) (time.Time, error) {
|
||||
buf := []byte(t)
|
||||
pos := bytes.IndexByte(buf, '.')
|
||||
var seconds, nanoseconds int64
|
||||
var err error
|
||||
if pos == -1 {
|
||||
seconds, err = strconv.ParseInt(t, 10, 0)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
} else {
|
||||
seconds, err = strconv.ParseInt(string(buf[:pos]), 10, 0)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
nano_buf := string(buf[pos+1:])
|
||||
// Pad as needed before converting to a decimal.
|
||||
// For example .030 -> .030000000 -> 30000000 nanoseconds
|
||||
if len(nano_buf) < maxNanoSecondIntSize {
|
||||
// Right pad
|
||||
nano_buf += strings.Repeat("0", maxNanoSecondIntSize-len(nano_buf))
|
||||
} else if len(nano_buf) > maxNanoSecondIntSize {
|
||||
// Right truncate
|
||||
nano_buf = nano_buf[:maxNanoSecondIntSize]
|
||||
}
|
||||
nanoseconds, err = strconv.ParseInt(string(nano_buf), 10, 0)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
}
|
||||
ts := time.Unix(seconds, nanoseconds)
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
// parsePAX parses PAX headers.
|
||||
// If an extended header (type 'x') is invalid, ErrHeader is returned
|
||||
func parsePAX(r io.Reader) (map[string]string, error) {
|
||||
buf, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For GNU PAX sparse format 0.0 support.
|
||||
// This function transforms the sparse format 0.0 headers into sparse format 0.1 headers.
|
||||
var sparseMap bytes.Buffer
|
||||
|
||||
headers := make(map[string]string)
|
||||
// Each record is constructed as
|
||||
// "%d %s=%s\n", length, keyword, value
|
||||
for len(buf) > 0 {
|
||||
// or the header was empty to start with.
|
||||
var sp int
|
||||
// The size field ends at the first space.
|
||||
sp = bytes.IndexByte(buf, ' ')
|
||||
if sp == -1 {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
// Parse the first token as a decimal integer.
|
||||
n, err := strconv.ParseInt(string(buf[:sp]), 10, 0)
|
||||
if err != nil {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
// Extract everything between the decimal and the n -1 on the
|
||||
// beginning to eat the ' ', -1 on the end to skip the newline.
|
||||
var record []byte
|
||||
record, buf = buf[sp+1:n-1], buf[n:]
|
||||
// The first equals is guaranteed to mark the end of the key.
|
||||
// Everything else is value.
|
||||
eq := bytes.IndexByte(record, '=')
|
||||
if eq == -1 {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
key, value := record[:eq], record[eq+1:]
|
||||
|
||||
keyStr := string(key)
|
||||
if keyStr == paxGNUSparseOffset || keyStr == paxGNUSparseNumBytes {
|
||||
// GNU sparse format 0.0 special key. Write to sparseMap instead of using the headers map.
|
||||
sparseMap.Write(value)
|
||||
sparseMap.Write([]byte{','})
|
||||
} else {
|
||||
// Normal key. Set the value in the headers map.
|
||||
headers[keyStr] = string(value)
|
||||
}
|
||||
}
|
||||
if sparseMap.Len() != 0 {
|
||||
// Add sparse info to headers, chopping off the extra comma
|
||||
sparseMap.Truncate(sparseMap.Len() - 1)
|
||||
headers[paxGNUSparseMap] = sparseMap.String()
|
||||
}
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
// cString parses bytes as a NUL-terminated C-style string.
|
||||
// If a NUL byte is not found then the whole slice is returned as a string.
|
||||
func cString(b []byte) string {
|
||||
n := 0
|
||||
for n < len(b) && b[n] != 0 {
|
||||
n++
|
||||
}
|
||||
return string(b[0:n])
|
||||
}
|
||||
|
||||
func (tr *Reader) octal(b []byte) int64 {
|
||||
// Check for binary format first.
|
||||
if len(b) > 0 && b[0]&0x80 != 0 {
|
||||
var x int64
|
||||
for i, c := range b {
|
||||
if i == 0 {
|
||||
c &= 0x7f // ignore signal bit in first byte
|
||||
}
|
||||
x = x<<8 | int64(c)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Because unused fields are filled with NULs, we need
|
||||
// to skip leading NULs. Fields may also be padded with
|
||||
// spaces or NULs.
|
||||
// So we remove leading and trailing NULs and spaces to
|
||||
// be sure.
|
||||
b = bytes.Trim(b, " \x00")
|
||||
|
||||
if len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
x, err := strconv.ParseUint(cString(b), 8, 64)
|
||||
if err != nil {
|
||||
tr.err = err
|
||||
}
|
||||
return int64(x)
|
||||
}
|
||||
|
||||
// skipUnread skips any unread bytes in the existing file entry, as well as any alignment padding.
|
||||
func (tr *Reader) skipUnread() {
|
||||
nr := tr.numBytes() + tr.pad // number of bytes to skip
|
||||
tr.curr, tr.pad = nil, 0
|
||||
if sr, ok := tr.r.(io.Seeker); ok {
|
||||
if _, err := sr.Seek(nr, os.SEEK_CUR); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, tr.err = io.CopyN(ioutil.Discard, tr.r, nr)
|
||||
}
|
||||
|
||||
func (tr *Reader) verifyChecksum(header []byte) bool {
|
||||
if tr.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
given := tr.octal(header[148:156])
|
||||
unsigned, signed := checksum(header)
|
||||
return given == unsigned || given == signed
|
||||
}
|
||||
|
||||
func (tr *Reader) readHeader() *Header {
|
||||
header := tr.hdrBuff[:]
|
||||
copy(header, zeroBlock)
|
||||
|
||||
if _, tr.err = io.ReadFull(tr.r, header); tr.err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Two blocks of zero bytes marks the end of the archive.
|
||||
if bytes.Equal(header, zeroBlock[0:blockSize]) {
|
||||
if _, tr.err = io.ReadFull(tr.r, header); tr.err != nil {
|
||||
return nil
|
||||
}
|
||||
if bytes.Equal(header, zeroBlock[0:blockSize]) {
|
||||
tr.err = io.EOF
|
||||
} else {
|
||||
tr.err = ErrHeader // zero block and then non-zero block
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !tr.verifyChecksum(header) {
|
||||
tr.err = ErrHeader
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unpack
|
||||
hdr := new(Header)
|
||||
s := slicer(header)
|
||||
|
||||
hdr.Name = cString(s.next(100))
|
||||
hdr.Mode = tr.octal(s.next(8))
|
||||
hdr.Uid = int(tr.octal(s.next(8)))
|
||||
hdr.Gid = int(tr.octal(s.next(8)))
|
||||
hdr.Size = tr.octal(s.next(12))
|
||||
hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0)
|
||||
s.next(8) // chksum
|
||||
hdr.Typeflag = s.next(1)[0]
|
||||
hdr.Linkname = cString(s.next(100))
|
||||
|
||||
// The remainder of the header depends on the value of magic.
|
||||
// The original (v7) version of tar had no explicit magic field,
|
||||
// so its magic bytes, like the rest of the block, are NULs.
|
||||
magic := string(s.next(8)) // contains version field as well.
|
||||
var format string
|
||||
switch {
|
||||
case magic[:6] == "ustar\x00": // POSIX tar (1003.1-1988)
|
||||
if string(header[508:512]) == "tar\x00" {
|
||||
format = "star"
|
||||
} else {
|
||||
format = "posix"
|
||||
}
|
||||
case magic == "ustar \x00": // old GNU tar
|
||||
format = "gnu"
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "posix", "gnu", "star":
|
||||
hdr.Uname = cString(s.next(32))
|
||||
hdr.Gname = cString(s.next(32))
|
||||
devmajor := s.next(8)
|
||||
devminor := s.next(8)
|
||||
if hdr.Typeflag == TypeChar || hdr.Typeflag == TypeBlock {
|
||||
hdr.Devmajor = tr.octal(devmajor)
|
||||
hdr.Devminor = tr.octal(devminor)
|
||||
}
|
||||
var prefix string
|
||||
switch format {
|
||||
case "posix", "gnu":
|
||||
prefix = cString(s.next(155))
|
||||
case "star":
|
||||
prefix = cString(s.next(131))
|
||||
hdr.AccessTime = time.Unix(tr.octal(s.next(12)), 0)
|
||||
hdr.ChangeTime = time.Unix(tr.octal(s.next(12)), 0)
|
||||
}
|
||||
if len(prefix) > 0 {
|
||||
hdr.Name = prefix + "/" + hdr.Name
|
||||
}
|
||||
}
|
||||
|
||||
if tr.err != nil {
|
||||
tr.err = ErrHeader
|
||||
return nil
|
||||
}
|
||||
|
||||
// Maximum value of hdr.Size is 64 GB (12 octal digits),
|
||||
// so there's no risk of int64 overflowing.
|
||||
nb := int64(hdr.Size)
|
||||
tr.pad = -nb & (blockSize - 1) // blockSize is a power of two
|
||||
|
||||
// Set the current file reader.
|
||||
tr.curr = ®FileReader{r: tr.r, nb: nb}
|
||||
|
||||
// Check for old GNU sparse format entry.
|
||||
if hdr.Typeflag == TypeGNUSparse {
|
||||
// Get the real size of the file.
|
||||
hdr.Size = tr.octal(header[483:495])
|
||||
|
||||
// Read the sparse map.
|
||||
sp := tr.readOldGNUSparseMap(header)
|
||||
if tr.err != nil {
|
||||
return nil
|
||||
}
|
||||
// Current file is a GNU sparse file. Update the current file reader.
|
||||
tr.curr = &sparseFileReader{rfr: tr.curr.(*regFileReader), sp: sp, tot: hdr.Size}
|
||||
}
|
||||
|
||||
return hdr
|
||||
}
|
||||
|
||||
// A sparseEntry holds a single entry in a sparse file's sparse map.
|
||||
// A sparse entry indicates the offset and size in a sparse file of a
|
||||
// block of data.
|
||||
type sparseEntry struct {
|
||||
offset int64
|
||||
numBytes int64
|
||||
}
|
||||
|
||||
// readOldGNUSparseMap reads the sparse map as stored in the old GNU sparse format.
|
||||
// The sparse map is stored in the tar header if it's small enough. If it's larger than four entries,
|
||||
// then one or more extension headers are used to store the rest of the sparse map.
|
||||
func (tr *Reader) readOldGNUSparseMap(header []byte) []sparseEntry {
|
||||
isExtended := header[oldGNUSparseMainHeaderIsExtendedOffset] != 0
|
||||
spCap := oldGNUSparseMainHeaderNumEntries
|
||||
if isExtended {
|
||||
spCap += oldGNUSparseExtendedHeaderNumEntries
|
||||
}
|
||||
sp := make([]sparseEntry, 0, spCap)
|
||||
s := slicer(header[oldGNUSparseMainHeaderOffset:])
|
||||
|
||||
// Read the four entries from the main tar header
|
||||
for i := 0; i < oldGNUSparseMainHeaderNumEntries; i++ {
|
||||
offset := tr.octal(s.next(oldGNUSparseOffsetSize))
|
||||
numBytes := tr.octal(s.next(oldGNUSparseNumBytesSize))
|
||||
if tr.err != nil {
|
||||
tr.err = ErrHeader
|
||||
return nil
|
||||
}
|
||||
if offset == 0 && numBytes == 0 {
|
||||
break
|
||||
}
|
||||
sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
|
||||
}
|
||||
|
||||
for isExtended {
|
||||
// There are more entries. Read an extension header and parse its entries.
|
||||
sparseHeader := make([]byte, blockSize)
|
||||
if _, tr.err = io.ReadFull(tr.r, sparseHeader); tr.err != nil {
|
||||
return nil
|
||||
}
|
||||
isExtended = sparseHeader[oldGNUSparseExtendedHeaderIsExtendedOffset] != 0
|
||||
s = slicer(sparseHeader)
|
||||
for i := 0; i < oldGNUSparseExtendedHeaderNumEntries; i++ {
|
||||
offset := tr.octal(s.next(oldGNUSparseOffsetSize))
|
||||
numBytes := tr.octal(s.next(oldGNUSparseNumBytesSize))
|
||||
if tr.err != nil {
|
||||
tr.err = ErrHeader
|
||||
return nil
|
||||
}
|
||||
if offset == 0 && numBytes == 0 {
|
||||
break
|
||||
}
|
||||
sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
|
||||
}
|
||||
}
|
||||
return sp
|
||||
}
|
||||
|
||||
// readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format version 1.0.
|
||||
// The sparse map is stored just before the file data and padded out to the nearest block boundary.
|
||||
func readGNUSparseMap1x0(r io.Reader) ([]sparseEntry, error) {
|
||||
buf := make([]byte, 2*blockSize)
|
||||
sparseHeader := buf[:blockSize]
|
||||
|
||||
// readDecimal is a helper function to read a decimal integer from the sparse map
|
||||
// while making sure to read from the file in blocks of size blockSize
|
||||
readDecimal := func() (int64, error) {
|
||||
// Look for newline
|
||||
nl := bytes.IndexByte(sparseHeader, '\n')
|
||||
if nl == -1 {
|
||||
if len(sparseHeader) >= blockSize {
|
||||
// This is an error
|
||||
return 0, ErrHeader
|
||||
}
|
||||
oldLen := len(sparseHeader)
|
||||
newLen := oldLen + blockSize
|
||||
if cap(sparseHeader) < newLen {
|
||||
// There's more header, but we need to make room for the next block
|
||||
copy(buf, sparseHeader)
|
||||
sparseHeader = buf[:newLen]
|
||||
} else {
|
||||
// There's more header, and we can just reslice
|
||||
sparseHeader = sparseHeader[:newLen]
|
||||
}
|
||||
|
||||
// Now that sparseHeader is large enough, read next block
|
||||
if _, err := io.ReadFull(r, sparseHeader[oldLen:newLen]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Look for a newline in the new data
|
||||
nl = bytes.IndexByte(sparseHeader[oldLen:newLen], '\n')
|
||||
if nl == -1 {
|
||||
// This is an error
|
||||
return 0, ErrHeader
|
||||
}
|
||||
nl += oldLen // We want the position from the beginning
|
||||
}
|
||||
// Now that we've found a newline, read a number
|
||||
n, err := strconv.ParseInt(string(sparseHeader[:nl]), 10, 0)
|
||||
if err != nil {
|
||||
return 0, ErrHeader
|
||||
}
|
||||
|
||||
// Update sparseHeader to consume this number
|
||||
sparseHeader = sparseHeader[nl+1:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Read the first block
|
||||
if _, err := io.ReadFull(r, sparseHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The first line contains the number of entries
|
||||
numEntries, err := readDecimal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read all the entries
|
||||
sp := make([]sparseEntry, 0, numEntries)
|
||||
for i := int64(0); i < numEntries; i++ {
|
||||
// Read the offset
|
||||
offset, err := readDecimal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read numBytes
|
||||
numBytes, err := readDecimal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
// readGNUSparseMap0x1 reads the sparse map as stored in GNU's PAX sparse format version 0.1.
|
||||
// The sparse map is stored in the PAX headers.
|
||||
func readGNUSparseMap0x1(headers map[string]string) ([]sparseEntry, error) {
|
||||
// Get number of entries
|
||||
numEntriesStr, ok := headers[paxGNUSparseNumBlocks]
|
||||
if !ok {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
numEntries, err := strconv.ParseInt(numEntriesStr, 10, 0)
|
||||
if err != nil {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
|
||||
sparseMap := strings.Split(headers[paxGNUSparseMap], ",")
|
||||
|
||||
// There should be two numbers in sparseMap for each entry
|
||||
if int64(len(sparseMap)) != 2*numEntries {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
|
||||
// Loop through the entries in the sparse map
|
||||
sp := make([]sparseEntry, 0, numEntries)
|
||||
for i := int64(0); i < numEntries; i++ {
|
||||
offset, err := strconv.ParseInt(sparseMap[2*i], 10, 0)
|
||||
if err != nil {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
numBytes, err := strconv.ParseInt(sparseMap[2*i+1], 10, 0)
|
||||
if err != nil {
|
||||
return nil, ErrHeader
|
||||
}
|
||||
sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
// numBytes returns the number of bytes left to read in the current file's entry
|
||||
// in the tar archive, or 0 if there is no current file.
|
||||
func (tr *Reader) numBytes() int64 {
|
||||
if tr.curr == nil {
|
||||
// No current file, so no bytes
|
||||
return 0
|
||||
}
|
||||
return tr.curr.numBytes()
|
||||
}
|
||||
|
||||
// Read reads from the current entry in the tar archive.
|
||||
// It returns 0, io.EOF when it reaches the end of that entry,
|
||||
// until Next is called to advance to the next entry.
|
||||
func (tr *Reader) Read(b []byte) (n int, err error) {
|
||||
if tr.curr == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n, err = tr.curr.Read(b)
|
||||
if err != nil && err != io.EOF {
|
||||
tr.err = err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rfr *regFileReader) Read(b []byte) (n int, err error) {
|
||||
if rfr.nb == 0 {
|
||||
// file consumed
|
||||
return 0, io.EOF
|
||||
}
|
||||
if int64(len(b)) > rfr.nb {
|
||||
b = b[0:rfr.nb]
|
||||
}
|
||||
n, err = rfr.r.Read(b)
|
||||
rfr.nb -= int64(n)
|
||||
|
||||
if err == io.EOF && rfr.nb > 0 {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// numBytes returns the number of bytes left to read in the file's data in the tar archive.
|
||||
func (rfr *regFileReader) numBytes() int64 {
|
||||
return rfr.nb
|
||||
}
|
||||
|
||||
// readHole reads a sparse file hole ending at offset toOffset
|
||||
func (sfr *sparseFileReader) readHole(b []byte, toOffset int64) int {
|
||||
n64 := toOffset - sfr.pos
|
||||
if n64 > int64(len(b)) {
|
||||
n64 = int64(len(b))
|
||||
}
|
||||
n := int(n64)
|
||||
for i := 0; i < n; i++ {
|
||||
b[i] = 0
|
||||
}
|
||||
sfr.pos += n64
|
||||
return n
|
||||
}
|
||||
|
||||
// Read reads the sparse file data in expanded form.
|
||||
func (sfr *sparseFileReader) Read(b []byte) (n int, err error) {
|
||||
if len(sfr.sp) == 0 {
|
||||
// No more data fragments to read from.
|
||||
if sfr.pos < sfr.tot {
|
||||
// We're in the last hole
|
||||
n = sfr.readHole(b, sfr.tot)
|
||||
return
|
||||
}
|
||||
// Otherwise, we're at the end of the file
|
||||
return 0, io.EOF
|
||||
}
|
||||
if sfr.pos < sfr.sp[0].offset {
|
||||
// We're in a hole
|
||||
n = sfr.readHole(b, sfr.sp[0].offset)
|
||||
return
|
||||
}
|
||||
|
||||
// We're not in a hole, so we'll read from the next data fragment
|
||||
posInFragment := sfr.pos - sfr.sp[0].offset
|
||||
bytesLeft := sfr.sp[0].numBytes - posInFragment
|
||||
if int64(len(b)) > bytesLeft {
|
||||
b = b[0:bytesLeft]
|
||||
}
|
||||
|
||||
n, err = sfr.rfr.Read(b)
|
||||
sfr.pos += int64(n)
|
||||
|
||||
if int64(n) == bytesLeft {
|
||||
// We're done with this fragment
|
||||
sfr.sp = sfr.sp[1:]
|
||||
}
|
||||
|
||||
if err == io.EOF && sfr.pos < sfr.tot {
|
||||
// We reached the end of the last fragment's data, but there's a final hole
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// numBytes returns the number of bytes left to read in the sparse file's
|
||||
// sparse-encoded data in the tar archive.
|
||||
func (sfr *sparseFileReader) numBytes() int64 {
|
||||
return sfr.rfr.nb
|
||||
}
|
743
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go
generated
vendored
Normal file
743
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go
generated
vendored
Normal file
@ -0,0 +1,743 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type untarTest struct {
|
||||
file string
|
||||
headers []*Header
|
||||
cksums []string
|
||||
}
|
||||
|
||||
var gnuTarTest = &untarTest{
|
||||
file: "testdata/gnu.tar",
|
||||
headers: []*Header{
|
||||
{
|
||||
Name: "small.txt",
|
||||
Mode: 0640,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 5,
|
||||
ModTime: time.Unix(1244428340, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "dsymonds",
|
||||
Gname: "eng",
|
||||
},
|
||||
{
|
||||
Name: "small2.txt",
|
||||
Mode: 0640,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 11,
|
||||
ModTime: time.Unix(1244436044, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "dsymonds",
|
||||
Gname: "eng",
|
||||
},
|
||||
},
|
||||
cksums: []string{
|
||||
"e38b27eaccb4391bdec553a7f3ae6b2f",
|
||||
"c65bd2e50a56a2138bf1716f2fd56fe9",
|
||||
},
|
||||
}
|
||||
|
||||
var sparseTarTest = &untarTest{
|
||||
file: "testdata/sparse-formats.tar",
|
||||
headers: []*Header{
|
||||
{
|
||||
Name: "sparse-gnu",
|
||||
Mode: 420,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Size: 200,
|
||||
ModTime: time.Unix(1392395740, 0),
|
||||
Typeflag: 0x53,
|
||||
Linkname: "",
|
||||
Uname: "david",
|
||||
Gname: "david",
|
||||
Devmajor: 0,
|
||||
Devminor: 0,
|
||||
},
|
||||
{
|
||||
Name: "sparse-posix-0.0",
|
||||
Mode: 420,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Size: 200,
|
||||
ModTime: time.Unix(1392342187, 0),
|
||||
Typeflag: 0x30,
|
||||
Linkname: "",
|
||||
Uname: "david",
|
||||
Gname: "david",
|
||||
Devmajor: 0,
|
||||
Devminor: 0,
|
||||
},
|
||||
{
|
||||
Name: "sparse-posix-0.1",
|
||||
Mode: 420,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Size: 200,
|
||||
ModTime: time.Unix(1392340456, 0),
|
||||
Typeflag: 0x30,
|
||||
Linkname: "",
|
||||
Uname: "david",
|
||||
Gname: "david",
|
||||
Devmajor: 0,
|
||||
Devminor: 0,
|
||||
},
|
||||
{
|
||||
Name: "sparse-posix-1.0",
|
||||
Mode: 420,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Size: 200,
|
||||
ModTime: time.Unix(1392337404, 0),
|
||||
Typeflag: 0x30,
|
||||
Linkname: "",
|
||||
Uname: "david",
|
||||
Gname: "david",
|
||||
Devmajor: 0,
|
||||
Devminor: 0,
|
||||
},
|
||||
{
|
||||
Name: "end",
|
||||
Mode: 420,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Size: 4,
|
||||
ModTime: time.Unix(1392398319, 0),
|
||||
Typeflag: 0x30,
|
||||
Linkname: "",
|
||||
Uname: "david",
|
||||
Gname: "david",
|
||||
Devmajor: 0,
|
||||
Devminor: 0,
|
||||
},
|
||||
},
|
||||
cksums: []string{
|
||||
"6f53234398c2449fe67c1812d993012f",
|
||||
"6f53234398c2449fe67c1812d993012f",
|
||||
"6f53234398c2449fe67c1812d993012f",
|
||||
"6f53234398c2449fe67c1812d993012f",
|
||||
"b0061974914468de549a2af8ced10316",
|
||||
},
|
||||
}
|
||||
|
||||
var untarTests = []*untarTest{
|
||||
gnuTarTest,
|
||||
sparseTarTest,
|
||||
{
|
||||
file: "testdata/star.tar",
|
||||
headers: []*Header{
|
||||
{
|
||||
Name: "small.txt",
|
||||
Mode: 0640,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 5,
|
||||
ModTime: time.Unix(1244592783, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "dsymonds",
|
||||
Gname: "eng",
|
||||
AccessTime: time.Unix(1244592783, 0),
|
||||
ChangeTime: time.Unix(1244592783, 0),
|
||||
},
|
||||
{
|
||||
Name: "small2.txt",
|
||||
Mode: 0640,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 11,
|
||||
ModTime: time.Unix(1244592783, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "dsymonds",
|
||||
Gname: "eng",
|
||||
AccessTime: time.Unix(1244592783, 0),
|
||||
ChangeTime: time.Unix(1244592783, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
file: "testdata/v7.tar",
|
||||
headers: []*Header{
|
||||
{
|
||||
Name: "small.txt",
|
||||
Mode: 0444,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 5,
|
||||
ModTime: time.Unix(1244593104, 0),
|
||||
Typeflag: '\x00',
|
||||
},
|
||||
{
|
||||
Name: "small2.txt",
|
||||
Mode: 0444,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 11,
|
||||
ModTime: time.Unix(1244593104, 0),
|
||||
Typeflag: '\x00',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
file: "testdata/pax.tar",
|
||||
headers: []*Header{
|
||||
{
|
||||
Name: "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
|
||||
Mode: 0664,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Uname: "shane",
|
||||
Gname: "shane",
|
||||
Size: 7,
|
||||
ModTime: time.Unix(1350244992, 23960108),
|
||||
ChangeTime: time.Unix(1350244992, 23960108),
|
||||
AccessTime: time.Unix(1350244992, 23960108),
|
||||
Typeflag: TypeReg,
|
||||
},
|
||||
{
|
||||
Name: "a/b",
|
||||
Mode: 0777,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Uname: "shane",
|
||||
Gname: "shane",
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1350266320, 910238425),
|
||||
ChangeTime: time.Unix(1350266320, 910238425),
|
||||
AccessTime: time.Unix(1350266320, 910238425),
|
||||
Typeflag: TypeSymlink,
|
||||
Linkname: "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
file: "testdata/nil-uid.tar", // golang.org/issue/5290
|
||||
headers: []*Header{
|
||||
{
|
||||
Name: "P1050238.JPG.log",
|
||||
Mode: 0664,
|
||||
Uid: 0,
|
||||
Gid: 0,
|
||||
Size: 14,
|
||||
ModTime: time.Unix(1365454838, 0),
|
||||
Typeflag: TypeReg,
|
||||
Linkname: "",
|
||||
Uname: "eyefi",
|
||||
Gname: "eyefi",
|
||||
Devmajor: 0,
|
||||
Devminor: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
file: "testdata/xattrs.tar",
|
||||
headers: []*Header{
|
||||
{
|
||||
Name: "small.txt",
|
||||
Mode: 0644,
|
||||
Uid: 1000,
|
||||
Gid: 10,
|
||||
Size: 5,
|
||||
ModTime: time.Unix(1386065770, 448252320),
|
||||
Typeflag: '0',
|
||||
Uname: "alex",
|
||||
Gname: "wheel",
|
||||
AccessTime: time.Unix(1389782991, 419875220),
|
||||
ChangeTime: time.Unix(1389782956, 794414986),
|
||||
Xattrs: map[string]string{
|
||||
"user.key": "value",
|
||||
"user.key2": "value2",
|
||||
// Interestingly, selinux encodes the terminating null inside the xattr
|
||||
"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "small2.txt",
|
||||
Mode: 0644,
|
||||
Uid: 1000,
|
||||
Gid: 10,
|
||||
Size: 11,
|
||||
ModTime: time.Unix(1386065770, 449252304),
|
||||
Typeflag: '0',
|
||||
Uname: "alex",
|
||||
Gname: "wheel",
|
||||
AccessTime: time.Unix(1389782991, 419875220),
|
||||
ChangeTime: time.Unix(1386065770, 449252304),
|
||||
Xattrs: map[string]string{
|
||||
"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
testLoop:
|
||||
for i, test := range untarTests {
|
||||
f, err := os.Open(test.file)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: Unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
defer f.Close()
|
||||
tr := NewReader(f)
|
||||
for j, header := range test.headers {
|
||||
hdr, err := tr.Next()
|
||||
if err != nil || hdr == nil {
|
||||
t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err)
|
||||
f.Close()
|
||||
continue testLoop
|
||||
}
|
||||
if !reflect.DeepEqual(*hdr, *header) {
|
||||
t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
|
||||
i, j, *hdr, *header)
|
||||
}
|
||||
}
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
continue testLoop
|
||||
}
|
||||
if hdr != nil || err != nil {
|
||||
t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, hdr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialRead(t *testing.T) {
|
||||
f, err := os.Open("testdata/gnu.tar")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tr := NewReader(f)
|
||||
|
||||
// Read the first four bytes; Next() should skip the last byte.
|
||||
hdr, err := tr.Next()
|
||||
if err != nil || hdr == nil {
|
||||
t.Fatalf("Didn't get first file: %v", err)
|
||||
}
|
||||
buf := make([]byte, 4)
|
||||
if _, err := io.ReadFull(tr, buf); err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if expected := []byte("Kilt"); !bytes.Equal(buf, expected) {
|
||||
t.Errorf("Contents = %v, want %v", buf, expected)
|
||||
}
|
||||
|
||||
// Second file
|
||||
hdr, err = tr.Next()
|
||||
if err != nil || hdr == nil {
|
||||
t.Fatalf("Didn't get second file: %v", err)
|
||||
}
|
||||
buf = make([]byte, 6)
|
||||
if _, err := io.ReadFull(tr, buf); err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if expected := []byte("Google"); !bytes.Equal(buf, expected) {
|
||||
t.Errorf("Contents = %v, want %v", buf, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrementalRead(t *testing.T) {
|
||||
test := gnuTarTest
|
||||
f, err := os.Open(test.file)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tr := NewReader(f)
|
||||
|
||||
headers := test.headers
|
||||
cksums := test.cksums
|
||||
nread := 0
|
||||
|
||||
// loop over all files
|
||||
for ; ; nread++ {
|
||||
hdr, err := tr.Next()
|
||||
if hdr == nil || err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
// check the header
|
||||
if !reflect.DeepEqual(*hdr, *headers[nread]) {
|
||||
t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
|
||||
*hdr, headers[nread])
|
||||
}
|
||||
|
||||
// read file contents in little chunks EOF,
|
||||
// checksumming all the way
|
||||
h := md5.New()
|
||||
rdbuf := make([]uint8, 8)
|
||||
for {
|
||||
nr, err := tr.Read(rdbuf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Read: unexpected error %v\n", err)
|
||||
break
|
||||
}
|
||||
h.Write(rdbuf[0:nr])
|
||||
}
|
||||
// verify checksum
|
||||
have := fmt.Sprintf("%x", h.Sum(nil))
|
||||
want := cksums[nread]
|
||||
if want != have {
|
||||
t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want)
|
||||
}
|
||||
}
|
||||
if nread != len(headers) {
|
||||
t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(headers), nread)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonSeekable(t *testing.T) {
|
||||
test := gnuTarTest
|
||||
f, err := os.Open(test.file)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
type readerOnly struct {
|
||||
io.Reader
|
||||
}
|
||||
tr := NewReader(readerOnly{f})
|
||||
nread := 0
|
||||
|
||||
for ; ; nread++ {
|
||||
_, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if nread != len(test.headers) {
|
||||
t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(test.headers), nread)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePAXHeader(t *testing.T) {
|
||||
paxTests := [][3]string{
|
||||
{"a", "a=name", "10 a=name\n"}, // Test case involving multiple acceptable lengths
|
||||
{"a", "a=name", "9 a=name\n"}, // Test case involving multiple acceptable length
|
||||
{"mtime", "mtime=1350244992.023960108", "30 mtime=1350244992.023960108\n"}}
|
||||
for _, test := range paxTests {
|
||||
key, expected, raw := test[0], test[1], test[2]
|
||||
reader := bytes.NewReader([]byte(raw))
|
||||
headers, err := parsePAX(reader)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't parse correctly formatted headers: %v", err)
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(headers[key], expected) {
|
||||
t.Errorf("mtime header incorrectly parsed: got %s, wanted %s", headers[key], expected)
|
||||
continue
|
||||
}
|
||||
trailer := make([]byte, 100)
|
||||
n, err := reader.Read(trailer)
|
||||
if err != io.EOF || n != 0 {
|
||||
t.Error("Buffer wasn't consumed")
|
||||
}
|
||||
}
|
||||
badHeader := bytes.NewReader([]byte("3 somelongkey="))
|
||||
if _, err := parsePAX(badHeader); err != ErrHeader {
|
||||
t.Fatal("Unexpected success when parsing bad header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePAXTime(t *testing.T) {
|
||||
// Some valid PAX time values
|
||||
timestamps := map[string]time.Time{
|
||||
"1350244992.023960108": time.Unix(1350244992, 23960108), // The common case
|
||||
"1350244992.02396010": time.Unix(1350244992, 23960100), // Lower precision value
|
||||
"1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value
|
||||
"1350244992": time.Unix(1350244992, 0), // Low precision value
|
||||
}
|
||||
for input, expected := range timestamps {
|
||||
ts, err := parsePAXTime(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ts.Equal(expected) {
|
||||
t.Fatalf("Time parsing failure %s %s", ts, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergePAX(t *testing.T) {
|
||||
hdr := new(Header)
|
||||
// Test a string, integer, and time based value.
|
||||
headers := map[string]string{
|
||||
"path": "a/b/c",
|
||||
"uid": "1000",
|
||||
"mtime": "1350244992.023960108",
|
||||
}
|
||||
err := mergePAX(hdr, headers)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := &Header{
|
||||
Name: "a/b/c",
|
||||
Uid: 1000,
|
||||
ModTime: time.Unix(1350244992, 23960108),
|
||||
}
|
||||
if !reflect.DeepEqual(hdr, want) {
|
||||
t.Errorf("incorrect merge: got %+v, want %+v", hdr, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSparseEndToEnd(t *testing.T) {
|
||||
test := sparseTarTest
|
||||
f, err := os.Open(test.file)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tr := NewReader(f)
|
||||
|
||||
headers := test.headers
|
||||
cksums := test.cksums
|
||||
nread := 0
|
||||
|
||||
// loop over all files
|
||||
for ; ; nread++ {
|
||||
hdr, err := tr.Next()
|
||||
if hdr == nil || err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
// check the header
|
||||
if !reflect.DeepEqual(*hdr, *headers[nread]) {
|
||||
t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
|
||||
*hdr, headers[nread])
|
||||
}
|
||||
|
||||
// read and checksum the file data
|
||||
h := md5.New()
|
||||
_, err = io.Copy(h, tr)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// verify checksum
|
||||
have := fmt.Sprintf("%x", h.Sum(nil))
|
||||
want := cksums[nread]
|
||||
if want != have {
|
||||
t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want)
|
||||
}
|
||||
}
|
||||
if nread != len(headers) {
|
||||
t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(headers), nread)
|
||||
}
|
||||
}
|
||||
|
||||
type sparseFileReadTest struct {
|
||||
sparseData []byte
|
||||
sparseMap []sparseEntry
|
||||
realSize int64
|
||||
expected []byte
|
||||
}
|
||||
|
||||
var sparseFileReadTests = []sparseFileReadTest{
|
||||
{
|
||||
sparseData: []byte("abcde"),
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 0, numBytes: 2},
|
||||
{offset: 5, numBytes: 3},
|
||||
},
|
||||
realSize: 8,
|
||||
expected: []byte("ab\x00\x00\x00cde"),
|
||||
},
|
||||
{
|
||||
sparseData: []byte("abcde"),
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 0, numBytes: 2},
|
||||
{offset: 5, numBytes: 3},
|
||||
},
|
||||
realSize: 10,
|
||||
expected: []byte("ab\x00\x00\x00cde\x00\x00"),
|
||||
},
|
||||
{
|
||||
sparseData: []byte("abcde"),
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 2},
|
||||
},
|
||||
realSize: 8,
|
||||
expected: []byte("\x00abc\x00\x00de"),
|
||||
},
|
||||
{
|
||||
sparseData: []byte("abcde"),
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 2},
|
||||
},
|
||||
realSize: 10,
|
||||
expected: []byte("\x00abc\x00\x00de\x00\x00"),
|
||||
},
|
||||
{
|
||||
sparseData: []byte(""),
|
||||
sparseMap: nil,
|
||||
realSize: 2,
|
||||
expected: []byte("\x00\x00"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestSparseFileReader(t *testing.T) {
|
||||
for i, test := range sparseFileReadTests {
|
||||
r := bytes.NewReader(test.sparseData)
|
||||
nb := int64(r.Len())
|
||||
sfr := &sparseFileReader{
|
||||
rfr: ®FileReader{r: r, nb: nb},
|
||||
sp: test.sparseMap,
|
||||
pos: 0,
|
||||
tot: test.realSize,
|
||||
}
|
||||
if sfr.numBytes() != nb {
|
||||
t.Errorf("test %d: Before reading, sfr.numBytes() = %d, want %d", i, sfr.numBytes(), nb)
|
||||
}
|
||||
buf, err := ioutil.ReadAll(sfr)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: Unexpected error: %v", i, err)
|
||||
}
|
||||
if e := test.expected; !bytes.Equal(buf, e) {
|
||||
t.Errorf("test %d: Contents = %v, want %v", i, buf, e)
|
||||
}
|
||||
if sfr.numBytes() != 0 {
|
||||
t.Errorf("test %d: After draining the reader, numBytes() was nonzero", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSparseIncrementalRead(t *testing.T) {
|
||||
sparseMap := []sparseEntry{{10, 2}}
|
||||
sparseData := []byte("Go")
|
||||
expected := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Go\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
r := bytes.NewReader(sparseData)
|
||||
nb := int64(r.Len())
|
||||
sfr := &sparseFileReader{
|
||||
rfr: ®FileReader{r: r, nb: nb},
|
||||
sp: sparseMap,
|
||||
pos: 0,
|
||||
tot: int64(len(expected)),
|
||||
}
|
||||
|
||||
// We'll read the data 6 bytes at a time, with a hole of size 10 at
|
||||
// the beginning and one of size 8 at the end.
|
||||
var outputBuf bytes.Buffer
|
||||
buf := make([]byte, 6)
|
||||
for {
|
||||
n, err := sfr.Read(buf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Read: unexpected error %v\n", err)
|
||||
}
|
||||
if n > 0 {
|
||||
_, err := outputBuf.Write(buf[:n])
|
||||
if err != nil {
|
||||
t.Errorf("Write: unexpected error %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
got := outputBuf.String()
|
||||
if got != expected {
|
||||
t.Errorf("Contents = %v, want %v", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadGNUSparseMap0x1(t *testing.T) {
|
||||
headers := map[string]string{
|
||||
paxGNUSparseNumBlocks: "4",
|
||||
paxGNUSparseMap: "0,5,10,5,20,5,30,5",
|
||||
}
|
||||
expected := []sparseEntry{
|
||||
{offset: 0, numBytes: 5},
|
||||
{offset: 10, numBytes: 5},
|
||||
{offset: 20, numBytes: 5},
|
||||
{offset: 30, numBytes: 5},
|
||||
}
|
||||
|
||||
sp, err := readGNUSparseMap0x1(headers)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(sp, expected) {
|
||||
t.Errorf("Incorrect sparse map: got %v, wanted %v", sp, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadGNUSparseMap1x0(t *testing.T) {
|
||||
// This test uses lots of holes so the sparse header takes up more than two blocks
|
||||
numEntries := 100
|
||||
expected := make([]sparseEntry, 0, numEntries)
|
||||
sparseMap := new(bytes.Buffer)
|
||||
|
||||
fmt.Fprintf(sparseMap, "%d\n", numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
offset := int64(2048 * i)
|
||||
numBytes := int64(1024)
|
||||
expected = append(expected, sparseEntry{offset: offset, numBytes: numBytes})
|
||||
fmt.Fprintf(sparseMap, "%d\n%d\n", offset, numBytes)
|
||||
}
|
||||
|
||||
// Make the header the smallest multiple of blockSize that fits the sparseMap
|
||||
headerBlocks := (sparseMap.Len() + blockSize - 1) / blockSize
|
||||
bufLen := blockSize * headerBlocks
|
||||
buf := make([]byte, bufLen)
|
||||
copy(buf, sparseMap.Bytes())
|
||||
|
||||
// Get an reader to read the sparse map
|
||||
r := bytes.NewReader(buf)
|
||||
|
||||
// Read the sparse map
|
||||
sp, err := readGNUSparseMap1x0(r)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(sp, expected) {
|
||||
t.Errorf("Incorrect sparse map: got %v, wanted %v", sp, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninitializedRead(t *testing.T) {
|
||||
test := gnuTarTest
|
||||
f, err := os.Open(test.file)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tr := NewReader(f)
|
||||
_, err = tr.Read([]byte{})
|
||||
if err == nil || err != io.EOF {
|
||||
t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF)
|
||||
}
|
||||
|
||||
}
|
20
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux dragonfly openbsd solaris
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func statAtime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(st.Atim.Unix())
|
||||
}
|
||||
|
||||
func statCtime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(st.Ctim.Unix())
|
||||
}
|
20
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin freebsd netbsd
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func statAtime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(st.Atimespec.Unix())
|
||||
}
|
||||
|
||||
func statCtime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(st.Ctimespec.Unix())
|
||||
}
|
32
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin dragonfly freebsd openbsd netbsd solaris
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sysStat = statUnix
|
||||
}
|
||||
|
||||
func statUnix(fi os.FileInfo, h *Header) error {
|
||||
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
h.Uid = int(sys.Uid)
|
||||
h.Gid = int(sys.Gid)
|
||||
// TODO(bradfitz): populate username & group. os/user
|
||||
// doesn't cache LookupId lookups, and lacks group
|
||||
// lookup functions.
|
||||
h.AccessTime = statAtime(sys)
|
||||
h.ChangeTime = statCtime(sys)
|
||||
// TODO(bradfitz): major/minor device numbers?
|
||||
return nil
|
||||
}
|
284
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go
generated
vendored
Normal file
284
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFileInfoHeader(t *testing.T) {
|
||||
fi, err := os.Stat("testdata/small.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := FileInfoHeader(fi, "")
|
||||
if err != nil {
|
||||
t.Fatalf("FileInfoHeader: %v", err)
|
||||
}
|
||||
if g, e := h.Name, "small.txt"; g != e {
|
||||
t.Errorf("Name = %q; want %q", g, e)
|
||||
}
|
||||
if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISREG; g != e {
|
||||
t.Errorf("Mode = %#o; want %#o", g, e)
|
||||
}
|
||||
if g, e := h.Size, int64(5); g != e {
|
||||
t.Errorf("Size = %v; want %v", g, e)
|
||||
}
|
||||
if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
|
||||
t.Errorf("ModTime = %v; want %v", g, e)
|
||||
}
|
||||
// FileInfoHeader should error when passing nil FileInfo
|
||||
if _, err := FileInfoHeader(nil, ""); err == nil {
|
||||
t.Fatalf("Expected error when passing nil to FileInfoHeader")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileInfoHeaderDir(t *testing.T) {
|
||||
fi, err := os.Stat("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := FileInfoHeader(fi, "")
|
||||
if err != nil {
|
||||
t.Fatalf("FileInfoHeader: %v", err)
|
||||
}
|
||||
if g, e := h.Name, "testdata/"; g != e {
|
||||
t.Errorf("Name = %q; want %q", g, e)
|
||||
}
|
||||
// Ignoring c_ISGID for golang.org/issue/4867
|
||||
if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm())|c_ISDIR; g != e {
|
||||
t.Errorf("Mode = %#o; want %#o", g, e)
|
||||
}
|
||||
if g, e := h.Size, int64(0); g != e {
|
||||
t.Errorf("Size = %v; want %v", g, e)
|
||||
}
|
||||
if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
|
||||
t.Errorf("ModTime = %v; want %v", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileInfoHeaderSymlink(t *testing.T) {
|
||||
h, err := FileInfoHeader(symlink{}, "some-target")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g, e := h.Name, "some-symlink"; g != e {
|
||||
t.Errorf("Name = %q; want %q", g, e)
|
||||
}
|
||||
if g, e := h.Linkname, "some-target"; g != e {
|
||||
t.Errorf("Linkname = %q; want %q", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
type symlink struct{}
|
||||
|
||||
func (symlink) Name() string { return "some-symlink" }
|
||||
func (symlink) Size() int64 { return 0 }
|
||||
func (symlink) Mode() os.FileMode { return os.ModeSymlink }
|
||||
func (symlink) ModTime() time.Time { return time.Time{} }
|
||||
func (symlink) IsDir() bool { return false }
|
||||
func (symlink) Sys() interface{} { return nil }
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
data := []byte("some file contents")
|
||||
|
||||
var b bytes.Buffer
|
||||
tw := NewWriter(&b)
|
||||
hdr := &Header{
|
||||
Name: "file.txt",
|
||||
Uid: 1 << 21, // too big for 8 octal digits
|
||||
Size: int64(len(data)),
|
||||
ModTime: time.Now(),
|
||||
}
|
||||
// tar only supports second precision.
|
||||
hdr.ModTime = hdr.ModTime.Add(-time.Duration(hdr.ModTime.Nanosecond()) * time.Nanosecond)
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
t.Fatalf("tw.WriteHeader: %v", err)
|
||||
}
|
||||
if _, err := tw.Write(data); err != nil {
|
||||
t.Fatalf("tw.Write: %v", err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatalf("tw.Close: %v", err)
|
||||
}
|
||||
|
||||
// Read it back.
|
||||
tr := NewReader(&b)
|
||||
rHdr, err := tr.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("tr.Next: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(rHdr, hdr) {
|
||||
t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
|
||||
}
|
||||
rData, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
t.Fatalf("Read: %v", err)
|
||||
}
|
||||
if !bytes.Equal(rData, data) {
|
||||
t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
|
||||
}
|
||||
}
|
||||
|
||||
type headerRoundTripTest struct {
|
||||
h *Header
|
||||
fm os.FileMode
|
||||
}
|
||||
|
||||
func TestHeaderRoundTrip(t *testing.T) {
|
||||
golden := []headerRoundTripTest{
|
||||
// regular file.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "test.txt",
|
||||
Mode: 0644 | c_ISREG,
|
||||
Size: 12,
|
||||
ModTime: time.Unix(1360600916, 0),
|
||||
Typeflag: TypeReg,
|
||||
},
|
||||
fm: 0644,
|
||||
},
|
||||
// hard link.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "hard.txt",
|
||||
Mode: 0644 | c_ISLNK,
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1360600916, 0),
|
||||
Typeflag: TypeLink,
|
||||
},
|
||||
fm: 0644 | os.ModeSymlink,
|
||||
},
|
||||
// symbolic link.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "link.txt",
|
||||
Mode: 0777 | c_ISLNK,
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1360600852, 0),
|
||||
Typeflag: TypeSymlink,
|
||||
},
|
||||
fm: 0777 | os.ModeSymlink,
|
||||
},
|
||||
// character device node.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "dev/null",
|
||||
Mode: 0666 | c_ISCHR,
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1360578951, 0),
|
||||
Typeflag: TypeChar,
|
||||
},
|
||||
fm: 0666 | os.ModeDevice | os.ModeCharDevice,
|
||||
},
|
||||
// block device node.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "dev/sda",
|
||||
Mode: 0660 | c_ISBLK,
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1360578954, 0),
|
||||
Typeflag: TypeBlock,
|
||||
},
|
||||
fm: 0660 | os.ModeDevice,
|
||||
},
|
||||
// directory.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "dir/",
|
||||
Mode: 0755 | c_ISDIR,
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1360601116, 0),
|
||||
Typeflag: TypeDir,
|
||||
},
|
||||
fm: 0755 | os.ModeDir,
|
||||
},
|
||||
// fifo node.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "dev/initctl",
|
||||
Mode: 0600 | c_ISFIFO,
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1360578949, 0),
|
||||
Typeflag: TypeFifo,
|
||||
},
|
||||
fm: 0600 | os.ModeNamedPipe,
|
||||
},
|
||||
// setuid.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "bin/su",
|
||||
Mode: 0755 | c_ISREG | c_ISUID,
|
||||
Size: 23232,
|
||||
ModTime: time.Unix(1355405093, 0),
|
||||
Typeflag: TypeReg,
|
||||
},
|
||||
fm: 0755 | os.ModeSetuid,
|
||||
},
|
||||
// setguid.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "group.txt",
|
||||
Mode: 0750 | c_ISREG | c_ISGID,
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1360602346, 0),
|
||||
Typeflag: TypeReg,
|
||||
},
|
||||
fm: 0750 | os.ModeSetgid,
|
||||
},
|
||||
// sticky.
|
||||
{
|
||||
h: &Header{
|
||||
Name: "sticky.txt",
|
||||
Mode: 0600 | c_ISREG | c_ISVTX,
|
||||
Size: 7,
|
||||
ModTime: time.Unix(1360602540, 0),
|
||||
Typeflag: TypeReg,
|
||||
},
|
||||
fm: 0600 | os.ModeSticky,
|
||||
},
|
||||
}
|
||||
|
||||
for i, g := range golden {
|
||||
fi := g.h.FileInfo()
|
||||
h2, err := FileInfoHeader(fi, "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fi.Name(), "/") {
|
||||
t.Errorf("FileInfo of %q contains slash: %q", g.h.Name, fi.Name())
|
||||
}
|
||||
name := path.Base(g.h.Name)
|
||||
if fi.IsDir() {
|
||||
name += "/"
|
||||
}
|
||||
if got, want := h2.Name, name; got != want {
|
||||
t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
|
||||
}
|
||||
if got, want := h2.Size, g.h.Size; got != want {
|
||||
t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
|
||||
}
|
||||
if got, want := h2.Mode, g.h.Mode; got != want {
|
||||
t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
|
||||
}
|
||||
if got, want := fi.Mode(), g.fm; got != want {
|
||||
t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
|
||||
}
|
||||
if got, want := h2.ModTime, g.h.ModTime; got != want {
|
||||
t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
|
||||
}
|
||||
if sysh, ok := fi.Sys().(*Header); !ok || sysh != g.h {
|
||||
t.Errorf("i=%d: Sys didn't return original *Header", i)
|
||||
}
|
||||
}
|
||||
}
|
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar
generated
vendored
Normal file
Binary file not shown.
1
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
Kilts
|
1
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
Google.com
|
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar
generated
vendored
Normal file
Binary file not shown.
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar
generated
vendored
Normal file
Binary file not shown.
396
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go
generated
vendored
Normal file
396
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go
generated
vendored
Normal file
@ -0,0 +1,396 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tar
|
||||
|
||||
// TODO(dsymonds):
|
||||
// - catch more errors (no first header, etc.)
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWriteTooLong = errors.New("archive/tar: write too long")
|
||||
ErrFieldTooLong = errors.New("archive/tar: header field too long")
|
||||
ErrWriteAfterClose = errors.New("archive/tar: write after close")
|
||||
errNameTooLong = errors.New("archive/tar: name too long")
|
||||
errInvalidHeader = errors.New("archive/tar: header field too long or contains invalid values")
|
||||
)
|
||||
|
||||
// A Writer provides sequential writing of a tar archive in POSIX.1 format.
|
||||
// A tar archive consists of a sequence of files.
|
||||
// Call WriteHeader to begin a new file, and then call Write to supply that file's data,
|
||||
// writing at most hdr.Size bytes in total.
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
err error
|
||||
nb int64 // number of unwritten bytes for current file entry
|
||||
pad int64 // amount of padding to write after current file entry
|
||||
closed bool
|
||||
usedBinary bool // whether the binary numeric field extension was used
|
||||
preferPax bool // use pax header instead of binary numeric header
|
||||
hdrBuff [blockSize]byte // buffer to use in writeHeader when writing a regular header
|
||||
paxHdrBuff [blockSize]byte // buffer to use in writeHeader when writing a pax header
|
||||
}
|
||||
|
||||
// NewWriter creates a new Writer writing to w.
|
||||
func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
|
||||
|
||||
// Flush finishes writing the current file (optional).
|
||||
func (tw *Writer) Flush() error {
|
||||
if tw.nb > 0 {
|
||||
tw.err = fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb)
|
||||
return tw.err
|
||||
}
|
||||
|
||||
n := tw.nb + tw.pad
|
||||
for n > 0 && tw.err == nil {
|
||||
nr := n
|
||||
if nr > blockSize {
|
||||
nr = blockSize
|
||||
}
|
||||
var nw int
|
||||
nw, tw.err = tw.w.Write(zeroBlock[0:nr])
|
||||
n -= int64(nw)
|
||||
}
|
||||
tw.nb = 0
|
||||
tw.pad = 0
|
||||
return tw.err
|
||||
}
|
||||
|
||||
// Write s into b, terminating it with a NUL if there is room.
|
||||
// If the value is too long for the field and allowPax is true add a paxheader record instead
|
||||
func (tw *Writer) cString(b []byte, s string, allowPax bool, paxKeyword string, paxHeaders map[string]string) {
|
||||
needsPaxHeader := allowPax && len(s) > len(b) || !isASCII(s)
|
||||
if needsPaxHeader {
|
||||
paxHeaders[paxKeyword] = s
|
||||
return
|
||||
}
|
||||
if len(s) > len(b) {
|
||||
if tw.err == nil {
|
||||
tw.err = ErrFieldTooLong
|
||||
}
|
||||
return
|
||||
}
|
||||
ascii := toASCII(s)
|
||||
copy(b, ascii)
|
||||
if len(ascii) < len(b) {
|
||||
b[len(ascii)] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Encode x as an octal ASCII string and write it into b with leading zeros.
|
||||
func (tw *Writer) octal(b []byte, x int64) {
|
||||
s := strconv.FormatInt(x, 8)
|
||||
// leading zeros, but leave room for a NUL.
|
||||
for len(s)+1 < len(b) {
|
||||
s = "0" + s
|
||||
}
|
||||
tw.cString(b, s, false, paxNone, nil)
|
||||
}
|
||||
|
||||
// Write x into b, either as octal or as binary (GNUtar/star extension).
|
||||
// If the value is too long for the field and writingPax is enabled both for the field and the add a paxheader record instead
|
||||
func (tw *Writer) numeric(b []byte, x int64, allowPax bool, paxKeyword string, paxHeaders map[string]string) {
|
||||
// Try octal first.
|
||||
s := strconv.FormatInt(x, 8)
|
||||
if len(s) < len(b) {
|
||||
tw.octal(b, x)
|
||||
return
|
||||
}
|
||||
|
||||
// If it is too long for octal, and pax is preferred, use a pax header
|
||||
if allowPax && tw.preferPax {
|
||||
tw.octal(b, 0)
|
||||
s := strconv.FormatInt(x, 10)
|
||||
paxHeaders[paxKeyword] = s
|
||||
return
|
||||
}
|
||||
|
||||
// Too big: use binary (big-endian).
|
||||
tw.usedBinary = true
|
||||
for i := len(b) - 1; x > 0 && i >= 0; i-- {
|
||||
b[i] = byte(x)
|
||||
x >>= 8
|
||||
}
|
||||
b[0] |= 0x80 // highest bit indicates binary format
|
||||
}
|
||||
|
||||
var (
|
||||
minTime = time.Unix(0, 0)
|
||||
// There is room for 11 octal digits (33 bits) of mtime.
|
||||
maxTime = minTime.Add((1<<33 - 1) * time.Second)
|
||||
)
|
||||
|
||||
// WriteHeader writes hdr and prepares to accept the file's contents.
|
||||
// WriteHeader calls Flush if it is not the first header.
|
||||
// Calling after a Close will return ErrWriteAfterClose.
|
||||
func (tw *Writer) WriteHeader(hdr *Header) error {
|
||||
return tw.writeHeader(hdr, true)
|
||||
}
|
||||
|
||||
// WriteHeader writes hdr and prepares to accept the file's contents.
|
||||
// WriteHeader calls Flush if it is not the first header.
|
||||
// Calling after a Close will return ErrWriteAfterClose.
|
||||
// As this method is called internally by writePax header to allow it to
|
||||
// suppress writing the pax header.
|
||||
func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
|
||||
if tw.closed {
|
||||
return ErrWriteAfterClose
|
||||
}
|
||||
if tw.err == nil {
|
||||
tw.Flush()
|
||||
}
|
||||
if tw.err != nil {
|
||||
return tw.err
|
||||
}
|
||||
|
||||
// a map to hold pax header records, if any are needed
|
||||
paxHeaders := make(map[string]string)
|
||||
|
||||
// TODO(shanemhansen): we might want to use PAX headers for
|
||||
// subsecond time resolution, but for now let's just capture
|
||||
// too long fields or non ascii characters
|
||||
|
||||
var header []byte
|
||||
|
||||
// We need to select which scratch buffer to use carefully,
|
||||
// since this method is called recursively to write PAX headers.
|
||||
// If allowPax is true, this is the non-recursive call, and we will use hdrBuff.
|
||||
// If allowPax is false, we are being called by writePAXHeader, and hdrBuff is
|
||||
// already being used by the non-recursive call, so we must use paxHdrBuff.
|
||||
header = tw.hdrBuff[:]
|
||||
if !allowPax {
|
||||
header = tw.paxHdrBuff[:]
|
||||
}
|
||||
copy(header, zeroBlock)
|
||||
s := slicer(header)
|
||||
|
||||
// keep a reference to the filename to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
|
||||
pathHeaderBytes := s.next(fileNameSize)
|
||||
|
||||
tw.cString(pathHeaderBytes, hdr.Name, true, paxPath, paxHeaders)
|
||||
|
||||
// Handle out of range ModTime carefully.
|
||||
var modTime int64
|
||||
if !hdr.ModTime.Before(minTime) && !hdr.ModTime.After(maxTime) {
|
||||
modTime = hdr.ModTime.Unix()
|
||||
}
|
||||
|
||||
tw.octal(s.next(8), hdr.Mode) // 100:108
|
||||
tw.numeric(s.next(8), int64(hdr.Uid), true, paxUid, paxHeaders) // 108:116
|
||||
tw.numeric(s.next(8), int64(hdr.Gid), true, paxGid, paxHeaders) // 116:124
|
||||
tw.numeric(s.next(12), hdr.Size, true, paxSize, paxHeaders) // 124:136
|
||||
tw.numeric(s.next(12), modTime, false, paxNone, nil) // 136:148 --- consider using pax for finer granularity
|
||||
s.next(8) // chksum (148:156)
|
||||
s.next(1)[0] = hdr.Typeflag // 156:157
|
||||
|
||||
tw.cString(s.next(100), hdr.Linkname, true, paxLinkpath, paxHeaders)
|
||||
|
||||
copy(s.next(8), []byte("ustar\x0000")) // 257:265
|
||||
tw.cString(s.next(32), hdr.Uname, true, paxUname, paxHeaders) // 265:297
|
||||
tw.cString(s.next(32), hdr.Gname, true, paxGname, paxHeaders) // 297:329
|
||||
tw.numeric(s.next(8), hdr.Devmajor, false, paxNone, nil) // 329:337
|
||||
tw.numeric(s.next(8), hdr.Devminor, false, paxNone, nil) // 337:345
|
||||
|
||||
// keep a reference to the prefix to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
|
||||
prefixHeaderBytes := s.next(155)
|
||||
tw.cString(prefixHeaderBytes, "", false, paxNone, nil) // 345:500 prefix
|
||||
|
||||
// Use the GNU magic instead of POSIX magic if we used any GNU extensions.
|
||||
if tw.usedBinary {
|
||||
copy(header[257:265], []byte("ustar \x00"))
|
||||
}
|
||||
|
||||
_, paxPathUsed := paxHeaders[paxPath]
|
||||
// try to use a ustar header when only the name is too long
|
||||
if !tw.preferPax && len(paxHeaders) == 1 && paxPathUsed {
|
||||
suffix := hdr.Name
|
||||
prefix := ""
|
||||
if len(hdr.Name) > fileNameSize && isASCII(hdr.Name) {
|
||||
var err error
|
||||
prefix, suffix, err = tw.splitUSTARLongName(hdr.Name)
|
||||
if err == nil {
|
||||
// ok we can use a ustar long name instead of pax, now correct the fields
|
||||
|
||||
// remove the path field from the pax header. this will suppress the pax header
|
||||
delete(paxHeaders, paxPath)
|
||||
|
||||
// update the path fields
|
||||
tw.cString(pathHeaderBytes, suffix, false, paxNone, nil)
|
||||
tw.cString(prefixHeaderBytes, prefix, false, paxNone, nil)
|
||||
|
||||
// Use the ustar magic if we used ustar long names.
|
||||
if len(prefix) > 0 && !tw.usedBinary {
|
||||
copy(header[257:265], []byte("ustar\x00"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The chksum field is terminated by a NUL and a space.
|
||||
// This is different from the other octal fields.
|
||||
chksum, _ := checksum(header)
|
||||
tw.octal(header[148:155], chksum)
|
||||
header[155] = ' '
|
||||
|
||||
if tw.err != nil {
|
||||
// problem with header; probably integer too big for a field.
|
||||
return tw.err
|
||||
}
|
||||
|
||||
if allowPax {
|
||||
for k, v := range hdr.Xattrs {
|
||||
paxHeaders[paxXattr+k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(paxHeaders) > 0 {
|
||||
if !allowPax {
|
||||
return errInvalidHeader
|
||||
}
|
||||
if err := tw.writePAXHeader(hdr, paxHeaders); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tw.nb = int64(hdr.Size)
|
||||
tw.pad = (blockSize - (tw.nb % blockSize)) % blockSize
|
||||
|
||||
_, tw.err = tw.w.Write(header)
|
||||
return tw.err
|
||||
}
|
||||
|
||||
// writeUSTARLongName splits a USTAR long name hdr.Name.
|
||||
// name must be < 256 characters. errNameTooLong is returned
|
||||
// if hdr.Name can't be split. The splitting heuristic
|
||||
// is compatible with gnu tar.
|
||||
func (tw *Writer) splitUSTARLongName(name string) (prefix, suffix string, err error) {
|
||||
length := len(name)
|
||||
if length > fileNamePrefixSize+1 {
|
||||
length = fileNamePrefixSize + 1
|
||||
} else if name[length-1] == '/' {
|
||||
length--
|
||||
}
|
||||
i := strings.LastIndex(name[:length], "/")
|
||||
// nlen contains the resulting length in the name field.
|
||||
// plen contains the resulting length in the prefix field.
|
||||
nlen := len(name) - i - 1
|
||||
plen := i
|
||||
if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize {
|
||||
err = errNameTooLong
|
||||
return
|
||||
}
|
||||
prefix, suffix = name[:i], name[i+1:]
|
||||
return
|
||||
}
|
||||
|
||||
// writePaxHeader writes an extended pax header to the
|
||||
// archive.
|
||||
func (tw *Writer) writePAXHeader(hdr *Header, paxHeaders map[string]string) error {
|
||||
// Prepare extended header
|
||||
ext := new(Header)
|
||||
ext.Typeflag = TypeXHeader
|
||||
// Setting ModTime is required for reader parsing to
|
||||
// succeed, and seems harmless enough.
|
||||
ext.ModTime = hdr.ModTime
|
||||
// The spec asks that we namespace our pseudo files
|
||||
// with the current pid.
|
||||
pid := os.Getpid()
|
||||
dir, file := path.Split(hdr.Name)
|
||||
fullName := path.Join(dir,
|
||||
fmt.Sprintf("PaxHeaders.%d", pid), file)
|
||||
|
||||
ascii := toASCII(fullName)
|
||||
if len(ascii) > 100 {
|
||||
ascii = ascii[:100]
|
||||
}
|
||||
ext.Name = ascii
|
||||
// Construct the body
|
||||
var buf bytes.Buffer
|
||||
|
||||
for k, v := range paxHeaders {
|
||||
fmt.Fprint(&buf, paxHeader(k+"="+v))
|
||||
}
|
||||
|
||||
ext.Size = int64(len(buf.Bytes()))
|
||||
if err := tw.writeHeader(ext, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tw.Write(buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// paxHeader formats a single pax record, prefixing it with the appropriate length
|
||||
func paxHeader(msg string) string {
|
||||
const padding = 2 // Extra padding for space and newline
|
||||
size := len(msg) + padding
|
||||
size += len(strconv.Itoa(size))
|
||||
record := fmt.Sprintf("%d %s\n", size, msg)
|
||||
if len(record) != size {
|
||||
// Final adjustment if adding size increased
|
||||
// the number of digits in size
|
||||
size = len(record)
|
||||
record = fmt.Sprintf("%d %s\n", size, msg)
|
||||
}
|
||||
return record
|
||||
}
|
||||
|
||||
// Write writes to the current entry in the tar archive.
|
||||
// Write returns the error ErrWriteTooLong if more than
|
||||
// hdr.Size bytes are written after WriteHeader.
|
||||
func (tw *Writer) Write(b []byte) (n int, err error) {
|
||||
if tw.closed {
|
||||
err = ErrWriteTooLong
|
||||
return
|
||||
}
|
||||
overwrite := false
|
||||
if int64(len(b)) > tw.nb {
|
||||
b = b[0:tw.nb]
|
||||
overwrite = true
|
||||
}
|
||||
n, err = tw.w.Write(b)
|
||||
tw.nb -= int64(n)
|
||||
if err == nil && overwrite {
|
||||
err = ErrWriteTooLong
|
||||
return
|
||||
}
|
||||
tw.err = err
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the tar archive, flushing any unwritten
|
||||
// data to the underlying writer.
|
||||
func (tw *Writer) Close() error {
|
||||
if tw.err != nil || tw.closed {
|
||||
return tw.err
|
||||
}
|
||||
tw.Flush()
|
||||
tw.closed = true
|
||||
if tw.err != nil {
|
||||
return tw.err
|
||||
}
|
||||
|
||||
// trailer: two zero blocks
|
||||
for i := 0; i < 2; i++ {
|
||||
_, tw.err = tw.w.Write(zeroBlock)
|
||||
if tw.err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return tw.err
|
||||
}
|
491
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go
generated
vendored
Normal file
491
Godeps/_workspace/src/github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go
generated
vendored
Normal file
@ -0,0 +1,491 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
"time"
|
||||
)
|
||||
|
||||
type writerTestEntry struct {
|
||||
header *Header
|
||||
contents string
|
||||
}
|
||||
|
||||
type writerTest struct {
|
||||
file string // filename of expected output
|
||||
entries []*writerTestEntry
|
||||
}
|
||||
|
||||
var writerTests = []*writerTest{
|
||||
// The writer test file was produced with this command:
|
||||
// tar (GNU tar) 1.26
|
||||
// ln -s small.txt link.txt
|
||||
// tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
|
||||
{
|
||||
file: "testdata/writer.tar",
|
||||
entries: []*writerTestEntry{
|
||||
{
|
||||
header: &Header{
|
||||
Name: "small.txt",
|
||||
Mode: 0640,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 5,
|
||||
ModTime: time.Unix(1246508266, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "dsymonds",
|
||||
Gname: "eng",
|
||||
},
|
||||
contents: "Kilts",
|
||||
},
|
||||
{
|
||||
header: &Header{
|
||||
Name: "small2.txt",
|
||||
Mode: 0640,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 11,
|
||||
ModTime: time.Unix(1245217492, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "dsymonds",
|
||||
Gname: "eng",
|
||||
},
|
||||
contents: "Google.com\n",
|
||||
},
|
||||
{
|
||||
header: &Header{
|
||||
Name: "link.txt",
|
||||
Mode: 0777,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Size: 0,
|
||||
ModTime: time.Unix(1314603082, 0),
|
||||
Typeflag: '2',
|
||||
Linkname: "small.txt",
|
||||
Uname: "strings",
|
||||
Gname: "strings",
|
||||
},
|
||||
// no contents
|
||||
},
|
||||
},
|
||||
},
|
||||
// The truncated test file was produced using these commands:
|
||||
// dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
|
||||
// tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
|
||||
{
|
||||
file: "testdata/writer-big.tar",
|
||||
entries: []*writerTestEntry{
|
||||
{
|
||||
header: &Header{
|
||||
Name: "tmp/16gig.txt",
|
||||
Mode: 0640,
|
||||
Uid: 73025,
|
||||
Gid: 5000,
|
||||
Size: 16 << 30,
|
||||
ModTime: time.Unix(1254699560, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "dsymonds",
|
||||
Gname: "eng",
|
||||
},
|
||||
// fake contents
|
||||
contents: strings.Repeat("\x00", 4<<10),
|
||||
},
|
||||
},
|
||||
},
|
||||
// The truncated test file was produced using these commands:
|
||||
// dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt
|
||||
// tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar
|
||||
{
|
||||
file: "testdata/writer-big-long.tar",
|
||||
entries: []*writerTestEntry{
|
||||
{
|
||||
header: &Header{
|
||||
Name: strings.Repeat("longname/", 15) + "16gig.txt",
|
||||
Mode: 0644,
|
||||
Uid: 1000,
|
||||
Gid: 1000,
|
||||
Size: 16 << 30,
|
||||
ModTime: time.Unix(1399583047, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "guillaume",
|
||||
Gname: "guillaume",
|
||||
},
|
||||
// fake contents
|
||||
contents: strings.Repeat("\x00", 4<<10),
|
||||
},
|
||||
},
|
||||
},
|
||||
// This file was produced using gnu tar 1.17
|
||||
// gnutar -b 4 --format=ustar (longname/)*15 + file.txt
|
||||
{
|
||||
file: "testdata/ustar.tar",
|
||||
entries: []*writerTestEntry{
|
||||
{
|
||||
header: &Header{
|
||||
Name: strings.Repeat("longname/", 15) + "file.txt",
|
||||
Mode: 0644,
|
||||
Uid: 0765,
|
||||
Gid: 024,
|
||||
Size: 06,
|
||||
ModTime: time.Unix(1360135598, 0),
|
||||
Typeflag: '0',
|
||||
Uname: "shane",
|
||||
Gname: "staff",
|
||||
},
|
||||
contents: "hello\n",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
|
||||
func bytestr(offset int, b []byte) string {
|
||||
const rowLen = 32
|
||||
s := fmt.Sprintf("%04x ", offset)
|
||||
for _, ch := range b {
|
||||
switch {
|
||||
case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
|
||||
s += fmt.Sprintf(" %c", ch)
|
||||
default:
|
||||
s += fmt.Sprintf(" %02x", ch)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Render a pseudo-diff between two blocks of bytes.
|
||||
func bytediff(a []byte, b []byte) string {
|
||||
const rowLen = 32
|
||||
s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
|
||||
for offset := 0; len(a)+len(b) > 0; offset += rowLen {
|
||||
na, nb := rowLen, rowLen
|
||||
if na > len(a) {
|
||||
na = len(a)
|
||||
}
|
||||
if nb > len(b) {
|
||||
nb = len(b)
|
||||
}
|
||||
sa := bytestr(offset, a[0:na])
|
||||
sb := bytestr(offset, b[0:nb])
|
||||
if sa != sb {
|
||||
s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
|
||||
}
|
||||
a = a[na:]
|
||||
b = b[nb:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestWriter(t *testing.T) {
|
||||
testLoop:
|
||||
for i, test := range writerTests {
|
||||
expected, err := ioutil.ReadFile(test.file)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: Unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
|
||||
big := false
|
||||
for j, entry := range test.entries {
|
||||
big = big || entry.header.Size > 1<<10
|
||||
if err := tw.WriteHeader(entry.header); err != nil {
|
||||
t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
|
||||
continue testLoop
|
||||
}
|
||||
if _, err := io.WriteString(tw, entry.contents); err != nil {
|
||||
t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
|
||||
continue testLoop
|
||||
}
|
||||
}
|
||||
// Only interested in Close failures for the small tests.
|
||||
if err := tw.Close(); err != nil && !big {
|
||||
t.Errorf("test %d: Failed closing archive: %v", i, err)
|
||||
continue testLoop
|
||||
}
|
||||
|
||||
actual := buf.Bytes()
|
||||
if !bytes.Equal(expected, actual) {
|
||||
t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
|
||||
i, bytediff(expected, actual))
|
||||
}
|
||||
if testing.Short() { // The second test is expensive.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPax(t *testing.T) {
|
||||
// Create an archive with a large name
|
||||
fileinfo, err := os.Stat("testdata/small.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hdr, err := FileInfoHeader(fileinfo, "")
|
||||
if err != nil {
|
||||
t.Fatalf("os.Stat: %v", err)
|
||||
}
|
||||
// Force a PAX long name to be written
|
||||
longName := strings.Repeat("ab", 100)
|
||||
contents := strings.Repeat(" ", int(hdr.Size))
|
||||
hdr.Name = longName
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&buf)
|
||||
if err := writer.WriteHeader(hdr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Simple test to make sure PAX extensions are in effect
|
||||
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
|
||||
t.Fatal("Expected at least one PAX header to be written.")
|
||||
}
|
||||
// Test that we can get a long name back out of the archive.
|
||||
reader := NewReader(&buf)
|
||||
hdr, err = reader.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hdr.Name != longName {
|
||||
t.Fatal("Couldn't recover long file name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaxSymlink(t *testing.T) {
|
||||
// Create an archive with a large linkname
|
||||
fileinfo, err := os.Stat("testdata/small.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hdr, err := FileInfoHeader(fileinfo, "")
|
||||
hdr.Typeflag = TypeSymlink
|
||||
if err != nil {
|
||||
t.Fatalf("os.Stat:1 %v", err)
|
||||
}
|
||||
// Force a PAX long linkname to be written
|
||||
longLinkname := strings.Repeat("1234567890/1234567890", 10)
|
||||
hdr.Linkname = longLinkname
|
||||
|
||||
hdr.Size = 0
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&buf)
|
||||
if err := writer.WriteHeader(hdr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Simple test to make sure PAX extensions are in effect
|
||||
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
|
||||
t.Fatal("Expected at least one PAX header to be written.")
|
||||
}
|
||||
// Test that we can get a long name back out of the archive.
|
||||
reader := NewReader(&buf)
|
||||
hdr, err = reader.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hdr.Linkname != longLinkname {
|
||||
t.Fatal("Couldn't recover long link name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaxNonAscii(t *testing.T) {
|
||||
// Create an archive with non ascii. These should trigger a pax header
|
||||
// because pax headers have a defined utf-8 encoding.
|
||||
fileinfo, err := os.Stat("testdata/small.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hdr, err := FileInfoHeader(fileinfo, "")
|
||||
if err != nil {
|
||||
t.Fatalf("os.Stat:1 %v", err)
|
||||
}
|
||||
|
||||
// some sample data
|
||||
chineseFilename := "文件名"
|
||||
chineseGroupname := "組"
|
||||
chineseUsername := "用戶名"
|
||||
|
||||
hdr.Name = chineseFilename
|
||||
hdr.Gname = chineseGroupname
|
||||
hdr.Uname = chineseUsername
|
||||
|
||||
contents := strings.Repeat(" ", int(hdr.Size))
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&buf)
|
||||
if err := writer.WriteHeader(hdr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Simple test to make sure PAX extensions are in effect
|
||||
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
|
||||
t.Fatal("Expected at least one PAX header to be written.")
|
||||
}
|
||||
// Test that we can get a long name back out of the archive.
|
||||
reader := NewReader(&buf)
|
||||
hdr, err = reader.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hdr.Name != chineseFilename {
|
||||
t.Fatal("Couldn't recover unicode name")
|
||||
}
|
||||
if hdr.Gname != chineseGroupname {
|
||||
t.Fatal("Couldn't recover unicode group")
|
||||
}
|
||||
if hdr.Uname != chineseUsername {
|
||||
t.Fatal("Couldn't recover unicode user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaxXattrs(t *testing.T) {
|
||||
xattrs := map[string]string{
|
||||
"user.key": "value",
|
||||
}
|
||||
|
||||
// Create an archive with an xattr
|
||||
fileinfo, err := os.Stat("testdata/small.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hdr, err := FileInfoHeader(fileinfo, "")
|
||||
if err != nil {
|
||||
t.Fatalf("os.Stat: %v", err)
|
||||
}
|
||||
contents := "Kilts"
|
||||
hdr.Xattrs = xattrs
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&buf)
|
||||
if err := writer.WriteHeader(hdr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test that we can get the xattrs back out of the archive.
|
||||
reader := NewReader(&buf)
|
||||
hdr, err = reader.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
|
||||
t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
|
||||
hdr.Xattrs, xattrs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPAXHeader(t *testing.T) {
|
||||
medName := strings.Repeat("CD", 50)
|
||||
longName := strings.Repeat("AB", 100)
|
||||
paxTests := [][2]string{
|
||||
{paxPath + "=/etc/hosts", "19 path=/etc/hosts\n"},
|
||||
{"a=b", "6 a=b\n"}, // Single digit length
|
||||
{"a=names", "11 a=names\n"}, // Test case involving carries
|
||||
{paxPath + "=" + longName, fmt.Sprintf("210 path=%s\n", longName)},
|
||||
{paxPath + "=" + medName, fmt.Sprintf("110 path=%s\n", medName)}}
|
||||
|
||||
for _, test := range paxTests {
|
||||
key, expected := test[0], test[1]
|
||||
if result := paxHeader(key); result != expected {
|
||||
t.Fatalf("paxHeader: got %s, expected %s", result, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUSTARLongName(t *testing.T) {
|
||||
// Create an archive with a path that failed to split with USTAR extension in previous versions.
|
||||
fileinfo, err := os.Stat("testdata/small.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hdr, err := FileInfoHeader(fileinfo, "")
|
||||
hdr.Typeflag = TypeDir
|
||||
if err != nil {
|
||||
t.Fatalf("os.Stat:1 %v", err)
|
||||
}
|
||||
// Force a PAX long name to be written. The name was taken from a practical example
|
||||
// that fails and replaced ever char through numbers to anonymize the sample.
|
||||
longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
|
||||
hdr.Name = longName
|
||||
|
||||
hdr.Size = 0
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&buf)
|
||||
if err := writer.WriteHeader(hdr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test that we can get a long name back out of the archive.
|
||||
reader := NewReader(&buf)
|
||||
hdr, err = reader.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hdr.Name != longName {
|
||||
t.Fatal("Couldn't recover long name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidTypeflagWithPAXHeader(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
tw := NewWriter(&buffer)
|
||||
|
||||
fileName := strings.Repeat("ab", 100)
|
||||
|
||||
hdr := &Header{
|
||||
Name: fileName,
|
||||
Size: 4,
|
||||
Typeflag: 0,
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
t.Fatalf("Failed to write header: %s", err)
|
||||
}
|
||||
if _, err := tw.Write([]byte("fooo")); err != nil {
|
||||
t.Fatalf("Failed to write the file's data: %s", err)
|
||||
}
|
||||
tw.Close()
|
||||
|
||||
tr := NewReader(&buffer)
|
||||
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read header: %s", err)
|
||||
}
|
||||
if header.Typeflag != 0 {
|
||||
t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
|
||||
}
|
||||
}
|
||||
}
|
7
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml
generated
vendored
7
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml
generated
vendored
@ -1,13 +1,12 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.1.2
|
||||
- 1.2.2
|
||||
- 1.3.1
|
||||
- 1.4
|
||||
- tip
|
||||
env:
|
||||
- GOARCH=amd64
|
||||
- GOARCH=386
|
||||
install:
|
||||
- go get -d ./...
|
||||
- make testdeps
|
||||
script:
|
||||
- go test ./...
|
||||
- make test
|
||||
|
31
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS
generated
vendored
31
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS
generated
vendored
@ -1,38 +1,63 @@
|
||||
# This is the official list of go-dockerclient authors for copyright purposes.
|
||||
|
||||
Adam Bell-Hanssen <adamb@aller.no>
|
||||
Aldrin Leal <aldrin@leal.eng.br>
|
||||
Andreas Jaekle <andreas@jaekle.net>
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Artem Sidorenko <artem@2realities.com>
|
||||
Andy Goldstein <andy.goldstein@redhat.com>
|
||||
Ben Marini <ben@remind101.com>
|
||||
Ben McCann <benmccann.com>
|
||||
Brian Lalor <blalor@bravo5.org>
|
||||
Burke Libbey <burke@libbey.me>
|
||||
Carlos Diaz-Padron <cpadron@mozilla.com>
|
||||
Cezar Sa Espinola <cezar.sa@corp.globo.com>
|
||||
Cheah Chu Yeow <chuyeow@gmail.com>
|
||||
cheneydeng <cheneydeng@qq.com>
|
||||
CMGS <ilskdw@gmail.com>
|
||||
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
||||
Darren Shepherd <darren@rancher.com>
|
||||
David Huie <dahuie@gmail.com>
|
||||
Dawn Chen <dawnchen@google.com>
|
||||
Ed <edrocksit@gmail.com>
|
||||
Eric Anderson <anderson@copperegg.com>
|
||||
Fabio Rehm <fgrehm@gmail.com>
|
||||
Fatih Arslan <ftharsln@gmail.com>
|
||||
Flavia Missi <flaviamissi@gmail.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Guillermo Álvarez Fernández <guillermo@cientifico.net>
|
||||
James Bardin <jbardin@litl.com>
|
||||
Jari Kolehmainen <jari.kolehmainen@digia.com>
|
||||
Jason Wilder <jwilder@litl.com>
|
||||
Jawher Moussa <jawher.moussa@gmail.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
Jeff Mitchell <jeffrey.mitchell@gmail.com>
|
||||
Jeffrey Hulten <jhulten@gmail.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Kamil Domanski <kamil@domanski.co>
|
||||
Karan Misra <kidoman@gmail.com>
|
||||
Kim, Hirokuni <hirokuni.kim@kvh.co.jp>
|
||||
liron-l <levinlir@gmail.com>
|
||||
Lucas Clemente <lucas@clemente.io>
|
||||
Lucas Weiblen <lucasweiblen@gmail.com>
|
||||
Mantas Matelis <mmatelis@coursera.org>
|
||||
Martin Sweeney <martin@sweeney.io>
|
||||
Máximo Cuadros Ortiz <mcuadros@gmail.com>
|
||||
Michal Fojtik <mfojtik@redhat.com>
|
||||
Mike Dillon <mike.dillon@synctree.com>
|
||||
Mrunal Patel <mrunalp@gmail.com>
|
||||
Nick Ethier <ncethier@gmail.com>
|
||||
Omeid Matten <public@omeid.me>
|
||||
Paul Morie <pmorie@gmail.com>
|
||||
Paul Weil <pweil@redhat.com>
|
||||
Peter Edge <peter.edge@gmail.com>
|
||||
Peter Jihoon Kim <raingrove@gmail.com>
|
||||
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
|
||||
Rafe Colton <r.colton@modcloth.com>
|
||||
Rafe Colton <rafael.colton@gmail.com>
|
||||
Rob Miller <rob@kalistra.com>
|
||||
Robert Williamson <williamson.robert@gmail.com>
|
||||
Salvador Gironès <salvadorgirones@gmail.com>
|
||||
Sam Rijs <srijs@airpost.net>
|
||||
Simon Eskildsen <sirup@sirupsen.com>
|
||||
Simon Menke <simon.menke@gmail.com>
|
||||
Skolos <skolos@gopherlab.com>
|
||||
@ -41,4 +66,8 @@ Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Summer Mousa <smousa@zenoss.com>
|
||||
Tarsis Azevedo <tarsis@corp.globo.com>
|
||||
Tim Schindler <tim@catalyst-zero.com>
|
||||
Tobi Knaup <tobi@mesosphere.io>
|
||||
Vincenzo Prignano <vincenzo.prignano@gmail.com>
|
||||
Wiliam Souza <wiliamsouza83@gmail.com>
|
||||
Ye Yin <eyniy@qq.com>
|
||||
Yuriy Bogdanov <chinsay@gmail.com>
|
||||
|
2
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/LICENSE
generated
vendored
2
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/LICENSE
generated
vendored
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2014, go-dockerclient authors
|
||||
Copyright (c) 2015, go-dockerclient authors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
35
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/Makefile
generated
vendored
Normal file
35
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/Makefile
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
.PHONY: \
|
||||
all \
|
||||
deps \
|
||||
updatedeps \
|
||||
testdeps \
|
||||
updatetestdeps \
|
||||
cov \
|
||||
test \
|
||||
clean
|
||||
|
||||
all: test
|
||||
|
||||
deps:
|
||||
go get -d -v ./...
|
||||
|
||||
updatedeps:
|
||||
go get -d -v -u -f ./...
|
||||
|
||||
testdeps:
|
||||
go get -d -v -t ./...
|
||||
|
||||
updatetestdeps:
|
||||
go get -d -v -t -u -f ./...
|
||||
|
||||
cov: testdeps
|
||||
go get -v github.com/axw/gocov/gocov
|
||||
go get golang.org/x/tools/cmd/cover
|
||||
gocov test | gocov report
|
||||
|
||||
test: testdeps
|
||||
go test ./...
|
||||
./testing/bin/fmtpolice
|
||||
|
||||
clean:
|
||||
go clean ./...
|
63
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown
generated
vendored
63
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown
generated
vendored
@ -3,11 +3,12 @@
|
||||
[](https://drone.io/github.com/fsouza/go-dockerclient/latest)
|
||||
[](https://travis-ci.org/fsouza/go-dockerclient)
|
||||
|
||||
[](http://godoc.org/github.com/fsouza/go-dockerclient)
|
||||
[](https://godoc.org/github.com/fsouza/go-dockerclient)
|
||||
|
||||
This package presents a client for the Docker remote API.
|
||||
This package presents a client for the Docker remote API. It also provides
|
||||
support for the extensions in the [Swarm API](https://docs.docker.com/swarm/API/).
|
||||
|
||||
For more details, check the [remote API documentation](http://docs.docker.io/en/latest/reference/api/docker_remote_api/).
|
||||
For more details, check the [remote API documentation](http://docs.docker.com/en/latest/reference/api/docker_remote_api/).
|
||||
|
||||
## Example
|
||||
|
||||
@ -15,22 +16,49 @@ For more details, check the [remote API documentation](http://docs.docker.io/en/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"fmt"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func main() {
|
||||
endpoint := "unix:///var/run/docker.sock"
|
||||
client, _ := docker.NewClient(endpoint)
|
||||
imgs, _ := client.ListImages(true)
|
||||
for _, img := range imgs {
|
||||
fmt.Println("ID: ", img.ID)
|
||||
fmt.Println("RepoTags: ", img.RepoTags)
|
||||
fmt.Println("Created: ", img.Created)
|
||||
fmt.Println("Size: ", img.Size)
|
||||
fmt.Println("VirtualSize: ", img.VirtualSize)
|
||||
fmt.Println("ParentId: ", img.ParentId)
|
||||
}
|
||||
endpoint := "unix:///var/run/docker.sock"
|
||||
client, _ := docker.NewClient(endpoint)
|
||||
imgs, _ := client.ListImages(docker.ListImagesOptions{All: false})
|
||||
for _, img := range imgs {
|
||||
fmt.Println("ID: ", img.ID)
|
||||
fmt.Println("RepoTags: ", img.RepoTags)
|
||||
fmt.Println("Created: ", img.Created)
|
||||
fmt.Println("Size: ", img.Size)
|
||||
fmt.Println("VirtualSize: ", img.VirtualSize)
|
||||
fmt.Println("ParentId: ", img.ParentID)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using with Boot2Docker
|
||||
|
||||
Boot2Docker runs Docker with TLS enabled. In order to instantiate the client you should use NewTLSClient, passing the endpoint and path for key and certificates as parameters.
|
||||
|
||||
For more details about TLS support in Boot2Docker, please refer to [TLS support](https://github.com/boot2docker/boot2docker#tls-support) on Boot2Docker's readme.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func main() {
|
||||
endpoint := "tcp://[ip]:[port]"
|
||||
path := os.Getenv("DOCKER_CERT_PATH")
|
||||
ca := fmt.Sprintf("%s/ca.pem", path)
|
||||
cert := fmt.Sprintf("%s/cert.pem", path)
|
||||
key := fmt.Sprintf("%s/key.pem", path)
|
||||
client, _ := docker.NewTLSClient(endpoint, cert, key, ca)
|
||||
// use client
|
||||
}
|
||||
```
|
||||
|
||||
@ -38,5 +66,4 @@ func main() {
|
||||
|
||||
You can run the tests with:
|
||||
|
||||
go get -d ./...
|
||||
go test ./...
|
||||
make test
|
||||
|
83
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/auth.go
generated
vendored
Normal file
83
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/auth.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AuthConfiguration represents authentication options to use in the PushImage
|
||||
// method. It represents the authentication in the Docker index server.
|
||||
type AuthConfiguration struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
ServerAddress string `json:"serveraddress,omitempty"`
|
||||
}
|
||||
|
||||
// AuthConfigurations represents authentication options to use for the
|
||||
// PushImage method accommodating the new X-Registry-Config header
|
||||
type AuthConfigurations struct {
|
||||
Configs map[string]AuthConfiguration `json:"configs"`
|
||||
}
|
||||
|
||||
// dockerConfig represents a registry authentation configuration from the
|
||||
// .dockercfg file.
|
||||
type dockerConfig struct {
|
||||
Auth string `json:"auth"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from the
|
||||
// ~/.dockercfg file.
|
||||
func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) {
|
||||
p := path.Join(os.Getenv("HOME"), ".dockercfg")
|
||||
r, err := os.Open(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAuthConfigurations(r)
|
||||
}
|
||||
|
||||
// NewAuthConfigurations returns AuthConfigurations from a JSON encoded string in the
|
||||
// same format as the .dockercfg file.
|
||||
func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) {
|
||||
var auth *AuthConfigurations
|
||||
var confs map[string]dockerConfig
|
||||
if err := json.NewDecoder(r).Decode(&confs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth, err := authConfigs(confs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
// authConfigs converts a dockerConfigs map to a AuthConfigurations object.
|
||||
func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
|
||||
c := &AuthConfigurations{
|
||||
Configs: make(map[string]AuthConfiguration),
|
||||
}
|
||||
for reg, conf := range confs {
|
||||
data, err := base64.StdEncoding.DecodeString(conf.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userpass := strings.Split(string(data), ":")
|
||||
c.Configs[reg] = AuthConfiguration{
|
||||
Email: conf.Email,
|
||||
Username: userpass[0],
|
||||
Password: userpass[1],
|
||||
ServerAddress: reg,
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
37
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/auth_test.go
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/auth_test.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAuthConfig(t *testing.T) {
|
||||
auth := base64.StdEncoding.EncodeToString([]byte("user:pass"))
|
||||
read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth))
|
||||
ac, err := NewAuthConfigurations(read)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
c, ok := ac.Configs["docker.io"]
|
||||
if !ok {
|
||||
t.Error("NewAuthConfigurations: Expected Configs to contain docker.io")
|
||||
}
|
||||
if got, want := c.Email, "user@example.com"; got != want {
|
||||
t.Errorf(`AuthConfigurations.Configs["docker.io"].Email: wrong result. Want %q. Got %q`, want, got)
|
||||
}
|
||||
if got, want := c.Username, "user"; got != want {
|
||||
t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got)
|
||||
}
|
||||
if got, want := c.Password, "pass"; got != want {
|
||||
t.Errorf(`AuthConfigurations.Configs["docker.io"].Password: wrong result. Want %q. Got %q`, want, got)
|
||||
}
|
||||
if got, want := c.ServerAddress, "docker.io"; got != want {
|
||||
t.Errorf(`AuthConfigurations.Configs["docker.io"].ServerAddress: wrong result. Want %q. Got %q`, want, got)
|
||||
}
|
||||
}
|
144
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/build_test.go
generated
vendored
Normal file
144
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/build_test.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
func TestBuildImageMultipleContextsError(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Name: "testImage",
|
||||
NoCache: true,
|
||||
SuppressOutput: true,
|
||||
RmTmpContainer: true,
|
||||
ForceRmTmpContainer: true,
|
||||
InputStream: &buf,
|
||||
OutputStream: &buf,
|
||||
ContextDir: "testing/data",
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != ErrMultipleContexts {
|
||||
t.Errorf("BuildImage: providing both InputStream and ContextDir should produce an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageContextDirDockerignoreParsing(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Name: "testImage",
|
||||
NoCache: true,
|
||||
SuppressOutput: true,
|
||||
RmTmpContainer: true,
|
||||
ForceRmTmpContainer: true,
|
||||
OutputStream: &buf,
|
||||
ContextDir: "testing/data",
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reqBody := fakeRT.requests[0].Body
|
||||
tmpdir, err := unpackBodyTarball(reqBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := os.RemoveAll(tmpdir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
files, err := ioutil.ReadDir(tmpdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
foundFiles := []string{}
|
||||
for _, file := range files {
|
||||
foundFiles = append(foundFiles, file.Name())
|
||||
}
|
||||
|
||||
expectedFiles := []string{
|
||||
".dockerignore",
|
||||
"Dockerfile",
|
||||
"barfile",
|
||||
"ca.pem",
|
||||
"cert.pem",
|
||||
"key.pem",
|
||||
"server.pem",
|
||||
"serverkey.pem",
|
||||
"symlink",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectedFiles, foundFiles) {
|
||||
t.Errorf(
|
||||
"BuildImage: incorrect files sent in tarball to docker server\nexpected %+v, found %+v",
|
||||
expectedFiles, foundFiles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageSendXRegistryConfig(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Name: "testImage",
|
||||
NoCache: true,
|
||||
SuppressOutput: true,
|
||||
RmTmpContainer: true,
|
||||
ForceRmTmpContainer: true,
|
||||
OutputStream: &buf,
|
||||
ContextDir: "testing/data",
|
||||
AuthConfigs: AuthConfigurations{
|
||||
Configs: map[string]AuthConfiguration{
|
||||
"quay.io": {
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "baz",
|
||||
ServerAddress: "quay.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encodedConfig := "eyJjb25maWdzIjp7InF1YXkuaW8iOnsidXNlcm5hbWUiOiJmb28iLCJwYXNzd29yZCI6ImJhciIsImVtYWlsIjoiYmF6Iiwic2VydmVyYWRkcmVzcyI6InF1YXkuaW8ifX19Cg=="
|
||||
|
||||
if err := client.BuildImage(opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
xRegistryConfig := fakeRT.requests[0].Header["X-Registry-Config"][0]
|
||||
if xRegistryConfig != encodedConfig {
|
||||
t.Errorf(
|
||||
"BuildImage: X-Registry-Config not set currectly: expected %q, got %q",
|
||||
encodedConfig,
|
||||
xRegistryConfig,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func unpackBodyTarball(req io.ReadCloser) (tmpdir string, err error) {
|
||||
tmpdir, err = ioutil.TempDir("", "go-dockerclient-test")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = archive.Untar(req, tmpdir, &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
NoLchown: true,
|
||||
})
|
||||
return
|
||||
}
|
7
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/change.go
generated
vendored
7
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/change.go
generated
vendored
@ -6,11 +6,18 @@ package docker
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ChangeType is a type for constants indicating the type of change
|
||||
// in a container
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
// ChangeModify is the ChangeType for container modifications
|
||||
ChangeModify ChangeType = iota
|
||||
|
||||
// ChangeAdd is the ChangeType for additions to a container
|
||||
ChangeAdd
|
||||
|
||||
// ChangeDelete is the ChangeType for deletions from a container
|
||||
ChangeDelete
|
||||
)
|
||||
|
||||
|
365
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go
generated
vendored
365
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -9,6 +9,8 @@ package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -32,22 +34,22 @@ var (
|
||||
// ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
|
||||
ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
|
||||
|
||||
apiVersion_1_12, _ = NewApiVersion("1.12")
|
||||
apiVersion112, _ = NewAPIVersion("1.12")
|
||||
)
|
||||
|
||||
// ApiVersion is an internal representation of a version of the Remote API.
|
||||
type ApiVersion []int
|
||||
// APIVersion is an internal representation of a version of the Remote API.
|
||||
type APIVersion []int
|
||||
|
||||
// NewApiVersion returns an instance of ApiVersion for the given string.
|
||||
// NewAPIVersion returns an instance of APIVersion for the given string.
|
||||
//
|
||||
// The given string must be in the form <major>.<minor>.<patch>, where <major>,
|
||||
// <minor> and <patch> are integer numbers.
|
||||
func NewApiVersion(input string) (ApiVersion, error) {
|
||||
func NewAPIVersion(input string) (APIVersion, error) {
|
||||
if !strings.Contains(input, ".") {
|
||||
return nil, fmt.Errorf("Unable to parse version %q", input)
|
||||
}
|
||||
arr := strings.Split(input, ".")
|
||||
ret := make(ApiVersion, len(arr))
|
||||
ret := make(APIVersion, len(arr))
|
||||
var err error
|
||||
for i, val := range arr {
|
||||
ret[i], err = strconv.Atoi(val)
|
||||
@ -58,7 +60,7 @@ func NewApiVersion(input string) (ApiVersion, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (version ApiVersion) String() string {
|
||||
func (version APIVersion) String() string {
|
||||
var str string
|
||||
for i, val := range version {
|
||||
str += strconv.Itoa(val)
|
||||
@ -69,23 +71,27 @@ func (version ApiVersion) String() string {
|
||||
return str
|
||||
}
|
||||
|
||||
func (version ApiVersion) LessThan(other ApiVersion) bool {
|
||||
// LessThan is a function for comparing APIVersion structs
|
||||
func (version APIVersion) LessThan(other APIVersion) bool {
|
||||
return version.compare(other) < 0
|
||||
}
|
||||
|
||||
func (version ApiVersion) LessThanOrEqualTo(other ApiVersion) bool {
|
||||
// LessThanOrEqualTo is a function for comparing APIVersion structs
|
||||
func (version APIVersion) LessThanOrEqualTo(other APIVersion) bool {
|
||||
return version.compare(other) <= 0
|
||||
}
|
||||
|
||||
func (version ApiVersion) GreaterThan(other ApiVersion) bool {
|
||||
// GreaterThan is a function for comparing APIVersion structs
|
||||
func (version APIVersion) GreaterThan(other APIVersion) bool {
|
||||
return version.compare(other) > 0
|
||||
}
|
||||
|
||||
func (version ApiVersion) GreaterThanOrEqualTo(other ApiVersion) bool {
|
||||
// GreaterThanOrEqualTo is a function for comparing APIVersion structs
|
||||
func (version APIVersion) GreaterThanOrEqualTo(other APIVersion) bool {
|
||||
return version.compare(other) >= 0
|
||||
}
|
||||
|
||||
func (version ApiVersion) compare(other ApiVersion) int {
|
||||
func (version APIVersion) compare(other APIVersion) int {
|
||||
for i, v := range version {
|
||||
if i <= len(other)-1 {
|
||||
otherVersion := other[i]
|
||||
@ -111,13 +117,14 @@ func (version ApiVersion) compare(other ApiVersion) int {
|
||||
type Client struct {
|
||||
SkipServerVersionCheck bool
|
||||
HTTPClient *http.Client
|
||||
TLSConfig *tls.Config
|
||||
|
||||
endpoint string
|
||||
endpointURL *url.URL
|
||||
eventMonitor *eventMonitoringState
|
||||
requestedApiVersion ApiVersion
|
||||
serverApiVersion ApiVersion
|
||||
expectedApiVersion ApiVersion
|
||||
requestedAPIVersion APIVersion
|
||||
serverAPIVersion APIVersion
|
||||
expectedAPIVersion APIVersion
|
||||
}
|
||||
|
||||
// NewClient returns a Client instance ready for communication with the given
|
||||
@ -132,16 +139,28 @@ func NewClient(endpoint string) (*Client, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// NewVersionedClient returns a Client instance ready for communication with
|
||||
// the given server endpoint, using a specific remote API version.
|
||||
func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) {
|
||||
u, err := parseEndpoint(endpoint)
|
||||
// NewTLSClient returns a Client instance ready for TLS communications with the givens
|
||||
// server endpoint, key and certificates . It will use the latest remote API version
|
||||
// available in the server.
|
||||
func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) {
|
||||
client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var requestedApiVersion ApiVersion
|
||||
client.SkipServerVersionCheck = true
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// NewVersionedClient returns a Client instance ready for communication with
|
||||
// the given server endpoint, using a specific remote API version.
|
||||
func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) {
|
||||
u, err := parseEndpoint(endpoint, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var requestedAPIVersion APIVersion
|
||||
if strings.Contains(apiVersionString, ".") {
|
||||
requestedApiVersion, err = NewApiVersion(apiVersionString)
|
||||
requestedAPIVersion, err = NewAPIVersion(apiVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -151,23 +170,79 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
|
||||
endpoint: endpoint,
|
||||
endpointURL: u,
|
||||
eventMonitor: new(eventMonitoringState),
|
||||
requestedApiVersion: requestedApiVersion,
|
||||
requestedAPIVersion: requestedAPIVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) checkApiVersion() error {
|
||||
serverApiVersionString, err := c.getServerApiVersionString()
|
||||
// NewVersionnedTLSClient has been DEPRECATED, please use NewVersionedTLSClient.
|
||||
func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
|
||||
return NewVersionedTLSClient(endpoint, cert, key, ca, apiVersionString)
|
||||
}
|
||||
|
||||
// NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens
|
||||
// server endpoint, key and certificates, using a specific remote API version.
|
||||
func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
|
||||
u, err := parseEndpoint(endpoint, true)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
c.serverApiVersion, err = NewApiVersion(serverApiVersionString)
|
||||
var requestedAPIVersion APIVersion
|
||||
if strings.Contains(apiVersionString, ".") {
|
||||
requestedAPIVersion, err = NewAPIVersion(apiVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if cert == "" || key == "" {
|
||||
return nil, errors.New("Both cert and key path are required")
|
||||
}
|
||||
tlsCert, err := tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if c.requestedApiVersion == nil {
|
||||
c.expectedApiVersion = c.serverApiVersion
|
||||
tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||
if ca == "" {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
} else {
|
||||
c.expectedApiVersion = c.requestedApiVersion
|
||||
cert, err := ioutil.ReadFile(ca)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caPool := x509.NewCertPool()
|
||||
if !caPool.AppendCertsFromPEM(cert) {
|
||||
return nil, errors.New("Could not add RootCA pem")
|
||||
}
|
||||
tlsConfig.RootCAs = caPool
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{
|
||||
HTTPClient: &http.Client{Transport: tr},
|
||||
TLSConfig: tlsConfig,
|
||||
endpoint: endpoint,
|
||||
endpointURL: u,
|
||||
eventMonitor: new(eventMonitoringState),
|
||||
requestedAPIVersion: requestedAPIVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) checkAPIVersion() error {
|
||||
serverAPIVersionString, err := c.getServerAPIVersionString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.requestedAPIVersion == nil {
|
||||
c.expectedAPIVersion = c.serverAPIVersion
|
||||
} else {
|
||||
c.expectedAPIVersion = c.requestedAPIVersion
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -177,7 +252,7 @@ func (c *Client) checkApiVersion() error {
|
||||
// See http://goo.gl/stJENm for more details.
|
||||
func (c *Client) Ping() error {
|
||||
path := "/_ping"
|
||||
body, status, err := c.do("GET", path, nil)
|
||||
body, status, err := c.do("GET", path, doOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -187,8 +262,8 @@ func (c *Client) Ping() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) getServerApiVersionString() (version string, err error) {
|
||||
body, status, err := c.do("GET", "/version", nil)
|
||||
func (c *Client) getServerAPIVersionString() (version string, err error) {
|
||||
body, status, err := c.do("GET", "/version", doOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -204,17 +279,22 @@ func (c *Client) getServerApiVersionString() (version string, err error) {
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (c *Client) do(method, path string, data interface{}) ([]byte, int, error) {
|
||||
type doOptions struct {
|
||||
data interface{}
|
||||
forceJSON bool
|
||||
}
|
||||
|
||||
func (c *Client) do(method, path string, doOptions doOptions) ([]byte, int, error) {
|
||||
var params io.Reader
|
||||
if data != nil {
|
||||
buf, err := json.Marshal(data)
|
||||
if doOptions.data != nil || doOptions.forceJSON {
|
||||
buf, err := json.Marshal(doOptions.data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
params = bytes.NewBuffer(buf)
|
||||
}
|
||||
if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil {
|
||||
err := c.checkApiVersion()
|
||||
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
|
||||
err := c.checkAPIVersion()
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
@ -224,7 +304,7 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error)
|
||||
return nil, -1, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if data != nil {
|
||||
if doOptions.data != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
@ -264,17 +344,26 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error)
|
||||
return body, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, headers map[string]string, in io.Reader, stdout, stderr io.Writer) error {
|
||||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
in = bytes.NewReader(nil)
|
||||
type streamOptions struct {
|
||||
setRawTerminal bool
|
||||
rawJSONStream bool
|
||||
headers map[string]string
|
||||
in io.Reader
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
}
|
||||
|
||||
func (c *Client) stream(method, path string, streamOptions streamOptions) error {
|
||||
if (method == "POST" || method == "PUT") && streamOptions.in == nil {
|
||||
streamOptions.in = bytes.NewReader(nil)
|
||||
}
|
||||
if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil {
|
||||
err := c.checkApiVersion()
|
||||
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
|
||||
err := c.checkAPIVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
req, err := http.NewRequest(method, c.getURL(path), in)
|
||||
req, err := http.NewRequest(method, c.getURL(path), streamOptions.in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -282,17 +371,17 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
|
||||
if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
for key, val := range headers {
|
||||
for key, val := range streamOptions.headers {
|
||||
req.Header.Set(key, val)
|
||||
}
|
||||
var resp *http.Response
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if stdout == nil {
|
||||
stdout = ioutil.Discard
|
||||
if streamOptions.stdout == nil {
|
||||
streamOptions.stdout = ioutil.Discard
|
||||
}
|
||||
if stderr == nil {
|
||||
stderr = ioutil.Discard
|
||||
if streamOptions.stderr == nil {
|
||||
streamOptions.stderr = ioutil.Discard
|
||||
}
|
||||
if protocol == "unix" {
|
||||
dial, err := net.Dial(protocol, address)
|
||||
@ -322,8 +411,8 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
|
||||
if resp.Header.Get("Content-Type") == "application/json" {
|
||||
// if we want to get raw json stream, just copy it back to output
|
||||
// without decoding it
|
||||
if rawJSONStream {
|
||||
_, err = io.Copy(stdout, resp.Body)
|
||||
if streamOptions.rawJSONStream {
|
||||
_, err = io.Copy(streamOptions.stdout, resp.Body)
|
||||
return err
|
||||
}
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
@ -335,41 +424,60 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
|
||||
return err
|
||||
}
|
||||
if m.Stream != "" {
|
||||
fmt.Fprint(stdout, m.Stream)
|
||||
fmt.Fprint(streamOptions.stdout, m.Stream)
|
||||
} else if m.Progress != "" {
|
||||
fmt.Fprintf(stdout, "%s %s\r", m.Status, m.Progress)
|
||||
fmt.Fprintf(streamOptions.stdout, "%s %s\r", m.Status, m.Progress)
|
||||
} else if m.Error != "" {
|
||||
return errors.New(m.Error)
|
||||
}
|
||||
if m.Status != "" {
|
||||
fmt.Fprintln(stdout, m.Status)
|
||||
fmt.Fprintln(streamOptions.stdout, m.Status)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(stdout, resp.Body)
|
||||
if streamOptions.setRawTerminal {
|
||||
_, err = io.Copy(streamOptions.stdout, resp.Body)
|
||||
} else {
|
||||
_, err = stdCopy(stdout, stderr, resp.Body)
|
||||
_, err = stdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) hijack(method, path string, success chan struct{}, setRawTerminal bool, in io.Reader, stderr, stdout io.Writer) error {
|
||||
if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil {
|
||||
err := c.checkApiVersion()
|
||||
type hijackOptions struct {
|
||||
success chan struct{}
|
||||
setRawTerminal bool
|
||||
in io.Reader
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
data interface{}
|
||||
}
|
||||
|
||||
func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error {
|
||||
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
|
||||
err := c.checkAPIVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if stdout == nil {
|
||||
stdout = ioutil.Discard
|
||||
|
||||
var params io.Reader
|
||||
if hijackOptions.data != nil {
|
||||
buf, err := json.Marshal(hijackOptions.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params = bytes.NewBuffer(buf)
|
||||
}
|
||||
if stderr == nil {
|
||||
stderr = ioutil.Discard
|
||||
|
||||
if hijackOptions.stdout == nil {
|
||||
hijackOptions.stdout = ioutil.Discard
|
||||
}
|
||||
req, err := http.NewRequest(method, c.getURL(path), nil)
|
||||
if hijackOptions.stderr == nil {
|
||||
hijackOptions.stderr = ioutil.Discard
|
||||
}
|
||||
req, err := http.NewRequest(method, c.getURL(path), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -380,34 +488,44 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
|
||||
protocol = "tcp"
|
||||
address = c.endpointURL.Host
|
||||
}
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return err
|
||||
var dial net.Conn
|
||||
if c.TLSConfig != nil && protocol != "unix" {
|
||||
dial, err = tlsDial(protocol, address, c.TLSConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dial, err = net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer dial.Close()
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
defer clientconn.Close()
|
||||
clientconn.Do(req)
|
||||
if success != nil {
|
||||
success <- struct{}{}
|
||||
<-success
|
||||
if hijackOptions.success != nil {
|
||||
hijackOptions.success <- struct{}{}
|
||||
<-hijackOptions.success
|
||||
}
|
||||
rwc, br := clientconn.Hijack()
|
||||
defer rwc.Close()
|
||||
errs := make(chan error, 2)
|
||||
exit := make(chan bool)
|
||||
go func() {
|
||||
defer close(exit)
|
||||
var err error
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(stdout, br)
|
||||
if hijackOptions.setRawTerminal {
|
||||
// When TTY is ON, use regular copy
|
||||
_, err = io.Copy(hijackOptions.stdout, br)
|
||||
} else {
|
||||
_, err = stdCopy(stdout, stderr, br)
|
||||
_, err = stdCopy(hijackOptions.stdout, hijackOptions.stderr, br)
|
||||
}
|
||||
errs <- err
|
||||
}()
|
||||
go func() {
|
||||
var err error
|
||||
if in != nil {
|
||||
_, err = io.Copy(rwc, in)
|
||||
if hijackOptions.in != nil {
|
||||
_, err = io.Copy(rwc, hijackOptions.in)
|
||||
}
|
||||
rwc.(interface {
|
||||
CloseWrite() error
|
||||
@ -424,11 +542,10 @@ func (c *Client) getURL(path string) string {
|
||||
urlStr = ""
|
||||
}
|
||||
|
||||
if c.requestedApiVersion != nil {
|
||||
return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedApiVersion, path)
|
||||
} else {
|
||||
return fmt.Sprintf("%s%s", urlStr, path)
|
||||
if c.requestedAPIVersion != nil {
|
||||
return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path)
|
||||
}
|
||||
return fmt.Sprintf("%s%s", urlStr, path)
|
||||
}
|
||||
|
||||
type jsonMessage struct {
|
||||
@ -461,33 +578,49 @@ func queryString(opts interface{}) string {
|
||||
} else if key == "-" {
|
||||
continue
|
||||
}
|
||||
v := value.Field(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
items.Add(key, "1")
|
||||
addQueryStringValue(items, key, value.Field(i))
|
||||
}
|
||||
return items.Encode()
|
||||
}
|
||||
|
||||
func addQueryStringValue(items url.Values, key string, v reflect.Value) {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
items.Add(key, "1")
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if v.Int() > 0 {
|
||||
items.Add(key, strconv.FormatInt(v.Int(), 10))
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if v.Float() > 0 {
|
||||
items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
|
||||
}
|
||||
case reflect.String:
|
||||
if v.String() != "" {
|
||||
items.Add(key, v.String())
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if !v.IsNil() {
|
||||
if b, err := json.Marshal(v.Interface()); err == nil {
|
||||
items.Add(key, string(b))
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if v.Int() > 0 {
|
||||
items.Add(key, strconv.FormatInt(v.Int(), 10))
|
||||
}
|
||||
case reflect.Map:
|
||||
if len(v.MapKeys()) > 0 {
|
||||
if b, err := json.Marshal(v.Interface()); err == nil {
|
||||
items.Add(key, string(b))
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if v.Float() > 0 {
|
||||
items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
|
||||
}
|
||||
case reflect.String:
|
||||
if v.String() != "" {
|
||||
items.Add(key, v.String())
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if !v.IsNil() {
|
||||
if b, err := json.Marshal(v.Interface()); err == nil {
|
||||
items.Add(key, string(b))
|
||||
}
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
vLen := v.Len()
|
||||
if vLen > 0 {
|
||||
for i := 0; i < vLen; i++ {
|
||||
addQueryStringValue(items, key, v.Index(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
return items.Encode()
|
||||
}
|
||||
|
||||
// Error represents failures in the API. It represents a failure from the API.
|
||||
@ -504,13 +637,31 @@ func (e *Error) Error() string {
|
||||
return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
|
||||
}
|
||||
|
||||
func parseEndpoint(endpoint string) (*url.URL, error) {
|
||||
func parseEndpoint(endpoint string, tls bool) (*url.URL, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
||||
if tls {
|
||||
u.Scheme = "https"
|
||||
}
|
||||
if u.Scheme == "tcp" {
|
||||
u.Scheme = "http"
|
||||
_, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
if e, ok := err.(*net.AddrError); ok {
|
||||
if e.Err == "missing port in address" {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
||||
|
||||
number, err := strconv.ParseInt(port, 10, 64)
|
||||
if err == nil && number == 2376 {
|
||||
u.Scheme = "https"
|
||||
} else {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" {
|
||||
return nil, ErrInvalidEndpoint
|
||||
|
101
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go
generated
vendored
101
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -39,8 +39,32 @@ func TestNewAPIClient(t *testing.T) {
|
||||
if !client.SkipServerVersionCheck {
|
||||
t.Error("Expected SkipServerVersionCheck to be true, got false")
|
||||
}
|
||||
if client.requestedApiVersion != nil {
|
||||
t.Errorf("Expected requestedApiVersion to be nil, got %#v.", client.requestedApiVersion)
|
||||
if client.requestedAPIVersion != nil {
|
||||
t.Errorf("Expected requestedAPIVersion to be nil, got %#v.", client.requestedAPIVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func newTLSClient(endpoint string) (*Client, error) {
|
||||
return NewTLSClient(endpoint,
|
||||
"testing/data/cert.pem",
|
||||
"testing/data/key.pem",
|
||||
"testing/data/ca.pem")
|
||||
}
|
||||
|
||||
func TestNewTSLAPIClient(t *testing.T) {
|
||||
endpoint := "https://localhost:4243"
|
||||
client, err := newTLSClient(endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.endpoint != endpoint {
|
||||
t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
|
||||
}
|
||||
if !client.SkipServerVersionCheck {
|
||||
t.Error("Expected SkipServerVersionCheck to be true, got false")
|
||||
}
|
||||
if client.requestedAPIVersion != nil {
|
||||
t.Errorf("Expected requestedAPIVersion to be nil, got %#v.", client.requestedAPIVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,14 +80,45 @@ func TestNewVersionedClient(t *testing.T) {
|
||||
if client.HTTPClient != http.DefaultClient {
|
||||
t.Errorf("Expected http.Client %#v. Got %#v.", http.DefaultClient, client.HTTPClient)
|
||||
}
|
||||
if reqVersion := client.requestedApiVersion.String(); reqVersion != "1.12" {
|
||||
t.Errorf("Wrong requestApiVersion. Want %q. Got %q.", "1.12", reqVersion)
|
||||
if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.12" {
|
||||
t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.12", reqVersion)
|
||||
}
|
||||
if client.SkipServerVersionCheck {
|
||||
t.Error("Expected SkipServerVersionCheck to be false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTLSVersionedClient(t *testing.T) {
|
||||
certPath := "testing/data/cert.pem"
|
||||
keyPath := "testing/data/key.pem"
|
||||
caPath := "testing/data/ca.pem"
|
||||
endpoint := "https://localhost:4243"
|
||||
client, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.endpoint != endpoint {
|
||||
t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
|
||||
}
|
||||
if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.14" {
|
||||
t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.14", reqVersion)
|
||||
}
|
||||
if client.SkipServerVersionCheck {
|
||||
t.Error("Expected SkipServerVersionCheck to be false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTLSVersionedClientInvalidCA(t *testing.T) {
|
||||
certPath := "testing/data/cert.pem"
|
||||
keyPath := "testing/data/key.pem"
|
||||
caPath := "testing/data/key.pem"
|
||||
endpoint := "https://localhost:4243"
|
||||
_, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
|
||||
if err == nil {
|
||||
t.Errorf("Expected invalid ca at %s", caPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClientInvalidEndpoint(t *testing.T) {
|
||||
cases := []string{
|
||||
"htp://localhost:3243", "http://localhost:a", "localhost:8080",
|
||||
@ -81,6 +136,29 @@ func TestNewClientInvalidEndpoint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTLSClient(t *testing.T) {
|
||||
var tests = []struct {
|
||||
endpoint string
|
||||
expected string
|
||||
}{
|
||||
{"tcp://localhost:2376", "https"},
|
||||
{"tcp://localhost:2375", "https"},
|
||||
{"tcp://localhost:4000", "https"},
|
||||
{"http://localhost:4000", "https"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, err := newTLSClient(tt.endpoint)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
got := client.endpointURL.Scheme
|
||||
if got != tt.expected {
|
||||
t.Errorf("endpointURL.Scheme: Got %s. Want %s.", got, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetURL(t *testing.T) {
|
||||
var tests = []struct {
|
||||
endpoint string
|
||||
@ -129,6 +207,7 @@ func TestQueryString(t *testing.T) {
|
||||
{ListContainersOptions{All: true}, "all=1"},
|
||||
{ListContainersOptions{Before: "something"}, "before=something"},
|
||||
{ListContainersOptions{Before: "something", Since: "other"}, "before=something&since=other"},
|
||||
{ListContainersOptions{Filters: map[string][]string{"status": {"paused", "running"}}}, "filters=%7B%22status%22%3A%5B%22paused%22%2C%22running%22%5D%7D"},
|
||||
{dumb{X: 10, Y: 10.35000}, "x=10&y=10.35"},
|
||||
{dumb{W: v, X: 10, Y: 10.35000}, f32QueryString},
|
||||
{dumb{X: 10, Y: 10.35000, Z: 10}, "x=10&y=10.35&zee=10"},
|
||||
@ -147,7 +226,7 @@ func TestQueryString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewApiVersionFailures(t *testing.T) {
|
||||
func TestNewAPIVersionFailures(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
expectedError string
|
||||
@ -156,17 +235,17 @@ func TestNewApiVersionFailures(t *testing.T) {
|
||||
{"1.0-beta", `Unable to parse version "1.0-beta": "0-beta" is not an integer`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
v, err := NewApiVersion(tt.input)
|
||||
v, err := NewAPIVersion(tt.input)
|
||||
if v != nil {
|
||||
t.Errorf("Expected <nil> version, got %v.", v)
|
||||
}
|
||||
if err.Error() != tt.expectedError {
|
||||
t.Errorf("NewApiVersion(%q): wrong error. Want %q. Got %q", tt.input, tt.expectedError, err.Error())
|
||||
t.Errorf("NewAPIVersion(%q): wrong error. Want %q. Got %q", tt.input, tt.expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiVersions(t *testing.T) {
|
||||
func TestAPIVersions(t *testing.T) {
|
||||
var tests = []struct {
|
||||
a string
|
||||
b string
|
||||
@ -192,8 +271,8 @@ func TestApiVersions(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
a, _ := NewApiVersion(tt.a)
|
||||
b, _ := NewApiVersion(tt.b)
|
||||
a, _ := NewAPIVersion(tt.a)
|
||||
b, _ := NewAPIVersion(tt.b)
|
||||
|
||||
if tt.expectedALessThanB && !a.LessThan(b) {
|
||||
t.Errorf("Expected %#v < %#v", a, b)
|
||||
|
224
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go
generated
vendored
224
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -18,15 +18,17 @@ import (
|
||||
|
||||
// ListContainersOptions specify parameters to the ListContainers function.
|
||||
//
|
||||
// See http://goo.gl/XqtcyU for more details.
|
||||
// See http://goo.gl/6Y4Gz7 for more details.
|
||||
type ListContainersOptions struct {
|
||||
All bool
|
||||
Size bool
|
||||
Limit int
|
||||
Since string
|
||||
Before string
|
||||
All bool
|
||||
Size bool
|
||||
Limit int
|
||||
Since string
|
||||
Before string
|
||||
Filters map[string][]string
|
||||
}
|
||||
|
||||
// APIPort is a type that represents a port mapping returned by the Docker API
|
||||
type APIPort struct {
|
||||
PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty"`
|
||||
PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty"`
|
||||
@ -51,10 +53,10 @@ type APIContainers struct {
|
||||
|
||||
// ListContainers returns a slice of containers matching the given criteria.
|
||||
//
|
||||
// See http://goo.gl/XqtcyU for more details.
|
||||
// See http://goo.gl/6Y4Gz7 for more details.
|
||||
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
|
||||
path := "/containers/json?" + queryString(opts)
|
||||
body, _, err := c.do("GET", path, nil)
|
||||
body, _, err := c.do("GET", path, doOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -88,8 +90,11 @@ func (p Port) Proto() string {
|
||||
type State struct {
|
||||
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
|
||||
Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"`
|
||||
Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty"`
|
||||
OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"`
|
||||
Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"`
|
||||
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
|
||||
Error string `json:"Error,omitempty" yaml:"Error,omitempty"`
|
||||
StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty"`
|
||||
FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty"`
|
||||
}
|
||||
@ -105,13 +110,18 @@ func (s *State) String() string {
|
||||
return fmt.Sprintf("Exit %d", s.ExitCode)
|
||||
}
|
||||
|
||||
// PortBinding represents the host/container port mapping as returned in the
|
||||
// `docker inspect` json
|
||||
type PortBinding struct {
|
||||
HostIp string `json:"HostIP,omitempty" yaml:"HostIP,omitempty"`
|
||||
HostIP string `json:"HostIP,omitempty" yaml:"HostIP,omitempty"`
|
||||
HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty"`
|
||||
}
|
||||
|
||||
// PortMapping represents a deprecated field in the `docker inspect` output,
|
||||
// and its value as found in NetworkSettings should always be nil
|
||||
type PortMapping map[string]string
|
||||
|
||||
// NetworkSettings contains network-related information about a container
|
||||
type NetworkSettings struct {
|
||||
IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"`
|
||||
IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"`
|
||||
@ -121,6 +131,8 @@ type NetworkSettings struct {
|
||||
Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"`
|
||||
}
|
||||
|
||||
// PortMappingAPI translates the port mappings as contained in NetworkSettings
|
||||
// into the format in which they would appear when returned by the API
|
||||
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
||||
var mapping []APIPort
|
||||
for port, bindings := range settings.Ports {
|
||||
@ -139,7 +151,7 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
||||
PrivatePort: int64(p),
|
||||
PublicPort: int64(h),
|
||||
Type: port.Proto(),
|
||||
IP: binding.HostIp,
|
||||
IP: binding.HostIP,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -154,14 +166,17 @@ func parsePort(rawPort string) (int, error) {
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
// Config is the list of configuration options used when creating a container.
|
||||
// Config does not contain the options that are specific to starting a container on a
|
||||
// given host. Those are contained in HostConfig
|
||||
type Config struct {
|
||||
Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"`
|
||||
Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty"`
|
||||
User string `json:"User,omitempty" yaml:"User,omitempty"`
|
||||
Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"`
|
||||
MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
|
||||
CpuShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
|
||||
CpuSet string `json:"CpuSet,omitempty" yaml:"CpuSet,omitempty"`
|
||||
CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
|
||||
CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
|
||||
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
|
||||
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
|
||||
AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
|
||||
@ -172,15 +187,37 @@ type Config struct {
|
||||
StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"`
|
||||
Env []string `json:"Env,omitempty" yaml:"Env,omitempty"`
|
||||
Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
|
||||
Dns []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
|
||||
DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
|
||||
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
|
||||
Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
|
||||
VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
|
||||
WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
|
||||
Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"`
|
||||
NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
|
||||
SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"`
|
||||
OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"`
|
||||
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"`
|
||||
}
|
||||
|
||||
// LogConfig defines the log driver type and the configuration for it.
|
||||
type LogConfig struct {
|
||||
Type string `json:"Type,omitempty" yaml:"Type,omitempty"`
|
||||
Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty"`
|
||||
}
|
||||
|
||||
// SwarmNode containers information about which Swarm node the container is on
|
||||
type SwarmNode struct {
|
||||
ID string `json:"ID,omitempty" yaml:"ID,omitempty"`
|
||||
IP string `json:"IP,omitempty" yaml:"IP,omitempty"`
|
||||
Addr string `json:"Addr,omitempty" yaml:"Addr,omitempty"`
|
||||
Name string `json:"Name,omitempty" yaml:"Name,omitempty"`
|
||||
CPUs int64 `json:"CPUs,omitempty" yaml:"CPUs,omitempty"`
|
||||
Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"`
|
||||
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"`
|
||||
}
|
||||
|
||||
// Container is the type encompasing everything about a container - its config,
|
||||
// hostconfig, etc.
|
||||
type Container struct {
|
||||
ID string `json:"Id" yaml:"Id"`
|
||||
|
||||
@ -193,6 +230,8 @@ type Container struct {
|
||||
State State `json:"State,omitempty" yaml:"State,omitempty"`
|
||||
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
|
||||
|
||||
Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty"`
|
||||
|
||||
NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"`
|
||||
|
||||
SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty"`
|
||||
@ -205,6 +244,28 @@ type Container struct {
|
||||
Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
|
||||
VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"`
|
||||
HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
|
||||
ExecIDs []string `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty"`
|
||||
|
||||
AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty"`
|
||||
}
|
||||
|
||||
// RenameContainerOptions specify parameters to the RenameContainer function.
|
||||
//
|
||||
// See http://goo.gl/L00hoj for more details.
|
||||
type RenameContainerOptions struct {
|
||||
// ID of container to rename
|
||||
ID string `qs:"-"`
|
||||
|
||||
// New name
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
}
|
||||
|
||||
// RenameContainer updates and existing containers name
|
||||
//
|
||||
// See http://goo.gl/L00hoj for more details.
|
||||
func (c *Client) RenameContainer(opts RenameContainerOptions) error {
|
||||
_, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
// InspectContainer returns information about a container by its ID.
|
||||
@ -212,7 +273,7 @@ type Container struct {
|
||||
// See http://goo.gl/CxVuJ5 for more details.
|
||||
func (c *Client) InspectContainer(id string) (*Container, error) {
|
||||
path := "/containers/" + id + "/json"
|
||||
body, status, err := c.do("GET", path, nil)
|
||||
body, status, err := c.do("GET", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: id}
|
||||
}
|
||||
@ -232,7 +293,7 @@ func (c *Client) InspectContainer(id string) (*Container, error) {
|
||||
// See http://goo.gl/QkW9sH for more details.
|
||||
func (c *Client) ContainerChanges(id string) ([]Change, error) {
|
||||
path := "/containers/" + id + "/changes"
|
||||
body, status, err := c.do("GET", path, nil)
|
||||
body, status, err := c.do("GET", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: id}
|
||||
}
|
||||
@ -249,10 +310,11 @@ func (c *Client) ContainerChanges(id string) ([]Change, error) {
|
||||
|
||||
// CreateContainerOptions specify parameters to the CreateContainer function.
|
||||
//
|
||||
// See http://goo.gl/mErxNp for more details.
|
||||
// See http://goo.gl/2xxQQK for more details.
|
||||
type CreateContainerOptions struct {
|
||||
Name string
|
||||
Config *Config `qs:"-"`
|
||||
Name string
|
||||
Config *Config `qs:"-"`
|
||||
HostConfig *HostConfig
|
||||
}
|
||||
|
||||
// CreateContainer creates a new container, returning the container instance,
|
||||
@ -261,7 +323,20 @@ type CreateContainerOptions struct {
|
||||
// See http://goo.gl/mErxNp for more details.
|
||||
func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
|
||||
path := "/containers/create?" + queryString(opts)
|
||||
body, status, err := c.do("POST", path, opts.Config)
|
||||
body, status, err := c.do(
|
||||
"POST",
|
||||
path,
|
||||
doOptions{
|
||||
data: struct {
|
||||
*Config
|
||||
HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
|
||||
}{
|
||||
opts.Config,
|
||||
opts.HostConfig,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if status == http.StatusNotFound {
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
@ -279,6 +354,8 @@ func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error
|
||||
return &container, nil
|
||||
}
|
||||
|
||||
// KeyValuePair is a type for generic key/value pairs as used in the Lxc
|
||||
// configuration
|
||||
type KeyValuePair struct {
|
||||
Key string `json:"Key,omitempty" yaml:"Key,omitempty"`
|
||||
Value string `json:"Value,omitempty" yaml:"Value,omitempty"`
|
||||
@ -315,6 +392,16 @@ func NeverRestart() RestartPolicy {
|
||||
return RestartPolicy{Name: "no"}
|
||||
}
|
||||
|
||||
// Device represents a device mapping between the Docker host and the
|
||||
// container.
|
||||
type Device struct {
|
||||
PathOnHost string `json:"PathOnHost,omitempty" yaml:"PathOnHost,omitempty"`
|
||||
PathInContainer string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty"`
|
||||
CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty"`
|
||||
}
|
||||
|
||||
// HostConfig contains the container options related to starting a container on
|
||||
// a given host
|
||||
type HostConfig struct {
|
||||
Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"`
|
||||
CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"`
|
||||
@ -325,24 +412,29 @@ type HostConfig struct {
|
||||
PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"`
|
||||
Links []string `json:"Links,omitempty" yaml:"Links,omitempty"`
|
||||
PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"`
|
||||
Dns []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
|
||||
DnsSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
|
||||
DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
|
||||
DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
|
||||
ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"`
|
||||
VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
|
||||
NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
|
||||
IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"`
|
||||
PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"`
|
||||
RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`
|
||||
Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"`
|
||||
LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"`
|
||||
ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"`
|
||||
SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"`
|
||||
CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"`
|
||||
}
|
||||
|
||||
// StartContainer starts a container, returning an error in case of failure.
|
||||
//
|
||||
// See http://goo.gl/iM5GYs for more details.
|
||||
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
|
||||
if hostConfig == nil {
|
||||
hostConfig = &HostConfig{}
|
||||
}
|
||||
path := "/containers/" + id + "/start"
|
||||
_, status, err := c.do("POST", path, hostConfig)
|
||||
_, status, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
return &NoSuchContainer{ID: id, Err: err}
|
||||
}
|
||||
if status == http.StatusNotModified {
|
||||
return &ContainerAlreadyRunning{ID: id}
|
||||
@ -359,7 +451,7 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
|
||||
// See http://goo.gl/EbcpXt for more details.
|
||||
func (c *Client) StopContainer(id string, timeout uint) error {
|
||||
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
_, status, err := c.do("POST", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
@ -378,7 +470,7 @@ func (c *Client) StopContainer(id string, timeout uint) error {
|
||||
// See http://goo.gl/VOzR2n for more details.
|
||||
func (c *Client) RestartContainer(id string, timeout uint) error {
|
||||
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
_, status, err := c.do("POST", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
@ -393,7 +485,7 @@ func (c *Client) RestartContainer(id string, timeout uint) error {
|
||||
// See http://goo.gl/AM5t42 for more details.
|
||||
func (c *Client) PauseContainer(id string) error {
|
||||
path := fmt.Sprintf("/containers/%s/pause", id)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
_, status, err := c.do("POST", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
@ -408,7 +500,7 @@ func (c *Client) PauseContainer(id string) error {
|
||||
// See http://goo.gl/eBrNSL for more details.
|
||||
func (c *Client) UnpauseContainer(id string) error {
|
||||
path := fmt.Sprintf("/containers/%s/unpause", id)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
_, status, err := c.do("POST", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
@ -418,6 +510,39 @@ func (c *Client) UnpauseContainer(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TopResult represents the list of processes running in a container, as
|
||||
// returned by /containers/<id>/top.
|
||||
//
|
||||
// See http://goo.gl/qu4gse for more details.
|
||||
type TopResult struct {
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
// TopContainer returns processes running inside a container
|
||||
//
|
||||
// See http://goo.gl/qu4gse for more details.
|
||||
func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
|
||||
var args string
|
||||
var result TopResult
|
||||
if psArgs != "" {
|
||||
args = fmt.Sprintf("?ps_args=%s", psArgs)
|
||||
}
|
||||
path := fmt.Sprintf("/containers/%s/top%s", id, args)
|
||||
body, status, err := c.do("GET", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return result, &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// KillContainerOptions represents the set of options that can be used in a
|
||||
// call to KillContainer.
|
||||
//
|
||||
@ -436,7 +561,7 @@ type KillContainerOptions struct {
|
||||
// See http://goo.gl/TFkECx for more details.
|
||||
func (c *Client) KillContainer(opts KillContainerOptions) error {
|
||||
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
_, status, err := c.do("POST", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
@ -467,7 +592,7 @@ type RemoveContainerOptions struct {
|
||||
// See http://goo.gl/ZB83ji for more details.
|
||||
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
|
||||
path := "/containers/" + opts.ID + "?" + queryString(opts)
|
||||
_, status, err := c.do("DELETE", path, nil)
|
||||
_, status, err := c.do("DELETE", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
@ -496,7 +621,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
url := fmt.Sprintf("/containers/%s/copy", opts.Container)
|
||||
body, status, err := c.do("POST", url, opts)
|
||||
body, status, err := c.do("POST", url, doOptions{data: opts})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
@ -512,7 +637,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
|
||||
//
|
||||
// See http://goo.gl/J88DHU for more details.
|
||||
func (c *Client) WaitContainer(id string) (int, error) {
|
||||
body, status, err := c.do("POST", "/containers/"+id+"/wait", nil)
|
||||
body, status, err := c.do("POST", "/containers/"+id+"/wait", doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return 0, &NoSuchContainer{ID: id}
|
||||
}
|
||||
@ -544,7 +669,7 @@ type CommitContainerOptions struct {
|
||||
// See http://goo.gl/Jn8pe8 for more details.
|
||||
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
|
||||
path := "/commit?" + queryString(opts)
|
||||
body, status, err := c.do("POST", path, opts.Run)
|
||||
body, status, err := c.do("POST", path, doOptions{data: opts.Run})
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
@ -603,7 +728,13 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
path := "/containers/" + opts.Container + "/attach?" + queryString(opts)
|
||||
return c.hijack("POST", path, opts.Success, opts.RawTerminal, opts.InputStream, opts.ErrorStream, opts.OutputStream)
|
||||
return c.hijack("POST", path, hijackOptions{
|
||||
success: opts.Success,
|
||||
setRawTerminal: opts.RawTerminal,
|
||||
in: opts.InputStream,
|
||||
stdout: opts.OutputStream,
|
||||
stderr: opts.ErrorStream,
|
||||
})
|
||||
}
|
||||
|
||||
// LogsOptions represents the set of options used when getting logs from a
|
||||
@ -635,7 +766,11 @@ func (c *Client) Logs(opts LogsOptions) error {
|
||||
opts.Tail = "all"
|
||||
}
|
||||
path := "/containers/" + opts.Container + "/logs?" + queryString(opts)
|
||||
return c.stream("GET", path, opts.RawTerminal, false, nil, nil, opts.OutputStream, opts.ErrorStream)
|
||||
return c.stream("GET", path, streamOptions{
|
||||
setRawTerminal: opts.RawTerminal,
|
||||
stdout: opts.OutputStream,
|
||||
stderr: opts.ErrorStream,
|
||||
})
|
||||
}
|
||||
|
||||
// ResizeContainerTTY resizes the terminal to the given height and width.
|
||||
@ -643,7 +778,7 @@ func (c *Client) ResizeContainerTTY(id string, height, width int) error {
|
||||
params := make(url.Values)
|
||||
params.Set("h", strconv.Itoa(height))
|
||||
params.Set("w", strconv.Itoa(width))
|
||||
_, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), nil)
|
||||
_, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
@ -665,15 +800,22 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error {
|
||||
return &NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
url := fmt.Sprintf("/containers/%s/export", opts.ID)
|
||||
return c.stream("GET", url, true, false, nil, nil, opts.OutputStream, nil)
|
||||
return c.stream("GET", url, streamOptions{
|
||||
setRawTerminal: true,
|
||||
stdout: opts.OutputStream,
|
||||
})
|
||||
}
|
||||
|
||||
// NoSuchContainer is the error returned when a given container does not exist.
|
||||
type NoSuchContainer struct {
|
||||
ID string
|
||||
ID string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (err *NoSuchContainer) Error() string {
|
||||
if err.Err != nil {
|
||||
return err.Err.Error()
|
||||
}
|
||||
return "No such container: " + err.ID
|
||||
}
|
||||
|
||||
|
177
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go
generated
vendored
177
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -7,6 +7,7 @@ package docker
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -102,6 +103,14 @@ func TestListContainersParams(t *testing.T) {
|
||||
ListContainersOptions{All: true, Limit: 10, Since: "adf9983", Before: "abdeef"},
|
||||
map[string][]string{"all": {"1"}, "limit": {"10"}, "since": {"adf9983"}, "before": {"abdeef"}},
|
||||
},
|
||||
{
|
||||
ListContainersOptions{Filters: map[string][]string{"status": {"paused", "running"}}},
|
||||
map[string][]string{"filters": {"{\"status\":[\"paused\",\"running\"]}"}},
|
||||
},
|
||||
{
|
||||
ListContainersOptions{All: true, Filters: map[string][]string{"exited": {"0"}, "status": {"exited"}}},
|
||||
map[string][]string{"all": {"1"}, "filters": {"{\"exited\":[\"0\"],\"status\":[\"exited\"]}"}},
|
||||
},
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: "[]", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
@ -146,6 +155,7 @@ func TestListContainersFailure(t *testing.T) {
|
||||
func TestInspectContainer(t *testing.T) {
|
||||
jsonContainer := `{
|
||||
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
|
||||
"AppArmorProfile": "Profile",
|
||||
"Created": "2013-05-07T14:51:42.087658+02:00",
|
||||
"Path": "date",
|
||||
"Args": [],
|
||||
@ -167,7 +177,10 @@ func TestInspectContainer(t *testing.T) {
|
||||
],
|
||||
"Image": "base",
|
||||
"Volumes": {},
|
||||
"VolumesFrom": ""
|
||||
"VolumesFrom": "",
|
||||
"SecurityOpt": [
|
||||
"label:user:USER"
|
||||
]
|
||||
},
|
||||
"State": {
|
||||
"Running": false,
|
||||
@ -176,6 +189,21 @@ func TestInspectContainer(t *testing.T) {
|
||||
"StartedAt": "2013-05-07T14:51:42.087658+02:00",
|
||||
"Ghost": false
|
||||
},
|
||||
"Node": {
|
||||
"ID": "4I4E:QR4I:Z733:QEZK:5X44:Q4T7:W2DD:JRDY:KB2O:PODO:Z5SR:XRB6",
|
||||
"IP": "192.168.99.105",
|
||||
"Addra": "192.168.99.105:2376",
|
||||
"Name": "node-01",
|
||||
"Cpus": 4,
|
||||
"Memory": 1048436736,
|
||||
"Labels": {
|
||||
"executiondriver": "native-0.2",
|
||||
"kernelversion": "3.18.5-tinycore64",
|
||||
"operatingsystem": "Boot2Docker 1.5.0 (TCL 5.4); master : a66bce5 - Tue Feb 10 23:31:27 UTC 2015",
|
||||
"provider": "virtualbox",
|
||||
"storagedriver": "aufs"
|
||||
}
|
||||
},
|
||||
"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"NetworkSettings": {
|
||||
"IpAddress": "",
|
||||
@ -201,7 +229,8 @@ func TestInspectContainer(t *testing.T) {
|
||||
]
|
||||
},
|
||||
"Links": null,
|
||||
"PublishAllPorts": false
|
||||
"PublishAllPorts": false,
|
||||
"CgroupParent": "/mesos"
|
||||
}
|
||||
}`
|
||||
var expected Container
|
||||
@ -440,6 +469,27 @@ func TestCreateContainerImageNotFound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerWithHostConfig(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "{}", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
config := Config{}
|
||||
hostConfig := HostConfig{PublishAllPorts: true}
|
||||
opts := CreateContainerOptions{Name: "TestCreateContainerWithHostConfig", Config: &config, HostConfig: &hostConfig}
|
||||
_, err := client.CreateContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
var gotBody map[string]interface{}
|
||||
err = json.NewDecoder(req.Body).Decode(&gotBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := gotBody["HostConfig"]; !ok {
|
||||
t.Errorf("CreateContainer: wrong body. HostConfig was not serialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
@ -482,12 +532,17 @@ func TestStartContainerNilHostConfig(t *testing.T) {
|
||||
if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType {
|
||||
t.Errorf("StartContainer(%q): Wrong content-type in request. Want %q. Got %q.", id, expectedContentType, contentType)
|
||||
}
|
||||
var buf [4]byte
|
||||
req.Body.Read(buf[:])
|
||||
if string(buf[:]) != "null" {
|
||||
t.Errorf("Startcontainer(%q): Wrong body. Want null. Got %s", buf[:])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.StartContainer("a2344", &HostConfig{})
|
||||
expected := &NoSuchContainer{ID: "a2344"}
|
||||
expected := &NoSuchContainer{ID: "a2344", Err: err.(*NoSuchContainer).Err}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("StartContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
@ -1251,13 +1306,21 @@ func TestLogsNoContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNoSuchContainerError(t *testing.T) {
|
||||
var err error = &NoSuchContainer{ID: "i345"}
|
||||
var err = &NoSuchContainer{ID: "i345"}
|
||||
expected := "No such container: i345"
|
||||
if got := err.Error(); got != expected {
|
||||
t.Errorf("NoSuchContainer: wrong message. Want %q. Got %q.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSuchContainerErrorMessage(t *testing.T) {
|
||||
var err = &NoSuchContainer{ID: "i345", Err: errors.New("some advanced error info")}
|
||||
expected := "some advanced error info"
|
||||
if got := err.Error(); got != expected {
|
||||
t.Errorf("NoSuchContainer: wrong message. Want %q. Got %q.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportContainer(t *testing.T) {
|
||||
content := "exported container tar content"
|
||||
out := stdoutMock{bytes.NewBufferString(content)}
|
||||
@ -1282,7 +1345,7 @@ func TestExportContainerViaUnixSocket(t *testing.T) {
|
||||
tempSocket := tempfile("export_socket")
|
||||
defer os.Remove(tempSocket)
|
||||
endpoint := "unix://" + tempSocket
|
||||
u, _ := parseEndpoint(endpoint)
|
||||
u, _ := parseEndpoint(endpoint, false)
|
||||
client := Client{
|
||||
HTTPClient: http.DefaultClient,
|
||||
endpoint: endpoint,
|
||||
@ -1414,3 +1477,105 @@ func TestNeverRestart(t *testing.T) {
|
||||
t.Errorf("NeverRestart(): wrong MaximumRetryCount. Want 0. Got %d", policy.MaximumRetryCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopContainer(t *testing.T) {
|
||||
jsonTop := `{
|
||||
"Processes": [
|
||||
[
|
||||
"ubuntu",
|
||||
"3087",
|
||||
"815",
|
||||
"0",
|
||||
"01:44",
|
||||
"?",
|
||||
"00:00:00",
|
||||
"cmd1"
|
||||
],
|
||||
[
|
||||
"root",
|
||||
"3158",
|
||||
"3087",
|
||||
"0",
|
||||
"01:44",
|
||||
"?",
|
||||
"00:00:01",
|
||||
"cmd2"
|
||||
]
|
||||
],
|
||||
"Titles": [
|
||||
"UID",
|
||||
"PID",
|
||||
"PPID",
|
||||
"C",
|
||||
"STIME",
|
||||
"TTY",
|
||||
"TIME",
|
||||
"CMD"
|
||||
]
|
||||
}`
|
||||
var expected TopResult
|
||||
err := json.Unmarshal([]byte(jsonTop), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id := "4fa6e0f0"
|
||||
fakeRT := &FakeRoundTripper{message: jsonTop, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
processes, err := client.TopContainer(id, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(processes, expected) {
|
||||
t.Errorf("TopContainer: Expected %#v. Got %#v.", expected, processes)
|
||||
}
|
||||
if len(processes.Processes) != 2 || len(processes.Processes[0]) != 8 ||
|
||||
processes.Processes[0][7] != "cmd1" {
|
||||
t.Errorf("TopContainer: Process list to include cmd1. Got %#v.", expected, processes)
|
||||
}
|
||||
expectedURI := "/containers/" + id + "/top"
|
||||
if !strings.HasSuffix(fakeRT.requests[0].URL.String(), expectedURI) {
|
||||
t.Errorf("TopContainer: Expected URI to have %q. Got %q.", expectedURI, fakeRT.requests[0].URL.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
_, err := client.TopContainer("abef348", "")
|
||||
expected := &NoSuchContainer{ID: "abef348"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("StopContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopContainerWithPsArgs(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "no such container", status: http.StatusNotFound}
|
||||
client := newTestClient(fakeRT)
|
||||
client.TopContainer("abef348", "aux")
|
||||
expectedURI := "/containers/abef348/top?ps_args=aux"
|
||||
if !strings.HasSuffix(fakeRT.requests[0].URL.String(), expectedURI) {
|
||||
t.Errorf("TopContainer: Expected URI to have %q. Got %q.", expectedURI, fakeRT.requests[0].URL.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := RenameContainerOptions{ID: "something_old", Name: "something_new"}
|
||||
err := client.RenameContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("RenameContainer: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/something_old/rename?name=something_new"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("RenameContainer: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
|
||||
}
|
||||
expectedValues := expectedURL.Query()["name"]
|
||||
actualValues := req.URL.Query()["name"]
|
||||
if len(actualValues) != 1 || expectedValues[0] != actualValues[0] {
|
||||
t.Errorf("RenameContainer: Wrong params in request. Want %q. Got %q.", expectedValues, actualValues)
|
||||
}
|
||||
}
|
||||
|
47
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go
generated
vendored
47
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go
generated
vendored
@ -1,10 +1,11 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -49,6 +50,11 @@ var (
|
||||
// ErrListenerAlreadyExists is the error returned when the listerner already
|
||||
// exists.
|
||||
ErrListenerAlreadyExists = errors.New("listener already exists for docker events")
|
||||
|
||||
// EOFEvent is sent when the event listener receives an EOF error.
|
||||
EOFEvent = &APIEvents{
|
||||
Status: "EOF",
|
||||
}
|
||||
)
|
||||
|
||||
// AddEventListener adds a new listener to container events in the Docker API.
|
||||
@ -111,6 +117,16 @@ func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvent
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) closeListeners() {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
for _, l := range eventState.listeners {
|
||||
close(l)
|
||||
eventState.Add(-1)
|
||||
}
|
||||
eventState.listeners = nil
|
||||
}
|
||||
|
||||
func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
|
||||
for _, b := range *list {
|
||||
if b == a {
|
||||
@ -152,7 +168,7 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if err = eventState.connectWithRetry(c); err != nil {
|
||||
eventState.terminate(err)
|
||||
eventState.terminate()
|
||||
}
|
||||
for eventState.isEnabled() {
|
||||
timeout := time.After(100 * time.Millisecond)
|
||||
@ -161,11 +177,16 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if ev == EOFEvent {
|
||||
eventState.closeListeners()
|
||||
eventState.terminate()
|
||||
return
|
||||
}
|
||||
eventState.updateLastSeen(ev)
|
||||
go eventState.sendEvent(ev)
|
||||
go eventState.updateLastSeen(ev)
|
||||
case err = <-eventState.errC:
|
||||
if err == ErrNoListeners {
|
||||
eventState.terminate(nil)
|
||||
eventState.terminate()
|
||||
return
|
||||
} else if err != nil {
|
||||
defer func() { go eventState.monitorEvents(c) }()
|
||||
@ -206,7 +227,7 @@ func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
|
||||
eventState.Add(1)
|
||||
defer eventState.Done()
|
||||
if eventState.isEnabled() {
|
||||
if eventState.noListeners() {
|
||||
if len(eventState.listeners) == 0 {
|
||||
eventState.errC <- ErrNoListeners
|
||||
return
|
||||
}
|
||||
@ -225,7 +246,7 @@ func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
|
||||
}
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) terminate(err error) {
|
||||
func (eventState *eventMonitoringState) terminate() {
|
||||
eventState.disableEventMonitoring()
|
||||
}
|
||||
|
||||
@ -240,7 +261,13 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan
|
||||
protocol = "tcp"
|
||||
address = c.endpointURL.Host
|
||||
}
|
||||
dial, err := net.Dial(protocol, address)
|
||||
var dial net.Conn
|
||||
var err error
|
||||
if c.TLSConfig == nil {
|
||||
dial, err = net.Dial(protocol, address)
|
||||
} else {
|
||||
dial, err = tls.Dial(protocol, address, c.TLSConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -261,6 +288,10 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan
|
||||
var event APIEvents
|
||||
if err = decoder.Decode(&event); err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
if c.eventMonitor.isEnabled() {
|
||||
// Signal that we're exiting.
|
||||
eventChan <- EOFEvent
|
||||
}
|
||||
break
|
||||
}
|
||||
errChan <- err
|
||||
@ -271,7 +302,7 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan
|
||||
if !c.eventMonitor.isEnabled() {
|
||||
return
|
||||
}
|
||||
c.eventMonitor.C <- &event
|
||||
eventChan <- &event
|
||||
}
|
||||
}(res, conn)
|
||||
return nil
|
||||
|
44
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event_test.go
generated
vendored
44
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event_test.go
generated
vendored
@ -6,7 +6,10 @@ package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@ -15,6 +18,39 @@ import (
|
||||
)
|
||||
|
||||
func TestEventListeners(t *testing.T) {
|
||||
testEventListeners("TestEventListeners", t, httptest.NewServer, NewClient)
|
||||
}
|
||||
|
||||
func TestTLSEventListeners(t *testing.T) {
|
||||
testEventListeners("TestTLSEventListeners", t, func(handler http.Handler) *httptest.Server {
|
||||
server := httptest.NewUnstartedServer(handler)
|
||||
|
||||
cert, err := tls.LoadX509KeyPair("testing/data/server.pem", "testing/data/serverkey.pem")
|
||||
if err != nil {
|
||||
t.Fatalf("Error loading server key pair: %s", err)
|
||||
}
|
||||
|
||||
caCert, err := ioutil.ReadFile("testing/data/ca.pem")
|
||||
if err != nil {
|
||||
t.Fatalf("Error loading ca certificate: %s", err)
|
||||
}
|
||||
caPool := x509.NewCertPool()
|
||||
if !caPool.AppendCertsFromPEM(caCert) {
|
||||
t.Fatalf("Could not add ca certificate")
|
||||
}
|
||||
|
||||
server.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caPool,
|
||||
}
|
||||
server.StartTLS()
|
||||
return server
|
||||
}, func(url string) (*Client, error) {
|
||||
return NewTLSClient(url, "testing/data/cert.pem", "testing/data/key.pem", "testing/data/ca.pem")
|
||||
})
|
||||
}
|
||||
|
||||
func testEventListeners(testName string, t *testing.T, buildServer func(http.Handler) *httptest.Server, buildClient func(string) (*Client, error)) {
|
||||
response := `{"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
|
||||
{"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
|
||||
{"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966}
|
||||
@ -22,7 +58,7 @@ func TestEventListeners(t *testing.T) {
|
||||
`
|
||||
|
||||
var req http.Request
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
server := buildServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rsc := bufio.NewScanner(strings.NewReader(response))
|
||||
for rsc.Scan() {
|
||||
w.Write([]byte(rsc.Text()))
|
||||
@ -33,7 +69,7 @@ func TestEventListeners(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
client, err := buildClient(server.URL)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create client: %s", err)
|
||||
}
|
||||
@ -53,7 +89,7 @@ func TestEventListeners(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case msg := <-listener:
|
||||
t.Logf("Recieved: %s", *msg)
|
||||
t.Logf("Received: %s", *msg)
|
||||
count++
|
||||
err = checkEvent(count, msg)
|
||||
if err != nil {
|
||||
@ -63,7 +99,7 @@ func TestEventListeners(t *testing.T) {
|
||||
return
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatal("TestAddEventListener timed out waiting on events")
|
||||
t.Fatalf("%s timed out waiting on events", testName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
184
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec.go
generated
vendored
Normal file
184
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Docs can currently be found at https://github.com/docker/docker/blob/master/docs/sources/reference/api/docker_remote_api_v1.15.md#exec-create
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// CreateExecOptions specify parameters to the CreateExecContainer function.
|
||||
//
|
||||
// See http://goo.gl/8izrzI for more details
|
||||
type CreateExecOptions struct {
|
||||
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
|
||||
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
|
||||
AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
|
||||
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"`
|
||||
Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
|
||||
Container string `json:"Container,omitempty" yaml:"Container,omitempty"`
|
||||
}
|
||||
|
||||
// StartExecOptions specify parameters to the StartExecContainer function.
|
||||
//
|
||||
// See http://goo.gl/JW8Lxl for more details
|
||||
type StartExecOptions struct {
|
||||
Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty"`
|
||||
|
||||
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"`
|
||||
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
ErrorStream io.Writer `qs:"-"`
|
||||
|
||||
// Use raw terminal? Usually true when the container contains a TTY.
|
||||
RawTerminal bool `qs:"-"`
|
||||
|
||||
// If set, after a successful connect, a sentinel will be sent and then the
|
||||
// client will block on receive before continuing.
|
||||
//
|
||||
// It must be an unbuffered channel. Using a buffered channel can lead
|
||||
// to unexpected behavior.
|
||||
Success chan struct{} `json:"-"`
|
||||
}
|
||||
|
||||
// Exec is the type representing a `docker exec` instance and containing the
|
||||
// instance ID
|
||||
type Exec struct {
|
||||
ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
|
||||
}
|
||||
|
||||
// ExecProcessConfig is a type describing the command associated to a Exec
|
||||
// instance. It's used in the ExecInspect type.
|
||||
//
|
||||
// See http://goo.gl/ypQULN for more details
|
||||
type ExecProcessConfig struct {
|
||||
Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
|
||||
User string `json:"user,omitempty" yaml:"user,omitempty"`
|
||||
Tty bool `json:"tty,omitempty" yaml:"tty,omitempty"`
|
||||
EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
|
||||
Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
// ExecInspect is a type with details about a exec instance, including the
|
||||
// exit code if the command has finished running. It's returned by a api
|
||||
// call to /exec/(id)/json
|
||||
//
|
||||
// See http://goo.gl/ypQULN for more details
|
||||
type ExecInspect struct {
|
||||
ID string `json:"ID,omitempty" yaml:"ID,omitempty"`
|
||||
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
|
||||
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
|
||||
OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
|
||||
OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty"`
|
||||
OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty"`
|
||||
ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty"`
|
||||
Container Container `json:"Container,omitempty" yaml:"Container,omitempty"`
|
||||
}
|
||||
|
||||
// CreateExec sets up an exec instance in a running container `id`, returning the exec
|
||||
// instance, or an error in case of failure.
|
||||
//
|
||||
// See http://goo.gl/8izrzI for more details
|
||||
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
|
||||
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
|
||||
body, status, err := c.do("POST", path, doOptions{data: opts})
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var exec Exec
|
||||
err = json.Unmarshal(body, &exec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &exec, nil
|
||||
}
|
||||
|
||||
// StartExec starts a previously set up exec instance id. If opts.Detach is
|
||||
// true, it returns after starting the exec command. Otherwise, it sets up an
|
||||
// interactive session with the exec command.
|
||||
//
|
||||
// See http://goo.gl/JW8Lxl for more details
|
||||
func (c *Client) StartExec(id string, opts StartExecOptions) error {
|
||||
if id == "" {
|
||||
return &NoSuchExec{ID: id}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/exec/%s/start", id)
|
||||
|
||||
if opts.Detach {
|
||||
_, status, err := c.do("POST", path, doOptions{data: opts})
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchExec{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.hijack("POST", path, hijackOptions{
|
||||
success: opts.Success,
|
||||
setRawTerminal: opts.RawTerminal,
|
||||
in: opts.InputStream,
|
||||
stdout: opts.OutputStream,
|
||||
stderr: opts.ErrorStream,
|
||||
data: opts,
|
||||
})
|
||||
}
|
||||
|
||||
// ResizeExecTTY resizes the tty session used by the exec command id. This API
|
||||
// is valid only if Tty was specified as part of creating and starting the exec
|
||||
// command.
|
||||
//
|
||||
// See http://goo.gl/YDSx1f for more details
|
||||
func (c *Client) ResizeExecTTY(id string, height, width int) error {
|
||||
params := make(url.Values)
|
||||
params.Set("h", strconv.Itoa(height))
|
||||
params.Set("w", strconv.Itoa(width))
|
||||
|
||||
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
|
||||
_, _, err := c.do("POST", path, doOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
// InspectExec returns low-level information about the exec command id.
|
||||
//
|
||||
// See http://goo.gl/ypQULN for more details
|
||||
func (c *Client) InspectExec(id string) (*ExecInspect, error) {
|
||||
path := fmt.Sprintf("/exec/%s/json", id)
|
||||
body, status, err := c.do("GET", path, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchExec{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var exec ExecInspect
|
||||
err = json.Unmarshal(body, &exec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &exec, nil
|
||||
}
|
||||
|
||||
// NoSuchExec is the error returned when a given exec instance does not exist.
|
||||
type NoSuchExec struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (err *NoSuchExec) Error() string {
|
||||
return "No such exec instance: " + err.ID
|
||||
}
|
259
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec_test.go
generated
vendored
Normal file
259
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec_test.go
generated
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExecCreate(t *testing.T) {
|
||||
jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}`
|
||||
var expected struct{ ID string }
|
||||
err := json.Unmarshal([]byte(jsonContainer), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
config := CreateExecOptions{
|
||||
Container: "test",
|
||||
AttachStdin: true,
|
||||
AttachStdout: true,
|
||||
AttachStderr: false,
|
||||
Tty: false,
|
||||
Cmd: []string{"touch", "/tmp/file"},
|
||||
}
|
||||
execObj, err := client.CreateExec(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
if execObj.ID != expectedID {
|
||||
t.Errorf("ExecCreate: wrong ID. Want %q. Got %q.", expectedID, execObj.ID)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("ExecCreate: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/test/exec"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
|
||||
}
|
||||
var gotBody struct{ ID string }
|
||||
err = json.NewDecoder(req.Body).Decode(&gotBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecStartDetached(t *testing.T) {
|
||||
execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
fakeRT := &FakeRoundTripper{status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
config := StartExecOptions{
|
||||
Detach: true,
|
||||
}
|
||||
err := client.StartExec(execID, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("ExecStart: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/exec/" + execID + "/start"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
|
||||
}
|
||||
t.Log(req.Body)
|
||||
var gotBody struct{ Detach bool }
|
||||
err = json.NewDecoder(req.Body).Decode(&gotBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !gotBody.Detach {
|
||||
t.Fatal("Expected Detach in StartExecOptions to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecStartAndAttach(t *testing.T) {
|
||||
var reader = strings.NewReader("send value")
|
||||
var req http.Request
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5})
|
||||
w.Write([]byte("hello"))
|
||||
req = *r
|
||||
}))
|
||||
defer server.Close()
|
||||
client, _ := NewClient(server.URL)
|
||||
client.SkipServerVersionCheck = true
|
||||
var stdout, stderr bytes.Buffer
|
||||
success := make(chan struct{})
|
||||
execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
opts := StartExecOptions{
|
||||
OutputStream: &stdout,
|
||||
ErrorStream: &stderr,
|
||||
InputStream: reader,
|
||||
RawTerminal: true,
|
||||
Success: success,
|
||||
}
|
||||
go client.StartExec(execID, opts)
|
||||
<-success
|
||||
}
|
||||
|
||||
func TestExecResize(t *testing.T) {
|
||||
execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
fakeRT := &FakeRoundTripper{status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
err := client.ResizeExecTTY(execID, 10, 20)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("ExecStart: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/exec/" + execID + "/resize?h=10&w=20"))
|
||||
if gotPath := req.URL.RequestURI(); gotPath != expectedURL.RequestURI() {
|
||||
t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecInspect(t *testing.T) {
|
||||
jsonExec := `{
|
||||
"ID": "32adfeeec34250f9530ce1dafd40c6233832315e065ea6b362d745e2f63cde0e",
|
||||
"Running": true,
|
||||
"ExitCode": 0,
|
||||
"ProcessConfig": {
|
||||
"privileged": false,
|
||||
"user": "",
|
||||
"tty": true,
|
||||
"entrypoint": "bash",
|
||||
"arguments": []
|
||||
},
|
||||
"OpenStdin": true,
|
||||
"OpenStderr": true,
|
||||
"OpenStdout": true,
|
||||
"Container": {
|
||||
"State": {
|
||||
"Running": true,
|
||||
"Paused": false,
|
||||
"Restarting": false,
|
||||
"OOMKilled": false,
|
||||
"Pid": 29392,
|
||||
"ExitCode": 0,
|
||||
"Error": "",
|
||||
"StartedAt": "2015-01-21T17:08:59.634662178Z",
|
||||
"FinishedAt": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"ID": "922cd0568714763dc725b24b7c9801016b2a3de68e2a1dc989bf5abf07740521",
|
||||
"Created": "2015-01-21T17:08:59.46407212Z",
|
||||
"Path": "/bin/bash",
|
||||
"Args": [
|
||||
"-lc",
|
||||
"tsuru_unit_agent http://192.168.50.4:8080 689b30e0ab3adce374346de2e72512138e0e8b75 gtest /var/lib/tsuru/start && tail -f /dev/null"
|
||||
],
|
||||
"Config": {
|
||||
"Hostname": "922cd0568714",
|
||||
"Domainname": "",
|
||||
"User": "ubuntu",
|
||||
"Memory": 0,
|
||||
"MemorySwap": 0,
|
||||
"CpuShares": 100,
|
||||
"Cpuset": "",
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"AttachStderr": false,
|
||||
"PortSpecs": null,
|
||||
"ExposedPorts": {
|
||||
"8888/tcp": {}
|
||||
},
|
||||
"Tty": false,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/bash",
|
||||
"-lc",
|
||||
"tsuru_unit_agent http://192.168.50.4:8080 689b30e0ab3adce374346de2e72512138e0e8b75 gtest /var/lib/tsuru/start && tail -f /dev/null"
|
||||
],
|
||||
"Image": "tsuru/app-gtest",
|
||||
"Volumes": null,
|
||||
"WorkingDir": "",
|
||||
"Entrypoint": null,
|
||||
"NetworkDisabled": false,
|
||||
"MacAddress": "",
|
||||
"OnBuild": null
|
||||
},
|
||||
"Image": "a88060b8b54fde0f7168c86742d0ce83b80f3f10925d85c98fdad9ed00bef544",
|
||||
"NetworkSettings": {
|
||||
"IPAddress": "172.17.0.8",
|
||||
"IPPrefixLen": 16,
|
||||
"MacAddress": "02:42:ac:11:00:08",
|
||||
"LinkLocalIPv6Address": "fe80::42:acff:fe11:8",
|
||||
"LinkLocalIPv6PrefixLen": 64,
|
||||
"GlobalIPv6Address": "",
|
||||
"GlobalIPv6PrefixLen": 0,
|
||||
"Gateway": "172.17.42.1",
|
||||
"IPv6Gateway": "",
|
||||
"Bridge": "docker0",
|
||||
"PortMapping": null,
|
||||
"Ports": {
|
||||
"8888/tcp": [
|
||||
{
|
||||
"HostIp": "0.0.0.0",
|
||||
"HostPort": "49156"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ResolvConfPath": "/var/lib/docker/containers/922cd0568714763dc725b24b7c9801016b2a3de68e2a1dc989bf5abf07740521/resolv.conf",
|
||||
"HostnamePath": "/var/lib/docker/containers/922cd0568714763dc725b24b7c9801016b2a3de68e2a1dc989bf5abf07740521/hostname",
|
||||
"HostsPath": "/var/lib/docker/containers/922cd0568714763dc725b24b7c9801016b2a3de68e2a1dc989bf5abf07740521/hosts",
|
||||
"Name": "/c7e43b72288ee9d0270a",
|
||||
"Driver": "aufs",
|
||||
"ExecDriver": "native-0.2",
|
||||
"MountLabel": "",
|
||||
"ProcessLabel": "",
|
||||
"AppArmorProfile": "",
|
||||
"RestartCount": 0,
|
||||
"UpdateDns": false,
|
||||
"Volumes": {},
|
||||
"VolumesRW": {}
|
||||
}
|
||||
}`
|
||||
var expected ExecInspect
|
||||
err := json.Unmarshal([]byte(jsonExec), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: jsonExec, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
expectedID := "32adfeeec34250f9530ce1dafd40c6233832315e065ea6b362d745e2f63cde0e"
|
||||
execObj, err := client.InspectExec(expectedID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(*execObj, expected) {
|
||||
t.Errorf("ExecInspect: Expected %#v. Got %#v.", expected, *execObj)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("ExecInspect: wrong HTTP method. Want %q. Got %q.", "GET", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/exec/" + expectedID + "/json"))
|
||||
if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("ExecInspect: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
237
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go
generated
vendored
237
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -25,9 +25,11 @@ type APIImages struct {
|
||||
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
|
||||
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
|
||||
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"`
|
||||
ParentId string `json:"ParentId,omitempty" yaml:"ParentId,omitempty"`
|
||||
ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty"`
|
||||
RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty"`
|
||||
}
|
||||
|
||||
// Image is the type representing a docker image and its various properties
|
||||
type Image struct {
|
||||
ID string `json:"Id" yaml:"Id"`
|
||||
Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty"`
|
||||
@ -52,6 +54,8 @@ type ImageHistory struct {
|
||||
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
|
||||
}
|
||||
|
||||
// ImagePre012 serves the same purpose as the Image type except that it is for
|
||||
// earlier versions of the Docker API (pre-012 to be specific)
|
||||
type ImagePre012 struct {
|
||||
ID string `json:"id"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
@ -66,6 +70,15 @@ type ImagePre012 struct {
|
||||
Size int64 `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// ListImagesOptions specify parameters to the ListImages function.
|
||||
//
|
||||
// See http://goo.gl/HRVN1Z for more details.
|
||||
type ListImagesOptions struct {
|
||||
All bool
|
||||
Filters map[string][]string
|
||||
Digests bool
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNoSuchImage is the error returned when the image does not exist.
|
||||
ErrNoSuchImage = errors.New("no such image")
|
||||
@ -77,19 +90,23 @@ var (
|
||||
// ErrMissingOutputStream is the error returned when no output stream
|
||||
// is provided to some calls, like BuildImage.
|
||||
ErrMissingOutputStream = errors.New("missing output stream")
|
||||
|
||||
// ErrMultipleContexts is the error returned when both a ContextDir and
|
||||
// InputStream are provided in BuildImageOptions
|
||||
ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream")
|
||||
|
||||
// ErrMustSpecifyNames is the error rreturned when the Names field on
|
||||
// ExportImagesOptions is nil or empty
|
||||
ErrMustSpecifyNames = errors.New("must specify at least one name to export")
|
||||
)
|
||||
|
||||
// ListImages returns the list of available images in the server.
|
||||
//
|
||||
// See http://goo.gl/VmcR6v for more details.
|
||||
func (c *Client) ListImages(all bool) ([]APIImages, error) {
|
||||
path := "/images/json?all="
|
||||
if all {
|
||||
path += "1"
|
||||
} else {
|
||||
path += "0"
|
||||
}
|
||||
body, _, err := c.do("GET", path, nil)
|
||||
// See http://goo.gl/HRVN1Z for more details.
|
||||
func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
|
||||
// TODO(pedge): what happens if we specify the digest parameter when using API Version <1.18?
|
||||
path := "/images/json?" + queryString(opts)
|
||||
body, _, err := c.do("GET", path, doOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -105,7 +122,7 @@ func (c *Client) ListImages(all bool) ([]APIImages, error) {
|
||||
//
|
||||
// See http://goo.gl/2oJmNs for more details.
|
||||
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
|
||||
body, status, err := c.do("GET", "/images/"+name+"/history", nil)
|
||||
body, status, err := c.do("GET", "/images/"+name+"/history", doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
@ -124,7 +141,29 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
|
||||
//
|
||||
// See http://goo.gl/znj0wM for more details.
|
||||
func (c *Client) RemoveImage(name string) error {
|
||||
_, status, err := c.do("DELETE", "/images/"+name, nil)
|
||||
_, status, err := c.do("DELETE", "/images/"+name, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveImageOptions present the set of options available for removing an image
|
||||
// from a registry.
|
||||
//
|
||||
// See http://goo.gl/6V48bF for more details.
|
||||
type RemoveImageOptions struct {
|
||||
Force bool `qs:"force"`
|
||||
NoPrune bool `qs:"noprune"`
|
||||
}
|
||||
|
||||
// RemoveImageExtended removes an image by its name or ID.
|
||||
// Extra params can be passed, see RemoveImageOptions
|
||||
//
|
||||
// See http://goo.gl/znj0wM for more details.
|
||||
func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error {
|
||||
uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts))
|
||||
_, status, err := c.do("DELETE", uri, doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
@ -135,7 +174,7 @@ func (c *Client) RemoveImage(name string) error {
|
||||
//
|
||||
// See http://goo.gl/Q112NY for more details.
|
||||
func (c *Client) InspectImage(name string) (*Image, error) {
|
||||
body, status, err := c.do("GET", "/images/"+name+"/json", nil)
|
||||
body, status, err := c.do("GET", "/images/"+name+"/json", doOptions{})
|
||||
if status == http.StatusNotFound {
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
@ -146,7 +185,7 @@ func (c *Client) InspectImage(name string) (*Image, error) {
|
||||
var image Image
|
||||
|
||||
// if the caller elected to skip checking the server's version, assume it's the latest
|
||||
if c.SkipServerVersionCheck || c.expectedApiVersion.GreaterThanOrEqualTo(apiVersion_1_12) {
|
||||
if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) {
|
||||
err = json.Unmarshal(body, &image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -187,15 +226,8 @@ type PushImageOptions struct {
|
||||
// Registry server to push the image
|
||||
Registry string
|
||||
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
}
|
||||
|
||||
// AuthConfiguration represents authentication options to use in the PushImage
|
||||
// method. It represents the authentication in the Docker index server.
|
||||
type AuthConfiguration struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
RawJSONStream bool `qs:"-"`
|
||||
}
|
||||
|
||||
// PushImage pushes an image to a remote registry, logging progress to w.
|
||||
@ -211,13 +243,12 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
|
||||
name := opts.Name
|
||||
opts.Name = ""
|
||||
path := "/images/" + name + "/push?" + queryString(&opts)
|
||||
var headers = make(map[string]string)
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(auth)
|
||||
|
||||
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
|
||||
|
||||
return c.stream("POST", path, true, false, headers, nil, opts.OutputStream, nil)
|
||||
return c.stream("POST", path, streamOptions{
|
||||
setRawTerminal: true,
|
||||
rawJSONStream: opts.RawJSONStream,
|
||||
headers: headersWithAuth(auth),
|
||||
stdout: opts.OutputStream,
|
||||
})
|
||||
}
|
||||
|
||||
// PullImageOptions present the set of options available for pulling an image
|
||||
@ -232,7 +263,7 @@ type PullImageOptions struct {
|
||||
RawJSONStream bool `qs:"-"`
|
||||
}
|
||||
|
||||
// PullImage pulls an image from a remote registry, logging progress to w.
|
||||
// PullImage pulls an image from a remote registry, logging progress to opts.OutputStream.
|
||||
//
|
||||
// See http://goo.gl/ACyYNS for more details.
|
||||
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
|
||||
@ -240,17 +271,19 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
|
||||
var headers = make(map[string]string)
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(auth)
|
||||
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
|
||||
|
||||
headers := headersWithAuth(auth)
|
||||
return c.createImage(queryString(&opts), headers, nil, opts.OutputStream, opts.RawJSONStream)
|
||||
}
|
||||
|
||||
func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool) error {
|
||||
path := "/images/create?" + qs
|
||||
return c.stream("POST", path, true, rawJSONStream, headers, in, w, nil)
|
||||
return c.stream("POST", path, streamOptions{
|
||||
setRawTerminal: true,
|
||||
rawJSONStream: rawJSONStream,
|
||||
headers: headers,
|
||||
in: in,
|
||||
stdout: w,
|
||||
})
|
||||
}
|
||||
|
||||
// LoadImageOptions represents the options for LoadImage Docker API Call
|
||||
@ -264,7 +297,10 @@ type LoadImageOptions struct {
|
||||
//
|
||||
// See http://goo.gl/Y8NNCq for more details.
|
||||
func (c *Client) LoadImage(opts LoadImageOptions) error {
|
||||
return c.stream("POST", "/images/load", true, false, nil, opts.InputStream, nil, nil)
|
||||
return c.stream("POST", "/images/load", streamOptions{
|
||||
setRawTerminal: true,
|
||||
in: opts.InputStream,
|
||||
})
|
||||
}
|
||||
|
||||
// ExportImageOptions represent the options for ExportImage Docker API call
|
||||
@ -279,7 +315,31 @@ type ExportImageOptions struct {
|
||||
//
|
||||
// See http://goo.gl/mi6kvk for more details.
|
||||
func (c *Client) ExportImage(opts ExportImageOptions) error {
|
||||
return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), true, false, nil, nil, opts.OutputStream, nil)
|
||||
return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{
|
||||
setRawTerminal: true,
|
||||
stdout: opts.OutputStream,
|
||||
})
|
||||
}
|
||||
|
||||
// ExportImagesOptions represent the options for ExportImages Docker API call
|
||||
//
|
||||
// See http://goo.gl/YeZzQK for more details.
|
||||
type ExportImagesOptions struct {
|
||||
Names []string
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
}
|
||||
|
||||
// ExportImages exports one or more images (as a tar file) into the stream
|
||||
//
|
||||
// See http://goo.gl/YeZzQK for more details.
|
||||
func (c *Client) ExportImages(opts ExportImagesOptions) error {
|
||||
if opts.Names == nil || len(opts.Names) == 0 {
|
||||
return ErrMustSpecifyNames
|
||||
}
|
||||
return c.stream("GET", "/images/get?"+queryString(&opts), streamOptions{
|
||||
setRawTerminal: true,
|
||||
stdout: opts.OutputStream,
|
||||
})
|
||||
}
|
||||
|
||||
// ImportImageOptions present the set of informations available for importing
|
||||
@ -323,15 +383,19 @@ func (c *Client) ImportImage(opts ImportImageOptions) error {
|
||||
// For more details about the Docker building process, see
|
||||
// http://goo.gl/tlPXPu.
|
||||
type BuildImageOptions struct {
|
||||
Name string `qs:"t"`
|
||||
NoCache bool `qs:"nocache"`
|
||||
SuppressOutput bool `qs:"q"`
|
||||
RmTmpContainer bool `qs:"rm"`
|
||||
ForceRmTmpContainer bool `qs:"forcerm"`
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
RawJSONStream bool `qs:"-"`
|
||||
Remote string `qs:"remote"`
|
||||
Name string `qs:"t"`
|
||||
Dockerfile string `qs:"dockerfile"`
|
||||
NoCache bool `qs:"nocache"`
|
||||
SuppressOutput bool `qs:"q"`
|
||||
RmTmpContainer bool `qs:"rm"`
|
||||
ForceRmTmpContainer bool `qs:"forcerm"`
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
RawJSONStream bool `qs:"-"`
|
||||
Remote string `qs:"remote"`
|
||||
Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header
|
||||
AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header
|
||||
ContextDir string `qs:"-"`
|
||||
}
|
||||
|
||||
// BuildImage builds an image from a tarball's url or a Dockerfile in the input
|
||||
@ -342,17 +406,33 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
|
||||
if opts.OutputStream == nil {
|
||||
return ErrMissingOutputStream
|
||||
}
|
||||
var headers map[string]string
|
||||
var headers = headersWithAuth(opts.Auth, opts.AuthConfigs)
|
||||
|
||||
if opts.Remote != "" && opts.Name == "" {
|
||||
opts.Name = opts.Remote
|
||||
}
|
||||
if opts.InputStream != nil {
|
||||
headers = map[string]string{"Content-Type": "application/tar"}
|
||||
if opts.InputStream != nil || opts.ContextDir != "" {
|
||||
headers["Content-Type"] = "application/tar"
|
||||
} else if opts.Remote == "" {
|
||||
return ErrMissingRepo
|
||||
}
|
||||
return c.stream("POST", fmt.Sprintf("/build?%s",
|
||||
queryString(&opts)), true, opts.RawJSONStream, headers, opts.InputStream, opts.OutputStream, nil)
|
||||
if opts.ContextDir != "" {
|
||||
if opts.InputStream != nil {
|
||||
return ErrMultipleContexts
|
||||
}
|
||||
var err error
|
||||
if opts.InputStream, err = createTarStream(opts.ContextDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.stream("POST", fmt.Sprintf("/build?%s", queryString(&opts)), streamOptions{
|
||||
setRawTerminal: true,
|
||||
rawJSONStream: opts.RawJSONStream,
|
||||
headers: headers,
|
||||
in: opts.InputStream,
|
||||
stdout: opts.OutputStream,
|
||||
})
|
||||
}
|
||||
|
||||
// TagImageOptions present the set of options to tag an image.
|
||||
@ -372,7 +452,8 @@ func (c *Client) TagImage(name string, opts TagImageOptions) error {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
_, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
|
||||
queryString(&opts)), nil)
|
||||
queryString(&opts)), doOptions{})
|
||||
|
||||
if status == http.StatusNotFound {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
@ -387,3 +468,49 @@ func isURL(u string) bool {
|
||||
}
|
||||
return p.Scheme == "http" || p.Scheme == "https"
|
||||
}
|
||||
|
||||
func headersWithAuth(auths ...interface{}) map[string]string {
|
||||
var headers = make(map[string]string)
|
||||
|
||||
for _, auth := range auths {
|
||||
switch auth.(type) {
|
||||
case AuthConfiguration:
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(auth)
|
||||
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
|
||||
case AuthConfigurations:
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(auth)
|
||||
headers["X-Registry-Config"] = base64.URLEncoding.EncodeToString(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
// APIImageSearch reflect the result of a search on the dockerHub
|
||||
//
|
||||
// See http://goo.gl/xI5lLZ for more details.
|
||||
type APIImageSearch struct {
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"`
|
||||
IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty"`
|
||||
}
|
||||
|
||||
// SearchImages search the docker hub with a specific given term.
|
||||
//
|
||||
// See http://goo.gl/xI5lLZ for more details.
|
||||
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) {
|
||||
body, _, err := c.do("GET", "/images/search?term="+term, doOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var searchResult []APIImageSearch
|
||||
err = json.Unmarshal(body, &searchResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return searchResult, nil
|
||||
}
|
||||
|
161
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go
generated
vendored
161
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -19,7 +19,7 @@ import (
|
||||
|
||||
func newTestClient(rt *FakeRoundTripper) Client {
|
||||
endpoint := "http://localhost:4243"
|
||||
u, _ := parseEndpoint("http://localhost:4243")
|
||||
u, _ := parseEndpoint("http://localhost:4243", false)
|
||||
client := Client{
|
||||
HTTPClient: &http.Client{Transport: rt},
|
||||
endpoint: endpoint,
|
||||
@ -88,7 +88,7 @@ func TestListImages(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
|
||||
images, err := client.ListImages(false)
|
||||
images, err := client.ListImages(ListImagesOptions{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -100,25 +100,42 @@ func TestListImages(t *testing.T) {
|
||||
func TestListImagesParameters(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "null", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
_, err := client.ListImages(false)
|
||||
_, err := client.ListImages(ListImagesOptions{All: false})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("ListImages(false: Wrong HTTP method. Want GET. Got %s.", req.Method)
|
||||
t.Errorf("ListImages({All: false}: Wrong HTTP method. Want GET. Got %s.", req.Method)
|
||||
}
|
||||
if all := req.URL.Query().Get("all"); all != "0" {
|
||||
t.Errorf("ListImages(false): Wrong parameter. Want all=0. Got all=%s", all)
|
||||
if all := req.URL.Query().Get("all"); all != "0" && all != "" {
|
||||
t.Errorf("ListImages({All: false}): Wrong parameter. Want all=0 or not present at all. Got all=%s", all)
|
||||
}
|
||||
fakeRT.Reset()
|
||||
_, err = client.ListImages(true)
|
||||
_, err = client.ListImages(ListImagesOptions{All: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = fakeRT.requests[0]
|
||||
if all := req.URL.Query().Get("all"); all != "1" {
|
||||
t.Errorf("ListImages(true): Wrong parameter. Want all=1. Got all=%s", all)
|
||||
t.Errorf("ListImages({All: true}): Wrong parameter. Want all=1. Got all=%s", all)
|
||||
}
|
||||
fakeRT.Reset()
|
||||
_, err = client.ListImages(ListImagesOptions{Filters: map[string][]string{
|
||||
"dangling": {"true"},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = fakeRT.requests[0]
|
||||
body := req.URL.Query().Get("filters")
|
||||
var filters map[string][]string
|
||||
err = json.Unmarshal([]byte(body), &filters)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(filters["dangling"]) != 1 || filters["dangling"][0] != "true" {
|
||||
t.Errorf("ListImages(dangling=[true]): Wrong filter map. Want dangling=[true], got dangling=%v", filters["dangling"])
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,6 +208,29 @@ func TestRemoveImageNotFound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImageExtended(t *testing.T) {
|
||||
name := "test"
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
err := client.RemoveImageExtended(name, RemoveImageOptions{Force: true, NoPrune: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expectedMethod := "DELETE"
|
||||
if req.Method != expectedMethod {
|
||||
t.Errorf("RemoveImage(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/images/" + name))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("RemoveImage(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path)
|
||||
}
|
||||
expectedQuery := "force=1&noprune=1"
|
||||
if query := req.URL.Query().Encode(); query != expectedQuery {
|
||||
t.Errorf("PushImage: Wrong query string. Want %q. Got %q.", expectedQuery, query)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectImage(t *testing.T) {
|
||||
body := `{
|
||||
"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
@ -266,6 +306,35 @@ func TestPushImage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageWithRawJSON(t *testing.T) {
|
||||
body := `
|
||||
{"status":"Pushing..."}
|
||||
{"status":"Pushing", "progress":"1/? (n/a)", "progressDetail":{"current":1}}}
|
||||
{"status":"Image successfully pushed"}
|
||||
`
|
||||
fakeRT := &FakeRoundTripper{
|
||||
message: body,
|
||||
status: http.StatusOK,
|
||||
header: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
|
||||
err := client.PushImage(PushImageOptions{
|
||||
Name: "test",
|
||||
OutputStream: &buf,
|
||||
RawJSONStream: true,
|
||||
}, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if buf.String() != body {
|
||||
t.Errorf("PushImage: Wrong raw output. Want %q. Got %q.", body, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageWithAuthentication(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
@ -790,3 +859,77 @@ func TestExportImage(t *testing.T) {
|
||||
t.Errorf("ExportIMage: wrong path. Expected %q. Got %q.", expectedPath, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportImages(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := ExportImagesOptions{Names: []string{"testimage1", "testimage2:latest"}, OutputStream: &buf}
|
||||
err := client.ExportImages(opts)
|
||||
if nil != err {
|
||||
t.Error(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("ExportImage: wrong method. Expected %q. Got %q.", "GET", req.Method)
|
||||
}
|
||||
expected := "http://localhost:4243/images/get?names=testimage1&names=testimage2%3Alatest"
|
||||
got := req.URL.String()
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ExportIMage: wrong path. Expected %q. Got %q.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportImagesNoNames(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := ExportImagesOptions{Names: []string{}, OutputStream: &buf}
|
||||
err := client.ExportImages(opts)
|
||||
if err == nil {
|
||||
t.Error("Expected an error")
|
||||
}
|
||||
if err != ErrMustSpecifyNames {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchImages(t *testing.T) {
|
||||
body := `[
|
||||
{
|
||||
"description":"A container with Cassandra 2.0.3",
|
||||
"is_official":true,
|
||||
"is_automated":true,
|
||||
"name":"poklet/cassandra",
|
||||
"star_count":17
|
||||
},
|
||||
{
|
||||
"description":"A container with Cassandra 2.0.3",
|
||||
"is_official":true,
|
||||
"is_automated":false,
|
||||
"name":"poklet/cassandra",
|
||||
"star_count":17
|
||||
}
|
||||
,
|
||||
{
|
||||
"description":"A container with Cassandra 2.0.3",
|
||||
"is_official":false,
|
||||
"is_automated":true,
|
||||
"name":"poklet/cassandra",
|
||||
"star_count":17
|
||||
}
|
||||
]`
|
||||
var expected []APIImageSearch
|
||||
err := json.Unmarshal([]byte(body), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
|
||||
result, err := client.SearchImages("cassandra")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("SearchImages: Wrong return value. Want %#v. Got %#v.", expected, result)
|
||||
}
|
||||
}
|
||||
|
6
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go
generated
vendored
6
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go
generated
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
//
|
||||
// See http://goo.gl/BOZrF5 for more details.
|
||||
func (c *Client) Version() (*Env, error) {
|
||||
body, _, err := c.do("GET", "/version", nil)
|
||||
body, _, err := c.do("GET", "/version", doOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -28,7 +28,7 @@ func (c *Client) Version() (*Env, error) {
|
||||
//
|
||||
// See http://goo.gl/wmqZsW for more details.
|
||||
func (c *Client) Info() (*Env, error) {
|
||||
body, _, err := c.do("GET", "/info", nil)
|
||||
body, _, err := c.do("GET", "/info", doOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
99
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/tar.go
generated
vendored
Normal file
99
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/tar.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright 2015 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
)
|
||||
|
||||
func createTarStream(srcPath string) (io.ReadCloser, error) {
|
||||
excludes, err := parseDockerignore(srcPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateContextDirectory(srcPath, excludes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tarOpts := &archive.TarOptions{
|
||||
ExcludePatterns: excludes,
|
||||
Compression: archive.Uncompressed,
|
||||
NoLchown: true,
|
||||
}
|
||||
return archive.TarWithOptions(srcPath, tarOpts)
|
||||
}
|
||||
|
||||
// validateContextDirectory checks if all the contents of the directory
|
||||
// can be read and returns an error if some files can't be read.
|
||||
// Symlinks which point to non-existing files don't trigger an error
|
||||
func validateContextDirectory(srcPath string, excludes []string) error {
|
||||
return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
|
||||
// skip this directory/file if it's not in the path, it won't get added to the context
|
||||
if relFilePath, err := filepath.Rel(srcPath, filePath); err != nil {
|
||||
return err
|
||||
} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
|
||||
return err
|
||||
} else if skip {
|
||||
if f.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
return fmt.Errorf("can't stat '%s'", filePath)
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// skip checking if symlinks point to non-existing files, such symlinks can be useful
|
||||
// also skip named pipes, because they hanging on open
|
||||
if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !f.IsDir() {
|
||||
currentFile, err := os.Open(filePath)
|
||||
if err != nil && os.IsPermission(err) {
|
||||
return fmt.Errorf("no permission to read from '%s'", filePath)
|
||||
}
|
||||
currentFile.Close()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func parseDockerignore(root string) ([]string, error) {
|
||||
var excludes []string
|
||||
ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore"))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err)
|
||||
}
|
||||
for _, pattern := range strings.Split(string(ignore), "\n") {
|
||||
matches, err := filepath.Match(pattern, "Dockerfile")
|
||||
if err != nil {
|
||||
return excludes, fmt.Errorf("bad .dockerignore pattern: '%s', error: %s", pattern, err)
|
||||
}
|
||||
if matches {
|
||||
return excludes, fmt.Errorf("dockerfile was excluded by .dockerignore pattern '%s'", pattern)
|
||||
}
|
||||
excludes = append(excludes, pattern)
|
||||
}
|
||||
|
||||
return excludes, nil
|
||||
}
|
38
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/bin/fmtpolice
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/bin/fmtpolice
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
readonly GOPATH="${GOPATH%%:*}"
|
||||
|
||||
main() {
|
||||
check_fmt
|
||||
check_lint
|
||||
}
|
||||
|
||||
check_fmt() {
|
||||
eval "set -e"
|
||||
for file in $(git ls-files '*.go') ; do
|
||||
gofmt $file | diff -u $file -
|
||||
done
|
||||
eval "set +e"
|
||||
}
|
||||
|
||||
check_lint() {
|
||||
_install_linter
|
||||
|
||||
for file in $(git ls-files '*.go') ; do
|
||||
if [[ ! "$(${GOPATH}/bin/golint $file)" =~ ^[[:blank:]]*$ ]] ; then
|
||||
_lint_verbose && exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_lint_verbose() {
|
||||
for file in $(git ls-files '*.go') ; do $GOPATH/bin/golint $file ; done
|
||||
}
|
||||
|
||||
_install_linter() {
|
||||
if [[ ! -x "${GOPATH}/bin/golint" ]] ; then
|
||||
go get -u -f github.com/golang/lint/golint
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
3
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/.dockerignore
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/.dockerignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
container.tar
|
||||
dockerfile.tar
|
||||
foofile
|
0
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/barfile
generated
vendored
Normal file
0
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/barfile
generated
vendored
Normal file
18
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/ca.pem
generated
vendored
Normal file
18
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/ca.pem
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC1TCCAb+gAwIBAgIQJ9MsNxrUxumNbAytGi3GEDALBgkqhkiG9w0BAQswFjEU
|
||||
MBIGA1UEChMLQm9vdDJEb2NrZXIwHhcNMTQxMDE2MjAyMTM4WhcNMTcwOTMwMjAy
|
||||
MTM4WjAWMRQwEgYDVQQKEwtCb290MkRvY2tlcjCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBALpFCSARjG+5yXoqr7UMzuE0df7RRZfeRZI06lJ02ZqV4Iii
|
||||
rgL7ML9yPxX50NbLnjiilSDTUhnyocYFItokzUzz8qpX/nlYhuN2Iqwh4d0aWS8z
|
||||
f5y248F+H1z+HY2W8NPl/6DVlVwYaNW1/k+RPMlHS0INLR6j+3Ievew7RNE0NnM2
|
||||
znELW6NetekDt3GUcz0Z95vDUDfdPnIk1eIFMmYvLxZh23xOca4Q37a3S8F3d+dN
|
||||
+OOpwjdgY9Qme0NQUaXpgp58jWuQfB8q7mZrdnLlLqRa8gx1HeDSotX7UmWtWPkb
|
||||
vd9EdlKLYw5PVpxMV1rkwf2t4TdgD5NfkpXlXkkCAwEAAaMjMCEwDgYDVR0PAQH/
|
||||
BAQDAgCkMA8GA1UdEwEB/wQFMAMBAf8wCwYJKoZIhvcNAQELA4IBAQBxYjHVSKqE
|
||||
MJw7CW0GddesULtXXVWGJuZdWJLQlPvPMfIfjIvlcZyS4cdVNiQ3sREFIZz8TpII
|
||||
CT0/Pg3sgv/FcOQe1CN0xZYZcyiAZHK1z0fJQq2qVpdv7+tJcjI2vvU6NI24iQCo
|
||||
W1wz25trJz9QbdB2MRLMjyz7TSWuafztIvcfEzaIdQ0Whqund/cSuPGQx5IwF83F
|
||||
rvlkOyJSH2+VIEBTCIuykJeL0DLTt8cePBQR5L1ISXb4RUMK9ZtqRscBRv8sn7o2
|
||||
ixG3wtL0gYF4xLtsQWVxI3iFVrU3WzOH/3c5shVRkWBd+AQRSwCJI4mKH7penJCF
|
||||
i3/zzlkvOnjV
|
||||
-----END CERTIFICATE-----
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user