mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
17b8aba1d9
Legacy plugins (aka pluginv1) calls in libnetwork are replaced with calls using the new plugin model (aka pluginv2). pkg/plugins is still used for managing the http client connections to the plugin. This commit makes the necessary changes in docker/docker. Part 2 will will take care of the libnetwork changes. Signed-off-by: Anusha Ragunathan <anusha@docker.com>
254 lines
6.3 KiB
Go
254 lines
6.3 KiB
Go
// +build experimental
|
|
|
|
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
"github.com/docker/docker/pkg/plugins"
|
|
"github.com/docker/docker/plugin/v2"
|
|
"github.com/docker/docker/reference"
|
|
)
|
|
|
|
var (
|
|
store *PluginStore
|
|
/* allowV1PluginsFallback determines daemon's support for V1 plugins.
|
|
* When the time comes to remove support for V1 plugins, flipping
|
|
* this bool is all that will be needed.
|
|
*/
|
|
allowV1PluginsFallback = true
|
|
)
|
|
|
|
// ErrNotFound indicates that a plugin was not found locally.
|
|
type ErrNotFound string
|
|
|
|
func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
|
|
|
|
// PluginStore manages the plugin inventory in memory and on-disk
|
|
type PluginStore struct {
|
|
sync.RWMutex
|
|
plugins map[string]*v2.Plugin
|
|
/* handlers are necessary for transition path of legacy plugins
|
|
* to the new model. Legacy plugins use Handle() for registering an
|
|
* activation callback.*/
|
|
handlers map[string]func(string, *plugins.Client)
|
|
nameToID map[string]string
|
|
plugindb string
|
|
}
|
|
|
|
// NewPluginStore creates a PluginStore.
|
|
func NewPluginStore(libRoot string) *PluginStore {
|
|
store = &PluginStore{
|
|
plugins: make(map[string]*v2.Plugin),
|
|
handlers: make(map[string]func(string, *plugins.Client)),
|
|
nameToID: make(map[string]string),
|
|
plugindb: filepath.Join(libRoot, "plugins.json"),
|
|
}
|
|
return store
|
|
}
|
|
|
|
// GetByName retreives a plugin by name.
|
|
func (ps *PluginStore) GetByName(name string) (*v2.Plugin, error) {
|
|
ps.RLock()
|
|
defer ps.RUnlock()
|
|
|
|
id, nameOk := ps.nameToID[name]
|
|
if !nameOk {
|
|
return nil, ErrNotFound(name)
|
|
}
|
|
|
|
p, idOk := ps.plugins[id]
|
|
if !idOk {
|
|
return nil, ErrNotFound(id)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// GetByID retreives a plugin by ID.
|
|
func (ps *PluginStore) GetByID(id string) (*v2.Plugin, error) {
|
|
ps.RLock()
|
|
defer ps.RUnlock()
|
|
|
|
p, idOk := ps.plugins[id]
|
|
if !idOk {
|
|
return nil, ErrNotFound(id)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// GetAll retreives all plugins.
|
|
func (ps *PluginStore) GetAll() map[string]*v2.Plugin {
|
|
ps.RLock()
|
|
defer ps.RUnlock()
|
|
return ps.plugins
|
|
}
|
|
|
|
// SetAll initialized plugins during daemon restore.
|
|
func (ps *PluginStore) SetAll(plugins map[string]*v2.Plugin) {
|
|
ps.Lock()
|
|
defer ps.Unlock()
|
|
ps.plugins = plugins
|
|
}
|
|
|
|
func (ps *PluginStore) getByCap(name string, capability string) (*v2.Plugin, error) {
|
|
ps.RLock()
|
|
defer ps.RUnlock()
|
|
|
|
p, err := ps.GetByName(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.FilterByCap(capability)
|
|
}
|
|
|
|
func (ps *PluginStore) getAllByCap(capability string) []CompatPlugin {
|
|
ps.RLock()
|
|
defer ps.RUnlock()
|
|
|
|
result := make([]CompatPlugin, 0, 1)
|
|
for _, p := range ps.plugins {
|
|
if _, err := p.FilterByCap(capability); err == nil {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// SetState sets the active state of the plugin and updates plugindb.
|
|
func (ps *PluginStore) SetState(p *v2.Plugin, state bool) {
|
|
ps.Lock()
|
|
defer ps.Unlock()
|
|
|
|
p.PluginObj.Enabled = state
|
|
ps.updatePluginDB()
|
|
}
|
|
|
|
// Add adds a plugin to memory and plugindb.
|
|
func (ps *PluginStore) Add(p *v2.Plugin) {
|
|
ps.Lock()
|
|
ps.plugins[p.GetID()] = p
|
|
ps.nameToID[p.Name()] = p.GetID()
|
|
ps.updatePluginDB()
|
|
ps.Unlock()
|
|
}
|
|
|
|
// Remove removes a plugin from memory, plugindb and disk.
|
|
func (ps *PluginStore) Remove(p *v2.Plugin) {
|
|
ps.Lock()
|
|
delete(ps.plugins, p.GetID())
|
|
delete(ps.nameToID, p.Name())
|
|
ps.updatePluginDB()
|
|
p.RemoveFromDisk()
|
|
ps.Unlock()
|
|
}
|
|
|
|
// Callers are expected to hold the store lock.
|
|
func (ps *PluginStore) updatePluginDB() error {
|
|
jsonData, err := json.Marshal(ps.plugins)
|
|
if err != nil {
|
|
logrus.Debugf("Error in json.Marshal: %v", err)
|
|
return err
|
|
}
|
|
ioutils.AtomicWriteFile(ps.plugindb, jsonData, 0600)
|
|
return nil
|
|
}
|
|
|
|
// LookupWithCapability returns a plugin matching the given name and capability.
|
|
func LookupWithCapability(name, capability string) (CompatPlugin, error) {
|
|
var (
|
|
p *v2.Plugin
|
|
err error
|
|
)
|
|
|
|
// Lookup using new model.
|
|
if store != nil {
|
|
fullName := name
|
|
if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate
|
|
if reference.IsNameOnly(named) {
|
|
named = reference.WithDefaultTag(named)
|
|
}
|
|
ref, ok := named.(reference.NamedTagged)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid name: %s", named.String())
|
|
}
|
|
fullName = ref.String()
|
|
}
|
|
p, err = store.GetByName(fullName)
|
|
if err == nil {
|
|
return p.FilterByCap(capability)
|
|
}
|
|
if _, ok := err.(ErrNotFound); !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Lookup using legacy model.
|
|
if allowV1PluginsFallback {
|
|
p, err := plugins.Get(name, capability)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("legacy plugin: %v", err)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
// FindWithCapability returns a list of plugins matching the given capability.
|
|
func FindWithCapability(capability string) ([]CompatPlugin, error) {
|
|
result := make([]CompatPlugin, 0, 1)
|
|
|
|
/* Daemon start always calls plugin.Init thereby initializing a store.
|
|
* So store on experimental builds can never be nil, even while
|
|
* handling legacy plugins. However, there are legacy plugin unit
|
|
* tests where the volume subsystem directly talks with the plugin,
|
|
* bypassing the daemon. For such tests, this check is necessary.
|
|
*/
|
|
if store != nil {
|
|
store.RLock()
|
|
result = store.getAllByCap(capability)
|
|
store.RUnlock()
|
|
}
|
|
|
|
// Lookup with legacy model
|
|
if allowV1PluginsFallback {
|
|
pl, err := plugins.GetAll(capability)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("legacy plugin: %v", err)
|
|
}
|
|
for _, p := range pl {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Handle sets a callback for a given capability. It is only used by network
|
|
// and ipam drivers during plugin registration. The callback registers the
|
|
// driver with the subsystem (network, ipam).
|
|
func Handle(capability string, callback func(string, *plugins.Client)) {
|
|
pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
|
|
|
|
// Register callback with new plugin model.
|
|
store.handlers[pluginType] = callback
|
|
|
|
// Register callback with legacy plugin model.
|
|
if allowV1PluginsFallback {
|
|
plugins.Handle(capability, callback)
|
|
}
|
|
}
|
|
|
|
// CallHandler calls the registered callback. It is invoked during plugin enable.
|
|
func (ps *PluginStore) CallHandler(p *v2.Plugin) {
|
|
for _, typ := range p.GetTypes() {
|
|
if handler := ps.handlers[typ.String()]; handler != nil {
|
|
handler(p.Name(), p.Client())
|
|
}
|
|
}
|
|
}
|