Merge pull request #7247 from guenter/update-dockerclient

Upgrade go-dockerclient dependency to support CgroupParent
This commit is contained in:
David Oppenheimer 2015-04-23 16:04:39 -07:00
commit 7c7b661cf3
14 changed files with 329 additions and 123 deletions

2
Godeps/Godeps.json generated
View File

@ -164,7 +164,7 @@
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "17d39bcb22e8103ba6d1c0cb2530c6434cb870a3"
"Rev": "09334c56c63bab2cd6c4ccab924d89e2419a361f"
},
{
"ImportPath": "github.com/garyburd/redigo/internal",

View File

@ -7,7 +7,6 @@ env:
- GOARCH=amd64
- GOARCH=386
install:
- go get -d ./...
- make testdeps
script:
- go test ./...
- ./testing/bin/fmtpolice
- make test

View File

@ -26,6 +26,7 @@ Fatih Arslan <ftharsln@gmail.com>
Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc>
Guillermo Álvarez Fernández <guillermo@cientifico.net>
James Bardin <jbardin@litl.com>
Jari Kolehmainen <jari.kolehmainen@digia.com>
Jason Wilder <jwilder@litl.com>
Jawher Moussa <jawher.moussa@gmail.com>
@ -49,6 +50,7 @@ Nick Ethier <ncethier@gmail.com>
Omeid Matten <public@omeid.me>
Paul Morie <pmorie@gmail.com>
Paul Weil <pweil@redhat.com>
Peter Edge <peter.edge@gmail.com>
Peter Jihoon Kim <raingrove@gmail.com>
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
Rafe Colton <rafael.colton@gmail.com>
@ -64,6 +66,7 @@ Sridhar Ratnakumar <sridharr@activestate.com>
Summer Mousa <smousa@zenoss.com>
Tarsis Azevedo <tarsis@corp.globo.com>
Tim Schindler <tim@catalyst-zero.com>
Tobi Knaup <tobi@mesosphere.io>
Vincenzo Prignano <vincenzo.prignano@gmail.com>
Wiliam Souza <wiliamsouza83@gmail.com>
Ye Yin <eyniy@qq.com>

View File

@ -0,0 +1,35 @@
.PHONY: \
all \
deps \
updatedeps \
testdeps \
updatetestdeps \
cov \
test \
clean
all: test
deps:
go get -d -v ./...
updatedeps:
go get -d -v -u -f ./...
testdeps:
go get -d -v -t ./...
updatetestdeps:
go get -d -v -t -u -f ./...
cov: testdeps
go get -v github.com/axw/gocov/gocov
go get golang.org/x/tools/cmd/cover
gocov test | gocov report
test: testdeps
go test ./...
./testing/bin/fmtpolice
clean:
go clean ./...

View File

@ -66,5 +66,4 @@ func main() {
You can run the tests with:
go get -d ./...
go test ./...
make test

View File

@ -252,7 +252,7 @@ func (c *Client) checkAPIVersion() error {
// See http://goo.gl/stJENm for more details.
func (c *Client) Ping() error {
path := "/_ping"
body, status, err := c.do("GET", path, nil, false)
body, status, err := c.do("GET", path, doOptions{})
if err != nil {
return err
}
@ -263,7 +263,7 @@ func (c *Client) Ping() error {
}
func (c *Client) getServerAPIVersionString() (version string, err error) {
body, status, err := c.do("GET", "/version", nil, false)
body, status, err := c.do("GET", "/version", doOptions{})
if err != nil {
return "", err
}
@ -279,10 +279,15 @@ func (c *Client) getServerAPIVersionString() (version string, err error) {
return version, nil
}
func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]byte, int, error) {
type doOptions struct {
data interface{}
forceJSON bool
}
func (c *Client) do(method, path string, doOptions doOptions) ([]byte, int, error) {
var params io.Reader
if data != nil || forceJSON {
buf, err := json.Marshal(data)
if doOptions.data != nil || doOptions.forceJSON {
buf, err := json.Marshal(doOptions.data)
if err != nil {
return nil, -1, err
}
@ -299,7 +304,7 @@ func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]by
return nil, -1, err
}
req.Header.Set("User-Agent", userAgent)
if data != nil {
if doOptions.data != nil {
req.Header.Set("Content-Type", "application/json")
} else if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
@ -339,9 +344,18 @@ func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]by
return body, resp.StatusCode, nil
}
func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, headers map[string]string, in io.Reader, stdout, stderr io.Writer) error {
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader(nil)
type streamOptions struct {
setRawTerminal bool
rawJSONStream bool
headers map[string]string
in io.Reader
stdout io.Writer
stderr io.Writer
}
func (c *Client) stream(method, path string, streamOptions streamOptions) error {
if (method == "POST" || method == "PUT") && streamOptions.in == nil {
streamOptions.in = bytes.NewReader(nil)
}
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
err := c.checkAPIVersion()
@ -349,7 +363,7 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
return err
}
}
req, err := http.NewRequest(method, c.getURL(path), in)
req, err := http.NewRequest(method, c.getURL(path), streamOptions.in)
if err != nil {
return err
}
@ -357,17 +371,17 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}
for key, val := range headers {
for key, val := range streamOptions.headers {
req.Header.Set(key, val)
}
var resp *http.Response
protocol := c.endpointURL.Scheme
address := c.endpointURL.Path
if stdout == nil {
stdout = ioutil.Discard
if streamOptions.stdout == nil {
streamOptions.stdout = ioutil.Discard
}
if stderr == nil {
stderr = ioutil.Discard
if streamOptions.stderr == nil {
streamOptions.stderr = ioutil.Discard
}
if protocol == "unix" {
dial, err := net.Dial(protocol, address)
@ -397,8 +411,8 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
if resp.Header.Get("Content-Type") == "application/json" {
// if we want to get raw json stream, just copy it back to output
// without decoding it
if rawJSONStream {
_, err = io.Copy(stdout, resp.Body)
if streamOptions.rawJSONStream {
_, err = io.Copy(streamOptions.stdout, resp.Body)
return err
}
dec := json.NewDecoder(resp.Body)
@ -410,28 +424,37 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
return err
}
if m.Stream != "" {
fmt.Fprint(stdout, m.Stream)
fmt.Fprint(streamOptions.stdout, m.Stream)
} else if m.Progress != "" {
fmt.Fprintf(stdout, "%s %s\r", m.Status, m.Progress)
fmt.Fprintf(streamOptions.stdout, "%s %s\r", m.Status, m.Progress)
} else if m.Error != "" {
return errors.New(m.Error)
}
if m.Status != "" {
fmt.Fprintln(stdout, m.Status)
fmt.Fprintln(streamOptions.stdout, m.Status)
}
}
} else {
if setRawTerminal {
_, err = io.Copy(stdout, resp.Body)
if streamOptions.setRawTerminal {
_, err = io.Copy(streamOptions.stdout, resp.Body)
} else {
_, err = stdCopy(stdout, stderr, resp.Body)
_, err = stdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body)
}
return err
}
return nil
}
func (c *Client) hijack(method, path string, success chan struct{}, setRawTerminal bool, in io.Reader, stderr, stdout io.Writer, data interface{}) error {
type hijackOptions struct {
success chan struct{}
setRawTerminal bool
in io.Reader
stdout io.Writer
stderr io.Writer
data interface{}
}
func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error {
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
err := c.checkAPIVersion()
if err != nil {
@ -440,19 +463,19 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
}
var params io.Reader
if data != nil {
buf, err := json.Marshal(data)
if hijackOptions.data != nil {
buf, err := json.Marshal(hijackOptions.data)
if err != nil {
return err
}
params = bytes.NewBuffer(buf)
}
if stdout == nil {
stdout = ioutil.Discard
if hijackOptions.stdout == nil {
hijackOptions.stdout = ioutil.Discard
}
if stderr == nil {
stderr = ioutil.Discard
if hijackOptions.stderr == nil {
hijackOptions.stderr = ioutil.Discard
}
req, err := http.NewRequest(method, c.getURL(path), params)
if err != nil {
@ -480,9 +503,9 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
clientconn := httputil.NewClientConn(dial, nil)
defer clientconn.Close()
clientconn.Do(req)
if success != nil {
success <- struct{}{}
<-success
if hijackOptions.success != nil {
hijackOptions.success <- struct{}{}
<-hijackOptions.success
}
rwc, br := clientconn.Hijack()
defer rwc.Close()
@ -491,18 +514,18 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
go func() {
defer close(exit)
var err error
if setRawTerminal {
if hijackOptions.setRawTerminal {
// When TTY is ON, use regular copy
_, err = io.Copy(stdout, br)
_, err = io.Copy(hijackOptions.stdout, br)
} else {
_, err = stdCopy(stdout, stderr, br)
_, err = stdCopy(hijackOptions.stdout, hijackOptions.stderr, br)
}
errs <- err
}()
go func() {
var err error
if in != nil {
_, err = io.Copy(rwc, in)
if hijackOptions.in != nil {
_, err = io.Copy(rwc, hijackOptions.in)
}
rwc.(interface {
CloseWrite() error
@ -555,39 +578,49 @@ func queryString(opts interface{}) string {
} else if key == "-" {
continue
}
v := value.Field(i)
switch v.Kind() {
case reflect.Bool:
if v.Bool() {
items.Add(key, "1")
addQueryStringValue(items, key, value.Field(i))
}
return items.Encode()
}
func addQueryStringValue(items url.Values, key string, v reflect.Value) {
switch v.Kind() {
case reflect.Bool:
if v.Bool() {
items.Add(key, "1")
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.Int() > 0 {
items.Add(key, strconv.FormatInt(v.Int(), 10))
}
case reflect.Float32, reflect.Float64:
if v.Float() > 0 {
items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
}
case reflect.String:
if v.String() != "" {
items.Add(key, v.String())
}
case reflect.Ptr:
if !v.IsNil() {
if b, err := json.Marshal(v.Interface()); err == nil {
items.Add(key, string(b))
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.Int() > 0 {
items.Add(key, strconv.FormatInt(v.Int(), 10))
}
case reflect.Map:
if len(v.MapKeys()) > 0 {
if b, err := json.Marshal(v.Interface()); err == nil {
items.Add(key, string(b))
}
case reflect.Float32, reflect.Float64:
if v.Float() > 0 {
items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
}
case reflect.String:
if v.String() != "" {
items.Add(key, v.String())
}
case reflect.Ptr:
if !v.IsNil() {
if b, err := json.Marshal(v.Interface()); err == nil {
items.Add(key, string(b))
}
}
case reflect.Map:
if len(v.MapKeys()) > 0 {
if b, err := json.Marshal(v.Interface()); err == nil {
items.Add(key, string(b))
}
}
case reflect.Array, reflect.Slice:
vLen := v.Len()
if vLen > 0 {
for i := 0; i < vLen; i++ {
addQueryStringValue(items, key, v.Index(i))
}
}
}
return items.Encode()
}
// Error represents failures in the API. It represents a failure from the API.

View File

@ -56,7 +56,7 @@ type APIContainers struct {
// See http://goo.gl/6Y4Gz7 for more details.
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
path := "/containers/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil, false)
body, _, err := c.do("GET", path, doOptions{})
if err != nil {
return nil, err
}
@ -90,6 +90,7 @@ func (p Port) Proto() string {
type State struct {
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"`
Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty"`
OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"`
Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"`
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
@ -263,7 +264,7 @@ type RenameContainerOptions struct {
//
// See http://goo.gl/L00hoj for more details.
func (c *Client) RenameContainer(opts RenameContainerOptions) error {
_, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), nil, false)
_, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{})
return err
}
@ -272,7 +273,7 @@ func (c *Client) RenameContainer(opts RenameContainerOptions) error {
// 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, false)
body, status, err := c.do("GET", path, doOptions{})
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: id}
}
@ -292,7 +293,7 @@ func (c *Client) InspectContainer(id string) (*Container, error) {
// See http://goo.gl/QkW9sH for more details.
func (c *Client) ContainerChanges(id string) ([]Change, error) {
path := "/containers/" + id + "/changes"
body, status, err := c.do("GET", path, nil, false)
body, status, err := c.do("GET", path, doOptions{})
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: id}
}
@ -322,13 +323,19 @@ type CreateContainerOptions struct {
// See http://goo.gl/mErxNp for more details.
func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
path := "/containers/create?" + queryString(opts)
body, status, err := c.do("POST", path, struct {
*Config
HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
}{
opts.Config,
opts.HostConfig,
}, false)
body, status, err := c.do(
"POST",
path,
doOptions{
data: struct {
*Config
HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
}{
opts.Config,
opts.HostConfig,
},
},
)
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
@ -417,6 +424,7 @@ type HostConfig struct {
LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"`
ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"`
SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"`
CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"`
}
// StartContainer starts a container, returning an error in case of failure.
@ -424,7 +432,7 @@ type HostConfig struct {
// See http://goo.gl/iM5GYs for more details.
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
path := "/containers/" + id + "/start"
_, status, err := c.do("POST", path, hostConfig, true)
_, status, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true})
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id, Err: err}
}
@ -443,7 +451,7 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
// See http://goo.gl/EbcpXt for more details.
func (c *Client) StopContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
_, status, err := c.do("POST", path, nil, false)
_, status, err := c.do("POST", path, doOptions{})
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
@ -462,7 +470,7 @@ func (c *Client) StopContainer(id string, timeout uint) error {
// See http://goo.gl/VOzR2n for more details.
func (c *Client) RestartContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
_, status, err := c.do("POST", path, nil, false)
_, status, err := c.do("POST", path, doOptions{})
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
@ -477,7 +485,7 @@ func (c *Client) RestartContainer(id string, timeout uint) error {
// See http://goo.gl/AM5t42 for more details.
func (c *Client) PauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/pause", id)
_, status, err := c.do("POST", path, nil, false)
_, status, err := c.do("POST", path, doOptions{})
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
@ -492,7 +500,7 @@ func (c *Client) PauseContainer(id string) error {
// See http://goo.gl/eBrNSL for more details.
func (c *Client) UnpauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/unpause", id)
_, status, err := c.do("POST", path, nil, false)
_, status, err := c.do("POST", path, doOptions{})
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
@ -521,7 +529,7 @@ func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
args = fmt.Sprintf("?ps_args=%s", psArgs)
}
path := fmt.Sprintf("/containers/%s/top%s", id, args)
body, status, err := c.do("GET", path, nil, false)
body, status, err := c.do("GET", path, doOptions{})
if status == http.StatusNotFound {
return result, &NoSuchContainer{ID: id}
}
@ -553,7 +561,7 @@ type KillContainerOptions struct {
// See http://goo.gl/TFkECx for more details.
func (c *Client) KillContainer(opts KillContainerOptions) error {
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
_, status, err := c.do("POST", path, nil, false)
_, status, err := c.do("POST", path, doOptions{})
if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.ID}
}
@ -584,7 +592,7 @@ type RemoveContainerOptions struct {
// See http://goo.gl/ZB83ji for more details.
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
path := "/containers/" + opts.ID + "?" + queryString(opts)
_, status, err := c.do("DELETE", path, nil, false)
_, status, err := c.do("DELETE", path, doOptions{})
if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.ID}
}
@ -613,7 +621,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
return &NoSuchContainer{ID: opts.Container}
}
url := fmt.Sprintf("/containers/%s/copy", opts.Container)
body, status, err := c.do("POST", url, opts, false)
body, status, err := c.do("POST", url, doOptions{data: opts})
if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.Container}
}
@ -629,7 +637,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
//
// See http://goo.gl/J88DHU for more details.
func (c *Client) WaitContainer(id string) (int, error) {
body, status, err := c.do("POST", "/containers/"+id+"/wait", nil, false)
body, status, err := c.do("POST", "/containers/"+id+"/wait", doOptions{})
if status == http.StatusNotFound {
return 0, &NoSuchContainer{ID: id}
}
@ -661,7 +669,7 @@ type CommitContainerOptions struct {
// See http://goo.gl/Jn8pe8 for more details.
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
path := "/commit?" + queryString(opts)
body, status, err := c.do("POST", path, opts.Run, false)
body, status, err := c.do("POST", path, doOptions{data: opts.Run})
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
@ -720,7 +728,13 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
return &NoSuchContainer{ID: opts.Container}
}
path := "/containers/" + opts.Container + "/attach?" + queryString(opts)
return c.hijack("POST", path, opts.Success, opts.RawTerminal, opts.InputStream, opts.ErrorStream, opts.OutputStream, nil)
return c.hijack("POST", path, hijackOptions{
success: opts.Success,
setRawTerminal: opts.RawTerminal,
in: opts.InputStream,
stdout: opts.OutputStream,
stderr: opts.ErrorStream,
})
}
// LogsOptions represents the set of options used when getting logs from a
@ -752,7 +766,11 @@ func (c *Client) Logs(opts LogsOptions) error {
opts.Tail = "all"
}
path := "/containers/" + opts.Container + "/logs?" + queryString(opts)
return c.stream("GET", path, opts.RawTerminal, false, nil, nil, opts.OutputStream, opts.ErrorStream)
return c.stream("GET", path, streamOptions{
setRawTerminal: opts.RawTerminal,
stdout: opts.OutputStream,
stderr: opts.ErrorStream,
})
}
// ResizeContainerTTY resizes the terminal to the given height and width.
@ -760,7 +778,7 @@ func (c *Client) ResizeContainerTTY(id string, height, width int) error {
params := make(url.Values)
params.Set("h", strconv.Itoa(height))
params.Set("w", strconv.Itoa(width))
_, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), nil, false)
_, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{})
return err
}
@ -782,7 +800,10 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error {
return &NoSuchContainer{ID: opts.ID}
}
url := fmt.Sprintf("/containers/%s/export", opts.ID)
return c.stream("GET", url, true, false, nil, nil, opts.OutputStream, nil)
return c.stream("GET", url, streamOptions{
setRawTerminal: true,
stdout: opts.OutputStream,
})
}
// NoSuchContainer is the error returned when a given container does not exist.

View File

@ -229,7 +229,8 @@ func TestInspectContainer(t *testing.T) {
]
},
"Links": null,
"PublishAllPorts": false
"PublishAllPorts": false,
"CgroupParent": "/mesos"
}
}`
var expected Container

View File

@ -90,7 +90,7 @@ type ExecInspect struct {
// See http://goo.gl/8izrzI for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
body, status, err := c.do("POST", path, opts, false)
body, status, err := c.do("POST", path, doOptions{data: opts})
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
@ -119,7 +119,7 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
path := fmt.Sprintf("/exec/%s/start", id)
if opts.Detach {
_, status, err := c.do("POST", path, opts, false)
_, status, err := c.do("POST", path, doOptions{data: opts})
if status == http.StatusNotFound {
return &NoSuchExec{ID: id}
}
@ -129,7 +129,14 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
return nil
}
return c.hijack("POST", path, opts.Success, opts.RawTerminal, opts.InputStream, opts.ErrorStream, opts.OutputStream, opts)
return c.hijack("POST", path, hijackOptions{
success: opts.Success,
setRawTerminal: opts.RawTerminal,
in: opts.InputStream,
stdout: opts.OutputStream,
stderr: opts.ErrorStream,
data: opts,
})
}
// ResizeExecTTY resizes the tty session used by the exec command id. This API
@ -143,7 +150,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
params.Set("w", strconv.Itoa(width))
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
_, _, err := c.do("POST", path, nil, false)
_, _, err := c.do("POST", path, doOptions{})
return err
}
@ -152,7 +159,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
// See http://goo.gl/ypQULN for more details
func (c *Client) InspectExec(id string) (*ExecInspect, error) {
path := fmt.Sprintf("/exec/%s/json", id)
body, status, err := c.do("GET", path, nil, false)
body, status, err := c.do("GET", path, doOptions{})
if status == http.StatusNotFound {
return nil, &NoSuchExec{ID: id}
}

View File

@ -26,6 +26,7 @@ type APIImages struct {
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"`
ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty"`
RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty"`
}
// Image is the type representing a docker image and its various properties
@ -71,10 +72,11 @@ type ImagePre012 struct {
// ListImagesOptions specify parameters to the ListImages function.
//
// See http://goo.gl/2rOLFF for more details.
// See http://goo.gl/HRVN1Z for more details.
type ListImagesOptions struct {
All bool
Filters map[string][]string
Digests bool
}
var (
@ -92,14 +94,19 @@ var (
// ErrMultipleContexts is the error returned when both a ContextDir and
// InputStream are provided in BuildImageOptions
ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream")
// ErrMustSpecifyNames is the error rreturned when the Names field on
// ExportImagesOptions is nil or empty
ErrMustSpecifyNames = errors.New("must specify at least one name to export")
)
// ListImages returns the list of available images in the server.
//
// See http://goo.gl/2rOLFF for more details.
// See http://goo.gl/HRVN1Z for more details.
func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
// TODO(pedge): what happens if we specify the digest parameter when using API Version <1.18?
path := "/images/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil, false)
body, _, err := c.do("GET", path, doOptions{})
if err != nil {
return nil, err
}
@ -115,7 +122,7 @@ func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
//
// See http://goo.gl/2oJmNs for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
body, status, err := c.do("GET", "/images/"+name+"/history", nil, false)
body, status, err := c.do("GET", "/images/"+name+"/history", doOptions{})
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
@ -134,7 +141,7 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
//
// See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImage(name string) error {
_, status, err := c.do("DELETE", "/images/"+name, nil, false)
_, status, err := c.do("DELETE", "/images/"+name, doOptions{})
if status == http.StatusNotFound {
return ErrNoSuchImage
}
@ -156,7 +163,7 @@ type RemoveImageOptions struct {
// See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error {
uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts))
_, status, err := c.do("DELETE", uri, nil, false)
_, status, err := c.do("DELETE", uri, doOptions{})
if status == http.StatusNotFound {
return ErrNoSuchImage
}
@ -167,7 +174,7 @@ func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error
//
// See http://goo.gl/Q112NY for more details.
func (c *Client) InspectImage(name string) (*Image, error) {
body, status, err := c.do("GET", "/images/"+name+"/json", nil, false)
body, status, err := c.do("GET", "/images/"+name+"/json", doOptions{})
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
@ -236,8 +243,12 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
name := opts.Name
opts.Name = ""
path := "/images/" + name + "/push?" + queryString(&opts)
headers := headersWithAuth(auth)
return c.stream("POST", path, true, opts.RawJSONStream, headers, nil, opts.OutputStream, nil)
return c.stream("POST", path, streamOptions{
setRawTerminal: true,
rawJSONStream: opts.RawJSONStream,
headers: headersWithAuth(auth),
stdout: opts.OutputStream,
})
}
// PullImageOptions present the set of options available for pulling an image
@ -266,7 +277,13 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) 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, rawJSONStream, headers, in, w, nil)
return c.stream("POST", path, streamOptions{
setRawTerminal: true,
rawJSONStream: rawJSONStream,
headers: headers,
in: in,
stdout: w,
})
}
// LoadImageOptions represents the options for LoadImage Docker API Call
@ -280,7 +297,10 @@ type LoadImageOptions struct {
//
// See http://goo.gl/Y8NNCq for more details.
func (c *Client) LoadImage(opts LoadImageOptions) error {
return c.stream("POST", "/images/load", true, false, nil, opts.InputStream, nil, nil)
return c.stream("POST", "/images/load", streamOptions{
setRawTerminal: true,
in: opts.InputStream,
})
}
// ExportImageOptions represent the options for ExportImage Docker API call
@ -295,7 +315,31 @@ type ExportImageOptions struct {
//
// See http://goo.gl/mi6kvk for more details.
func (c *Client) ExportImage(opts ExportImageOptions) error {
return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), true, false, nil, nil, opts.OutputStream, nil)
return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{
setRawTerminal: true,
stdout: opts.OutputStream,
})
}
// ExportImagesOptions represent the options for ExportImages Docker API call
//
// See http://goo.gl/YeZzQK for more details.
type ExportImagesOptions struct {
Names []string
OutputStream io.Writer `qs:"-"`
}
// ExportImages exports one or more images (as a tar file) into the stream
//
// See http://goo.gl/YeZzQK for more details.
func (c *Client) ExportImages(opts ExportImagesOptions) error {
if opts.Names == nil || len(opts.Names) == 0 {
return ErrMustSpecifyNames
}
return c.stream("GET", "/images/get?"+queryString(&opts), streamOptions{
setRawTerminal: true,
stdout: opts.OutputStream,
})
}
// ImportImageOptions present the set of informations available for importing
@ -382,8 +426,13 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
}
}
return c.stream("POST", fmt.Sprintf("/build?%s",
queryString(&opts)), true, opts.RawJSONStream, headers, opts.InputStream, opts.OutputStream, nil)
return c.stream("POST", fmt.Sprintf("/build?%s", queryString(&opts)), streamOptions{
setRawTerminal: true,
rawJSONStream: opts.RawJSONStream,
headers: headers,
in: opts.InputStream,
stdout: opts.OutputStream,
})
}
// TagImageOptions present the set of options to tag an image.
@ -403,7 +452,7 @@ func (c *Client) TagImage(name string, opts TagImageOptions) error {
return ErrNoSuchImage
}
_, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
queryString(&opts)), nil, false)
queryString(&opts)), doOptions{})
if status == http.StatusNotFound {
return ErrNoSuchImage
@ -454,7 +503,7 @@ type APIImageSearch struct {
//
// See http://goo.gl/xI5lLZ for more details.
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) {
body, _, err := c.do("GET", "/images/search?term="+term, nil, false)
body, _, err := c.do("GET", "/images/search?term="+term, doOptions{})
if err != nil {
return nil, err
}

View File

@ -860,6 +860,40 @@ func TestExportImage(t *testing.T) {
}
}
func TestExportImages(t *testing.T) {
var buf bytes.Buffer
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
opts := ExportImagesOptions{Names: []string{"testimage1", "testimage2:latest"}, OutputStream: &buf}
err := client.ExportImages(opts)
if nil != err {
t.Error(err)
}
req := fakeRT.requests[0]
if req.Method != "GET" {
t.Errorf("ExportImage: wrong method. Expected %q. Got %q.", "GET", req.Method)
}
expected := "http://localhost:4243/images/get?names=testimage1&names=testimage2%3Alatest"
got := req.URL.String()
if !reflect.DeepEqual(got, expected) {
t.Errorf("ExportIMage: wrong path. Expected %q. Got %q.", expected, got)
}
}
func TestExportImagesNoNames(t *testing.T) {
var buf bytes.Buffer
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
opts := ExportImagesOptions{Names: []string{}, OutputStream: &buf}
err := client.ExportImages(opts)
if err == nil {
t.Error("Expected an error")
}
if err != ErrMustSpecifyNames {
t.Error(err)
}
}
func TestSearchImages(t *testing.T) {
body := `[
{

View File

@ -13,7 +13,7 @@ import (
//
// See http://goo.gl/BOZrF5 for more details.
func (c *Client) Version() (*Env, error) {
body, _, err := c.do("GET", "/version", nil, false)
body, _, err := c.do("GET", "/version", doOptions{})
if err != nil {
return nil, err
}
@ -28,7 +28,7 @@ func (c *Client) Version() (*Env, error) {
//
// See http://goo.gl/wmqZsW for more details.
func (c *Client) Info() (*Env, error) {
body, _, err := c.do("GET", "/info", nil, false)
body, _, err := c.do("GET", "/info", doOptions{})
if err != nil {
return nil, err
}

View File

@ -25,6 +25,8 @@ import (
"github.com/gorilla/mux"
)
var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`)
// DockerServer represents a programmable, concurrent (not much), HTTP server
// implementing a fake version of the Docker remote API.
//
@ -339,6 +341,11 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
name := r.URL.Query().Get("name")
if name != "" && !nameRegexp.MatchString(name) {
http.Error(w, "Invalid container name", http.StatusInternalServerError)
return
}
if _, err := s.findImage(config.Image); err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
@ -363,7 +370,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
}
container := docker.Container{
Name: r.URL.Query().Get("name"),
Name: name,
ID: s.generateID(),
Created: time.Now(),
Path: path,

View File

@ -241,6 +241,24 @@ func TestCreateContainerInvalidBody(t *testing.T) {
}
}
func TestCreateContainerInvalidName(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true,
"PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"],
"Image":"base", "Volumes":{}, "VolumesFrom":""}`
request, _ := http.NewRequest("POST", "/containers/create?name=myapp/container1", strings.NewReader(body))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusInternalServerError {
t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code)
}
expectedBody := "Invalid container name\n"
if got := recorder.Body.String(); got != expectedBody {
t.Errorf("CreateContainer: wrong body. Want %q. Got %q.", expectedBody, got)
}
}
func TestCreateContainerImageNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()