package plugin // import "github.com/docker/docker/plugin" import ( "fmt" "strings" "github.com/docker/distribution/reference" "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/plugingetter" "github.com/docker/docker/pkg/plugins" v2 "github.com/docker/docker/plugin/v2" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // allowV1PluginsFallback determines daemon's support for V1 plugins. // When the time comes to remove support for V1 plugins, flipping // this bool is all that will be needed. const allowV1PluginsFallback = true // defaultAPIVersion is the version of the plugin API for volume, network, // IPAM and authz. This is a very stable API. When we update this API, then // pluginType should include a version. e.g. "networkdriver/2.0". const defaultAPIVersion = "1.0" // GetV2Plugin retrieves a plugin by name, id or partial ID. func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) { ps.RLock() defer ps.RUnlock() id, err := ps.resolvePluginID(refOrID) if err != nil { return nil, err } p, idOk := ps.plugins[id] if !idOk { return nil, errors.WithStack(errNotFound(id)) } return p, nil } // validateName returns error if name is already reserved. always call with lock and full name func (ps *Store) validateName(name string) error { for _, p := range ps.plugins { if p.Name() == name { return alreadyExistsError(name) } } return nil } // GetAll retrieves all plugins. func (ps *Store) GetAll() map[string]*v2.Plugin { ps.RLock() defer ps.RUnlock() return ps.plugins } // SetAll initialized plugins during daemon restore. func (ps *Store) SetAll(plugins map[string]*v2.Plugin) { ps.Lock() defer ps.Unlock() for _, p := range plugins { ps.setSpecOpts(p) } ps.plugins = plugins } func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin { ps.RLock() defer ps.RUnlock() result := make([]plugingetter.CompatPlugin, 0, 1) for _, p := range ps.plugins { if p.IsEnabled() { if _, err := p.FilterByCap(capability); err == nil { result = append(result, p) } } } return result } // SetState sets the active state of the plugin and updates plugindb. func (ps *Store) SetState(p *v2.Plugin, state bool) { ps.Lock() defer ps.Unlock() p.PluginObj.Enabled = state } func (ps *Store) setSpecOpts(p *v2.Plugin) { var specOpts []SpecOpt for _, typ := range p.GetTypes() { opts, ok := ps.specOpts[typ.String()] if ok { specOpts = append(specOpts, opts...) } } p.SetSpecOptModifier(func(s *specs.Spec) { for _, o := range specOpts { o(s) } }) } // Add adds a plugin to memory and plugindb. // An error will be returned if there is a collision. func (ps *Store) Add(p *v2.Plugin) error { ps.Lock() defer ps.Unlock() if v, exist := ps.plugins[p.GetID()]; exist { return fmt.Errorf("plugin %q has the same ID %s as %q", p.Name(), p.GetID(), v.Name()) } ps.setSpecOpts(p) ps.plugins[p.GetID()] = p return nil } // Remove removes a plugin from memory and plugindb. func (ps *Store) Remove(p *v2.Plugin) { ps.Lock() delete(ps.plugins, p.GetID()) ps.Unlock() } // Get returns an enabled plugin matching the given name and capability. func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) { // Lookup using new model. if ps != nil { p, err := ps.GetV2Plugin(name) if err == nil { if p.IsEnabled() { fp, err := p.FilterByCap(capability) if err != nil { return nil, err } p.AddRefCount(mode) return fp, nil } // Plugin was found but it is disabled, so we should not fall back to legacy plugins // but we should error out right away return nil, errDisabled(name) } var ierr errNotFound if !errors.As(err, &ierr) { return nil, err } } if !allowV1PluginsFallback { return nil, errNotFound(name) } p, err := plugins.Get(name, capability) if err == nil { return p, nil } if errors.Is(err, plugins.ErrNotFound) { return nil, errNotFound(name) } return nil, errors.Wrap(errdefs.System(err), "legacy plugin") } // GetAllManagedPluginsByCap returns a list of managed plugins matching the given capability. func (ps *Store) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin { return ps.getAllByCap(capability) } // GetAllByCap returns a list of enabled plugins matching the given capability. func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) { result := make([]plugingetter.CompatPlugin, 0, 1) /* Daemon start always calls plugin.Init thereby initializing a store. * So store on experimental builds can never be nil, even while * handling legacy plugins. However, there are legacy plugin unit * tests where the volume subsystem directly talks with the plugin, * bypassing the daemon. For such tests, this check is necessary. */ if ps != nil { result = ps.getAllByCap(capability) } // Lookup with legacy model if allowV1PluginsFallback { l := plugins.NewLocalRegistry() pl, err := l.GetAll(capability) if err != nil { return nil, errors.Wrap(errdefs.System(err), "legacy plugin") } for _, p := range pl { result = append(result, p) } } return result, nil } func pluginType(cap string) string { return fmt.Sprintf("docker.%s/%s", strings.ToLower(cap), defaultAPIVersion) } // Handle sets a callback for a given capability. It is only used by network // and ipam drivers during plugin registration. The callback registers the // driver with the subsystem (network, ipam). func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) { typ := pluginType(capability) // Register callback with new plugin model. ps.Lock() handlers, ok := ps.handlers[typ] if !ok { handlers = []func(string, *plugins.Client){} } handlers = append(handlers, callback) ps.handlers[typ] = handlers ps.Unlock() // Register callback with legacy plugin model. if allowV1PluginsFallback { plugins.Handle(capability, callback) } } // RegisterRuntimeOpt stores a list of SpecOpts for the provided capability. // These options are applied to the runtime spec before a plugin is started for the specified capability. func (ps *Store) RegisterRuntimeOpt(cap string, opts ...SpecOpt) { ps.Lock() defer ps.Unlock() typ := pluginType(cap) ps.specOpts[typ] = append(ps.specOpts[typ], opts...) } // CallHandler calls the registered callback. It is invoked during plugin enable. func (ps *Store) CallHandler(p *v2.Plugin) { for _, typ := range p.GetTypes() { for _, handler := range ps.handlers[typ.String()] { handler(p.Name(), p.Client()) } } } // resolvePluginID must be protected by ps.RLock func (ps *Store) resolvePluginID(idOrName string) (string, error) { if validFullID.MatchString(idOrName) { return idOrName, nil } ref, err := reference.ParseNormalizedNamed(idOrName) if err != nil { return "", errors.WithStack(errNotFound(idOrName)) } if _, ok := ref.(reference.Canonical); ok { logrus.Warnf("canonical references cannot be resolved: %v", reference.FamiliarString(ref)) return "", errors.WithStack(errNotFound(idOrName)) } ref = reference.TagNameOnly(ref) for _, p := range ps.plugins { if p.PluginObj.Name == reference.FamiliarString(ref) { return p.PluginObj.ID, nil } } var found *v2.Plugin for id, p := range ps.plugins { // this can be optimized if strings.HasPrefix(id, idOrName) { if found != nil { return "", errors.WithStack(errAmbiguous(idOrName)) } found = p } } if found == nil { return "", errors.WithStack(errNotFound(idOrName)) } return found.PluginObj.ID, nil }