plugin/store.go
3d86b0c7
 package plugin
27a55fba
 
 import (
7781a1bf
 	"fmt"
 	"strings"
 
3a127939
 	"github.com/docker/distribution/reference"
d453fe35
 	"github.com/docker/docker/errdefs"
a98be034
 	"github.com/docker/docker/pkg/plugingetter"
27a55fba
 	"github.com/docker/docker/pkg/plugins"
7781a1bf
 	"github.com/docker/docker/plugin/v2"
3d86b0c7
 	"github.com/pkg/errors"
1009e6a4
 	"github.com/sirupsen/logrus"
27a55fba
 )
 
7781a1bf
 /* 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.
  */
 const allowV1PluginsFallback bool = true
 
 /* defaultAPIVersion is the version of the plugin API for volume, network,
    IPAM and authz. This is a very stable API. When we update this API, then
11454e1c
    pluginType should include a version. e.g. "networkdriver/2.0".
7781a1bf
 */
 const defaultAPIVersion string = "1.0"
 
03bf3788
 // GetV2Plugin retrieves a plugin by name, id or partial ID.
3d86b0c7
 func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
7781a1bf
 	ps.RLock()
 	defer ps.RUnlock()
 
3d86b0c7
 	id, err := ps.resolvePluginID(refOrID)
 	if err != nil {
 		return nil, err
7781a1bf
 	}
 
 	p, idOk := ps.plugins[id]
 	if !idOk {
ebcb7d6b
 		return nil, errors.WithStack(errNotFound(id))
7781a1bf
 	}
3d86b0c7
 
7781a1bf
 	return p, nil
 }
 
3d86b0c7
 // validateName returns error if name is already reserved. always call with lock and full name
 func (ps *Store) validateName(name string) error {
 	for _, p := range ps.plugins {
 		if p.Name() == name {
ebcb7d6b
 			return alreadyExistsError(name)
3d86b0c7
 		}
7781a1bf
 	}
3d86b0c7
 	return nil
7781a1bf
 }
 
03bf3788
 // GetAll retrieves all plugins.
7781a1bf
 func (ps *Store) GetAll() map[string]*v2.Plugin {
 	ps.RLock()
 	defer ps.RUnlock()
 	return ps.plugins
 }
 
 // SetAll initialized plugins during daemon restore.
 func (ps *Store) SetAll(plugins map[string]*v2.Plugin) {
 	ps.Lock()
 	defer ps.Unlock()
 	ps.plugins = plugins
 }
 
 func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin {
 	ps.RLock()
 	defer ps.RUnlock()
 
 	result := make([]plugingetter.CompatPlugin, 0, 1)
 	for _, p := range ps.plugins {
34f4b197
 		if p.IsEnabled() {
 			if _, err := p.FilterByCap(capability); err == nil {
 				result = append(result, p)
 			}
7781a1bf
 		}
27a55fba
 	}
7781a1bf
 	return result
 }
 
 // SetState sets the active state of the plugin and updates plugindb.
 func (ps *Store) SetState(p *v2.Plugin, state bool) {
 	ps.Lock()
 	defer ps.Unlock()
 
 	p.PluginObj.Enabled = state
 }
 
 // Add adds a plugin to memory and plugindb.
662d4569
 // An error will be returned if there is a collision.
 func (ps *Store) Add(p *v2.Plugin) error {
7781a1bf
 	ps.Lock()
662d4569
 	defer ps.Unlock()
 
 	if v, exist := ps.plugins[p.GetID()]; exist {
 		return fmt.Errorf("plugin %q has the same ID %s as %q", p.Name(), p.GetID(), v.Name())
 	}
 	ps.plugins[p.GetID()] = p
 	return nil
 }
 
7781a1bf
 // Remove removes a plugin from memory and plugindb.
 func (ps *Store) Remove(p *v2.Plugin) {
 	ps.Lock()
 	delete(ps.plugins, p.GetID())
 	ps.Unlock()
 }
 
84e58e2f
 // Get returns an enabled plugin matching the given name and capability.
