Commit 7c7b661c authored by David Oppenheimer's avatar David Oppenheimer

Merge pull request #7247 from guenter/update-dockerclient

Upgrade go-dockerclient dependency to support CgroupParent
parents d25a1412 522d4c3b
...@@ -164,7 +164,7 @@ ...@@ -164,7 +164,7 @@
}, },
{ {
"ImportPath": "github.com/fsouza/go-dockerclient", "ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "17d39bcb22e8103ba6d1c0cb2530c6434cb870a3" "Rev": "09334c56c63bab2cd6c4ccab924d89e2419a361f"
}, },
{ {
"ImportPath": "github.com/garyburd/redigo/internal", "ImportPath": "github.com/garyburd/redigo/internal",
......
...@@ -7,7 +7,6 @@ env: ...@@ -7,7 +7,6 @@ env:
- GOARCH=amd64 - GOARCH=amd64
- GOARCH=386 - GOARCH=386
install: install:
- go get -d ./... - make testdeps
script: script:
- go test ./... - make test
- ./testing/bin/fmtpolice
...@@ -26,6 +26,7 @@ Fatih Arslan <ftharsln@gmail.com> ...@@ -26,6 +26,7 @@ Fatih Arslan <ftharsln@gmail.com>
Flavia Missi <flaviamissi@gmail.com> Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc> Francisco Souza <f@souza.cc>
Guillermo Álvarez Fernández <guillermo@cientifico.net> Guillermo Álvarez Fernández <guillermo@cientifico.net>
James Bardin <jbardin@litl.com>
Jari Kolehmainen <jari.kolehmainen@digia.com> Jari Kolehmainen <jari.kolehmainen@digia.com>
Jason Wilder <jwilder@litl.com> Jason Wilder <jwilder@litl.com>
Jawher Moussa <jawher.moussa@gmail.com> Jawher Moussa <jawher.moussa@gmail.com>
...@@ -49,6 +50,7 @@ Nick Ethier <ncethier@gmail.com> ...@@ -49,6 +50,7 @@ Nick Ethier <ncethier@gmail.com>
Omeid Matten <public@omeid.me> Omeid Matten <public@omeid.me>
Paul Morie <pmorie@gmail.com> Paul Morie <pmorie@gmail.com>
Paul Weil <pweil@redhat.com> Paul Weil <pweil@redhat.com>
Peter Edge <peter.edge@gmail.com>
Peter Jihoon Kim <raingrove@gmail.com> Peter Jihoon Kim <raingrove@gmail.com>
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com> Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
Rafe Colton <rafael.colton@gmail.com> Rafe Colton <rafael.colton@gmail.com>
...@@ -64,6 +66,7 @@ Sridhar Ratnakumar <sridharr@activestate.com> ...@@ -64,6 +66,7 @@ Sridhar Ratnakumar <sridharr@activestate.com>
Summer Mousa <smousa@zenoss.com> Summer Mousa <smousa@zenoss.com>
Tarsis Azevedo <tarsis@corp.globo.com> Tarsis Azevedo <tarsis@corp.globo.com>
Tim Schindler <tim@catalyst-zero.com> Tim Schindler <tim@catalyst-zero.com>
Tobi Knaup <tobi@mesosphere.io>
Vincenzo Prignano <vincenzo.prignano@gmail.com> Vincenzo Prignano <vincenzo.prignano@gmail.com>
Wiliam Souza <wiliamsouza83@gmail.com> Wiliam Souza <wiliamsouza83@gmail.com>
Ye Yin <eyniy@qq.com> Ye Yin <eyniy@qq.com>
......
.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 ./...
...@@ -66,5 +66,4 @@ func main() { ...@@ -66,5 +66,4 @@ func main() {
You can run the tests with: You can run the tests with:
go get -d ./... make test
go test ./...
...@@ -252,7 +252,7 @@ func (c *Client) checkAPIVersion() error { ...@@ -252,7 +252,7 @@ func (c *Client) checkAPIVersion() error {
// See http://goo.gl/stJENm for more details. // See http://goo.gl/stJENm for more details.
func (c *Client) Ping() error { func (c *Client) Ping() error {
path := "/_ping" path := "/_ping"
body, status, err := c.do("GET", path, nil, false) body, status, err := c.do("GET", path, doOptions{})
if err != nil { if err != nil {
return err return err
} }
...@@ -263,7 +263,7 @@ func (c *Client) Ping() error { ...@@ -263,7 +263,7 @@ func (c *Client) Ping() error {
} }
func (c *Client) getServerAPIVersionString() (version string, err 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 { if err != nil {
return "", err return "", err
} }
...@@ -279,10 +279,15 @@ func (c *Client) getServerAPIVersionString() (version string, err error) { ...@@ -279,10 +279,15 @@ func (c *Client) getServerAPIVersionString() (version string, err error) {
return version, nil 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 var params io.Reader
if data != nil || forceJSON { if doOptions.data != nil || doOptions.forceJSON {
buf, err := json.Marshal(data) buf, err := json.Marshal(doOptions.data)
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
} }
...@@ -299,7 +304,7 @@ func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]by ...@@ -299,7 +304,7 @@ func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]by
return nil, -1, err return nil, -1, err
} }
req.Header.Set("User-Agent", userAgent) req.Header.Set("User-Agent", userAgent)
if data != nil { if doOptions.data != nil {
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
} else if method == "POST" { } else if method == "POST" {
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
...@@ -339,9 +344,18 @@ func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]by ...@@ -339,9 +344,18 @@ func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]by
return body, resp.StatusCode, nil 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 { type streamOptions struct {
if (method == "POST" || method == "PUT") && in == nil { setRawTerminal bool
in = bytes.NewReader(nil) 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 { if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
err := c.checkAPIVersion() err := c.checkAPIVersion()
...@@ -349,7 +363,7 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, ...@@ -349,7 +363,7 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
return err return err
} }
} }
req, err := http.NewRequest(method, c.getURL(path), in) req, err := http.NewRequest(method, c.getURL(path), streamOptions.in)
if err != nil { if err != nil {
return err return err
} }
...@@ -357,17 +371,17 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, ...@@ -357,17 +371,17 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
if method == "POST" { if method == "POST" {
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
} }
for key, val := range headers { for key, val := range streamOptions.headers {
req.Header.Set(key, val) req.Header.Set(key, val)
} }
var resp *http.Response var resp *http.Response
protocol := c.endpointURL.Scheme protocol := c.endpointURL.Scheme
address := c.endpointURL.Path address := c.endpointURL.Path
if stdout == nil { if streamOptions.stdout == nil {
stdout = ioutil.Discard streamOptions.stdout = ioutil.Discard
} }
if stderr == nil { if streamOptions.stderr == nil {
stderr = ioutil.Discard streamOptions.stderr = ioutil.Discard
} }
if protocol == "unix" { if protocol == "unix" {
dial, err := net.Dial(protocol, address) dial, err := net.Dial(protocol, address)
...@@ -397,8 +411,8 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, ...@@ -397,8 +411,8 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
if resp.Header.Get("Content-Type") == "application/json" { if resp.Header.Get("Content-Type") == "application/json" {
// if we want to get raw json stream, just copy it back to output // if we want to get raw json stream, just copy it back to output
// without decoding it // without decoding it
if rawJSONStream { if streamOptions.rawJSONStream {
_, err = io.Copy(stdout, resp.Body) _, err = io.Copy(streamOptions.stdout, resp.Body)
return err return err
} }
dec := json.NewDecoder(resp.Body) dec := json.NewDecoder(resp.Body)
...@@ -410,28 +424,37 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, ...@@ -410,28 +424,37 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
return err return err
} }
if m.Stream != "" { if m.Stream != "" {
fmt.Fprint(stdout, m.Stream) fmt.Fprint(streamOptions.stdout, m.Stream)
} else if m.Progress != "" { } 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 != "" { } else if m.Error != "" {
return errors.New(m.Error) return errors.New(m.Error)
} }
if m.Status != "" { if m.Status != "" {
fmt.Fprintln(stdout, m.Status) fmt.Fprintln(streamOptions.stdout, m.Status)
} }
} }
} else { } else {
if setRawTerminal { if streamOptions.setRawTerminal {
_, err = io.Copy(stdout, resp.Body) _, err = io.Copy(streamOptions.stdout, resp.Body)
} else { } else {
_, err = stdCopy(stdout, stderr, resp.Body) _, err = stdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body)
} }
return err return err
} }
return nil 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 { if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
err := c.checkAPIVersion() err := c.checkAPIVersion()
if err != nil { if err != nil {
...@@ -440,19 +463,19 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin ...@@ -440,19 +463,19 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
} }
var params io.Reader var params io.Reader
if data != nil { if hijackOptions.data != nil {
buf, err := json.Marshal(data) buf, err := json.Marshal(hijackOptions.data)
if err != nil { if err != nil {
return err return err
} }
params = bytes.NewBuffer(buf) params = bytes.NewBuffer(buf)
} }
if stdout == nil { if hijackOptions.stdout == nil {
stdout = ioutil.Discard hijackOptions.stdout = ioutil.Discard
} }
if stderr == nil { if hijackOptions.stderr == nil {
stderr = ioutil.Discard hijackOptions.stderr = ioutil.Discard
} }
req, err := http.NewRequest(method, c.getURL(path), params) req, err := http.NewRequest(method, c.getURL(path), params)
if err != nil { if err != nil {
...@@ -480,9 +503,9 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin ...@@ -480,9 +503,9 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
clientconn := httputil.NewClientConn(dial, nil) clientconn := httputil.NewClientConn(dial, nil)
defer clientconn.Close() defer clientconn.Close()
clientconn.Do(req) clientconn.Do(req)
if success != nil { if hijackOptions.success != nil {
success <- struct{}{} hijackOptions.success <- struct{}{}
<-success <-hijackOptions.success
} }
rwc, br := clientconn.Hijack() rwc, br := clientconn.Hijack()
defer rwc.Close() defer rwc.Close()
...@@ -491,18 +514,18 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin ...@@ -491,18 +514,18 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
go func() { go func() {
defer close(exit) defer close(exit)
var err error var err error
if setRawTerminal { if hijackOptions.setRawTerminal {
// When TTY is ON, use regular copy // When TTY is ON, use regular copy
_, err = io.Copy(stdout, br) _, err = io.Copy(hijackOptions.stdout, br)
} else { } else {
_, err = stdCopy(stdout, stderr, br) _, err = stdCopy(hijackOptions.stdout, hijackOptions.stderr, br)
} }
errs <- err errs <- err
}() }()
go func() { go func() {
var err error var err error
if in != nil { if hijackOptions.in != nil {
_, err = io.Copy(rwc, in) _, err = io.Copy(rwc, hijackOptions.in)
} }
rwc.(interface { rwc.(interface {
CloseWrite() error CloseWrite() error
...@@ -555,39 +578,49 @@ func queryString(opts interface{}) string { ...@@ -555,39 +578,49 @@ func queryString(opts interface{}) string {
} else if key == "-" { } else if key == "-" {
continue continue
} }
v := value.Field(i) addQueryStringValue(items, key, value.Field(i))
switch v.Kind() { }
case reflect.Bool: return items.Encode()
if v.Bool() { }
items.Add(key, "1")
} func addQueryStringValue(items url.Values, key string, v reflect.Value) {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: switch v.Kind() {
if v.Int() > 0 { case reflect.Bool:
items.Add(key, strconv.FormatInt(v.Int(), 10)) if v.Bool() {
} items.Add(key, "1")
case reflect.Float32, reflect.Float64: }
if v.Float() > 0 { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) if v.Int() > 0 {
} items.Add(key, strconv.FormatInt(v.Int(), 10))
case reflect.String: }
if v.String() != "" { case reflect.Float32, reflect.Float64:
items.Add(key, v.String()) 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.Ptr: }
if !v.IsNil() { case reflect.Map:
if b, err := json.Marshal(v.Interface()); err == nil { if len(v.MapKeys()) > 0 {
items.Add(key, string(b)) if b, err := json.Marshal(v.Interface()); err == nil {
} items.Add(key, string(b))
} }
case reflect.Map: }
if len(v.MapKeys()) > 0 { case reflect.Array, reflect.Slice:
if b, err := json.Marshal(v.Interface()); err == nil { vLen := v.Len()
items.Add(key, string(b)) 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. // Error represents failures in the API. It represents a failure from the API.
......
...@@ -229,7 +229,8 @@ func TestInspectContainer(t *testing.T) { ...@@ -229,7 +229,8 @@ func TestInspectContainer(t *testing.T) {
] ]
}, },
"Links": null, "Links": null,
"PublishAllPorts": false "PublishAllPorts": false,
"CgroupParent": "/mesos"
} }
}` }`
var expected Container var expected Container
......
...@@ -90,7 +90,7 @@ type ExecInspect struct { ...@@ -90,7 +90,7 @@ type ExecInspect struct {
// See http://goo.gl/8izrzI for more details // See http://goo.gl/8izrzI for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container) 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 { if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container} return nil, &NoSuchContainer{ID: opts.Container}
} }
...@@ -119,7 +119,7 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error { ...@@ -119,7 +119,7 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
path := fmt.Sprintf("/exec/%s/start", id) path := fmt.Sprintf("/exec/%s/start", id)
if opts.Detach { if opts.Detach {
_, status, err := c.do("POST", path, opts, false) _, status, err := c.do("POST", path, doOptions{data: opts})
if status == http.StatusNotFound { if status == http.StatusNotFound {
return &NoSuchExec{ID: id} return &NoSuchExec{ID: id}
} }
...@@ -129,7 +129,14 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error { ...@@ -129,7 +129,14 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
return nil 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 // 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 { ...@@ -143,7 +150,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
params.Set("w", strconv.Itoa(width)) params.Set("w", strconv.Itoa(width))
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode()) 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 return err
} }
...@@ -152,7 +159,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error { ...@@ -152,7 +159,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
// See http://goo.gl/ypQULN for more details // See http://goo.gl/ypQULN for more details
func (c *Client) InspectExec(id string) (*ExecInspect, error) { func (c *Client) InspectExec(id string) (*ExecInspect, error) {
path := fmt.Sprintf("/exec/%s/json", id) 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 { if status == http.StatusNotFound {
return nil, &NoSuchExec{ID: id} return nil, &NoSuchExec{ID: id}
} }
......
...@@ -26,6 +26,7 @@ type APIImages struct { ...@@ -26,6 +26,7 @@ type APIImages struct {
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"` Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"` VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"`
ParentID string `json:"ParentId,omitempty" yaml:"ParentId,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 // Image is the type representing a docker image and its various properties
...@@ -71,10 +72,11 @@ type ImagePre012 struct { ...@@ -71,10 +72,11 @@ type ImagePre012 struct {
// ListImagesOptions specify parameters to the ListImages function. // 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 { type ListImagesOptions struct {
All bool All bool
Filters map[string][]string Filters map[string][]string
Digests bool
} }
var ( var (
...@@ -92,14 +94,19 @@ var ( ...@@ -92,14 +94,19 @@ var (
// ErrMultipleContexts is the error returned when both a ContextDir and // ErrMultipleContexts is the error returned when both a ContextDir and
// InputStream are provided in BuildImageOptions // InputStream are provided in BuildImageOptions
ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream") 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. // 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) { 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) path := "/images/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil, false) body, _, err := c.do("GET", path, doOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -115,7 +122,7 @@ func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { ...@@ -115,7 +122,7 @@ func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
// //
// See http://goo.gl/2oJmNs for more details. // See http://goo.gl/2oJmNs for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { 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 { if status == http.StatusNotFound {
return nil, ErrNoSuchImage return nil, ErrNoSuchImage
} }
...@@ -134,7 +141,7 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { ...@@ -134,7 +141,7 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
// //
// See http://goo.gl/znj0wM for more details. // See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImage(name string) error { 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 { if status == http.StatusNotFound {
return ErrNoSuchImage return ErrNoSuchImage
} }
...@@ -156,7 +163,7 @@ type RemoveImageOptions struct { ...@@ -156,7 +163,7 @@ type RemoveImageOptions struct {
// See http://goo.gl/znj0wM for more details. // See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error { func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error {
uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts)) 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 { if status == http.StatusNotFound {
return ErrNoSuchImage return ErrNoSuchImage
} }
...@@ -167,7 +174,7 @@ func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error ...@@ -167,7 +174,7 @@ func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error
// //
// See http://goo.gl/Q112NY for more details. // See http://goo.gl/Q112NY for more details.
func (c *Client) InspectImage(name string) (*Image, error) { 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 { if status == http.StatusNotFound {
return nil, ErrNoSuchImage return nil, ErrNoSuchImage
} }
...@@ -236,8 +243,12 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error ...@@ -236,8 +243,12 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
name := opts.Name name := opts.Name
opts.Name = "" opts.Name = ""
path := "/images/" + name + "/push?" + queryString(&opts) path := "/images/" + name + "/push?" + queryString(&opts)
headers := headersWithAuth(auth) return c.stream("POST", path, streamOptions{
return c.stream("POST", path, true, opts.RawJSONStream, headers, nil, opts.OutputStream, nil) setRawTerminal: true,
rawJSONStream: opts.RawJSONStream,
headers: headersWithAuth(auth),
stdout: opts.OutputStream,
})
} }
// PullImageOptions present the set of options available for pulling an image // PullImageOptions present the set of options available for pulling an image
...@@ -266,7 +277,13 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error ...@@ -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 { func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool) error {
path := "/images/create?" + qs 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 // LoadImageOptions represents the options for LoadImage Docker API Call
...@@ -280,7 +297,10 @@ type LoadImageOptions struct { ...@@ -280,7 +297,10 @@ type LoadImageOptions struct {
// //
// See http://goo.gl/Y8NNCq for more details. // See http://goo.gl/Y8NNCq for more details.
func (c *Client) LoadImage(opts LoadImageOptions) error { 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 // ExportImageOptions represent the options for ExportImage Docker API call
...@@ -295,7 +315,31 @@ type ExportImageOptions struct { ...@@ -295,7 +315,31 @@ type ExportImageOptions struct {
// //
// See http://goo.gl/mi6kvk for more details. // See http://goo.gl/mi6kvk for more details.
func (c *Client) ExportImage(opts ExportImageOptions) error { 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 // ImportImageOptions present the set of informations available for importing
...@@ -382,8 +426,13 @@ func (c *Client) BuildImage(opts BuildImageOptions) error { ...@@ -382,8 +426,13 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
} }
} }
return c.stream("POST", fmt.Sprintf("/build?%s", return c.stream("POST", fmt.Sprintf("/build?%s", queryString(&opts)), streamOptions{
queryString(&opts)), true, opts.RawJSONStream, headers, opts.InputStream, opts.OutputStream, nil) setRawTerminal: true,
rawJSONStream: opts.RawJSONStream,
headers: headers,
in: opts.InputStream,
stdout: opts.OutputStream,
})
} }
// TagImageOptions present the set of options to tag an image. // TagImageOptions present the set of options to tag an image.
...@@ -403,7 +452,7 @@ func (c *Client) TagImage(name string, opts TagImageOptions) error { ...@@ -403,7 +452,7 @@ func (c *Client) TagImage(name string, opts TagImageOptions) error {
return ErrNoSuchImage return ErrNoSuchImage
} }
_, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s", _, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
queryString(&opts)), nil, false) queryString(&opts)), doOptions{})
if status == http.StatusNotFound { if status == http.StatusNotFound {
return ErrNoSuchImage return ErrNoSuchImage
...@@ -454,7 +503,7 @@ type APIImageSearch struct { ...@@ -454,7 +503,7 @@ type APIImageSearch struct {
// //
// See http://goo.gl/xI5lLZ for more details. // See http://goo.gl/xI5lLZ for more details.
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) { 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 { if err != nil {
return nil, err return nil, err
} }
......
...@@ -860,6 +860,40 @@ func TestExportImage(t *testing.T) { ...@@ -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) { func TestSearchImages(t *testing.T) {
body := `[ body := `[
{ {
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
// //
// See http://goo.gl/BOZrF5 for more details. // See http://goo.gl/BOZrF5 for more details.
func (c *Client) Version() (*Env, error) { func (c *Client) Version() (*Env, error) {
body, _, err := c.do("GET", "/version", nil, false) body, _, err := c.do("GET", "/version", doOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -28,7 +28,7 @@ func (c *Client) Version() (*Env, error) { ...@@ -28,7 +28,7 @@ func (c *Client) Version() (*Env, error) {
// //
// See http://goo.gl/wmqZsW for more details. // See http://goo.gl/wmqZsW for more details.
func (c *Client) Info() (*Env, error) { func (c *Client) Info() (*Env, error) {
body, _, err := c.do("GET", "/info", nil, false) body, _, err := c.do("GET", "/info", doOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -25,6 +25,8 @@ import ( ...@@ -25,6 +25,8 @@ import (
"github.com/gorilla/mux" "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 // DockerServer represents a programmable, concurrent (not much), HTTP server
// implementing a fake version of the Docker remote API. // implementing a fake version of the Docker remote API.
// //
...@@ -339,6 +341,11 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { ...@@ -339,6 +341,11 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return 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 { if _, err := s.findImage(config.Image); err != nil {
http.Error(w, err.Error(), http.StatusNotFound) http.Error(w, err.Error(), http.StatusNotFound)
return return
...@@ -363,7 +370,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { ...@@ -363,7 +370,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
} }
container := docker.Container{ container := docker.Container{
Name: r.URL.Query().Get("name"), Name: name,
ID: s.generateID(), ID: s.generateID(),
Created: time.Now(), Created: time.Now(),
Path: path, Path: path,
......
...@@ -241,6 +241,24 @@ func TestCreateContainerInvalidBody(t *testing.T) { ...@@ -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) { func TestCreateContainerImageNotFound(t *testing.T) {
server := DockerServer{} server := DockerServer{}
server.buildMuxer() server.buildMuxer()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment