Merge pull request #12770 from bparees/master

bump(fsouza/go-dockerclient): 42d06e2b125654477366c320dcea99107a86e9c2
This commit is contained in:
Nikhil Jindal 2015-08-24 11:29:02 -07:00
commit 03d961a8b3
21 changed files with 916 additions and 45 deletions

2
Godeps/Godeps.json generated
View File

@ -232,7 +232,7 @@
}, },
{ {
"ImportPath": "github.com/fsouza/go-dockerclient", "ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "933433faa3e1c0bbc825b251143f8e77affbf797" "Rev": "42d06e2b125654477366c320dcea99107a86e9c2"
}, },
{ {
"ImportPath": "github.com/garyburd/redigo/internal", "ImportPath": "github.com/garyburd/redigo/internal",

View File

@ -8,8 +8,9 @@ Artem Sidorenko <artem@2realities.com>
Andy Goldstein <andy.goldstein@redhat.com> Andy Goldstein <andy.goldstein@redhat.com>
Ben Marini <ben@remind101.com> Ben Marini <ben@remind101.com>
Ben McCann <benmccann.com> Ben McCann <benmccann.com>
Brian Lalor <blalor@bravo5.org>
Brendan Fosberry <brendan@codeship.com> Brendan Fosberry <brendan@codeship.com>
Brian Lalor <blalor@bravo5.org>
Brian Palmer <brianp@instructure.com>
Burke Libbey <burke@libbey.me> Burke Libbey <burke@libbey.me>
Carlos Diaz-Padron <cpadron@mozilla.com> Carlos Diaz-Padron <cpadron@mozilla.com>
Cezar Sa Espinola <cezar.sa@corp.globo.com> Cezar Sa Espinola <cezar.sa@corp.globo.com>
@ -17,6 +18,7 @@ Cheah Chu Yeow <chuyeow@gmail.com>
cheneydeng <cheneydeng@qq.com> cheneydeng <cheneydeng@qq.com>
CMGS <ilskdw@gmail.com> CMGS <ilskdw@gmail.com>
Craig Jellick <craig@rancher.com> Craig Jellick <craig@rancher.com>
Dan Williams <dcbw@redhat.com>
Daniel, Dao Quang Minh <dqminh89@gmail.com> Daniel, Dao Quang Minh <dqminh89@gmail.com>
Daniel Garcia <daniel@danielgarcia.info> Daniel Garcia <daniel@danielgarcia.info>
Darren Shepherd <darren@rancher.com> Darren Shepherd <darren@rancher.com>
@ -80,6 +82,7 @@ Sunjin Lee <styner32@gmail.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> Tobi Knaup <tobi@mesosphere.io>
Tonic <tonicbupt@gmail.com>
ttyh061 <ttyh061@gmail.com> ttyh061 <ttyh061@gmail.com>
Victor Marmol <vmarmol@google.com> Victor Marmol <vmarmol@google.com>
Vincenzo Prignano <vincenzo.prignano@gmail.com> Vincenzo Prignano <vincenzo.prignano@gmail.com>

View File

@ -7,8 +7,20 @@
This package presents a client for the Docker remote API. It also provides This package presents a client for the Docker remote API. It also provides
support for the extensions in the [Swarm API](https://docs.docker.com/swarm/API/). support for the extensions in the [Swarm API](https://docs.docker.com/swarm/API/).
This package also provides support for docker's network API, which is a simple
passthrough to the libnetwork remote API. Note that docker's network API is
only available in docker 1.8 and above, and only enabled in docker if
DOCKER_EXPERIMENTAL is defined during the docker build process.
For more details, check the [remote API documentation](http://docs.docker.com/en/latest/reference/api/docker_remote_api/). For more details, check the [remote API documentation](http://docs.docker.com/en/latest/reference/api/docker_remote_api/).
## Vendoring
If you are having issues with Go 1.5 and have `GO15VENDOREXPERIMENT` set with an application that has go-dockerclient vendored,
please update your vendoring of go-dockerclient :) We recently moved the `vendor` directory to `external` so that go-dockerclient
is compatible with this configuration. See [338](https://github.com/fsouza/go-dockerclient/issues/338) and [339](https://github.com/fsouza/go-dockerclient/pull/339)
for details.
## Example ## Example
```go ```go

View File

@ -8,6 +8,7 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -15,6 +16,8 @@ import (
"strings" "strings"
) )
var AuthParseError error = errors.New("Failed to read authentication from dockercfg")
// AuthConfiguration represents authentication options to use in the PushImage // AuthConfiguration represents authentication options to use in the PushImage
// method. It represents the authentication in the Docker index server. // method. It represents the authentication in the Docker index server.
type AuthConfiguration struct { type AuthConfiguration struct {
@ -99,6 +102,9 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
return nil, err return nil, err
} }
userpass := strings.Split(string(data), ":") userpass := strings.Split(string(data), ":")
if len(userpass) != 2 {
return nil, AuthParseError
}
c.Configs[reg] = AuthConfiguration{ c.Configs[reg] = AuthConfiguration{
Email: conf.Email, Email: conf.Email,
Username: userpass[0], Username: userpass[0],

View File

@ -37,6 +37,18 @@ func TestAuthLegacyConfig(t *testing.T) {
} }
} }
func TestAuthBadConfig(t *testing.T) {
auth := base64.StdEncoding.EncodeToString([]byte("userpass"))
read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth))
ac, err := NewAuthConfigurations(read)
if err != AuthParseError {
t.Errorf("Incorrect error returned %v\n", err)
}
if ac != nil {
t.Errorf("Invalid auth configuration returned, should be nil %v\n", ac)
}
}
func TestAuthConfig(t *testing.T) { func TestAuthConfig(t *testing.T) {
auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) auth := base64.StdEncoding.EncodeToString([]byte("user:pass"))
read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{"auth":"%s","email":"user@example.com"}}}`, auth)) read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{"auth":"%s","email":"user@example.com"}}}`, auth))

