github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+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  	v2 "github.com/docker/docker/plugin/v2"
    12  	specs "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  		var ierr errNotFound
   157  		if !errors.As(err, &ierr) {
   158  			return nil, err
   159  		}
   160  	}
   161  
   162  	if !allowV1PluginsFallback {
   163  		return nil, errNotFound(name)
   164  	}
   165  
   166  	p, err := plugins.Get(name, capability)
   167  	if err == nil {
   168  		return p, nil
   169  	}
   170  	if errors.Is(err, plugins.ErrNotFound) {
   171  		return nil, errNotFound(name)
   172  	}
   173  	return nil, errors.Wrap(errdefs.System(err), "legacy plugin")
   174  }
   175  
   176  // GetAllManagedPluginsByCap returns a list of managed plugins matching the given capability.
   177  func (ps *Store) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin {
   178  	return ps.getAllByCap(capability)
   179  }
   180  
   181  // GetAllByCap returns a list of enabled plugins matching the given capability.
   182  func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
   183  	result := make([]plugingetter.CompatPlugin, 0, 1)
   184  
   185  	/* Daemon start always calls plugin.Init thereby initializing a store.
   186  	 * So store on experimental builds can never be nil, even while
   187  	 * handling legacy plugins. However, there are legacy plugin unit
   188  	 * tests where the volume subsystem directly talks with the plugin,
   189  	 * bypassing the daemon. For such tests, this check is necessary.
   190  	 */
   191  	if ps != nil {
   192  		result = ps.getAllByCap(capability)
   193  	}
   194  
   195  	// Lookup with legacy model
   196  	if allowV1PluginsFallback {
   197  		pl, err := plugins.GetAll(capability)
   198  		if err != nil {
   199  			return nil, errors.Wrap(errdefs.System(err), "legacy plugin")
   200  		}
   201  		for _, p := range pl {
   202  			result = append(result, p)
   203  		}
   204  	}
   205  	return result, nil
   206  }
   207  
   208  func pluginType(cap string) string {
   209  	return fmt.Sprintf("docker.%s/%s", strings.ToLower(cap), defaultAPIVersion)
   210  }
   211  
   212  // Handle sets a callback for a given capability. It is only used by network
   213  // and ipam drivers during plugin registration. The callback registers the
   214  // driver with the subsystem (network, ipam).
   215  func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) {
   216  	typ := pluginType(capability)
   217  
   218  	// Register callback with new plugin model.
   219  	ps.Lock()
   220  	handlers, ok := ps.handlers[typ]
   221  	if !ok {
   222  		handlers = []func(string, *plugins.Client){}
   223  	}
   224  	handlers = append(handlers, callback)
   225  	ps.handlers[typ] = handlers
   226  	ps.Unlock()
   227  
   228  	// Register callback with legacy plugin model.
   229  	if allowV1PluginsFallback {
   230  		plugins.Handle(capability, callback)
   231  	}
   232  }
   233  
   234  // RegisterRuntimeOpt stores a list of SpecOpts for the provided capability.
   235  // These options are applied to the runtime spec before a plugin is started for the specified capability.
   236  func (ps *Store) RegisterRuntimeOpt(cap string, opts ...SpecOpt) {
   237  	ps.Lock()
   238  	defer ps.Unlock()
   239  	typ := pluginType(cap)
   240  	ps.specOpts[typ] = append(ps.specOpts[typ], opts...)
   241  }
   242  
   243  // CallHandler calls the registered callback. It is invoked during plugin enable.
   244  func (ps *Store) CallHandler(p *v2.Plugin) {
   245  	for _, typ := range p.GetTypes() {
   246  		for _, handler := range ps.handlers[typ.String()] {
   247  			handler(p.Name(), p.Client())
   248  		}
   249  	}
   250  }
   251  
   252  // resolvePluginID must be protected by ps.RLock
   253  func (ps *Store) resolvePluginID(idOrName string) (string, error) {
   254  	if validFullID.MatchString(idOrName) {
   255  		return idOrName, nil
   256  	}
   257  
   258  	ref, err := reference.ParseNormalizedNamed(idOrName)
   259  	if err != nil {
   260  		return "", errors.WithStack(errNotFound(idOrName))
   261  	}
   262  	if _, ok := ref.(reference.Canonical); ok {
   263  		logrus.Warnf("canonical references cannot be resolved: %v", reference.FamiliarString(ref))
   264  		return "", errors.WithStack(errNotFound(idOrName))
   265  	}
   266  
   267  	ref = reference.TagNameOnly(ref)
   268  
   269  	for _, p := range ps.plugins {
   270  		if p.PluginObj.Name == reference.FamiliarString(ref) {
   271  			return p.PluginObj.ID, nil
   272  		}
   273  	}
   274  
   275  	var found *v2.Plugin
   276  	for id, p := range ps.plugins { // this can be optimized
   277  		if strings.HasPrefix(id, idOrName) {
   278  			if found != nil {
   279  				return "", errors.WithStack(errAmbiguous(idOrName))
   280  			}
   281  			found = p
   282  		}
   283  	}
   284  	if found == nil {
   285  		return "", errors.WithStack(errNotFound(idOrName))
   286  	}
   287  	return found.PluginObj.ID, nil
   288  }