diff --git a/experimental/plugin_api.md b/experimental/plugin_api.md index f38db9cdaf..50163e6060 100644 --- a/experimental/plugin_api.md +++ b/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 diff --git a/integration-cli/docker_cli_start_volume_driver_unix_test.go b/integration-cli/docker_cli_start_volume_driver_unix_test.go index 2a952620fa..ddf8d228b4 100644 --- a/integration-cli/docker_cli_start_volume_driver_unix_test.go +++ b/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) } } diff --git a/pkg/plugins/discovery.go b/pkg/plugins/discovery.go index 0160fd5d87..2128a9202c 100644 --- a/pkg/plugins/discovery.go +++ b/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 -} +type LocalRegistry struct{} -func newLocalRegistry(path string) *LocalRegistry { - if len(path) == 0 { - path = defaultLocalRegistry - } - - 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") + + for _, p := range socketpaths { + if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 { + return newLocalPlugin(name, "unix://"+p), nil + } } - socketpath := filepath + ".sock" - if fi, err := os.Stat(socketpath); err == nil { - return readPluginSocketInfo(socketpath, fi) + var txtspecpaths []string + for _, p := range specsPaths { + txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".spec")...) + txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".json")...) } - jsonpath := filepath + ".json" - if _, err := os.Stat(jsonpath); err == nil { - return readPluginJSONInfo(name, jsonpath) + 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), + } +} diff --git a/pkg/plugins/discovery_test.go b/pkg/plugins/discovery_test.go index e6001c51c7..5610fe1e9f 100644 --- a/pkg/plugins/discovery_test.go +++ b/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) diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 642e9c8b53..a624e79944 100644 --- a/pkg/plugins/plugins.go +++ b/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