diff --git a/api/types/plugin.go b/api/types/plugin.go index 601c0ac12a..11091ef274 100644 --- a/api/types/plugin.go +++ b/api/types/plugin.go @@ -1,5 +1,3 @@ -// +build experimental - package types import ( diff --git a/daemon/daemon.go b/daemon/daemon.go old mode 100755 new mode 100644 index cc537ae973..9911f3d2a3 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -47,6 +47,7 @@ import ( "github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/truncindex" + pluginstore "github.com/docker/docker/plugin/store" "github.com/docker/docker/reference" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" @@ -94,6 +95,7 @@ type Daemon struct { gidMaps []idtools.IDMap layerStore layer.Store imageStore image.Store + pluginStore *pluginstore.Store nameIndex *registrar.Registrar linkIndex *linkIndex containerd libcontainerd.Client @@ -548,6 +550,9 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot if driverName == "" { driverName = config.GraphDriver } + + d.pluginStore = pluginstore.NewStore(config.Root) + d.layerStore, err = layer.NewStoreFromOptions(layer.StoreOptions{ StorePath: config.Root, MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"), @@ -555,6 +560,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot GraphDriverOptions: config.GraphOptions, UIDMaps: uidMaps, GIDMaps: gidMaps, + PluginGetter: d.pluginStore, }) if err != nil { return nil, err @@ -911,6 +917,8 @@ func (daemon *Daemon) configureVolumes(rootUID, rootGID int) (*store.VolumeStore return nil, err } + volumedrivers.RegisterPluginGetter(daemon.pluginStore) + if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) { return nil, fmt.Errorf("local volume driver could not be registered") } diff --git a/daemon/daemon_experimental.go b/daemon/daemon_experimental.go index 149e4c71bc..22795eec83 100644 --- a/daemon/daemon_experimental.go +++ b/daemon/daemon_experimental.go @@ -13,7 +13,7 @@ func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container. } func pluginInit(d *Daemon, cfg *Config, remote libcontainerd.Remote) error { - return plugin.Init(cfg.Root, remote, d.RegistryService, cfg.LiveRestoreEnabled, d.LogPluginEvent) + return plugin.Init(cfg.Root, d.pluginStore, remote, d.RegistryService, cfg.LiveRestoreEnabled, d.LogPluginEvent) } func pluginShutdown() { diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 434669da20..19bd8afad5 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/plugin/getter" ) // FsMagic unsigned id of the filesystem in use. @@ -134,11 +135,11 @@ func Register(name string, initFunc InitFunc) error { } // GetDriver initializes and returns the registered driver -func GetDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) { +func GetDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap, plugingetter getter.PluginGetter) (Driver, error) { if initFunc, exists := drivers[name]; exists { return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps) } - if pluginDriver, err := lookupPlugin(name, home, options); err == nil { + if pluginDriver, err := lookupPlugin(name, home, options, plugingetter); err == nil { return pluginDriver, nil } logrus.Errorf("Failed to GetDriver graph %s %s", name, home) @@ -155,10 +156,10 @@ func getBuiltinDriver(name, home string, options []string, uidMaps, gidMaps []id } // New creates the driver and initializes it at the specified root. -func New(root string, name string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) { +func New(root string, name string, options []string, uidMaps, gidMaps []idtools.IDMap, plugingetter getter.PluginGetter) (Driver, error) { if name != "" { logrus.Debugf("[graphdriver] trying provided driver: %s", name) // so the logs show specified driver - return GetDriver(name, root, options, uidMaps, gidMaps) + return GetDriver(name, root, options, uidMaps, gidMaps, plugingetter) } // Guess for prior driver diff --git a/daemon/graphdriver/graphtest/graphtest_unix.go b/daemon/graphdriver/graphtest/graphtest_unix.go index 68be47b171..70fba957f9 100644 --- a/daemon/graphdriver/graphtest/graphtest_unix.go +++ b/daemon/graphdriver/graphtest/graphtest_unix.go @@ -41,7 +41,7 @@ func newDriver(t testing.TB, name string, options []string) *Driver { t.Fatal(err) } - d, err := graphdriver.GetDriver(name, root, options, nil, nil) + d, err := graphdriver.GetDriver(name, root, options, nil, nil, nil) if err != nil { t.Logf("graphdriver: %v\n", err) if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites || err == graphdriver.ErrIncompatibleFS { diff --git a/daemon/graphdriver/plugin.go b/daemon/graphdriver/plugin.go index 9f172b72d4..9a044d2f86 100644 --- a/daemon/graphdriver/plugin.go +++ b/daemon/graphdriver/plugin.go @@ -6,7 +6,7 @@ import ( "fmt" "io" - "github.com/docker/docker/pkg/plugins" + "github.com/docker/docker/plugin/getter" ) type pluginClient interface { @@ -18,8 +18,8 @@ type pluginClient interface { SendFile(string, io.Reader, interface{}) error } -func lookupPlugin(name, home string, opts []string) (Driver, error) { - pl, err := plugins.Get(name, "GraphDriver") +func lookupPlugin(name, home string, opts []string, pluginGetter getter.PluginGetter) (Driver, error) { + pl, err := pluginGetter.Get(name, "GraphDriver", getter.LOOKUP) if err != nil { return nil, fmt.Errorf("Error looking up graphdriver plugin %s: %v", name, err) } diff --git a/daemon/graphdriver/plugin_unsupported.go b/daemon/graphdriver/plugin_unsupported.go index daa7a170e4..a16ef06858 100644 --- a/daemon/graphdriver/plugin_unsupported.go +++ b/daemon/graphdriver/plugin_unsupported.go @@ -2,6 +2,8 @@ package graphdriver -func lookupPlugin(name, home string, opts []string) (Driver, error) { +import "github.com/docker/docker/plugin/getter" + +func lookupPlugin(name, home string, opts []string, plugingetter getter.PluginGetter) (Driver, error) { return nil, ErrNotSupported } diff --git a/layer/layer_store.go b/layer/layer_store.go index 6d5cb2599d..a587b01abf 100644 --- a/layer/layer_store.go +++ b/layer/layer_store.go @@ -14,6 +14,7 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/plugin/getter" "github.com/vbatts/tar-split/tar/asm" "github.com/vbatts/tar-split/tar/storage" ) @@ -44,6 +45,7 @@ type StoreOptions struct { GraphDriverOptions []string UIDMaps []idtools.IDMap GIDMaps []idtools.IDMap + PluginGetter getter.PluginGetter } // NewStoreFromOptions creates a new Store instance @@ -53,7 +55,8 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) { options.GraphDriver, options.GraphDriverOptions, options.UIDMaps, - options.GIDMaps) + options.GIDMaps, + options.PluginGetter) if err != nil { return nil, fmt.Errorf("error initializing graphdriver: %v", err) } diff --git a/layer/layer_test.go b/layer/layer_test.go index 9df71b92a6..2d9e1e9b55 100644 --- a/layer/layer_test.go +++ b/layer/layer_test.go @@ -39,7 +39,7 @@ func newVFSGraphDriver(td string) (graphdriver.Driver, error) { }, } - return graphdriver.GetDriver("vfs", td, nil, uidMap, gidMap) + return graphdriver.GetDriver("vfs", td, nil, uidMap, gidMap, nil) } func newTestGraphDriver(t *testing.T) (graphdriver.Driver, func()) { diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index debcd087c9..9385b9b1bc 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -83,8 +83,8 @@ func (p *Plugin) Client() *Client { return p.client } -// IsLegacy returns true for legacy plugins and false otherwise. -func (p *Plugin) IsLegacy() bool { +// IsV1 returns true for V1 plugins and false otherwise. +func (p *Plugin) IsV1() bool { return true } diff --git a/plugin/backend.go b/plugin/backend.go index 56d361076b..826075f3d3 100644 --- a/plugin/backend.go +++ b/plugin/backend.go @@ -17,7 +17,6 @@ import ( "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/plugin/distribution" "github.com/docker/docker/plugin/v2" - "github.com/docker/docker/reference" ) // Disable deactivates a plugin, which implies that they cannot be used by containers. @@ -57,9 +56,9 @@ func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) { // Pull pulls a plugin and computes the privileges required to install it. func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) { - ref, err := reference.ParseNamed(name) + ref, err := distribution.GetRef(name) if err != nil { - logrus.Debugf("error in reference.ParseNamed: %v", err) + logrus.Debugf("error in distribution.GetRef: %v", err) return nil, err } name = ref.String() @@ -76,7 +75,7 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A return nil, err } - pd, err := distribution.Pull(name, pm.registryService, metaHeader, authConfig) + pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig) if err != nil { logrus.Debugf("error in distribution.Pull(): %v", err) return nil, err @@ -87,10 +86,7 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A return nil, err } - var tag string - if ref, ok := ref.(reference.NamedTagged); ok { - tag = ref.Tag() - } + tag := distribution.GetTag(ref) p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, tag) if err := p.InitPlugin(pm.libRoot); err != nil { return nil, err diff --git a/plugin/distribution/pull.go b/plugin/distribution/pull.go index d061744558..a4824539d5 100644 --- a/plugin/distribution/pull.go +++ b/plugin/distribution/pull.go @@ -62,14 +62,26 @@ func (pd *pullData) Layer() (io.ReadCloser, error) { return rsc, nil } -// Pull downloads the plugin from Store -func Pull(name string, rs registry.Service, metaheader http.Header, authConfig *types.AuthConfig) (PullData, error) { +// GetRef returns the distribution reference for a given name. +func GetRef(name string) (reference.Named, error) { ref, err := reference.ParseNamed(name) if err != nil { - logrus.Debugf("pull.go: error in ParseNamed: %v", err) return nil, err } + return ref, nil +} +// GetTag returns the tag associated with the given reference name. +func GetTag(ref reference.Named) string { + tag := DefaultTag + if ref, ok := ref.(reference.NamedTagged); ok { + tag = ref.Tag() + } + return tag +} + +// Pull downloads the plugin from Store +func Pull(ref reference.Named, rs registry.Service, metaheader http.Header, authConfig *types.AuthConfig) (PullData, error) { repoInfo, err := rs.ResolveRepository(ref) if err != nil { logrus.Debugf("pull.go: error in ResolveRepository: %v", err) diff --git a/plugin/getter/interface.go b/plugin/getter/interface.go new file mode 100644 index 0000000000..1c1977d300 --- /dev/null +++ b/plugin/getter/interface.go @@ -0,0 +1,25 @@ +package getter + +import "github.com/docker/docker/pkg/plugins" + +const ( + // LOOKUP doesn't update RefCount + LOOKUP = 0 + // CREATE increments RefCount + CREATE = 1 + // REMOVE decrements RefCount + REMOVE = -1 +) + +// CompatPlugin is a abstraction to handle both v2(new) and v1(legacy) plugins. +type CompatPlugin interface { + Client() *plugins.Client + Name() string + IsV1() bool +} + +// PluginGetter is the interface implemented by Store +type PluginGetter interface { + Get(name, capability string, mode int) (CompatPlugin, error) + GetAllByCap(capability string) ([]CompatPlugin, error) +} diff --git a/plugin/manager.go b/plugin/manager.go index 4fa6b143b7..cc7efa1223 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -35,7 +35,7 @@ type Manager struct { sync.RWMutex libRoot string runRoot string - pluginStore *store.PluginStore + pluginStore *store.Store containerdClient libcontainerd.Client registryService registry.Service liveRestore bool @@ -50,7 +50,7 @@ func GetManager() *Manager { // Init (was NewManager) instantiates the singleton Manager. // TODO: revert this to NewManager once we get rid of all the singletons. -func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) { +func Init(root string, ps *store.Store, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) { if manager != nil { return nil } @@ -59,7 +59,7 @@ func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRes manager = &Manager{ libRoot: root, runRoot: "/run/docker", - pluginStore: store.NewPluginStore(root), + pluginStore: ps, registryService: rs, liveRestore: liveRestore, pluginEventLogger: evL, diff --git a/plugin/store/defs.go b/plugin/store/defs.go new file mode 100644 index 0000000000..c6c76766a7 --- /dev/null +++ b/plugin/store/defs.go @@ -0,0 +1,31 @@ +package store + +import ( + "path/filepath" + "sync" + + "github.com/docker/docker/pkg/plugins" + "github.com/docker/docker/plugin/v2" +) + +// Store manages the plugin inventory in memory and on-disk +type Store struct { + sync.RWMutex + plugins map[string]*v2.Plugin + /* handlers are necessary for transition path of legacy plugins + * to the new model. Legacy plugins use Handle() for registering an + * activation callback.*/ + handlers map[string]func(string, *plugins.Client) + nameToID map[string]string + plugindb string +} + +// NewStore creates a Store. +func NewStore(libRoot string) *Store { + return &Store{ + plugins: make(map[string]*v2.Plugin), + handlers: make(map[string]func(string, *plugins.Client)), + nameToID: make(map[string]string), + plugindb: filepath.Join(libRoot, "plugins", "plugins.json"), + } +} diff --git a/plugin/store/interface.go b/plugin/store/interface.go deleted file mode 100644 index d33a5f5dde..0000000000 --- a/plugin/store/interface.go +++ /dev/null @@ -1,19 +0,0 @@ -package store - -import "github.com/docker/docker/pkg/plugins" - -const ( - // LOOKUP doesn't update RefCount - LOOKUP = 0 - // CREATE increments RefCount - CREATE = 1 - // REMOVE decrements RefCount - REMOVE = -1 -) - -// CompatPlugin is an abstraction to handle both new and legacy (v1) plugins. -type CompatPlugin interface { - Client() *plugins.Client - Name() string - IsLegacy() bool -} diff --git a/plugin/store/legacy.go b/plugin/store/legacy.go deleted file mode 100644 index 86b276fc5d..0000000000 --- a/plugin/store/legacy.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build !experimental - -package store - -import ( - "github.com/docker/docker/pkg/plugins" -) - -// FindWithCapability returns a list of plugins matching the given capability. -func FindWithCapability(capability string) ([]CompatPlugin, error) { - pl, err := plugins.GetAll(capability) - if err != nil { - return nil, err - } - result := make([]CompatPlugin, len(pl)) - for i, p := range pl { - result[i] = p - } - return result, nil -} - -// LookupWithCapability returns a plugin matching the given name and capability. -func LookupWithCapability(name, capability string, _ int) (CompatPlugin, error) { - return plugins.Get(name, capability) -} diff --git a/plugin/store/store.go b/plugin/store/store.go index 4063e762ea..2ba1b4ad94 100644 --- a/plugin/store/store.go +++ b/plugin/store/store.go @@ -1,257 +1,26 @@ -// +build experimental +// +build !experimental package store import ( - "encoding/json" - "fmt" - "path/filepath" - "strings" - "sync" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/plugins" - "github.com/docker/docker/plugin/v2" - "github.com/docker/docker/reference" + "github.com/docker/docker/plugin/getter" ) -var ( - store *PluginStore - /* 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. - */ - allowV1PluginsFallback = true -) - -// ErrNotFound indicates that a plugin was not found locally. -type ErrNotFound string - -func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) } - -// PluginStore manages the plugin inventory in memory and on-disk -type PluginStore struct { - sync.RWMutex - plugins map[string]*v2.Plugin - /* handlers are necessary for transition path of legacy plugins - * to the new model. Legacy plugins use Handle() for registering an - * activation callback.*/ - handlers map[string]func(string, *plugins.Client) - nameToID map[string]string - plugindb string -} - -// NewPluginStore creates a PluginStore. -func NewPluginStore(libRoot string) *PluginStore { - store = &PluginStore{ - plugins: make(map[string]*v2.Plugin), - handlers: make(map[string]func(string, *plugins.Client)), - nameToID: make(map[string]string), - plugindb: filepath.Join(libRoot, "plugins.json"), - } - return store -} - -// GetByName retreives a plugin by name. -func (ps *PluginStore) GetByName(name string) (*v2.Plugin, error) { - ps.RLock() - defer ps.RUnlock() - - id, nameOk := ps.nameToID[name] - if !nameOk { - return nil, ErrNotFound(name) - } - - p, idOk := ps.plugins[id] - if !idOk { - return nil, ErrNotFound(id) - } - return p, nil -} - -// GetByID retreives a plugin by ID. -func (ps *PluginStore) GetByID(id string) (*v2.Plugin, error) { - ps.RLock() - defer ps.RUnlock() - - p, idOk := ps.plugins[id] - if !idOk { - return nil, ErrNotFound(id) - } - return p, nil -} - -// GetAll retreives all plugins. -func (ps *PluginStore) GetAll() map[string]*v2.Plugin { - ps.RLock() - defer ps.RUnlock() - return ps.plugins -} - -// SetAll initialized plugins during daemon restore. -func (ps *PluginStore) SetAll(plugins map[string]*v2.Plugin) { - ps.Lock() - defer ps.Unlock() - ps.plugins = plugins -} - -func (ps *PluginStore) getByCap(name string, capability string) (*v2.Plugin, error) { - ps.RLock() - defer ps.RUnlock() - - p, err := ps.GetByName(name) +// GetAllByCap returns a list of plugins matching the given capability. +func (ps Store) GetAllByCap(capability string) ([]getter.CompatPlugin, error) { + pl, err := plugins.GetAll(capability) if err != nil { return nil, err } - return p.FilterByCap(capability) -} - -func (ps *PluginStore) getAllByCap(capability string) []CompatPlugin { - ps.RLock() - defer ps.RUnlock() - - result := make([]CompatPlugin, 0, 1) - for _, p := range ps.plugins { - 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 *PluginStore) SetState(p *v2.Plugin, state bool) { - ps.Lock() - defer ps.Unlock() - - p.PluginObj.Enabled = state - ps.updatePluginDB() -} - -// Add adds a plugin to memory and plugindb. -func (ps *PluginStore) Add(p *v2.Plugin) { - ps.Lock() - ps.plugins[p.GetID()] = p - ps.nameToID[p.Name()] = p.GetID() - ps.updatePluginDB() - ps.Unlock() -} - -// Remove removes a plugin from memory, plugindb and disk. -func (ps *PluginStore) Remove(p *v2.Plugin) { - ps.Lock() - delete(ps.plugins, p.GetID()) - delete(ps.nameToID, p.Name()) - ps.updatePluginDB() - p.RemoveFromDisk() - ps.Unlock() -} - -// Callers are expected to hold the store lock. -func (ps *PluginStore) updatePluginDB() error { - jsonData, err := json.Marshal(ps.plugins) - if err != nil { - logrus.Debugf("Error in json.Marshal: %v", err) - return err - } - ioutils.AtomicWriteFile(ps.plugindb, jsonData, 0600) - return nil -} - -// LookupWithCapability returns a plugin matching the given name and capability. -func LookupWithCapability(name, capability string, mode int) (CompatPlugin, error) { - var ( - p *v2.Plugin - err error - ) - - // Lookup using new model. - if store != nil { - fullName := name - if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate - if reference.IsNameOnly(named) { - named = reference.WithDefaultTag(named) - } - ref, ok := named.(reference.NamedTagged) - if !ok { - return nil, fmt.Errorf("invalid name: %s", named.String()) - } - fullName = ref.String() - } - p, err = store.GetByName(fullName) - if err == nil { - p.Lock() - p.RefCount += mode - p.Unlock() - return p.FilterByCap(capability) - } - if _, ok := err.(ErrNotFound); !ok { - return nil, err - } - } - - // Lookup using legacy model. - if allowV1PluginsFallback { - p, err := plugins.Get(name, capability) - if err != nil { - return nil, fmt.Errorf("legacy plugin: %v", err) - } - return p, nil - } - - return nil, err -} - -// FindWithCapability returns a list of plugins matching the given capability. -func FindWithCapability(capability string) ([]CompatPlugin, error) { - result := make([]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 store != nil { - store.RLock() - result = store.getAllByCap(capability) - store.RUnlock() - } - - // Lookup with legacy model - if allowV1PluginsFallback { - pl, err := plugins.GetAll(capability) - if err != nil { - return nil, fmt.Errorf("legacy plugin: %v", err) - } - for _, p := range pl { - result = append(result, p) - } + result := make([]getter.CompatPlugin, len(pl)) + for i, p := range pl { + result[i] = p } return result, nil } -// 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 Handle(capability string, callback func(string, *plugins.Client)) { - pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability)) - - // Register callback with new plugin model. - store.handlers[pluginType] = callback - - // Register callback with legacy plugin model. - if allowV1PluginsFallback { - plugins.Handle(capability, callback) - } -} - -// CallHandler calls the registered callback. It is invoked during plugin enable. -func (ps *PluginStore) CallHandler(p *v2.Plugin) { - for _, typ := range p.GetTypes() { - if handler := ps.handlers[typ.String()]; handler != nil { - handler(p.Name(), p.Client()) - } - } +// Get returns a plugin matching the given name and capability. +func (ps Store) Get(name, capability string, _ int) (getter.CompatPlugin, error) { + return plugins.Get(name, capability) } diff --git a/plugin/store/store_experimental.go b/plugin/store/store_experimental.go new file mode 100644 index 0000000000..a1fe10851f --- /dev/null +++ b/plugin/store/store_experimental.go @@ -0,0 +1,234 @@ +// +build experimental + +package store + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/plugins" + "github.com/docker/docker/plugin/getter" + "github.com/docker/docker/plugin/v2" + "github.com/docker/docker/reference" +) + +/* 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. + */ +var allowV1PluginsFallback = true + +// ErrNotFound indicates that a plugin was not found locally. +type ErrNotFound string + +func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) } + +// GetByName retreives a plugin by name. +func (ps *Store) GetByName(name string) (*v2.Plugin, error) { + ps.RLock() + defer ps.RUnlock() + + id, nameOk := ps.nameToID[name] + if !nameOk { + return nil, ErrNotFound(name) + } + + p, idOk := ps.plugins[id] + if !idOk { + return nil, ErrNotFound(id) + } + return p, nil +} + +// GetByID retreives a plugin by ID. +func (ps *Store) GetByID(id string) (*v2.Plugin, error) { + ps.RLock() + defer ps.RUnlock() + + p, idOk := ps.plugins[id] + if !idOk { + return nil, ErrNotFound(id) + } + return p, nil +} + +// GetAll retreives 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() + ps.plugins = plugins +} + +func (ps *Store) getByCap(name string, capability string) (*v2.Plugin, error) { + ps.RLock() + defer ps.RUnlock() + + p, err := ps.GetByName(name) + if err != nil { + return nil, err + } + return p.FilterByCap(capability) +} + +func (ps *Store) getAllByCap(capability string) []getter.CompatPlugin { + ps.RLock() + defer ps.RUnlock() + + result := make([]getter.CompatPlugin, 0, 1) + for _, p := range ps.plugins { + 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 + ps.updatePluginDB() +} + +// Add adds a plugin to memory and plugindb. +func (ps *Store) Add(p *v2.Plugin) { + ps.Lock() + ps.plugins[p.GetID()] = p + ps.nameToID[p.Name()] = p.GetID() + ps.updatePluginDB() + ps.Unlock() +} + +// Remove removes a plugin from memory, plugindb and disk. +func (ps *Store) Remove(p *v2.Plugin) { + ps.Lock() + delete(ps.plugins, p.GetID()) + delete(ps.nameToID, p.Name()) + ps.updatePluginDB() + p.RemoveFromDisk() + ps.Unlock() +} + +// Callers are expected to hold the store lock. +func (ps *Store) updatePluginDB() error { + jsonData, err := json.Marshal(ps.plugins) + if err != nil { + logrus.Debugf("Error in json.Marshal: %v", err) + return err + } + ioutils.AtomicWriteFile(ps.plugindb, jsonData, 0600) + return nil +} + +// Get returns a plugin matching the given name and capability. +func (ps *Store) Get(name, capability string, mode int) (getter.CompatPlugin, error) { + var ( + p *v2.Plugin + err error + ) + + // Lookup using new model. + if ps != nil { + fullName := name + if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } + ref, ok := named.(reference.NamedTagged) + if !ok { + return nil, fmt.Errorf("invalid name: %s", named.String()) + } + fullName = ref.String() + } + p, err = ps.GetByName(fullName) + if err == nil { + p.Lock() + p.RefCount += mode + p.Unlock() + return p.FilterByCap(capability) + } + if _, ok := err.(ErrNotFound); !ok { + return nil, err + } + } + + // Lookup using legacy model. + if allowV1PluginsFallback { + p, err := plugins.Get(name, capability) + if err != nil { + return nil, fmt.Errorf("legacy plugin: %v", err) + } + return p, nil + } + + return nil, err +} + +// GetAllByCap returns a list of plugins matching the given capability. +func (ps *Store) GetAllByCap(capability string) ([]getter.CompatPlugin, error) { + result := make([]getter.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 { + ps.RLock() + result = ps.getAllByCap(capability) + ps.RUnlock() + } + + // Lookup with legacy model + if allowV1PluginsFallback { + pl, err := plugins.GetAll(capability) + if err != nil { + return nil, fmt.Errorf("legacy plugin: %v", err) + } + for _, p := range pl { + result = append(result, p) + } + } + return result, nil +} + +// 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)) { + pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability)) + + store := &ps + + // Register callback with new plugin model. + store.Lock() + store.handlers[pluginType] = callback + store.Unlock() + + // Register callback with legacy plugin model. + if allowV1PluginsFallback { + plugins.Handle(capability, callback) + } +} + +// CallHandler calls the registered callback. It is invoked during plugin enable. +func (ps *Store) CallHandler(p *v2.Plugin) { + for _, typ := range p.GetTypes() { + if handler := ps.handlers[typ.String()]; handler != nil { + handler(p.Name(), p.Client()) + } + } +} diff --git a/plugin/v2/plugin.go b/plugin/v2/plugin.go index 80cd3d80e0..9cd37d3f06 100644 --- a/plugin/v2/plugin.go +++ b/plugin/v2/plugin.go @@ -1,32 +1,13 @@ -// +build experimental - package v2 import ( - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "strings" "sync" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/plugins" - "github.com/docker/docker/pkg/system" "github.com/docker/docker/restartmanager" - "github.com/opencontainers/runtime-spec/specs-go" ) -const defaultPluginRuntimeDestination = "/run/docker/plugins" - -// ErrInadequateCapability indicates that the plugin did not have the requested capability. -type ErrInadequateCapability string - -func (cap ErrInadequateCapability) Error() string { - return fmt.Sprintf("plugin does not provide %q capability", cap) -} - // Plugin represents an individual plugin. type Plugin struct { sync.RWMutex @@ -37,226 +18,3 @@ type Plugin struct { ExitChan chan bool `json:"-"` RefCount int `json:"-"` } - -func newPluginObj(name, id, tag string) types.Plugin { - return types.Plugin{Name: name, ID: id, Tag: tag} -} - -// NewPlugin creates a plugin. -func NewPlugin(name, id, runRoot, tag string) *Plugin { - return &Plugin{ - PluginObj: newPluginObj(name, id, tag), - RuntimeSourcePath: filepath.Join(runRoot, id), - } -} - -// Client returns the plugin client. -func (p *Plugin) Client() *plugins.Client { - return p.PClient -} - -// IsLegacy returns true for legacy plugins and false otherwise. -func (p *Plugin) IsLegacy() bool { - return false -} - -// Name returns the plugin name. -func (p *Plugin) Name() string { - name := p.PluginObj.Name - if len(p.PluginObj.Tag) > 0 { - // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these - name += ":" + p.PluginObj.Tag - } - return name -} - -// FilterByCap query the plugin for a given capability. -func (p *Plugin) FilterByCap(capability string) (*Plugin, error) { - capability = strings.ToLower(capability) - for _, typ := range p.PluginObj.Manifest.Interface.Types { - if typ.Capability == capability && typ.Prefix == "docker" { - return p, nil - } - } - return nil, ErrInadequateCapability(capability) -} - -// RemoveFromDisk deletes the plugin's runtime files from disk. -func (p *Plugin) RemoveFromDisk() error { - return os.RemoveAll(p.RuntimeSourcePath) -} - -// InitPlugin populates the plugin object from the plugin manifest file. -func (p *Plugin) InitPlugin(libRoot string) error { - dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json")) - if err != nil { - return err - } - err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest) - dt.Close() - if err != nil { - return err - } - - p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts)) - for i, mount := range p.PluginObj.Manifest.Mounts { - p.PluginObj.Config.Mounts[i] = mount - } - p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env)) - for _, env := range p.PluginObj.Manifest.Env { - if env.Value != nil { - p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) - } - } - copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value) - - f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json")) - if err != nil { - return err - } - err = json.NewEncoder(f).Encode(&p.PluginObj.Config) - f.Close() - return err -} - -// Set is used to pass arguments to the plugin. -func (p *Plugin) Set(args []string) error { - m := make(map[string]string, len(args)) - for _, arg := range args { - i := strings.Index(arg, "=") - if i < 0 { - return fmt.Errorf("No equal sign '=' found in %s", arg) - } - m[arg[:i]] = arg[i+1:] - } - return errors.New("not implemented") -} - -// ComputePrivileges takes the manifest file and computes the list of access necessary -// for the plugin on the host. -func (p *Plugin) ComputePrivileges() types.PluginPrivileges { - m := p.PluginObj.Manifest - var privileges types.PluginPrivileges - if m.Network.Type != "null" && m.Network.Type != "bridge" { - privileges = append(privileges, types.PluginPrivilege{ - Name: "network", - Description: "", - Value: []string{m.Network.Type}, - }) - } - for _, mount := range m.Mounts { - if mount.Source != nil { - privileges = append(privileges, types.PluginPrivilege{ - Name: "mount", - Description: "", - Value: []string{*mount.Source}, - }) - } - } - for _, device := range m.Devices { - if device.Path != nil { - privileges = append(privileges, types.PluginPrivilege{ - Name: "device", - Description: "", - Value: []string{*device.Path}, - }) - } - } - if len(m.Capabilities) > 0 { - privileges = append(privileges, types.PluginPrivilege{ - Name: "capabilities", - Description: "", - Value: m.Capabilities, - }) - } - return privileges -} - -// IsEnabled returns the active state of the plugin. -func (p *Plugin) IsEnabled() bool { - p.RLock() - defer p.RUnlock() - - return p.PluginObj.Enabled -} - -// GetID returns the plugin's ID. -func (p *Plugin) GetID() string { - p.RLock() - defer p.RUnlock() - - return p.PluginObj.ID -} - -// GetSocket returns the plugin socket. -func (p *Plugin) GetSocket() string { - p.RLock() - defer p.RUnlock() - - return p.PluginObj.Manifest.Interface.Socket -} - -// GetTypes returns the interface types of a plugin. -func (p *Plugin) GetTypes() []types.PluginInterfaceType { - p.RLock() - defer p.RUnlock() - - return p.PluginObj.Manifest.Interface.Types -} - -// InitSpec creates an OCI spec from the plugin's config. -func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { - rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") - s.Root = specs.Root{ - Path: rootfs, - Readonly: false, // TODO: all plugins should be readonly? settable in manifest? - } - - mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ - Source: &p.RuntimeSourcePath, - Destination: defaultPluginRuntimeDestination, - Type: "bind", - Options: []string{"rbind", "rshared"}, - }) - for _, mount := range mounts { - m := specs.Mount{ - Destination: mount.Destination, - Type: mount.Type, - Options: mount.Options, - } - // TODO: if nil, then it's required and user didn't set it - if mount.Source != nil { - m.Source = *mount.Source - } - if m.Source != "" && m.Type == "bind" { - fi, err := os.Lstat(filepath.Join(rootfs, string(os.PathSeparator), m.Destination)) // TODO: followsymlinks - if err != nil { - return nil, err - } - if fi.IsDir() { - if err := os.MkdirAll(m.Source, 0700); err != nil { - return nil, err - } - } - } - s.Mounts = append(s.Mounts, m) - } - - envs := make([]string, 1, len(p.PluginObj.Config.Env)+1) - envs[0] = "PATH=" + system.DefaultPathEnv - envs = append(envs, p.PluginObj.Config.Env...) - - args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...) - cwd := p.PluginObj.Manifest.Workdir - if len(cwd) == 0 { - cwd = "/" - } - s.Process = specs.Process{ - Terminal: false, - Args: args, - Cwd: cwd, - Env: envs, - } - - return &s, nil -} diff --git a/plugin/v2/plugin_experimental.go b/plugin/v2/plugin_experimental.go new file mode 100644 index 0000000000..948437c186 --- /dev/null +++ b/plugin/v2/plugin_experimental.go @@ -0,0 +1,249 @@ +// +build experimental + +package v2 + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/plugins" + "github.com/docker/docker/pkg/system" + "github.com/opencontainers/runtime-spec/specs-go" +) + +const defaultPluginRuntimeDestination = "/run/docker/plugins" + +// ErrInadequateCapability indicates that the plugin did not have the requested capability. +type ErrInadequateCapability string + +func (cap ErrInadequateCapability) Error() string { + return fmt.Sprintf("plugin does not provide %q capability", cap) +} + +func newPluginObj(name, id, tag string) types.Plugin { + return types.Plugin{Name: name, ID: id, Tag: tag} +} + +// NewPlugin creates a plugin. +func NewPlugin(name, id, runRoot, tag string) *Plugin { + return &Plugin{ + PluginObj: newPluginObj(name, id, tag), + RuntimeSourcePath: filepath.Join(runRoot, id), + } +} + +// Client returns the plugin client. +func (p *Plugin) Client() *plugins.Client { + return p.PClient +} + +// IsV1 returns true for V1 plugins and false otherwise. +func (p *Plugin) IsV1() bool { + return false +} + +// Name returns the plugin name. +func (p *Plugin) Name() string { + name := p.PluginObj.Name + if len(p.PluginObj.Tag) > 0 { + // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these + name += ":" + p.PluginObj.Tag + } + return name +} + +// FilterByCap query the plugin for a given capability. +func (p *Plugin) FilterByCap(capability string) (*Plugin, error) { + capability = strings.ToLower(capability) + for _, typ := range p.PluginObj.Manifest.Interface.Types { + if typ.Capability == capability && typ.Prefix == "docker" { + return p, nil + } + } + return nil, ErrInadequateCapability(capability) +} + +// RemoveFromDisk deletes the plugin's runtime files from disk. +func (p *Plugin) RemoveFromDisk() error { + return os.RemoveAll(p.RuntimeSourcePath) +} + +// InitPlugin populates the plugin object from the plugin manifest file. +func (p *Plugin) InitPlugin(libRoot string) error { + dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json")) + if err != nil { + return err + } + err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest) + dt.Close() + if err != nil { + return err + } + + p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts)) + for i, mount := range p.PluginObj.Manifest.Mounts { + p.PluginObj.Config.Mounts[i] = mount + } + p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env)) + for _, env := range p.PluginObj.Manifest.Env { + if env.Value != nil { + p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) + } + } + copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value) + + f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json")) + if err != nil { + return err + } + err = json.NewEncoder(f).Encode(&p.PluginObj.Config) + f.Close() + return err +} + +// Set is used to pass arguments to the plugin. +func (p *Plugin) Set(args []string) error { + m := make(map[string]string, len(args)) + for _, arg := range args { + i := strings.Index(arg, "=") + if i < 0 { + return fmt.Errorf("No equal sign '=' found in %s", arg) + } + m[arg[:i]] = arg[i+1:] + } + return errors.New("not implemented") +} + +// ComputePrivileges takes the manifest file and computes the list of access necessary +// for the plugin on the host. +func (p *Plugin) ComputePrivileges() types.PluginPrivileges { + m := p.PluginObj.Manifest + var privileges types.PluginPrivileges + if m.Network.Type != "null" && m.Network.Type != "bridge" { + privileges = append(privileges, types.PluginPrivilege{ + Name: "network", + Description: "", + Value: []string{m.Network.Type}, + }) + } + for _, mount := range m.Mounts { + if mount.Source != nil { + privileges = append(privileges, types.PluginPrivilege{ + Name: "mount", + Description: "", + Value: []string{*mount.Source}, + }) + } + } + for _, device := range m.Devices { + if device.Path != nil { + privileges = append(privileges, types.PluginPrivilege{ + Name: "device", + Description: "", + Value: []string{*device.Path}, + }) + } + } + if len(m.Capabilities) > 0 { + privileges = append(privileges, types.PluginPrivilege{ + Name: "capabilities", + Description: "", + Value: m.Capabilities, + }) + } + return privileges +} + +// IsEnabled returns the active state of the plugin. +func (p *Plugin) IsEnabled() bool { + p.RLock() + defer p.RUnlock() + + return p.PluginObj.Enabled +} + +// GetID returns the plugin's ID. +func (p *Plugin) GetID() string { + p.RLock() + defer p.RUnlock() + + return p.PluginObj.ID +} + +// GetSocket returns the plugin socket. +func (p *Plugin) GetSocket() string { + p.RLock() + defer p.RUnlock() + + return p.PluginObj.Manifest.Interface.Socket +} + +// GetTypes returns the interface types of a plugin. +func (p *Plugin) GetTypes() []types.PluginInterfaceType { + p.RLock() + defer p.RUnlock() + + return p.PluginObj.Manifest.Interface.Types +} + +// InitSpec creates an OCI spec from the plugin's config. +func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { + rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") + s.Root = specs.Root{ + Path: rootfs, + Readonly: false, // TODO: all plugins should be readonly? settable in manifest? + } + + mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ + Source: &p.RuntimeSourcePath, + Destination: defaultPluginRuntimeDestination, + Type: "bind", + Options: []string{"rbind", "rshared"}, + }) + for _, mount := range mounts { + m := specs.Mount{ + Destination: mount.Destination, + Type: mount.Type, + Options: mount.Options, + } + // TODO: if nil, then it's required and user didn't set it + if mount.Source != nil { + m.Source = *mount.Source + } + if m.Source != "" && m.Type == "bind" { + fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks + if err != nil { + return nil, err + } + if fi.IsDir() { + if err := os.MkdirAll(m.Source, 0700); err != nil { + return nil, err + } + } + } + s.Mounts = append(s.Mounts, m) + } + + envs := make([]string, 1, len(p.PluginObj.Config.Env)+1) + envs[0] = "PATH=" + system.DefaultPathEnv + envs = append(envs, p.PluginObj.Config.Env...) + + args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...) + cwd := p.PluginObj.Manifest.Workdir + if len(cwd) == 0 { + cwd = "/" + } + s.Process = specs.Process{ + Terminal: false, + Args: args, + Cwd: cwd, + Env: envs, + } + + return &s, nil +} diff --git a/volume/drivers/extpoint.go b/volume/drivers/extpoint.go index 314535ca24..eae24d9fd9 100644 --- a/volume/drivers/extpoint.go +++ b/volume/drivers/extpoint.go @@ -7,14 +7,17 @@ import ( "sync" "github.com/docker/docker/pkg/locker" - pluginStore "github.com/docker/docker/plugin/store" + "github.com/docker/docker/plugin/getter" "github.com/docker/docker/volume" ) // currently created by hand. generation tool would generate this like: // $ extpoint-gen Driver > volume/extpoint.go -var drivers = &driverExtpoint{extensions: make(map[string]volume.Driver), driverLock: &locker.Locker{}} +var drivers = &driverExtpoint{ + extensions: make(map[string]volume.Driver), + driverLock: &locker.Locker{}, +} const extName = "VolumeDriver" @@ -49,7 +52,13 @@ type volumeDriver interface { type driverExtpoint struct { extensions map[string]volume.Driver sync.Mutex - driverLock *locker.Locker + driverLock *locker.Locker + plugingetter getter.PluginGetter +} + +// RegisterPluginGetter sets the plugingetter +func RegisterPluginGetter(plugingetter getter.PluginGetter) { + drivers.plugingetter = plugingetter } // Register associates the given driver to the given name, checking if @@ -72,6 +81,7 @@ func Register(extension volume.Driver, name string) bool { } drivers.extensions[name] = extension + return true } @@ -102,7 +112,7 @@ func lookup(name string, mode int) (volume.Driver, error) { return ext, nil } - p, err := pluginStore.LookupWithCapability(name, extName, mode) + p, err := drivers.plugingetter.Get(name, extName, mode) if err != nil { return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err) } @@ -112,7 +122,7 @@ func lookup(name string, mode int) (volume.Driver, error) { return nil, err } - if p.IsLegacy() { + if p.IsV1() { drivers.Lock() drivers.extensions[name] = d drivers.Unlock() @@ -134,7 +144,7 @@ func GetDriver(name string) (volume.Driver, error) { if name == "" { name = volume.DefaultDriverName } - return lookup(name, pluginStore.LOOKUP) + return lookup(name, getter.LOOKUP) } // CreateDriver returns a volume driver by its name and increments RefCount. @@ -143,7 +153,7 @@ func CreateDriver(name string) (volume.Driver, error) { if name == "" { name = volume.DefaultDriverName } - return lookup(name, pluginStore.CREATE) + return lookup(name, getter.CREATE) } // RemoveDriver returns a volume driver by its name and decrements RefCount.. @@ -152,7 +162,7 @@ func RemoveDriver(name string) (volume.Driver, error) { if name == "" { name = volume.DefaultDriverName } - return lookup(name, pluginStore.REMOVE) + return lookup(name, getter.REMOVE) } // GetDriverList returns list of volume drivers registered. @@ -169,7 +179,7 @@ func GetDriverList() []string { // GetAllDrivers lists all the registered drivers func GetAllDrivers() ([]volume.Driver, error) { - plugins, err := pluginStore.FindWithCapability(extName) + plugins, err := drivers.plugingetter.GetAllByCap(extName) if err != nil { return nil, fmt.Errorf("error listing plugins: %v", err) } @@ -190,7 +200,7 @@ func GetAllDrivers() ([]volume.Driver, error) { } ext = NewVolumeDriver(name, p.Client()) - if p.IsLegacy() { + if p.IsV1() { drivers.extensions[name] = ext } ds = append(ds, ext) diff --git a/volume/drivers/extpoint_test.go b/volume/drivers/extpoint_test.go index f96b3104b2..eb6d14bb70 100644 --- a/volume/drivers/extpoint_test.go +++ b/volume/drivers/extpoint_test.go @@ -3,16 +3,20 @@ package volumedrivers import ( "testing" + pluginstore "github.com/docker/docker/plugin/store" volumetestutils "github.com/docker/docker/volume/testutils" ) func TestGetDriver(t *testing.T) { + pluginStore := pluginstore.NewStore("/var/lib/docker") + RegisterPluginGetter(pluginStore) + _, err := GetDriver("missing") if err == nil { t.Fatal("Expected error, was nil") } - Register(volumetestutils.NewFakeDriver("fake"), "fake") + d, err := GetDriver("fake") if err != nil { t.Fatal(err) diff --git a/volume/store/store_test.go b/volume/store/store_test.go index 089c14f8a2..b27c6578d0 100644 --- a/volume/store/store_test.go +++ b/volume/store/store_test.go @@ -5,11 +5,15 @@ import ( "strings" "testing" + pluginstore "github.com/docker/docker/plugin/store" "github.com/docker/docker/volume/drivers" volumetestutils "github.com/docker/docker/volume/testutils" ) func TestCreate(t *testing.T) { + pluginStore := pluginstore.NewStore("/var/lib/docker") + volumedrivers.RegisterPluginGetter(pluginStore) + volumedrivers.Register(volumetestutils.NewFakeDriver("fake"), "fake") defer volumedrivers.Unregister("fake") s, err := New("")