Bump go-dockerclient.

This commit is contained in:
Paul Liétar 2014-09-23 16:32:13 +02:00
parent 1cbf0bc910
commit 3956f6ce4d
40 changed files with 1512 additions and 3706 deletions

4
Godeps/Godeps.json generated
View File

@ -50,8 +50,8 @@
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Comment": "0.2.1-185-g4fc0e0d",
"Rev": "4fc0e0dbba85f2f799aa77cea594d40267f5bd5a"
"Comment": "0.2.1-251-g2e21eae",
"Rev": "2e21eaef5e7d46f002e259eb7cde39ed3680a7b4"
},
{
"ImportPath": "github.com/godbus/dbus",

View File

@ -1,8 +1,8 @@
language: go
go:
- 1.1.2
- 1.2
- 1.3
- 1.2.2
- 1.3.1
- tip
env:
- GOARCH=amd64

View File

@ -1,13 +1,20 @@
# This is the official list of go-dockerclient authors for copyright purposes.
Aldrin Leal <aldrin@leal.eng.br>
Andreas Jaekle <andreas@jaekle.net>
Andrews Medina <andrewsmedina@gmail.com>
Andy Goldstein <andy.goldstein@redhat.com>
Ben McCann <benmccann.com>
Carlos Diaz-Padron <cpadron@mozilla.com>
Cezar Sa Espinola <cezar.sa@corp.globo.com>
Cheah Chu Yeow <chuyeow@gmail.com>
cheneydeng <cheneydeng@qq.com>
CMGS <ilskdw@gmail.com>
Daniel, Dao Quang Minh <dqminh89@gmail.com>
David Huie <dahuie@gmail.com>
Ed <edrocksit@gmail.com>
Eric Anderson <anderson@copperegg.com>
Fabio Rehm <fgrehm@gmail.com>
Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc>
Jari Kolehmainen <jari.kolehmainen@digia.com>
@ -15,12 +22,16 @@ Jason Wilder <jwilder@litl.com>
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
Jeff Mitchell <jeffrey.mitchell@gmail.com>
Jeffrey Hulten <jhulten@gmail.com>
Johan Euphrosine <proppy@google.com>
Karan Misra <kidoman@gmail.com>
Kim, Hirokuni <hirokuni.kim@kvh.co.jp>
Lucas Clemente <lucas@clemente.io>
Omeid Matten <public@omeid.me>
Paul Morie <pmorie@gmail.com>
Peter Jihoon Kim <raingrove@gmail.com>
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
Rafe Colton <r.colton@modcloth.com>
Robert Williamson <williamson.robert@gmail.com>
Salvador Gironès <salvadorgirones@gmail.com>
Simon Eskildsen <sirup@sirupsen.com>
Simon Menke <simon.menke@gmail.com>

View File

@ -11,27 +11,28 @@ For more details, check the [remote API documentation](http://docs.docker.io/en/
## Example
package main
```go
package main
import (
"fmt"
"github.com/fsouza/go-dockerclient"
)
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)
}
}
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)
}
}
```
## Developing

View File

@ -16,7 +16,7 @@ const (
// Change represents a change in a container.
//
// See http://goo.gl/DpGyzK for more details.
// See http://goo.gl/QkW9sH for more details.
type Change struct {
Path string
Kind ChangeType

View File

@ -4,7 +4,7 @@
// Package docker provides a client for the Docker remote API.
//
// See http://goo.gl/mxyql for more details on the remote API.
// See http://goo.gl/G3plxW for more details on the remote API.
package docker
import (
@ -21,9 +21,6 @@ import (
"reflect"
"strconv"
"strings"
"sync"
"github.com/fsouza/go-dockerclient/utils"
)
const userAgent = "go-dockerclient"
@ -113,11 +110,11 @@ func (version ApiVersion) compare(other ApiVersion) int {
// interaction with the API.
type Client struct {
SkipServerVersionCheck bool
HTTPClient *http.Client
endpoint string
endpointURL *url.URL
eventMonitor *eventMonitoringState
client *http.Client
requestedApiVersion ApiVersion
serverApiVersion ApiVersion
expectedApiVersion ApiVersion
@ -142,7 +139,6 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
if err != nil {
return nil, err
}
var requestedApiVersion ApiVersion
if strings.Contains(apiVersionString, ".") {
requestedApiVersion, err = NewApiVersion(apiVersionString)
@ -150,11 +146,10 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
return nil, err
}
}
return &Client{
HTTPClient: http.DefaultClient,
endpoint: endpoint,
endpointURL: u,
client: http.DefaultClient,
eventMonitor: new(eventMonitoringState),
requestedApiVersion: requestedApiVersion,
}, nil
@ -177,29 +172,6 @@ func (c *Client) checkApiVersion() error {
return nil
}
func parseApiVersionString(input string) (version uint16, err error) {
version = 0
if !strings.Contains(input, ".") {
return 0, fmt.Errorf("Unable to parse version '%s'", input)
}
arr := strings.Split(input, ".")
major, err := strconv.Atoi(arr[0])
if err != nil {
return version, err
}
minor, err := strconv.Atoi(arr[1])
if err != nil {
return version, err
}
version = uint16(major)<<8 | uint16(minor)
return version, nil
}
// Ping pings the docker server
//
// See http://goo.gl/stJENm for more details.
@ -223,13 +195,11 @@ func (c *Client) getServerApiVersionString() (version string, err error) {
if status != http.StatusOK {
return "", fmt.Errorf("Received unexpected status %d while trying to retrieve the server version", status)
}
var versionResponse map[string]string
err = json.Unmarshal(body, &versionResponse)
if err != nil {
return "", err
}
version = versionResponse["ApiVersion"]
return version, nil
}
@ -243,14 +213,12 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error)
}
params = bytes.NewBuffer(buf)
}
if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil {
err := c.checkApiVersion()
if err != nil {
return nil, -1, err
}
}
req, err := http.NewRequest(method, c.getURL(path), params)
if err != nil {
return nil, -1, err
@ -277,7 +245,7 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error)
}
defer clientconn.Close()
} else {
resp, err = c.client.Do(req)
resp, err = c.HTTPClient.Do(req)
}
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
@ -296,7 +264,7 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error)
return body, resp.StatusCode, nil
}
func (c *Client) stream(method, path string, setRawTerminal bool, headers map[string]string, in io.Reader, stdout, stderr io.Writer) error {
func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, headers map[string]string, in io.Reader, stdout, stderr io.Writer) error {
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader(nil)
}
@ -335,7 +303,7 @@ func (c *Client) stream(method, path string, setRawTerminal bool, headers map[st
resp, err = clientconn.Do(req)
defer clientconn.Close()
} else {
resp, err = c.client.Do(req)
resp, err = c.HTTPClient.Do(req)
}
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
@ -352,6 +320,12 @@ func (c *Client) stream(method, path string, setRawTerminal bool, headers map[st
return newError(resp.StatusCode, body)
}
if resp.Header.Get("Content-Type") == "application/json" {
// if we want to get raw json stream, just copy it back to output
// without decoding it
if rawJSONStream {
_, err = io.Copy(stdout, resp.Body)
return err
}
dec := json.NewDecoder(resp.Body)
for {
var m jsonMessage
@ -375,7 +349,7 @@ func (c *Client) stream(method, path string, setRawTerminal bool, headers map[st
if setRawTerminal {
_, err = io.Copy(stdout, resp.Body)
} else {
_, err = utils.StdCopy(stdout, stderr, resp.Body)
_, err = stdCopy(stdout, stderr, resp.Body)
}
return err
}
@ -418,18 +392,17 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
<-success
}
rwc, br := clientconn.Hijack()
var wg sync.WaitGroup
wg.Add(2)
errs := make(chan error, 2)
exit := make(chan bool)
go func() {
defer close(exit)
var err error
if setRawTerminal {
_, err = io.Copy(stdout, br)
} else {
_, err = utils.StdCopy(stdout, stderr, br)
_, err = stdCopy(stdout, stderr, br)
}
errs <- err
wg.Done()
}()
go func() {
var err error
@ -440,14 +413,9 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
CloseWrite() error
}).CloseWrite()
errs <- err
wg.Done()
}()
wg.Wait()
close(errs)
if err := <-errs; err != nil {
return err
}
return nil
<-exit
return <-errs
}
func (c *Client) getURL(path string) string {

View File

@ -24,10 +24,9 @@ func TestNewAPIClient(t *testing.T) {
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)
if client.HTTPClient != http.DefaultClient {
t.Errorf("Expected http.Client %#v. Got %#v.", http.DefaultClient, client.HTTPClient)
}
// test unix socket endpoints
endpoint = "unix:///var/run/docker.sock"
client, err = NewClient(endpoint)
@ -54,8 +53,8 @@ func TestNewVersionedClient(t *testing.T) {
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)
if client.HTTPClient != http.DefaultClient {
t.Errorf("Expected http.Client %#v. Got %#v.", http.DefaultClient, client.HTTPClient)
}
if reqVersion := client.requestedApiVersion.String(); reqVersion != "1.12" {
t.Errorf("Wrong requestApiVersion. Want %q. Got %q.", "1.12", reqVersion)
@ -249,16 +248,22 @@ func TestPingFailingWrongStatus(t *testing.T) {
type FakeRoundTripper struct {
message string
status int
header map[string]string
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{
res := &http.Response{
StatusCode: rt.status,
Body: ioutil.NopCloser(body),
}, nil
Header: make(http.Header),
}
for k, v := range rt.header {
res.Header.Set(k, v)
}
return res, nil
}
func (rt *FakeRoundTripper) Reset() {

View File

@ -18,7 +18,7 @@ import (
// ListContainersOptions specify parameters to the ListContainers function.
//
// See http://goo.gl/QpCnDN for more details.
// See http://goo.gl/XqtcyU for more details.
type ListContainersOptions struct {
All bool
Size bool
@ -28,30 +28,30 @@ type ListContainersOptions struct {
}
type APIPort struct {
PrivatePort int64
PublicPort int64
Type string
IP string
PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty"`
PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty"`
Type string `json:"Type,omitempty" yaml:"Type,omitempty"`
IP string `json:"IP,omitempty" yaml:"IP,omitempty"`
}
// 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
ID string `json:"Id" yaml:"Id"`
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
Command string `json:"Command,omitempty" yaml:"Command,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
Status string `json:"Status,omitempty" yaml:"Status,omitempty"`
Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty"`
SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"`
SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"`
Names []string `json:"Names,omitempty" yaml:"Names,omitempty"`
}
// ListContainers returns a slice of containers matching the given criteria.
//
// See http://goo.gl/QpCnDN for more details.
// See http://goo.gl/XqtcyU for more details.
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
path := "/containers/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil)
@ -86,12 +86,12 @@ func (p Port) Proto() string {
// State represents the state of a container.
type State struct {
Running bool
Paused bool
Pid int
ExitCode int
StartedAt time.Time
FinishedAt time.Time
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"`
Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"`
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty"`
FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty"`
}
// String returns the string representation of a state.
@ -106,19 +106,19 @@ func (s *State) String() string {
}
type PortBinding struct {
HostIp string
HostPort string
HostIp string `json:"HostIP,omitempty" yaml:"HostIP,omitempty"`
HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty"`
}
type PortMapping map[string]string
type NetworkSettings struct {
IPAddress string
IPPrefixLen int
Gateway string
Bridge string
PortMapping map[string]PortMapping
Ports map[Port][]PortBinding
IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"`
IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"`
Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"`
Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"`
PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"`
Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"`
}
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
@ -155,60 +155,61 @@ func parsePort(rawPort string) (int, error) {
}
type Config struct {
Hostname string
Domainname string
User string
Memory uint64
MemorySwap uint64
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
Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"`
Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty"`
User string `json:"User,omitempty" yaml:"User,omitempty"`
Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"`
MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
CpuShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
CpuSet string `json:"CpuSet,omitempty" yaml:"CpuSet,omitempty"`
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"`
ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"`
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"`
OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"`
Env []string `json:"Env,omitempty" yaml:"Env,omitempty"`
Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
Dns []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"`
NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
}
type Container struct {
ID string
ID string `json:"Id" yaml:"Id"`
Created time.Time
Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty"`
Path string
Args []string
Path string `json:"Path,omitempty" yaml:"Path,omitempty"`
Args []string `json:"Args,omitempty" yaml:"Args,omitempty"`
Config *Config
State State
Image string
Config *Config `json:"Config,omitempty" yaml:"Config,omitempty"`
State State `json:"State,omitempty" yaml:"State,omitempty"`
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
NetworkSettings *NetworkSettings
NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"`
SysInitPath string
ResolvConfPath string
HostnamePath string
HostsPath string
Name string
Driver string
SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty"`
ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty"`
HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty"`
HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty"`
Name string `json:"Name,omitempty" yaml:"Name,omitempty"`
Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"`
Volumes map[string]string
VolumesRW map[string]bool
HostConfig *HostConfig
Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"`
HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
}
// InspectContainer returns information about a container by its ID.
//
// See http://goo.gl/2o52Sx for more details.
// See http://goo.gl/CxVuJ5 for more details.
func (c *Client) InspectContainer(id string) (*Container, error) {
path := "/containers/" + id + "/json"
body, status, err := c.do("GET", path, nil)
@ -228,7 +229,7 @@ func (c *Client) InspectContainer(id string) (*Container, error) {
// ContainerChanges returns changes in the filesystem of the given container.
//
// See http://goo.gl/DpGyzK for more details.
// See http://goo.gl/QkW9sH for more details.
func (c *Client) ContainerChanges(id string) ([]Change, error) {
path := "/containers/" + id + "/changes"
body, status, err := c.do("GET", path, nil)
@ -248,7 +249,7 @@ func (c *Client) ContainerChanges(id string) ([]Change, error) {
// CreateContainerOptions specify parameters to the CreateContainer function.
//
// See http://goo.gl/WPPYtB for more details.
// See http://goo.gl/mErxNp for more details.
type CreateContainerOptions struct {
Name string
Config *Config `qs:"-"`
@ -257,7 +258,7 @@ type CreateContainerOptions struct {
// CreateContainer creates a new container, returning the container instance,
// or an error in case of failure.
//
// See http://goo.gl/tjihUc for more details.
// See http://goo.gl/mErxNp for more details.
func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
path := "/containers/create?" + queryString(opts)
body, status, err := c.do("POST", path, opts.Config)
@ -279,27 +280,61 @@ func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error
}
type KeyValuePair struct {
Key string
Value string
Key string `json:"Key,omitempty" yaml:"Key,omitempty"`
Value string `json:"Value,omitempty" yaml:"Value,omitempty"`
}
// RestartPolicy represents the policy for automatically restarting a container.
//
// Possible values are:
//
// - always: the docker daemon will always restart the container
// - on-failure: the docker daemon will restart the container on failures, at
// most MaximumRetryCount times
// - no: the docker daemon will not restart the container automatically
type RestartPolicy struct {
Name string `json:"Name,omitempty" yaml:"Name,omitempty"`
MaximumRetryCount int `json:"MaximumRetryCount,omitempty" yaml:"MaximumRetryCount,omitempty"`
}
// AlwaysRestart returns a restart policy that tells the Docker daemon to
// always restart the container.
func AlwaysRestart() RestartPolicy {
return RestartPolicy{Name: "always"}
}
// RestartOnFailure returns a restart policy that tells the Docker daemon to
// restart the container on failures, trying at most maxRetry times.
func RestartOnFailure(maxRetry int) RestartPolicy {
return RestartPolicy{Name: "on-failure", MaximumRetryCount: maxRetry}
}
// NeverRestart returns a restart policy that tells the Docker daemon to never
// restart the container on failures.
func NeverRestart() RestartPolicy {
return RestartPolicy{Name: "no"}
}
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
DnsSearch []string
VolumesFrom []string
NetworkMode string
Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"`
CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"`
CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"`
ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"`
LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"`
Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"`
PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"`
Links []string `json:"Links,omitempty" yaml:"Links,omitempty"`
PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"`
Dns []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
DnsSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`
}
// StartContainer starts a container, returning an errror in case of failure.
// StartContainer starts a container, returning an error in case of failure.
//
// See http://goo.gl/y5GZlE for more details.
// See http://goo.gl/iM5GYs for more details.
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
if hostConfig == nil {
hostConfig = &HostConfig{}
@ -309,6 +344,9 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
if status == http.StatusNotModified {
return &ContainerAlreadyRunning{ID: id}
}
if err != nil {
return err
}
@ -318,13 +356,16 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
// StopContainer stops a container, killing it after the given timeout (in
// seconds).
//
// See http://goo.gl/X2mj8t for more details.
// See http://goo.gl/EbcpXt for more details.
func (c *Client) StopContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
_, status, err := c.do("POST", path, nil)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
if status == http.StatusNotModified {
return &ContainerNotRunning{ID: id}
}
if err != nil {
return err
}
@ -334,7 +375,7 @@ func (c *Client) StopContainer(id string, timeout uint) error {
// RestartContainer stops a container, killing it after the given timeout (in
// seconds), during the stop process.
//
// See http://goo.gl/zms73Z for more details.
// See http://goo.gl/VOzR2n for more details.
func (c *Client) RestartContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
_, status, err := c.do("POST", path, nil)
@ -379,6 +420,8 @@ func (c *Client) UnpauseContainer(id string) error {
// KillContainerOptions represents the set of options that can be used in a
// call to KillContainer.
//
// See http://goo.gl/TFkECx for more details.
type KillContainerOptions struct {
// The ID of the container.
ID string `qs:"-"`
@ -390,7 +433,7 @@ type KillContainerOptions struct {
// KillContainer kills a container, returning an error in case of failure.
//
// See http://goo.gl/DPbbBy for more details.
// See http://goo.gl/TFkECx for more details.
func (c *Client) KillContainer(opts KillContainerOptions) error {
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
_, status, err := c.do("POST", path, nil)
@ -404,6 +447,8 @@ func (c *Client) KillContainer(opts KillContainerOptions) error {
}
// RemoveContainerOptions encapsulates options to remove a container.
//
// See http://goo.gl/ZB83ji for more details.
type RemoveContainerOptions struct {
// The ID of the container.
ID string `qs:"-"`
@ -419,7 +464,7 @@ type RemoveContainerOptions struct {
// RemoveContainer removes a container, returning an error in case of failure.
//
// See http://goo.gl/PBvGdU for more details.
// See http://goo.gl/ZB83ji for more details.
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
path := "/containers/" + opts.ID + "?" + queryString(opts)
_, status, err := c.do("DELETE", path, nil)
@ -435,7 +480,7 @@ func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
// 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.
// See http://goo.gl/rINMlw for more details.
type CopyFromContainerOptions struct {
OutputStream io.Writer `json:"-"`
Container string `json:"-"`
@ -445,7 +490,7 @@ type CopyFromContainerOptions struct {
// CopyFromContainer copy files or folders from a container, using a given
// resource.
//
// See http://goo.gl/mnxRMl for more details.
// See http://goo.gl/rINMlw for more details.
func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container}
@ -465,7 +510,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
// WaitContainer blocks until the given container stops, return the exit code
// of the container status.
//
// See http://goo.gl/gnHJL2 for more details.
// See http://goo.gl/J88DHU for more details.
func (c *Client) WaitContainer(id string) (int, error) {
body, status, err := c.do("POST", "/containers/"+id+"/wait", nil)
if status == http.StatusNotFound {
@ -484,7 +529,7 @@ func (c *Client) WaitContainer(id string) (int, error) {
// CommitContainerOptions aggregates parameters to the CommitContainer method.
//
// See http://goo.gl/628gxm for more details.
// See http://goo.gl/Jn8pe8 for more details.
type CommitContainerOptions struct {
Container string
Repository string `qs:"repo"`
@ -496,7 +541,7 @@ type CommitContainerOptions struct {
// CommitContainer creates a new image from a container's changes.
//
// See http://goo.gl/628gxm for more details.
// See http://goo.gl/Jn8pe8 for more details.
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
path := "/commit?" + queryString(opts)
body, status, err := c.do("POST", path, opts.Run)
@ -517,7 +562,7 @@ func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
// AttachToContainerOptions is the set of options that can be used when
// attaching to a container.
//
// See http://goo.gl/oPzcqH for more details.
// See http://goo.gl/RRAhws for more details.
type AttachToContainerOptions struct {
Container string `qs:"-"`
InputStream io.Reader `qs:"-"`
@ -552,7 +597,7 @@ type AttachToContainerOptions struct {
// AttachToContainer attaches to a container, using the given options.
//
// See http://goo.gl/oPzcqH for more details.
// See http://goo.gl/RRAhws for more details.
func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container}
@ -590,7 +635,7 @@ func (c *Client) Logs(opts LogsOptions) error {
opts.Tail = "all"
}
path := "/containers/" + opts.Container + "/logs?" + queryString(opts)
return c.stream("GET", path, opts.RawTerminal, nil, nil, opts.OutputStream, opts.ErrorStream)
return c.stream("GET", path, opts.RawTerminal, false, nil, nil, opts.OutputStream, opts.ErrorStream)
}
// ResizeContainerTTY resizes the terminal to the given height and width.
@ -605,7 +650,7 @@ func (c *Client) ResizeContainerTTY(id string, height, width int) error {
// ExportContainerOptions is the set of parameters to the ExportContainer
// method.
//
// See http://goo.gl/Lqk0FZ for more details.
// See http://goo.gl/hnzE62 for more details.
type ExportContainerOptions struct {
ID string
OutputStream io.Writer
@ -614,13 +659,13 @@ type ExportContainerOptions struct {
// 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.
// See http://goo.gl/hnzE62 for more details.
func (c *Client) ExportContainer(opts ExportContainerOptions) error {
if opts.ID == "" {
return NoSuchContainer{ID: opts.ID}
return &NoSuchContainer{ID: opts.ID}
}
url := fmt.Sprintf("/containers/%s/export", opts.ID)
return c.stream("GET", url, true, nil, nil, opts.OutputStream, nil)
return c.stream("GET", url, true, false, nil, nil, opts.OutputStream, nil)
}
// NoSuchContainer is the error returned when a given container does not exist.
@ -628,6 +673,26 @@ type NoSuchContainer struct {
ID string
}
func (err NoSuchContainer) Error() string {
func (err *NoSuchContainer) Error() string {
return "No such container: " + err.ID
}
// ContainerAlreadyRunning is the error returned when a given container is
// already running.
type ContainerAlreadyRunning struct {
ID string
}
func (err *ContainerAlreadyRunning) Error() string {
return "Container already running: " + err.ID
}
// ContainerNotRunning is the error returned when a given container is not
// running.
type ContainerNotRunning struct {
ID string
}
func (err *ContainerNotRunning) Error() string {
return "Container not running: " + err.ID
}

View File

@ -225,6 +225,88 @@ func TestInspectContainer(t *testing.T) {
}
}
func TestInspectContainerNegativeSwap(t *testing.T) {
jsonContainer := `{
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
"Created": "2013-05-07T14:51:42.087658+02:00",
"Path": "date",
"Args": [],
"Config": {
"Hostname": "4fa6e0f0c678",
"User": "",
"Memory": 17179869184,
"MemorySwap": -1,
"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"}
@ -411,6 +493,15 @@ func TestStartContainerNotFound(t *testing.T) {
}
}
func TestStartContainerAlreadyRunning(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "container already running", status: http.StatusNotModified})
err := client.StartContainer("a2334", &HostConfig{})
expected := &ContainerAlreadyRunning{ID: "a2334"}
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)
@ -438,6 +529,15 @@ func TestStopContainerNotFound(t *testing.T) {
}
}
func TestStopContainerNotRunning(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "container not running", status: http.StatusNotModified})
err := client.StopContainer("a2334", 10)
expected := &ContainerNotRunning{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)
@ -1184,9 +1284,9 @@ func TestExportContainerViaUnixSocket(t *testing.T) {
endpoint := "unix://" + tempSocket
u, _ := parseEndpoint(endpoint)
client := Client{
HTTPClient: http.DefaultClient,
endpoint: endpoint,
endpointURL: u,
client: http.DefaultClient,
SkipServerVersionCheck: true,
}
listening := make(chan string)
@ -1231,8 +1331,12 @@ 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)
e, ok := err.(*NoSuchContainer)
if !ok {
t.Errorf("ExportContainer: wrong error. Want NoSuchContainer. Got %#v.", e)
}
if e.ID != "" {
t.Errorf("ExportContainer: wrong ID. Want %q. Got %q", "", e.ID)
}
}
@ -1279,3 +1383,34 @@ func TestPassingNameOptToCreateContainerReturnsItInContainer(t *testing.T) {
t.Errorf("Container name expected to be TestCreateContainer, was %s", container.Name)
}
}
func TestAlwaysRestart(t *testing.T) {
policy := AlwaysRestart()
if policy.Name != "always" {
t.Errorf("AlwaysRestart(): wrong policy name. Want %q. Got %q", "always", policy.Name)
}
if policy.MaximumRetryCount != 0 {
t.Errorf("AlwaysRestart(): wrong MaximumRetryCount. Want 0. Got %d", policy.MaximumRetryCount)
}
}
func TestRestartOnFailure(t *testing.T) {
const retry = 5
policy := RestartOnFailure(retry)
if policy.Name != "on-failure" {
t.Errorf("RestartOnFailure(%d): wrong policy name. Want %q. Got %q", retry, "on-failure", policy.Name)
}
if policy.MaximumRetryCount != retry {
t.Errorf("RestartOnFailure(%d): wrong MaximumRetryCount. Want %d. Got %d", retry, retry, policy.MaximumRetryCount)
}
}
func TestNeverRestart(t *testing.T) {
policy := NeverRestart()
if policy.Name != "no" {
t.Errorf("NeverRestart(): wrong policy name. Want %q. Got %q", "always", policy.Name)
}
if policy.MaximumRetryCount != 0 {
t.Errorf("NeverRestart(): wrong MaximumRetryCount. Want 0. Got %d", policy.MaximumRetryCount)
}
}

View File

@ -1,147 +0,0 @@
// 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...)
}

View File

@ -1,111 +0,0 @@
// 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)
}
}

View File

@ -1,238 +0,0 @@
// 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
}

View File

@ -1,127 +0,0 @@
// 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")
}
}

