1e1d326b50
Signed-off-by: Jan Garcia <github-public@n-garcia.com>
287 lines
7.5 KiB
Go
287 lines
7.5 KiB
Go
package plugin // import "github.com/docker/docker/plugin"
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/docker/docker/pkg/plugingetter"
|
|
"github.com/docker/docker/pkg/plugins"
|
|
v2 "github.com/docker/docker/plugin/v2"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"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 = 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 = "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()
|
|
|
|
for _, p := range plugins {
|
|
ps.setSpecOpts(p)
|
|
}
|
|
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
|
|
}
|
|
|
|
func (ps *Store) setSpecOpts(p *v2.Plugin) {
|
|
var specOpts []SpecOpt
|
|
for _, typ := range p.GetTypes() {
|
|
opts, ok := ps.specOpts[typ.String()]
|
|
if ok {
|
|
specOpts = append(specOpts, opts...)
|
|
}
|
|
}
|
|
|
|
p.SetSpecOptModifier(func(s *specs.Spec) {
|
|
for _, o := range specOpts {
|
|
o(s)
|
|
}
|
|
})
|
|
}
|
|
|
|
// 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.setSpecOpts(p)
|
|
|
|
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) {
|
|
// Lookup using new model.
|
|
if ps != nil {
|
|
p, err := ps.GetV2Plugin(name)
|
|
if err == nil {
|
|
if p.IsEnabled() {
|
|
fp, err := p.FilterByCap(capability)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.AddRefCount(mode)
|
|
return fp, nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
var ierr errNotFound
|
|
if !errors.As(err, &ierr) {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if !allowV1PluginsFallback {
|
|
return nil, errNotFound(name)
|
|
}
|
|
|
|
p, err := plugins.Get(name, capability)
|
|
if err == nil {
|
|
return p, nil
|
|
}
|
|
if errors.Is(err, plugins.ErrNotFound) {
|
|
return nil, errNotFound(name)
|
|
}
|
|
return nil, errors.Wrap(errdefs.System(err), "legacy plugin")
|
|
}
|
|
|
|
// 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 {
|
|
result = ps.getAllByCap(capability)
|
|
}
|
|
|
|
// Lookup with legacy model
|
|
if allowV1PluginsFallback {
|
|
l := plugins.NewLocalRegistry()
|
|
pl, err := l.GetAll(capability)
|
|
if err != nil {
|
|
return nil, errors.Wrap(errdefs.System(err), "legacy plugin")
|
|
}
|
|
for _, p := range pl {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func pluginType(cap string) string {
|
|
return fmt.Sprintf("docker.%s/%s", strings.ToLower(cap), defaultAPIVersion)
|
|
}
|
|
|
|
// 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)) {
|
|
typ := pluginType(capability)
|
|
|
|
// Register callback with new plugin model.
|
|
ps.Lock()
|
|
handlers, ok := ps.handlers[typ]
|
|
if !ok {
|
|
handlers = []func(string, *plugins.Client){}
|
|
}
|
|
handlers = append(handlers, callback)
|
|
ps.handlers[typ] = handlers
|
|
ps.Unlock()
|
|
|
|
// Register callback with legacy plugin model.
|
|
if allowV1PluginsFallback {
|
|
plugins.Handle(capability, callback)
|
|
}
|
|
}
|
|
|
|
// RegisterRuntimeOpt stores a list of SpecOpts for the provided capability.
|
|
// These options are applied to the runtime spec before a plugin is started for the specified capability.
|
|
func (ps *Store) RegisterRuntimeOpt(cap string, opts ...SpecOpt) {
|
|
ps.Lock()
|
|
defer ps.Unlock()
|
|
typ := pluginType(cap)
|
|
ps.specOpts[typ] = append(ps.specOpts[typ], opts...)
|
|
}
|
|
|
|
// 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())
|
|
}
|
|
}
|
|
}
|
|
|
|
// resolvePluginID must be protected by ps.RLock
|
|
func (ps *Store) resolvePluginID(idOrName string) (string, error) {
|
|
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
|
|
}
|