diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4b246783..0b391d0a 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -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", diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml index cfcd173a..ad1dff7a 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml @@ -1,8 +1,8 @@ language: go go: - 1.1.2 - - 1.2 - - 1.3 + - 1.2.2 + - 1.3.1 - tip env: - GOARCH=amd64 diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS index d42a14a8..bb798814 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS @@ -1,13 +1,20 @@ # This is the official list of go-dockerclient authors for copyright purposes. +Aldrin Leal +Andreas Jaekle Andrews Medina Andy Goldstein Ben McCann +Carlos Diaz-Padron Cezar Sa Espinola Cheah Chu Yeow cheneydeng +CMGS +Daniel, Dao Quang Minh +David Huie Ed Eric Anderson +Fabio Rehm Flavia Missi Francisco Souza Jari Kolehmainen @@ -15,12 +22,16 @@ Jason Wilder Jean-Baptiste Dalido Jeff Mitchell Jeffrey Hulten +Johan Euphrosine +Karan Misra +Kim, Hirokuni Lucas Clemente Omeid Matten Paul Morie Peter Jihoon Kim Philippe Lafoucrière Rafe Colton +Robert Williamson Salvador Gironès Simon Eskildsen Simon Menke diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown index 7b4e959e..66cedca6 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown @@ -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 diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/change.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/change.go index 79260731..cafffadd 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/change.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/change.go @@ -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 diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go index 681b7bf7..1da35d7e 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go @@ -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 { diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go index 53edd6c2..9def1716 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go @@ -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() { diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go index 25e1fad5..16ce048e 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go index fbe9d1f7..f3e19542 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go @@ -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) + } +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/engine.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/engine.go deleted file mode 100644 index 3e4cd577..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/engine.go +++ /dev/null @@ -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...) -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/engine_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/engine_test.go deleted file mode 100644 index dba7b074..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/engine_test.go +++ /dev/null @@ -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) - } -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/env.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/env.go deleted file mode 100644 index 00af1862..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/env.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/env_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/env_test.go deleted file mode 100644 index d77e8e25..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/env_test.go +++ /dev/null @@ -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") - } -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/hack.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/hack.go deleted file mode 100644 index 7801274f..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/hack.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/helpers_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/helpers_test.go deleted file mode 100644 index 0e2c1548..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/helpers_test.go +++ /dev/null @@ -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...) -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/http.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/http.go deleted file mode 100644 index fa586c42..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/http.go +++ /dev/null @@ -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() -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/job.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/job.go deleted file mode 100644 index 53c418ea..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/job.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/job_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/job_test.go deleted file mode 100644 index c58ee4b4..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/job_test.go +++ /dev/null @@ -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) - } -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/streams.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/streams.go deleted file mode 100644 index 2dcfe23d..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/streams.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/streams_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/streams_test.go deleted file mode 100644 index 177fb21e..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/engine/streams_test.go +++ /dev/null @@ -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) - } -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/env.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/env.go new file mode 100644 index 00000000..c54b0b0e --- /dev/null +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/env.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/env_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/env_test.go new file mode 100644 index 00000000..6d03d7b3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/env_test.go @@ -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 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 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") +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go index 1808e416..262d4ee2 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go @@ -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 { diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/example_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/example_test.go index ba44dd8c..8c2c719e 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/example_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/example_test.go @@ -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) + } +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go index 4280d135..0c122e8c 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go @@ -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 diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go index a2db84db..53ea9b18 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go @@ -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 \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) + } +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go index 1b9267e2..2678ab5c 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go @@ -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, "" +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc_test.go index 8cf283e5..ceaf076e 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc_test.go @@ -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 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) + } + } +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/stdcopy.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/stdcopy.go new file mode 100644 index 00000000..3782f3d0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/stdcopy.go @@ -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 + } +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/stdcopy_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/stdcopy_test.go similarity index 61% rename from Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/stdcopy_test.go rename to Godeps/_workspace/src/github.com/fsouza/go-dockerclient/stdcopy_test.go index 81820ebb..75b8922f 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/stdcopy_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/stdcopy_test.go @@ -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), } } diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go index c67df2c3..54b2d5a6 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go @@ -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") + +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go index bfc84e52..52030043 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go @@ -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 diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/writer.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/writer.go new file mode 100644 index 00000000..42752b03 --- /dev/null +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/writer.go @@ -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)} +} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/random.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/random.go deleted file mode 100644 index e2e68365..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/random.go +++ /dev/null @@ -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) -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/stdcopy.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/stdcopy.go deleted file mode 100644 index dec604ce..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/stdcopy.go +++ /dev/null @@ -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 - } -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/uname_darwin.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/uname_darwin.go deleted file mode 100644 index 8e599699..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/uname_darwin.go +++ /dev/null @@ -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") -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/uname_linux.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/uname_linux.go deleted file mode 100644 index 9ee7e3d9..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/uname_linux.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils.go deleted file mode 100644 index 84b2815a..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils.go +++ /dev/null @@ -1,1114 +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" - "crypto/sha1" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "index/suffixarray" - "io" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "strconv" - "strings" - "sync" - "time" -) - -var ( - IAMSTATIC bool // whether or not Docker itself was compiled statically via ./hack/make.sh binary - INITSHA1 string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary - INITPATH string // custom location to search for a valid dockerinit binary (available for packagers as a last resort escape hatch) -) - -// A common interface to access the Fatal method of -// both testing.B and testing.T. -type Fataler interface { - Fatal(args ...interface{}) -} - -// Go is a basic promise implementation: it wraps calls a function in a goroutine, -// and returns a channel which will later return the function's return value. -func Go(f func() error) chan error { - ch := make(chan error) - go func() { - ch <- f() - }() - return ch -} - -// Request a given URL and return an io.Reader -func Download(url string) (*http.Response, error) { - var resp *http.Response - var err error - if resp, err = http.Get(url); err != nil { - return nil, err - } - if resp.StatusCode >= 400 { - return nil, errors.New("Got HTTP status code >= 400: " + resp.Status) - } - return resp, nil -} - -func logf(level string, format string, a ...interface{}) { - // Retrieve the stack infos - _, file, line, ok := runtime.Caller(2) - if !ok { - file = "" - line = -1 - } else { - file = file[strings.LastIndex(file, "/")+1:] - } - - fmt.Fprintf(os.Stderr, fmt.Sprintf("[%s] %s:%d %s\n", level, file, line, format), a...) -} - -// Debug function, if the debug flag is set, then display. Do nothing otherwise -// If Docker is in damon mode, also send the debug info on the socket -func Debugf(format string, a ...interface{}) { - if os.Getenv("DEBUG") != "" { - logf("debug", format, a...) - } -} - -func Errorf(format string, a ...interface{}) { - logf("error", format, a...) -} - -// HumanDuration returns a human-readable approximation of a duration -// (eg. "About a minute", "4 hours ago", etc.) -func HumanDuration(d time.Duration) string { - if seconds := int(d.Seconds()); seconds < 1 { - return "Less than a second" - } else if seconds < 60 { - return fmt.Sprintf("%d seconds", seconds) - } else if minutes := int(d.Minutes()); minutes == 1 { - return "About a minute" - } else if minutes < 60 { - return fmt.Sprintf("%d minutes", minutes) - } else if hours := int(d.Hours()); hours == 1 { - return "About an hour" - } else if hours < 48 { - return fmt.Sprintf("%d hours", hours) - } else if hours < 24*7*2 { - return fmt.Sprintf("%d days", hours/24) - } else if hours < 24*30*3 { - return fmt.Sprintf("%d weeks", hours/24/7) - } else if hours < 24*365*2 { - return fmt.Sprintf("%d months", hours/24/30) - } - return fmt.Sprintf("%f years", d.Hours()/24/365) -} - -// HumanSize returns a human-readable approximation of a size -// using SI standard (eg. "44kB", "17MB") -func HumanSize(size int64) string { - i := 0 - var sizef float64 - sizef = float64(size) - units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} - for sizef >= 1000.0 { - sizef = sizef / 1000.0 - i++ - } - return fmt.Sprintf("%.4g %s", sizef, units[i]) -} - -// Parses a human-readable string representing an amount of RAM -// in bytes, kibibytes, mebibytes or gibibytes, and returns the -// number of bytes, or -1 if the string is unparseable. -// Units are case-insensitive, and the 'b' suffix is optional. -func RAMInBytes(size string) (bytes int64, err error) { - re, error := regexp.Compile("^(\\d+)([kKmMgG])?[bB]?$") - if error != nil { - return -1, error - } - - matches := re.FindStringSubmatch(size) - - if len(matches) != 3 { - return -1, fmt.Errorf("Invalid size: '%s'", size) - } - - memLimit, error := strconv.ParseInt(matches[1], 10, 0) - if error != nil { - return -1, error - } - - unit := strings.ToLower(matches[2]) - - if unit == "k" { - memLimit *= 1024 - } else if unit == "m" { - memLimit *= 1024 * 1024 - } else if unit == "g" { - memLimit *= 1024 * 1024 * 1024 - } - - return memLimit, nil -} - -func Trunc(s string, maxlen int) string { - if len(s) <= maxlen { - return s - } - return s[:maxlen] -} - -// Figure out the absolute path of our own binary (if it's still around). -func SelfPath() string { - path, err := exec.LookPath(os.Args[0]) - if err != nil { - if os.IsNotExist(err) { - return "" - } - if execErr, ok := err.(*exec.Error); ok && os.IsNotExist(execErr.Err) { - return "" - } - panic(err) - } - path, err = filepath.Abs(path) - if err != nil { - if os.IsNotExist(err) { - return "" - } - panic(err) - } - return path -} - -func dockerInitSha1(target string) string { - f, err := os.Open(target) - if err != nil { - return "" - } - defer f.Close() - h := sha1.New() - _, err = io.Copy(h, f) - if err != nil { - return "" - } - return hex.EncodeToString(h.Sum(nil)) -} - -func isValidDockerInitPath(target string, selfPath string) bool { // target and selfPath should be absolute (InitPath and SelfPath already do this) - if target == "" { - return false - } - if IAMSTATIC { - if selfPath == "" { - return false - } - if target == selfPath { - return true - } - targetFileInfo, err := os.Lstat(target) - if err != nil { - return false - } - selfPathFileInfo, err := os.Lstat(selfPath) - if err != nil { - return false - } - return os.SameFile(targetFileInfo, selfPathFileInfo) - } - return INITSHA1 != "" && dockerInitSha1(target) == INITSHA1 -} - -// Figure out the path of our dockerinit (which may be SelfPath()) -func DockerInitPath(localCopy string) string { - selfPath := SelfPath() - if isValidDockerInitPath(selfPath, selfPath) { - // if we're valid, don't bother checking anything else - return selfPath - } - var possibleInits = []string{ - localCopy, - INITPATH, - filepath.Join(filepath.Dir(selfPath), "dockerinit"), - - // FHS 3.0 Draft: "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec." - // http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec - "/usr/libexec/docker/dockerinit", - "/usr/local/libexec/docker/dockerinit", - - // FHS 2.3: "/usr/lib includes object files, libraries, and internal binaries that are not intended to be executed directly by users or shell scripts." - // http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA - "/usr/lib/docker/dockerinit", - "/usr/local/lib/docker/dockerinit", - } - for _, dockerInit := range possibleInits { - if dockerInit == "" { - continue - } - path, err := exec.LookPath(dockerInit) - if err == nil { - path, err = filepath.Abs(path) - if err != nil { - // LookPath already validated that this file exists and is executable (following symlinks), so how could Abs fail? - panic(err) - } - if isValidDockerInitPath(path, selfPath) { - return path - } - } - } - return "" -} - -type NopWriter struct{} - -func (*NopWriter) Write(buf []byte) (int, error) { - return len(buf), nil -} - -type nopWriteCloser struct { - io.Writer -} - -func (w *nopWriteCloser) Close() error { return nil } - -func NopWriteCloser(w io.Writer) io.WriteCloser { - return &nopWriteCloser{w} -} - -type bufReader struct { - sync.Mutex - buf *bytes.Buffer - reader io.Reader - err error - wait sync.Cond -} - -func NewBufReader(r io.Reader) *bufReader { - reader := &bufReader{ - buf: &bytes.Buffer{}, - reader: r, - } - reader.wait.L = &reader.Mutex - go reader.drain() - return reader -} - -func (r *bufReader) drain() { - buf := make([]byte, 1024) - for { - n, err := r.reader.Read(buf) - r.Lock() - if err != nil { - r.err = err - } else { - r.buf.Write(buf[0:n]) - } - r.wait.Signal() - r.Unlock() - if err != nil { - break - } - } -} - -func (r *bufReader) Read(p []byte) (n int, err error) { - r.Lock() - defer r.Unlock() - for { - n, err = r.buf.Read(p) - if n > 0 { - return n, err - } - if r.err != nil { - return 0, r.err - } - r.wait.Wait() - } -} - -func (r *bufReader) Close() error { - closer, ok := r.reader.(io.ReadCloser) - if !ok { - return nil - } - return closer.Close() -} - -type WriteBroadcaster struct { - sync.Mutex - buf *bytes.Buffer - writers map[StreamWriter]bool -} - -type StreamWriter struct { - wc io.WriteCloser - stream string -} - -func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) { - w.Lock() - sw := StreamWriter{wc: writer, stream: stream} - w.writers[sw] = true - w.Unlock() -} - -type JSONLog struct { - Log string `json:"log,omitempty"` - Stream string `json:"stream,omitempty"` - Created time.Time `json:"time"` -} - -func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { - w.Lock() - defer w.Unlock() - w.buf.Write(p) - for sw := range w.writers { - lp := p - if sw.stream != "" { - lp = nil - for { - line, err := w.buf.ReadString('\n') - if err != nil { - w.buf.Write([]byte(line)) - break - } - b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now().UTC()}) - if err != nil { - // On error, evict the writer - delete(w.writers, sw) - continue - } - lp = append(lp, b...) - lp = append(lp, '\n') - } - } - if n, err := sw.wc.Write(lp); err != nil || n != len(lp) { - // On error, evict the writer - delete(w.writers, sw) - } - } - return len(p), nil -} - -func (w *WriteBroadcaster) CloseWriters() error { - w.Lock() - defer w.Unlock() - for sw := range w.writers { - sw.wc.Close() - } - w.writers = make(map[StreamWriter]bool) - return nil -} - -func NewWriteBroadcaster() *WriteBroadcaster { - return &WriteBroadcaster{writers: make(map[StreamWriter]bool), buf: bytes.NewBuffer(nil)} -} - -func GetTotalUsedFds() int { - if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { - Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) - } else { - return len(fds) - } - return -1 -} - -// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. -// This is used to retrieve image and container IDs by more convenient shorthand prefixes. -type TruncIndex struct { - index *suffixarray.Index - ids map[string]bool - bytes []byte -} - -func NewTruncIndex() *TruncIndex { - return &TruncIndex{ - index: suffixarray.New([]byte{' '}), - ids: make(map[string]bool), - bytes: []byte{' '}, - } -} - -func (idx *TruncIndex) Add(id string) error { - if strings.Contains(id, " ") { - return fmt.Errorf("Illegal character: ' '") - } - if _, exists := idx.ids[id]; exists { - return fmt.Errorf("Id already exists: %s", id) - } - idx.ids[id] = true - idx.bytes = append(idx.bytes, []byte(id+" ")...) - idx.index = suffixarray.New(idx.bytes) - return nil -} - -func (idx *TruncIndex) Delete(id string) error { - if _, exists := idx.ids[id]; !exists { - return fmt.Errorf("No such id: %s", id) - } - before, after, err := idx.lookup(id) - if err != nil { - return err - } - delete(idx.ids, id) - idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...) - idx.index = suffixarray.New(idx.bytes) - return nil -} - -func (idx *TruncIndex) lookup(s string) (int, int, error) { - offsets := idx.index.Lookup([]byte(" "+s), -1) - //log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes()) - if offsets == nil || len(offsets) == 0 || len(offsets) > 1 { - return -1, -1, fmt.Errorf("No such id: %s", s) - } - offsetBefore := offsets[0] + 1 - offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ") - return offsetBefore, offsetAfter, nil -} - -func (idx *TruncIndex) Get(s string) (string, error) { - before, after, err := idx.lookup(s) - //log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after) - if err != nil { - return "", err - } - return string(idx.bytes[before:after]), err -} - -// TruncateID returns a shorthand version of a string identifier for convenience. -// A collision with other shorthands is very unlikely, but possible. -// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller -// will need to use a langer prefix, or the full-length Id. -func TruncateID(id string) string { - shortLen := 12 - if len(id) < shortLen { - shortLen = len(id) - } - return id[:shortLen] -} - -// Code c/c from io.Copy() modified to handle escape sequence -func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) { - buf := make([]byte, 32*1024) - for { - nr, er := src.Read(buf) - if nr > 0 { - // ---- Docker addition - // char 16 is C-p - if nr == 1 && buf[0] == 16 { - nr, er = src.Read(buf) - // char 17 is C-q - if nr == 1 && buf[0] == 17 { - if err := src.Close(); err != nil { - return 0, err - } - return 0, nil - } - } - // ---- End of docker - nw, ew := dst.Write(buf[0:nr]) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er == io.EOF { - break - } - if er != nil { - err = er - break - } - } - return written, err -} - -func HashData(src io.Reader) (string, error) { - h := sha256.New() - if _, err := io.Copy(h, src); err != nil { - return "", err - } - return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil -} - -type KernelVersionInfo struct { - Kernel int - Major int - Minor int - Flavor string -} - -func (k *KernelVersionInfo) String() string { - flavor := "" - if len(k.Flavor) > 0 { - flavor = fmt.Sprintf("-%s", k.Flavor) - } - return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, flavor) -} - -// Compare two KernelVersionInfo struct. -// Returns -1 if a < b, = if a == b, 1 it a > b -func CompareKernelVersion(a, b *KernelVersionInfo) int { - if a.Kernel < b.Kernel { - return -1 - } else if a.Kernel > b.Kernel { - return 1 - } - - if a.Major < b.Major { - return -1 - } else if a.Major > b.Major { - return 1 - } - - if a.Minor < b.Minor { - return -1 - } else if a.Minor > b.Minor { - return 1 - } - - return 0 -} - -func GetKernelVersion() (*KernelVersionInfo, error) { - var ( - err error - ) - - uts, err := uname() - if err != nil { - return nil, err - } - - release := make([]byte, len(uts.Release)) - - i := 0 - for _, c := range uts.Release { - release[i] = byte(c) - i++ - } - - // Remove the \x00 from the release for Atoi to parse correctly - release = release[:bytes.IndexByte(release, 0)] - - return ParseRelease(string(release)) -} - -func ParseRelease(release string) (*KernelVersionInfo, error) { - var ( - flavor string - kernel, major, minor int - err error - ) - - tmp := strings.SplitN(release, "-", 2) - tmp2 := strings.Split(tmp[0], ".") - - if len(tmp2) > 0 { - kernel, err = strconv.Atoi(tmp2[0]) - if err != nil { - return nil, err - } - } - - if len(tmp2) > 1 { - major, err = strconv.Atoi(tmp2[1]) - if err != nil { - return nil, err - } - } - - if len(tmp2) > 2 { - // Removes "+" because git kernels might set it - minorUnparsed := strings.Trim(tmp2[2], "+") - minor, err = strconv.Atoi(minorUnparsed) - if err != nil { - return nil, err - } - } - - if len(tmp) == 2 { - flavor = tmp[1] - } else { - flavor = "" - } - - return &KernelVersionInfo{ - Kernel: kernel, - Major: major, - Minor: minor, - Flavor: flavor, - }, nil -} - -// FIXME: this is deprecated by CopyWithTar in archive.go -func CopyDirectory(source, dest string) error { - if output, err := exec.Command("cp", "-ra", source, dest).CombinedOutput(); err != nil { - return fmt.Errorf("Error copy: %s (%s)", err, output) - } - return nil -} - -type NopFlusher struct{} - -func (f *NopFlusher) Flush() {} - -type WriteFlusher struct { - sync.Mutex - w io.Writer - flusher http.Flusher -} - -func (wf *WriteFlusher) Write(b []byte) (n int, err error) { - wf.Lock() - defer wf.Unlock() - n, err = wf.w.Write(b) - wf.flusher.Flush() - return n, err -} - -// Flush the stream immediately. -func (wf *WriteFlusher) Flush() { - wf.Lock() - defer wf.Unlock() - wf.flusher.Flush() -} - -func NewWriteFlusher(w io.Writer) *WriteFlusher { - var flusher http.Flusher - if f, ok := w.(http.Flusher); ok { - flusher = f - } else { - flusher = &NopFlusher{} - } - return &WriteFlusher{w: w, flusher: flusher} -} - -func IsURL(str string) bool { - return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://") -} - -func IsGIT(str string) bool { - return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/") -} - -// GetResolvConf opens and read the content of /etc/resolv.conf. -// It returns it as byte slice. -func GetResolvConf() ([]byte, error) { - resolv, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - Errorf("Error openning resolv.conf: %s", err) - return nil, err - } - return resolv, nil -} - -// CheckLocalDns looks into the /etc/resolv.conf, -// it returns true if there is a local nameserver or if there is no nameserver. -func CheckLocalDns(resolvConf []byte) bool { - var parsedResolvConf = StripComments(resolvConf, []byte("#")) - if !bytes.Contains(parsedResolvConf, []byte("nameserver")) { - return true - } - for _, ip := range [][]byte{ - []byte("127.0.0.1"), - []byte("127.0.1.1"), - } { - if bytes.Contains(parsedResolvConf, ip) { - return true - } - } - return false -} - -// StripComments parses input into lines and strips away comments. -func StripComments(input []byte, commentMarker []byte) []byte { - lines := bytes.Split(input, []byte("\n")) - var output []byte - for _, currentLine := range lines { - var commentIndex = bytes.Index(currentLine, commentMarker) - if commentIndex == -1 { - output = append(output, currentLine...) - } else { - output = append(output, currentLine[:commentIndex]...) - } - output = append(output, []byte("\n")...) - } - return output -} - -// GetNameserversAsCIDR returns nameservers (if any) listed in -// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") -// This function's output is intended for net.ParseCIDR -func GetNameserversAsCIDR(resolvConf []byte) []string { - var parsedResolvConf = StripComments(resolvConf, []byte("#")) - nameservers := []string{} - re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`) - for _, line := range bytes.Split(parsedResolvConf, []byte("\n")) { - var ns = re.FindSubmatch(line) - if len(ns) > 0 { - nameservers = append(nameservers, string(ns[1])+"/32") - } - } - - return nameservers -} - -// FIXME: Change this not to receive default value as parameter -func ParseHost(defaultHost string, defaultPort int, defaultUnix, addr string) (string, error) { - var ( - proto string - host string - port int - ) - addr = strings.TrimSpace(addr) - switch { - case strings.HasPrefix(addr, "unix://"): - proto = "unix" - addr = strings.TrimPrefix(addr, "unix://") - if addr == "" { - addr = defaultUnix - } - case strings.HasPrefix(addr, "tcp://"): - proto = "tcp" - addr = strings.TrimPrefix(addr, "tcp://") - case addr == "": - proto = "unix" - addr = defaultUnix - default: - if strings.Contains(addr, "://") { - return "", fmt.Errorf("Invalid bind address protocol: %s", addr) - } - proto = "tcp" - } - - if proto != "unix" && strings.Contains(addr, ":") { - hostParts := strings.Split(addr, ":") - if len(hostParts) != 2 { - return "", fmt.Errorf("Invalid bind address format: %s", addr) - } - if hostParts[0] != "" { - host = hostParts[0] - } else { - host = defaultHost - } - - if p, err := strconv.Atoi(hostParts[1]); err == nil && p != 0 { - port = p - } else { - port = defaultPort - } - - } else { - host = addr - port = defaultPort - } - if proto == "unix" { - return fmt.Sprintf("%s://%s", proto, host), nil - } - return fmt.Sprintf("%s://%s:%d", proto, host, port), nil -} - -func GetReleaseVersion() string { - resp, err := http.Get("http://get.docker.io/latest") - if err != nil { - return "" - } - defer resp.Body.Close() - if resp.ContentLength > 24 || resp.StatusCode != 200 { - return "" - } - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "" - } - return strings.TrimSpace(string(body)) -} - -// Get a repos name and returns the right reposName + tag -// The tag can be confusing because of a port in a repository name. -// Ex: localhost.localdomain:5000/samalba/hipache:latest -func ParseRepositoryTag(repos string) (string, string) { - n := strings.LastIndex(repos, ":") - if n < 0 { - return repos, "" - } - if tag := repos[n+1:]; !strings.Contains(tag, "/") { - return repos[:n], tag - } - return repos, "" -} - -type User struct { - Uid string // user id - Gid string // primary group id - Username string - Name string - HomeDir string -} - -// UserLookup check if the given username or uid is present in /etc/passwd -// and returns the user struct. -// If the username is not found, an error is returned. -func UserLookup(uid string) (*User, error) { - file, err := ioutil.ReadFile("/etc/passwd") - if err != nil { - return nil, err - } - for _, line := range strings.Split(string(file), "\n") { - data := strings.Split(line, ":") - if len(data) > 5 && (data[0] == uid || data[2] == uid) { - return &User{ - Uid: data[2], - Gid: data[3], - Username: data[0], - Name: data[4], - HomeDir: data[5], - }, nil - } - } - return nil, fmt.Errorf("User not found in /etc/passwd") -} - -type DependencyGraph struct { - nodes map[string]*DependencyNode -} - -type DependencyNode struct { - id string - deps map[*DependencyNode]bool -} - -func NewDependencyGraph() DependencyGraph { - return DependencyGraph{ - nodes: map[string]*DependencyNode{}, - } -} - -func (graph *DependencyGraph) addNode(node *DependencyNode) string { - if graph.nodes[node.id] == nil { - graph.nodes[node.id] = node - } - return node.id -} - -func (graph *DependencyGraph) NewNode(id string) string { - if graph.nodes[id] != nil { - return id - } - nd := &DependencyNode{ - id: id, - deps: map[*DependencyNode]bool{}, - } - graph.addNode(nd) - return id -} - -func (graph *DependencyGraph) AddDependency(node, to string) error { - if graph.nodes[node] == nil { - return fmt.Errorf("Node %s does not belong to this graph", node) - } - - if graph.nodes[to] == nil { - return fmt.Errorf("Node %s does not belong to this graph", to) - } - - if node == to { - return fmt.Errorf("Dependency loops are forbidden!") - } - - graph.nodes[node].addDependency(graph.nodes[to]) - return nil -} - -func (node *DependencyNode) addDependency(to *DependencyNode) bool { - node.deps[to] = true - return node.deps[to] -} - -func (node *DependencyNode) Degree() int { - return len(node.deps) -} - -// The magic happens here :: -func (graph *DependencyGraph) GenerateTraversalMap() ([][]string, error) { - Debugf("Generating traversal map. Nodes: %d", len(graph.nodes)) - result := [][]string{} - processed := map[*DependencyNode]bool{} - // As long as we haven't processed all nodes... - for len(processed) < len(graph.nodes) { - // Use a temporary buffer for processed nodes, otherwise - // nodes that depend on each other could end up in the same round. - tmpProcessed := []*DependencyNode{} - for _, node := range graph.nodes { - // If the node has more dependencies than what we have cleared, - // it won't be valid for this round. - if node.Degree() > len(processed) { - continue - } - // If it's already processed, get to the next one - if processed[node] { - continue - } - // It's not been processed yet and has 0 deps. Add it! - // (this is a shortcut for what we're doing below) - if node.Degree() == 0 { - tmpProcessed = append(tmpProcessed, node) - continue - } - // If at least one dep hasn't been processed yet, we can't - // add it. - ok := true - for dep := range node.deps { - if !processed[dep] { - ok = false - break - } - } - // All deps have already been processed. Add it! - if ok { - tmpProcessed = append(tmpProcessed, node) - } - } - Debugf("Round %d: found %d available nodes", len(result), len(tmpProcessed)) - // If no progress has been made this round, - // that means we have circular dependencies. - if len(tmpProcessed) == 0 { - return nil, fmt.Errorf("Could not find a solution to this dependency graph") - } - round := []string{} - for _, nd := range tmpProcessed { - round = append(round, nd.id) - processed[nd] = true - } - result = append(result, round) - } - return result, nil -} - -// An StatusError reports an unsuccessful exit by a command. -type StatusError struct { - Status string - StatusCode int -} - -func (e *StatusError) Error() string { - return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) -} - -func quote(word string, buf *bytes.Buffer) { - // Bail out early for "simple" strings - if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { - buf.WriteString(word) - return - } - - buf.WriteString("'") - - for i := 0; i < len(word); i++ { - b := word[i] - if b == '\'' { - // Replace literal ' with a close ', a \', and a open ' - buf.WriteString("'\\''") - } else { - buf.WriteByte(b) - } - } - - buf.WriteString("'") -} - -// Take a list of strings and escape them so they will be handled right -// when passed as arguments to an program via a shell -func ShellQuoteArguments(args []string) string { - var buf bytes.Buffer - for i, arg := range args { - if i != 0 { - buf.WriteByte(' ') - } - quote(arg, &buf) - } - return buf.String() -} - -func IsClosedError(err error) bool { - /* This comparison is ugly, but unfortunately, net.go doesn't export errClosing. - * See: - * http://golang.org/src/pkg/net/net.go - * https://code.google.com/p/go/issues/detail?id=4337 - * https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ - */ - return strings.HasSuffix(err.Error(), "use of closed network connection") -} - -func PartParser(template, data string) (map[string]string, error) { - // ip:public:private - var ( - templateParts = strings.Split(template, ":") - parts = strings.Split(data, ":") - out = make(map[string]string, len(templateParts)) - ) - if len(parts) != len(templateParts) { - return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) - } - - for i, t := range templateParts { - value := "" - if len(parts) > i { - value = parts[i] - } - out[t] = value - } - return out, nil -} - -var globalTestID string - -// GetCallerName introspects the call stack and returns the name of the -// function `depth` levels down in the stack. -func GetCallerName(depth int) string { - // Use the caller function name as a prefix. - // This helps trace temp directories back to their test. - pc, _, _, _ := runtime.Caller(depth + 1) - callerLongName := runtime.FuncForPC(pc).Name() - parts := strings.Split(callerLongName, ".") - callerShortName := parts[len(parts)-1] - return callerShortName -} - -func CopyFile(src, dst string) (int64, error) { - if src == dst { - return 0, nil - } - sf, err := os.Open(src) - if err != nil { - return 0, err - } - defer sf.Close() - if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { - return 0, err - } - df, err := os.Create(dst) - if err != nil { - return 0, err - } - defer df.Close() - return io.Copy(df, sf) -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils_test.go deleted file mode 100644 index 7c5dadee..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils_test.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils_windows.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils_windows.go deleted file mode 100644 index a6815a49..00000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/utils/utils_windows.go +++ /dev/null @@ -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") -}