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",
"Rev": "933433faa3e1c0bbc825b251143f8e77affbf797"
"Rev": "42d06e2b125654477366c320dcea99107a86e9c2"
},
{
"ImportPath": "github.com/garyburd/redigo/internal",

View File

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

View File

@ -8,6 +8,7 @@ import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
@ -15,6 +16,8 @@ import (
"strings"
)
var AuthParseError error = errors.New("Failed to read authentication from dockercfg")
// AuthConfiguration represents authentication options to use in the PushImage
// method. It represents the authentication in the Docker index server.
type AuthConfiguration struct {
@ -99,6 +102,9 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
return nil, err
}
userpass := strings.Split(string(data), ":")
if len(userpass) != 2 {
return nil, AuthParseError
}
c.Configs[reg] = AuthConfiguration{
Email: conf.Email,
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) {
auth := base64.StdEncoding.EncodeToString([]byte("user:pass"))
read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{"auth":"%s","email":"user@example.com"}}}`, auth))

View File

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

View File

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

View File

@ -128,13 +128,23 @@ type PortMapping map[string]string
// NetworkSettings contains network-related information about a container
type NetworkSettings struct {
IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"`
IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"`
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"`
Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"`
PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"`
Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"`
IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"`
IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"`
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"`
Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"`
PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"`
Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"`
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
@ -726,6 +736,18 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
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)
stats := new(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
stats = new(Stats)
select {
case <-opts.Done:
readCloser.Close()
default:
// Continue
}
}
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
===
[![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)
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:
r := mux.NewRouter()
// Only matches if domain is "www.domain.com".
r.Host("www.domain.com")
// Only matches if domain is "www.example.com".
r.Host("www.example.com")
// Matches a dynamic subdomain.
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:
r.HandleFunc("/products", ProductsHandler).
Host("www.domain.com").
Host("www.example.com").
Methods("GET").
Schemes("http")
@ -103,11 +103,11 @@ 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.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:
r := mux.NewRouter()
s := r.Host("www.domain.com").Subrouter()
s := r.Host("www.example.com").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)
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
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.
// 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 {
if rv := context.Get(r, routeKey); rv != nil {
return rv.(*Route)

View File

@ -7,11 +7,24 @@ package mux
import (
"fmt"
"net/http"
"strings"
"testing"
"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 {
title string // title of the test
route *Route // the route being tested
@ -108,6 +121,15 @@ func TestHost(t *testing.T) {
path: "",
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",
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
@ -135,6 +157,33 @@ func TestHost(t *testing.T) {
path: "",
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",
route: new(Route).Path("/{category:a|b/c}"),
@ -260,6 +309,42 @@ func TestPath(t *testing.T) {
path: "/111/222/333",
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 {
@ -597,6 +682,15 @@ func TestQueries(t *testing.T) {
path: "",
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",
route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
@ -606,6 +700,42 @@ func TestQueries(t *testing.T) {
path: "",
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",
route: new(Route).Queries("foo", ""),
@ -660,6 +790,15 @@ func TestQueries(t *testing.T) {
path: "",
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 {

View File

@ -545,7 +545,7 @@ func TestMatchedRouteName(t *testing.T) {
router := NewRouter()
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)
var rv RouteMatch
ok := router.Match(request, &rv)
@ -563,10 +563,10 @@ func TestMatchedRouteName(t *testing.T) {
func TestSubRouting(t *testing.T) {
// Example from docs.
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")
url := "http://www.domain.com/products/"
url := "http://www.example.com/products/"
request, _ := http.NewRequest("GET", url, nil)
var rv RouteMatch
ok := router.Match(request, &rv)

View File

@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
)
@ -72,13 +73,14 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
tpl[idxs[i]:end])
}
// 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.
fmt.Fprintf(reverse, "%s%%s", raw)
// Append variable name and compiled pattern.
varsN[i/2] = name
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
varsN[varIdx] = name
varsR[varIdx], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
if err != nil {
return nil, err
}
@ -224,6 +226,11 @@ func braceIndices(s string) ([]int, error) {
return idxs, nil
}
// varGroupName builds a capturing group name for the indexed variable.
func varGroupName(idx int) string {
return "v" + strconv.Itoa(idx)
}
// ----------------------------------------------------------------------------
// routeRegexpGroup
// ----------------------------------------------------------------------------
@ -241,8 +248,13 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
if v.host != nil {
hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
if hostVars != nil {
for k, v := range v.host.varsN {
m.Vars[v] = hostVars[k+1]
subexpNames := v.host.regexp.SubexpNames()
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 {
pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
if pathVars != nil {
for k, v := range v.path.varsN {
m.Vars[v] = pathVars[k+1]
subexpNames := v.path.regexp.SubexpNames()
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.
if v.path.strictSlash {
@ -273,8 +290,13 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
for _, q := range v.queries {
queryVars := q.regexp.FindStringSubmatch(q.getUrlQuery(req))
if queryVars != nil {
for k, v := range q.varsN {
m.Vars[v] = queryVars[k+1]
subexpNames := q.regexp.SubexpNames()
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:
//
// r := mux.NewRouter()
// r.Host("www.domain.com")
// r.Host("www.example.com")
// r.Host("{subdomain}.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:
//
// r := mux.NewRouter()
// s := r.Host("www.domain.com").Subrouter()
// s := r.Host("www.example.com").Subrouter()
// s.HandleFunc("/products/", ProductsHandler)
// s.HandleFunc("/products/{key}", ProductHandler)
// 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
iMut sync.RWMutex
imgIDs map[string]string
networks []*docker.Network
netMut sync.RWMutex
listener net.Listener
mux *mux.Router
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("/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("/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.
@ -981,3 +986,77 @@ func (s *DockerServer) getExec(id string) (*docker.ExecInspect, error) {
}
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)
}
}
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)
}
}