Browse Source

Merge pull request #13524 from calavera/plugin_json_spec

Plugins JSON spec.
Brian Goff 10 năm trước cách đây
mục cha
commit
f13b40f6e7

+ 2 - 2
api/client/cli.go

@@ -15,8 +15,8 @@ import (
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/pkg/homedir"
 	"github.com/docker/docker/pkg/homedir"
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/sockets"
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/pkg/term"
-	"github.com/docker/docker/utils"
 )
 )
 
 
 // DockerCli represents the docker command line client.
 // 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{
 	tr := &http.Transport{
 		TLSClientConfig: tlsConfig,
 		TLSClientConfig: tlsConfig,
 	}
 	}
-	utils.ConfigureTCPTransport(tr, proto, addr)
+	sockets.ConfigureTCPTransport(tr, proto, addr)
 
 
 	configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker"))
 	configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker"))
 	if e != nil {
 	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
 Docker discovers plugins by looking for them in the plugin directory whenever a
 user or container tries to use one by name.
 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.
 * `.sock` files are UNIX domain sockets.
 * `.spec` files are text files containing a URL, such as `unix:///other.sock`.
 * `.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.
 The name of the file (excluding the extension) determines the plugin name.
 
 
 For example, the `flocker` plugin might create a UNIX socket at
 For example, the `flocker` plugin might create a UNIX socket at
 `/usr/share/docker/plugins/flocker.sock`.
 `/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
 ## 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) {
 func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
 	s.d = NewDaemon(c)
 	s.d = NewDaemon(c)
 	s.ec = &eventCounter{}
 	s.ec = &eventCounter{}
-
 }
 }
 
 
 func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {
 func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {

+ 12 - 19
pkg/plugins/client.go

@@ -5,12 +5,13 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
-	"net"
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/sockets"
+	"github.com/docker/docker/pkg/tlsconfig"
 )
 )
 
 
 const (
 const (
@@ -18,11 +19,18 @@ const (
 	defaultTimeOut  = 30
 	defaultTimeOut  = 30
 )
 )
 
 
-func NewClient(addr string) *Client {
+func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
 	tr := &http.Transport{}
 	tr := &http.Transport{}
+
+	c, err := tlsconfig.Client(tlsConfig)
+	if err != nil {
+		return nil, err
+	}
+	tr.TLSClientConfig = c
+
 	protoAndAddr := strings.Split(addr, "://")
 	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 {
 type Client struct {
@@ -96,18 +104,3 @@ func backoff(retries int) time.Duration {
 func abort(start time.Time, timeOff time.Duration) bool {
 func abort(start time.Time, timeOff time.Duration) bool {
 	return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second
 	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"
 	"reflect"
 	"testing"
 	"testing"
 	"time"
 	"time"
+
+	"github.com/docker/docker/pkg/tlsconfig"
 )
 )
 
 
 var (
 var (
@@ -27,7 +29,7 @@ func teardownRemotePluginServer() {
 }
 }
 
 
 func TestFailedConnection(t *testing.T) {
 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)
 	err := c.callWithRetry("Service.Method", nil, nil, false)
 	if err == nil {
 	if err == nil {
 		t.Fatal("Unexpected successful connection")
 		t.Fatal("Unexpected successful connection")
@@ -51,7 +53,7 @@ func TestEchoInputOutput(t *testing.T) {
 		io.Copy(w, r.Body)
 		io.Copy(w, r.Body)
 	})
 	})
 
 
-	c := NewClient(addr)
+	c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
 	var output Manifest
 	var output Manifest
 	err := c.Call("Test.Echo", m, &output)
 	err := c.Call("Test.Echo", m, &output)
 	if err != nil {
 	if err != nil {

+ 41 - 14
pkg/plugins/discovery.go

@@ -1,6 +1,7 @@
 package plugins
 package plugins
 
 
 import (
 import (
+	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
@@ -37,25 +38,25 @@ func (l *LocalRegistry) Plugin(name string) (*Plugin, error) {
 	filepath := filepath.Join(l.path, name)
 	filepath := filepath.Join(l.path, name)
 	specpath := filepath + ".spec"
 	specpath := filepath + ".spec"
 	if fi, err := os.Stat(specpath); err == nil {
 	if fi, err := os.Stat(specpath); err == nil {
-		return readPluginInfo(specpath, fi)
+		return readPluginSpecInfo(specpath, fi)
 	}
 	}
+
 	socketpath := filepath + ".sock"
 	socketpath := filepath + ".sock"
 	if fi, err := os.Stat(socketpath); err == nil {
 	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
 	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]
 	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)
 	content, err := ioutil.ReadFile(path)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -71,8 +72,34 @@ func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) {
 		return nil, fmt.Errorf("Unknown protocol")
 		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) {
 func TestFileSpecPlugin(t *testing.T) {
-	tmpdir, err := ioutil.TempDir("", "docker-test")
+	tmpdir, err := ioutil.TempDir("", "docker-test-")
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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"
 	"sync"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/tlsconfig"
 )
 )
 
 
 var (
 var (
@@ -26,22 +27,36 @@ type Manifest struct {
 }
 }
 
 
 type Plugin 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 {
 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 {
 	if err != nil {
 		return err
 		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)
 	logrus.Debugf("%s's manifest: %v", p.Name, m)
 	p.Manifest = m
 	p.Manifest = m
+
 	for _, iface := range m.Implements {
 	for _, iface := range m.Implements {
 		handler, handled := extpointHandlers[iface]
 		handler, handled := extpointHandlers[iface]
 		if !handled {
 		if !handled {

+ 17 - 0
pkg/sockets/tcp_socket.go

@@ -3,6 +3,8 @@ package sockets
 import (
 import (
 	"crypto/tls"
 	"crypto/tls"
 	"net"
 	"net"
+	"net/http"
+	"time"
 
 
 	"github.com/docker/docker/pkg/listenbuffer"
 	"github.com/docker/docker/pkg/listenbuffer"
 )
 )
@@ -18,3 +20,18 @@ func NewTcpSocket(addr string, tlsConfig *tls.Config, activate <-chan struct{})
 	}
 	}
 	return l, nil
 	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"
 	"testing"
 
 
 	"github.com/docker/docker/pkg/plugins"
 	"github.com/docker/docker/pkg/plugins"
+	"github.com/docker/docker/pkg/tlsconfig"
 )
 )
 
 
 func TestVolumeRequestError(t *testing.T) {
 func TestVolumeRequestError(t *testing.T) {
@@ -42,11 +43,14 @@ func TestVolumeRequestError(t *testing.T) {
 	})
 	})
 
 
 	u, _ := url.Parse(server.URL)
 	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}
 	driver := volumeDriverProxy{client}
 
 
-	err := driver.Create("volume")
-	if err == nil {
+	if err = driver.Create("volume"); err == nil {
 		t.Fatal("Expected error, was nil")
 		t.Fatal("Expected error, was nil")
 	}
 	}