| // Package plugins provides structures and helper functions to manage Docker | |
| // plugins. | |
| // | |
| // Docker discovers plugins by looking for them in the plugin directory whenever | |
| // a user or container tries to use one by name. 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. This is handled | |
| // by the Registry interface, which lets you list all plugins or get a plugin by | |
| // its name if it exists. | |
| // | |
| // The plugins need to implement an HTTP server and bind this to the UNIX socket | |
| // or the address specified in the spec files. | |
| // A handshake is send at /Plugin.Activate, and plugins are expected to return | |
| // a Manifest with a list of Docker subsystems which this plugin implements. | |
| // | |
| // In order to use a plugins, you can use the `Get` with the name of the | |
| // plugin and the subsystem it implements. | |
| // | |
| // plugin, err := plugins.Get("example", "VolumeDriver") | |
| // if err != nil { | |
| // return fmt.Errorf("Error looking up volume plugin example: %v", err) | |
| // } | |
| package plugins // import "github.com/docker/docker/pkg/plugins" | |
| import ( | |
| "errors" | |
| "fmt" | |
| "sync" | |
| "time" | |
| "github.com/docker/go-connections/tlsconfig" | |
| "github.com/sirupsen/logrus" | |
| ) | |
| // ProtocolSchemeHTTPV1 is the name of the protocol used for interacting with plugins using this package. | |
| const ProtocolSchemeHTTPV1 = "moby.plugins.http/v1" | |
| var ( | |
| // ErrNotImplements is returned if the plugin does not implement the requested driver. | |
| ErrNotImplements = errors.New("Plugin does not implement the requested driver") | |
| ) | |
| type plugins struct { | |
| sync.Mutex | |
| plugins map[string]*Plugin | |
| } | |
| type extpointHandlers struct { | |
| sync.RWMutex | |
| extpointHandlers map[string][]func(string, *Client) | |
| } | |
| var ( | |
| storage = plugins{plugins: make(map[string]*Plugin)} | |
| handlers = extpointHandlers{extpointHandlers: make(map[string][]func(string, *Client))} | |
| ) | |
| // Manifest lists what a plugin implements. | |
| type Manifest struct { | |
| // List of subsystem the plugin implements. | |
| Implements []string | |
| } | |
| // Plugin is the definition of a docker plugin. | |
| type Plugin struct { | |
| // Name of the plugin | |
| name string | |
| // Address of the plugin | |
| Addr string | |
| // TLS configuration of the plugin | |
| TLSConfig *tlsconfig.Options | |
| // Client attached to the plugin | |
| client *Client | |
| // Manifest of the plugin (see above) | |
| Manifest *Manifest `json:"-"` | |
| // wait for activation to finish | |
| activateWait *sync.Cond | |
| // error produced by activation | |
| activateErr error | |
| // keeps track of callback handlers run against this plugin | |
| handlersRun bool | |
| } | |
| // Name returns the name of the plugin. | |
| func (p *Plugin) Name() string { | |
| return p.name | |
| } | |
| // Client returns a ready-to-use plugin client that can be used to communicate with the plugin. | |
| func (p *Plugin) Client() *Client { | |
| return p.client | |
| } | |
| // Protocol returns the protocol name/version used for plugins in this package. | |
| func (p *Plugin) Protocol() string { | |
| return ProtocolSchemeHTTPV1 | |
| } | |
| // IsV1 returns true for V1 plugins and false otherwise. | |
| func (p *Plugin) IsV1() bool { | |
| return true | |
| } | |
| // NewLocalPlugin creates a new local plugin. | |
| func NewLocalPlugin(name, addr string) *Plugin { | |
| return &Plugin{ | |
| name: name, | |
| Addr: addr, | |
| // TODO: change to nil | |
| TLSConfig: &tlsconfig.Options{InsecureSkipVerify: true}, | |
| activateWait: sync.NewCond(&sync.Mutex{}), | |
| } | |
| } | |
| func (p *Plugin) activate() error { | |
| p.activateWait.L.Lock() | |
| if p.activated() { | |
| p.runHandlers() | |
| p.activateWait.L.Unlock() | |
| return p.activateErr | |
| } | |
| p.activateErr = p.activateWithLock() | |
| p.runHandlers() | |
| p.activateWait.L.Unlock() | |
| p.activateWait.Broadcast() | |
| return p.activateErr | |
| } | |
| // runHandlers runs the registered handlers for the implemented plugin types | |
| // This should only be run after activation, and while the activation lock is held. | |
| func (p *Plugin) runHandlers() { | |
| if !p.activated() { | |
| return | |
| } | |
| handlers.RLock() | |
| if !p.handlersRun { | |
| for _, iface := range p.Manifest.Implements { | |
| hdlrs, handled := handlers.extpointHandlers[iface] | |
| if !handled { | |
| continue | |
| } | |
| for _, handler := range hdlrs { | |
| handler(p.name, p.client) | |
| } | |
| } | |
| p.handlersRun = true | |
| } | |
| handlers.RUnlock() | |
| } | |
| // activated returns if the plugin has already been activated. | |
| // This should only be called with the activation lock held | |
| func (p *Plugin) activated() bool { | |
| return p.Manifest != nil | |
| } | |
| func (p *Plugin) activateWithLock() error { | |
| 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 | |
| } | |
| p.Manifest = m | |
| return nil | |
| } | |
| func (p *Plugin) waitActive() error { | |
| p.activateWait.L.Lock() | |
| for !p.activated() && p.activateErr == nil { | |
| p.activateWait.Wait() | |
| } | |
| p.activateWait.L.Unlock() | |
| return p.activateErr | |
| } | |
| func (p *Plugin) implements(kind string) bool { | |
| if p.Manifest == nil { | |
| return false | |
| } | |
| for _, driver := range p.Manifest.Implements { | |
| if driver == kind { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| func load(name string) (*Plugin, error) { | |
| return loadWithRetry(name, true) | |
| } | |
| func loadWithRetry(name string, retry bool) (*Plugin, error) { | |
| registry := NewLocalRegistry() | |
| start := time.Now() | |
| var retries int | |
| for { | |
| pl, err := registry.Plugin(name) | |
| if err != nil { | |
| if !retry { | |
| return nil, err | |
| } | |
| timeOff := backoff(retries) | |
| if abort(start, timeOff) { | |
| return nil, err | |
| } | |
| retries++ | |
| logrus.Warnf("Unable to locate plugin: %s, retrying in %v", name, timeOff) | |
| time.Sleep(timeOff) | |
| continue | |
| } | |
| storage.Lock() | |
| if pl, exists := storage.plugins[name]; exists { | |
| storage.Unlock() | |
| return pl, pl.activate() | |
| } | |
| storage.plugins[name] = pl | |
| storage.Unlock() | |
| err = pl.activate() | |
| if err != nil { | |
| storage.Lock() | |
| delete(storage.plugins, name) | |
| storage.Unlock() | |
| } | |
| return pl, err | |
| } | |
| } | |
| func get(name string) (*Plugin, error) { | |
| storage.Lock() | |
| pl, ok := storage.plugins[name] | |
| storage.Unlock() | |
| if ok { | |
| return pl, pl.activate() | |
| } | |
| return load(name) | |
| } | |
| // Get returns the plugin given the specified name and requested implementation. | |
| func Get(name, imp string) (*Plugin, error) { | |
| if name == "" { | |
| return nil, errors.New("Unable to find plugin without name") | |
| } | |
| pl, err := get(name) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if err := pl.waitActive(); err == nil && pl.implements(imp) { | |
| logrus.Debugf("%s implements: %s", name, imp) | |
| return pl, nil | |
| } | |
| return nil, fmt.Errorf("%w: plugin=%q, requested implementation=%q", ErrNotImplements, name, imp) | |
| } | |
| // Handle adds the specified function to the extpointHandlers. | |
| func Handle(iface string, fn func(string, *Client)) { | |
| handlers.Lock() | |
| hdlrs, ok := handlers.extpointHandlers[iface] | |
| if !ok { | |
| hdlrs = []func(string, *Client){} | |
| } | |
| hdlrs = append(hdlrs, fn) | |
| handlers.extpointHandlers[iface] = hdlrs | |
| storage.Lock() | |
| for _, p := range storage.plugins { | |
| p.activateWait.L.Lock() | |
| if p.activated() && p.implements(iface) { | |
| p.handlersRun = false | |
| } | |
| p.activateWait.L.Unlock() | |
| } | |
| storage.Unlock() | |
| handlers.Unlock() | |
| } | |
| // GetAll returns all the plugins for the specified implementation | |
| func (l *LocalRegistry) GetAll(imp string) ([]*Plugin, error) { | |
| pluginNames, err := l.Scan() | |
| if err != nil { | |
| return nil, err | |
| } | |
| type plLoad struct { | |
| pl *Plugin | |
| err error | |
| } | |
| chPl := make(chan *plLoad, len(pluginNames)) | |
| var wg sync.WaitGroup | |
| for _, name := range pluginNames { | |
| storage.Lock() | |
| pl, ok := storage.plugins[name] | |
| storage.Unlock() | |
| if ok { | |
| chPl <- &plLoad{pl, nil} | |
| continue | |
| } | |
| wg.Add(1) | |
| go func(name string) { | |
| defer wg.Done() | |
| pl, err := loadWithRetry(name, false) | |
| chPl <- &plLoad{pl, err} | |
| }(name) | |
| } | |
| wg.Wait() | |
| close(chPl) | |
| var out []*Plugin | |
| for pl := range chPl { | |
| if pl.err != nil { | |
| logrus.Error(pl.err) | |
| continue | |
| } | |
| if err := pl.pl.waitActive(); err == nil && pl.pl.implements(imp) { | |
| out = append(out, pl.pl) | |
| } | |
| } | |
| return out, nil | |
| } |