View File

@ -1,25 +0,0 @@
// 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
}

View File

@ -1,28 +0,0 @@
// 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...)
}

View File

@ -1,44 +0,0 @@
// 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()
}

View File

@ -1,197 +0,0 @@
// 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)
}

View File

@ -1,84 +0,0 @@
// 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)
}
}

View File

@ -1,196 +0,0 @@
// 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
}

View File

@ -1,298 +0,0 @@
// 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)
}
}

View File

@ -0,0 +1,168 @@
// 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 docker
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
)
// Env represents a list of key-pair represented in the form KEY=VALUE.
type Env []string
// Get returns the string value of the given key.
func (env *Env) Get(key string) (value string) {
return env.Map()[key]
}
// Exists checks whether the given key is defined in the internal Env
// representation.
func (env *Env) Exists(key string) bool {
_, exists := env.Map()[key]
return exists
}
// GetBool returns a boolean representation of the given key. The key is false
// whenever its value if 0, no, false, none or an empty string. Any other value
// will be interpreted as true.
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
}
// SetBool defines a boolean value to the given key.
func (env *Env) SetBool(key string, value bool) {
if value {
env.Set(key, "1")
} else {
env.Set(key, "0")
}
}
// GetInt returns the value of the provided key, converted to int.
//
// It the value cannot be represented as an integer, it returns -1.
func (env *Env) GetInt(key string) int {
return int(env.GetInt64(key))
}
// SetInt defines an integer value to the given key.
func (env *Env) SetInt(key string, value int) {
env.Set(key, strconv.Itoa(value))
}
// GetInt64 returns the value of the provided key, converted to int64.
//
// It the value cannot be represented as an integer, it returns -1.
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
}
// SetInt64 defines an integer (64-bit wide) value to the given key.
func (env *Env) SetInt64(key string, value int64) {
env.Set(key, strconv.FormatInt(value, 10))
}
// GetJSON unmarshals the value of the provided key in the provided iface.
//
// iface is a value that can be provided to the json.Unmarshal function.
func (env *Env) GetJSON(key string, iface interface{}) error {
sval := env.Get(key)
if sval == "" {
return nil
}
return json.Unmarshal([]byte(sval), iface)
}
// SetJSON marshals the given value to JSON format and stores it using the
// provided key.
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
}
// GetList returns a list of strings matching the provided key. It handles the
// list as a JSON representation of a list of strings.
//
// If the given key matches to a single string, it will return a list
// containing only the value that matches the key.
func (env *Env) GetList(key string) []string {
sval := env.Get(key)
if sval == "" {
return nil
}
var l []string
if err := json.Unmarshal([]byte(sval), &l); err != nil {
l = append(l, sval)
}
return l
}
// SetList stores the given list in the provided key, after serializing it to
// JSON format.
func (env *Env) SetList(key string, value []string) error {
return env.SetJSON(key, value)
}
// Set defines the value of a key to the given string.
func (env *Env) Set(key, value string) {
*env = append(*env, key+"="+value)
}
// Decode 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
}
// SetAuto will try to define the Set* method to call based on the given value.
func (env *Env) SetAuto(key string, value interface{}) {
if fval, ok := value.(float64); ok {
env.SetInt64(key, int64(fval))
} else if sval, ok := value.(string); ok {
env.Set(key, sval)
} else if val, err := json.Marshal(value); err == nil {
env.Set(key, string(val))
} else {
env.Set(key, fmt.Sprintf("%v", value))
}
}
// Map returns the map representation of the env.
func (env *Env) Map() map[string]string {
if len(*env) == 0 {
return nil
}
m := make(map[string]string)
for _, kv := range *env {
parts := strings.SplitN(kv, "=", 2)
m[parts[0]] = parts[1]
}
return m
}

