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