github.com/yesnault/moby@v1.13.1/plugin/store.go (about)

     1  package plugin
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/Sirupsen/logrus"
     8  	"github.com/docker/docker/pkg/plugingetter"
     9  	"github.com/docker/docker/pkg/plugins"
    10  	"github.com/docker/docker/plugin/v2"
    11  	"github.com/docker/docker/reference"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  /* allowV1PluginsFallback determines daemon's support for V1 plugins.
    16   * When the time comes to remove support for V1 plugins, flipping
    17   * this bool is all that will be needed.
    18   */
    19  const allowV1PluginsFallback bool = true
    20  
    21  /* defaultAPIVersion is the version of the plugin API for volume, network,
    22     IPAM and authz. This is a very stable API. When we update this API, then
    23     pluginType should include a version. eg "networkdriver/2.0".
    24  */
    25  const defaultAPIVersion string = "1.0"
    26  
    27  // ErrNotFound indicates that a plugin was not found locally.
    28  type ErrNotFound string
    29  
    30  func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
    31  
    32  // ErrAmbiguous indicates that a plugin was not found locally.
    33  type ErrAmbiguous string
    34  
    35  func (name ErrAmbiguous) Error() string {
    36  	return fmt.Sprintf("multiple plugins found for %q", string(name))
    37  }
    38  
    39  // GetV2Plugin retreives a plugin by name, id or partial ID.
    40  func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
    41  	ps.RLock()
    42  	defer ps.RUnlock()
    43  
    44  	id, err := ps.resolvePluginID(refOrID)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	p, idOk := ps.plugins[id]
    50  	if !idOk {
    51  		return nil, errors.WithStack(ErrNotFound(id))
    52  	}
    53  
    54  	return p, nil
    55  }
    56  
    57  // validateName returns error if name is already reserved. always call with lock and full name
    58  func (ps *Store) validateName(name string) error {
    59  	for _, p := range ps.plugins {
    60  		if p.Name() == name {
    61  			return errors.Errorf("plugin %q already exists", name)
    62  		}
    63  	}
    64  	return nil
    65  }
    66  
    67  // GetAll retreives all plugins.
    68  func (ps *Store) GetAll() map[string]*v2.Plugin {
    69  	ps.RLock()
    70  	defer ps.RUnlock()
    71  	return ps.plugins
    72  }
    73  
    74  // SetAll initialized plugins during daemon restore.
    75  func (ps *Store) SetAll(plugins map[string]*v2.Plugin) {
    76  	ps.Lock()
    77  	defer ps.Unlock()
    78  	ps.plugins = plugins
    79  }
    80  
    81  func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin {
    82  	ps.RLock()
    83  	defer ps.RUnlock()
    84  
    85  	result := make([]plugingetter.CompatPlugin, 0, 1)
    86  	for _, p := range ps.plugins {
    87  		if p.IsEnabled() {
    88  			if _, err := p.FilterByCap(capability); err == nil {
    89  				result = append(result, p)
    90  			}
    91  		}
    92  	}
    93  	return result
    94  }
    95  
    96  // SetState sets the active state of the plugin and updates plugindb.
    97  func (ps *Store) SetState(p *v2.Plugin, state bool) {
    98  	ps.Lock()
    99  	defer ps.Unlock()
   100  
   101  	p.PluginObj.Enabled = state
   102  }
   103  
   104  // Add adds a plugin to memory and plugindb.
   105  // An error will be returned if there is a collision.
   106  func (ps *Store) Add(p *v2.Plugin) error {
   107  	ps.Lock()
   108  	defer ps.Unlock()
   109  
   110  	if v, exist := ps.plugins[p.GetID()]; exist {
   111  		return fmt.Errorf("plugin %q has the same ID %s as %q", p.Name(), p.GetID(), v.Name())
   112  	}
   113  	ps.plugins[p.GetID()] = p
   114  	return nil
   115  }
   116  
   117  // Remove removes a plugin from memory and plugindb.
   118  func (ps *Store) Remove(p *v2.Plugin) {
   119  	ps.Lock()
   120  	delete(ps.plugins, p.GetID())
   121  	ps.Unlock()
   122  }
   123  
   124  // Get returns an enabled plugin matching the given name and capability.
   125  func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) {
   126  	var (
   127  		p   *v2.Plugin
   128  		err error
   129  	)
   130  
   131  	// Lookup using new model.
   132  	if ps != nil {
   133  		p, err = ps.GetV2Plugin(name)
   134  		if err == nil {
   135  			p.AddRefCount(mode)
   136  			if p.IsEnabled() {
   137  				return p.FilterByCap(capability)
   138  			}
   139  			// Plugin was found but it is disabled, so we should not fall back to legacy plugins
   140  			// but we should error out right away
   141  			return nil, ErrNotFound(name)
   142  		}
   143  		if _, ok := errors.Cause(err).(ErrNotFound); !ok {
   144  			return nil, err
   145  		}
   146  	}
   147  
   148  	// Lookup using legacy model.
   149  	if allowV1PluginsFallback {
   150  		p, err := plugins.Get(name, capability)
   151  		if err != nil {
   152  			return nil, fmt.Errorf("legacy plugin: %v", err)
   153  		}
   154  		return p, nil
   155  	}
   156  
   157  	return nil, err
   158  }
   159  
   160  // GetAllManagedPluginsByCap returns a list of managed plugins matching the given capability.
   161  func (ps *Store) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin {
   162  	return ps.getAllByCap(capability)
   163  }
   164  
   165  // GetAllByCap returns a list of enabled plugins matching the given capability.
   166  func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
   167  	result := make([]plugingetter.CompatPlugin, 0, 1)
   168  
   169  	/* Daemon start always calls plugin.Init thereby initializing a store.
   170  	 * So store on experimental builds can never be nil, even while
   171  	 * handling legacy plugins. However, there are legacy plugin unit
   172  	 * tests where the volume subsystem directly talks with the plugin,
   173  	 * bypassing the daemon. For such tests, this check is necessary.
   174  	 */
   175  	if ps != nil {
   176  		ps.RLock()
   177  		result = ps.getAllByCap(capability)
   178  		ps.RUnlock()
   179  	}
   180  
   181  	// Lookup with legacy model
   182  	if allowV1PluginsFallback {
   183  		pl, err := plugins.GetAll(capability)
   184  		if err != nil {
   185  			return nil, fmt.Errorf("legacy plugin: %v", err)
   186  		}
   187  		for _, p := range pl {
   188  			result = append(result, p)
   189  		}
   190  	}
   191  	return result, nil
   192  }
   193  
   194  // Handle sets a callback for a given capability. It is only used by network
   195  // and ipam drivers during plugin registration. The callback registers the
   196  // driver with the subsystem (network, ipam).
   197  func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) {
   198  	pluginType := fmt.Sprintf("docker.%s/%s", strings.ToLower(capability), defaultAPIVersion)
   199  
   200  	// Register callback with new plugin model.
   201  	ps.Lock()
   202  	handlers, ok := ps.handlers[pluginType]
   203  	if !ok {
   204  		handlers = []func(string, *plugins.Client){}
   205  	}
   206  	handlers = append(handlers, callback)
   207  	ps.handlers[pluginType] = handlers
   208  	ps.Unlock()
   209  
   210  	// Register callback with legacy plugin model.
   211  	if allowV1PluginsFallback {
   212  		plugins.Handle(capability, callback)
   213  	}
   214  }
   215  
   216  // CallHandler calls the registered callback. It is invoked during plugin enable.
   217  func (ps *Store) CallHandler(p *v2.Plugin) {
   218  	for _, typ := range p.GetTypes() {
   219  		for _, handler := range ps.handlers[typ.String()] {
   220  			handler(p.Name(), p.Client())
   221  		}
   222  	}
   223  }
   224  
   225  func (ps *Store) resolvePluginID(idOrName string) (string, error) {
   226  	ps.RLock() // todo: fix
   227  	defer ps.RUnlock()
   228  
   229  	if validFullID.MatchString(idOrName) {
   230  		return idOrName, nil
   231  	}
   232  
   233  	ref, err := reference.ParseNamed(idOrName)
   234  	if err != nil {
   235  		return "", errors.WithStack(ErrNotFound(idOrName))
   236  	}
   237  	if _, ok := ref.(reference.Canonical); ok {
   238  		logrus.Warnf("canonical references cannot be resolved: %v", ref.String())
   239  		return "", errors.WithStack(ErrNotFound(idOrName))
   240  	}
   241  
   242  	fullRef := reference.WithDefaultTag(ref)
   243  
   244  	for _, p := range ps.plugins {
   245  		if p.PluginObj.Name == fullRef.String() {
   246  			return p.PluginObj.ID, nil
   247  		}
   248  	}
   249  
   250  	var found *v2.Plugin
   251  	for id, p := range ps.plugins { // this can be optimized
   252  		if strings.HasPrefix(id, idOrName) {
   253  			if found != nil {
   254  				return "", errors.WithStack(ErrAmbiguous(idOrName))
   255  			}
   256  			found = p
   257  		}
   258  	}
   259  	if found == nil {
   260  		return "", errors.WithStack(ErrNotFound(idOrName))
   261  	}
   262  	return found.PluginObj.ID, nil
   263  }