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 |
} |