Merge pull request #26645 from anusha-ragunathan/use-pluginv2-graph
Make graphdrivers work with pluginv2.
This commit is contained in:
commit
ef728a1641
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…
Reference in a new issue