View File

@ -28,10 +28,11 @@ import (
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/homedir" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/homedir"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy"
"time"
) )
const userAgent = "go-dockerclient" const userAgent = "go-dockerclient"
@ -628,18 +629,20 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error
if hijackOptions.in != nil { if hijackOptions.in != nil {
_, err := io.Copy(rwc, hijackOptions.in) _, err := io.Copy(rwc, hijackOptions.in)
errChanIn <- err errChanIn <- err
} else {
errChanIn <- nil
} }
rwc.(interface { rwc.(interface {
CloseWrite() error CloseWrite() error
}).CloseWrite() }).CloseWrite()
}() }()
<-exit <-exit
select { errIn := <-errChanIn
case err = <-errChanIn: errOut := <-errChanOut
return err if errIn != nil {
case err = <-errChanOut: return errIn
return err
} }
return errOut
} }
func (c *Client) getURL(path string) string { func (c *Client) getURL(path string) string {

View File

@ -329,16 +329,17 @@ func TestPingFailingWrongStatus(t *testing.T) {
func TestPingErrorWithUnixSocket(t *testing.T) { func TestPingErrorWithUnixSocket(t *testing.T) {
go func() { go func() {
li, err := net.Listen("unix", "/tmp/echo.sock") li, err := net.Listen("unix", "/tmp/echo.sock")
if err != nil {
t.Fatal(err)
}
defer li.Close() defer li.Close()
if err != nil { if err != nil {
t.Fatalf("Expected to get listner, but failed: %#v", err) t.Fatalf("Expected to get listner, but failed: %#v", err)
return
} }
fd, err := li.Accept() fd, err := li.Accept()
if err != nil { if err != nil {
t.Fatalf("Expected to accept connection, but failed: %#v", err) t.Fatalf("Expected to accept connection, but failed: %#v", err)
return
} }
buf := make([]byte, 512) buf := make([]byte, 512)

View File

@ -135,6 +135,16 @@ type NetworkSettings struct {
Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"` Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"`
PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"` PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"`
Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"` Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"`
NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"`
EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"`
SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty"`
GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"`
GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"`
IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"`
LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty"`
LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty"`
SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty"`
SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty"`
} }
// PortMappingAPI translates the port mappings as contained in NetworkSettings // PortMappingAPI translates the port mappings as contained in NetworkSettings
@ -726,6 +736,18 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
close(errC) close(errC)
}() }()
quit := make(chan struct{})
defer close(quit)
go func() {
// block here waiting for the signal to stop function
select {
case <-opts.Done:
readCloser.Close()
case <-quit:
return
}
}()
decoder := json.NewDecoder(readCloser) decoder := json.NewDecoder(readCloser)
stats := new(Stats) stats := new(Stats)
for err := decoder.Decode(&stats); err != io.EOF; err = decoder.Decode(stats) { for err := decoder.Decode(&stats); err != io.EOF; err = decoder.Decode(stats) {
@ -734,12 +756,6 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
} }
opts.Stats <- stats opts.Stats <- stats
stats = new(Stats) stats = new(Stats)
select {
case <-opts.Done:
readCloser.Close()
default:
// Continue
}
} }
return nil return nil
} }

View File

@ -0,0 +1,11 @@
// +build !windows
package archive
import (
"path/filepath"
)
func normalizePath(path string) string {
return filepath.ToSlash(path)
}

View File

@ -0,0 +1,9 @@
package archive
import (
"path/filepath"
)
func normalizePath(path string) string {
return filepath.FromSlash(path)
}

View File

