Forráskód Böngészése

Merge pull request #13524 from calavera/plugin_json_spec

Plugins JSON spec.
Brian Goff 10 éve
szülő
commit
f13b40f6e7

+ 2 - 2
api/client/cli.go

@@ -15,8 +15,8 @@ import (
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/pkg/homedir"
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/sockets"
 	"github.com/docker/docker/pkg/term"
-	"github.com/docker/docker/utils"
 )
 
 // DockerCli represents the docker command line client.
@@ -210,7 +210,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a
 	tr := &http.Transport{
 		TLSClientConfig: tlsConfig,
 	}
-	utils.ConfigureTCPTransport(tr, proto, addr)
+	sockets.ConfigureTCPTransport(tr, proto, addr)
 
 	configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker"))
 	if e != nil {

+ 20 - 3
experimental/plugin_api.md

@@ -26,18 +26,35 @@ containers is recommended.
 Docker discovers plugins by looking for them in the plugin directory whenever a
 user or container tries to use one by name.
 
-There are two types of files which can be put in the plugin directory.
+There are three types of files which can be put in the plugin directory.
 
 * `.sock` files are UNIX domain sockets.
 * `.spec` files are text files containing a URL, such as `unix:///other.sock`.
+* `.json` files are text files containing a full json specification for the plugin.
 
 The name of the file (excluding the extension) determines the plugin name.
 
 For example, the `flocker` plugin might create a UNIX socket at
 `/usr/share/docker/plugins/flocker.sock`.
 
-Plugins must be run locally on the same machine as the Docker daemon.  UNIX
-domain sockets are strongly encouraged for security reasons.
+### JSON specification
+
+This is the JSON format for a plugin:
+
+```json
+{
+  "Name": "plugin-example",
+  "Addr": "https://example.com/docker/plugin",
+  "TLSConfig": {
+    "InsecureSkipVerify": false,
+    "CAFile": "/usr/shared/docker/certs/example-ca.pem",
+    "CertFile": "/usr/shared/docker/certs/example-cert.pem",
+    "KeyFile": "/usr/shared/docker/certs/example-key.pem",
+  }
+}
+```
+
+The `TLSConfig` field is optional and TLS will only be verified if this configuration is present.
 
 ## Plugin lifecycle
 

+ 0 - 1
integration-cli/docker_cli_start_volume_driver_unix_test.go

@@ -41,7 +41,6 @@ type DockerExternalVolumeSuite struct {
 func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
 	s.d = NewDaemon(c)
 	s.ec = &eventCounter{}
-
 }
 
 func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {

+ 12 - 19
pkg/plugins/client.go

@@ -5,12 +5,13 @@ import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
-	"net"
 	"net/http"
 	"strings"
 	"time"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/sockets"
+	"github.com/docker/docker/pkg/tlsconfig"
 )
 
 const (
@@ -18,11 +19,18 @@ const (
 	defaultTimeOut  = 30
 )
 
-func NewClient(addr string) *Client {
+func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
 	tr := &http.Transport{}
+
+	c, err := tlsconfig.Client(tlsConfig)
+	if err != nil {
+		return nil, err
+	}
+	tr.TLSClientConfig = c
+
 	protoAndAddr := strings.Split(addr, "://")
-	configureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1])
-	return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}
+	sockets.ConfigureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1])
+	return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}, nil
 }
 
 type Client struct {
@@ -96,18 +104,3 @@ 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 configureTCPTransport(tr *http.Transport, proto, addr string) {
-	// Why 32? See https://github.com/docker/docker/pull/8035.
-	timeout := 32 * time.Second
-	if proto == "unix" {
-		// No need for compression in local communications.
-		tr.DisableCompression = true
-		tr.Dial = func(_, _ string) (net.Conn, error) {
-			return net.DialTimeout(proto, addr, timeout)
-		}
-	} else {
-		tr.Proxy = http.ProxyFromEnvironment
-		tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
-	}
-}

+ 4 - 2
pkg/plugins/client_test.go

@@ -7,6 +7,8 @@ import (
 	"reflect"
 	"testing"
 	"time"
+
+	"github.com/docker/docker/pkg/tlsconfig"
 )
 
 var (
@@ -27,7 +29,7 @@ func teardownRemotePluginServer() {
 }
 
 func TestFailedConnection(t *testing.T) {
-	c := NewClient("tcp://127.0.0.1:1")
+	c, _ := NewClient("tcp://127.0.0.1:1", tlsconfig.Options{InsecureSkipVerify: true})
 	err := c.callWithRetry("Service.Method", nil, nil, false)
 	if err == nil {
 		t.Fatal("Unexpected successful connection")
@@ -51,7 +53,7 @@ func TestEchoInputOutput(t *testing.T) {
 		io.Copy(w, r.Body)
 	})
 
-	c := NewClient(addr)
+	c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
 	var output Manifest
 	err := c.Call("Test.Echo", m, &output)
 	if err != nil {

+ 41 - 14
pkg/plugins/discovery.go

@@ -1,6 +1,7 @@
 package plugins
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
 	"io/ioutil"
@@ -37,25 +38,25 @@ func (l *LocalRegistry) Plugin(name string) (*Plugin, error) {
 	filepath := filepath.Join(l.path, name)
 	specpath := filepath + ".spec"
 	if fi, err := os.Stat(specpath); err == nil {
-		return readPluginInfo(specpath, fi)
+		return readPluginSpecInfo(specpath, fi)
 	}
+
 	socketpath := filepath + ".sock"
 	if fi, err := os.Stat(socketpath); err == nil {
-		return readPluginInfo(socketpath, fi)
+		return readPluginSocketInfo(socketpath, fi)
+	}
+
+	jsonpath := filepath + ".json"
+	if _, err := os.Stat(jsonpath); err == nil {
+		return readPluginJSONInfo(name, jsonpath)
 	}
+
 	return nil, ErrNotFound
 }
 
-func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) {
+func readPluginSpecInfo(path string, fi os.FileInfo) (*Plugin, error) {
 	name := strings.Split(fi.Name(), ".")[0]
 
-	if fi.Mode()&os.ModeSocket != 0 {
-		return &Plugin{
-			Name: name,
-			Addr: "unix://" + path,
-		}, nil
-	}
-
 	content, err := ioutil.ReadFile(path)
 	if err != nil {
 		return nil, err
@@ -71,8 +72,34 @@ func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) {
 		return nil, fmt.Errorf("Unknown protocol")
 	}
 
-	return &Plugin{
-		Name: name,
-		Addr: addr,
-	}, nil
+	return newLocalPlugin(name, addr), nil
+}
+
+func readPluginSocketInfo(path string, fi os.FileInfo) (*Plugin, error) {
+	name := strings.Split(fi.Name(), ".")[0]
+
+	if fi.Mode()&os.ModeSocket == 0 {
+		return nil, fmt.Errorf("%s is not a socket", path)
+	}
+
+	return newLocalPlugin(name, "unix://"+path), nil
+}
+
+func readPluginJSONInfo(name, path string) (*Plugin, error) {
+	f, err := os.Open(path)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	var p Plugin
+	if err := json.NewDecoder(f).Decode(&p); err != nil {
+		return nil, err
+	}
+	p.Name = name
+	if len(p.TLSConfig.CAFile) == 0 {
+		p.TLSConfig.InsecureSkipVerify = true
+	}
+
+	return &p, nil
 }

+ 49 - 1
pkg/plugins/discovery_test.go

@@ -61,7 +61,7 @@ func TestLocalSocket(t *testing.T) {
 }
 
 func TestFileSpecPlugin(t *testing.T) {
-	tmpdir, err := ioutil.TempDir("", "docker-test")
+	tmpdir, err := ioutil.TempDir("", "docker-test-")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -102,3 +102,51 @@ func TestFileSpecPlugin(t *testing.T) {
 		}
 	}
 }
+
+func TestFileJSONSpecPlugin(t *testing.T) {
+	tmpdir, err := ioutil.TempDir("", "docker-test-")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	p := filepath.Join(tmpdir, "example.json")
+	spec := `{
+  "Name": "plugin-example",
+  "Addr": "https://example.com/docker/plugin",
+  "TLSConfig": {
+    "CAFile": "/usr/shared/docker/certs/example-ca.pem",
+    "CertFile": "/usr/shared/docker/certs/example-cert.pem",
+    "KeyFile": "/usr/shared/docker/certs/example-key.pem"
+	}
+}`
+
+	if err = ioutil.WriteFile(p, []byte(spec), 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	r := newLocalRegistry(tmpdir)
+	plugin, err := r.Plugin("example")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if plugin.Name != "example" {
+		t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name)
+	}
+
+	if plugin.Addr != "https://example.com/docker/plugin" {
+		t.Fatalf("Expected plugin addr `https://example.com/docker/plugin`, got %s\n", plugin.Addr)
+	}
+
+	if plugin.TLSConfig.CAFile != "/usr/shared/docker/certs/example-ca.pem" {
+		t.Fatalf("Expected plugin CA `/usr/shared/docker/certs/example-ca.pem`, got %s\n", plugin.TLSConfig.CAFile)
+	}
+
+	if plugin.TLSConfig.CertFile != "/usr/shared/docker/certs/example-cert.pem" {
+		t.Fatalf("Expected plugin Certificate `/usr/shared/docker/certs/example-cert.pem`, got %s\n", plugin.TLSConfig.CertFile)
+	}
+
+	if plugin.TLSConfig.KeyFile != "/usr/shared/docker/certs/example-key.pem" {
+		t.Fatalf("Expected plugin Key `/usr/shared/docker/certs/example-key.pem`, got %s\n", plugin.TLSConfig.KeyFile)
+	}
+}

+ 22 - 7
pkg/plugins/plugins.go

@@ -5,6 +5,7 @@ import (
 	"sync"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/tlsconfig"
 )
 
 var (
@@ -26,22 +27,36 @@ type Manifest struct {
 }
 
 type Plugin struct {
-	Name     string
-	Addr     string
-	Client   *Client
-	Manifest *Manifest
+	Name      string `json:"-"`
+	Addr      string
+	TLSConfig tlsconfig.Options
+	Client    *Client   `json:"-"`
+	Manifest  *Manifest `json:"-"`
+}
+
+func newLocalPlugin(name, addr string) *Plugin {
+	return &Plugin{
+		Name:      name,
+		Addr:      addr,
+		TLSConfig: tlsconfig.Options{InsecureSkipVerify: true},
+	}
 }
 
 func (p *Plugin) activate() error {
-	m := new(Manifest)
-	p.Client = NewClient(p.Addr)
-	err := p.Client.Call("Plugin.Activate", nil, m)
+	c, err := NewClient(p.Addr, p.TLSConfig)
 	if err != nil {
 		return err
 	}
+	p.Client = c
+
+	m := new(Manifest)
+	if err = p.Client.Call("Plugin.Activate", nil, m); err != nil {
+		return err
+	}
 
 	logrus.Debugf("%s's manifest: %v", p.Name, m)
 	p.Manifest = m
+
 	for _, iface := range m.Implements {
 		handler, handled := extpointHandlers[iface]
 		if !handled {

+ 17 - 0
pkg/sockets/tcp_socket.go

@@ -3,6 +3,8 @@ package sockets
 import (
 	"crypto/tls"
 	"net"
+	"net/http"
+	"time"
 
 	"github.com/docker/docker/pkg/listenbuffer"
 )
@@ -18,3 +20,18 @@ func NewTcpSocket(addr string, tlsConfig *tls.Config, activate <-chan struct{})
 	}
 	return l, nil
 }
+
+func ConfigureTCPTransport(tr *http.Transport, proto, addr string) {
+	// Why 32? See https://github.com/docker/docker/pull/8035.
+	timeout := 32 * time.Second
+	if proto == "unix" {
+		// No need for compression in local communications.
+		tr.DisableCompression = true
+		tr.Dial = func(_, _ string) (net.Conn, error) {
+			return net.DialTimeout(proto, addr, timeout)
+		}
+	} else {
+		tr.Proxy = http.ProxyFromEnvironment
+		tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
+	}
+}

+ 0 - 22
utils/tcp.go

@@ -1,22 +0,0 @@
-package utils
-
-import (
-	"net"
-	"net/http"
-	"time"
-)
-
-func ConfigureTCPTransport(tr *http.Transport, proto, addr string) {
-	// Why 32? See https://github.com/docker/docker/pull/8035.
-	timeout := 32 * time.Second
-	if proto == "unix" {
-		// No need for compression in local communications.
-		tr.DisableCompression = true
-		tr.Dial = func(_, _ string) (net.Conn, error) {
-			return net.DialTimeout(proto, addr, timeout)
-		}
-	} else {
-		tr.Proxy = http.ProxyFromEnvironment
-		tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
-	}
-}

+ 7 - 3
volume/drivers/proxy_test.go

@@ -9,6 +9,7 @@ import (
 	"testing"
 
 	"github.com/docker/docker/pkg/plugins"
+	"github.com/docker/docker/pkg/tlsconfig"
 )
 
 func TestVolumeRequestError(t *testing.T) {
@@ -42,11 +43,14 @@ func TestVolumeRequestError(t *testing.T) {
 	})
 
 	u, _ := url.Parse(server.URL)
-	client := plugins.NewClient("tcp://" + u.Host)
+	client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true})
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	driver := volumeDriverProxy{client}
 
-	err := driver.Create("volume")
-	if err == nil {
+	if err = driver.Create("volume"); err == nil {
 		t.Fatal("Expected error, was nil")
 	}