7781a1bf
 func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) {
 	// Lookup using new model.
 	if ps != nil {
36ebf948
 		p, err := ps.GetV2Plugin(name)
7781a1bf
 		if err == nil {
84e58e2f
 			if p.IsEnabled() {
3816b514
 				fp, err := p.FilterByCap(capability)
 				if err != nil {
 					return nil, err
 				}
 				p.AddRefCount(mode)
 				return fp, nil
84e58e2f
 			}
3816b514
 
84e58e2f
 			// Plugin was found but it is disabled, so we should not fall back to legacy plugins
 			// but we should error out right away
ebcb7d6b
 			return nil, errDisabled(name)
7781a1bf
 		}
ebcb7d6b
 		if _, ok := errors.Cause(err).(errNotFound); !ok {
7781a1bf
 			return nil, err
 		}
 	}
 
36ebf948
 	if !allowV1PluginsFallback {
 		return nil, errNotFound(name)
7781a1bf
 	}
 
36ebf948
 	p, err := plugins.Get(name, capability)
 	if err == nil {
 		return p, nil
 	}
 	if errors.Cause(err) == plugins.ErrNotFound {
 		return nil, errNotFound(name)
 	}
87a12421
 	return nil, errors.Wrap(errdefs.System(err), "legacy plugin")
7781a1bf
 }
 
8f1b7935
 // GetAllManagedPluginsByCap returns a list of managed plugins matching the given capability.
 func (ps *Store) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin {
 	return ps.getAllByCap(capability)
 }
 
84e58e2f
 // GetAllByCap returns a list of enabled plugins matching the given capability.
7781a1bf
 func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
 	result := make([]plugingetter.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 ps != nil {
 		ps.RLock()
 		result = ps.getAllByCap(capability)
 		ps.RUnlock()
 	}
 
 	// Lookup with legacy model
 	if allowV1PluginsFallback {
 		pl, err := plugins.GetAll(capability)
 		if err != nil {
87a12421
 			return nil, errors.Wrap(errdefs.System(err), "legacy plugin")
7781a1bf
 		}
 		for _, p := range pl {
 			result = append(result, p)
 		}
 	}
 	return result, nil
17b8aba1
 }
b3bd7f80
 
 // 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 (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) {
7781a1bf
 	pluginType := fmt.Sprintf("docker.%s/%s", strings.ToLower(capability), defaultAPIVersion)
 
 	// Register callback with new plugin model.
 	ps.Lock()
 	handlers, ok := ps.handlers[pluginType]
 	if !ok {
 		handlers = []func(string, *plugins.Client){}
 	}
 	handlers = append(handlers, callback)
 	ps.handlers[pluginType] = handlers
 	ps.Unlock()
 
 	// 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 *Store) CallHandler(p *v2.Plugin) {
 	for _, typ := range p.GetTypes() {
 		for _, handler := range ps.handlers[typ.String()] {
 			handler(p.Name(), p.Client())
 		}
 	}
b3bd7f80
 }
0ce6e070
 
3d86b0c7
 func (ps *Store) resolvePluginID(idOrName string) (string, error) {
 	ps.RLock() // todo: fix
0ce6e070
 	defer ps.RUnlock()
 
3d86b0c7
 	if validFullID.MatchString(idOrName) {
 		return idOrName, nil
 	}
 
3a127939
 	ref, err := reference.ParseNormalizedNamed(idOrName)
3d86b0c7
 	if err != nil {
ebcb7d6b
 		return "", errors.WithStack(errNotFound(idOrName))
3d86b0c7
 	}
 	if _, ok := ref.(reference.Canonical); ok {
3a127939
 		logrus.Warnf("canonical references cannot be resolved: %v", reference.FamiliarString(ref))
ebcb7d6b
 		return "", errors.WithStack(errNotFound(idOrName))
3d86b0c7
 	}
 
3a127939
 	ref = reference.TagNameOnly(ref)
3d86b0c7
 
 	for _, p := range ps.plugins {
3a127939
 		if p.PluginObj.Name == reference.FamiliarString(ref) {
3d86b0c7
 			return p.PluginObj.ID, nil
 		}
 	}
 
0ce6e070
 	var found *v2.Plugin
3d86b0c7
 	for id, p := range ps.plugins { // this can be optimized
 		if strings.HasPrefix(id, idOrName) {
0ce6e070
 			if found != nil {
ebcb7d6b
 				return "", errors.WithStack(errAmbiguous(idOrName))
0ce6e070
 			}
 			found = p
 		}
 	}
 	if found == nil {
ebcb7d6b
 		return "", errors.WithStack(errNotFound(idOrName))
0ce6e070
 	}
3d86b0c7
 	return found.PluginObj.ID, nil
0ce6e070
 }