Small refactor to be able to use custom transports
to call remote plugins.
Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -6,17 +6,17 @@ import ( |
| 6 | 6 |
"io" |
| 7 | 7 |
"io/ioutil" |
| 8 | 8 |
"net/http" |
| 9 |
- "strings" |
|
| 9 |
+ "net/url" |
|
| 10 | 10 |
"time" |
| 11 | 11 |
|
| 12 | 12 |
"github.com/Sirupsen/logrus" |
| 13 |
+ "github.com/docker/docker/pkg/plugins/transport" |
|
| 13 | 14 |
"github.com/docker/go-connections/sockets" |
| 14 | 15 |
"github.com/docker/go-connections/tlsconfig" |
| 15 | 16 |
) |
| 16 | 17 |
|
| 17 | 18 |
const ( |
| 18 |
- versionMimetype = "application/vnd.docker.plugins.v1.2+json" |
|
| 19 |
- defaultTimeOut = 30 |
|
| 19 |
+ defaultTimeOut = 30 |
|
| 20 | 20 |
) |
| 21 | 21 |
|
| 22 | 22 |
// NewClient creates a new plugin client (http). |
| ... | ... |
@@ -29,23 +29,38 @@ func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
|
| 29 | 29 |
} |
| 30 | 30 |
tr.TLSClientConfig = c |
| 31 | 31 |
|
| 32 |
- protoAndAddr := strings.Split(addr, "://") |
|
| 33 |
- if err := sockets.ConfigureTransport(tr, protoAndAddr[0], protoAndAddr[1]); err != nil {
|
|
| 32 |
+ u, err := url.Parse(addr) |
|
| 33 |
+ if err != nil {
|
|
| 34 |
+ return nil, err |
|
| 35 |
+ } |
|
| 36 |
+ socket := u.Host |
|
| 37 |
+ if socket == "" {
|
|
| 38 |
+ // valid local socket addresses have the host empty. |
|
| 39 |
+ socket = u.Path |
|
| 40 |
+ } |
|
| 41 |
+ if err := sockets.ConfigureTransport(tr, u.Scheme, socket); err != nil {
|
|
| 34 | 42 |
return nil, err |
| 35 | 43 |
} |
| 44 |
+ scheme := httpScheme(u) |
|
| 36 | 45 |
|
| 37 |
- scheme := protoAndAddr[0] |
|
| 38 |
- if scheme != "https" {
|
|
| 39 |
- scheme = "http" |
|
| 46 |
+ clientTransport := transport.NewHTTPTransport(tr, scheme, socket) |
|
| 47 |
+ return NewClientWithTransport(clientTransport), nil |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+// NewClientWithTransport creates a new plugin client with a given transport. |
|
| 51 |
+func NewClientWithTransport(tr transport.Transport) *Client {
|
|
| 52 |
+ return &Client{
|
|
| 53 |
+ http: &http.Client{
|
|
| 54 |
+ Transport: tr, |
|
| 55 |
+ }, |
|
| 56 |
+ requestFactory: tr, |
|
| 40 | 57 |
} |
| 41 |
- return &Client{&http.Client{Transport: tr}, scheme, protoAndAddr[1]}, nil
|
|
| 42 | 58 |
} |
| 43 | 59 |
|
| 44 | 60 |
// Client represents a plugin client. |
| 45 | 61 |
type Client struct {
|
| 46 |
- http *http.Client // http client to use |
|
| 47 |
- scheme string // scheme protocol of the plugin |
|
| 48 |
- addr string // http address of the plugin |
|
| 62 |
+ http *http.Client // http client to use |
|
| 63 |
+ requestFactory transport.RequestFactory |
|
| 49 | 64 |
} |
| 50 | 65 |
|
| 51 | 66 |
// 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{})
|
| 94 | 94 |
} |
| 95 | 95 |
|
| 96 | 96 |
func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) (io.ReadCloser, error) {
|
| 97 |
- req, err := http.NewRequest("POST", "/"+serviceMethod, data)
|
|
| 97 |
+ req, err := c.requestFactory.NewRequest(serviceMethod, data) |
|
| 98 | 98 |
if err != nil {
|
| 99 | 99 |
return nil, err |
| 100 | 100 |
} |
| 101 |
- req.Header.Add("Accept", versionMimetype)
|
|
| 102 |
- req.URL.Scheme = c.scheme |
|
| 103 |
- req.URL.Host = c.addr |
|
| 104 | 101 |
|
| 105 | 102 |
var retries int |
| 106 | 103 |
start := time.Now() |
| ... | ... |
@@ -117,7 +129,7 @@ func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) |
| 117 | 117 |
return nil, err |
| 118 | 118 |
} |
| 119 | 119 |
retries++ |
| 120 |
- logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
|
|
| 120 |
+ logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", req.URL, timeOff)
|
|
| 121 | 121 |
time.Sleep(timeOff) |
| 122 | 122 |
continue |
| 123 | 123 |
} |
| ... | ... |
@@ -163,3 +175,11 @@ func backoff(retries int) time.Duration {
|
| 163 | 163 |
func abort(start time.Time, timeOff time.Duration) bool {
|
| 164 | 164 |
return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second |
| 165 | 165 |
} |
| 166 |
+ |
|
| 167 |
+func httpScheme(u *url.URL) string {
|
|
| 168 |
+ scheme := u.Scheme |
|
| 169 |
+ if scheme != "https" {
|
|
| 170 |
+ scheme = "http" |
|
| 171 |
+ } |
|
| 172 |
+ return scheme |
|
| 173 |
+} |
| ... | ... |
@@ -4,10 +4,12 @@ import ( |
| 4 | 4 |
"io" |
| 5 | 5 |
"net/http" |
| 6 | 6 |
"net/http/httptest" |
| 7 |
+ "net/url" |
|
| 7 | 8 |
"reflect" |
| 8 | 9 |
"testing" |
| 9 | 10 |
"time" |
| 10 | 11 |
|
| 12 |
+ "github.com/docker/docker/pkg/plugins/transport" |
|
| 11 | 13 |
"github.com/docker/go-connections/tlsconfig" |
| 12 | 14 |
) |
| 13 | 15 |
|
| ... | ... |
@@ -48,7 +50,7 @@ func TestEchoInputOutput(t *testing.T) {
|
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 | 50 |
header := w.Header() |
| 51 |
- header.Set("Content-Type", versionMimetype)
|
|
| 51 |
+ header.Set("Content-Type", transport.VersionMimetype)
|
|
| 52 | 52 |
|
| 53 | 53 |
io.Copy(w, r.Body) |
| 54 | 54 |
}) |
| ... | ... |
@@ -119,9 +121,14 @@ func TestClientScheme(t *testing.T) {
|
| 119 | 119 |
} |
| 120 | 120 |
|
| 121 | 121 |
for addr, scheme := range cases {
|
| 122 |
- c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
|
|
| 123 |
- if c.scheme != scheme {
|
|
| 124 |
- t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, c.scheme)
|
|
| 122 |
+ u, err := url.Parse(addr) |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ t.Fatal(err) |
|
| 125 |
+ } |
|
| 126 |
+ s := httpScheme(u) |
|
| 127 |
+ |
|
| 128 |
+ if s != scheme {
|
|
| 129 |
+ t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, s)
|
|
| 125 | 130 |
} |
| 126 | 131 |
} |
| 127 | 132 |
} |
| 128 | 133 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,36 @@ |
| 0 |
+package transport |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "net/http" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// httpTransport holds an http.RoundTripper |
|
| 8 |
+// and information about the scheme and address the transport |
|
| 9 |
+// sends request to. |
|
| 10 |
+type httpTransport struct {
|
|
| 11 |
+ http.RoundTripper |
|
| 12 |
+ scheme string |
|
| 13 |
+ addr string |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+// NewHTTPTransport creates a new httpTransport. |
|
| 17 |
+func NewHTTPTransport(r http.RoundTripper, scheme, addr string) Transport {
|
|
| 18 |
+ return httpTransport{
|
|
| 19 |
+ RoundTripper: r, |
|
| 20 |
+ scheme: scheme, |
|
| 21 |
+ addr: addr, |
|
| 22 |
+ } |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// NewRequest creates a new http.Request and sets the URL |
|
| 26 |
+// scheme and address with the transport's fields. |
|
| 27 |
+func (t httpTransport) NewRequest(path string, data io.Reader) (*http.Request, error) {
|
|
| 28 |
+ req, err := newHTTPRequest(path, data) |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ return nil, err |
|
| 31 |
+ } |
|
| 32 |
+ req.URL.Scheme = t.scheme |
|
| 33 |
+ req.URL.Host = t.addr |
|
| 34 |
+ return req, nil |
|
| 35 |
+} |
| 0 | 36 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,36 @@ |
| 0 |
+package transport |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "net/http" |
|
| 5 |
+ "strings" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// VersionMimetype is the Content-Type the engine sends to plugins. |
|
| 9 |
+const VersionMimetype = "application/vnd.docker.plugins.v1.2+json" |
|
| 10 |
+ |
|
| 11 |
+// RequestFactory defines an interface that |
|
| 12 |
+// transports can implement to create new requests. |
|
| 13 |
+type RequestFactory interface {
|
|
| 14 |
+ NewRequest(path string, data io.Reader) (*http.Request, error) |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+// Transport defines an interface that plugin transports |
|
| 18 |
+// must implement. |
|
| 19 |
+type Transport interface {
|
|
| 20 |
+ http.RoundTripper |
|
| 21 |
+ RequestFactory |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+// newHTTPRequest creates a new request with a path and a body. |
|
| 25 |
+func newHTTPRequest(path string, data io.Reader) (*http.Request, error) {
|
|
| 26 |
+ if !strings.HasPrefix(path, "/") {
|
|
| 27 |
+ path = "/" + path |
|
| 28 |
+ } |
|
| 29 |
+ req, err := http.NewRequest("POST", path, data)
|
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ return nil, err |
|
| 32 |
+ } |
|
| 33 |
+ req.Header.Add("Accept", VersionMimetype)
|
|
| 34 |
+ return req, nil |
|
| 35 |
+} |