@ -1,7 +1,235 @@
mux mux
=== ===
[![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie)
[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux) [![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux)
gorilla/mux is a powerful URL router and dispatcher. Package gorilla/mux implements a request router and dispatcher.
Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux The name mux stands for "HTTP request multiplexer". Like the standard
http.ServeMux, mux.Router matches incoming requests against a list of
registered routes and calls a handler for the route that matches the URL
or other conditions. The main features are:
* Requests can be matched based on URL host, path, path prefix, schemes,
header and query values, HTTP methods or using custom matchers.
* URL hosts and paths can have variables with an optional regular
expression.
* Registered URLs can be built, or "reversed", which helps maintaining
references to resources.
* Routes can be used as subrouters: nested routes are only tested if the
parent route matches. This is useful to define groups of routes that
share common conditions like a host, a path prefix or other repeated
attributes. As a bonus, this optimizes request matching.
* It implements the http.Handler interface so it is compatible with the
standard http.ServeMux.
Let's start registering a couple of URL paths and handlers:
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", r)
}
Here we register three routes mapping URL paths to handlers. This is
equivalent to how http.HandleFunc() works: if an incoming request URL matches
one of the paths, the corresponding handler is called passing
(http.ResponseWriter, *http.Request) as parameters.
Paths can have variables. They are defined using the format {name} or
{name:pattern}. If a regular expression pattern is not defined, the matched
variable will be anything until the next slash. For example:
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
The names are used to create a map of route variables which can be retrieved
calling mux.Vars():
vars := mux.Vars(request)
category := vars["category"]
And this is all you need to know about the basic usage. More advanced options
are explained below.
Routes can also be restricted to a domain or subdomain. Just define a host
pattern to be matched. They can also have variables:
r := mux.NewRouter()
// Only matches if domain is "www.example.com".
r.Host("www.example.com")
// Matches a dynamic subdomain.
r.Host("{subdomain:[a-z]+}.domain.com")
There are several other matchers that can be added. To match path prefixes:
r.PathPrefix("/products/")
...or HTTP methods:
r.Methods("GET", "POST")
...or URL schemes:
r.Schemes("https")
...or header values:
r.Headers("X-Requested-With", "XMLHttpRequest")
...or query values:
r.Queries("key", "value")
...or to use a custom matcher function:
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
return r.ProtoMajor == 0
})
...and finally, it is possible to combine several matchers in a single route:
r.HandleFunc("/products", ProductsHandler).
Host("www.example.com").
Methods("GET").
Schemes("http")
Setting the same matching conditions again and again can be boring, so we have
a way to group several routes that share the same requirements.
We call it "subrouting".
For example, let's say we have several URLs that should only match when the
host is `www.example.com`. Create a route for that host and get a "subrouter"
from it:
r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()
Then register routes in the subrouter:
s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
The three URL paths we registered above will only be tested if the domain is
`www.example.com`, because the subrouter is tested first. This is not
only convenient, but also optimizes request matching. You can create
subrouters combining any attribute matchers accepted by a route.
Subrouters can be used to create domain or path "namespaces": you define
subrouters in a central place and then parts of the app can register its
paths relatively to a given subrouter.
There's one more thing about subroutes. When a subrouter has a path prefix,
the inner routes use it as base for their paths:
r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()
// "/products/"
s.HandleFunc("/", ProductsHandler)
// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)
Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built,
or "reversed". We define a name calling Name() on a route. For example:
r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
Name("article")
To build a URL, get the route and call the URL() method, passing a sequence of
key/value pairs for the route variables. For the previous route, we would do:
url, err := r.Get("article").URL("category", "technology", "id", "42")
...and the result will be a url.URL with the following path:
"/articles/technology/42"
This also works for host variables:
r := mux.NewRouter()
r.Host("{subdomain}.domain.com").
Path("/articles/{category}/{id:[0-9]+}").
HandlerFunc(ArticleHandler).
Name("article")
// url.String() will be "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
"category", "technology",
"id", "42")
All variables defined in the route are required, and their values must
conform to the corresponding patterns. These requirements guarantee that a
generated URL will always match a registered route -- the only exception is
for explicitly defined "build-only" routes which never match.
Regex support also exists for matching Headers within a route. For example, we could do:
r.HeadersRegexp("Content-Type", "application/(text|json)")
...and the route will match both requests with a Content-Type of `application/json` as well as
`application/text`
There's also a way to build only the URL host or path for a route:
use the methods URLHost() or URLPath() instead. For the previous route,
we would do:
// "http://news.domain.com/"
host, err := r.Get("article").URLHost("subdomain", "news")
// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
And if you use subrouters, host and path defined separately can be built
as well:
r := mux.NewRouter()
s := r.Host("{subdomain}.domain.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
HandlerFunc(ArticleHandler).
Name("article")
// "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
"category", "technology",
"id", "42")
## Full Example
Here's a complete, runnable example of a small mux based server:
```go
package main
import (
"net/http"
"github.com/gorilla/mux"
)
func YourHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Gorilla!\n"))
}
func main() {
r := mux.NewRouter()
// Routes consist of a path and a handler function.
r.HandleFunc("/", YourHandler)
// Bind to a port and pass our router in
http.ListenAndServe(":8000", r)
}
```
## License
BSD licensed. See the LICENSE file for details.

View File

