github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/plugin/store.go (about)

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