From 44005e59d430258ae0c99afe5cbfb1a5743c66f4 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 1 Mar 2016 20:16:40 -0500 Subject: [PATCH] Call plugins with custom transports. Small refactor to be able to use custom transports to call remote plugins. Signed-off-by: David Calavera --- authorization/authz_unix_test.go | 3 +- plugins/client.go | 56 ++++++++++++++++++++++---------- plugins/client_test.go | 15 ++++++--- plugins/transport/http.go | 36 ++++++++++++++++++++ plugins/transport/transport.go | 36 ++++++++++++++++++++ 5 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 plugins/transport/http.go create mode 100644 plugins/transport/transport.go diff --git a/authorization/authz_unix_test.go b/authorization/authz_unix_test.go index b79e3f2..7d673fe 100644 --- a/authorization/authz_unix_test.go +++ b/authorization/authz_unix_test.go @@ -18,10 +18,11 @@ import ( "testing" "bytes" + "strings" + "github.com/docker/docker/pkg/plugins" "github.com/docker/go-connections/tlsconfig" "github.com/gorilla/mux" - "strings" ) const pluginAddress = "authzplugin.sock" diff --git a/plugins/client.go b/plugins/client.go index 85dbb80..b54d6f2 100644 --- a/plugins/client.go +++ b/plugins/client.go @@ -6,17 +6,17 @@ import ( "io" "io/ioutil" "net/http" - "strings" + "net/url" "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/plugins/transport" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" ) const ( - versionMimetype = "application/vnd.docker.plugins.v1.2+json" - defaultTimeOut = 30 + defaultTimeOut = 30 ) // NewClient creates a new plugin client (http). @@ -29,23 +29,38 @@ func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) { } tr.TLSClientConfig = c - protoAndAddr := strings.Split(addr, "://") - if err := sockets.ConfigureTransport(tr, protoAndAddr[0], protoAndAddr[1]); err != nil { + u, err := url.Parse(addr) + if err != nil { return nil, err } - - scheme := protoAndAddr[0] - if scheme != "https" { - scheme = "http" + socket := u.Host + if socket == "" { + // valid local socket addresses have the host empty. + socket = u.Path + } + if err := sockets.ConfigureTransport(tr, u.Scheme, socket); err != nil { + return nil, err + } + scheme := httpScheme(u) + + clientTransport := transport.NewHTTPTransport(tr, scheme, socket) + return NewClientWithTransport(clientTransport), nil +} + +// NewClientWithTransport creates a new plugin client with a given transport. +func NewClientWithTransport(tr transport.Transport) *Client { + return &Client{ + http: &http.Client{ + Transport: tr, + }, + requestFactory: tr, } - return &Client{&http.Client{Transport: tr}, scheme, protoAndAddr[1]}, nil } // Client represents a plugin client. type Client struct { - http *http.Client // http client to use - scheme string // scheme protocol of the plugin - addr string // http address of the plugin + http *http.Client // http client to use + requestFactory transport.RequestFactory } // Call calls the specified method with the specified arguments for the plugin. @@ -94,13 +109,10 @@ func (c *Client) SendFile(serviceMethod string, data io.Reader, ret interface{}) } func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) (io.ReadCloser, error) { - req, err := http.NewRequest("POST", "/"+serviceMethod, data) + req, err := c.requestFactory.NewRequest(serviceMethod, data) if err != nil { return nil, err } - req.Header.Add("Accept", versionMimetype) - req.URL.Scheme = c.scheme - req.URL.Host = c.addr var retries int start := time.Now() @@ -117,7 +129,7 @@ func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) return nil, err } retries++ - logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff) + logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", req.URL, timeOff) time.Sleep(timeOff) continue } @@ -163,3 +175,11 @@ func backoff(retries int) time.Duration { func abort(start time.Time, timeOff time.Duration) bool { return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second } + +func httpScheme(u *url.URL) string { + scheme := u.Scheme + if scheme != "https" { + scheme = "http" + } + return scheme +} diff --git a/plugins/client_test.go b/plugins/client_test.go index d9e14e2..3fa2ff4 100644 --- a/plugins/client_test.go +++ b/plugins/client_test.go @@ -4,10 +4,12 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "reflect" "testing" "time" + "github.com/docker/docker/pkg/plugins/transport" "github.com/docker/go-connections/tlsconfig" ) @@ -48,7 +50,7 @@ func TestEchoInputOutput(t *testing.T) { } header := w.Header() - header.Set("Content-Type", versionMimetype) + header.Set("Content-Type", transport.VersionMimetype) io.Copy(w, r.Body) }) @@ -119,9 +121,14 @@ func TestClientScheme(t *testing.T) { } for addr, scheme := range cases { - c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true}) - if c.scheme != scheme { - t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, c.scheme) + u, err := url.Parse(addr) + if err != nil { + t.Fatal(err) + } + s := httpScheme(u) + + if s != scheme { + t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, s) } } } diff --git a/plugins/transport/http.go b/plugins/transport/http.go new file mode 100644 index 0000000..5be146a --- /dev/null +++ b/plugins/transport/http.go @@ -0,0 +1,36 @@ +package transport + +import ( + "io" + "net/http" +) + +// httpTransport holds an http.RoundTripper +// and information about the scheme and address the transport +// sends request to. +type httpTransport struct { + http.RoundTripper + scheme string + addr string +} + +// NewHTTPTransport creates a new httpTransport. +func NewHTTPTransport(r http.RoundTripper, scheme, addr string) Transport { + return httpTransport{ + RoundTripper: r, + scheme: scheme, + addr: addr, + } +} + +// NewRequest creates a new http.Request and sets the URL +// scheme and address with the transport's fields. +func (t httpTransport) NewRequest(path string, data io.Reader) (*http.Request, error) { + req, err := newHTTPRequest(path, data) + if err != nil { + return nil, err + } + req.URL.Scheme = t.scheme + req.URL.Host = t.addr + return req, nil +} diff --git a/plugins/transport/transport.go b/plugins/transport/transport.go new file mode 100644 index 0000000..d7f1e21 --- /dev/null +++ b/plugins/transport/transport.go @@ -0,0 +1,36 @@ +package transport + +import ( + "io" + "net/http" + "strings" +) + +// VersionMimetype is the Content-Type the engine sends to plugins. +const VersionMimetype = "application/vnd.docker.plugins.v1.2+json" + +// RequestFactory defines an interface that +// transports can implement to create new requests. +type RequestFactory interface { + NewRequest(path string, data io.Reader) (*http.Request, error) +} + +// Transport defines an interface that plugin transports +// must implement. +type Transport interface { + http.RoundTripper + RequestFactory +} + +// newHTTPRequest creates a new request with a path and a body. +func newHTTPRequest(path string, data io.Reader) (*http.Request, error) { + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + req, err := http.NewRequest("POST", path, data) + if err != nil { + return nil, err + } + req.Header.Add("Accept", VersionMimetype) + return req, nil +}