Browse Source

Separate plugin sockets and specs.

Check if there is a plugin socket first under `/run/docker/plugins/NAME.sock`.
If there is no socket for a plugin, check `/etc/docker/plugins/NAME.spec` and
`/usr/lib/docker/plugins/NAME.spec` for spec files.

Signed-off-by: David Calavera <david.calavera@gmail.com>
David Calavera 10 years ago
parent
commit
6c0795747b

+ 13 - 3
experimental/plugin_api.md

@@ -12,8 +12,7 @@ This is an experimental feature. For information on installing and using experim
 ## What plugins are
 
 A plugin is a process running on the same docker host as the docker daemon,
-which registers itself by placing a file in `/usr/share/docker/plugins` (the
-"plugin directory").
+which registers itself by placing a file in one of the plugin directories described in [Plugin discovery](#plugin-discovery).
 
 Plugins have human-readable names, which are short, lowercase strings. For
 example, `flocker` or `weave`.
@@ -32,10 +31,21 @@ There are three types of files which can be put in the plugin directory.
 * `.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.
 
+UNIX domain socket files must be located under `/run/docker/plugins`, whereas
+spec files can be located either under `/etc/docker/plugins` or `/usr/lib/docker/plugins`.
+
 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`.
+`/run/docker/plugins/flocker.sock`.
+
+You can define each plugin into a separated subdirectory if you want to isolate definitions from each other.
+For example, you can create the `flocker` socket under `/run/docker/plugins/flocker/flocker.sock` and only
+mount `/run/docker/plugins/flocker` inside the `flocker` container.
+
+Docker always searches for unix sockets in `/run/docker/plugins` first. It checks for spec or json files under
+`/etc/docker/plugins` and `/usr/lib/docker/plugins` if the socket doesn't exist. The directory scan stops as
+soon as it finds the first plugin definition with the given name.
 
 ### JSON specification
 

+ 3 - 3
integration-cli/docker_cli_start_volume_driver_unix_test.go

@@ -129,11 +129,11 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
 		fmt.Fprintln(w, `{}`)
 	})
 
-	if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil {
+	if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
 		c.Fatal(err)
 	}
 
-	if err := ioutil.WriteFile("/usr/share/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644); err != nil {
+	if err := ioutil.WriteFile("/etc/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644); err != nil {
 		c.Fatal(err)
 	}
 }
@@ -141,7 +141,7 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
 func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
 	s.server.Close()
 
-	if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
+	if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
 		c.Fatal(err)
 	}
 }

+ 30 - 35
pkg/plugins/discovery.go

@@ -11,10 +11,10 @@ import (
 	"strings"
 )
 
-const defaultLocalRegistry = "/usr/share/docker/plugins"
-
 var (
 	ErrNotFound = errors.New("Plugin not found")
+	socketsPath = "/run/docker/plugins"
+	specsPaths  = []string{"/etc/docker/plugins", "/usr/lib/docker/plugins"}
 )
 
 type Registry interface {
@@ -22,41 +22,39 @@ type Registry interface {
 	Plugin(name string) (*Plugin, error)
 }
 
-type LocalRegistry struct {
-	path string
-}
-
-func newLocalRegistry(path string) *LocalRegistry {
-	if len(path) == 0 {
-		path = defaultLocalRegistry
-	}
+type LocalRegistry struct{}
 
-	return &LocalRegistry{path}
+func newLocalRegistry() LocalRegistry {
+	return LocalRegistry{}
 }
 
 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 readPluginSpecInfo(specpath, fi)
-	}
+	socketpaths := pluginPaths(socketsPath, name, ".sock")
 
-	socketpath := filepath + ".sock"
-	if fi, err := os.Stat(socketpath); err == nil {
-		return readPluginSocketInfo(socketpath, fi)
+	for _, p := range socketpaths {
+		if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 {
+			return newLocalPlugin(name, "unix://"+p), nil
+		}
 	}
 
-	jsonpath := filepath + ".json"
-	if _, err := os.Stat(jsonpath); err == nil {
-		return readPluginJSONInfo(name, jsonpath)
+	var txtspecpaths []string
+	for _, p := range specsPaths {
+		txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".spec")...)
+		txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".json")...)
 	}
 
+	for _, p := range txtspecpaths {
+		if _, err := os.Stat(p); err == nil {
+			if strings.HasSuffix(p, ".json") {
+				return readPluginJSONInfo(name, p)
+			}
+			return readPluginInfo(name, p)
+		}
+	}
 	return nil, ErrNotFound
 }
 
