First commit
This commit is contained in:
12
third_party/github.com/fsouza/go-dockerclient/.travis.yml
vendored
Normal file
12
third_party/github.com/fsouza/go-dockerclient/.travis.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.1.2
|
||||
- 1.2
|
||||
- tip
|
||||
env:
|
||||
- GOARCH=amd64
|
||||
- GOARCH=386
|
||||
install:
|
||||
- go get -d ./...
|
||||
script:
|
||||
- go test ./...
|
29
third_party/github.com/fsouza/go-dockerclient/AUTHORS
vendored
Normal file
29
third_party/github.com/fsouza/go-dockerclient/AUTHORS
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# This is the official list of go-dockerclient authors for copyright purposes.
|
||||
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Andy Goldstein <andy.goldstein@redhat.com>
|
||||
Ben McCann <benmccann.com>
|
||||
Cezar Sa Espinola <cezar.sa@corp.globo.com>
|
||||
cheneydeng <cheneydeng@qq.com>
|
||||
Ed <edrocksit@gmail.com>
|
||||
Eric Anderson <anderson@copperegg.com>
|
||||
Flavia Missi <flaviamissi@gmail.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Jason Wilder <jwilder@litl.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
Jeff Mitchell <jeffrey.mitchell@gmail.com>
|
||||
Jeffrey Hulten <jhulten@gmail.com>
|
||||
Lucas Clemente <lucas@clemente.io>
|
||||
Paul Morie <pmorie@gmail.com>
|
||||
Peter Jihoon Kim <raingrove@gmail.com>
|
||||
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
|
||||
Salvador Gironès <salvadorgirones@gmail.com>
|
||||
Simon Eskildsen <sirup@sirupsen.com>
|
||||
Simon Menke <simon.menke@gmail.com>
|
||||
Skolos <skolos@gopherlab.com>
|
||||
Soulou <leo@unbekandt.eu>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Summer Mousa <smousa@zenoss.com>
|
||||
Tarsis Azevedo <tarsis@corp.globo.com>
|
||||
Tim Schindler <tim@catalyst-zero.com>
|
||||
Wiliam Souza <wiliamsouza83@gmail.com>
|
6
third_party/github.com/fsouza/go-dockerclient/DOCKER-LICENSE
vendored
Normal file
6
third_party/github.com/fsouza/go-dockerclient/DOCKER-LICENSE
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
You can find the Docker license int the following link:
|
||||
https://raw2.github.com/dotcloud/docker/master/LICENSE
|
22
third_party/github.com/fsouza/go-dockerclient/LICENSE
vendored
Normal file
22
third_party/github.com/fsouza/go-dockerclient/LICENSE
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2014, go-dockerclient authors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
47
third_party/github.com/fsouza/go-dockerclient/README.markdown
vendored
Normal file
47
third_party/github.com/fsouza/go-dockerclient/README.markdown
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
#go-dockerclient
|
||||
|
||||
[](https://drone.io/github.com/fsouza/go-dockerclient/latest)
|
||||
[](https://travis-ci.org/fsouza/go-dockerclient)
|
||||
|
||||
[](http://godoc.org/github.com/fsouza/go-dockerclient)
|
||||
|
||||
This package presents a client for the Docker remote API.
|
||||
|
||||
For more details, check the [remote API documentation](http://docs.docker.io/en/latest/reference/api/docker_remote_api/).
|
||||
|
||||
##Versioning
|
||||
|
||||
* Version 0.1 is compatible with Docker v0.7.1
|
||||
* The master is compatible with Docker's master
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"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)
|
||||
fmt.Println("Repository: ", img.Repository)
|
||||
}
|
||||
}
|
||||
|
||||
## Developing
|
||||
|
||||
You can run the tests with:
|
||||
|
||||
go get -d ./...
|
||||
go test ./...
|
36
third_party/github.com/fsouza/go-dockerclient/change.go
vendored
Normal file
36
third_party/github.com/fsouza/go-dockerclient/change.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
ChangeModify ChangeType = iota
|
||||
ChangeAdd
|
||||
ChangeDelete
|
||||
)
|
||||
|
||||
// Change represents a change in a container.
|
||||
//
|
||||
// See http://goo.gl/DpGyzK for more details.
|
||||
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)
|
||||
}
|
26
third_party/github.com/fsouza/go-dockerclient/change_test.go
vendored
Normal file
26
third_party/github.com/fsouza/go-dockerclient/change_test.go
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChangeString(t *testing.T) {
|
||||
var tests = []struct {
|
||||
change Change
|
||||
expected string
|
||||
}{
|
||||
{Change{"/etc/passwd", ChangeModify}, "C /etc/passwd"},
|
||||
{Change{"/etc/passwd", ChangeAdd}, "A /etc/passwd"},
|
||||
{Change{"/etc/passwd", ChangeDelete}, "D /etc/passwd"},
|
||||
{Change{"/etc/passwd", 33}, " /etc/passwd"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := tt.change.String(); got != tt.expected {
|
||||
t.Errorf("Change.String(): want %q. Got %q.", tt.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
352
third_party/github.com/fsouza/go-dockerclient/client.go
vendored
Normal file
352
third_party/github.com/fsouza/go-dockerclient/client.go
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
// 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.
|
||||
|
||||
// Package docker provides a client for the Docker remote API.
|
||||
//
|
||||
// See http://goo.gl/mxyql for more details on the remote API.
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const userAgent = "go-dockerclient"
|
||||
|
||||
var (
|
||||
// ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL.
|
||||
ErrInvalidEndpoint = errors.New("invalid endpoint")
|
||||
|
||||
// ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
|
||||
ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
|
||||
)
|
||||
|
||||
// Client is the basic type of this package. It provides methods for
|
||||
// interaction with the API.
|
||||
type Client struct {
|
||||
endpoint string
|
||||
endpointURL *url.URL
|
||||
eventMonitor *eventMonitoringState
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client instance ready for communication with the
|
||||
// given server endpoint.
|
||||
func NewClient(endpoint string) (*Client, error) {
|
||||
u, err := parseEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{
|
||||
endpoint: endpoint,
|
||||
endpointURL: u,
|
||||
client: http.DefaultClient,
|
||||
eventMonitor: new(eventMonitoringState),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) do(method, path string, data interface{}) ([]byte, int, error) {
|
||||
var params io.Reader
|
||||
if data != nil {
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
params = bytes.NewBuffer(buf)
|
||||
}
|
||||
req, err := http.NewRequest(method, c.getURL(path), params)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if data != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
var resp *http.Response
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if protocol == "unix" {
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
resp, err = clientconn.Do(req)
|
||||
defer clientconn.Close()
|
||||
} else {
|
||||
resp, err = c.client.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, -1, ErrConnectionRefused
|
||||
}
|
||||
return nil, -1, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
return nil, resp.StatusCode, newError(resp.StatusCode, body)
|
||||
}
|
||||
return body, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func (c *Client) stream(method, path string, headers map[string]string, in io.Reader, out io.Writer) error {
|
||||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
in = bytes.NewReader(nil)
|
||||
}
|
||||
req, err := http.NewRequest(method, c.getURL(path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
for key, val := range headers {
|
||||
req.Header.Set(key, val)
|
||||
}
|
||||
var resp *http.Response
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if out == nil {
|
||||
out = ioutil.Discard
|
||||
}
|
||||
if protocol == "unix" {
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
resp, err = clientconn.Do(req)
|
||||
defer clientconn.Close()
|
||||
} else {
|
||||
resp, err = c.client.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return ErrConnectionRefused
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return newError(resp.StatusCode, body)
|
||||
}
|
||||
if resp.Header.Get("Content-Type") == "application/json" {
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
var m jsonMessage
|
||||
if err := dec.Decode(&m); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Stream != "" {
|
||||
fmt.Fprint(out, m.Stream)
|
||||
} else if m.Progress != "" {
|
||||
fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress)
|
||||
} else if m.Error != "" {
|
||||
return errors.New(m.Error)
|
||||
}
|
||||
if m.Status != "" {
|
||||
fmt.Fprintln(out, m.Status)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) hijack(method, path string, success chan struct{}, in io.Reader, errStream io.Writer, out io.Writer) error {
|
||||
req, err := http.NewRequest(method, c.getURL(path), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if protocol != "unix" {
|
||||
protocol = "tcp"
|
||||
address = c.endpointURL.Host
|
||||
}
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dial.Close()
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
clientconn.Do(req)
|
||||
if success != nil {
|
||||
success <- struct{}{}
|
||||
<-success
|
||||
}
|
||||
rwc, br := clientconn.Hijack()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
errs := make(chan error, 2)
|
||||
go func() {
|
||||
var err error
|
||||
if in != nil {
|
||||
_, err = io.Copy(out, br)
|
||||
} else {
|
||||
_, err = utils.StdCopy(out, errStream, br)
|
||||
}
|
||||
errs <- err
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
var err error
|
||||
if in != nil {
|
||||
_, err = io.Copy(rwc, in)
|
||||
}
|
||||
rwc.(interface {
|
||||
CloseWrite() error
|
||||
}).CloseWrite()
|
||||
errs <- err
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
if err := <-errs; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) getURL(path string) string {
|
||||
urlStr := strings.TrimRight(c.endpointURL.String(), "/")
|
||||
if c.endpointURL.Scheme == "unix" {
|
||||
urlStr = ""
|
||||
}
|
||||
return fmt.Sprintf("%s%s", urlStr, path)
|
||||
}
|
||||
|
||||
type jsonMessage struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Progress string `json:"progress,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Stream string `json:"stream,omitempty"`
|
||||
}
|
||||
|
||||
func queryString(opts interface{}) string {
|
||||
if opts == nil {
|
||||
return ""
|
||||
}
|
||||
value := reflect.ValueOf(opts)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
if value.Kind() != reflect.Struct {
|
||||
return ""
|
||||
}
|
||||
items := url.Values(map[string][]string{})
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
field := value.Type().Field(i)
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
key := field.Tag.Get("qs")
|
||||
if key == "" {
|
||||
key = strings.ToLower(field.Name)
|
||||
} else if key == "-" {
|
||||
continue
|
||||
}
|
||||
v := value.Field(i)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return items.Encode()
|
||||
}
|
||||
|
||||
// Error represents failures in the API. It represents a failure from the API.
|
||||
type Error struct {
|
||||
Status int
|
||||
Message string
|
||||
}
|
||||
|
||||
func newError(status int, body []byte) *Error {
|
||||
return &Error{Status: status, Message: string(body)}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
|
||||
}
|
||||
|
||||
func parseEndpoint(endpoint string) (*url.URL, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
||||
if u.Scheme == "tcp" {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" {
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
||||
if u.Scheme != "unix" {
|
||||
_, 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 > 0 && number < 65536 {
|
||||
return u, nil
|
||||
}
|
||||
} else {
|
||||
return u, nil // we don't need port when using a unix socket
|
||||
}
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
161
third_party/github.com/fsouza/go-dockerclient/client_test.go
vendored
Normal file
161
third_party/github.com/fsouza/go-dockerclient/client_test.go
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewAPIClient(t *testing.T) {
|
||||
endpoint := "http://localhost:4243"
|
||||
client, err := NewClient(endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.endpoint != endpoint {
|
||||
t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
|
||||
}
|
||||
if client.client != http.DefaultClient {
|
||||
t.Errorf("Expected http.Client %#v. Got %#v.", http.DefaultClient, client.client)
|
||||
}
|
||||
|
||||
// test unix socket endpoints
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
client, err = NewClient(endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.endpoint != endpoint {
|
||||
t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewClientInvalidEndpoint(t *testing.T) {
|
||||
cases := []string{
|
||||
"htp://localhost:3243", "http://localhost:a", "localhost:8080",
|
||||
"", "localhost", "http://localhost:8080:8383", "http://localhost:65536",
|
||||
"https://localhost:-20",
|
||||
}
|
||||
for _, c := range cases {
|
||||
client, err := NewClient(c)
|
||||
if client != nil {
|
||||
t.Errorf("Want <nil> client for invalid endpoint, got %#v.", client)
|
||||
}
|
||||
if !reflect.DeepEqual(err, ErrInvalidEndpoint) {
|
||||
t.Errorf("NewClient(%q): Got invalid error for invalid endpoint. Want %#v. Got %#v.", c, ErrInvalidEndpoint, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetURL(t *testing.T) {
|
||||
var tests = []struct {
|
||||
endpoint string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{"http://localhost:4243/", "/", "http://localhost:4243/"},
|
||||
{"http://localhost:4243", "/", "http://localhost:4243/"},
|
||||
{"http://localhost:4243", "/containers/ps", "http://localhost:4243/containers/ps"},
|
||||
{"tcp://localhost:4243", "/containers/ps", "http://localhost:4243/containers/ps"},
|
||||
{"http://localhost:4243/////", "/", "http://localhost:4243/"},
|
||||
{"unix:///var/run/docker.socket", "/containers", "/containers"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client, _ := NewClient(tt.endpoint)
|
||||
client.endpoint = tt.endpoint
|
||||
got := client.getURL(tt.path)
|
||||
if got != tt.expected {
|
||||
t.Errorf("getURL(%q): Got %s. Want %s.", tt.path, got, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
err := newError(400, []byte("bad parameter"))
|
||||
expected := Error{Status: 400, Message: "bad parameter"}
|
||||
if !reflect.DeepEqual(expected, *err) {
|
||||
t.Errorf("Wrong error type. Want %#v. Got %#v.", expected, *err)
|
||||
}
|
||||
message := "API error (400): bad parameter"
|
||||
if err.Error() != message {
|
||||
t.Errorf("Wrong error message. Want %q. Got %q.", message, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryString(t *testing.T) {
|
||||
v := float32(2.4)
|
||||
f32QueryString := fmt.Sprintf("w=%s&x=10&y=10.35", strconv.FormatFloat(float64(v), 'f', -1, 64))
|
||||
jsonPerson := url.QueryEscape(`{"Name":"gopher","age":4}`)
|
||||
var tests = []struct {
|
||||
input interface{}
|
||||
want string
|
||||
}{
|
||||
{&ListContainersOptions{All: true}, "all=1"},
|
||||
{ListContainersOptions{All: true}, "all=1"},
|
||||
{ListContainersOptions{Before: "something"}, "before=something"},
|
||||
{ListContainersOptions{Before: "something", Since: "other"}, "before=something&since=other"},
|
||||
{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"},
|
||||
{dumb{v: 4, X: 10, Y: 10.35000}, "x=10&y=10.35"},
|
||||
{dumb{T: 10, Y: 10.35000}, "y=10.35"},
|
||||
{dumb{Person: &person{Name: "gopher", Age: 4}}, "p=" + jsonPerson},
|
||||
{nil, ""},
|
||||
{10, ""},
|
||||
{"not_a_struct", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := queryString(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("queryString(%v). Want %q. Got %q.", tt.input, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FakeRoundTripper struct {
|
||||
message string
|
||||
status int
|
||||
requests []*http.Request
|
||||
}
|
||||
|
||||
func (rt *FakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
body := strings.NewReader(rt.message)
|
||||
rt.requests = append(rt.requests, r)
|
||||
return &http.Response{
|
||||
StatusCode: rt.status,
|
||||
Body: ioutil.NopCloser(body),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rt *FakeRoundTripper) Reset() {
|
||||
rt.requests = nil
|
||||
}
|
||||
|
||||
type person struct {
|
||||
Name string
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
type dumb struct {
|
||||
T int `qs:"-"`
|
||||
v int
|
||||
W float32
|
||||
X int
|
||||
Y float64
|
||||
Z int `qs:"zee"`
|
||||
Person *person `qs:"p"`
|
||||
}
|
||||
|
||||
type fakeEndpointURL struct {
|
||||
Scheme string
|
||||
}
|
583
third_party/github.com/fsouza/go-dockerclient/container.go
vendored
Normal file
583
third_party/github.com/fsouza/go-dockerclient/container.go
vendored
Normal file
@@ -0,0 +1,583 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ListContainersOptions specify parameters to the ListContainers function.
|
||||
//
|
||||
// See http://goo.gl/QpCnDN for more details.
|
||||
type ListContainersOptions struct {
|
||||
All bool
|
||||
Size bool
|
||||
Limit int
|
||||
Since string
|
||||
Before string
|
||||
}
|
||||
|
||||
type APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
IP string
|
||||
}
|
||||
|
||||
// APIContainers represents a container.
|
||||
//
|
||||
// See http://goo.gl/QeFH7U for more details.
|
||||
type APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports []APIPort
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
// ListContainers returns a slice of containers matching the given criteria.
|
||||
//
|
||||
// See http://goo.gl/QpCnDN for more details.
|
||||
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
|
||||
path := "/containers/json?" + queryString(opts)
|
||||
body, _, err := c.do("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var containers []APIContainers
|
||||
err = json.Unmarshal(body, &containers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
// Port represents the port number and the protocol, in the form
|
||||
// <number>/<protocol>. For example: 80/tcp.
|
||||
type Port string
|
||||
|
||||
// Port returns the number of the port.
|
||||
func (p Port) Port() string {
|
||||
return strings.Split(string(p), "/")[0]
|
||||
}
|
||||
|
||||
// Proto returns the name of the protocol.
|
||||
func (p Port) Proto() string {
|
||||
parts := strings.Split(string(p), "/")
|
||||
if len(parts) == 1 {
|
||||
return "tcp"
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
// State represents the state of a container.
|
||||
type State struct {
|
||||
sync.RWMutex
|
||||
Running bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
Ghost bool
|
||||
}
|
||||
|
||||
// String returns the string representation of a state.
|
||||
func (s *State) String() string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
if s.Running {
|
||||
if s.Ghost {
|
||||
return "Ghost"
|
||||
}
|
||||
return fmt.Sprintf("Up %s", time.Now().UTC().Sub(s.StartedAt))
|
||||
}
|
||||
return fmt.Sprintf("Exit %d", s.ExitCode)
|
||||
}
|
||||
|
||||
type PortBinding struct {
|
||||
HostIp string
|
||||
HostPort string
|
||||
}
|
||||
|
||||
type PortMapping map[string]string
|
||||
|
||||
type NetworkSettings struct {
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping
|
||||
Ports map[Port][]PortBinding
|
||||
}
|
||||
|
||||
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
||||
var mapping []APIPort
|
||||
for port, bindings := range settings.Ports {
|
||||
p, _ := parsePort(port.Port())
|
||||
if len(bindings) == 0 {
|
||||
mapping = append(mapping, APIPort{
|
||||
PublicPort: int64(p),
|
||||
Type: port.Proto(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
p, _ := parsePort(port.Port())
|
||||
h, _ := parsePort(binding.HostPort)
|
||||
mapping = append(mapping, APIPort{
|
||||
PrivatePort: int64(p),
|
||||
PublicPort: int64(h),
|
||||
Type: port.Proto(),
|
||||
IP: binding.HostIp,
|
||||
})
|
||||
}
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
func parsePort(rawPort string) (int, error) {
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Hostname string
|
||||
Domainname string
|
||||
User string
|
||||
Memory int64
|
||||
MemorySwap int64
|
||||
CpuShares int64
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string
|
||||
ExposedPorts map[Port]struct{}
|
||||
Tty bool
|
||||
OpenStdin bool
|
||||
StdinOnce bool
|
||||
Env []string
|
||||
Cmd []string
|
||||
Dns []string // For Docker API v1.9 and below only
|
||||
Image string
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
WorkingDir string
|
||||
Entrypoint []string
|
||||
NetworkDisabled bool
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
ID string
|
||||
|
||||
Created time.Time
|
||||
|
||||
Path string
|
||||
Args []string
|
||||
|
||||
Config *Config
|
||||
State State
|
||||
Image string
|
||||
|
||||
NetworkSettings *NetworkSettings
|
||||
|
||||
SysInitPath string
|
||||
ResolvConfPath string
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
Name string
|
||||
Driver string
|
||||
|
||||
Volumes map[string]string
|
||||
VolumesRW map[string]bool
|
||||
HostConfig *HostConfig
|
||||
}
|
||||
|
||||
// InspectContainer returns information about a container by its ID.
|
||||
//
|
||||
// See http://goo.gl/2o52Sx for more details.
|
||||
func (c *Client) InspectContainer(id string) (*Container, error) {
|
||||
path := "/containers/" + id + "/json"
|
||||
body, status, err := c.do("GET", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var container Container
|
||||
err = json.Unmarshal(body, &container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &container, nil
|
||||
}
|
||||
|
||||
// ContainerChanges returns changes in the filesystem of the given container.
|
||||
//
|
||||
// See http://goo.gl/DpGyzK for more details.
|
||||
func (c *Client) ContainerChanges(id string) ([]Change, error) {
|
||||
path := "/containers/" + id + "/changes"
|
||||
body, status, err := c.do("GET", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var changes []Change
|
||||
err = json.Unmarshal(body, &changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
// CreateContainerOptions specify parameters to the CreateContainer function.
|
||||
//
|
||||
// See http://goo.gl/WPPYtB for more details.
|
||||
type CreateContainerOptions struct {
|
||||
Name string
|
||||
Config *Config `qs:"-"`
|
||||
}
|
||||
|
||||
// CreateContainer creates a new container, returning the container instance,
|
||||
// or an error in case of failure.
|
||||
//
|
||||
// See http://goo.gl/tjihUc 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)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var container Container
|
||||
err = json.Unmarshal(body, &container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container.Name = opts.Name
|
||||
|
||||
return &container, nil
|
||||
}
|
||||
|
||||
type KeyValuePair struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
type HostConfig struct {
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
LxcConf []KeyValuePair
|
||||
Privileged bool
|
||||
PortBindings map[Port][]PortBinding
|
||||
Links []string
|
||||
PublishAllPorts bool
|
||||
Dns []string // For Docker API v1.10 and above only
|
||||
}
|
||||
|
||||
// StartContainer starts a container, returning an errror in case of failure.
|
||||
//
|
||||
// See http://goo.gl/y5GZlE 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)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopContainer stops a container, killing it after the given timeout (in
|
||||
// seconds).
|
||||
//
|
||||
// See http://goo.gl/X2mj8t 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)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestartContainer stops a container, killing it after the given timeout (in
|
||||
// seconds), during the stop process.
|
||||
//
|
||||
// See http://goo.gl/zms73Z 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)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KillContainerOptions represents the set of options that can be used in a
|
||||
// call to KillContainer.
|
||||
type KillContainerOptions struct {
|
||||
// The ID of the container.
|
||||
ID string `qs:"-"`
|
||||
|
||||
// The signal to send to the container. When omitted, Docker server
|
||||
// will assume SIGKILL.
|
||||
Signal Signal
|
||||
}
|
||||
|
||||
// KillContainer kills a container, returning an error in case of failure.
|
||||
//
|
||||
// See http://goo.gl/DPbbBy for more details.
|
||||
func (c *Client) KillContainer(opts KillContainerOptions) error {
|
||||
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveContainerOptions encapsulates options to remove a container.
|
||||
type RemoveContainerOptions struct {
|
||||
// The ID of the container.
|
||||
ID string `qs:"-"`
|
||||
|
||||
// A flag that indicates whether Docker should remove the volumes
|
||||
// associated to the container.
|
||||
RemoveVolumes bool `qs:"v"`
|
||||
|
||||
// A flag that indicates whether Docker should remove the container
|
||||
// even if it is currently running.
|
||||
Force bool
|
||||
}
|
||||
|
||||
// RemoveContainer removes a container, returning an error in case of failure.
|
||||
//
|
||||
// See http://goo.gl/PBvGdU for more details.
|
||||
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
|
||||
path := "/containers/" + opts.ID + "?" + queryString(opts)
|
||||
_, status, err := c.do("DELETE", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFromContainerOptions is the set of options that can be used when copying
|
||||
// files or folders from a container.
|
||||
//
|
||||
// See http://goo.gl/mnxRMl for more details.
|
||||
type CopyFromContainerOptions struct {
|
||||
OutputStream io.Writer `json:"-"`
|
||||
Container string `json:"-"`
|
||||
Resource string
|
||||
}
|
||||
|
||||
// CopyFromContainer copy files or folders from a container, using a given
|
||||
// resource.
|
||||
//
|
||||
// See http://goo.gl/mnxRMl for more details.
|
||||
func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
|
||||
if opts.Container == "" {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
url := fmt.Sprintf("/containers/%s/copy", opts.Container)
|
||||
body, status, err := c.do("POST", url, opts)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.Copy(opts.OutputStream, bytes.NewBuffer(body))
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitContainer blocks until the given container stops, return the exit code
|
||||
// of the container status.
|
||||
//
|
||||
// See http://goo.gl/gnHJL2 for more details.
|
||||
func (c *Client) WaitContainer(id string) (int, error) {
|
||||
body, status, err := c.do("POST", "/containers/"+id+"/wait", nil)
|
||||
if status == http.StatusNotFound {
|
||||
return 0, &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var r struct{ StatusCode int }
|
||||
err = json.Unmarshal(body, &r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r.StatusCode, nil
|
||||
}
|
||||
|
||||
// CommitContainerOptions aggregates parameters to the CommitContainer method.
|
||||
//
|
||||
// See http://goo.gl/628gxm for more details.
|
||||
type CommitContainerOptions struct {
|
||||
Container string
|
||||
Repository string `qs:"repo"`
|
||||
Tag string
|
||||
Message string `qs:"m"`
|
||||
Author string
|
||||
Run *Config `qs:"-"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
ID string `json:"id"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
Container string `json:"container,omitempty"`
|
||||
ContainerConfig Config `json:"container_config,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Config *Config `json:"config,omitempty"`
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
Size int64
|
||||
}
|
||||
|
||||
// CommitContainer creates a new image from a container's changes.
|
||||
//
|
||||
// See http://goo.gl/628gxm for more details.
|
||||
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
|
||||
path := "/commit?" + queryString(opts)
|
||||
body, status, err := c.do("POST", path, opts.Run)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var image Image
|
||||
err = json.Unmarshal(body, &image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &image, nil
|
||||
}
|
||||
|
||||
// AttachToContainerOptions is the set of options that can be used when
|
||||
// attaching to a container.
|
||||
//
|
||||
// See http://goo.gl/oPzcqH for more details.
|
||||
type AttachToContainerOptions struct {
|
||||
Container string `qs:"-"`
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
ErrorStream io.Writer `qs:"-"`
|
||||
|
||||
// Get container logs, sending it to OutputStream.
|
||||
Logs bool
|
||||
|
||||
// Stream the response?
|
||||
Stream bool
|
||||
|
||||
// Attach to stdin, and use InputFile.
|
||||
Stdin bool
|
||||
|
||||
// Attach to stdout, and use OutputStream.
|
||||
Stdout bool
|
||||
|
||||
// Attach to stderr, and use ErrorStream.
|
||||
Stderr bool
|
||||
|
||||
// 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{}
|
||||
}
|
||||
|
||||
// AttachToContainer attaches to a container, using the given options.
|
||||
//
|
||||
// See http://goo.gl/oPzcqH for more details.
|
||||
func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
|
||||
if opts.Container == "" {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
path := "/containers/" + opts.Container + "/attach?" + queryString(opts)
|
||||
return c.hijack("POST", path, opts.Success, opts.InputStream, opts.ErrorStream, opts.OutputStream)
|
||||
}
|
||||
|
||||
// ResizeContainerTTY resizes the terminal to the given height and width.
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExportContainerOptions is the set of parameters to the ExportContainer
|
||||
// method.
|
||||
//
|
||||
// See http://goo.gl/Lqk0FZ for more details.
|
||||
type ExportContainerOptions struct {
|
||||
ID string
|
||||
OutputStream io.Writer
|
||||
}
|
||||
|
||||
// ExportContainer export the contents of container id as tar archive
|
||||
// and prints the exported contents to stdout.
|
||||
//
|
||||
// See http://goo.gl/Lqk0FZ for more details.
|
||||
func (c *Client) ExportContainer(opts ExportContainerOptions) error {
|
||||
if opts.ID == "" {
|
||||
return NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
url := fmt.Sprintf("/containers/%s/export", opts.ID)
|
||||
return c.stream("GET", url, nil, nil, opts.OutputStream)
|
||||
}
|
||||
|
||||
// NoSuchContainer is the error returned when a given container does not exist.
|
||||
type NoSuchContainer struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (err NoSuchContainer) Error() string {
|
||||
return "No such container: " + err.ID
|
||||
}
|
888
third_party/github.com/fsouza/go-dockerclient/container_test.go
vendored
Normal file
888
third_party/github.com/fsouza/go-dockerclient/container_test.go
vendored
Normal file
@@ -0,0 +1,888 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListContainers(t *testing.T) {
|
||||
jsonContainers := `[
|
||||
{
|
||||
"Id": "8dfafdbc3a40",
|
||||
"Image": "base:latest",
|
||||
"Command": "echo 1",
|
||||
"Created": 1367854155,
|
||||
"Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
||||
"Status": "Exit 0"
|
||||
},
|
||||
{
|
||||
"Id": "9cd87474be90",
|
||||
"Image": "base:latest",
|
||||
"Command": "echo 222222",
|
||||
"Created": 1367854155,
|
||||
"Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
||||
"Status": "Exit 0"
|
||||
},
|
||||
{
|
||||
"Id": "3176a2479c92",
|
||||
"Image": "base:latest",
|
||||
"Command": "echo 3333333333333333",
|
||||
"Created": 1367854154,
|
||||
"Ports":[{"PrivatePort": 2221, "PublicPort": 3331, "Type": "tcp"}],
|
||||
"Status": "Exit 0"
|
||||
},
|
||||
{
|
||||
"Id": "4cb07b47f9fb",
|
||||
"Image": "base:latest",
|
||||
"Command": "echo 444444444444444444444444444444444",
|
||||
"Ports":[{"PrivatePort": 2223, "PublicPort": 3332, "Type": "tcp"}],
|
||||
"Created": 1367854152,
|
||||
"Status": "Exit 0"
|
||||
}
|
||||
]`
|
||||
var expected []APIContainers
|
||||
err := json.Unmarshal([]byte(jsonContainers), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := newTestClient(&FakeRoundTripper{message: jsonContainers, status: http.StatusOK})
|
||||
containers, err := client.ListContainers(ListContainersOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(containers, expected) {
|
||||
t.Errorf("ListContainers: Expected %#v. Got %#v.", expected, containers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListContainersParams(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input ListContainersOptions
|
||||
params map[string][]string
|
||||
}{
|
||||
{ListContainersOptions{}, map[string][]string{}},
|
||||
{ListContainersOptions{All: true}, map[string][]string{"all": {"1"}}},
|
||||
{ListContainersOptions{All: true, Limit: 10}, map[string][]string{"all": {"1"}, "limit": {"10"}}},
|
||||
{
|
||||
ListContainersOptions{All: true, Limit: 10, Since: "adf9983", Before: "abdeef"},
|
||||
map[string][]string{"all": {"1"}, "limit": {"10"}, "since": {"adf9983"}, "before": {"abdeef"}},
|
||||
},
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: "[]", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
u, _ := url.Parse(client.getURL("/containers/json"))
|
||||
for _, tt := range tests {
|
||||
client.ListContainers(tt.input)
|
||||
got := map[string][]string(fakeRT.requests[0].URL.Query())
|
||||
if !reflect.DeepEqual(got, tt.params) {
|
||||
t.Errorf("Expected %#v, got %#v.", tt.params, got)
|
||||
}
|
||||
if path := fakeRT.requests[0].URL.Path; path != u.Path {
|
||||
t.Errorf("Wrong path on request. Want %q. Got %q.", u.Path, path)
|
||||
}
|
||||
if meth := fakeRT.requests[0].Method; meth != "GET" {
|
||||
t.Errorf("Wrong HTTP method. Want GET. Got %s.", meth)
|
||||
}
|
||||
fakeRT.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestListContainersFailure(t *testing.T) {
|
||||
var tests = []struct {
|
||||
status int
|
||||
message string
|
||||
}{
|
||||
{400, "bad parameter"},
|
||||
{500, "internal server error"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client := newTestClient(&FakeRoundTripper{message: tt.message, status: tt.status})
|
||||
expected := Error{Status: tt.status, Message: tt.message}
|
||||
containers, err := client.ListContainers(ListContainersOptions{})
|
||||
if !reflect.DeepEqual(expected, *err.(*Error)) {
|
||||
t.Errorf("Wrong error in ListContainers. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
if len(containers) > 0 {
|
||||
t.Errorf("ListContainers failure. Expected empty list. Got %#v.", containers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainer(t *testing.T) {
|
||||
jsonContainer := `{
|
||||
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
|
||||
"Created": "2013-05-07T14:51:42.087658+02:00",
|
||||
"Path": "date",
|
||||
"Args": [],
|
||||
"Config": {
|
||||
"Hostname": "4fa6e0f0c678",
|
||||
"User": "",
|
||||
"Memory": 0,
|
||||
"MemorySwap": 0,
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": true,
|
||||
"AttachStderr": true,
|
||||
"PortSpecs": null,
|
||||
"Tty": false,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Env": null,
|
||||
"Cmd": [
|
||||
"date"
|
||||
],
|
||||
"Image": "base",
|
||||
"Volumes": {},
|
||||
"VolumesFrom": ""
|
||||
},
|
||||
"State": {
|
||||
"Running": false,
|
||||
"Pid": 0,
|
||||
"ExitCode": 0,
|
||||
"StartedAt": "2013-05-07T14:51:42.087658+02:00",
|
||||
"Ghost": false
|
||||
},
|
||||
"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"NetworkSettings": {
|
||||
"IpAddress": "",
|
||||
"IpPrefixLen": 0,
|
||||
"Gateway": "",
|
||||
"Bridge": "",
|
||||
"PortMapping": null
|
||||
},
|
||||
"SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker",
|
||||
"ResolvConfPath": "/etc/resolv.conf",
|
||||
"Volumes": {},
|
||||
"HostConfig": {
|
||||
"Binds": null,
|
||||
"ContainerIDFile": "",
|
||||
"LxcConf": [],
|
||||
"Privileged": false,
|
||||
"PortBindings": {
|
||||
"80/tcp": [
|
||||
{
|
||||
"HostIp": "0.0.0.0",
|
||||
"HostPort": "49153"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Links": null,
|
||||
"PublishAllPorts": false
|
||||
}
|
||||
}`
|
||||
var expected Container
|
||||
err := json.Unmarshal([]byte(jsonContainer), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c678"
|
||||
container, err := client.InspectContainer(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(*container, expected) {
|
||||
t.Errorf("InspectContainer(%q): Expected %#v. Got %#v.", id, expected, container)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/json"))
|
||||
if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("InspectContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainerFailure(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "server error", status: 500})
|
||||
expected := Error{Status: 500, Message: "server error"}
|
||||
container, err := client.InspectContainer("abe033")
|
||||
if container != nil {
|
||||
t.Errorf("InspectContainer: Expected <nil> container, got %#v", container)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, *err.(*Error)) {
|
||||
t.Errorf("InspectContainer: Wrong error information. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: 404})
|
||||
container, err := client.InspectContainer("abe033")
|
||||
if container != nil {
|
||||
t.Errorf("InspectContainer: Expected <nil> container, got %#v", container)
|
||||
}
|
||||
expected := &NoSuchContainer{ID: "abe033"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("InspectContainer: Wrong error information. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerChanges(t *testing.T) {
|
||||
jsonChanges := `[
|
||||
{
|
||||
"Path":"/dev",
|
||||
"Kind":0
|
||||
},
|
||||
{
|
||||
"Path":"/dev/kmsg",
|
||||
"Kind":1
|
||||
},
|
||||
{
|
||||
"Path":"/test",
|
||||
"Kind":1
|
||||
}
|
||||
]`
|
||||
var expected []Change
|
||||
err := json.Unmarshal([]byte(jsonChanges), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: jsonChanges, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c678"
|
||||
changes, err := client.ContainerChanges(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(changes, expected) {
|
||||
t.Errorf("ContainerChanges(%q): Expected %#v. Got %#v.", id, expected, changes)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/changes"))
|
||||
if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("ContainerChanges(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerChangesFailure(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "server error", status: 500})
|
||||
expected := Error{Status: 500, Message: "server error"}
|
||||
changes, err := client.ContainerChanges("abe033")
|
||||
if changes != nil {
|
||||
t.Errorf("ContainerChanges: Expected <nil> changes, got %#v", changes)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, *err.(*Error)) {
|
||||
t.Errorf("ContainerChanges: Wrong error information. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerChangesNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: 404})
|
||||
changes, err := client.ContainerChanges("abe033")
|
||||
if changes != nil {
|
||||
t.Errorf("ContainerChanges: Expected <nil> changes, got %#v", changes)
|
||||
}
|
||||
expected := &NoSuchContainer{ID: "abe033"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("ContainerChanges: Wrong error information. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainer(t *testing.T) {
|
||||
jsonContainer := `{
|
||||
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
|
||||
"Warnings": []
|
||||
}`
|
||||
var expected Container
|
||||
err := json.Unmarshal([]byte(jsonContainer), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
config := Config{AttachStdout: true, AttachStdin: true}
|
||||
opts := CreateContainerOptions{Name: "TestCreateContainer", Config: &config}
|
||||
container, err := client.CreateContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
if container.ID != id {
|
||||
t.Errorf("CreateContainer: wrong ID. Want %q. Got %q.", id, container.ID)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("CreateContainer: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/create"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("CreateContainer: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
|
||||
}
|
||||
var gotBody Config
|
||||
err = json.NewDecoder(req.Body).Decode(&gotBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerImageNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "No such image", status: http.StatusNotFound})
|
||||
config := Config{AttachStdout: true, AttachStdin: true}
|
||||
container, err := client.CreateContainer(CreateContainerOptions{Config: &config})
|
||||
if container != nil {
|
||||
t.Errorf("CreateContainer: expected <nil> container, got %#v.", container)
|
||||
}
|
||||
if !reflect.DeepEqual(err, ErrNoSuchImage) {
|
||||
t.Errorf("CreateContainer: Wrong error type. Want %#v. Got %#v.", ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.StartContainer(id, &HostConfig{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("StartContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/start"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("StartContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
expectedContentType := "application/json"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerNilHostConfig(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.StartContainer(id, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("StartContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/start"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("StartContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
expectedContentType := "application/json"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.StartContainer("a2344", &HostConfig{})
|
||||
expected := &NoSuchContainer{ID: "a2344"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("StartContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.StopContainer(id, 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("StopContainer(%q, 10): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/stop"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("StopContainer(%q, 10): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.StopContainer("a2334", 10)
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("StopContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.RestartContainer(id, 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("RestartContainer(%q, 10): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/restart"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("RestartContainer(%q, 10): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.RestartContainer("a2334", 10)
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("RestartContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKillContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.KillContainer(KillContainerOptions{ID: id})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("KillContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/kill"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("KillContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKillContainerSignal(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.KillContainer(KillContainerOptions{ID: id, Signal: SIGTERM})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("KillContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
if signal := req.URL.Query().Get("signal"); signal != "15" {
|
||||
t.Errorf("KillContainer(%q): Wrong query string in request. Want %q. Got %q.", id, "15", signal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKillContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.KillContainer(KillContainerOptions{ID: "a2334"})
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("KillContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
opts := RemoveContainerOptions{ID: id}
|
||||
err := client.RemoveContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "DELETE" {
|
||||
t.Errorf("RemoveContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "DELETE", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("RemoveContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerRemoveVolumes(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
opts := RemoveContainerOptions{ID: id, RemoveVolumes: true}
|
||||
err := client.RemoveContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
params := map[string][]string(req.URL.Query())
|
||||
expected := map[string][]string{"v": {"1"}}
|
||||
if !reflect.DeepEqual(params, expected) {
|
||||
t.Errorf("RemoveContainer(%q): wrong parameters. Want %#v. Got %#v.", id, expected, params)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.RemoveContainer(RemoveContainerOptions{ID: "a2334"})
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("RemoveContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResizeContainerTTY(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.ResizeContainerTTY(id, 40, 80)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("ResizeContainerTTY(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/resize"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("ResizeContainerTTY(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
expectedParams := map[string][]string{
|
||||
"w": {"80"},
|
||||
"h": {"40"},
|
||||
}
|
||||
if !reflect.DeepEqual(got, expectedParams) {
|
||||
t.Errorf("Expected %#v, got %#v.", expectedParams, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: `{"StatusCode": 56}`, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
status, err := client.WaitContainer(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != 56 {
|
||||
t.Errorf("WaitContainer(%q): wrong return. Want 56. Got %d.", id, status)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("WaitContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/wait"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("WaitContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
_, err := client.WaitContainer("a2334")
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("WaitContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainer(t *testing.T) {
|
||||
response := `{"Id":"596069db4bf5"}`
|
||||
client := newTestClient(&FakeRoundTripper{message: response, status: http.StatusOK})
|
||||
id := "596069db4bf5"
|
||||
image, err := client.CommitContainer(CommitContainerOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if image.ID != id {
|
||||
t.Errorf("CommitContainer: Wrong image id. Want %q. Got %q.", id, image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerParams(t *testing.T) {
|
||||
cfg := Config{Memory: 67108864}
|
||||
json, _ := json.Marshal(&cfg)
|
||||
var tests = []struct {
|
||||
input CommitContainerOptions
|
||||
params map[string][]string
|
||||
body []byte
|
||||
}{
|
||||
{CommitContainerOptions{}, map[string][]string{}, nil},
|
||||
{CommitContainerOptions{Container: "44c004db4b17"}, map[string][]string{"container": {"44c004db4b17"}}, nil},
|
||||
{
|
||||
CommitContainerOptions{Container: "44c004db4b17", Repository: "tsuru/python", Message: "something"},
|
||||
map[string][]string{"container": {"44c004db4b17"}, "repo": {"tsuru/python"}, "m": {"something"}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
CommitContainerOptions{Container: "44c004db4b17", Run: &cfg},
|
||||
map[string][]string{"container": {"44c004db4b17"}},
|
||||
json,
|
||||
},
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: "[]", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
u, _ := url.Parse(client.getURL("/commit"))
|
||||
for _, tt := range tests {
|
||||
client.CommitContainer(tt.input)
|
||||
got := map[string][]string(fakeRT.requests[0].URL.Query())
|
||||
if !reflect.DeepEqual(got, tt.params) {
|
||||
t.Errorf("Expected %#v, got %#v.", tt.params, got)
|
||||
}
|
||||
if path := fakeRT.requests[0].URL.Path; path != u.Path {
|
||||
t.Errorf("Wrong path on request. Want %q. Got %q.", u.Path, path)
|
||||
}
|
||||
if meth := fakeRT.requests[0].Method; meth != "POST" {
|
||||
t.Errorf("Wrong HTTP method. Want POST. Got %s.", meth)
|
||||
}
|
||||
if tt.body != nil {
|
||||
if requestBody, err := ioutil.ReadAll(fakeRT.requests[0].Body); err == nil {
|
||||
if bytes.Compare(requestBody, tt.body) != 0 {
|
||||
t.Errorf("Expected body %#v, got %#v", tt.body, requestBody)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Error reading request body: %#v", err)
|
||||
}
|
||||
}
|
||||
fakeRT.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerFailure(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusInternalServerError})
|
||||
_, err := client.CommitContainer(CommitContainerOptions{})
|
||||
if err == nil {
|
||||
t.Error("Expected non-nil error, got <nil>.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
_, err := client.CommitContainer(CommitContainerOptions{})
|
||||
expected := &NoSuchContainer{ID: ""}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("CommitContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachToContainerLogs(t *testing.T) {
|
||||
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, 19})
|
||||
w.Write([]byte("something happened!"))
|
||||
req = *r
|
||||
}))
|
||||
defer server.Close()
|
||||
client, _ := NewClient(server.URL)
|
||||
var buf bytes.Buffer
|
||||
opts := AttachToContainerOptions{
|
||||
Container: "a123456",
|
||||
OutputStream: &buf,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
Logs: true,
|
||||
}
|
||||
err := client.AttachToContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "something happened!"
|
||||
if buf.String() != expected {
|
||||
t.Errorf("AttachToContainer for logs: wrong output. Want %q. Got %q.", expected, buf.String())
|
||||
}
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("AttachToContainer: wrong HTTP method. Want POST. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/containers/a123456/attach"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
expectedQs := map[string][]string{
|
||||
"logs": {"1"},
|
||||
"stdout": {"1"},
|
||||
"stderr": {"1"},
|
||||
}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expectedQs) {
|
||||
t.Errorf("AttachToContainer: wrong query string. Want %#v. Got %#v.", expectedQs, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachToContainer(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)
|
||||
var stdout, stderr bytes.Buffer
|
||||
opts := AttachToContainerOptions{
|
||||
Container: "a123456",
|
||||
OutputStream: &stdout,
|
||||
ErrorStream: &stderr,
|
||||
InputStream: reader,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
Stream: true,
|
||||
}
|
||||
var err = client.AttachToContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := map[string][]string{
|
||||
"stdin": {"1"},
|
||||
"stdout": {"1"},
|
||||
"stderr": {"1"},
|
||||
"stream": {"1"},
|
||||
}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("AttachToContainer: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachToContainerWithoutContainer(t *testing.T) {
|
||||
var client Client
|
||||
err := client.AttachToContainer(AttachToContainerOptions{})
|
||||
expected := &NoSuchContainer{ID: ""}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("AttachToContainer: wrong error. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSuchContainerError(t *testing.T) {
|
||||
var err error = &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 TestExportContainer(t *testing.T) {
|
||||
content := "exported container tar content"
|
||||
out := stdoutMock{bytes.NewBufferString(content)}
|
||||
client := newTestClient(&FakeRoundTripper{status: http.StatusOK})
|
||||
opts := ExportContainerOptions{ID: "4fa6e0f0c678", OutputStream: out}
|
||||
err := client.ExportContainer(opts)
|
||||
if err != nil {
|
||||
t.Errorf("ExportContainer: caugh error %#v while exporting container, expected nil", err.Error())
|
||||
}
|
||||
if out.String() != content {
|
||||
t.Errorf("ExportContainer: wrong stdout. Want %#v. Got %#v.", content, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportContainerViaUnixSocket(t *testing.T) {
|
||||
if runtime.GOOS != "darwin" {
|
||||
t.Skip("skipping test on %q", runtime.GOOS)
|
||||
}
|
||||
content := "exported container tar content"
|
||||
var buf []byte
|
||||
out := bytes.NewBuffer(buf)
|
||||
tempSocket := tempfile("export_socket")
|
||||
defer os.Remove(tempSocket)
|
||||
endpoint := "unix://" + tempSocket
|
||||
u, _ := parseEndpoint(endpoint)
|
||||
client := Client{
|
||||
endpoint: endpoint,
|
||||
endpointURL: u,
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
listening := make(chan string)
|
||||
done := make(chan int)
|
||||
go runStreamConnServer(t, "unix", tempSocket, listening, done)
|
||||
<-listening // wait for server to start
|
||||
opts := ExportContainerOptions{ID: "4fa6e0f0c678", OutputStream: out}
|
||||
err := client.ExportContainer(opts)
|
||||
<-done // make sure server stopped
|
||||
if err != nil {
|
||||
t.Errorf("ExportContainer: caugh error %#v while exporting container, expected nil", err.Error())
|
||||
}
|
||||
if out.String() != content {
|
||||
t.Errorf("ExportContainer: wrong stdout. Want %#v. Got %#v.", content, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func runStreamConnServer(t *testing.T, network, laddr string, listening chan<- string, done chan<- int) {
|
||||
defer close(done)
|
||||
l, err := net.Listen(network, laddr)
|
||||
if err != nil {
|
||||
t.Errorf("Listen(%q, %q) failed: %v", network, laddr, err)
|
||||
listening <- "<nil>"
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
listening <- l.Addr().String()
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
t.Logf("Accept failed: %v", err)
|
||||
return
|
||||
}
|
||||
c.Write([]byte("HTTP/1.1 200 OK\n\nexported container tar content"))
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func tempfile(filename string) string {
|
||||
return os.TempDir() + "/" + filename + "." + strconv.Itoa(os.Getpid())
|
||||
}
|
||||
|
||||
func TestExportContainerNoId(t *testing.T) {
|
||||
client := Client{}
|
||||
out := stdoutMock{bytes.NewBufferString("")}
|
||||
err := client.ExportContainer(ExportContainerOptions{OutputStream: out})
|
||||
if err != (NoSuchContainer{}) {
|
||||
t.Errorf("ExportContainer: wrong error. Want %#v. Got %#v.", NoSuchContainer{}, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFromContainer(t *testing.T) {
|
||||
content := "File content"
|
||||
out := stdoutMock{bytes.NewBufferString(content)}
|
||||
client := newTestClient(&FakeRoundTripper{status: http.StatusOK})
|
||||
opts := CopyFromContainerOptions{
|
||||
Container: "a123456",
|
||||
OutputStream: out,
|
||||
}
|
||||
err := client.CopyFromContainer(opts)
|
||||
if err != nil {
|
||||
t.Errorf("CopyFromContainer: caugh error %#v while copying from container, expected nil", err.Error())
|
||||
}
|
||||
if out.String() != content {
|
||||
t.Errorf("CopyFromContainer: wrong stdout. Want %#v. Got %#v.", content, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFromContainerEmptyContainer(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{status: http.StatusOK})
|
||||
err := client.CopyFromContainer(CopyFromContainerOptions{})
|
||||
_, ok := err.(*NoSuchContainer)
|
||||
if !ok {
|
||||
t.Errorf("CopyFromContainer: invalid error returned. Want NoSuchContainer, got %#v.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassingNameOptToCreateContainerReturnsItInContainer(t *testing.T) {
|
||||
jsonContainer := `{
|
||||
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
|
||||
"Warnings": []
|
||||
}`
|
||||
fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
config := Config{AttachStdout: true, AttachStdin: true}
|
||||
opts := CreateContainerOptions{Name: "TestCreateContainer", Config: &config}
|
||||
container, err := client.CreateContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.Name != "TestCreateContainer" {
|
||||
t.Errorf("Container name expected to be TestCreateContainer, was %s", container.Name)
|
||||
}
|
||||
}
|
147
third_party/github.com/fsouza/go-dockerclient/engine/engine.go
vendored
Normal file
147
third_party/github.com/fsouza/go-dockerclient/engine/engine.go
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient/utils"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Handler func(*Job) Status
|
||||
|
||||
var globalHandlers map[string]Handler
|
||||
|
||||
func init() {
|
||||
globalHandlers = make(map[string]Handler)
|
||||
}
|
||||
|
||||
func Register(name string, handler Handler) error {
|
||||
_, exists := globalHandlers[name]
|
||||
if exists {
|
||||
return fmt.Errorf("Can't overwrite global handler for command %s", name)
|
||||
}
|
||||
globalHandlers[name] = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
// The Engine is the core of Docker.
|
||||
// It acts as a store for *containers*, and allows manipulation of these
|
||||
// containers by executing *jobs*.
|
||||
type Engine struct {
|
||||
root string
|
||||
handlers map[string]Handler
|
||||
hack Hack // data for temporary hackery (see hack.go)
|
||||
id string
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Stdin io.Reader
|
||||
}
|
||||
|
||||
func (eng *Engine) Root() string {
|
||||
return eng.root
|
||||
}
|
||||
|
||||
func (eng *Engine) Register(name string, handler Handler) error {
|
||||
eng.Logf("Register(%s) (handlers=%v)", name, eng.handlers)
|
||||
_, exists := eng.handlers[name]
|
||||
if exists {
|
||||
return fmt.Errorf("Can't overwrite handler for command %s", name)
|
||||
}
|
||||
eng.handlers[name] = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
// New initializes a new engine managing the directory specified at `root`.
|
||||
// `root` is used to store containers and any other state private to the engine.
|
||||
// Changing the contents of the root without executing a job will cause unspecified
|
||||
// behavior.
|
||||
func New(root string) (*Engine, error) {
|
||||
// Check for unsupported architectures
|
||||
if runtime.GOARCH != "amd64" {
|
||||
return nil, fmt.Errorf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
|
||||
}
|
||||
// Check for unsupported kernel versions
|
||||
// FIXME: it would be cleaner to not test for specific versions, but rather
|
||||
// test for specific functionalities.
|
||||
// Unfortunately we can't test for the feature "does not cause a kernel panic"
|
||||
// without actually causing a kernel panic, so we need this workaround until
|
||||
// the circumstances of pre-3.8 crashes are clearer.
|
||||
// For details see http://github.com/dotcloud/docker/issues/407
|
||||
if k, err := utils.GetKernelVersion(); err != nil {
|
||||
log.Printf("WARNING: %s\n", err)
|
||||
} else {
|
||||
if utils.CompareKernelVersion(k, &utils.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
|
||||
if os.Getenv("DOCKER_NOWARN_KERNEL_VERSION") == "" {
|
||||
log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Docker makes some assumptions about the "absoluteness" of root
|
||||
// ... so let's make sure it has no symlinks
|
||||
if p, err := filepath.Abs(root); err != nil {
|
||||
log.Fatalf("Unable to get absolute root (%s): %s", root, err)
|
||||
} else {
|
||||
root = p
|
||||
}
|
||||
if p, err := filepath.EvalSymlinks(root); err != nil {
|
||||
log.Fatalf("Unable to canonicalize root (%s): %s", root, err)
|
||||
} else {
|
||||
root = p
|
||||
}
|
||||
|
||||
eng := &Engine{
|
||||
root: root,
|
||||
handlers: make(map[string]Handler),
|
||||
id: utils.RandomString(),
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Stdin: os.Stdin,
|
||||
}
|
||||
// Copy existing global handlers
|
||||
for k, v := range globalHandlers {
|
||||
eng.handlers[k] = v
|
||||
}
|
||||
return eng, nil
|
||||
}
|
||||
|
||||
func (eng *Engine) String() string {
|
||||
return fmt.Sprintf("%s|%s", eng.Root(), eng.id[:8])
|
||||
}
|
||||
|
||||
// Job creates a new job which can later be executed.
|
||||
// This function mimics `Command` from the standard os/exec package.
|
||||
func (eng *Engine) Job(name string, args ...string) *Job {
|
||||
job := &Job{
|
||||
Eng: eng,
|
||||
Name: name,
|
||||
Args: args,
|
||||
Stdin: NewInput(),
|
||||
Stdout: NewOutput(),
|
||||
Stderr: NewOutput(),
|
||||
env: &Env{},
|
||||
}
|
||||
job.Stderr.Add(utils.NopWriteCloser(eng.Stderr))
|
||||
handler, exists := eng.handlers[name]
|
||||
if exists {
|
||||
job.handler = handler
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
|
||||
prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
|
||||
return fmt.Fprintf(eng.Stderr, prefixedFormat, args...)
|
||||
}
|
111
third_party/github.com/fsouza/go-dockerclient/engine/engine_test.go
vendored
Normal file
111
third_party/github.com/fsouza/go-dockerclient/engine/engine_test.go
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
if err := Register("dummy1", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Register("dummy1", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
|
||||
eng := newTestEngine(t)
|
||||
|
||||
//Should fail because global handlers are copied
|
||||
//at the engine creation
|
||||
if err := eng.Register("dummy1", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
|
||||
if err := eng.Register("dummy2", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := eng.Register("dummy2", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJob(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
job1 := eng.Job("dummy1", "--level=awesome")
|
||||
|
||||
if job1.handler != nil {
|
||||
t.Fatalf("job1.handler should be empty")
|
||||
}
|
||||
|
||||
h := func(j *Job) Status {
|
||||
j.Printf("%s\n", j.Name)
|
||||
return 42
|
||||
}
|
||||
|
||||
eng.Register("dummy2", h)
|
||||
job2 := eng.Job("dummy2", "--level=awesome")
|
||||
|
||||
if job2.handler == nil {
|
||||
t.Fatalf("job2.handler shouldn't be nil")
|
||||
}
|
||||
|
||||
if job2.handler(job2) != 42 {
|
||||
t.Fatalf("handler dummy2 was not found in job2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineRoot(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "docker-test-TestEngineCreateDir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
dir := path.Join(tmp, "dir")
|
||||
eng, err := New(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st, err := os.Stat(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !st.IsDir() {
|
||||
t.Fatalf("engine.New() created something other than a directory at %s", dir)
|
||||
}
|
||||
r := eng.Root()
|
||||
r, _ = filepath.EvalSymlinks(r)
|
||||
dir, _ = filepath.EvalSymlinks(dir)
|
||||
if r != dir {
|
||||
t.Fatalf("Expected: %v\nReceived: %v", dir, r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineString(t *testing.T) {
|
||||
eng1 := newTestEngine(t)
|
||||
defer os.RemoveAll(eng1.Root())
|
||||
eng2 := newTestEngine(t)
|
||||
defer os.RemoveAll(eng2.Root())
|
||||
s1 := eng1.String()
|
||||
s2 := eng2.String()
|
||||
if eng1 == eng2 {
|
||||
t.Fatalf("Different engines should have different names (%v == %v)", s1, s2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineLogf(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
input := "Test log line"
|
||||
if n, err := eng.Logf("%s\n", input); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n < len(input) {
|
||||
t.Fatalf("Test: Logf() should print at least as much as the input\ninput=%d\nprinted=%d", len(input), n)
|
||||
}
|
||||
}
|
238
third_party/github.com/fsouza/go-dockerclient/engine/env.go
vendored
Normal file
238
third_party/github.com/fsouza/go-dockerclient/engine/env.go
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Env []string
|
||||
|
||||
func (env *Env) Get(key string) (value string) {
|
||||
// FIXME: use Map()
|
||||
for _, kv := range *env {
|
||||
if strings.Index(kv, "=") == -1 {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
if parts[0] != key {
|
||||
continue
|
||||
}
|
||||
if len(parts) < 2 {
|
||||
value = ""
|
||||
} else {
|
||||
value = parts[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (env *Env) Exists(key string) bool {
|
||||
_, exists := env.Map()[key]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (env *Env) GetBool(key string) (value bool) {
|
||||
s := strings.ToLower(strings.Trim(env.Get(key), " \t"))
|
||||
if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (env *Env) SetBool(key string, value bool) {
|
||||
if value {
|
||||
env.Set(key, "1")
|
||||
} else {
|
||||
env.Set(key, "0")
|
||||
}
|
||||
}
|
||||
|
||||
func (env *Env) GetInt(key string) int {
|
||||
return int(env.GetInt64(key))
|
||||
}
|
||||
|
||||
func (env *Env) GetInt64(key string) int64 {
|
||||
s := strings.Trim(env.Get(key), " \t")
|
||||
val, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (env *Env) SetInt(key string, value int) {
|
||||
env.Set(key, fmt.Sprintf("%d", value))
|
||||
}
|
||||
|
||||
func (env *Env) SetInt64(key string, value int64) {
|
||||
env.Set(key, fmt.Sprintf("%d", value))
|
||||
}
|
||||
|
||||
// Returns nil if key not found
|
||||
func (env *Env) GetList(key string) []string {
|
||||
sval := env.Get(key)
|
||||
if sval == "" {
|
||||
return nil
|
||||
}
|
||||
l := make([]string, 0, 1)
|
||||
if err := json.Unmarshal([]byte(sval), &l); err != nil {
|
||||
l = append(l, sval)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (env *Env) GetJson(key string, iface interface{}) error {
|
||||
sval := env.Get(key)
|
||||
if sval == "" {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal([]byte(sval), iface)
|
||||
}
|
||||
|
||||
func (env *Env) SetJson(key string, value interface{}) error {
|
||||
sval, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env.Set(key, string(sval))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) SetList(key string, value []string) error {
|
||||
return env.SetJson(key, value)
|
||||
}
|
||||
|
||||
func (env *Env) Set(key, value string) {
|
||||
*env = append(*env, key+"="+value)
|
||||
}
|
||||
|
||||
func NewDecoder(src io.Reader) *Decoder {
|
||||
return &Decoder{
|
||||
json.NewDecoder(src),
|
||||
}
|
||||
}
|
||||
|
||||
type Decoder struct {
|
||||
*json.Decoder
|
||||
}
|
||||
|
||||
func (decoder *Decoder) Decode() (*Env, error) {
|
||||
m := make(map[string]interface{})
|
||||
if err := decoder.Decoder.Decode(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env := &Env{}
|
||||
for key, value := range m {
|
||||
env.SetAuto(key, value)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// DecodeEnv decodes `src` as a json dictionary, and adds
|
||||
// each decoded key-value pair to the environment.
|
||||
//
|
||||
// If `src` cannot be decoded as a json dictionary, an error
|
||||
// is returned.
|
||||
func (env *Env) Decode(src io.Reader) error {
|
||||
m := make(map[string]interface{})
|
||||
if err := json.NewDecoder(src).Decode(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
env.SetAuto(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) SetAuto(k string, v interface{}) {
|
||||
// FIXME: we fix-convert float values to int, because
|
||||
// encoding/json decodes integers to float64, but cannot encode them back.
|
||||
// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
|
||||
if fval, ok := v.(float64); ok {
|
||||
env.SetInt64(k, int64(fval))
|
||||
} else if sval, ok := v.(string); ok {
|
||||
env.Set(k, sval)
|
||||
} else if val, err := json.Marshal(v); err == nil {
|
||||
env.Set(k, string(val))
|
||||
} else {
|
||||
env.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
func (env *Env) Encode(dst io.Writer) error {
|
||||
m := make(map[string]interface{})
|
||||
for k, v := range env.Map() {
|
||||
var val interface{}
|
||||
if err := json.Unmarshal([]byte(v), &val); err == nil {
|
||||
// FIXME: we fix-convert float values to int, because
|
||||
// encoding/json decodes integers to float64, but cannot encode them back.
|
||||
// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
|
||||
if fval, isFloat := val.(float64); isFloat {
|
||||
val = int(fval)
|
||||
}
|
||||
m[k] = val
|
||||
} else {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
if err := json.NewEncoder(dst).Encode(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) WriteTo(dst io.Writer) (n int64, err error) {
|
||||
// FIXME: return the number of bytes written to respect io.WriterTo
|
||||
return 0, env.Encode(dst)
|
||||
}
|
||||
|
||||
func (env *Env) Export(dst interface{}) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("ExportEnv %s", err)
|
||||
}
|
||||
}()
|
||||
var buf bytes.Buffer
|
||||
// step 1: encode/marshal the env to an intermediary json representation
|
||||
if err := env.Encode(&buf); err != nil {
|
||||
return err
|
||||
}
|
||||
// step 2: decode/unmarshal the intermediary json into the destination object
|
||||
if err := json.NewDecoder(&buf).Decode(dst); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) Import(src interface{}) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("ImportEnv: %s", err)
|
||||
}
|
||||
}()
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(src); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := env.Decode(&buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) Map() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, kv := range *env {
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
m[parts[0]] = parts[1]
|
||||
}
|
||||
return m
|
||||
}
|
127
third_party/github.com/fsouza/go-dockerclient/engine/env_test.go
vendored
Normal file
127
third_party/github.com/fsouza/go-dockerclient/engine/env_test.go
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewJob(t *testing.T) {
|
||||
job := mkJob(t, "dummy", "--level=awesome")
|
||||
if job.Name != "dummy" {
|
||||
t.Fatalf("Wrong job name: %s", job.Name)
|
||||
}
|
||||
if len(job.Args) != 1 {
|
||||
t.Fatalf("Wrong number of job arguments: %d", len(job.Args))
|
||||
}
|
||||
if job.Args[0] != "--level=awesome" {
|
||||
t.Fatalf("Wrong job arguments: %s", job.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenv(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.Setenv("foo", "bar")
|
||||
if val := job.Getenv("foo"); val != "bar" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
|
||||
job.Setenv("bar", "")
|
||||
if val := job.Getenv("bar"); val != "" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
if val := job.Getenv("nonexistent"); val != "" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvBool(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.SetenvBool("foo", true)
|
||||
if val := job.GetenvBool("foo"); !val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %t", val)
|
||||
}
|
||||
|
||||
job.SetenvBool("bar", false)
|
||||
if val := job.GetenvBool("bar"); val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %t", val)
|
||||
}
|
||||
|
||||
if val := job.GetenvBool("nonexistent"); val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %t", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvInt(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
|
||||
job.SetenvInt("foo", -42)
|
||||
if val := job.GetenvInt("foo"); val != -42 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
|
||||
job.SetenvInt("bar", 42)
|
||||
if val := job.GetenvInt("bar"); val != 42 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
if val := job.GetenvInt("nonexistent"); val != -1 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvList(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
|
||||
job.SetenvList("foo", []string{"bar"})
|
||||
if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
|
||||
job.SetenvList("bar", nil)
|
||||
if val := job.GetenvList("bar"); val != nil {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
if val := job.GetenvList("nonexistent"); val != nil {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportEnv(t *testing.T) {
|
||||
type dummy struct {
|
||||
DummyInt int
|
||||
DummyStringArray []string
|
||||
}
|
||||
|
||||
job := mkJob(t, "dummy")
|
||||
if err := job.ImportEnv(&dummy{42, []string{"foo", "bar"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dmy := dummy{}
|
||||
if err := job.ExportEnv(&dmy); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if dmy.DummyInt != 42 {
|
||||
t.Fatalf("Expected 42, got %d", dmy.DummyInt)
|
||||
}
|
||||
|
||||
if len(dmy.DummyStringArray) != 2 || dmy.DummyStringArray[0] != "foo" || dmy.DummyStringArray[1] != "bar" {
|
||||
t.Fatalf("Expected {foo, bar}, got %v", dmy.DummyStringArray)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEnviron(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.Setenv("foo", "bar")
|
||||
val, exists := job.Environ()["foo"]
|
||||
if !exists {
|
||||
t.Fatalf("foo not found in the environ")
|
||||
}
|
||||
if val != "bar" {
|
||||
t.Fatalf("bar not found in the environ")
|
||||
}
|
||||
}
|
25
third_party/github.com/fsouza/go-dockerclient/engine/hack.go
vendored
Normal file
25
third_party/github.com/fsouza/go-dockerclient/engine/hack.go
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
type Hack map[string]interface{}
|
||||
|
||||
func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
|
||||
if eng.hack == nil {
|
||||
return nil
|
||||
}
|
||||
val, exists := eng.hack[key]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) {
|
||||
if eng.hack == nil {
|
||||
eng.hack = make(Hack)
|
||||
}
|
||||
eng.hack[key] = val
|
||||
}
|
28
third_party/github.com/fsouza/go-dockerclient/engine/helpers_test.go
vendored
Normal file
28
third_party/github.com/fsouza/go-dockerclient/engine/helpers_test.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var globalTestID string
|
||||
|
||||
func newTestEngine(t *testing.T) *Engine {
|
||||
tmp, err := ioutil.TempDir("", "asd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
eng, err := New(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return eng
|
||||
}
|
||||
|
||||
func mkJob(t *testing.T, name string, args ...string) *Job {
|
||||
return newTestEngine(t).Job(name, args...)
|
||||
}
|
44
third_party/github.com/fsouza/go-dockerclient/engine/http.go
vendored
Normal file
44
third_party/github.com/fsouza/go-dockerclient/engine/http.go
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// ServeHTTP executes a job as specified by the http request `r`, and sends the
|
||||
// result as an http response.
|
||||
// This method allows an Engine instance to be passed as a standard http.Handler interface.
|
||||
//
|
||||
// Note that the protocol used in this methid is a convenience wrapper and is not the canonical
|
||||
// implementation of remote job execution. This is because HTTP/1 does not handle stream multiplexing,
|
||||
// and so cannot differentiate stdout from stderr. Additionally, headers cannot be added to a response
|
||||
// once data has been written to the body, which makes it inconvenient to return metadata such
|
||||
// as the exit status.
|
||||
//
|
||||
func (eng *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
jobName := path.Base(r.URL.Path)
|
||||
jobArgs, exists := r.URL.Query()["a"]
|
||||
if !exists {
|
||||
jobArgs = []string{}
|
||||
}
|
||||
w.Header().Set("Job-Name", jobName)
|
||||
for _, arg := range jobArgs {
|
||||
w.Header().Add("Job-Args", arg)
|
||||
}
|
||||
job := eng.Job(jobName, jobArgs...)
|
||||
job.Stdout.Add(w)
|
||||
job.Stderr.Add(w)
|
||||
// FIXME: distinguish job status from engine error in Run()
|
||||
// The former should be passed as a special header, the former
|
||||
// should cause a 500 status
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// The exit status cannot be sent reliably with HTTP1, because headers
|
||||
// can only be sent before the body.
|
||||
// (we could possibly use http footers via chunked encoding, but I couldn't find
|
||||
// how to use them in net/http)
|
||||
job.Run()
|
||||
}
|
197
third_party/github.com/fsouza/go-dockerclient/engine/job.go
vendored
Normal file
197
third_party/github.com/fsouza/go-dockerclient/engine/job.go
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A job is the fundamental unit of work in the docker engine.
|
||||
// Everything docker can do should eventually be exposed as a job.
|
||||
// For example: execute a process in a container, create a new container,
|
||||
// download an archive from the internet, serve the http api, etc.
|
||||
//
|
||||
// The job API is designed after unix processes: a job has a name, arguments,
|
||||
// environment variables, standard streams for input, output and error, and
|
||||
// an exit status which can indicate success (0) or error (anything else).
|
||||
//
|
||||
// One slight variation is that jobs report their status as a string. The
|
||||
// string "0" indicates success, and any other strings indicates an error.
|
||||
// This allows for richer error reporting.
|
||||
//
|
||||
type Job struct {
|
||||
Eng *Engine
|
||||
Name string
|
||||
Args []string
|
||||
env *Env
|
||||
Stdout *Output
|
||||
Stderr *Output
|
||||
Stdin *Input
|
||||
handler Handler
|
||||
status Status
|
||||
end time.Time
|
||||
onExit []func()
|
||||
}
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
StatusOK Status = 0
|
||||
StatusErr Status = 1
|
||||
StatusNotFound Status = 127
|
||||
)
|
||||
|
||||
// Run executes the job and blocks until the job completes.
|
||||
// If the job returns a failure status, an error is returned
|
||||
// which includes the status.
|
||||
func (job *Job) Run() error {
|
||||
// FIXME: make this thread-safe
|
||||
// FIXME: implement wait
|
||||
if !job.end.IsZero() {
|
||||
return fmt.Errorf("%s: job has already completed", job.Name)
|
||||
}
|
||||
// Log beginning and end of the job
|
||||
job.Eng.Logf("+job %s", job.CallString())
|
||||
defer func() {
|
||||
job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString())
|
||||
}()
|
||||
var errorMessage string
|
||||
job.Stderr.AddString(&errorMessage)
|
||||
if job.handler == nil {
|
||||
job.Errorf("%s: command not found", job.Name)
|
||||
job.status = 127
|
||||
} else {
|
||||
job.status = job.handler(job)
|
||||
job.end = time.Now()
|
||||
}
|
||||
// Wait for all background tasks to complete
|
||||
if err := job.Stdout.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := job.Stderr.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if job.status != 0 {
|
||||
return fmt.Errorf("%s: %s", job.Name, errorMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (job *Job) CallString() string {
|
||||
return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", "))
|
||||
}
|
||||
|
||||
func (job *Job) StatusString() string {
|
||||
// If the job hasn't completed, status string is empty
|
||||
if job.end.IsZero() {
|
||||
return ""
|
||||
}
|
||||
var okerr string
|
||||
if job.status == StatusOK {
|
||||
okerr = "OK"
|
||||
} else {
|
||||
okerr = "ERR"
|
||||
}
|
||||
return fmt.Sprintf(" = %s (%d)", okerr, job.status)
|
||||
}
|
||||
|
||||
// String returns a human-readable description of `job`
|
||||
func (job *Job) String() string {
|
||||
return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
|
||||
}
|
||||
|
||||
func (job *Job) Getenv(key string) (value string) {
|
||||
return job.env.Get(key)
|
||||
}
|
||||
|
||||
func (job *Job) GetenvBool(key string) (value bool) {
|
||||
return job.env.GetBool(key)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvBool(key string, value bool) {
|
||||
job.env.SetBool(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) GetenvInt64(key string) int64 {
|
||||
return job.env.GetInt64(key)
|
||||
}
|
||||
|
||||
func (job *Job) GetenvInt(key string) int {
|
||||
return job.env.GetInt(key)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvInt64(key string, value int64) {
|
||||
job.env.SetInt64(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvInt(key string, value int) {
|
||||
job.env.SetInt(key, value)
|
||||
}
|
||||
|
||||
// Returns nil if key not found
|
||||
func (job *Job) GetenvList(key string) []string {
|
||||
return job.env.GetList(key)
|
||||
}
|
||||
|
||||
func (job *Job) GetenvJson(key string, iface interface{}) error {
|
||||
return job.env.GetJson(key, iface)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvJson(key string, value interface{}) error {
|
||||
return job.env.SetJson(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvList(key string, value []string) error {
|
||||
return job.env.SetJson(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) Setenv(key, value string) {
|
||||
job.env.Set(key, value)
|
||||
}
|
||||
|
||||
// DecodeEnv decodes `src` as a json dictionary, and adds
|
||||
// each decoded key-value pair to the environment.
|
||||
//
|
||||
// If `src` cannot be decoded as a json dictionary, an error
|
||||
// is returned.
|
||||
func (job *Job) DecodeEnv(src io.Reader) error {
|
||||
return job.env.Decode(src)
|
||||
}
|
||||
|
||||
func (job *Job) EncodeEnv(dst io.Writer) error {
|
||||
return job.env.Encode(dst)
|
||||
}
|
||||
|
||||
func (job *Job) ExportEnv(dst interface{}) (err error) {
|
||||
return job.env.Export(dst)
|
||||
}
|
||||
|
||||
func (job *Job) ImportEnv(src interface{}) (err error) {
|
||||
return job.env.Import(src)
|
||||
}
|
||||
|
||||
func (job *Job) Environ() map[string]string {
|
||||
return job.env.Map()
|
||||
}
|
||||
|
||||
func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {
|
||||
prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n"))
|
||||
return fmt.Fprintf(job.Stderr, prefixedFormat, args...)
|
||||
}
|
||||
|
||||
func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(job.Stdout, format, args...)
|
||||
}
|
||||
|
||||
func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(job.Stderr, format, args...)
|
||||
}
|
||||
|
||||
func (job *Job) Error(err error) (int, error) {
|
||||
return fmt.Fprintf(job.Stderr, "%s", err)
|
||||
}
|
84
third_party/github.com/fsouza/go-dockerclient/engine/job_test.go
vendored
Normal file
84
third_party/github.com/fsouza/go-dockerclient/engine/job_test.go
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJobStatusOK(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
eng.Register("return_ok", func(job *Job) Status { return StatusOK })
|
||||
err := eng.Job("return_ok").Run()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected: err=%v\nReceived: err=%v", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobStatusErr(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
eng.Register("return_err", func(job *Job) Status { return StatusErr })
|
||||
err := eng.Job("return_err").Run()
|
||||
if err == nil {
|
||||
t.Fatalf("When a job returns StatusErr, Run() should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobStatusNotFound(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
eng.Register("return_not_found", func(job *Job) Status { return StatusNotFound })
|
||||
err := eng.Job("return_not_found").Run()
|
||||
if err == nil {
|
||||
t.Fatalf("When a job returns StatusNotFound, Run() should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobStdoutString(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
// FIXME: test multiple combinations of output and status
|
||||
eng.Register("say_something_in_stdout", func(job *Job) Status {
|
||||
job.Printf("Hello world\n")
|
||||
return StatusOK
|
||||
})
|
||||
|
||||
job := eng.Job("say_something_in_stdout")
|
||||
var output string
|
||||
if err := job.Stdout.AddString(&output); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expectedOutput := "Hello world"; output != expectedOutput {
|
||||
t.Fatalf("Stdout last line:\nExpected: %v\nReceived: %v", expectedOutput, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobStderrString(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
// FIXME: test multiple combinations of output and status
|
||||
eng.Register("say_something_in_stderr", func(job *Job) Status {
|
||||
job.Errorf("Warning, something might happen\nHere it comes!\nOh no...\nSomething happened\n")
|
||||
return StatusOK
|
||||
})
|
||||
|
||||
job := eng.Job("say_something_in_stderr")
|
||||
var output string
|
||||
if err := job.Stderr.AddString(&output); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expectedOutput := "Something happened"; output != expectedOutput {
|
||||
t.Fatalf("Stderr last line:\nExpected: %v\nReceived: %v", expectedOutput, output)
|
||||
}
|
||||
}
|
196
third_party/github.com/fsouza/go-dockerclient/engine/streams.go
vendored
Normal file
196
third_party/github.com/fsouza/go-dockerclient/engine/streams.go
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/ring"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Output struct {
|
||||
sync.Mutex
|
||||
dests []io.Writer
|
||||
tasks sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewOutput returns a new Output object with no destinations attached.
|
||||
// Writing to an empty Output will cause the written data to be discarded.
|
||||
func NewOutput() *Output {
|
||||
return &Output{}
|
||||
}
|
||||
|
||||
// Add attaches a new destination to the Output. Any data subsequently written
|
||||
// to the output will be written to the new destination in addition to all the others.
|
||||
// This method is thread-safe.
|
||||
// FIXME: Add cannot fail
|
||||
func (o *Output) Add(dst io.Writer) error {
|
||||
o.Mutex.Lock()
|
||||
defer o.Mutex.Unlock()
|
||||
o.dests = append(o.dests, dst)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPipe creates an in-memory pipe with io.Pipe(), adds its writing end as a destination,
|
||||
// and returns its reading end for consumption by the caller.
|
||||
// This is a rough equivalent similar to Cmd.StdoutPipe() in the standard os/exec package.
|
||||
// This method is thread-safe.
|
||||
func (o *Output) AddPipe() (io.Reader, error) {
|
||||
r, w := io.Pipe()
|
||||
o.Add(w)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// AddTail starts a new goroutine which will read all subsequent data written to the output,
|
||||
// line by line, and append the last `n` lines to `dst`.
|
||||
func (o *Output) AddTail(dst *[]string, n int) error {
|
||||
src, err := o.AddPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.tasks.Add(1)
|
||||
go func() {
|
||||
defer o.tasks.Done()
|
||||
Tail(src, n, dst)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddString starts a new goroutine which will read all subsequent data written to the output,
|
||||
// line by line, and store the last line into `dst`.
|
||||
func (o *Output) AddString(dst *string) error {
|
||||
src, err := o.AddPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.tasks.Add(1)
|
||||
go func() {
|
||||
defer o.tasks.Done()
|
||||
lines := make([]string, 0, 1)
|
||||
Tail(src, 1, &lines)
|
||||
if len(lines) == 0 {
|
||||
*dst = ""
|
||||
} else {
|
||||
*dst = lines[0]
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes the same data to all registered destinations.
|
||||
// This method is thread-safe.
|
||||
func (o *Output) Write(p []byte) (n int, err error) {
|
||||
o.Mutex.Lock()
|
||||
defer o.Mutex.Unlock()
|
||||
var firstErr error
|
||||
for _, dst := range o.dests {
|
||||
_, err := dst.Write(p)
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return len(p), firstErr
|
||||
}
|
||||
|
||||
// Close unregisters all destinations and waits for all background
|
||||
// AddTail and AddString tasks to complete.
|
||||
// The Close method of each destination is called if it exists.
|
||||
func (o *Output) Close() error {
|
||||
o.Mutex.Lock()
|
||||
defer o.Mutex.Unlock()
|
||||
var firstErr error
|
||||
for _, dst := range o.dests {
|
||||
if closer, ok := dst.(io.WriteCloser); ok {
|
||||
err := closer.Close()
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
o.tasks.Wait()
|
||||
return firstErr
|
||||
}
|
||||
|
||||
type Input struct {
|
||||
src io.Reader
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewInput returns a new Input object with no source attached.
|
||||
// Reading to an empty Input will return io.EOF.
|
||||
func NewInput() *Input {
|
||||
return &Input{}
|
||||
}
|
||||
|
||||
// Read reads from the input in a thread-safe way.
|
||||
func (i *Input) Read(p []byte) (n int, err error) {
|
||||
i.Mutex.Lock()
|
||||
defer i.Mutex.Unlock()
|
||||
if i.src == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return i.src.Read(p)
|
||||
}
|
||||
|
||||
// Add attaches a new source to the input.
|
||||
// Add can only be called once per input. Subsequent calls will
|
||||
// return an error.
|
||||
func (i *Input) Add(src io.Reader) error {
|
||||
i.Mutex.Lock()
|
||||
defer i.Mutex.Unlock()
|
||||
if i.src != nil {
|
||||
return fmt.Errorf("Maximum number of sources reached: 1")
|
||||
}
|
||||
i.src = src
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tail reads from `src` line per line, and returns the last `n` lines as an array.
|
||||
// A ring buffer is used to only store `n` lines at any time.
|
||||
func Tail(src io.Reader, n int, dst *[]string) {
|
||||
scanner := bufio.NewScanner(src)
|
||||
r := ring.New(n)
|
||||
for scanner.Scan() {
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
r.Value = scanner.Text()
|
||||
r = r.Next()
|
||||
}
|
||||
r.Do(func(v interface{}) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
*dst = append(*dst, v.(string))
|
||||
})
|
||||
}
|
||||
|
||||
// AddEnv starts a new goroutine which will decode all subsequent data
|
||||
// as a stream of json-encoded objects, and point `dst` to the last
|
||||
// decoded object.
|
||||
// The result `env` can be queried using the type-neutral Env interface.
|
||||
// It is not safe to query `env` until the Output is closed.
|
||||
func (o *Output) AddEnv() (dst *Env, err error) {
|
||||
src, err := o.AddPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dst = &Env{}
|
||||
o.tasks.Add(1)
|
||||
go func() {
|
||||
defer o.tasks.Done()
|
||||
decoder := NewDecoder(src)
|
||||
for {
|
||||
env, err := decoder.Decode()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
*dst = *env
|
||||
}
|
||||
}()
|
||||
return dst, nil
|
||||
}
|
298
third_party/github.com/fsouza/go-dockerclient/engine/streams_test.go
vendored
Normal file
298
third_party/github.com/fsouza/go-dockerclient/engine/streams_test.go
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOutputAddString(t *testing.T) {
|
||||
var testInputs = [][2]string{
|
||||
{
|
||||
"hello, world!",
|
||||
"hello, world!",
|
||||
},
|
||||
|
||||
{
|
||||
"One\nTwo\nThree",
|
||||
"Three",
|
||||
},
|
||||
|
||||
{
|
||||
"",
|
||||
"",
|
||||
},
|
||||
|
||||
{
|
||||
"A line\nThen another nl-terminated line\n",
|
||||
"Then another nl-terminated line",
|
||||
},
|
||||
|
||||
{
|
||||
"A line followed by an empty line\n\n",
|
||||
"",
|
||||
},
|
||||
}
|
||||
for _, testData := range testInputs {
|
||||
input := testData[0]
|
||||
expectedOutput := testData[1]
|
||||
o := NewOutput()
|
||||
var output string
|
||||
if err := o.AddString(&output); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if n, err := o.Write([]byte(input)); err != nil {
|
||||
t.Error(err)
|
||||
} else if n != len(input) {
|
||||
t.Errorf("Expected %d, got %d", len(input), n)
|
||||
}
|
||||
o.Close()
|
||||
if output != expectedOutput {
|
||||
t.Errorf("Last line is not stored as return string.\nInput: '%s'\nExpected: '%s'\nGot: '%s'", input, expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type sentinelWriteCloser struct {
|
||||
calledWrite bool
|
||||
calledClose bool
|
||||
}
|
||||
|
||||
func (w *sentinelWriteCloser) Write(p []byte) (int, error) {
|
||||
w.calledWrite = true
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *sentinelWriteCloser) Close() error {
|
||||
w.calledClose = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestOutputAddEnv(t *testing.T) {
|
||||
input := "{\"foo\": \"bar\", \"answer_to_life_the_universe_and_everything\": 42}"
|
||||
o := NewOutput()
|
||||
result, err := o.AddEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
o.Write([]byte(input))
|
||||
o.Close()
|
||||
if v := result.Get("foo"); v != "bar" {
|
||||
t.Errorf("Expected %v, got %v", "bar", v)
|
||||
}
|
||||
if v := result.GetInt("answer_to_life_the_universe_and_everything"); v != 42 {
|
||||
t.Errorf("Expected %v, got %v", 42, v)
|
||||
}
|
||||
if v := result.Get("this-value-doesnt-exist"); v != "" {
|
||||
t.Errorf("Expected %v, got %v", "", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputAddClose(t *testing.T) {
|
||||
o := NewOutput()
|
||||
var s sentinelWriteCloser
|
||||
if err := o.Add(&s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := o.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Write data after the output is closed.
|
||||
// Write should succeed, but no destination should receive it.
|
||||
if _, err := o.Write([]byte("foo bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !s.calledClose {
|
||||
t.Fatal("Output.Close() didn't close the destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputAddPipe(t *testing.T) {
|
||||
var testInputs = []string{
|
||||
"hello, world!",
|
||||
"One\nTwo\nThree",
|
||||
"",
|
||||
"A line\nThen another nl-terminated line\n",
|
||||
"A line followed by an empty line\n\n",
|
||||
}
|
||||
for _, input := range testInputs {
|
||||
expectedOutput := input
|
||||
o := NewOutput()
|
||||
r, err := o.AddPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go func(o *Output) {
|
||||
if n, err := o.Write([]byte(input)); err != nil {
|
||||
t.Error(err)
|
||||
} else if n != len(input) {
|
||||
t.Errorf("Expected %d, got %d", len(input), n)
|
||||
}
|
||||
if err := o.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}(o)
|
||||
output, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != expectedOutput {
|
||||
t.Errorf("Last line is not stored as return string.\nExpected: '%s'\nGot: '%s'", expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTail(t *testing.T) {
|
||||
var tests = make(map[string][][]string)
|
||||
tests["hello, world!"] = [][]string{
|
||||
{},
|
||||
{"hello, world!"},
|
||||
{"hello, world!"},
|
||||
{"hello, world!"},
|
||||
}
|
||||
tests["One\nTwo\nThree"] = [][]string{
|
||||
{},
|
||||
{"Three"},
|
||||
{"Two", "Three"},
|
||||
{"One", "Two", "Three"},
|
||||
}
|
||||
for input, outputs := range tests {
|
||||
for n, expectedOutput := range outputs {
|
||||
var output []string
|
||||
Tail(strings.NewReader(input), n, &output)
|
||||
if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) {
|
||||
t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot : '%s'", n, expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputAddTail(t *testing.T) {
|
||||
var tests = make(map[string][][]string)
|
||||
tests["hello, world!"] = [][]string{
|
||||
{},
|
||||
{"hello, world!"},
|
||||
{"hello, world!"},
|
||||
{"hello, world!"},
|
||||
}
|
||||
tests["One\nTwo\nThree"] = [][]string{
|
||||
{},
|
||||
{"Three"},
|
||||
{"Two", "Three"},
|
||||
{"One", "Two", "Three"},
|
||||
}
|
||||
for input, outputs := range tests {
|
||||
for n, expectedOutput := range outputs {
|
||||
o := NewOutput()
|
||||
var output []string
|
||||
if err := o.AddTail(&output, n); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if n, err := o.Write([]byte(input)); err != nil {
|
||||
t.Error(err)
|
||||
} else if n != len(input) {
|
||||
t.Errorf("Expected %d, got %d", len(input), n)
|
||||
}
|
||||
o.Close()
|
||||
if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) {
|
||||
t.Errorf("Tail(%d) returned wrong result.\nExpected: %v\nGot: %v", n, expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lastLine(txt string) string {
|
||||
scanner := bufio.NewScanner(strings.NewReader(txt))
|
||||
var lastLine string
|
||||
for scanner.Scan() {
|
||||
lastLine = scanner.Text()
|
||||
}
|
||||
return lastLine
|
||||
}
|
||||
|
||||
func TestOutputAdd(t *testing.T) {
|
||||
o := NewOutput()
|
||||
b := &bytes.Buffer{}
|
||||
o.Add(b)
|
||||
input := "hello, world!"
|
||||
if n, err := o.Write([]byte(input)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n != len(input) {
|
||||
t.Fatalf("Expected %d, got %d", len(input), n)
|
||||
}
|
||||
if output := b.String(); output != input {
|
||||
t.Fatalf("Received wrong data from Add.\nExpected: '%s'\nGot: '%s'", input, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputWriteError(t *testing.T) {
|
||||
o := NewOutput()
|
||||
buf := &bytes.Buffer{}
|
||||
o.Add(buf)
|
||||
r, w := io.Pipe()
|
||||
input := "Hello there"
|
||||
expectedErr := fmt.Errorf("This is an error")
|
||||
r.CloseWithError(expectedErr)
|
||||
o.Add(w)
|
||||
n, err := o.Write([]byte(input))
|
||||
if err != expectedErr {
|
||||
t.Fatalf("Output.Write() should return the first error encountered, if any")
|
||||
}
|
||||
if buf.String() != input {
|
||||
t.Fatalf("Output.Write() should attempt write on all destinations, even after encountering an error")
|
||||
}
|
||||
if n != len(input) {
|
||||
t.Fatalf("Output.Write() should return the size of the input if it successfully writes to at least one destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputAddEmpty(t *testing.T) {
|
||||
i := NewInput()
|
||||
var b bytes.Buffer
|
||||
if err := i.Add(&b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := ioutil.ReadAll(i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
t.Fatalf("Read from empty input shoul yield no data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputAddTwo(t *testing.T) {
|
||||
i := NewInput()
|
||||
var b1 bytes.Buffer
|
||||
// First add should succeed
|
||||
if err := i.Add(&b1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var b2 bytes.Buffer
|
||||
// Second add should fail
|
||||
if err := i.Add(&b2); err == nil {
|
||||
t.Fatalf("Adding a second source should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputAddNotEmpty(t *testing.T) {
|
||||
i := NewInput()
|
||||
b := bytes.NewBufferString("hello world\nabc")
|
||||
expectedResult := b.String()
|
||||
i.Add(b)
|
||||
result, err := ioutil.ReadAll(i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(result) != expectedResult {
|
||||
t.Fatalf("Expected: %v\nReceived: %v", expectedResult, result)
|
||||
}
|
||||
}
|
279
third_party/github.com/fsouza/go-dockerclient/event.go
vendored
Normal file
279
third_party/github.com/fsouza/go-dockerclient/event.go
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// APIEvents represents an event returned by the API.
|
||||
type APIEvents struct {
|
||||
Status string
|
||||
ID string
|
||||
From string
|
||||
Time int64
|
||||
}
|
||||
|
||||
type eventMonitoringState struct {
|
||||
sync.RWMutex
|
||||
sync.WaitGroup
|
||||
enabled bool
|
||||
lastSeen *int64
|
||||
C chan *APIEvents
|
||||
errC chan error
|
||||
listeners []chan<- *APIEvents
|
||||
}
|
||||
|
||||
const (
|
||||
maxMonitorConnRetries = 5
|
||||
retryInitialWaitTime = 10.
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoListeners is the error returned when no listeners are available
|
||||
// to receive an event.
|
||||
ErrNoListeners = errors.New("no listeners present to receive event")
|
||||
|
||||
// ErrListenerAlreadyExists is the error returned when the listerner already
|
||||
// exists.
|
||||
ErrListenerAlreadyExists = errors.New("listener already exists for docker events")
|
||||
)
|
||||
|
||||
// AddEventListener adds a new listener to container events in the Docker API.
|
||||
//
|
||||
// The parameter is a channel through which events will be sent.
|
||||
func (c *Client) AddEventListener(listener chan<- *APIEvents) error {
|
||||
var err error
|
||||
if !c.eventMonitor.isEnabled() {
|
||||
err = c.eventMonitor.enableEventMonitoring(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = c.eventMonitor.addListener(listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveEventListener removes a listener from the monitor.
|
||||
func (c *Client) RemoveEventListener(listener chan *APIEvents) error {
|
||||
err := c.eventMonitor.removeListener(listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c.eventMonitor.listeners) == 0 {
|
||||
err = c.eventMonitor.disableEventMonitoring()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if listenerExists(listener, &eventState.listeners) {
|
||||
return ErrListenerAlreadyExists
|
||||
}
|
||||
eventState.Add(1)
|
||||
eventState.listeners = append(eventState.listeners, listener)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if listenerExists(listener, &eventState.listeners) {
|
||||
var newListeners []chan<- *APIEvents
|
||||
for _, l := range eventState.listeners {
|
||||
if l != listener {
|
||||
newListeners = append(newListeners, l)
|
||||
}
|
||||
}
|
||||
eventState.listeners = newListeners
|
||||
eventState.Add(-1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
|
||||
for _, b := range *list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if !eventState.enabled {
|
||||
eventState.enabled = true
|
||||
var lastSeenDefault = int64(0)
|
||||
eventState.lastSeen = &lastSeenDefault
|
||||
eventState.C = make(chan *APIEvents, 100)
|
||||
eventState.errC = make(chan error, 1)
|
||||
go eventState.monitorEvents(c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) disableEventMonitoring() error {
|
||||
eventState.Wait()
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if eventState.enabled {
|
||||
eventState.enabled = false
|
||||
close(eventState.C)
|
||||
close(eventState.errC)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) monitorEvents(c *Client) {
|
||||
var err error
|
||||
for eventState.noListeners() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if err = eventState.connectWithRetry(c); err != nil {
|
||||
eventState.terminate(err)
|
||||
}
|
||||
for eventState.isEnabled() {
|
||||
timeout := time.After(100 * time.Millisecond)
|
||||
select {
|
||||
case ev, ok := <-eventState.C:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
go eventState.sendEvent(ev)
|
||||
go eventState.updateLastSeen(ev)
|
||||
case err = <-eventState.errC:
|
||||
if err == ErrNoListeners {
|
||||
eventState.terminate(nil)
|
||||
return
|
||||
} else if err != nil {
|
||||
defer func() { go eventState.monitorEvents(c) }()
|
||||
return
|
||||
}
|
||||
case <-timeout:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) connectWithRetry(c *Client) error {
|
||||
var retries int
|
||||
var err error
|
||||
for err = c.eventHijack(atomic.LoadInt64(eventState.lastSeen), eventState.C, eventState.errC); err != nil && retries < maxMonitorConnRetries; retries++ {
|
||||
waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries)))
|
||||
time.Sleep(time.Duration(waitTime) * time.Millisecond)
|
||||
err = c.eventHijack(atomic.LoadInt64(eventState.lastSeen), eventState.C, eventState.errC)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) noListeners() bool {
|
||||
eventState.RLock()
|
||||
defer eventState.RUnlock()
|
||||
return len(eventState.listeners) == 0
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) isEnabled() bool {
|
||||
eventState.RLock()
|
||||
defer eventState.RUnlock()
|
||||
return eventState.enabled
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
|
||||
eventState.RLock()
|
||||
defer eventState.RUnlock()
|
||||
eventState.Add(1)
|
||||
defer eventState.Done()
|
||||
if eventState.isEnabled() {
|
||||
if eventState.noListeners() {
|
||||
eventState.errC <- ErrNoListeners
|
||||
return
|
||||
}
|
||||
|
||||
for _, listener := range eventState.listeners {
|
||||
listener <- event
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if atomic.LoadInt64(eventState.lastSeen) < e.Time {
|
||||
atomic.StoreInt64(eventState.lastSeen, e.Time)
|
||||
}
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) terminate(err error) {
|
||||
eventState.disableEventMonitoring()
|
||||
}
|
||||
|
||||
func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error {
|
||||
uri := "/events"
|
||||
if startTime != 0 {
|
||||
uri += fmt.Sprintf("?since=%d", startTime)
|
||||
}
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if protocol != "unix" {
|
||||
protocol = "tcp"
|
||||
address = c.endpointURL.Host
|
||||
}
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn := httputil.NewClientConn(dial, nil)
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := conn.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func(res *http.Response, conn *httputil.ClientConn) {
|
||||
defer conn.Close()
|
||||
defer res.Body.Close()
|
||||
decoder := json.NewDecoder(res.Body)
|
||||
for {
|
||||
var event APIEvents
|
||||
if err = decoder.Decode(&event); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
errChan <- err
|
||||
}
|
||||
if event.Time == 0 {
|
||||
continue
|
||||
}
|
||||
if !c.eventMonitor.isEnabled() {
|
||||
return
|
||||
} else {
|
||||
c.eventMonitor.C <- &event
|
||||
}
|
||||
}
|
||||
}(res, conn)
|
||||
return nil
|
||||
}
|
92
third_party/github.com/fsouza/go-dockerclient/event_test.go
vendored
Normal file
92
third_party/github.com/fsouza/go-dockerclient/event_test.go
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEventListeners(t *testing.T) {
|
||||
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}
|
||||
{"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970}
|
||||
`
|
||||
|
||||
var req http.Request
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rsc := bufio.NewScanner(strings.NewReader(response))
|
||||
for rsc.Scan() {
|
||||
w.Write([]byte(rsc.Text()))
|
||||
w.(http.Flusher).Flush()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
req = *r
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create client: %s", err)
|
||||
}
|
||||
|
||||
listener := make(chan *APIEvents, 10)
|
||||
defer func() { time.Sleep(10 * time.Millisecond); client.RemoveEventListener(listener) }()
|
||||
|
||||
err = client.AddEventListener(listener)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to add event listener: %s", err)
|
||||
}
|
||||
|
||||
timeout := time.After(1 * time.Second)
|
||||
var count int
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-listener:
|
||||
t.Logf("Recieved: %s", *msg)
|
||||
count++
|
||||
err = checkEvent(count, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Check event failed: %s", err)
|
||||
}
|
||||
if count == 4 {
|
||||
return
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatal("TestAddEventListener timed out waiting on events")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkEvent(index int, event *APIEvents) error {
|
||||
if event.ID != "dfdf82bd3881" {
|
||||
return fmt.Errorf("event ID did not match. Expected dfdf82bd3881 got %s", event.ID)
|
||||
}
|
||||
if event.From != "base:latest" {
|
||||
return fmt.Errorf("event from did not match. Expected base:latest got %s", event.From)
|
||||
}
|
||||
var status string
|
||||
switch index {
|
||||
case 1:
|
||||
status = "create"
|
||||
case 2:
|
||||
status = "start"
|
||||
case 3:
|
||||
status = "stop"
|
||||
case 4:
|
||||
status = "destroy"
|
||||
}
|
||||
if event.Status != status {
|
||||
return fmt.Errorf("event status did not match. Expected %s got %s", status, event.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
133
third_party/github.com/fsouza/go-dockerclient/example_test.go
vendored
Normal file
133
third_party/github.com/fsouza/go-dockerclient/example_test.go
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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.
|
||||
|
||||
package docker_test
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func ExampleClient_AttachToContainer() {
|
||||
client, err := docker.NewClient("http://localhost:4243")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Reading logs from container a84849 and sending them to buf.
|
||||
var buf bytes.Buffer
|
||||
err = client.AttachToContainer(docker.AttachToContainerOptions{
|
||||
Container: "a84849",
|
||||
OutputStream: &buf,
|
||||
Logs: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(buf.String())
|
||||
// Attaching to stdout and streaming.
|
||||
buf.Reset()
|
||||
err = client.AttachToContainer(docker.AttachToContainerOptions{
|
||||
Container: "a84849",
|
||||
OutputStream: &buf,
|
||||
Stdout: true,
|
||||
Stream: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(buf.String())
|
||||
}
|
||||
|
||||
func ExampleClient_CopyFromContainer() {
|
||||
client, err := docker.NewClient("http://localhost:4243")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cid := "a84849"
|
||||
// Copy resulting file
|
||||
var buf bytes.Buffer
|
||||
filename := "/tmp/output.txt"
|
||||
err = client.CopyFromContainer(docker.CopyFromContainerOptions{
|
||||
Container: cid,
|
||||
Resource: filename,
|
||||
OutputStream: &buf,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error while copying from %s: %s\n", cid, err)
|
||||
}
|
||||
content := new(bytes.Buffer)
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
tr := tar.NewReader(r)
|
||||
tr.Next()
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err := io.Copy(content, tr); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(buf.String())
|
||||
}
|
||||
|
||||
func ExampleClient_BuildImage() {
|
||||
client, err := docker.NewClient("http://localhost:4243")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
inputbuf, outputbuf := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
|
||||
tr := tar.NewWriter(inputbuf)
|
||||
tr.WriteHeader(&tar.Header{Name: "Dockerfile", Size: 10, ModTime: t, AccessTime: t, ChangeTime: t})
|
||||
tr.Write([]byte("FROM base\n"))
|
||||
tr.Close()
|
||||
opts := docker.BuildImageOptions{
|
||||
Name: "test",
|
||||
InputStream: inputbuf,
|
||||
OutputStream: outputbuf,
|
||||
}
|
||||
if err := client.BuildImage(opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_ListenEvents() {
|
||||
client, err := docker.NewClient("http://localhost:4243")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
listener := make(chan *docker.APIEvents)
|
||||
err = client.AddEventListener(listener)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
|
||||
err = client.RemoveEventListener(listener)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
timeout := time.After(1 * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-listener:
|
||||
log.Println(msg)
|
||||
case <-timeout:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
265
third_party/github.com/fsouza/go-dockerclient/image.go
vendored
Normal file
265
third_party/github.com/fsouza/go-dockerclient/image.go
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
// APIImages represent an image returned in the ListImages call.
|
||||
type APIImages struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNoSuchImage is the error returned when the image does not exist.
|
||||
ErrNoSuchImage = errors.New("no such image")
|
||||
|
||||
// ErrMissingRepo is the error returned when the remote repository is
|
||||
// missing.
|
||||
ErrMissingRepo = errors.New("missing remote repository e.g. 'github.com/user/repo'")
|
||||
|
||||
// ErrMissingOutputStream is the error returned when no output stream
|
||||
// is provided to some calls, like BuildImage.
|
||||
ErrMissingOutputStream = errors.New("missing output stream")
|
||||
)
|
||||
|
||||
// ListImages returns the list of available images in the server.
|
||||
//
|
||||
// See http://goo.gl/dkMrwP 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var images []APIImages
|
||||
err = json.Unmarshal(body, &images)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// RemoveImage removes an image by its name or ID.
|
||||
//
|
||||
// See http://goo.gl/7hjHHy for more details.
|
||||
func (c *Client) RemoveImage(name string) error {
|
||||
_, status, err := c.do("DELETE", "/images/"+name, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// InspectImage returns an image by its name or ID.
|
||||
//
|
||||
// See http://goo.gl/pHEbma for more details.
|
||||
func (c *Client) InspectImage(name string) (*Image, error) {
|
||||
body, status, err := c.do("GET", "/images/"+name+"/json", nil)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var image Image
|
||||
err = json.Unmarshal(body, &image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &image, nil
|
||||
}
|
||||
|
||||
// PushImageOptions represents options to use in the PushImage method.
|
||||
//
|
||||
// See http://goo.gl/GBmyhc for more details.
|
||||
type PushImageOptions struct {
|
||||
// Name of the image
|
||||
Name string
|
||||
|
||||
// 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 authencation in the Docker index server.
|
||||
type AuthConfiguration struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// PushImage pushes an image to a remote registry, logging progress to w.
|
||||
//
|
||||
// An empty instance of AuthConfiguration may be used for unauthenticated
|
||||
// pushes.
|
||||
//
|
||||
// See http://goo.gl/GBmyhc for more details.
|
||||
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error {
|
||||
if opts.Name == "" {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
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, headers, nil, opts.OutputStream)
|
||||
}
|
||||
|
||||
// PullImageOptions present the set of options available for pulling an image
|
||||
// from a registry.
|
||||
//
|
||||
// See http://goo.gl/PhBKnS for more details.
|
||||
type PullImageOptions struct {
|
||||
Repository string `qs:"fromImage"`
|
||||
Registry string
|
||||
Tag string
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
}
|
||||
|
||||
// PullImage pulls an image from a remote registry, logging progress to w.
|
||||
//
|
||||
// See http://goo.gl/PhBKnS for more details.
|
||||
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
|
||||
if opts.Repository == "" {
|
||||
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())
|
||||
|
||||
return c.createImage(queryString(&opts), headers, nil, opts.OutputStream)
|
||||
}
|
||||
|
||||
func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer) error {
|
||||
path := "/images/create?" + qs
|
||||
return c.stream("POST", path, headers, in, w)
|
||||
}
|
||||
|
||||
// ImportImageOptions present the set of informations available for importing
|
||||
// an image from a source file or the stdin.
|
||||
//
|
||||
// See http://goo.gl/PhBKnS for more details.
|
||||
type ImportImageOptions struct {
|
||||
Repository string `qs:"repo"`
|
||||
Source string `qs:"fromSrc"`
|
||||
Tag string `qs:"tag"`
|
||||
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
}
|
||||
|
||||
// ImportImage imports an image from a url, a file or stdin
|
||||
//
|
||||
// See http://goo.gl/PhBKnS for more details.
|
||||
func (c *Client) ImportImage(opts ImportImageOptions) error {
|
||||
if opts.Repository == "" {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
if opts.Source != "-" {
|
||||
opts.InputStream = nil
|
||||
}
|
||||
if opts.Source != "-" && !isURL(opts.Source) {
|
||||
f, err := os.Open(opts.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := ioutil.ReadAll(f)
|
||||
opts.InputStream = bytes.NewBuffer(b)
|
||||
opts.Source = "-"
|
||||
}
|
||||
return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream)
|
||||
}
|
||||
|
||||
// BuildImageOptions present the set of informations available for building
|
||||
// an image from a tarfile with a Dockerfile in it,the details about Dockerfile
|
||||
// see http://docs.docker.io/en/latest/reference/builder/
|
||||
type BuildImageOptions struct {
|
||||
Name string `qs:"t"`
|
||||
NoCache bool `qs:"nocache"`
|
||||
SuppressOutput bool `qs:"q"`
|
||||
RmTmpContainer bool `qs:"rm"`
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
Remote string `qs:"remote"`
|
||||
}
|
||||
|
||||
// BuildImage builds an image from a tarball's url or a Dockerfile in the input
|
||||
// stream.
|
||||
func (c *Client) BuildImage(opts BuildImageOptions) error {
|
||||
if opts.OutputStream == nil {
|
||||
return ErrMissingOutputStream
|
||||
}
|
||||
var headers map[string]string
|
||||
if opts.Remote != "" && opts.Name == "" {
|
||||
opts.Name = opts.Remote
|
||||
}
|
||||
if opts.InputStream != nil {
|
||||
headers = map[string]string{"Content-Type": "application/tar"}
|
||||
} else if opts.Remote == "" {
|
||||
return ErrMissingRepo
|
||||
}
|
||||
return c.stream("POST", fmt.Sprintf("/build?%s",
|
||||
queryString(&opts)), headers, opts.InputStream, opts.OutputStream)
|
||||
}
|
||||
|
||||
// TagImageOptions present the set of options to tag an image
|
||||
type TagImageOptions struct {
|
||||
Repo string `qs:"repo"`
|
||||
Force bool `qs:"force"`
|
||||
}
|
||||
|
||||
// TagImage adds a tag to the image 'name'
|
||||
func (c *Client) TagImage(name string, opts TagImageOptions) error {
|
||||
if name == "" {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
_, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
|
||||
queryString(&opts)), nil)
|
||||
if status == http.StatusNotFound {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func isURL(u string) bool {
|
||||
p, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return p.Scheme == "http" || p.Scheme == "https"
|
||||
}
|
641
third_party/github.com/fsouza/go-dockerclient/image_test.go
vendored
Normal file
641
third_party/github.com/fsouza/go-dockerclient/image_test.go
vendored
Normal file
@@ -0,0 +1,641 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newTestClient(rt *FakeRoundTripper) Client {
|
||||
endpoint := "http://localhost:4243"
|
||||
u, _ := parseEndpoint("http://localhost:4243")
|
||||
client := Client{
|
||||
endpoint: endpoint,
|
||||
endpointURL: u,
|
||||
client: &http.Client{Transport: rt},
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
type stdoutMock struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (m stdoutMock) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stdinMock struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (m stdinMock) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestListImages(t *testing.T) {
|
||||
body := `[
|
||||
{
|
||||
"Repository":"base",
|
||||
"Tag":"ubuntu-12.10",
|
||||
"Id":"b750fe79269d",
|
||||
"Created":1364102658
|
||||
},
|
||||
{
|
||||
"Repository":"base",
|
||||
"Tag":"ubuntu-quantal",
|
||||
"Id":"b750fe79269d",
|
||||
"Created":1364102658
|
||||
},
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.04",
|
||||
"ubuntu:precise",
|
||||
"ubuntu:latest"
|
||||
],
|
||||
"Id": "8dbd9e392a964c",
|
||||
"Created": 1365714795,
|
||||
"Size": 131506275,
|
||||
"VirtualSize": 131506275
|
||||
},
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.10",
|
||||
"ubuntu:quantal"
|
||||
],
|
||||
"ParentId": "27cf784147099545",
|
||||
"Id": "b750fe79269d2e",
|
||||
"Created": 1364102658,
|
||||
"Size": 24653,
|
||||
"VirtualSize": 180116135
|
||||
}
|
||||
]`
|
||||
var expected []APIImages
|
||||
err := json.Unmarshal([]byte(body), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
|
||||
images, err := client.ListImages(false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(images, expected) {
|
||||
t.Errorf("ListImages: Wrong return value. Want %#v. Got %#v.", expected, images)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListImagesParameters(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "null", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
_, err := client.ListImages(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)
|
||||
}
|
||||
if all := req.URL.Query().Get("all"); all != "0" {
|
||||
t.Errorf("ListImages(false): Wrong parameter. Want all=0. Got all=%s", all)
|
||||
}
|
||||
fakeRT.Reset()
|
||||
_, err = client.ListImages(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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImage(t *testing.T) {
|
||||
name := "test"
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
err := client.RemoveImage(name)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImageNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such image", status: http.StatusNotFound})
|
||||
err := client.RemoveImage("test:")
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("RemoveImage: wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectImage(t *testing.T) {
|
||||
body := `{
|
||||
"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"parent":"27cf784147099545",
|
||||
"created":"2013-03-23T22:24:18.818426-07:00",
|
||||
"container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
|
||||
"container_config":{"Memory":0}
|
||||
}`
|
||||
var expected Image
|
||||
json.Unmarshal([]byte(body), &expected)
|
||||
fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
image, err := client.InspectImage(expected.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(*image, expected) {
|
||||
t.Errorf("InspectImage(%q): Wrong image returned. Want %#v. Got %#v.", expected.ID, expected, *image)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("InspectImage(%q): Wrong HTTP method. Want GET. Got %s.", expected.ID, req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/images/" + expected.ID + "/json"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("InspectImage(%q): Wrong request URL. Want %q. Got %q.", expected.ID, u.Path, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectImageNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such image", status: http.StatusNotFound})
|
||||
name := "test"
|
||||
image, err := client.InspectImage(name)
|
||||
if image != nil {
|
||||
t.Errorf("InspectImage(%q): expected <nil> image, got %#v.", name, image)
|
||||
}
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("InspectImage(%q): wrong error. Want %#v. Got %#v.", name, ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImage(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
err := client.PushImage(PushImageOptions{Name: "test", OutputStream: &buf}, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "Pushing 1/100"
|
||||
if buf.String() != expected {
|
||||
t.Errorf("PushImage: Wrong output. Want %q. Got %q.", expected, buf.String())
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("PushImage: Wrong HTTP method. Want POST. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/images/test/push"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("PushImage: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
if query := req.URL.Query().Encode(); query != "" {
|
||||
t.Errorf("PushImage: Wrong query string. Want no parameters, got %q.", query)
|
||||
}
|
||||
|
||||
auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth"))
|
||||
if err != nil {
|
||||
t.Errorf("PushImage: caught error decoding auth. %#v", err.Error())
|
||||
}
|
||||
if strings.TrimSpace(string(auth)) != "{}" {
|
||||
t.Errorf("PushImage: wrong body. Want %q. Got %q.",
|
||||
base64.URLEncoding.EncodeToString([]byte("{}")), req.Header.Get("X-Registry-Auth"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageWithAuthentication(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
inputAuth := AuthConfiguration{
|
||||
Username: "gopher",
|
||||
Password: "gopher123",
|
||||
Email: "gopher@tsuru.io",
|
||||
}
|
||||
err := client.PushImage(PushImageOptions{Name: "test", OutputStream: &buf}, inputAuth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
var gotAuth AuthConfiguration
|
||||
|
||||
auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth"))
|
||||
if err != nil {
|
||||
t.Errorf("PushImage: caught error decoding auth. %#v", err.Error())
|
||||
}
|
||||
|
||||
err = json.Unmarshal(auth, &gotAuth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(gotAuth, inputAuth) {
|
||||
t.Errorf("PushImage: wrong auth configuration. Want %#v. Got %#v.", inputAuth, gotAuth)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageCustomRegistry(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var authConfig AuthConfiguration
|
||||
var buf bytes.Buffer
|
||||
opts := PushImageOptions{
|
||||
Name: "test", Registry: "docker.tsuru.io",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.PushImage(opts, authConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expectedQuery := "registry=docker.tsuru.io"
|
||||
if query := req.URL.Query().Encode(); query != expectedQuery {
|
||||
t.Errorf("PushImage: Wrong query string. Want %q. Got %q.", expectedQuery, query)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageNoName(t *testing.T) {
|
||||
client := Client{}
|
||||
err := client.PushImage(PushImageOptions{}, AuthConfiguration{})
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("PushImage: got wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImage(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
err := client.PullImage(PullImageOptions{Repository: "base", OutputStream: &buf},
|
||||
AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "Pulling 1/100"
|
||||
if buf.String() != expected {
|
||||
t.Errorf("PullImage: Wrong output. Want %q. Got %q.", expected, buf.String())
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("PullImage: Wrong HTTP method. Want POST. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/images/create"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("PullImage: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
expectedQuery := "fromImage=base"
|
||||
if query := req.URL.Query().Encode(); query != expectedQuery {
|
||||
t.Errorf("PullImage: Wrong query strin. Want %q. Got %q.", expectedQuery, query)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImageWithoutOutputStream(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := PullImageOptions{
|
||||
Repository: "base",
|
||||
Registry: "docker.tsuru.io",
|
||||
}
|
||||
err := client.PullImage(opts, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImageCustomRegistry(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := PullImageOptions{
|
||||
Repository: "base",
|
||||
Registry: "docker.tsuru.io",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.PullImage(opts, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImageTag(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := PullImageOptions{
|
||||
Repository: "base",
|
||||
Registry: "docker.tsuru.io",
|
||||
Tag: "latest",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.PullImage(opts, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}, "tag": {"latest"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImageNoRepository(t *testing.T) {
|
||||
var opts PullImageOptions
|
||||
client := Client{}
|
||||
err := client.PullImage(opts, AuthConfiguration{})
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("PullImage: got wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageFromUrl(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := ImportImageOptions{
|
||||
Source: "http://mycompany.com/file.tar",
|
||||
Repository: "testimage",
|
||||
Tag: "tag",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}, "tag": {opts.Tag}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageFromInput(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
in := bytes.NewBufferString("tar content")
|
||||
var buf bytes.Buffer
|
||||
opts := ImportImageOptions{
|
||||
Source: "-", Repository: "testimage",
|
||||
InputStream: in, OutputStream: &buf,
|
||||
Tag: "tag",
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}, "tag": {opts.Tag}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("ImportImage: caugth error while reading body %#v", err.Error())
|
||||
}
|
||||
e := "tar content"
|
||||
if string(body) != e {
|
||||
t.Errorf("ImportImage: wrong body. Want %#v. Got %#v.", e, string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageDoesNotPassesInputIfSourceIsNotDash(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
in := bytes.NewBufferString("foo")
|
||||
opts := ImportImageOptions{
|
||||
Source: "http://test.com/container.tar", Repository: "testimage",
|
||||
InputStream: in, OutputStream: &buf,
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("ImportImage: caugth error while reading body %#v", err.Error())
|
||||
}
|
||||
if string(body) != "" {
|
||||
t.Errorf("ImportImage: wrong body. Want nothing. Got %#v.", string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageShouldPassTarContentToBodyWhenSourceIsFilePath(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
tarPath := "testing/data/container.tar"
|
||||
opts := ImportImageOptions{
|
||||
Source: tarPath, Repository: "testimage",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tar, err := os.Open(tarPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
tarContent, err := ioutil.ReadAll(tar)
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(tarContent, body) {
|
||||
t.Errorf("ImportImage: wrong body. Want %#v content. Got %#v.", tarPath, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageShouldChangeSourceToDashWhenItsAFilePath(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
tarPath := "testing/data/container.tar"
|
||||
opts := ImportImageOptions{
|
||||
Source: tarPath, Repository: "testimage",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromSrc": {"-"}, "repo": {opts.Repository}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageParameters(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,
|
||||
InputStream: &buf,
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != nil && strings.Index(err.Error(), "build image fail") == -1 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"t": {opts.Name}, "nocache": {"1"}, "q": {"1"}, "rm": {"1"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageParametersForRemoteBuild(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Name: "testImage",
|
||||
Remote: "testing/data/container.tar",
|
||||
SuppressOutput: true,
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"t": {opts.Name}, "remote": {opts.Remote}, "q": {"1"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageMissingRepoAndNilInput(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Name: "testImage",
|
||||
SuppressOutput: true,
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != ErrMissingRepo {
|
||||
t.Errorf("BuildImage: wrong error returned. Want %#v. Got %#v.", ErrMissingRepo, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageMissingOutputStream(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := BuildImageOptions{Name: "testImage"}
|
||||
err := client.BuildImage(opts)
|
||||
if err != ErrMissingOutputStream {
|
||||
t.Errorf("BuildImage: wrong error returned. Want %#v. Got %#v.", ErrMissingOutputStream, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageRemoteWithoutName(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Remote: "testing/data/container.tar",
|
||||
SuppressOutput: true,
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"t": {opts.Remote}, "remote": {opts.Remote}, "q": {"1"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagImageParameters(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := TagImageOptions{Repo: "testImage"}
|
||||
err := client.TagImage("base", opts)
|
||||
if err != nil && strings.Index(err.Error(), "tag image fail") == -1 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := "http://localhost:4243/images/base/tag?repo=testImage"
|
||||
got := req.URL.String()
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("TagImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagImageMissingRepo(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := TagImageOptions{Repo: "testImage"}
|
||||
err := client.TagImage("", opts)
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("TestTag: wrong error returned. Want %#v. Got %#v.",
|
||||
ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUrl(t *testing.T) {
|
||||
url := "http://foo.bar/"
|
||||
result := isURL(url)
|
||||
if !result {
|
||||
t.Errorf("isURL: wrong match. Expected %#v to be a url. Got %#v.", url, result)
|
||||
}
|
||||
url = "/foo/bar.tar"
|
||||
result = isURL(url)
|
||||
if result {
|
||||
t.Errorf("isURL: wrong match. Expected %#v to not be a url. Got %#v", url, result)
|
||||
}
|
||||
}
|
46
third_party/github.com/fsouza/go-dockerclient/misc.go
vendored
Normal file
46
third_party/github.com/fsouza/go-dockerclient/misc.go
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/fsouza/go-dockerclient/engine"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Version returns version information about the docker server.
|
||||
//
|
||||
// See http://goo.gl/IqKNRE for more details.
|
||||
func (c *Client) Version() (*engine.Env, error) {
|
||||
body, _, err := c.do("GET", "/version", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := engine.NewOutput()
|
||||
remoteVersion, err := out.AddEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.Copy(out, bytes.NewReader(body)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return remoteVersion, nil
|
||||
}
|
||||
|
||||
// Info returns system-wide information, like the number of running containers.
|
||||
//
|
||||
// See http://goo.gl/LOmySw for more details.
|
||||
func (c *Client) Info() (*engine.Env, error) {
|
||||
body, _, err := c.do("GET", "/info", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var info engine.Env
|
||||
err = info.Decode(bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, nil
|
||||
}
|
122
third_party/github.com/fsouza/go-dockerclient/misc_test.go
vendored
Normal file
122
third_party/github.com/fsouza/go-dockerclient/misc_test.go
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/fsouza/go-dockerclient/engine"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type DockerVersion struct {
|
||||
Version string
|
||||
GitCommit string
|
||||
GoVersion string
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
body := `{
|
||||
"Version":"0.2.2",
|
||||
"GitCommit":"5a2a5cc+CHANGES",
|
||||
"GoVersion":"go1.0.3"
|
||||
}`
|
||||
fakeRT := FakeRoundTripper{message: body, status: http.StatusOK}
|
||||
client := newTestClient(&fakeRT)
|
||||
expected := DockerVersion{
|
||||
Version: "0.2.2",
|
||||
GitCommit: "5a2a5cc+CHANGES",
|
||||
GoVersion: "go1.0.3",
|
||||
}
|
||||
version, err := client.Version()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result := version.Get("Version"); result != expected.Version {
|
||||
t.Errorf("Version(): Wrong result. Want %#v. Got %#v.", expected.Version, version.Get("Version"))
|
||||
}
|
||||
if result := version.Get("GitCommit"); result != expected.GitCommit {
|
||||
t.Errorf("GitCommit(): Wrong result. Want %#v. Got %#v.", expected.GitCommit, version.Get("GitCommit"))
|
||||
}
|
||||
if result := version.Get("GoVersion"); result != expected.GoVersion {
|
||||
t.Errorf("GoVersion(): Wrong result. Want %#v. Got %#v.", expected.GoVersion, version.Get("GoVersion"))
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("Version(): wrong request method. Want GET. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/version"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("Version(): wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionError(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "internal error", status: http.StatusInternalServerError}
|
||||
client := newTestClient(fakeRT)
|
||||
version, err := client.Version()
|
||||
if version != nil {
|
||||
t.Errorf("Version(): expected <nil> value, got %#v.", version)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("Version(): unexpected <nil> error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
body := `{
|
||||
"Containers":11,
|
||||
"Images":16,
|
||||
"Debug":0,
|
||||
"NFd":11,
|
||||
"NGoroutines":21,
|
||||
"MemoryLimit":1,
|
||||
"SwapLimit":0
|
||||
}`
|
||||
fakeRT := FakeRoundTripper{message: body, status: http.StatusOK}
|
||||
client := newTestClient(&fakeRT)
|
||||
expected := engine.Env{}
|
||||
expected.SetInt("Containers", 11)
|
||||
expected.SetInt("Images", 16)
|
||||
expected.SetBool("Debug", false)
|
||||
expected.SetInt("NFd", 11)
|
||||
expected.SetInt("NGoroutines", 21)
|
||||
expected.SetBool("MemoryLimit", true)
|
||||
expected.SetBool("SwapLimit", false)
|
||||
info, err := client.Info()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
infoSlice := []string(*info)
|
||||
expectedSlice := []string(expected)
|
||||
sort.Strings(infoSlice)
|
||||
sort.Strings(expectedSlice)
|
||||
if !reflect.DeepEqual(expectedSlice, infoSlice) {
|
||||
t.Errorf("Info(): Wrong result.\nWant %#v.\nGot %#v.", expected, *info)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("Info(): Wrong HTTP method. Want GET. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/info"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("Info(): Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoError(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "internal error", status: http.StatusInternalServerError}
|
||||
client := newTestClient(fakeRT)
|
||||
version, err := client.Info()
|
||||
if version != nil {
|
||||
t.Errorf("Info(): expected <nil> value, got %#v.", version)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("Info(): unexpected <nil> error")
|
||||
}
|
||||
}
|
49
third_party/github.com/fsouza/go-dockerclient/signal.go
vendored
Normal file
49
third_party/github.com/fsouza/go-dockerclient/signal.go
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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.
|
||||
|
||||
package docker
|
||||
|
||||
// Signal represents a signal that can be send to the container on
|
||||
// KillContainer call.
|
||||
type Signal int
|
||||
|
||||
// These values represent all signals available on Linux, where containers will
|
||||
// be running.
|
||||
const (
|
||||
SIGABRT = Signal(0x6)
|
||||
SIGALRM = Signal(0xe)
|
||||
SIGBUS = Signal(0x7)
|
||||
SIGCHLD = Signal(0x11)
|
||||
SIGCLD = Signal(0x11)
|
||||
SIGCONT = Signal(0x12)
|
||||
SIGFPE = Signal(0x8)
|
||||
SIGHUP = Signal(0x1)
|
||||
SIGILL = Signal(0x4)
|
||||
SIGINT = Signal(0x2)
|
||||
SIGIO = Signal(0x1d)
|
||||
SIGIOT = Signal(0x6)
|
||||
SIGKILL = Signal(0x9)
|
||||
SIGPIPE = Signal(0xd)
|
||||
SIGPOLL = Signal(0x1d)
|
||||
SIGPROF = Signal(0x1b)
|
||||
SIGPWR = Signal(0x1e)
|
||||
SIGQUIT = Signal(0x3)
|
||||
SIGSEGV = Signal(0xb)
|
||||
SIGSTKFLT = Signal(0x10)
|
||||
SIGSTOP = Signal(0x13)
|
||||
SIGSYS = Signal(0x1f)
|
||||
SIGTERM = Signal(0xf)
|
||||
SIGTRAP = Signal(0x5)
|
||||
SIGTSTP = Signal(0x14)
|
||||
SIGTTIN = Signal(0x15)
|
||||
SIGTTOU = Signal(0x16)
|
||||
SIGUNUSED = Signal(0x1f)
|
||||
SIGURG = Signal(0x17)
|
||||
SIGUSR1 = Signal(0xa)
|
||||
SIGUSR2 = Signal(0xc)
|
||||
SIGVTALRM = Signal(0x1a)
|
||||
SIGWINCH = Signal(0x1c)
|
||||
SIGXCPU = Signal(0x18)
|
||||
SIGXFSZ = Signal(0x19)
|
||||
)
|
15
third_party/github.com/fsouza/go-dockerclient/testing/data/Dockerfile
vendored
Normal file
15
third_party/github.com/fsouza/go-dockerclient/testing/data/Dockerfile
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# this file describes how to build tsuru python image
|
||||
# to run it:
|
||||
# 1- install docker
|
||||
# 2- run: $ docker build -t tsuru/python https://raw.github.com/tsuru/basebuilder/master/python/Dockerfile
|
||||
|
||||
from base:ubuntu-quantal
|
||||
run apt-get install wget -y --force-yes
|
||||
run wget http://github.com/tsuru/basebuilder/tarball/master -O basebuilder.tar.gz --no-check-certificate
|
||||
run mkdir /var/lib/tsuru
|
||||
run tar -xvf basebuilder.tar.gz -C /var/lib/tsuru --strip 1
|
||||
run cp /var/lib/tsuru/python/deploy /var/lib/tsuru
|
||||
run cp /var/lib/tsuru/base/restart /var/lib/tsuru
|
||||
run cp /var/lib/tsuru/base/start /var/lib/tsuru
|
||||
run /var/lib/tsuru/base/install
|
||||
run /var/lib/tsuru/base/setup
|
BIN
third_party/github.com/fsouza/go-dockerclient/testing/data/container.tar
vendored
Normal file
BIN
third_party/github.com/fsouza/go-dockerclient/testing/data/container.tar
vendored
Normal file
Binary file not shown.
BIN
third_party/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar
vendored
Normal file
BIN
third_party/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar
vendored
Normal file
Binary file not shown.
568
third_party/github.com/fsouza/go-dockerclient/testing/server.go
vendored
Normal file
568
third_party/github.com/fsouza/go-dockerclient/testing/server.go
vendored
Normal file
@@ -0,0 +1,568 @@
|
||||
// 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.
|
||||
|
||||
// Package testing provides a fake implementation of the Docker API, useful for
|
||||
// testing purpose.
|
||||
package testing
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"github.com/fsouza/go-dockerclient/utils"
|
||||
"github.com/gorilla/mux"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DockerServer represents a programmable, concurrent (not much), HTTP server
|
||||
// implementing a fake version of the Docker remote API.
|
||||
//
|
||||
// It can used in standalone mode, listening for connections or as an arbitrary
|
||||
// HTTP handler.
|
||||
//
|
||||
// For more details on the remote API, check http://goo.gl/yMI1S.
|
||||
type DockerServer struct {
|
||||
containers []*docker.Container
|
||||
cMut sync.RWMutex
|
||||
images []docker.Image
|
||||
iMut sync.RWMutex
|
||||
imgIDs map[string]string
|
||||
listener net.Listener
|
||||
mux *mux.Router
|
||||
hook func(*http.Request)
|
||||
failures map[string]FailureSpec
|
||||
}
|
||||
|
||||
// FailureSpec is used with PrepareFailure and describes in which situations
|
||||
// the request should fail. UrlRegex is mandatory, if a container id is sent
|
||||
// on the request you can also specify the other properties.
|
||||
type FailureSpec struct {
|
||||
UrlRegex string
|
||||
ContainerPath string
|
||||
ContainerArgs []string
|
||||
}
|
||||
|
||||
// NewServer returns a new instance of the fake server, in standalone mode. Use
|
||||
// the method URL to get the URL of the server.
|
||||
//
|
||||
// It receives the bind address (use 127.0.0.1:0 for getting an available port
|
||||
// on the host) and a hook function, that will be called on every request.
|
||||
func NewServer(bind string, hook func(*http.Request)) (*DockerServer, error) {
|
||||
listener, err := net.Listen("tcp", bind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := DockerServer{listener: listener, imgIDs: make(map[string]string), hook: hook,
|
||||
failures: make(map[string]FailureSpec)}
|
||||
server.buildMuxer()
|
||||
go http.Serve(listener, &server)
|
||||
return &server, nil
|
||||
}
|
||||
|
||||
func (s *DockerServer) buildMuxer() {
|
||||
s.mux = mux.NewRouter()
|
||||
s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer))
|
||||
s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers))
|
||||
s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer))
|
||||
s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer))
|
||||
s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer))
|
||||
s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
|
||||
s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer))
|
||||
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("/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))
|
||||
s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage))
|
||||
s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage))
|
||||
s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage))
|
||||
s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents)
|
||||
}
|
||||
|
||||
// PrepareFailure adds a new expected failure based on a FailureSpec
|
||||
// it receives an id for the failure and the spec.
|
||||
func (s *DockerServer) PrepareFailure(id string, spec FailureSpec) {
|
||||
s.failures[id] = spec
|
||||
}
|
||||
|
||||
// ResetFailure removes an expected failure identified by the id
|
||||
func (s *DockerServer) ResetFailure(id string) {
|
||||
delete(s.failures, id)
|
||||
}
|
||||
|
||||
// Stop stops the server.
|
||||
func (s *DockerServer) Stop() {
|
||||
if s.listener != nil {
|
||||
s.listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// URL returns the HTTP URL of the server.
|
||||
func (s *DockerServer) URL() string {
|
||||
if s.listener == nil {
|
||||
return ""
|
||||
}
|
||||
return "http://" + s.listener.Addr().String() + "/"
|
||||
}
|
||||
|
||||
// ServeHTTP handles HTTP requests sent to the server.
|
||||
func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.mux.ServeHTTP(w, r)
|
||||
if s.hook != nil {
|
||||
s.hook(r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
for errorId, spec := range s.failures {
|
||||
matched, err := regexp.MatchString(spec.UrlRegex, r.URL.Path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
id := mux.Vars(r)["id"]
|
||||
if id != "" {
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if spec.ContainerPath != "" && container.Path != spec.ContainerPath {
|
||||
continue
|
||||
}
|
||||
if spec.ContainerArgs != nil && reflect.DeepEqual(container.Args, spec.ContainerArgs) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
http.Error(w, errorId, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
f(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) {
|
||||
all := r.URL.Query().Get("all")
|
||||
s.cMut.RLock()
|
||||
result := make([]docker.APIContainers, len(s.containers))
|
||||
for i, container := range s.containers {
|
||||
if all == "1" || container.State.Running {
|
||||
result[i] = docker.APIContainers{
|
||||
ID: container.ID,
|
||||
Image: container.Image,
|
||||
Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
|
||||
Created: container.Created.Unix(),
|
||||
Status: container.State.String(),
|
||||
Ports: container.NetworkSettings.PortMappingAPI(),
|
||||
}
|
||||
}
|
||||
}
|
||||
s.cMut.RUnlock()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) {
|
||||
s.cMut.RLock()
|
||||
result := make([]docker.APIImages, len(s.images))
|
||||
for i, image := range s.images {
|
||||
result[i] = docker.APIImages{
|
||||
ID: image.ID,
|
||||
Created: image.Created.Unix(),
|
||||
}
|
||||
}
|
||||
s.cMut.RUnlock()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func (s *DockerServer) findImage(id string) (string, error) {
|
||||
s.iMut.RLock()
|
||||
defer s.iMut.RUnlock()
|
||||
image, ok := s.imgIDs[id]
|
||||
if ok {
|
||||
return image, nil
|
||||
}
|
||||
image, _, err := s.findImageByID(id)
|
||||
return image, err
|
||||
}
|
||||
|
||||
func (s *DockerServer) findImageByID(id string) (string, int, error) {
|
||||
s.iMut.RLock()
|
||||
defer s.iMut.RUnlock()
|
||||
for i, image := range s.images {
|
||||
if image.ID == id {
|
||||
return image.ID, i, nil
|
||||
}
|
||||
}
|
||||
return "", -1, errors.New("No such image")
|
||||
}
|
||||
|
||||
func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
|
||||
var config docker.Config
|
||||
defer r.Body.Close()
|
||||
err := json.NewDecoder(r.Body).Decode(&config)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
image, err := s.findImage(config.Image)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
ports := map[docker.Port][]docker.PortBinding{}
|
||||
for port := range config.ExposedPorts {
|
||||
ports[port] = []docker.PortBinding{{
|
||||
HostIp: "0.0.0.0",
|
||||
HostPort: strconv.Itoa(mathrand.Int() % 65536),
|
||||
}}
|
||||
}
|
||||
|
||||
//the container may not have cmd when using a Dockerfile
|
||||
var path string
|
||||
var args []string
|
||||
if len(config.Cmd) == 1 {
|
||||
path = config.Cmd[0]
|
||||
} else if len(config.Cmd) > 1 {
|
||||
path = config.Cmd[0]
|
||||
args = config.Cmd[1:]
|
||||
}
|
||||
|
||||
container := docker.Container{
|
||||
ID: s.generateID(),
|
||||
Created: time.Now(),
|
||||
Path: path,
|
||||
Args: args,
|
||||
Config: &config,
|
||||
State: docker.State{
|
||||
Running: false,
|
||||
Pid: mathrand.Int() % 50000,
|
||||
ExitCode: 0,
|
||||
StartedAt: time.Now(),
|
||||
},
|
||||
Image: image,
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2),
|
||||
IPPrefixLen: 24,
|
||||
Gateway: "172.16.42.1",
|
||||
Bridge: "docker0",
|
||||
Ports: ports,
|
||||
},
|
||||
}
|
||||
s.cMut.Lock()
|
||||
s.containers = append(s.containers, &container)
|
||||
s.cMut.Unlock()
|
||||
var c = struct{ ID string }{ID: container.ID}
|
||||
json.NewEncoder(w).Encode(c)
|
||||
}
|
||||
|
||||
func (s *DockerServer) generateID() string {
|
||||
var buf [16]byte
|
||||
rand.Read(buf[:])
|
||||
return fmt.Sprintf("%x", buf)
|
||||
}
|
||||
|
||||
func (s *DockerServer) inspectContainer(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
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(container)
|
||||
}
|
||||
|
||||
func (s *DockerServer) startContainer(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
|
||||
}
|
||||
s.cMut.Lock()
|
||||
defer s.cMut.Unlock()
|
||||
if container.State.Running {
|
||||
http.Error(w, "Container already running", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
container.State.Running = true
|
||||
}
|
||||
|
||||
func (s *DockerServer) stopContainer(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
|
||||
}
|
||||
s.cMut.Lock()
|
||||
defer s.cMut.Unlock()
|
||||
if !container.State.Running {
|
||||
http.Error(w, "Container not running", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
container.State.Running = false
|
||||
}
|
||||
|
||||
func (s *DockerServer) attachContainer(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
|
||||
}
|
||||
outStream := utils.NewStdWriter(w, utils.Stdout)
|
||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
if container.State.Running {
|
||||
fmt.Fprintf(outStream, "Container %q is running\n", container.ID)
|
||||
} else {
|
||||
fmt.Fprintf(outStream, "Container %q is not running\n", container.ID)
|
||||
}
|
||||
fmt.Fprintln(outStream, "What happened?")
|
||||
fmt.Fprintln(outStream, "Something happened")
|
||||
}
|
||||
|
||||
func (s *DockerServer) waitContainer(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
|
||||
}
|
||||
for {
|
||||
time.Sleep(1e6)
|
||||
s.cMut.RLock()
|
||||
if !container.State.Running {
|
||||
s.cMut.RUnlock()
|
||||
break
|
||||
}
|
||||
s.cMut.RUnlock()
|
||||
}
|
||||
w.Write([]byte(`{"StatusCode":0}`))
|
||||
}
|
||||
|
||||
func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
_, index, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if s.containers[index].State.Running {
|
||||
msg := "Error: API error (406): Impossible to remove a running container, please stop it first"
|
||||
http.Error(w, msg, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
s.cMut.Lock()
|
||||
defer s.cMut.Unlock()
|
||||
s.containers[index] = s.containers[len(s.containers)-1]
|
||||
s.containers = s.containers[:len(s.containers)-1]
|
||||
}
|
||||
|
||||
func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Query().Get("container")
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
var config *docker.Config
|
||||
runConfig := r.URL.Query().Get("run")
|
||||
if runConfig != "" {
|
||||
config = new(docker.Config)
|
||||
err = json.Unmarshal([]byte(runConfig), config)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
image := docker.Image{
|
||||
ID: "img-" + container.ID,
|
||||
Parent: container.Image,
|
||||
Container: container.ID,
|
||||
Comment: r.URL.Query().Get("m"),
|
||||
Author: r.URL.Query().Get("author"),
|
||||
Config: config,
|
||||
}
|
||||
repository := r.URL.Query().Get("repo")
|
||||
s.iMut.Lock()
|
||||
s.images = append(s.images, image)
|
||||
if repository != "" {
|
||||
s.imgIDs[repository] = image.ID
|
||||
}
|
||||
s.iMut.Unlock()
|
||||
fmt.Fprintf(w, `{"ID":%q}`, image.ID)
|
||||
}
|
||||
|
||||
func (s *DockerServer) findContainer(id string) (*docker.Container, int, error) {
|
||||
s.cMut.RLock()
|
||||
defer s.cMut.RUnlock()
|
||||
for i, container := range s.containers {
|
||||
if container.ID == id {
|
||||
return container, i, nil
|
||||
}
|
||||
}
|
||||
return nil, -1, errors.New("No such container")
|
||||
}
|
||||
|
||||
func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) {
|
||||
if ct := r.Header.Get("Content-Type"); ct == "application/tar" {
|
||||
gotDockerFile := false
|
||||
tr := tar.NewReader(r.Body)
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if header.Name == "Dockerfile" {
|
||||
gotDockerFile = true
|
||||
}
|
||||
}
|
||||
if !gotDockerFile {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("miss Dockerfile"))
|
||||
return
|
||||
}
|
||||
}
|
||||
//we did not use that Dockerfile to build image cause we are a fake Docker daemon
|
||||
image := docker.Image{
|
||||
ID: s.generateID(),
|
||||
}
|
||||
query := r.URL.Query()
|
||||
repository := image.ID
|
||||
if t := query.Get("t"); t != "" {
|
||||
repository = t
|
||||
}
|
||||
s.iMut.Lock()
|
||||
s.images = append(s.images, image)
|
||||
s.imgIDs[repository] = image.ID
|
||||
s.iMut.Unlock()
|
||||
w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID)))
|
||||
}
|
||||
|
||||
func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) {
|
||||
repository := r.URL.Query().Get("fromImage")
|
||||
image := docker.Image{
|
||||
ID: s.generateID(),
|
||||
}
|
||||
s.iMut.Lock()
|
||||
s.images = append(s.images, image)
|
||||
if repository != "" {
|
||||
s.imgIDs[repository] = image.ID
|
||||
}
|
||||
s.iMut.Unlock()
|
||||
}
|
||||
|
||||
func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
s.iMut.RLock()
|
||||
if _, ok := s.imgIDs[name]; !ok {
|
||||
s.iMut.RUnlock()
|
||||
http.Error(w, "No such image", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
s.iMut.RUnlock()
|
||||
fmt.Fprintln(w, "Pushing...")
|
||||
fmt.Fprintln(w, "Pushed")
|
||||
}
|
||||
|
||||
func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
s.iMut.RLock()
|
||||
if img, ok := s.imgIDs[id]; ok {
|
||||
id = img
|
||||
}
|
||||
s.iMut.RUnlock()
|
||||
_, index, err := s.findImageByID(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
s.iMut.Lock()
|
||||
defer s.iMut.Unlock()
|
||||
s.images[index] = s.images[len(s.images)-1]
|
||||
s.images = s.images[:len(s.images)-1]
|
||||
}
|
||||
|
||||
func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
if id, ok := s.imgIDs[name]; ok {
|
||||
s.iMut.Lock()
|
||||
defer s.iMut.Unlock()
|
||||
|
||||
for _, img := range s.images {
|
||||
if img.ID == id {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(img)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var events [][]byte
|
||||
count := mathrand.Intn(20)
|
||||
for i := 0; i < count; i++ {
|
||||
data, err := json.Marshal(s.generateEvent())
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
events = append(events, data)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
for _, d := range events {
|
||||
fmt.Fprintln(w, d)
|
||||
time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerServer) generateEvent() *docker.APIEvents {
|
||||
var eventType string
|
||||
switch mathrand.Intn(4) {
|
||||
case 0:
|
||||
eventType = "create"
|
||||
case 1:
|
||||
eventType = "start"
|
||||
case 2:
|
||||
eventType = "stop"
|
||||
case 3:
|
||||
eventType = "destroy"
|
||||
}
|
||||
return &docker.APIEvents{
|
||||
ID: s.generateID(),
|
||||
Status: eventType,
|
||||
From: "mybase:latest",
|
||||
Time: time.Now().Unix(),
|
||||
}
|
||||
}
|
764
third_party/github.com/fsouza/go-dockerclient/testing/server_test.go
vendored
Normal file
764
third_party/github.com/fsouza/go-dockerclient/testing/server_test.go
vendored
Normal file
@@ -0,0 +1,764 @@
|
||||
// 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.
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
server, err := NewServer("127.0.0.1:0", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer server.listener.Close()
|
||||
conn, err := net.Dial("tcp", server.listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestServerStop(t *testing.T) {
|
||||
server, err := NewServer("127.0.0.1:0", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server.Stop()
|
||||
_, err = net.Dial("tcp", server.listener.Addr().String())
|
||||
if err == nil {
|
||||
t.Error("Unexpected <nil> error when dialing to stopped server")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerStopNoListener(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.Stop()
|
||||
}
|
||||
|
||||
func TestServerURL(t *testing.T) {
|
||||
server, err := NewServer("127.0.0.1:0", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer server.Stop()
|
||||
url := server.URL()
|
||||
if expected := "http://" + server.listener.Addr().String() + "/"; url != expected {
|
||||
t.Errorf("DockerServer.URL(): Want %q. Got %q.", expected, url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerURLNoListener(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
url := server.URL()
|
||||
if url != "" {
|
||||
t.Errorf("DockerServer.URL(): Expected empty URL on handler mode, got %q.", url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWithHook(t *testing.T) {
|
||||
var called bool
|
||||
server, _ := NewServer("127.0.0.1:0", func(*http.Request) { called = true })
|
||||
defer server.Stop()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if !called {
|
||||
t.Error("ServeHTTP did not call the hook function.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListContainers(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("ListContainers: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := make([]docker.APIContainers, 2)
|
||||
for i, container := range server.containers {
|
||||
expected[i] = docker.APIContainers{
|
||||
ID: container.ID,
|
||||
Image: container.Image,
|
||||
Command: strings.Join(container.Config.Cmd, " "),
|
||||
Created: container.Created.Unix(),
|
||||
Status: container.State.String(),
|
||||
Ports: container.NetworkSettings.PortMappingAPI(),
|
||||
}
|
||||
}
|
||||
var got []docker.APIContainers
|
||||
err := json.NewDecoder(recorder.Body).Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ListContainers. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRunningContainers(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=0", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("ListRunningContainers: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
var got []docker.APIContainers
|
||||
err := json.NewDecoder(recorder.Body).Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) == 0 {
|
||||
t.Errorf("ListRunningContainers: Want 0. Got %d.", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.imgIDs = map[string]string{"base": "a1234"}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true,
|
||||
"PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":""}`
|
||||
request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader(body))
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusCreated {
|
||||
t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code)
|
||||
}
|
||||
var returned docker.Container
|
||||
err := json.NewDecoder(recorder.Body).Decode(&returned)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stored := server.containers[0]
|
||||
if returned.ID != stored.ID {
|
||||
t.Errorf("CreateContainer: ID mismatch. Stored: %q. Returned: %q.", stored.ID, returned.ID)
|
||||
}
|
||||
if stored.State.Running {
|
||||
t.Errorf("CreateContainer should not set container to running state.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerInvalidBody(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader("whaaaaaat---"))
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerImageNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true,
|
||||
"PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"],
|
||||
"Image":"base", "Volumes":{}, "VolumesFrom":""}`
|
||||
request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader(body))
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/commit?container="+server.containers[0].ID, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("CommitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := fmt.Sprintf(`{"ID":"%s"}`, server.images[0].ID)
|
||||
if got := recorder.Body.String(); got != expected {
|
||||
t.Errorf("CommitContainer: wrong response body. Want %q. Got %q.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerComplete(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.imgIDs = make(map[string]string)
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
queryString := "container=" + server.containers[0].ID + "&repo=tsuru/python&m=saving&author=developers"
|
||||
queryString += `&run={"Cmd": ["cat", "/world"],"PortSpecs":["22"]}`
|
||||
request, _ := http.NewRequest("POST", "/commit?"+queryString, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
image := server.images[0]
|
||||
if image.Parent != server.containers[0].Image {
|
||||
t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", server.containers[0].Image, image.Parent)
|
||||
}
|
||||
if image.Container != server.containers[0].ID {
|
||||
t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", server.containers[0].ID, image.Container)
|
||||
}
|
||||
message := "saving"
|
||||
if image.Comment != message {
|
||||
t.Errorf("CommitContainer: wrong comment (commit message). Want %q. Got %q.", message, image.Comment)
|
||||
}
|
||||
author := "developers"
|
||||
if image.Author != author {
|
||||
t.Errorf("CommitContainer: wrong author. Want %q. Got %q.", author, image.Author)
|
||||
}
|
||||
if id := server.imgIDs["tsuru/python"]; id != image.ID {
|
||||
t.Errorf("CommitContainer: wrong ID saved for repository. Want %q. Got %q.", image.ID, id)
|
||||
}
|
||||
portSpecs := []string{"22"}
|
||||
if !reflect.DeepEqual(image.Config.PortSpecs, portSpecs) {
|
||||
t.Errorf("CommitContainer: wrong port spec in config. Want %#v. Got %#v.", portSpecs, image.Config.PortSpecs)
|
||||
}
|
||||
cmd := []string{"cat", "/world"}
|
||||
if !reflect.DeepEqual(image.Config.Cmd, cmd) {
|
||||
t.Errorf("CommitContainer: wrong cmd in config. Want %#v. Got %#v.", cmd, image.Config.Cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerInvalidRun(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/commit?container="+server.containers[0].ID+"&run=abc---", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("CommitContainer. Wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/commit?container=abc123", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("CommitContainer. Wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/json", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("GET", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("InspectContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := server.containers[0]
|
||||
var got docker.Container
|
||||
err := json.NewDecoder(recorder.Body).Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got.Config, expected.Config) {
|
||||
t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got)
|
||||
}
|
||||
if !reflect.DeepEqual(got.NetworkSettings, expected.NetworkSettings) {
|
||||
t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got)
|
||||
}
|
||||
got.State.StartedAt = expected.State.StartedAt
|
||||
got.State.FinishedAt = expected.State.FinishedAt
|
||||
got.Config = expected.Config
|
||||
got.Created = expected.Created
|
||||
got.NetworkSettings = expected.NetworkSettings
|
||||
if !reflect.DeepEqual(got, *expected) {
|
||||
t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/abc123/json", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("InspectContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
if !server.containers[0].State.Running {
|
||||
t.Error("StartContainer: did not set the container to running state")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/containers/abc123/start"
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerAlreadyRunning(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/stop", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNoContent {
|
||||
t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
if server.containers[0].State.Running {
|
||||
t.Error("StopContainer: did not stop the container")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/containers/abc123/stop"
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainerNotRunning(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/stop", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/wait", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
go func() {
|
||||
server.cMut.Lock()
|
||||
server.containers[0].State.Running = false
|
||||
server.cMut.Unlock()
|
||||
}()
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := `{"StatusCode":0}`
|
||||
if body := recorder.Body.String(); body != expected {
|
||||
t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/containers/abc123/wait"
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("WaitContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/attach?logs=1", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
lines := []string{
|
||||
fmt.Sprintf("\x01\x00\x00\x00\x03\x00\x00\x00Container %q is running", server.containers[0].ID),
|
||||
"What happened?",
|
||||
"Something happened",
|
||||
}
|
||||
expected := strings.Join(lines, "\n") + "\n"
|
||||
if body := recorder.Body.String(); body == expected {
|
||||
t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/containers/abc123/attach?logs=1"
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("AttachContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNoContent {
|
||||
t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
if len(server.containers) > 0 {
|
||||
t.Error("RemoveContainer: did not remove the container.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/abc123")
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerRunning(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusInternalServerError {
|
||||
t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code)
|
||||
}
|
||||
if len(server.containers) < 1 {
|
||||
t.Error("RemoveContainer: should not remove the container.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImage(t *testing.T) {
|
||||
server := DockerServer{imgIDs: make(map[string]string)}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/images/create?fromImage=base", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
if len(server.images) != 1 {
|
||||
t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images))
|
||||
}
|
||||
if _, ok := server.imgIDs["base"]; !ok {
|
||||
t.Error("PullImage: Repository should not be empty.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImage(t *testing.T) {
|
||||
server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/images/tsuru/python/push", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/images/tsuru/python/push", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func addContainers(server *DockerServer, n int) {
|
||||
server.cMut.Lock()
|
||||
defer server.cMut.Unlock()
|
||||
for i := 0; i < n; i++ {
|
||||
date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour)
|
||||
container := docker.Container{
|
||||
ID: fmt.Sprintf("%x", rand.Int()%10000),
|
||||
Created: date,
|
||||
Path: "ls",
|
||||
Args: []string{"-la", ".."},
|
||||
Config: &docker.Config{
|
||||
Hostname: fmt.Sprintf("docker-%d", i),
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Env: []string{"ME=you", fmt.Sprintf("NUMBER=%d", i)},
|
||||
Cmd: []string{"ls", "-la", ".."},
|
||||
Image: "base",
|
||||
},
|
||||
State: docker.State{
|
||||
Running: false,
|
||||
Pid: 400 + i,
|
||||
ExitCode: 0,
|
||||
StartedAt: date,
|
||||
},
|
||||
Image: "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
IPAddress: fmt.Sprintf("10.10.10.%d", i+2),
|
||||
IPPrefixLen: 24,
|
||||
Gateway: "10.10.10.1",
|
||||
Bridge: "docker0",
|
||||
PortMapping: map[string]docker.PortMapping{
|
||||
"Tcp": {"8888": fmt.Sprintf("%d", 49600+i)},
|
||||
},
|
||||
},
|
||||
ResolvConfPath: "/etc/resolv.conf",
|
||||
}
|
||||
server.containers = append(server.containers, &container)
|
||||
}
|
||||
}
|
||||
|
||||
func addImages(server *DockerServer, n int, repo bool) {
|
||||
server.iMut.Lock()
|
||||
defer server.iMut.Unlock()
|
||||
if server.imgIDs == nil {
|
||||
server.imgIDs = make(map[string]string)
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour)
|
||||
image := docker.Image{
|
||||
ID: fmt.Sprintf("%x", rand.Int()%10000),
|
||||
Created: date,
|
||||
}
|
||||
server.images = append(server.images, image)
|
||||
if repo {
|
||||
repo := "docker/python-" + image.ID
|
||||
server.imgIDs[repo] = image.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListImages(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addImages(&server, 2, false)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/images/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("ListImages: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := make([]docker.APIImages, 2)
|
||||
for i, image := range server.images {
|
||||
expected[i] = docker.APIImages{
|
||||
ID: image.ID,
|
||||
Created: image.Created.Unix(),
|
||||
}
|
||||
}
|
||||
var got []docker.APIImages
|
||||
err := json.NewDecoder(recorder.Body).Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ListImages. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImage(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addImages(&server, 1, false)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/images/%s", server.images[0].ID)
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNoContent {
|
||||
t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
if len(server.images) > 0 {
|
||||
t.Error("RemoveImage: did not remove the image.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImageByName(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addImages(&server, 1, true)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/images/docker/python-" + server.images[0].ID
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNoContent {
|
||||
t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
if len(server.images) > 0 {
|
||||
t.Error("RemoveImage: did not remove the image.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareFailure(t *testing.T) {
|
||||
server := DockerServer{failures: make(map[string]FailureSpec)}
|
||||
server.buildMuxer()
|
||||
errorId := "my_error"
|
||||
failure := FailureSpec{UrlRegex: "containers/json"}
|
||||
server.PrepareFailure(errorId, failure)
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
if recorder.Body.String() != errorId+"\n" {
|
||||
t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorId, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareFailureUsingContainerPath(t *testing.T) {
|
||||
server := DockerServer{failures: make(map[string]FailureSpec)}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
errorId := "my_path_error"
|
||||
failure := FailureSpec{UrlRegex: "containers/.*?/start", ContainerPath: "ls"}
|
||||
server.PrepareFailure(errorId, failure)
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("TestPrepareFailureUsingContainerPath: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
if recorder.Body.String() != errorId+"\n" {
|
||||
t.Errorf("TestPrepareFailureUsingContainerPath: wrong message. Want %s. Got %s.", errorId, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareFailureUsingContainerPathWithWrongPath(t *testing.T) {
|
||||
server := DockerServer{failures: make(map[string]FailureSpec)}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
errorId := "my_path_error"
|
||||
failure := FailureSpec{UrlRegex: "containers/.*?/start", ContainerPath: "xxx"}
|
||||
server.PrepareFailure(errorId, failure)
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveFailure(t *testing.T) {
|
||||
server := DockerServer{failures: make(map[string]FailureSpec)}
|
||||
server.buildMuxer()
|
||||
errorId := "my_error"
|
||||
failure := FailureSpec{UrlRegex: "containers/json"}
|
||||
server.PrepareFailure(errorId, failure)
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
server.ResetFailure(errorId)
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("RemoveFailure: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageWithContentTypeTar(t *testing.T) {
|
||||
server := DockerServer{imgIDs: make(map[string]string)}
|
||||
imageName := "teste"
|
||||
recorder := httptest.NewRecorder()
|
||||
tarFile, err := os.Open("data/dockerfile.tar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tarFile.Close()
|
||||
request, _ := http.NewRequest("POST", "/build?t=teste", tarFile)
|
||||
request.Header.Add("Content-Type", "application/tar")
|
||||
server.buildImage(recorder, request)
|
||||
if recorder.Body.String() == "miss Dockerfile" {
|
||||
t.Errorf("BuildImage: miss Dockerfile")
|
||||
return
|
||||
}
|
||||
if _, ok := server.imgIDs[imageName]; ok == false {
|
||||
t.Errorf("BuildImage: image %s not builded", imageName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageWithRemoteDockerfile(t *testing.T) {
|
||||
server := DockerServer{imgIDs: make(map[string]string)}
|
||||
imageName := "teste"
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/build?t=teste&remote=http://localhost/Dockerfile", nil)
|
||||
server.buildImage(recorder, request)
|
||||
if _, ok := server.imgIDs[imageName]; ok == false {
|
||||
t.Errorf("BuildImage: image %s not builded", imageName)
|
||||
}
|
||||
}
|
20
third_party/github.com/fsouza/go-dockerclient/utils/random.go
vendored
Normal file
20
third_party/github.com/fsouza/go-dockerclient/utils/random.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
)
|
||||
|
||||
func RandomString() string {
|
||||
id := make([]byte, 32)
|
||||
_, err := io.ReadFull(rand.Reader, id)
|
||||
if err != nil {
|
||||
panic(err) // This shouldn't happen
|
||||
}
|
||||
return hex.EncodeToString(id)
|
||||
}
|
158
third_party/github.com/fsouza/go-dockerclient/utils/stdcopy.go
vendored
Normal file
158
third_party/github.com/fsouza/go-dockerclient/utils/stdcopy.go
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
StdWriterPrefixLen = 8
|
||||
StdWriterFdIndex = 0
|
||||
StdWriterSizeIndex = 4
|
||||
)
|
||||
|
||||
type StdType [StdWriterPrefixLen]byte
|
||||
|
||||
var (
|
||||
Stdin StdType = StdType{0: 0}
|
||||
Stdout StdType = StdType{0: 1}
|
||||
Stderr StdType = StdType{0: 2}
|
||||
)
|
||||
|
||||
type StdWriter struct {
|
||||
io.Writer
|
||||
prefix StdType
|
||||
sizeBuf []byte
|
||||
}
|
||||
|
||||
func (w *StdWriter) Write(buf []byte) (n int, err error) {
|
||||
if w == nil || w.Writer == nil {
|
||||
return 0, errors.New("Writer not instanciated")
|
||||
}
|
||||
binary.BigEndian.PutUint32(w.prefix[4:], uint32(len(buf)))
|
||||
buf = append(w.prefix[:], buf...)
|
||||
|
||||
n, err = w.Writer.Write(buf)
|
||||
return n - StdWriterPrefixLen, err
|
||||
}
|
||||
|
||||
// NewStdWriter instanciates a new Writer.
|
||||
// Everything written to it will be encapsulated using a custom format,
|
||||
// and written to the underlying `w` stream.
|
||||
// This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection.
|
||||
// `t` indicates the id of the stream to encapsulate.
|
||||
// It can be utils.Stdin, utils.Stdout, utils.Stderr.
|
||||
func NewStdWriter(w io.Writer, t StdType) *StdWriter {
|
||||
if len(t) != StdWriterPrefixLen {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &StdWriter{
|
||||
Writer: w,
|
||||
prefix: t,
|
||||
sizeBuf: make([]byte, 4),
|
||||
}
|
||||
}
|
||||
|
||||
var ErrInvalidStdHeader = errors.New("Unrecognized input header")
|
||||
|
||||
// StdCopy is a modified version of io.Copy.
|
||||
//
|
||||
// StdCopy will demultiplex `src`, assuming that it contains two streams,
|
||||
// previously multiplexed together using a StdWriter instance.
|
||||
// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
|
||||
//
|
||||
// StdCopy will read until it hits EOF on `src`. It will then return a nil error.
|
||||
// In other words: if `err` is non nil, it indicates a real underlying error.
|
||||
//
|
||||
// `written` will hold the total number of bytes written to `dstout` and `dsterr`.
|
||||
func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
|
||||
var (
|
||||
buf = make([]byte, 32*1024+StdWriterPrefixLen+1)
|
||||
bufLen = len(buf)
|
||||
nr, nw int
|
||||
er, ew error
|
||||
out io.Writer
|
||||
frameSize int
|
||||
)
|
||||
|
||||
for {
|
||||
// Make sure we have at least a full header
|
||||
for nr < StdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, er = src.Read(buf[nr:])
|
||||
if er == io.EOF {
|
||||
return written, nil
|
||||
}
|
||||
if er != nil {
|
||||
return 0, er
|
||||
}
|
||||
nr += nr2
|
||||
}
|
||||
|
||||
// Check the first byte to know where to write
|
||||
switch buf[StdWriterFdIndex] {
|
||||
case 0:
|
||||
fallthrough
|
||||
case 1:
|
||||
// Write on stdout
|
||||
out = dstout
|
||||
case 2:
|
||||
// Write on stderr
|
||||
out = dsterr
|
||||
default:
|
||||
Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex])
|
||||
return 0, ErrInvalidStdHeader
|
||||
}
|
||||
|
||||
// Retrieve the size of the frame
|
||||
frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4]))
|
||||
|
||||
// Check if the buffer is big enough to read the frame.
|
||||
// Extend it if necessary.
|
||||
if frameSize+StdWriterPrefixLen > bufLen {
|
||||
Debugf("Extending buffer cap.")
|
||||
buf = append(buf, make([]byte, frameSize-len(buf)+1)...)
|
||||
bufLen = len(buf)
|
||||
}
|
||||
|
||||
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
||||
for nr < frameSize+StdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, er = src.Read(buf[nr:])
|
||||
if er == io.EOF {
|
||||
return written, nil
|
||||
}
|
||||
if er != nil {
|
||||
Debugf("Error reading frame: %s", er)
|
||||
return 0, er
|
||||
}
|
||||
nr += nr2
|
||||
}
|
||||
|
||||
// Write the retrieved frame (without header)
|
||||
nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
Debugf("Error writing frame: %s", ew)
|
||||
return 0, ew
|
||||
}
|
||||
// If the frame has not been fully written: error
|
||||
if nw != frameSize {
|
||||
Debugf("Error Short Write: (%d on %d)", nw, frameSize)
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
|
||||
// Move the rest of the buffer to the beginning
|
||||
copy(buf, buf[frameSize+StdWriterPrefixLen:])
|
||||
// Move the index
|
||||
nr -= frameSize + StdWriterPrefixLen
|
||||
}
|
||||
}
|
17
third_party/github.com/fsouza/go-dockerclient/utils/uname_darwin.go
vendored
Normal file
17
third_party/github.com/fsouza/go-dockerclient/utils/uname_darwin.go
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Utsname struct {
|
||||
Release [65]byte
|
||||
}
|
||||
|
||||
func uname() (*Utsname, error) {
|
||||
return nil, errors.New("Kernel version detection is not available on darwin")
|
||||
}
|
20
third_party/github.com/fsouza/go-dockerclient/utils/uname_linux.go
vendored
Normal file
20
third_party/github.com/fsouza/go-dockerclient/utils/uname_linux.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Utsname syscall.Utsname
|
||||
|
||||
func uname() (*syscall.Utsname, error) {
|
||||
uts := &syscall.Utsname{}
|
||||
|
||||
if err := syscall.Uname(uts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uts, nil
|
||||
}
|
1114
third_party/github.com/fsouza/go-dockerclient/utils/utils.go
vendored
Normal file
1114
third_party/github.com/fsouza/go-dockerclient/utils/utils.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
535
third_party/github.com/fsouza/go-dockerclient/utils/utils_test.go
vendored
Normal file
535
third_party/github.com/fsouza/go-dockerclient/utils/utils_test.go
vendored
Normal file
@@ -0,0 +1,535 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"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))
|
||||
}
|
||||
}
|
||||
|
||||
type dummyWriter struct {
|
||||
buffer bytes.Buffer
|
||||
failOnWrite bool
|
||||
}
|
||||
|
||||
func (dw *dummyWriter) Write(p []byte) (n int, err error) {
|
||||
if dw.failOnWrite {
|
||||
return 0, errors.New("Fake fail")
|
||||
}
|
||||
return dw.buffer.Write(p)
|
||||
}
|
||||
|
||||
func (dw *dummyWriter) String() string {
|
||||
return dw.buffer.String()
|
||||
}
|
||||
|
||||
func (dw *dummyWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestWriteBroadcaster(t *testing.T) {
|
||||
writer := NewWriteBroadcaster()
|
||||
|
||||
// Test 1: Both bufferA and bufferB should contain "foo"
|
||||
bufferA := &dummyWriter{}
|
||||
writer.AddWriter(bufferA, "")
|
||||
bufferB := &dummyWriter{}
|
||||
writer.AddWriter(bufferB, "")
|
||||
writer.Write([]byte("foo"))
|
||||
|
||||
if bufferA.String() != "foo" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
|
||||
if bufferB.String() != "foo" {
|
||||
t.Errorf("Buffer contains %v", bufferB.String())
|
||||
}
|
||||
|
||||
// Test2: bufferA and bufferB should contain "foobar",
|
||||
// while bufferC should only contain "bar"
|
||||
bufferC := &dummyWriter{}
|
||||
writer.AddWriter(bufferC, "")
|
||||
writer.Write([]byte("bar"))
|
||||
|
||||
if bufferA.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
|
||||
if bufferB.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferB.String())
|
||||
}
|
||||
|
||||
if bufferC.String() != "bar" {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
|
||||
// Test3: Test eviction on failure
|
||||
bufferA.failOnWrite = true
|
||||
writer.Write([]byte("fail"))
|
||||
if bufferA.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
if bufferC.String() != "barfail" {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
// Even though we reset the flag, no more writes should go in there
|
||||
bufferA.failOnWrite = false
|
||||
writer.Write([]byte("test"))
|
||||
if bufferA.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
if bufferC.String() != "barfailtest" {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
|
||||
writer.CloseWriters()
|
||||
}
|
||||
|
||||
type devNullCloser int
|
||||
|
||||
func (d devNullCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d devNullCloser) Write(buf []byte) (int, error) {
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// This test checks for races. It is only useful when run with the race detector.
|
||||
func TestRaceWriteBroadcaster(t *testing.T) {
|
||||
writer := NewWriteBroadcaster()
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
writer.AddWriter(devNullCloser(0), "")
|
||||
c <- true
|
||||
}()
|
||||
writer.Write([]byte("hello"))
|
||||
<-c
|
||||
}
|
||||
|
||||
// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
|
||||
func TestTruncIndex(t *testing.T) {
|
||||
index := NewTruncIndex()
|
||||
// Get on an empty index
|
||||
if _, err := index.Get("foobar"); err == nil {
|
||||
t.Fatal("Get on an empty index should return an error")
|
||||
}
|
||||
|
||||
// Spaces should be illegal in an id
|
||||
if err := index.Add("I have a space"); err == nil {
|
||||
t.Fatalf("Adding an id with ' ' should return an error")
|
||||
}
|
||||
|
||||
id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96"
|
||||
// Add an id
|
||||
if err := index.Add(id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Get a non-existing id
|
||||
assertIndexGet(t, index, "abracadabra", "", true)
|
||||
// Get the exact id
|
||||
assertIndexGet(t, index, id, id, false)
|
||||
// The first letter should match
|
||||
assertIndexGet(t, index, id[:1], id, false)
|
||||
// The first half should match
|
||||
assertIndexGet(t, index, id[:len(id)/2], id, false)
|
||||
// The second half should NOT match
|
||||
assertIndexGet(t, index, id[len(id)/2:], "", true)
|
||||
|
||||
id2 := id[:6] + "blabla"
|
||||
// Add an id
|
||||
if err := index.Add(id2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Both exact IDs should work
|
||||
assertIndexGet(t, index, id, id, false)
|
||||
assertIndexGet(t, index, id2, id2, false)
|
||||
|
||||
// 6 characters or less should conflict
|
||||
assertIndexGet(t, index, id[:6], "", true)
|
||||
assertIndexGet(t, index, id[:4], "", true)
|
||||
assertIndexGet(t, index, id[:1], "", true)
|
||||
|
||||
// 7 characters should NOT conflict
|
||||
assertIndexGet(t, index, id[:7], id, false)
|
||||
assertIndexGet(t, index, id2[:7], id2, false)
|
||||
|
||||
// Deleting a non-existing id should return an error
|
||||
if err := index.Delete("non-existing"); err == nil {
|
||||
t.Fatalf("Deleting a non-existing id should return an error")
|
||||
}
|
||||
|
||||
// Deleting id2 should remove conflicts
|
||||
if err := index.Delete(id2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// id2 should no longer work
|
||||
assertIndexGet(t, index, id2, "", true)
|
||||
assertIndexGet(t, index, id2[:7], "", true)
|
||||
assertIndexGet(t, index, id2[:11], "", true)
|
||||
|
||||
// conflicts between id and id2 should be gone
|
||||
assertIndexGet(t, index, id[:6], id, false)
|
||||
assertIndexGet(t, index, id[:4], id, false)
|
||||
assertIndexGet(t, index, id[:1], id, false)
|
||||
|
||||
// non-conflicting substrings should still not conflict
|
||||
assertIndexGet(t, index, id[:7], id, false)
|
||||
assertIndexGet(t, index, id[:15], id, false)
|
||||
assertIndexGet(t, index, id, id, false)
|
||||
}
|
||||
|
||||
func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult string, expectError bool) {
|
||||
if result, err := index.Get(input); err != nil && !expectError {
|
||||
t.Fatalf("Unexpected error getting '%s': %s", input, err)
|
||||
} else if err == nil && expectError {
|
||||
t.Fatalf("Getting '%s' should return an error", input)
|
||||
} else if result != expectedResult {
|
||||
t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
|
||||
}
|
||||
}
|
||||
|
||||
func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) {
|
||||
if r := CompareKernelVersion(a, b); r != result {
|
||||
t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareKernelVersion(t *testing.T) {
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
0)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
-1)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
|
||||
1)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "16"},
|
||||
0)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
1)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Flavor: "25"},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
|
||||
-1)
|
||||
}
|
||||
|
||||
func TestHumanSize(t *testing.T) {
|
||||
|
||||
size := strings.Trim(HumanSize(1000), " \t")
|
||||
expect := "1 kB"
|
||||
if size != expect {
|
||||
t.Errorf("1000 -> expected '%s', got '%s'", expect, size)
|
||||
}
|
||||
|
||||
size = strings.Trim(HumanSize(1024), " \t")
|
||||
expect = "1.024 kB"
|
||||
if size != expect {
|
||||
t.Errorf("1024 -> expected '%s', got '%s'", expect, size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRAMInBytes(t *testing.T) {
|
||||
assertRAMInBytes(t, "32", false, 32)
|
||||
assertRAMInBytes(t, "32b", false, 32)
|
||||
assertRAMInBytes(t, "32B", false, 32)
|
||||
assertRAMInBytes(t, "32k", false, 32*1024)
|
||||
assertRAMInBytes(t, "32K", false, 32*1024)
|
||||
assertRAMInBytes(t, "32kb", false, 32*1024)
|
||||
assertRAMInBytes(t, "32Kb", false, 32*1024)
|
||||
assertRAMInBytes(t, "32Mb", false, 32*1024*1024)
|
||||
assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024)
|
||||
|
||||
assertRAMInBytes(t, "", true, -1)
|
||||
assertRAMInBytes(t, "hello", true, -1)
|
||||
assertRAMInBytes(t, "-32", true, -1)
|
||||
assertRAMInBytes(t, " 32 ", true, -1)
|
||||
assertRAMInBytes(t, "32 mb", true, -1)
|
||||
assertRAMInBytes(t, "32m b", true, -1)
|
||||
assertRAMInBytes(t, "32bm", true, -1)
|
||||
}
|
||||
|
||||
func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) {
|
||||
actualBytes, err := RAMInBytes(size)
|
||||
if (err != nil) && !expectError {
|
||||
t.Errorf("Unexpected error parsing '%s': %s", size, err)
|
||||
}
|
||||
if (err == nil) && expectError {
|
||||
t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes)
|
||||
}
|
||||
if actualBytes != expectedBytes {
|
||||
t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHost(t *testing.T) {
|
||||
var (
|
||||
defaultHttpHost = "127.0.0.1"
|
||||
defaultHttpPort = 4243
|
||||
defaultUnix = "/var/run/docker.sock"
|
||||
)
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "0.0.0.0"); err != nil || addr != "tcp://0.0.0.0:4243" {
|
||||
t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "0.0.0.1:5555"); err != nil || addr != "tcp://0.0.0.1:5555" {
|
||||
t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, ":6666"); err != nil || addr != "tcp://127.0.0.1:6666" {
|
||||
t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "tcp://:7777"); err != nil || addr != "tcp://127.0.0.1:7777" {
|
||||
t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, ""); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||
t.Errorf("empty argument -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "unix:///var/run/docker.sock"); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "unix://"); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "udp://127.0.0.1"); err == nil {
|
||||
t.Errorf("udp protocol address expected error return, but err == nil. Got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "udp://127.0.0.1:4243"); err == nil {
|
||||
t.Errorf("udp protocol address expected error return, but err == nil. Got %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRepositoryTag(t *testing.T) {
|
||||
if repo, tag := ParseRepositoryTag("root"); repo != "root" || tag != "" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("root:tag"); repo != "root" || tag != "tag" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "tag", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("user/repo"); repo != "user/repo" || tag != "" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("user/repo:tag"); repo != "user/repo" || tag != "tag" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "tag", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("url:5000/repo"); repo != "url:5000/repo" || tag != "" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("url:5000/repo:tag"); repo != "url:5000/repo" || tag != "tag" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "tag", repo, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetResolvConf(t *testing.T) {
|
||||
resolvConfUtils, err := GetResolvConf()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(resolvConfUtils) != string(resolvConfSystem) {
|
||||
t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLocalDns(t *testing.T) {
|
||||
for resolv, result := range map[string]bool{`# Dynamic
|
||||
nameserver 10.0.2.3
|
||||
search dotcloud.net`: false,
|
||||
`# Dynamic
|
||||
#nameserver 127.0.0.1
|
||||
nameserver 10.0.2.3
|
||||
search dotcloud.net`: false,
|
||||
`# Dynamic
|
||||
nameserver 10.0.2.3 #not used 127.0.1.1
|
||||
search dotcloud.net`: false,
|
||||
`# Dynamic
|
||||
#nameserver 10.0.2.3
|
||||
#search dotcloud.net`: true,
|
||||
`# Dynamic
|
||||
nameserver 127.0.0.1
|
||||
search dotcloud.net`: true,
|
||||
`# Dynamic
|
||||
nameserver 127.0.1.1
|
||||
search dotcloud.net`: true,
|
||||
`# Dynamic
|
||||
`: true,
|
||||
``: true,
|
||||
} {
|
||||
if CheckLocalDns([]byte(resolv)) != result {
|
||||
t.Fatalf("Wrong local dns detection: {%s} should be %v", resolv, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertParseRelease(t *testing.T, release string, b *KernelVersionInfo, result int) {
|
||||
var (
|
||||
a *KernelVersionInfo
|
||||
)
|
||||
a, _ = ParseRelease(release)
|
||||
|
||||
if r := CompareKernelVersion(a, b); r != result {
|
||||
t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRelease(t *testing.T) {
|
||||
assertParseRelease(t, "3.8.0", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0)
|
||||
assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54}, 0)
|
||||
assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: "1"}, 0)
|
||||
assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "19-generic"}, 0)
|
||||
}
|
||||
|
||||
func TestDependencyGraphCircular(t *testing.T) {
|
||||
g1 := NewDependencyGraph()
|
||||
a := g1.NewNode("a")
|
||||
b := g1.NewNode("b")
|
||||
g1.AddDependency(a, b)
|
||||
g1.AddDependency(b, a)
|
||||
res, err := g1.GenerateTraversalMap()
|
||||
if res != nil {
|
||||
t.Fatalf("Expected nil result")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error (circular graph can not be resolved)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependencyGraph(t *testing.T) {
|
||||
g1 := NewDependencyGraph()
|
||||
a := g1.NewNode("a")
|
||||
b := g1.NewNode("b")
|
||||
c := g1.NewNode("c")
|
||||
d := g1.NewNode("d")
|
||||
g1.AddDependency(b, a)
|
||||
g1.AddDependency(c, a)
|
||||
g1.AddDependency(d, c)
|
||||
g1.AddDependency(d, b)
|
||||
res, err := g1.GenerateTraversalMap()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
if res == nil {
|
||||
t.Fatalf("Unexpected nil result")
|
||||
}
|
||||
|
||||
if len(res) != 3 {
|
||||
t.Fatalf("Expected map of length 3, found %d instead", len(res))
|
||||
}
|
||||
|
||||
if len(res[0]) != 1 || res[0][0] != "a" {
|
||||
t.Fatalf("Expected [a], found %v instead", res[0])
|
||||
}
|
||||
|
||||
if len(res[1]) != 2 {
|
||||
t.Fatalf("Expected 2 nodes for step 2, found %d", len(res[1]))
|
||||
}
|
||||
|
||||
if (res[1][0] != "b" && res[1][1] != "b") || (res[1][0] != "c" && res[1][1] != "c") {
|
||||
t.Fatalf("Expected [b, c], found %v instead", res[1])
|
||||
}
|
||||
|
||||
if len(res[2]) != 1 || res[2][0] != "d" {
|
||||
t.Fatalf("Expected [d], found %v instead", res[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePortMapping(t *testing.T) {
|
||||
data, err := PartParser("ip:public:private", "192.168.1.1:80:8080")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(data) != 3 {
|
||||
t.FailNow()
|
||||
}
|
||||
if data["ip"] != "192.168.1.1" {
|
||||
t.Fail()
|
||||
}
|
||||
if data["public"] != "80" {
|
||||
t.Fail()
|
||||
}
|
||||
if data["private"] != "8080" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNameserversAsCIDR(t *testing.T) {
|
||||
for resolv, result := range map[string][]string{`
|
||||
nameserver 1.2.3.4
|
||||
nameserver 40.3.200.10
|
||||
search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
|
||||
`search example.com`: {},
|
||||
`nameserver 1.2.3.4
|
||||
search example.com
|
||||
nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
|
||||
``: {},
|
||||
` nameserver 1.2.3.4 `: {"1.2.3.4/32"},
|
||||
`search example.com
|
||||
nameserver 1.2.3.4
|
||||
#nameserver 4.3.2.1`: {"1.2.3.4/32"},
|
||||
`search example.com
|
||||
nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
|
||||
} {
|
||||
test := GetNameserversAsCIDR([]byte(resolv))
|
||||
if !StrSlicesEqual(test, result) {
|
||||
t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StrSlicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
17
third_party/github.com/fsouza/go-dockerclient/utils/utils_windows.go
vendored
Normal file
17
third_party/github.com/fsouza/go-dockerclient/utils/utils_windows.go
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Utsname struct {
|
||||
Release [65]byte
|
||||
}
|
||||
|
||||
func uname() (*Utsname, error) {
|
||||
return nil, errors.New("Kernel version detection is not available on windows")
|
||||
}
|
Reference in New Issue
Block a user