1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #13951 from calavera/plugins_path

Separate plugin sockets and specs.
This commit is contained in:
Sebastiaan van Stijn 2015-07-17 21:11:31 +02:00
commit a763637eae
5 changed files with 110 additions and 88 deletions

View file

@ -12,8 +12,7 @@ This is an experimental feature. For information on installing and using experim
## What plugins are ## What plugins are
A plugin is a process running on the same docker host as the docker daemon, 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 which registers itself by placing a file in one of the plugin directories described in [Plugin discovery](#plugin-discovery).
"plugin directory").
Plugins have human-readable names, which are short, lowercase strings. For Plugins have human-readable names, which are short, lowercase strings. For
example, `flocker` or `weave`. 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`. * `.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. * `.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. 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`. `/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 ### JSON specification

View file

@ -129,11 +129,11 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
fmt.Fprintln(w, `{}`) 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) 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) c.Fatal(err)
} }
} }
@ -141,7 +141,7 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) { func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
s.server.Close() 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) c.Fatal(err)
} }
} }

View file

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

View file

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

View file

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