Merge pull request #3726 from mrunalp/update/go-dockerclient

Update github.com/fsouza/go-dockerclient to pick up IpcMode support.
This commit is contained in:
Vish Kannan 2015-01-22 13:44:01 -08:00
commit 2863fa9fe6
20 changed files with 509 additions and 66 deletions

19
Godeps/Godeps.json generated
View File

@ -46,42 +46,42 @@
{
"ImportPath": "github.com/docker/docker/pkg/archive",
"Comment": "v1.4.1-108-g364720b",
"Rev": "364720b5e7e725cdc466171de873eefdb8609a33"
"Rev": "211513156dc1ace48e630b4bf4ea0fcfdc8d9abf"
},
{
"ImportPath": "github.com/docker/docker/pkg/fileutils",
"Comment": "v1.4.1-108-g364720b",
"Rev": "364720b5e7e725cdc466171de873eefdb8609a33"
"Rev": "211513156dc1ace48e630b4bf4ea0fcfdc8d9abf"
},
{
"ImportPath": "github.com/docker/docker/pkg/ioutils",
"Comment": "v1.4.1-108-g364720b",
"Rev": "364720b5e7e725cdc466171de873eefdb8609a33"
"Rev": "211513156dc1ace48e630b4bf4ea0fcfdc8d9abf"
},
{
"ImportPath": "github.com/docker/docker/pkg/pools",
"Comment": "v1.4.1-108-g364720b",
"Rev": "364720b5e7e725cdc466171de873eefdb8609a33"
"Rev": "211513156dc1ace48e630b4bf4ea0fcfdc8d9abf"
},
{
"ImportPath": "github.com/docker/docker/pkg/promise",
"Comment": "v1.4.1-108-g364720b",
"Rev": "364720b5e7e725cdc466171de873eefdb8609a33"
"Rev": "211513156dc1ace48e630b4bf4ea0fcfdc8d9abf"
},
{
"ImportPath": "github.com/docker/docker/pkg/system",
"Comment": "v1.4.1-108-g364720b",
"Rev": "364720b5e7e725cdc466171de873eefdb8609a33"
"Rev": "211513156dc1ace48e630b4bf4ea0fcfdc8d9abf"
},
{
"ImportPath": "github.com/docker/docker/pkg/units",
"Comment": "v1.4.1-108-g364720b",
"Rev": "364720b5e7e725cdc466171de873eefdb8609a33"
"Rev": "211513156dc1ace48e630b4bf4ea0fcfdc8d9abf"
},
{
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
"Comment": "v1.4.1-108-g364720b",
"Rev": "364720b5e7e725cdc466171de873eefdb8609a33"
"Rev": "211513156dc1ace48e630b4bf4ea0fcfdc8d9abf"
},
{
"ImportPath": "github.com/elazarl/go-bindata-assetfs",
@ -94,8 +94,7 @@
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Comment": "0.2.1-334-g9c377ff",
"Rev": "9c377ffd9aed48a012adf1c3fd517fe98394120b"
"Rev": "d19717788084716e4adff0515be6289aa04bec46"
},
{
"ImportPath": "github.com/ghodss/yaml",

View File

@ -30,11 +30,11 @@ type (
ArchiveReader io.Reader
Compression int
TarOptions struct {
Includes []string
Excludes []string
Compression Compression
NoLchown bool
Name string
IncludeFiles []string
ExcludePatterns []string
Compression Compression
NoLchown bool
Name string
}
// Archiver allows the reuse of most utility functions of this package
@ -378,7 +378,7 @@ func escapeName(name string) string {
}
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
// paths are included in `options.Includes` (if non-nil) or not in `options.Excludes`.
// 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()
@ -401,12 +401,14 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
// mutating the filesystem and we can see transient errors
// from this
if options.Includes == nil {
options.Includes = []string{"."}
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.Includes {
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)
@ -420,10 +422,19 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
return nil
}
skip, err := fileutils.Matches(relFilePath, options.Excludes)
if err != nil {
log.Debugf("Error matching %s", relFilePath, err)
return err
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 {
@ -433,6 +444,11 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
return nil
}
if seen[relFilePath] {
return nil
}
seen[relFilePath] = true
// Rename the base resource
if options.Name != "" && filePath == srcPath+"/"+filepath.Base(relFilePath) {
renamedRelFilePath = relFilePath
@ -487,7 +503,7 @@ loop:
// This keeps "../" as-is, but normalizes "/../" to "/"
hdr.Name = filepath.Clean(hdr.Name)
for _, exclude := range options.Excludes {
for _, exclude := range options.ExcludePatterns {
if strings.HasPrefix(hdr.Name, exclude) {
continue loop
}
@ -563,8 +579,8 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
if options == nil {
options = &TarOptions{}
}
if options.Excludes == nil {
options.Excludes = []string{}
if options.ExcludePatterns == nil {
options.ExcludePatterns = []string{}
}
decompressedArchive, err := DecompressStream(archive)
if err != nil {

View File

@ -165,8 +165,8 @@ func TestTarUntar(t *testing.T) {
Gzip,
} {
changes, err := tarUntar(t, origin, &TarOptions{
Compression: c,
Excludes: []string{"3"},
Compression: c,
ExcludePatterns: []string{"3"},
})
if err != nil {
@ -196,8 +196,8 @@ func TestTarWithOptions(t *testing.T) {
opts *TarOptions
numChanges int
}{
{&TarOptions{Includes: []string{"1"}}, 1},
{&TarOptions{Excludes: []string{"2"}}, 1},
{&TarOptions{IncludeFiles: []string{"1"}}, 1},
{&TarOptions{ExcludePatterns: []string{"2"}}, 1},
}
for _, testCase := range cases {
changes, err := tarUntar(t, origin, testCase.opts)

View File

@ -286,7 +286,7 @@ func TestApplyLayer(t *testing.T) {
t.Fatal(err)
}
if err := ApplyLayer(src, layerCopy); err != nil {
if _, err := ApplyLayer(src, layerCopy); err != nil {
t.Fatal(err)
}

View File

@ -15,7 +15,7 @@ import (
"github.com/docker/docker/pkg/system"
)
func UnpackLayer(dest string, layer ArchiveReader) error {
func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
tr := tar.NewReader(layer)
trBuf := pools.BufioReader32KPool.Get(tr)
defer pools.BufioReader32KPool.Put(trBuf)
@ -33,9 +33,11 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
break
}
if err != nil {
return err
return 0, err
}
size += hdr.Size
// Normalize name, for safety and for a simple is-root check
hdr.Name = filepath.Clean(hdr.Name)
@ -48,7 +50,7 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
err = os.MkdirAll(parentPath, 0600)
if err != nil {
return err
return 0, err
}
}
}
@ -63,12 +65,12 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
aufsHardlinks[basename] = hdr
if aufsTempdir == "" {
if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
return err
return 0, err
}
defer os.RemoveAll(aufsTempdir)
}
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil {
return err
return 0, err
}
}
continue
@ -77,10 +79,10 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
path := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, path)
if err != nil {
return err
return 0, err
}
if strings.HasPrefix(rel, "..") {
return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
}
base := filepath.Base(path)
@ -88,7 +90,7 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
originalBase := base[len(".wh."):]
originalPath := filepath.Join(filepath.Dir(path), originalBase)
if err := os.RemoveAll(originalPath); err != nil {
return err
return 0, err
}
} else {
// If path exits we almost always just want to remove and replace it.
@ -98,7 +100,7 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
if fi, err := os.Lstat(path); err == nil {
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
if err := os.RemoveAll(path); err != nil {
return err
return 0, err
}
}
}
@ -113,18 +115,18 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
linkBasename := filepath.Base(hdr.Linkname)
srcHdr = aufsHardlinks[linkBasename]
if srcHdr == nil {
return fmt.Errorf("Invalid aufs hardlink")
return 0, fmt.Errorf("Invalid aufs hardlink")
}
tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
if err != nil {
return err
return 0, err
}
defer tmpFile.Close()
srcData = tmpFile
}
if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil {
return err
return 0, err
}
// Directory mtimes must be handled at the end to avoid further
@ -139,27 +141,29 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
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 0, err
}
}
return nil
return size, nil
}
// ApplyLayer parses a diff in the standard layer format from `layer`, and
// applies it to the directory `dest`.
func ApplyLayer(dest string, layer ArchiveReader) error {
// 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 err
return 0, err
}
defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
layer, err = DecompressStream(layer)
if err != nil {
return err
return 0, err
}
return UnpackLayer(dest, layer)
}

View File

@ -17,7 +17,8 @@ var testUntarFns = map[string]func(string, io.Reader) error{
return Untar(r, dest, nil)
},
"applylayer": func(dest string, r io.Reader) error {
return ApplyLayer(dest, ArchiveReader(r))
_, err := ApplyLayer(dest, ArchiveReader(r))
return err
},
}

