github.com/rish1988/moby@v25.0.2+incompatible/plugin/store.go (about)

     1  package plugin // import "github.com/docker/docker/plugin"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/containerd/log"
     9  	"github.com/distribution/reference"
    10  	"github.com/docker/docker/errdefs"
    11  	"github.com/docker/docker/pkg/plugingetter"
    12  	"github.com/docker/docker/pkg/plugins"
    13  	v2 "github.com/docker/docker/plugin/v2"
    14  	specs "github.com/opencontainers/runtime-spec/specs-go"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // allowV1PluginsFallback determines daemon's support for V1 plugins.
    19  // When the time comes to remove support for V1 plugins, flipping
    20  // this bool is all that will be needed.
    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  const defaultAPIVersion = "1.0"
    27  
    28  // GetV2Plugin retrieves a plugin by name, id or partial ID.
    29  func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
    30  	ps.RLock()
    31  	defer ps.RUnlock()
    32  
    33  	id, err := ps.resolvePluginID(refOrID)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	p, idOk := ps.plugins[id]
    39  	if !idOk {
    40  		return nil, errors.WithStack(errNotFound(id))
    41  	}
    42  
    43  	return p, nil
    44  }
    45  
    46  // validateName returns error if name is already reserved. always call with lock and full name
    47  func (ps *Store) validateName(name string) error {
    48  	for _, p := range ps.plugins {
    49  		if p.Name() == name {
    50  			return alreadyExistsError(name)
    51  		}
    52  	}
    53  	return nil
    54  }
    55  
    56  // GetAll retrieves all plugins.
    57  func (ps *Store) GetAll() map[string]*v2.Plugin {
    58  	ps.RLock()
    59  	defer ps.RUnlock()
    60  	return ps.plugins
    61  }
    62  
    63  // SetAll initialized plugins during daemon restore.
    64  func (ps *Store) SetAll(plugins map[string]*v2.Plugin) {
    65  	ps.Lock()
    66  	defer ps.Unlock()
    67  
    68  	for _, p := range plugins {
    69  		ps.setSpecOpts(p)
    70  	}
    71  	ps.plugins = plugins
    72  }
    73  
    74  func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin {
    75  	ps.RLock()
    76  	defer ps.RUnlock()
    77  
    78  	result := make([]plugingetter.CompatPlugin, 0, 1)
    79  	for _, p := range ps.plugins {
    80  		if p.IsEnabled() {
    81  			if _, err := p.FilterByCap(capability); err == nil {
    82  				result = append(result, p)
    83  			}
    84  		}
    85  	}
    86  	return result
    87  }
    88  
    89  // SetState sets the active state of the plugin and updates plugindb.
    90  func (ps *Store) SetState(p *v2.Plugin, state bool) {
    91  	ps.Lock()
    92  	defer ps.Unlock()
    93  
    94  	p.PluginObj.Enabled = state
    95  }
    96  
    97  func (ps *Store) setSpecOpts(p *v2.Plugin) {
    98  	var specOpts []SpecOpt
    99  	for _, typ := range p.GetTypes() {
   100  		opts, ok := ps.specOpts[typ.String()]
   101  		if ok {
   102  			specOpts = append(specOpts, opts...)
   103  		}
   104  	}
   105  
   106  	p.SetSpecOptModifier(func(s *specs.Spec) {
   107  		for _, o := range specOpts {
   108  			o(s)
   109  		}
   110  	})
   111  }
   112  
   113  // Add adds a plugin to memory and plugindb.
   114  // An error will be returned if there is a collision.
   115  func (ps *Store) Add(p *v2.Plugin) error {
   116  	ps.Lock()
   117  	defer ps.Unlock()
   118  
   119  	if v, exist := ps.plugins[p.GetID()]; exist {
   120  		return fmt.Errorf("plugin %q has the same ID %s as %q", p.Name(), p.GetID(), v.Name())
   121  	}
   122  
   123  	ps.setSpecOpts(p)
   124  
   125  	ps.plugins[p.GetID()] = p
   126  	return nil
   127  }
   128  
   129  // Remove removes a plugin from memory and plugindb.
   130  func (ps *Store) Remove(p *v2.Plugin) {
   131  	ps.Lock()
   132  	delete(ps.plugins, p.GetID())
   133  	ps.Unlock()
   134  }
   135  
   136  // Get returns an enabled plugin matching the given name and capability.
   137  func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) {
   138  	// Lookup using new model.
   139  	if ps != nil {
   140  		p, err := ps.GetV2Plugin(name)
   141  		if err == nil {
   142  			if p.IsEnabled() {
   143  				fp, err := p.FilterByCap(capability)
   144  				if err != nil {
   145  					return nil, err
   146  				}
   147  				p.AddRefCount(mode)
   148  				return fp, nil
   149  			}
   150  
   151  			// Plugin was found but it is disabled, so we should not fall back to legacy plugins
   152  			// but we should error out right away
   153  			return nil, errDisabled(name)
   154  		}
   155  		var ierr errNotFound
   156  		if !errors.As(err, &ierr) {
   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.Is(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  		result = ps.getAllByCap(capability)
   192  	}
   193  
   194  	// Lookup with legacy model
   195  	if allowV1PluginsFallback {
   196  		l := plugins.NewLocalRegistry()
   197  		pl, err := l.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  		log.G(context.TODO()).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  }