Make graphdrivers work with pluginv2.
As part of making graphdrivers support pluginv2, a PluginGetter interface was necessary for cleaner separation and avoiding import cycles. This commit creates a PluginGetter interface and makes pluginStore implement it. Then the pluginStore object is created in the daemon (rather than by the plugin manager) and passed to plugin init as well as to the different subsystems (eg. graphdrivers, volumedrivers). A side effect of this change was that some code was moved out of experimental. This is good, since plugin support will be stable soon. Signed-off-by: Anusha Ragunathan <anusha@docker.com>
This commit is contained in:
parent
beea098b97
commit
fefea805e9
24 changed files with 629 additions and 569 deletions
|
@ -1,5 +1,3 @@
|
|||
// +build experimental
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
|
|
8
daemon/daemon.go
Executable file → Normal file
8
daemon/daemon.go
Executable file → Normal file
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
25
plugin/getter/interface.go
Normal file
25
plugin/getter/interface.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
|
|
31
plugin/store/defs.go
Normal file
31
plugin/store/defs.go
Normal file
|
@ -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"),
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
234
plugin/store/store_experimental.go
Normal file
234
plugin/store/store_experimental.go
Normal file
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
249
plugin/v2/plugin_experimental.go
Normal file
249
plugin/v2/plugin_experimental.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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("")
|
||||
|
|
Loading…
Add table
Reference in a new issue