123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- package plugin // import "github.com/docker/docker/plugin"
- import (
- "context"
- "fmt"
- "strings"
- "github.com/containerd/log"
- "github.com/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"
- )
- // 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 {
- log.G(context.TODO()).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
- }
|