Browse code

Call plugins with custom transports.

Small refactor to be able to use custom transports
to call remote plugins.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2016/03/02 10:16:40
Showing 5 changed files
... ...
@@ -18,10 +18,11 @@ import (
18 18
 	"testing"
19 19
 
20 20
 	"bytes"
21
+	"strings"
22
+
21 23
 	"github.com/docker/docker/pkg/plugins"
22 24
 	"github.com/docker/go-connections/tlsconfig"
23 25
 	"github.com/gorilla/mux"
24
-	"strings"
25 26
 )
26 27
 
27 28
 const pluginAddress = "authzplugin.sock"
... ...
@@ -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
+}