@ -60,8 +60,8 @@ Routes can also be restricted to a domain or subdomain. Just define a host
pattern to be matched. They can also have variables: pattern to be matched. They can also have variables:
r := mux.NewRouter() r := mux.NewRouter()
// Only matches if domain is "www.domain.com". // Only matches if domain is "www.example.com".
r.Host("www.domain.com") r.Host("www.example.com")
// Matches a dynamic subdomain. // Matches a dynamic subdomain.
r.Host("{subdomain:[a-z]+}.domain.com") r.Host("{subdomain:[a-z]+}.domain.com")
@ -94,7 +94,7 @@ There are several other matchers that can be added. To match path prefixes:
...and finally, it is possible to combine several matchers in a single route: ...and finally, it is possible to combine several matchers in a single route:
r.HandleFunc("/products", ProductsHandler). r.HandleFunc("/products", ProductsHandler).
Host("www.domain.com"). Host("www.example.com").
Methods("GET"). Methods("GET").
Schemes("http") Schemes("http")
@ -103,11 +103,11 @@ a way to group several routes that share the same requirements.
We call it "subrouting". We call it "subrouting".
For example, let's say we have several URLs that should only match when the For example, let's say we have several URLs that should only match when the
host is "www.domain.com". Create a route for that host and get a "subrouter" host is "www.example.com". Create a route for that host and get a "subrouter"
from it: from it:
r := mux.NewRouter() r := mux.NewRouter()
s := r.Host("www.domain.com").Subrouter() s := r.Host("www.example.com").Subrouter()
Then register routes in the subrouter: Then register routes in the subrouter:
@ -116,7 +116,7 @@ Then register routes in the subrouter:
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
The three URL paths we registered above will only be tested if the domain is The three URL paths we registered above will only be tested if the domain is
"www.domain.com", because the subrouter is tested first. This is not "www.example.com", because the subrouter is tested first. This is not
only convenient, but also optimizes request matching. You can create only convenient, but also optimizes request matching. You can create
subrouters combining any attribute matchers accepted by a route. subrouters combining any attribute matchers accepted by a route.

View File

