ebcb7d6b40
Use strongly typed errors to set HTTP status codes. Error interfaces are defined in the api/errors package and errors returned from controllers are checked against these interfaces. Errors can be wraeped in a pkg/errors.Causer, as long as somewhere in the line of causes one of the interfaces is implemented. The special error interfaces take precedence over Causer, meaning if both Causer and one of the new error interfaces are implemented, the Causer is not traversed. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
254 lines
6.6 KiB
Go
254 lines
6.6 KiB
Go
package plugin
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/pkg/plugingetter"
|
|
"github.com/docker/docker/pkg/plugins"
|
|
"github.com/docker/docker/plugin/v2"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
/* 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
|
|
pluginType should include a version. e.g. "networkdriver/2.0".
|
|
*/
|
|
const defaultAPIVersion string = "1.0"
|
|
|
|
// GetV2Plugin retrieves a plugin by name, id or partial ID.
|
|
func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
|
|
ps.RLock()
|
|
defer ps.RUnlock()
|
|
|
|
id, err := ps.resolvePluginID(refOrID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p, idOk := ps.plugins[id]
|
|
if !idOk {
|
|
return nil, errors.WithStack(errNotFound(id))
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// 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 {
|
|
return alreadyExistsError(name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetAll retrieves all plugins.
|
|
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 {
|
|
if p.IsEnabled() {
|
|
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 *Store) SetState(p *v2.Plugin, state bool) {
|
|
ps.Lock()
|
|
defer ps.Unlock()
|
|
|
|
p.PluginObj.Enabled = state
|
|
}
|
|
|
|
// Add adds a plugin to memory and plugindb.
|
|
// An error will be returned if there is a collision.
|
|
func (ps *Store) Add(p *v2.Plugin) error {
|
|
ps.Lock()
|
|
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
|
|
}
|
|
|
|
// Remove removes a plugin from memory and plugindb.
|
|
func (ps *Store) Remove(p *v2.Plugin) {
|
|
ps.Lock()
|
|
delete(ps.plugins, p.GetID())
|
|
ps.Unlock()
|
|
}
|
|
|
|
// Get returns an enabled plugin matching the given name and capability.
|
|
func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) {
|
|
var (
|
|
p *v2.Plugin
|
|
err error
|
|
)
|
|
|
|
// Lookup using new model.
|
|
if ps != nil {
|
|
p, err = ps.GetV2Plugin(name)
|
|
if err == nil {
|
|
p.AddRefCount(mode)
|
|
if p.IsEnabled() {
|
|
return p.FilterByCap(capability)
|
|
}
|
|
// Plugin was found but it is disabled, so we should not fall back to legacy plugins
|
|
// but we should error out right away
|
|
return nil, errDisabled(name)
|
|
}
|
|
if _, ok := errors.Cause(err).(errNotFound); !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Lookup using legacy model.
|
|
if allowV1PluginsFallback {
|
|
p, err := plugins.Get(name, capability)
|
|
if err != nil {
|
|
if errors.Cause(err) == plugins.ErrNotFound {
|
|
return nil, errNotFound(name)
|
|
}
|
|
return nil, errors.Wrap(systemError{err}, "legacy plugin")
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
// GetAllManagedPluginsByCap returns a list of managed plugins matching the given capability.
|
|
func (ps *Store) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin {
|
|
return ps.getAllByCap(capability)
|
|
}
|
|
|
|
// GetAllByCap returns a list of enabled plugins matching the given capability.
|
|
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 {
|
|
return nil, errors.Wrap(systemError{err}, "legacy plugin")
|
|
}
|
|
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 (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) {
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ps *Store) resolvePluginID(idOrName string) (string, error) {
|
|
ps.RLock() // todo: fix
|
|
defer ps.RUnlock()
|
|
|
|
if validFullID.MatchString(idOrName) {
|
|
return idOrName, nil
|
|
}
|
|
|
|
ref, err := reference.ParseNormalizedNamed(idOrName)
|
|
if err != nil {
|
|
return "", errors.WithStack(errNotFound(idOrName))
|
|
}
|
|
if _, ok := ref.(reference.Canonical); ok {
|
|
logrus.Warnf("canonical references cannot be resolved: %v", reference.FamiliarString(ref))
|
|
return "", errors.WithStack(errNotFound(idOrName))
|
|
}
|
|
|
|
ref = reference.TagNameOnly(ref)
|
|
|
|
for _, p := range ps.plugins {
|
|
if p.PluginObj.Name == reference.FamiliarString(ref) {
|
|
return p.PluginObj.ID, nil
|
|
}
|
|
}
|
|
|
|
var found *v2.Plugin
|
|
for id, p := range ps.plugins { // this can be optimized
|
|
if strings.HasPrefix(id, idOrName) {
|
|
if found != nil {
|
|
return "", errors.WithStack(errAmbiguous(idOrName))
|
|
}
|
|
found = p
|
|
}
|
|
}
|
|
if found == nil {
|
|
return "", errors.WithStack(errNotFound(idOrName))
|
|
}
|
|
return found.PluginObj.ID, nil
|
|
}
|