-func readPluginSpecInfo(path string, fi os.FileInfo) (*Plugin, error) {
-	name := strings.Split(fi.Name(), ".")[0]
-
+func readPluginInfo(name, path string) (*Plugin, error) {
 	content, err := ioutil.ReadFile(path)
 	if err != nil {
 		return nil, err
@@ -75,16 +73,6 @@ func readPluginSpecInfo(path string, fi os.FileInfo) (*Plugin, error) {
 	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 {
@@ -103,3 +91,10 @@ func readPluginJSONInfo(name, path string) (*Plugin, error) {
 
 	return &p, nil
 }
+
+func pluginPaths(base, name, ext string) []string {
+	return []string{
+		filepath.Join(base, name+ext),
+		filepath.Join(base, name, name+ext),
+	}
+}

+ 63 - 46
pkg/plugins/discovery_test.go

@@ -10,62 +10,72 @@ import (
 	"testing"
 )
 
-func TestUnknownLocalPath(t *testing.T) {
+func setup(t *testing.T) (string, func()) {
 	tmpdir, err := ioutil.TempDir("", "docker-test")
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer os.RemoveAll(tmpdir)
+	backup := socketsPath
+	socketsPath = tmpdir
+	specsPaths = []string{tmpdir}
 
-	l := newLocalRegistry(filepath.Join(tmpdir, "unknown"))
-	_, err = l.Plugin("foo")
-	if err == nil || err != ErrNotFound {
-		t.Fatalf("Expected error for unknown directory")
+	return tmpdir, func() {
+		socketsPath = backup
+		os.RemoveAll(tmpdir)
 	}
 }
 
 func TestLocalSocket(t *testing.T) {
-	tmpdir, err := ioutil.TempDir("", "docker-test")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer os.RemoveAll(tmpdir)
-	l, err := net.Listen("unix", filepath.Join(tmpdir, "echo.sock"))
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer l.Close()
+	tmpdir, unregister := setup(t)
+	defer unregister()
 
-	r := newLocalRegistry(tmpdir)
-	p, err := r.Plugin("echo")
-	if err != nil {
-		t.Fatal(err)
+	cases := []string{
+		filepath.Join(tmpdir, "echo.sock"),
+		filepath.Join(tmpdir, "echo", "echo.sock"),
 	}
 
-	pp, err := r.Plugin("echo")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !reflect.DeepEqual(p, pp) {
-		t.Fatalf("Expected %v, was %v\n", p, pp)
-	}
+	for _, c := range cases {
+		if err := os.MkdirAll(filepath.Dir(c), 0755); err != nil {
+			t.Fatal(err)
+		}
 
-	if p.Name != "echo" {
-		t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
-	}
+		l, err := net.Listen("unix", c)
+		if err != nil {
+			t.Fatal(err)
+		}
 
-	addr := fmt.Sprintf("unix://%s/echo.sock", tmpdir)
-	if p.Addr != addr {
-		t.Fatalf("Expected plugin addr `%s`, got %s\n", addr, p.Addr)
+		r := newLocalRegistry()
+		p, err := r.Plugin("echo")
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		pp, err := r.Plugin("echo")
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(p, pp) {
+			t.Fatalf("Expected %v, was %v\n", p, pp)
+		}
+
+		if p.Name != "echo" {
+			t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
+		}
+
+		addr := fmt.Sprintf("unix://%s", c)
+		if p.Addr != addr {
+			t.Fatalf("Expected plugin addr `%s`, got %s\n", addr, p.Addr)
+		}
+		if p.TLSConfig.InsecureSkipVerify != true {
+			t.Fatalf("Expected TLS verification to be skipped")
+		}
+		l.Close()
 	}
 }
 
 func TestFileSpecPlugin(t *testing.T) {
-	tmpdir, err := ioutil.TempDir("", "docker-test-")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer os.RemoveAll(tmpdir)
+	tmpdir, unregister := setup(t)
+	defer unregister()
 
 	cases := []struct {
 		path string
@@ -74,16 +84,21 @@ func TestFileSpecPlugin(t *testing.T) {
 		fail bool
 	}{
 		{filepath.Join(tmpdir, "echo.spec"), "echo", "unix://var/lib/docker/plugins/echo.sock", false},
+		{filepath.Join(tmpdir, "echo", "echo.spec"), "echo", "unix://var/lib/docker/plugins/echo.sock", false},
 		{filepath.Join(tmpdir, "foo.spec"), "foo", "tcp://localhost:8080", false},
+		{filepath.Join(tmpdir, "foo", "foo.spec"), "foo", "tcp://localhost:8080", false},
 		{filepath.Join(tmpdir, "bar.spec"), "bar", "localhost:8080", true}, // unknown transport
 	}
 
 	for _, c := range cases {
-		if err = ioutil.WriteFile(c.path, []byte(c.addr), 0644); err != nil {
+		if err := os.MkdirAll(filepath.Dir(c.path), 0755); err != nil {
+			t.Fatal(err)
+		}
+		if err := ioutil.WriteFile(c.path, []byte(c.addr), 0644); err != nil {
 			t.Fatal(err)
 		}
 
-		r := newLocalRegistry(tmpdir)
+		r := newLocalRegistry()
 		p, err := r.Plugin(c.name)
 		if c.fail && err == nil {
 			continue
@@ -100,14 +115,16 @@ func TestFileSpecPlugin(t *testing.T) {
 		if p.Addr != c.addr {
 			t.Fatalf("Expected plugin addr `%s`, got %s\n", c.addr, p.Addr)
 		}
+
+		if p.TLSConfig.InsecureSkipVerify != true {
+			t.Fatalf("Expected TLS verification to be skipped")
+		}
 	}
 }
 
 func TestFileJSONSpecPlugin(t *testing.T) {
-	tmpdir, err := ioutil.TempDir("", "docker-test-")
-	if err != nil {
-		t.Fatal(err)
-	}
+	tmpdir, unregister := setup(t)
+	defer unregister()
 
 	p := filepath.Join(tmpdir, "example.json")
 	spec := `{
@@ -120,11 +137,11 @@ func TestFileJSONSpecPlugin(t *testing.T) {
 	}
 }`
 
-	if err = ioutil.WriteFile(p, []byte(spec), 0644); err != nil {
+	if err := ioutil.WriteFile(p, []byte(spec), 0644); err != nil {
 		t.Fatal(err)
 	}
 
-	r := newLocalRegistry(tmpdir)
+	r := newLocalRegistry()
 	plugin, err := r.Plugin("example")
 	if err != nil {
 		t.Fatal(err)

+ 1 - 1
pkg/plugins/plugins.go

@@ -68,7 +68,7 @@ func (p *Plugin) activate() error {
 }
 
 func load(name string) (*Plugin, error) {
-	registry := newLocalRegistry("")
+	registry := newLocalRegistry()
 	pl, err := registry.Plugin(name)
 	if err != nil {
 		return nil, err