View File

@ -39,7 +39,7 @@ var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB",
// HumanSize returns a human-readable approximation of a size
// using SI standard (eg. "44kB", "17MB")
func HumanSize(size int64) string {
func HumanSize(size float64) string {
return intToString(float64(size), 1000.0, decimapAbbrs)
}

View File

@ -23,9 +23,9 @@ func TestHumanSize(t *testing.T) {
assertEquals(t, "1 MB", HumanSize(1000000))
assertEquals(t, "1.049 MB", HumanSize(1048576))
assertEquals(t, "2 MB", HumanSize(2*MB))
assertEquals(t, "3.42 GB", HumanSize(int64(float64(3.42*GB))))
assertEquals(t, "5.372 TB", HumanSize(int64(float64(5.372*TB))))
assertEquals(t, "2.22 PB", HumanSize(int64(float64(2.22*PB))))
assertEquals(t, "3.42 GB", HumanSize(float64(3.42*GB)))
assertEquals(t, "5.372 TB", HumanSize(float64(5.372*TB)))
assertEquals(t, "2.22 PB", HumanSize(float64(2.22*PB)))
}
func TestFromHumanSize(t *testing.T) {

View File

@ -2,6 +2,7 @@ language: go
go:
- 1.2.2
- 1.3.1
- 1.4
- tip
env:
- GOARCH=amd64

View File

@ -3,6 +3,7 @@
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 McCann <benmccann.com>
Carlos Diaz-Padron <cpadron@mozilla.com>
@ -16,6 +17,7 @@ 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>
Jari Kolehmainen <jari.kolehmainen@digia.com>
@ -25,11 +27,14 @@ 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>
Lucas Clemente <lucas@clemente.io>
Martin Sweeney <martin@sweeney.io>
Máximo Cuadros Ortiz <mcuadros@gmail.com>
Mike Dillon <mike.dillon@synctree.com>
Mrunal Patel <mrunalp@gmail.com>
Omeid Matten <public@omeid.me>
Paul Morie <pmorie@gmail.com>
Peter Jihoon Kim <raingrove@gmail.com>

View File

@ -22,7 +22,7 @@ import (
func main() {
endpoint := "unix:///var/run/docker.sock"
client, _ := docker.NewClient(endpoint)
imgs, _ := client.ListImages(true)
imgs, _ := client.ListImages(docker.ListImagesOptions{All: false})
for _, img := range imgs {
fmt.Println("ID: ", img.ID)
fmt.Println("RepoTags: ", img.RepoTags)

View File

@ -460,24 +460,34 @@ 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
}
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 {
// When TTY is ON, use regular copy
_, err = io.Copy(stdout, br)
} else {
_, err = stdCopy(stdout, stderr, br)

View File

@ -358,6 +358,7 @@ type HostConfig struct {
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"`
RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`
}

View File

@ -56,6 +56,34 @@ 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.
//
@ -119,6 +147,26 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
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, nil)
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

View File

@ -10,6 +10,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
)
@ -126,3 +127,133 @@ func TestExecResize(t *testing.T) {
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)
}
}

View File

@ -27,9 +27,9 @@ func createTarStream(srcPath string) (io.ReadCloser, error) {
return nil, err
}
tarOpts := &archive.TarOptions{
Excludes: excludes,
Compression: archive.Uncompressed,
NoLchown: true,
ExcludePatterns: excludes,
Compression: archive.Uncompressed,
NoLchown: true,
}
return archive.TarWithOptions(srcPath, tarOpts)
}

View File

@ -31,7 +31,7 @@ _lint_verbose() {
_install_linter() {
if [[ ! -x "${GOPATH}/bin/golint" ]] ; then
go get -u github.com/golang/lint/golint
go get -u -f github.com/golang/lint/golint
fi
}

View File

@ -34,7 +34,7 @@ import (
// For more details on the remote API, check http://goo.gl/G3plxW.
type DockerServer struct {
containers []*docker.Container
execs []*docker.Exec
execs []*docker.ExecInspect
cMut sync.RWMutex
images []docker.Image
iMut sync.RWMutex
@ -99,7 +99,9 @@ func (s *DockerServer) buildMuxer() {
s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer))
s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer))
s.mux.Path("/containers/{id:.*}/exec").Methods("POST").HandlerFunc(s.handlerWrapper(s.createExecContainer))
s.mux.Path("/exec/{id:.*}/resize").Methods("POST").HandlerFunc(s.handlerWrapper(s.resizeExecContainer))
s.mux.Path("/exec/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startExecContainer))
s.mux.Path("/exec/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectExecContainer))
s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage))
s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage))
s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages))
@ -724,10 +726,31 @@ func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) {
}
func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
exec := docker.ExecInspect{
ID: "id-exec-created-by-test",
Container: *container,
}
var params docker.CreateExecOptions
err = json.NewDecoder(r.Body).Decode(&params)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if len(params.Cmd) > 0 {
exec.ProcessConfig.EntryPoint = params.Cmd[0]
if len(params.Cmd) > 1 {
exec.ProcessConfig.Arguments = params.Cmd[1:]
}
}
s.execs = append(s.execs, &exec)
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
exec := docker.Exec{ID: "id-exec-created-by-test"}
s.execs = append(s.execs, &exec)
json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID})
}
@ -742,3 +765,27 @@ func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request
}
w.WriteHeader(http.StatusNotFound)
}
func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
for _, exec := range s.execs {
if exec.ID == id {
w.WriteHeader(http.StatusOK)
return
}
}
w.WriteHeader(http.StatusNotFound)
}
func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
for _, exec := range s.execs {
if exec.ID == id {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(exec)
return
}
}
w.WriteHeader(http.StatusNotFound)
}

View File

@ -1089,3 +1089,83 @@ func TestDefaultHandler(t *testing.T) {
t.Fatalf("DefaultHandler: Expected to return server.mux, got: %#v", server.DefaultHandler())
}
}
func TestCreateExecContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 2)
server.buildMuxer()
recorder := httptest.NewRecorder()
body := `{"Cmd": ["bash", "-c", "ls"]}`
path := fmt.Sprintf("/containers/%s/exec", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, strings.NewReader(body))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
serverExec := server.execs[0]
var got docker.Exec
err := json.NewDecoder(recorder.Body).Decode(&got)
if err != nil {
t.Fatal(err)
}
if got.ID != serverExec.ID {
t.Errorf("CreateExec: wrong value. Want %#v. Got %#v.", serverExec.ID, got.ID)
}
expected := docker.ExecInspect{
ID: got.ID,
ProcessConfig: docker.ExecProcessConfig{
EntryPoint: "bash",
Arguments: []string{"-c", "ls"},
},
Container: *server.containers[0],
}
if !reflect.DeepEqual(*serverExec, expected) {
t.Errorf("InspectContainer: wrong value. Want:\n%#v\nGot:\n%#v\n", expected, *serverExec)
}
}
func TestInspectExecContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.buildMuxer()
recorder := httptest.NewRecorder()
body := `{"Cmd": ["bash", "-c", "ls"]}`
path := fmt.Sprintf("/containers/%s/exec", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, strings.NewReader(body))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
var got docker.Exec
err := json.NewDecoder(recorder.Body).Decode(&got)
if err != nil {
t.Fatal(err)
}
path = fmt.Sprintf("/exec/%s/json", got.ID)
request, _ = http.NewRequest("GET", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
var got2 docker.ExecInspect
err = json.NewDecoder(recorder.Body).Decode(&got2)
if err != nil {
t.Fatal(err)
}
expected := docker.ExecInspect{
ID: got.ID,
ProcessConfig: docker.ExecProcessConfig{
EntryPoint: "bash",
Arguments: []string{"-c", "ls"},
},
Container: *server.containers[0],
}
got2.Container.State.StartedAt = expected.Container.State.StartedAt
got2.Container.State.FinishedAt = expected.Container.State.FinishedAt
got2.Container.Config = expected.Container.Config
got2.Container.Created = expected.Container.Created
got2.Container.NetworkSettings = expected.Container.NetworkSettings
if !reflect.DeepEqual(got2, expected) {
t.Errorf("InspectContainer: wrong value. Want:\n%#v\nGot:\n%#v\n", expected, got2)
}
}

View File

@ -0,0 +1,100 @@
// Copyright 2014 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.
//
// The content is borrowed from Docker's own source code to provide a simple
// tls based dialer
package docker
import (
"crypto/tls"
"errors"
"net"
"strings"
"time"
)
type tlsClientCon struct {
*tls.Conn
rawConn net.Conn
}
func (c *tlsClientCon) CloseWrite() error {
// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
// on its underlying connection.
if cwc, ok := c.rawConn.(interface {
CloseWrite() error
}); ok {
return cwc.CloseWrite()
}
return nil
}
func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
// We want the Timeout and Deadline values from dialer to cover the
// whole process: TCP connection and TLS handshake. This means that we
// also need to start our own timers now.
timeout := dialer.Timeout
if !dialer.Deadline.IsZero() {
deadlineTimeout := dialer.Deadline.Sub(time.Now())
if timeout == 0 || deadlineTimeout < timeout {
timeout = deadlineTimeout
}
}
var errChannel chan error
if timeout != 0 {
errChannel = make(chan error, 2)
time.AfterFunc(timeout, func() {
errChannel <- errors.New("")
})
}
rawConn, err := dialer.Dial(network, addr)
if err != nil {
return nil, err
}
colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 {
colonPos = len(addr)
}
hostname := addr[:colonPos]
// If no ServerName is set, infer the ServerName
// from the hostname we're connecting to.
if config.ServerName == "" {
// Make a copy to avoid polluting argument or default.
c := *config
c.ServerName = hostname
config = &c
}
conn := tls.Client(rawConn, config)
if timeout == 0 {
err = conn.Handshake()
} else {
go func() {
errChannel <- conn.Handshake()
}()
err = <-errChannel
}
if err != nil {
rawConn.Close()
return nil, err
}
// This is Docker difference with standard's crypto/tls package: returned a
// wrapper which holds both the TLS and raw connections.
return &tlsClientCon{conn, rawConn}, nil
}
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
}