View File

@ -0,0 +1,349 @@
// 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 DOCKER-LICENSE file.
package docker
import (
"bytes"
"errors"
"reflect"
"sort"
"testing"
)
func TestGet(t *testing.T) {
var tests = []struct {
input []string
query string
expected string
}{
{[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PATH", "/usr/bin:/bin"},
{[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PYTHONPATH", "/usr/local"},
{[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PYTHONPATHI", ""},
{[]string{"WAT="}, "WAT", ""},
}
for _, tt := range tests {
env := Env(tt.input)
got := env.Get(tt.query)
if got != tt.expected {
t.Errorf("Env.Get(%q): wrong result. Want %q. Got %q", tt.query, tt.expected, got)
}
}
}
func TestExists(t *testing.T) {
var tests = []struct {
input []string
query string
expected bool
}{
{[]string{"WAT=", "PYTHONPATH=/usr/local"}, "WAT", true},
{[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PYTHONPATH", true},
{[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PYTHONPATHI", false},
}
for _, tt := range tests {
env := Env(tt.input)
got := env.Exists(tt.query)
if got != tt.expected {
t.Errorf("Env.Exists(%q): wrong result. Want %v. Got %v", tt.query, tt.expected, got)
}
}
}
func TestGetBool(t *testing.T) {
var tests = []struct {
input string
expected bool
}{
{"EMTPY_VAR", false}, {"ZERO_VAR", false}, {"NO_VAR", false},
{"FALSE_VAR", false}, {"NONE_VAR", false}, {"TRUE_VAR", true},
{"WAT", true}, {"PATH", true}, {"ONE_VAR", true}, {"NO_VAR_TAB", false},
}
env := Env([]string{
"EMPTY_VAR=", "ZERO_VAR=0", "NO_VAR=no", "FALSE_VAR=false",
"NONE_VAR=none", "TRUE_VAR=true", "WAT=wat", "PATH=/usr/bin:/bin",
"ONE_VAR=1", "NO_VAR_TAB=0 \t\t\t",
})
for _, tt := range tests {
got := env.GetBool(tt.input)
if got != tt.expected {
t.Errorf("Env.GetBool(%q): wrong result. Want %v. Got %v.", tt.input, tt.expected, got)
}
}
}
func TestSetBool(t *testing.T) {
var tests = []struct {
input bool
expected string
}{
{true, "1"}, {false, "0"},
}
for _, tt := range tests {
var env Env
env.SetBool("SOME", tt.input)
if got := env.Get("SOME"); got != tt.expected {
t.Errorf("Env.SetBool(%v): wrong result. Want %q. Got %q", tt.input, tt.expected, got)
}
}
}
func TestGetInt(t *testing.T) {
var tests = []struct {
input string
expected int
}{
{"NEGATIVE_INTEGER", -10}, {"NON_INTEGER", -1}, {"ONE", 1}, {"TWO", 2},
}
env := Env([]string{"NEGATIVE_INTEGER=-10", "NON_INTEGER=wat", "ONE=1", "TWO=2"})
for _, tt := range tests {
got := env.GetInt(tt.input)
if got != tt.expected {
t.Errorf("Env.GetInt(%q): wrong result. Want %d. Got %d", tt.input, tt.expected, got)
}
}
}
func TestSetInt(t *testing.T) {
var tests = []struct {
input int
expected string
}{
{10, "10"}, {13, "13"}, {7, "7"}, {33, "33"},
{0, "0"}, {-34, "-34"},
}
for _, tt := range tests {
var env Env
env.SetInt("SOME", tt.input)
if got := env.Get("SOME"); got != tt.expected {
t.Errorf("Env.SetBool(%d): wrong result. Want %q. Got %q", tt.input, tt.expected, got)
}
}
}
func TestGetInt64(t *testing.T) {
var tests = []struct {
input string
expected int64
}{
{"NEGATIVE_INTEGER", -10}, {"NON_INTEGER", -1}, {"ONE", 1}, {"TWO", 2},
}
env := Env([]string{"NEGATIVE_INTEGER=-10", "NON_INTEGER=wat", "ONE=1", "TWO=2"})
for _, tt := range tests {
got := env.GetInt64(tt.input)
if got != tt.expected {
t.Errorf("Env.GetInt64(%q): wrong result. Want %d. Got %d", tt.input, tt.expected, got)
}
}
}
func TestSetInt64(t *testing.T) {
var tests = []struct {
input int64
expected string
}{
{10, "10"}, {13, "13"}, {7, "7"}, {33, "33"},
{0, "0"}, {-34, "-34"},
}
for _, tt := range tests {
var env Env
env.SetInt64("SOME", tt.input)
if got := env.Get("SOME"); got != tt.expected {
t.Errorf("Env.SetBool(%d): wrong result. Want %q. Got %q", tt.input, tt.expected, got)
}
}
}
func TestGetJSON(t *testing.T) {
var p struct {
Name string `json:"name"`
Age int `json:"age"`
}
var env Env
env.Set("person", `{"name":"Gopher","age":5}`)
err := env.GetJSON("person", &p)
if err != nil {
t.Error(err)
}
if p.Name != "Gopher" {
t.Errorf("Env.GetJSON(%q): wrong name. Want %q. Got %q", "person", "Gopher", p.Name)
}
if p.Age != 5 {
t.Errorf("Env.GetJSON(%q): wrong age. Want %d. Got %d", "person", 5, p.Age)
}
}
func TestGetJSONAbsent(t *testing.T) {
var l []string
var env Env
err := env.GetJSON("person", &l)
if err != nil {
t.Error(err)
}
if l != nil {
t.Errorf("Env.GetJSON(): get unexpected list %v", l)
}
}
func TestGetJSONFailure(t *testing.T) {
var p []string
var env Env
env.Set("list-person", `{"name":"Gopher","age":5}`)
err := env.GetJSON("list-person", &p)
if err == nil {
t.Errorf("Env.GetJSON(%q): got unexpected <nil> error.", "list-person")
}
}
func TestSetJSON(t *testing.T) {
var p1 = struct {
Name string `json:"name"`
Age int `json:"age"`
}{Name: "Gopher", Age: 5}
var env Env
err := env.SetJSON("person", p1)
if err != nil {
t.Error(err)
}
var p2 struct {
Name string `json:"name"`
Age int `json:"age"`
}
err = env.GetJSON("person", &p2)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(p1, p2) {
t.Errorf("Env.SetJSON(%q): wrong result. Want %v. Got %v", "person", p1, p2)
}
}
func TestSetJSONFailure(t *testing.T) {
var env Env
err := env.SetJSON("person", unmarshable{})
if err == nil {
t.Error("Env.SetJSON(): got unexpected <nil> error")
}
if env.Exists("person") {
t.Errorf("Env.SetJSON(): should not define the key %q, but did", "person")
}
}
func TestGetList(t *testing.T) {
var tests = []struct {
input string
expected []string
}{
{"WAT=wat", []string{"wat"}},
{`WAT=["wat","wet","wit","wot","wut"]`, []string{"wat", "wet", "wit", "wot", "wut"}},
{"WAT=", nil},
}
for _, tt := range tests {
env := Env([]string{tt.input})
got := env.GetList("WAT")
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("Env.GetList(%q): wrong result. Want %v. Got %v", "WAT", tt.expected, got)
}
}
}
func TestSetList(t *testing.T) {
list := []string{"a", "b", "c"}
var env Env
env.SetList("SOME", list)
if got := env.GetList("SOME"); !reflect.DeepEqual(got, list) {
t.Errorf("Env.SetList(%v): wrong result. Got %v", list, got)
}
}
func TestSet(t *testing.T) {
var env Env
env.Set("PATH", "/home/bin:/bin")
env.Set("SOMETHING", "/usr/bin")
env.Set("PATH", "/bin")
if expected, got := "/usr/bin", env.Get("SOMETHING"); got != expected {
t.Errorf("Env.Set(%q): wrong result. Want %q. Got %q", expected, expected, got)
}
if expected, got := "/bin", env.Get("PATH"); got != expected {
t.Errorf("Env.Set(%q): wrong result. Want %q. Got %q", expected, expected, got)
}
}
func TestDecode(t *testing.T) {
var tests = []struct {
input string
expectedOut []string
expectedErr string
}{
{
`{"PATH":"/usr/bin:/bin","containers":54,"wat":["123","345"]}`,
[]string{"PATH=/usr/bin:/bin", "containers=54", `wat=["123","345"]`},
"",
},
{"}}", nil, "invalid character '}' looking for beginning of value"},
{`{}`, nil, ""},
}
for _, tt := range tests {
var env Env
err := env.Decode(bytes.NewBufferString(tt.input))
if tt.expectedErr == "" {
if err != nil {
t.Error(err)
}
} else if tt.expectedErr != err.Error() {
t.Errorf("Env.Decode(): invalid error. Want %q. Got %q.", tt.expectedErr, err)
}
got := []string(env)
sort.Strings(got)
sort.Strings(tt.expectedOut)
if !reflect.DeepEqual(got, tt.expectedOut) {
t.Errorf("Env.Decode(): wrong result. Want %v. Got %v.", tt.expectedOut, got)
}
}
}
func TestSetAuto(t *testing.T) {
buf := bytes.NewBufferString("oi")
var tests = []struct {
input interface{}
expected string
}{
{10, "10"},
{10.3, "10"},
{"oi", "oi"},
{buf, "{}"},
{unmarshable{}, "{}"},
}
for _, tt := range tests {
var env Env
env.SetAuto("SOME", tt.input)
if got := env.Get("SOME"); got != tt.expected {
t.Errorf("Env.SetAuto(%v): wrong result. Want %q. Got %q", tt.input, tt.expected, got)
}
}
}
func TestMap(t *testing.T) {
var tests = []struct {
input []string
expected map[string]string
}{
{[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, map[string]string{"PATH": "/usr/bin:/bin", "PYTHONPATH": "/usr/local"}},
{nil, nil},
}
for _, tt := range tests {
env := Env(tt.input)
got := env.Map()
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("Env.Map(): wrong result. Want %v. Got %v", tt.expected, got)
}
}
}
type unmarshable struct {
}
func (unmarshable) MarshalJSON() ([]byte, error) {
return nil, errors.New("cannot marshal")
}

View File

@ -20,10 +20,10 @@ import (
// APIEvents represents an event returned by the API.
type APIEvents struct {
Status string
ID string
From string
Time int64
Status string `json:"Status,omitempty" yaml:"Status,omitempty"`
ID string `json:"ID,omitempty" yaml:"ID,omitempty"`
From string `json:"From,omitempty" yaml:"From,omitempty"`
Time int64 `json:"Time,omitempty" yaml:"Time,omitempty"`
}
type eventMonitoringState struct {

View File

@ -7,6 +7,7 @@ package docker_test
import (
"archive/tar"
"bytes"
"fmt"
"io"
"log"
"time"
@ -33,7 +34,6 @@ func ExampleClient_AttachToContainer() {
log.Fatal(err)
}
log.Println(buf.String())
// Attaching to stdout and streaming.
buf.Reset()
err = client.AttachToContainer(docker.AttachToContainerOptions{
Container: "a84849",
@ -53,7 +53,6 @@ func ExampleClient_CopyFromContainer() {
log.Fatal(err)
}
cid := "a84849"
// Copy resulting file
var buf bytes.Buffer
filename := "/tmp/output.txt"
err = client.CopyFromContainer(docker.CopyFromContainerOptions{
@ -132,3 +131,38 @@ func ExampleClient_ListenEvents() {
}
}
func ExampleEnv_Map() {
e := docker.Env([]string{"A=1", "B=2", "C=3"})
envs := e.Map()
for k, v := range envs {
fmt.Printf("%s=%q\n", k, v)
}
}
func ExampleEnv_SetJSON() {
type Person struct {
Name string
Age int
}
p := Person{Name: "Gopher", Age: 4}
var e docker.Env
err := e.SetJSON("person", p)
if err != nil {
log.Fatal(err)
}
}
func ExampleEnv_GetJSON() {
type Person struct {
Name string
Age int
}
p := Person{Name: "Gopher", Age: 4}
var e docker.Env
e.Set("person", `{"name":"Gopher","age":4}`)
err := e.GetJSON("person", &p)
if err != nil {
log.Fatal(err)
}
}

View File

@ -20,28 +20,36 @@ import (
// 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"`
ID string `json:"Id" yaml:"Id"`
RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"`
ParentId string `json:"ParentId,omitempty" yaml:"ParentId,omitempty"`
}
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:"containerconfig,omitempty"`
DockerVersion string `json:"dockerversion,omitempty"`
Author string `json:"author,omitempty"`
Config *Config `json:"config,omitempty"`
Architecture string `json:"architecture,omitempty"`
Size int64
ID string `json:"Id" yaml:"Id"`
Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty"`
Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty"`
Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty"`
Container string `json:"Container,omitempty" yaml:"Container,omitempty"`
ContainerConfig Config `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty"`
DockerVersion string `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty"`
Author string `json:"Author,omitempty" yaml:"Author,omitempty"`
Config *Config `json:"Config,omitempty" yaml:"Config,omitempty"`
Architecture string `json:"Architecture,omitempty" yaml:"Architecture,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
// ImageHistory represent a layer in an image's history returned by the
// ImageHistory call.
type ImageHistory struct {
ID string `json:"Id" yaml:"Id"`
Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
type ImagePre012 struct {
@ -55,7 +63,7 @@ type ImagePre012 struct {
Author string `json:"author,omitempty"`
Config *Config `json:"config,omitempty"`
Architecture string `json:"architecture,omitempty"`
Size int64
Size int64 `json:"size,omitempty"`
}
var (
@ -73,7 +81,7 @@ var (
// ListImages returns the list of available images in the server.
//
// See http://goo.gl/dkMrwP for more details.
// See http://goo.gl/VmcR6v for more details.
func (c *Client) ListImages(all bool) ([]APIImages, error) {
path := "/images/json?all="
if all {
@ -93,9 +101,28 @@ func (c *Client) ListImages(all bool) ([]APIImages, error) {
return images, nil
}
// ImageHistory returns the history of the image by its name or ID.
//
// See http://goo.gl/2oJmNs for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
body, status, err := c.do("GET", "/images/"+name+"/history", nil)
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
if err != nil {
return nil, err
}
var history []ImageHistory
err = json.Unmarshal(body, &history)
if err != nil {
return nil, err
}
return history, nil
}
// RemoveImage removes an image by its name or ID.
//
// See http://goo.gl/7hjHHy for more details.
// See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImage(name string) error {
_, status, err := c.do("DELETE", "/images/"+name, nil)
if status == http.StatusNotFound {
@ -106,7 +133,7 @@ func (c *Client) RemoveImage(name string) error {
// InspectImage returns an image by its name or ID.
//
// See http://goo.gl/pHEbma for more details.
// See http://goo.gl/Q112NY for more details.
func (c *Client) InspectImage(name string) (*Image, error) {
body, status, err := c.do("GET", "/images/"+name+"/json", nil)
if status == http.StatusNotFound {
@ -149,7 +176,7 @@ func (c *Client) InspectImage(name string) (*Image, error) {
// PushImageOptions represents options to use in the PushImage method.
//
// See http://goo.gl/GBmyhc for more details.
// See http://goo.gl/pN8A3P for more details.
type PushImageOptions struct {
// Name of the image
Name string
@ -164,7 +191,7 @@ type PushImageOptions struct {
}
// AuthConfiguration represents authentication options to use in the PushImage
// method. It represents the authencation in the Docker index server.
// method. It represents the authentication in the Docker index server.
type AuthConfiguration struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
@ -176,7 +203,7 @@ type AuthConfiguration struct {
// An empty instance of AuthConfiguration may be used for unauthenticated
// pushes.
//
// See http://goo.gl/GBmyhc for more details.
// See http://goo.gl/pN8A3P for more details.
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error {
if opts.Name == "" {
return ErrNoSuchImage
@ -190,23 +217,24 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
return c.stream("POST", path, true, headers, nil, opts.OutputStream, nil)
return c.stream("POST", path, true, false, headers, nil, opts.OutputStream, nil)
}
// PullImageOptions present the set of options available for pulling an image
// from a registry.
//
// See http://goo.gl/PhBKnS for more details.
// See http://goo.gl/ACyYNS for more details.
type PullImageOptions struct {
Repository string `qs:"fromImage"`
Registry string
Tag string
OutputStream io.Writer `qs:"-"`
Repository string `qs:"fromImage"`
Registry string
Tag string
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
}
// PullImage pulls an image from a remote registry, logging progress to w.
//
// See http://goo.gl/PhBKnS for more details.
// See http://goo.gl/ACyYNS for more details.
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
if opts.Repository == "" {
return ErrNoSuchImage
@ -217,12 +245,41 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error
json.NewEncoder(&buf).Encode(auth)
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
return c.createImage(queryString(&opts), headers, nil, opts.OutputStream)
return c.createImage(queryString(&opts), headers, nil, opts.OutputStream, opts.RawJSONStream)
}
func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer) error {
func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool) error {
path := "/images/create?" + qs
return c.stream("POST", path, true, headers, in, w, nil)
return c.stream("POST", path, true, rawJSONStream, headers, in, w, nil)
}
// LoadImageOptions represents the options for LoadImage Docker API Call
//
// See http://goo.gl/Y8NNCq for more details.
type LoadImageOptions struct {
InputStream io.Reader
}
// LoadImage imports a tarball docker image
//
// See http://goo.gl/Y8NNCq for more details.
func (c *Client) LoadImage(opts LoadImageOptions) error {
return c.stream("POST", "/images/load", true, false, nil, opts.InputStream, nil, nil)
}
// ExportImageOptions represent the options for ExportImage Docker API call
//
// See http://goo.gl/mi6kvk for more details.
type ExportImageOptions struct {
Name string
OutputStream io.Writer
}
// ExportImage exports an image (as a tar file) into the stream
//
// See http://goo.gl/mi6kvk for more details.
func (c *Client) ExportImage(opts ExportImageOptions) error {
return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), true, false, nil, nil, opts.OutputStream, nil)
}
// ImportImageOptions present the set of informations available for importing
@ -257,24 +314,30 @@ func (c *Client) ImportImage(opts ImportImageOptions) error {
opts.InputStream = bytes.NewBuffer(b)
opts.Source = "-"
}
return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream)
return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, false)
}
// 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/
// BuildImageOptions present the set of informations available for building an
// image from a tarfile with a Dockerfile in it.
//
// For more details about the Docker building process, see
// http://goo.gl/tlPXPu.
type BuildImageOptions struct {
Name string `qs:"t"`
NoCache bool `qs:"nocache"`
SuppressOutput bool `qs:"q"`
RmTmpContainer bool `qs:"rm"`
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
Remote string `qs:"remote"`
Name string `qs:"t"`
NoCache bool `qs:"nocache"`
SuppressOutput bool `qs:"q"`
RmTmpContainer bool `qs:"rm"`
ForceRmTmpContainer bool `qs:"forcerm"`
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
Remote string `qs:"remote"`
}
// BuildImage builds an image from a tarball's url or a Dockerfile in the input
// stream.
//
// See http://goo.gl/wRsW76 for more details.
func (c *Client) BuildImage(opts BuildImageOptions) error {
if opts.OutputStream == nil {
return ErrMissingOutputStream
@ -289,17 +352,21 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
return ErrMissingRepo
}
return c.stream("POST", fmt.Sprintf("/build?%s",
queryString(&opts)), true, headers, opts.InputStream, opts.OutputStream, nil)
queryString(&opts)), true, opts.RawJSONStream, headers, opts.InputStream, opts.OutputStream, nil)
}
// TagImageOptions present the set of options to tag an image
// TagImageOptions present the set of options to tag an image.
//
// See http://goo.gl/5g6qFy for more details.
type TagImageOptions struct {
Repo string
Tag string
Force bool
}
// TagImage adds a tag to the image 'name'
// TagImage adds a tag to the image identified by the given name.
//
// See http://goo.gl/5g6qFy for more details.
func (c *Client) TagImage(name string, opts TagImageOptions) error {
if name == "" {
return ErrNoSuchImage

View File

@ -21,9 +21,9 @@ func newTestClient(rt *FakeRoundTripper) Client {
endpoint := "http://localhost:4243"
u, _ := parseEndpoint("http://localhost:4243")
client := Client{
HTTPClient: &http.Client{Transport: rt},
endpoint: endpoint,
endpointURL: u,
client: &http.Client{Transport: rt},
SkipServerVersionCheck: true,
}
return client
@ -122,6 +122,48 @@ func TestListImagesParameters(t *testing.T) {
}
}
func TestImageHistory(t *testing.T) {
body := `[
{
"Id": "25daec02219d2d852f7526137213a9b199926b4b24e732eab5b8bc6c49bd470e",
"Tags": [
"debian:7.6",
"debian:latest",
"debian:7",
"debian:wheezy"
],
"Created": 1409856216,
"CreatedBy": "/bin/sh -c #(nop) CMD [/bin/bash]"
},
{
"Id": "41026a5347fb5be6ed16115bf22df8569697139f246186de9ae8d4f67c335dce",
"Created": 1409856213,
"CreatedBy": "/bin/sh -c #(nop) ADD file:1ee9e97209d00e3416a4543b23574cc7259684741a46bbcbc755909b8a053a38 in /",
"Size": 85178663
},
{
"Id": "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158",
"Tags": [
"scratch:latest"
],
"Created": 1371157430
}
]`
var expected []ImageHistory
err := json.Unmarshal([]byte(body), &expected)
if err != nil {
t.Fatal(err)
}
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
history, err := client.ImageHistory("debian:latest")
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(history, expected) {
t.Errorf("ImageHistory: Wrong return value. Want %#v. Got %#v.", expected, history)
}
}
func TestRemoveImage(t *testing.T) {
name := "test"
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
@ -309,6 +351,33 @@ func TestPullImage(t *testing.T) {
}
}
func TestPullImageWithRawJSON(t *testing.T) {
body := `
{"status":"Pulling..."}
{"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}}
`
fakeRT := &FakeRoundTripper{
message: body,
status: http.StatusOK,
header: map[string]string{
"Content-Type": "application/json",
},
}
client := newTestClient(fakeRT)
var buf bytes.Buffer
err := client.PullImage(PullImageOptions{
Repository: "base",
OutputStream: &buf,
RawJSONStream: true,
}, AuthConfiguration{})
if err != nil {
t.Fatal(err)
}
if buf.String() != body {
t.Errorf("PullImage: Wrong raw output. Want %q. Got %q", body, buf.String())
}
}
func TestPullImageWithoutOutputStream(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
client := newTestClient(fakeRT)
@ -514,19 +583,20 @@ func TestBuildImageParameters(t *testing.T) {
client := newTestClient(fakeRT)
var buf bytes.Buffer
opts := BuildImageOptions{
Name: "testImage",
NoCache: true,
SuppressOutput: true,
RmTmpContainer: true,
InputStream: &buf,
OutputStream: &buf,
Name: "testImage",
NoCache: true,
SuppressOutput: true,
RmTmpContainer: true,
ForceRmTmpContainer: 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"}}
expected := map[string][]string{"t": {opts.Name}, "nocache": {"1"}, "q": {"1"}, "rm": {"1"}, "forcerm": {"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)
@ -551,7 +621,7 @@ func TestBuildImageParametersForRemoteBuild(t *testing.T) {
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)
t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got)
}
}
@ -580,6 +650,44 @@ func TestBuildImageMissingOutputStream(t *testing.T) {
}
}
func TestBuildImageWithRawJSON(t *testing.T) {
body := `
{"stream":"Step 0 : FROM ubuntu:latest\n"}
{"stream":" ---\u003e 4300eb9d3c8d\n"}
{"stream":"Step 1 : MAINTAINER docker <eng@docker.com>\n"}
{"stream":" ---\u003e Using cache\n"}
{"stream":" ---\u003e 3a3ed758c370\n"}
{"stream":"Step 2 : CMD /usr/bin/top\n"}
{"stream":" ---\u003e Running in 36b1479cc2e4\n"}
{"stream":" ---\u003e 4b6188aebe39\n"}
{"stream":"Removing intermediate container 36b1479cc2e4\n"}
{"stream":"Successfully built 4b6188aebe39\n"}
`
fakeRT := &FakeRoundTripper{
message: body,
status: http.StatusOK,
header: map[string]string{
"Content-Type": "application/json",
},
}
client := newTestClient(fakeRT)
var buf bytes.Buffer
opts := BuildImageOptions{
Name: "testImage",
RmTmpContainer: true,
InputStream: &buf,
OutputStream: &buf,
RawJSONStream: true,
}
err := client.BuildImage(opts)
if err != nil {
t.Fatal(err)
}
if buf.String() != body {
t.Errorf("BuildImage: Wrong raw output. Want %q. Got %q.", body, buf.String())
}
}
func TestBuildImageRemoteWithoutName(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
@ -640,3 +748,45 @@ func TestIsUrl(t *testing.T) {
t.Errorf("isURL: wrong match. Expected %#v to not be a url. Got %#v", url, result)
}
}
func TestLoadImage(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
tar, err := os.Open("testing/data/container.tar")
if err != nil {
t.Fatal(err)
} else {
defer tar.Close()
}
opts := LoadImageOptions{InputStream: tar}
err = client.LoadImage(opts)
if nil != err {
t.Error(err)
}
req := fakeRT.requests[0]
if req.Method != "POST" {
t.Errorf("LoadImage: wrong method. Expected %q. Got %q.", "POST", req.Method)
}
if req.URL.Path != "/images/load" {
t.Errorf("LoadImage: wrong URL. Expected %q. Got %q.", "/images/load", req.URL.Path)
}
}
func TestExportImage(t *testing.T) {
var buf bytes.Buffer
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
opts := ExportImageOptions{Name: "testimage", OutputStream: &buf}
err := client.ExportImage(opts)
if nil != err {
t.Error(err)
}
req := fakeRT.requests[0]
if req.Method != "GET" {
t.Errorf("ExportImage: wrong method. Expected %q. Got %q.", "GET", req.Method)
}
expectedPath := "/images/testimage/get"
if req.URL.Path != expectedPath {
t.Errorf("ExportIMage: wrong path. Expected %q. Got %q.", expectedPath, req.URL.Path)
}
}

View File

@ -6,42 +6,54 @@ package docker
import (
"bytes"
"io"
"github.com/fsouza/go-dockerclient/engine"
"strings"
)
// Version returns version information about the docker server.
//
// See http://goo.gl/IqKNRE for more details.
func (c *Client) Version() (*engine.Env, error) {
// See http://goo.gl/BOZrF5 for more details.
func (c *Client) Version() (*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 {
var env Env
if err := env.Decode(bytes.NewReader(body)); err != nil {
return nil, err
}
if _, err := io.Copy(out, bytes.NewReader(body)); err != nil {
return nil, err
}
return remoteVersion, nil
return &env, nil
}
// Info returns system-wide information, like the number of running containers.
// Info returns system-wide information about the Docker server.
//
// See http://goo.gl/LOmySw for more details.
func (c *Client) Info() (*engine.Env, error) {
// See http://goo.gl/wmqZsW for more details.
func (c *Client) Info() (*Env, error) {
body, _, err := c.do("GET", "/info", nil)
if err != nil {
return nil, err
}
var info engine.Env
var info Env
err = info.Decode(bytes.NewReader(body))
if err != nil {
return nil, err
}
return &info, nil
}
// ParseRepositoryTag gets the name of the repository and returns it splitted
// in two parts: the repository and the tag.
//
// Some examples:
//
// localhost.localdomain:5000/samalba/hipache:latest -> localhost.localdomain:5000/samalba/hipache, latest
// localhost.localdomain:5000/samalba/hipache -> localhost.localdomain:5000/samalba/hipache, ""
func ParseRepositoryTag(repoTag string) (repository string, tag string) {
n := strings.LastIndex(repoTag, ":")
if n < 0 {
return repoTag, ""
}
if tag := repoTag[n+1:]; !strings.Contains(tag, "/") {
return repoTag[:n], tag
}
return repoTag, ""
}

View File

@ -10,8 +10,6 @@ import (
"reflect"
"sort"
"testing"
"github.com/fsouza/go-dockerclient/engine"
)
type DockerVersion struct {
@ -81,7 +79,7 @@ func TestInfo(t *testing.T) {
}`
fakeRT := FakeRoundTripper{message: body, status: http.StatusOK}
client := newTestClient(&fakeRT)
expected := engine.Env{}
expected := Env{}
expected.SetInt("Containers", 11)
expected.SetInt("Images", 16)
expected.SetBool("Debug", false)
@ -121,3 +119,41 @@ func TestInfoError(t *testing.T) {
t.Error("Info(): unexpected <nil> error")
}
}
func TestParseRepositoryTag(t *testing.T) {
var tests = []struct {
input string
expectedRepo string
expectedTag string
}{
{
"localhost.localdomain:5000/samalba/hipache:latest",
"localhost.localdomain:5000/samalba/hipache",
"latest",
},
{
"localhost.localdomain:5000/samalba/hipache",
"localhost.localdomain:5000/samalba/hipache",
"",
},
{
"tsuru/python",
"tsuru/python",
"",
},
{
"tsuru/python:2.7",
"tsuru/python",
"2.7",
},
}
for _, tt := range tests {
repo, tag := ParseRepositoryTag(tt.input)
if repo != tt.expectedRepo {
t.Errorf("ParseRepositoryTag(%q): wrong repository. Want %q. Got %q", tt.input, tt.expectedRepo, repo)
}
if tag != tt.expectedTag {
t.Errorf("ParseRepositoryTag(%q): wrong tag. Want %q. Got %q", tt.input, tt.expectedTag, tag)
}
}
}

View File

@ -0,0 +1,91 @@
// 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 docker
import (
"encoding/binary"
"errors"
"io"
)
const (
stdWriterPrefixLen = 8
stdWriterFdIndex = 0
stdWriterSizeIndex = 4
)
var errInvalidStdHeader = errors.New("Unrecognized input header")
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 {
for nr < stdWriterPrefixLen {
var nr2 int
nr2, er = src.Read(buf[nr:])
if er == io.EOF {
if nr < stdWriterPrefixLen && nr2 < stdWriterPrefixLen {
return written, nil
}
nr += nr2
break
} else if er != nil {
return 0, er
}
nr += nr2
}
switch buf[stdWriterFdIndex] {
case 0:
fallthrough
case 1:
out = dstout
case 2:
out = dsterr
default:
return 0, errInvalidStdHeader
}
frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
if frameSize+stdWriterPrefixLen > bufLen {
buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-len(buf)+1)...)
bufLen = len(buf)
}
for nr < frameSize+stdWriterPrefixLen {
var nr2 int
nr2, er = src.Read(buf[nr:])
if er == io.EOF {
if nr == 0 {
return written, nil
}
nr += nr2
break
} else if er != nil {
return 0, er
}
nr += nr2
}
bound := frameSize + stdWriterPrefixLen
if bound > nr {
bound = nr
}
nw, ew = out.Write(buf[stdWriterPrefixLen:bound])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
return 0, ew
}
if nw != frameSize {
return written, io.ErrShortWrite
}
copy(buf, buf[frameSize+stdWriterPrefixLen:])
nr -= frameSize + stdWriterPrefixLen
}
}

View File

@ -2,10 +2,11 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the DOCKER-LICENSE file.
package utils
package docker
import (
"bytes"
"encoding/binary"
"errors"
"io"
"strings"
@ -28,7 +29,7 @@ func TestStdCopy(t *testing.T) {
input.Write([]byte("just kidding"))
input.Write([]byte{0, 0, 0, 0, 0, 0, 0, 6})
input.Write([]byte("\nyeah!"))
n, err := StdCopy(&stdout, &stderr, &input)
n, err := stdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
@ -36,19 +37,19 @@ func TestStdCopy(t *testing.T) {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "something happened!" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened!", got)
t.Errorf("stdCopy: wrong stderr. Want %q. Got %q.", "something happened!", got)
}
if got := stdout.String(); got != "just kidding\nyeah!" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "just kidding\nyeah!", got)
t.Errorf("stdCopy: wrong stdout. Want %q. Got %q.", "just kidding\nyeah!", got)
}
}
func TestStdCopyStress(t *testing.T) {
var input, stdout, stderr bytes.Buffer
value := strings.Repeat("something ", 4096)
writer := NewStdWriter(&input, Stdout)
writer := newStdWriter(&input, Stdout)
writer.Write([]byte(value))
n, err := StdCopy(&stdout, &stderr, &input)
n, err := stdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
@ -56,22 +57,22 @@ func TestStdCopyStress(t *testing.T) {
t.Errorf("Wrong number of bytes. Want 40960. Got %d.", n)
}
if got := stderr.String(); got != "" {
t.Errorf("StdCopy: wrong stderr. Want empty string. Got %q", got)
t.Errorf("stdCopy: wrong stderr. Want empty string. Got %q", got)
}
if got := stdout.String(); got != value {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q", value, got)
t.Errorf("stdCopy: wrong stdout. Want %q. Got %q", value, got)
}
}
func TestStdCopyInvalidStdHeader(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{3, 0, 0, 0, 0, 0, 0, 19})
n, err := StdCopy(&stdout, &stderr, &input)
n, err := stdCopy(&stdout, &stderr, &input)
if n != 0 {
t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d", n)
t.Errorf("stdCopy: wrong number of bytes. Want 0. Got %d", n)
}
if err != ErrInvalidStdHeader {
t.Errorf("StdCopy: wrong error. Want ErrInvalidStdHeader. Got %#v", err)
if err != errInvalidStdHeader {
t.Errorf("stdCopy: wrong error. Want ErrInvalidStdHeader. Got %#v", err)
}
}
@ -79,7 +80,7 @@ func TestStdCopyBigFrame(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 18})
input.Write([]byte("something happened!"))
n, err := StdCopy(&stdout, &stderr, &input)
n, err := stdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
@ -87,10 +88,10 @@ func TestStdCopyBigFrame(t *testing.T) {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "something happened" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened", got)
t.Errorf("stdCopy: wrong stderr. Want %q. Got %q.", "something happened", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
t.Errorf("stdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
@ -98,41 +99,41 @@ func TestStdCopySmallFrame(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 20})
input.Write([]byte("something happened!"))
n, err := StdCopy(&stdout, &stderr, &input)
n, err := stdCopy(&stdout, &stderr, &input)
if err != io.ErrShortWrite {
t.Errorf("StdCopy: wrong error. Want ShortWrite. Got %#v", err)
t.Errorf("stdCopy: wrong error. Want ShortWrite. Got %#v", err)
}
if expected := int64(19); n != expected {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "something happened!" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened", got)
t.Errorf("stdCopy: wrong stderr. Want %q. Got %q.", "something happened", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
t.Errorf("stdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
func TestStdCopyEmpty(t *testing.T) {
var input, stdout, stderr bytes.Buffer
n, err := StdCopy(&stdout, &stderr, &input)
n, err := stdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
if n != 0 {
t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n)
t.Errorf("stdCopy: wrong number of bytes. Want 0. Got %d.", n)
}
}
func TestStdCopyCorruptedHeader(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0})
n, err := StdCopy(&stdout, &stderr, &input)
n, err := stdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
if n != 0 {
t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n)
t.Errorf("stdCopy: wrong number of bytes. Want 0. Got %d.", n)
}
}
@ -140,7 +141,7 @@ func TestStdCopyTruncateWriter(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
n, err := StdCopy(&stdout, iotest.TruncateWriter(&stderr, 7), &input)
n, err := stdCopy(&stdout, iotest.TruncateWriter(&stderr, 7), &input)
if err != nil {
t.Fatal(err)
}
@ -148,28 +149,28 @@ func TestStdCopyTruncateWriter(t *testing.T) {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "somethi" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "somethi", got)
t.Errorf("stdCopy: wrong stderr. Want %q. Got %q.", "somethi", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
t.Errorf("stdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
func TestStdCopyHeaderOnly(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
n, err := StdCopy(&stdout, iotest.TruncateWriter(&stderr, 7), &input)
n, err := stdCopy(&stdout, iotest.TruncateWriter(&stderr, 7), &input)
if err != io.ErrShortWrite {
t.Errorf("StdCopy: wrong error. Want ShortWrite. Got %#v", err)
t.Errorf("stdCopy: wrong error. Want ShortWrite. Got %#v", err)
}
if n != 0 {
t.Errorf("Wrong number of bytes. Want 0. Got %d.", n)
}
if got := stderr.String(); got != "" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "", got)
t.Errorf("stdCopy: wrong stderr. Want %q. Got %q.", "", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
t.Errorf("stdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
@ -177,7 +178,7 @@ func TestStdCopyDataErrReader(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
n, err := StdCopy(&stdout, &stderr, iotest.DataErrReader(&input))
n, err := stdCopy(&stdout, &stderr, iotest.DataErrReader(&input))
if err != nil {
t.Fatal(err)
}
@ -185,10 +186,10 @@ func TestStdCopyDataErrReader(t *testing.T) {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "something happened!" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened!", got)
t.Errorf("stdCopy: wrong stderr. Want %q. Got %q.", "something happened!", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
t.Errorf("stdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
@ -196,9 +197,9 @@ func TestStdCopyTimeoutReader(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
_, err := StdCopy(&stdout, &stderr, iotest.TimeoutReader(&input))
_, err := stdCopy(&stdout, &stderr, iotest.TimeoutReader(&input))
if err != iotest.ErrTimeout {
t.Errorf("StdCopy: wrong error. Want ErrTimeout. Got %#v.", err)
t.Errorf("stdCopy: wrong error. Want ErrTimeout. Got %#v.", err)
}
}
@ -207,11 +208,48 @@ func TestStdCopyWriteError(t *testing.T) {
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
var stdout, stderr errorWriter
n, err := StdCopy(stdout, stderr, &input)
n, err := stdCopy(stdout, stderr, &input)
if err.Error() != "something went wrong" {
t.Errorf("StdCopy: wrong error. Want %q. Got %q", "something went wrong", err)
t.Errorf("stdCopy: wrong error. Want %q. Got %q", "something went wrong", err)
}
if n != 0 {
t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n)
t.Errorf("stdCopy: wrong number of bytes. Want 0. Got %d.", n)
}
}
type StdType [8]byte
var (
Stdin = StdType{0: 0}
Stdout = StdType{0: 1}
Stderr = 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 - 8, err
}
func newStdWriter(w io.Writer, t StdType) *StdWriter {
if len(t) != 8 {
return nil
}
return &StdWriter{
Writer: w,
prefix: t,
sizeBuf: make([]byte, 4),
}
}

View File

@ -12,9 +12,6 @@ import (
"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"
@ -23,6 +20,9 @@ import (
"strings"
"sync"
"time"
"github.com/fsouza/go-dockerclient"
"github.com/gorilla/mux"
)
// DockerServer represents a programmable, concurrent (not much), HTTP server
@ -31,7 +31,7 @@ import (
// 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.
// For more details on the remote API, check http://goo.gl/G3plxW.
type DockerServer struct {
containers []*docker.Container
cMut sync.RWMutex
@ -103,6 +103,8 @@ func (s *DockerServer) buildMuxer() {
s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage))
s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents)
s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker))
s.mux.Path("/images/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage))
s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage))
}
// PrepareFailure adds a new expected failure based on a URL regexp it receives
@ -224,6 +226,11 @@ func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) {
ID: image.ID,
Created: image.Created.Unix(),
}
for tag, id := range s.imgIDs {
if id == image.ID {
result[i].RepoTags = append(result[i].RepoTags, tag)
}
}
}
s.cMut.RUnlock()
w.Header().Set("Content-Type", "application/json")
@ -408,7 +415,7 @@ func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
outStream := utils.NewStdWriter(w, utils.Stdout)
outStream := newStdWriter(w, 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)
@ -649,3 +656,13 @@ func (s *DockerServer) generateEvent() *docker.APIEvents {
Time: time.Now().Unix(),
}
}
func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/tar")
}

View File

@ -7,7 +7,6 @@ package testing
import (
"encoding/json"
"fmt"
"github.com/fsouza/go-dockerclient"
"math/rand"
"net"
"net/http"
@ -17,6 +16,8 @@ import (
"strings"
"testing"
"time"
"github.com/fsouza/go-dockerclient"
)
func TestNewServer(t *testing.T) {
@ -780,7 +781,7 @@ func addImages(server *DockerServer, n int, repo bool) {
func TestListImages(t *testing.T) {
server := DockerServer{}
addImages(&server, 2, false)
addImages(&server, 2, true)
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/images/json?all=1", nil)
@ -791,8 +792,9 @@ func TestListImages(t *testing.T) {
expected := make([]docker.APIImages, 2)
for i, image := range server.images {
expected[i] = docker.APIImages{
ID: image.ID,
Created: image.Created.Unix(),
ID: image.ID,
Created: image.Created.Unix(),
RepoTags: []string{"docker/python-" + image.ID},
}
}
var got []docker.APIImages

View File

@ -0,0 +1,43 @@
// 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/binary"
"errors"
"io"
)
type stdType [8]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 - 8, err
}
func newStdWriter(w io.Writer, t stdType) *stdWriter {
if len(t) != 8 {
return nil
}
return &stdWriter{Writer: w, prefix: t, sizeBuf: make([]byte, 4)}
}

View File

@ -1,20 +0,0 @@
// 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)
}

View File

@ -1,168 +0,0 @@
// 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 {
if nr < StdWriterPrefixLen && nr2 < StdWriterPrefixLen {
return written, nil
}
nr += nr2
break
} else 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+StdWriterPrefixLen-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 {
if nr == 0 {
return written, nil
}
nr += nr2
break
} else if er != nil {
Debugf("Error reading frame: %s", er)
return 0, er
}
nr += nr2
}
// Write the retrieved frame (without header)
bound := frameSize + StdWriterPrefixLen
if bound > nr {
bound = nr
}
nw, ew = out.Write(buf[StdWriterPrefixLen:bound])
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 written, io.ErrShortWrite
}
// Move the rest of the buffer to the beginning
copy(buf, buf[frameSize+StdWriterPrefixLen:])
// Move the index
nr -= frameSize + StdWriterPrefixLen
}
}

View File

@ -1,17 +0,0 @@
// 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")
}

View File

@ -1,20 +0,0 @@
// 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
}

File diff suppressed because it is too large Load Diff

View File

@ -1,535 +0,0 @@
// 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
}

View File

@ -1,17 +0,0 @@
// 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")
}