@ -312,6 +312,10 @@ func Vars(r *http.Request) map[string]string {
} }
// CurrentRoute returns the matched route for the current request, if any. // CurrentRoute returns the matched route for the current request, if any.
// This only works when called inside the handler of the matched route
// because the matched route is stored in the request context which is cleared
// after the handler returns, unless the KeepContext option is set on the
// Router.
func CurrentRoute(r *http.Request) *Route { func CurrentRoute(r *http.Request) *Route {
if rv := context.Get(r, routeKey); rv != nil { if rv := context.Get(r, routeKey); rv != nil {
return rv.(*Route) return rv.(*Route)

View File

@ -7,11 +7,24 @@ package mux
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strings"
"testing" "testing"
"github.com/fsouza/go-dockerclient/external/github.com/gorilla/context" "github.com/fsouza/go-dockerclient/external/github.com/gorilla/context"
) )
func (r *Route) GoString() string {
matchers := make([]string, len(r.matchers))
for i, m := range r.matchers {
matchers[i] = fmt.Sprintf("%#v", m)
}
return fmt.Sprintf("&Route{matchers:[]matcher{%s}}", strings.Join(matchers, ", "))
}
func (r *routeRegexp) GoString() string {
return fmt.Sprintf("&routeRegexp{template: %q, matchHost: %t, matchQuery: %t, strictSlash: %t, regexp: regexp.MustCompile(%q), reverse: %q, varsN: %v, varsR: %v", r.template, r.matchHost, r.matchQuery, r.strictSlash, r.regexp.String(), r.reverse, r.varsN, r.varsR)
}
type routeTest struct { type routeTest struct {
title string // title of the test title string // title of the test
route *Route // the route being tested route *Route // the route being tested
@ -108,6 +121,15 @@ func TestHost(t *testing.T) {
path: "", path: "",
shouldMatch: true, shouldMatch: true,
}, },
{
title: "Host route with pattern, additional capturing group, match",
route: new(Route).Host("aaa.{v1:[a-z]{2}(b|c)}.ccc"),
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{"v1": "bbb"},
host: "aaa.bbb.ccc",
path: "",
shouldMatch: true,
},
{ {
title: "Host route with pattern, wrong host in request URL", title: "Host route with pattern, wrong host in request URL",
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
@ -135,6 +157,33 @@ func TestHost(t *testing.T) {
path: "", path: "",
shouldMatch: false, shouldMatch: false,
}, },
{
title: "Host route with hyphenated name and pattern, match",
route: new(Route).Host("aaa.{v-1:[a-z]{3}}.ccc"),
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{"v-1": "bbb"},
host: "aaa.bbb.ccc",
path: "",
shouldMatch: true,
},
{
title: "Host route with hyphenated name and pattern, additional capturing group, match",
route: new(Route).Host("aaa.{v-1:[a-z]{2}(b|c)}.ccc"),
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{"v-1": "bbb"},
host: "aaa.bbb.ccc",
path: "",
shouldMatch: true,
},
{
title: "Host route with multiple hyphenated names and patterns, match",
route: new(Route).Host("{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}"),
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"},
host: "aaa.bbb.ccc",
path: "",
shouldMatch: true,
},
{ {
title: "Path route with single pattern with pipe, match", title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"), route: new(Route).Path("/{category:a|b/c}"),
@ -260,6 +309,42 @@ func TestPath(t *testing.T) {
path: "/111/222/333", path: "/111/222/333",
shouldMatch: false, shouldMatch: false,
}, },
{
title: "Path route with multiple patterns with pipe, match",
route: new(Route).Path("/{category:a|(b/c)}/{product}/{id:[0-9]+}"),
request: newRequest("GET", "http://localhost/a/product_name/1"),
vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
host: "",
path: "/a/product_name/1",
shouldMatch: true,
},
{
title: "Path route with hyphenated name and pattern, match",
route: new(Route).Path("/111/{v-1:[0-9]{3}}/333"),
request: newRequest("GET", "http://localhost/111/222/333"),
vars: map[string]string{"v-1": "222"},
host: "",
path: "/111/222/333",
shouldMatch: true,
},
{
title: "Path route with multiple hyphenated names and patterns, match",
route: new(Route).Path("/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}"),
request: newRequest("GET", "http://localhost/111/222/333"),
vars: map[string]string{"v-1": "111", "v-2": "222", "v-3": "333"},
host: "",
path: "/111/222/333",
shouldMatch: true,
},
{
title: "Path route with multiple hyphenated names and patterns with pipe, match",
route: new(Route).Path("/{product-category:a|(b/c)}/{product-name}/{product-id:[0-9]+}"),
request: newRequest("GET", "http://localhost/a/product_name/1"),
vars: map[string]string{"product-category": "a", "product-name": "product_name", "product-id": "1"},
host: "",
path: "/a/product_name/1",
shouldMatch: true,
},
} }
for _, test := range tests { for _, test := range tests {
@ -597,6 +682,15 @@ func TestQueries(t *testing.T) {
path: "", path: "",
shouldMatch: false, shouldMatch: false,
}, },
{
title: "Queries route with regexp pattern with quantifier, additional capturing group",
route: new(Route).Queries("foo", "{v1:[0-9]{1}(a|b)}"),
request: newRequest("GET", "http://localhost?foo=1a"),
vars: map[string]string{"v1": "1a"},
host: "",
path: "",
shouldMatch: true,
},
{ {
title: "Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match", title: "Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match",
route: new(Route).Queries("foo", "{v1:[0-9]{1}}"), route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
@ -606,6 +700,42 @@ func TestQueries(t *testing.T) {
path: "", path: "",
shouldMatch: false, shouldMatch: false,
}, },
{
title: "Queries route with hyphenated name, match",
route: new(Route).Queries("foo", "{v-1}"),
request: newRequest("GET", "http://localhost?foo=bar"),
vars: map[string]string{"v-1": "bar"},
host: "",
path: "",
shouldMatch: true,
},
{
title: "Queries route with multiple hyphenated names, match",
route: new(Route).Queries("foo", "{v-1}", "baz", "{v-2}"),
request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
vars: map[string]string{"v-1": "bar", "v-2": "ding"},
host: "",
path: "",
shouldMatch: true,
},
{
title: "Queries route with hyphenate name and pattern, match",
route: new(Route).Queries("foo", "{v-1:[0-9]+}"),
request: newRequest("GET", "http://localhost?foo=10"),
vars: map[string]string{"v-1": "10"},
host: "",
path: "",
shouldMatch: true,
},
{
title: "Queries route with hyphenated name and pattern with quantifier, additional capturing group",
route: new(Route).Queries("foo", "{v-1:[0-9]{1}(a|b)}"),
request: newRequest("GET", "http://localhost?foo=1a"),
vars: map[string]string{"v-1": "1a"},
host: "",
path: "",
shouldMatch: true,
},
{ {
title: "Queries route with empty value, should match", title: "Queries route with empty value, should match",
route: new(Route).Queries("foo", ""), route: new(Route).Queries("foo", ""),
@ -660,6 +790,15 @@ func TestQueries(t *testing.T) {
path: "", path: "",
shouldMatch: true, shouldMatch: true,
}, },
{
title: "Queries route, bad submatch",
route: new(Route).Queries("foo", "bar", "baz", "ding"),
request: newRequest("GET", "http://localhost?fffoo=bar&baz=dingggg"),
vars: map[string]string{},
host: "",
path: "",
shouldMatch: false,
},
} }
for _, test := range tests { for _, test := range tests {

View File

@ -545,7 +545,7 @@ func TestMatchedRouteName(t *testing.T) {
router := NewRouter() router := NewRouter()
route := router.NewRoute().Path("/products/").Name(routeName) route := router.NewRoute().Path("/products/").Name(routeName)
url := "http://www.domain.com/products/" url := "http://www.example.com/products/"
request, _ := http.NewRequest("GET", url, nil) request, _ := http.NewRequest("GET", url, nil)
var rv RouteMatch var rv RouteMatch
ok := router.Match(request, &rv) ok := router.Match(request, &rv)
@ -563,10 +563,10 @@ func TestMatchedRouteName(t *testing.T) {
func TestSubRouting(t *testing.T) { func TestSubRouting(t *testing.T) {
// Example from docs. // Example from docs.
router := NewRouter() router := NewRouter()
subrouter := router.NewRoute().Host("www.domain.com").Subrouter() subrouter := router.NewRoute().Host("www.example.com").Subrouter()
route := subrouter.NewRoute().Path("/products/").Name("products") route := subrouter.NewRoute().Path("/products/").Name("products")
url := "http://www.domain.com/products/" url := "http://www.example.com/products/"
request, _ := http.NewRequest("GET", url, nil) request, _ := http.NewRequest("GET", url, nil)
var rv RouteMatch var rv RouteMatch
ok := router.Match(request, &rv) ok := router.Match(request, &rv)

View File

@ -10,6 +10,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
@ -72,13 +73,14 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
tpl[idxs[i]:end]) tpl[idxs[i]:end])
} }
// Build the regexp pattern. // Build the regexp pattern.
fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) varIdx := i / 2
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(varIdx), patt)
// Build the reverse template. // Build the reverse template.
fmt.Fprintf(reverse, "%s%%s", raw) fmt.Fprintf(reverse, "%s%%s", raw)
// Append variable name and compiled pattern. // Append variable name and compiled pattern.
varsN[i/2] = name varsN[varIdx] = name
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) varsR[varIdx], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -224,6 +226,11 @@ func braceIndices(s string) ([]int, error) {
return idxs, nil return idxs, nil
} }
// varGroupName builds a capturing group name for the indexed variable.
func varGroupName(idx int) string {
return "v" + strconv.Itoa(idx)
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// routeRegexpGroup // routeRegexpGroup
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -241,8 +248,13 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
if v.host != nil { if v.host != nil {
hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
if hostVars != nil { if hostVars != nil {
for k, v := range v.host.varsN { subexpNames := v.host.regexp.SubexpNames()
m.Vars[v] = hostVars[k+1] varName := 0
for i, name := range subexpNames[1:] {
if name != "" && name == varGroupName(varName) {
m.Vars[v.host.varsN[varName]] = hostVars[i+1]
varName++
}
} }
} }
} }
@ -250,8 +262,13 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
if v.path != nil { if v.path != nil {
pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
if pathVars != nil { if pathVars != nil {
for k, v := range v.path.varsN { subexpNames := v.path.regexp.SubexpNames()
m.Vars[v] = pathVars[k+1] varName := 0
for i, name := range subexpNames[1:] {
if name != "" && name == varGroupName(varName) {
m.Vars[v.path.varsN[varName]] = pathVars[i+1]
varName++
}
} }
// Check if we should redirect. // Check if we should redirect.
if v.path.strictSlash { if v.path.strictSlash {
@ -273,8 +290,13 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
for _, q := range v.queries { for _, q := range v.queries {
queryVars := q.regexp.FindStringSubmatch(q.getUrlQuery(req)) queryVars := q.regexp.FindStringSubmatch(q.getUrlQuery(req))
if queryVars != nil { if queryVars != nil {
for k, v := range q.varsN { subexpNames := q.regexp.SubexpNames()
m.Vars[v] = queryVars[k+1] varName := 0
for i, name := range subexpNames[1:] {
if name != "" && name == varGroupName(varName) {
m.Vars[q.varsN[varName]] = queryVars[i+1]
varName++
}
} }
} }
} }

View File

@ -255,7 +255,7 @@ func (r *Route) HeadersRegexp(pairs ...string) *Route {
// For example: // For example:
// //
// r := mux.NewRouter() // r := mux.NewRouter()
// r.Host("www.domain.com") // r.Host("www.example.com")
// r.Host("{subdomain}.domain.com") // r.Host("{subdomain}.domain.com")
// r.Host("{subdomain:[a-z]+}.domain.com") // r.Host("{subdomain:[a-z]+}.domain.com")
// //
@ -414,7 +414,7 @@ func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
// It will test the inner routes only if the parent route matched. For example: // It will test the inner routes only if the parent route matched. For example:
// //
// r := mux.NewRouter() // r := mux.NewRouter()
// s := r.Host("www.domain.com").Subrouter() // s := r.Host("www.example.com").Subrouter()
// s.HandleFunc("/products/", ProductsHandler) // s.HandleFunc("/products/", ProductsHandler)
// s.HandleFunc("/products/{key}", ProductHandler) // s.HandleFunc("/products/{key}", ProductHandler)
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) // s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)

View File

@ -0,0 +1,127 @@
// Copyright 2015 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"encoding/json"
"errors"
"fmt"
"net/http"
)
// ErrNetworkAlreadyExists is the error returned by CreateNetwork when the
// network already exists.
var ErrNetworkAlreadyExists = errors.New("network already exists")
// Network represents a network.
//
// See https://goo.gl/FDkCdQ for more details.
type Network struct {
Name string `json:"name"`
ID string `json:"id"`
Type string `json:"type"`
Endpoints []*Endpoint `json:"endpoints"`
}
// Endpoint represents an endpoint.
//
// See https://goo.gl/FDkCdQ for more details.
type Endpoint struct {
Name string `json:"name"`
ID string `json:"id"`
Network string `json:"network"`
}
// ListNetworks returns all networks.
//
// See https://goo.gl/4hCNtZ for more details.
func (c *Client) ListNetworks() ([]Network, error) {
body, _, err := c.do("GET", "/networks", doOptions{})
if err != nil {
return nil, err
}
var networks []Network
if err := json.Unmarshal(body, &networks); err != nil {
return nil, err
}
return networks, nil
}
// NetworkInfo returns information about a network by its ID.
//
// See https://goo.gl/4hCNtZ for more details.
func (c *Client) NetworkInfo(id string) (*Network, error) {
path := "/networks/" + id
body, status, err := c.do("GET", path, doOptions{})
if status == http.StatusNotFound {
return nil, &NoSuchNetwork{ID: id}
}
if err != nil {
return nil, err
}
var network Network
if err := json.Unmarshal(body, &network); err != nil {
return nil, err
}
return &network, nil
}
// CreateNetworkOptions specify parameters to the CreateNetwork function and
// (for now) is the expected body of the "create network" http request message
//
// See https://goo.gl/FDkCdQ for more details.
type CreateNetworkOptions struct {
Name string `json:"name"`
NetworkType string `json:"network_type"`
Options map[string]interface{} `json:"options"`
}
// CreateNetwork creates a new network, returning the network instance,
// or an error in case of failure.
//
// See http://goo.gl/mErxNp for more details.
func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) {
body, status, err := c.do(
"POST",
"/networks",
doOptions{
data: opts,
},
)
if status == http.StatusConflict {
return nil, ErrNetworkAlreadyExists
}
if err != nil {
return nil, err
}
type createNetworkResponse struct {
ID string
}
var (
network Network
resp createNetworkResponse
)
err = json.Unmarshal(body, &resp)
if err != nil {
return nil, err
}
network.Name = opts.Name
network.ID = resp.ID
network.Type = opts.NetworkType
return &network, nil
}
// NoSuchNetwork is the error returned when a given network does not exist.
type NoSuchNetwork struct {
ID string
}
func (err *NoSuchNetwork) Error() string {
return fmt.Sprintf("No such network: %s", err.ID)
}

View File

@ -0,0 +1,96 @@
// Copyright 2015 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"encoding/json"
"net/http"
"net/url"
"reflect"
"testing"
)
func TestListNetworks(t *testing.T) {
jsonNetworks := `[
{
"ID": "8dfafdbc3a40",
"Name": "blah",
"Type": "bridge",
"Endpoints":[{"ID": "918c11c8288a", "Name": "dsafdsaf", "Network": "8dfafdbc3a40"}]
},
{
"ID": "9fb1e39c",
"Name": "foo",
"Type": "bridge",
"Endpoints":[{"ID": "c080be979dda", "Name": "lllll2222", "Network": "9fb1e39c"}]
}
]`
var expected []Network
err := json.Unmarshal([]byte(jsonNetworks), &expected)
if err != nil {
t.Fatal(err)
}
client := newTestClient(&FakeRoundTripper{message: jsonNetworks, status: http.StatusOK})
containers, err := client.ListNetworks()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(containers, expected) {
t.Errorf("ListNetworks: Expected %#v. Got %#v.", expected, containers)
}
}
func TestNetworkInfo(t *testing.T) {
jsonNetwork := `{
"ID": "8dfafdbc3a40",
"Name": "blah",
"Type": "bridge",
"Endpoints":[{"ID": "918c11c8288a", "Name": "dsafdsaf", "Network": "8dfafdbc3a40"}]
}`
var expected Network
err := json.Unmarshal([]byte(jsonNetwork), &expected)
if err != nil {
t.Fatal(err)
}
fakeRT := &FakeRoundTripper{message: jsonNetwork, status: http.StatusOK}
client := newTestClient(fakeRT)
id := "8dfafdbc3a40"
network, err := client.NetworkInfo(id)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*network, expected) {
t.Errorf("NetworkInfo(%q): Expected %#v. Got %#v.", id, expected, network)
}
expectedURL, _ := url.Parse(client.getURL("/networks/8dfafdbc3a40"))
if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path {
t.Errorf("NetworkInfo(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
}
}
func TestNetworkCreate(t *testing.T) {
jsonID := `{"ID": "8dfafdbc3a40"}`
jsonNetwork := `{
"ID": "8dfafdbc3a40",
"Name": "foobar",
"Type": "bridge"
}`
var expected Network
err := json.Unmarshal([]byte(jsonNetwork), &expected)
if err != nil {
t.Fatal(err)
}
client := newTestClient(&FakeRoundTripper{message: jsonID, status: http.StatusOK})
opts := CreateNetworkOptions{"foobar", "bridge", nil}
network, err := client.CreateNetwork(opts)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*network, expected) {
t.Errorf("CreateNetwork: Expected %#v. Got %#v.", expected, network)
}
}

View File

@ -43,6 +43,8 @@ type DockerServer struct {
images []docker.Image images []docker.Image
iMut sync.RWMutex iMut sync.RWMutex
imgIDs map[string]string imgIDs map[string]string
networks []*docker.Network
netMut sync.RWMutex
listener net.Listener listener net.Listener
mux *mux.Router mux *mux.Router
hook func(*http.Request) hook func(*http.Request)
@ -124,6 +126,9 @@ func (s *DockerServer) buildMuxer() {
s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker)) 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/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage))
s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage)) s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage))
s.mux.Path("/networks").Methods("GET").HandlerFunc(s.handlerWrapper(s.listNetworks))
s.mux.Path("/networks/{id:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.networkInfo))
s.mux.Path("/networks").Methods("POST").HandlerFunc(s.handlerWrapper(s.createNetwork))
} }
// SetHook changes the hook function used by the server. // SetHook changes the hook function used by the server.
@ -981,3 +986,77 @@ func (s *DockerServer) getExec(id string) (*docker.ExecInspect, error) {
} }
return nil, errors.New("exec not found") return nil, errors.New("exec not found")
} }
func (s *DockerServer) findNetwork(idOrName string) (*docker.Network, int, error) {
s.netMut.RLock()
defer s.netMut.RUnlock()
for i, network := range s.networks {
if network.ID == idOrName || network.Name == idOrName {
return network, i, nil
}
}
return nil, -1, errors.New("No such network")
}
func (s *DockerServer) listNetworks(w http.ResponseWriter, r *http.Request) {
s.netMut.RLock()
result := make([]docker.Network, 0, len(s.networks))
for _, network := range s.networks {
result = append(result, *network)
}
s.netMut.RUnlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(result)
}
func (s *DockerServer) networkInfo(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
network, _, err := s.findNetwork(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(network)
}
// isValidName validates configuration objects supported by libnetwork
func isValidName(name string) bool {
if name == "" || strings.Contains(name, ".") {
return false
}
return true
}
func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) {
var config *docker.CreateNetworkOptions
defer r.Body.Close()
err := json.NewDecoder(r.Body).Decode(&config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if !isValidName(config.Name) {
http.Error(w, "Invalid network name", http.StatusBadRequest)
return
}
if n, _, _ := s.findNetwork(config.Name); n != nil {
http.Error(w, "network already exists", http.StatusForbidden)
return
}
generatedID := s.generateID()
network := docker.Network{
Name: config.Name,
ID: generatedID,
Type: config.NetworkType,
}
s.netMut.Lock()
s.networks = append(s.networks, &network)
s.netMut.Unlock()
w.WriteHeader(http.StatusCreated)
var c = struct{ ID string }{ID: network.ID}
json.NewEncoder(w).Encode(c)
}

View File

@ -1679,3 +1679,106 @@ func TestStatsContainerStream(t *testing.T) {
t.Errorf("StatsContainer: wrong value. Want %#v. Got %#v.", expected, got) t.Errorf("StatsContainer: wrong value. Want %#v. Got %#v.", expected, got)
} }
} }
func addNetworks(server *DockerServer, n int) {
server.netMut.Lock()
defer server.netMut.Unlock()
for i := 0; i < n; i++ {
netid := fmt.Sprintf("%x", rand.Int()%10000)
network := docker.Network{
Name: netid,
ID: fmt.Sprintf("%x", rand.Int()%10000),
Type: "bridge",
Endpoints: []*docker.Endpoint{
&docker.Endpoint{
Name: "blah",
ID: fmt.Sprintf("%x", rand.Int()%10000),
Network: netid,
},
},
}
server.networks = append(server.networks, &network)
}
}
func TestListNetworks(t *testing.T) {
server := DockerServer{}
addNetworks(&server, 2)
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/networks", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("ListNetworks: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
expected := make([]docker.Network, 2)
for i, network := range server.networks {
expected[i] = docker.Network{
ID: network.ID,
Name: network.Name,
Type: network.Type,
Endpoints: network.Endpoints,
}
}
var got []docker.Network
err := json.NewDecoder(recorder.Body).Decode(&got)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, expected) {
t.Errorf("ListNetworks. Want %#v. Got %#v.", expected, got)
}
}
type createNetworkResponse struct {
ID string `json:"ID"`
}
func TestCreateNetwork(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
netid := fmt.Sprintf("%x", rand.Int()%10000)
netname := fmt.Sprintf("%x", rand.Int()%10000)
body := fmt.Sprintf(`{"ID": "%s", "Name": "%s", "Type": "bridge" }`, netid, netname)
request, _ := http.NewRequest("POST", "/networks", strings.NewReader(body))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated {
t.Errorf("CreateNetwork: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code)
}
var returned createNetworkResponse
err := json.NewDecoder(recorder.Body).Decode(&returned)
if err != nil {
t.Fatal(err)
}
stored := server.networks[0]
if returned.ID != stored.ID {
t.Errorf("CreateNetwork: ID mismatch. Stored: %q. Returned: %q.", stored.ID, returned)
}
}
func TestCreateNetworkInvalidBody(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("POST", "/networks", strings.NewReader("whaaaaaat---"))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Errorf("CreateNetwork: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
}
}
func TestCreateNetworkDuplicateName(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
addNetworks(&server, 1)
server.networks[0].Name = "mynetwork"
recorder := httptest.NewRecorder()
body := fmt.Sprintf(`{"ID": "%s", "Name": "mynetwork", "Type": "bridge" }`, fmt.Sprintf("%x", rand.Int()%10000))
request, _ := http.NewRequest("POST", "/networks", strings.NewReader(body))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusForbidden {
t.Errorf("CreateNetwork: wrong status. Want %d. Got %d.", http.StatusForbidden, recorder.Code)
}
}