1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #26058 from anusha-ragunathan/add-pluginstore

Reorganize plugin package into sub packages.
This commit is contained in:
Tibor Vass 2016-08-31 11:58:34 -07:00 committed by GitHub
commit e3bc989a9d
10 changed files with 595 additions and 447 deletions

View file

@ -15,39 +15,40 @@ import (
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/plugin/distribution"
"github.com/docker/docker/plugin/v2"
"github.com/docker/docker/reference"
"github.com/docker/engine-api/types"
)
// Disable deactivates a plugin, which implies that they cannot be used by containers.
func (pm *Manager) Disable(name string) error {
p, err := pm.get(name)
p, err := pm.pluginStore.GetByName(name)
if err != nil {
return err
}
if err := pm.disable(p); err != nil {
return err
}
pm.pluginEventLogger(p.PluginObj.ID, name, "disable")
pm.pluginEventLogger(p.GetID(), name, "disable")
return nil
}
// Enable activates a plugin, which implies that they are ready to be used by containers.
func (pm *Manager) Enable(name string) error {
p, err := pm.get(name)
p, err := pm.pluginStore.GetByName(name)
if err != nil {
return err
}
if err := pm.enable(p, false); err != nil {
return err
}
pm.pluginEventLogger(p.PluginObj.ID, name, "enable")
pm.pluginEventLogger(p.GetID(), name, "enable")
return nil
}
// Inspect examines a plugin manifest
func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
p, err := pm.get(name)
p, err := pm.pluginStore.GetByName(name)
if err != nil {
return tp, err
}
@ -63,7 +64,7 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
}
name = ref.String()
if p, _ := pm.get(name); p != nil {
if p, _ := pm.pluginStore.GetByName(name); p != nil {
logrus.Debugf("plugin already exists")
return nil, fmt.Errorf("%s exists", name)
}
@ -86,25 +87,25 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
return nil, err
}
p := pm.newPlugin(ref, pluginID)
if err := pm.initPlugin(p); err != nil {
var tag string
if ref, ok := ref.(reference.NamedTagged); ok {
tag = ref.Tag()
}
p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, tag)
if err := p.InitPlugin(pm.libRoot); err != nil {
return nil, err
}
pm.Lock()
pm.plugins[pluginID] = p
pm.nameToID[name] = pluginID
pm.save()
pm.Unlock()
pm.pluginStore.Add(p)
pm.pluginEventLogger(pluginID, name, "pull")
return computePrivileges(&p.PluginObj.Manifest), nil
return p.ComputePrivileges(), nil
}
// List displays the list of plugins and associated metadata.
func (pm *Manager) List() ([]types.Plugin, error) {
out := make([]types.Plugin, 0, len(pm.plugins))
for _, p := range pm.plugins {
plugins := pm.pluginStore.GetAll()
out := make([]types.Plugin, 0, len(plugins))
for _, p := range plugins {
out = append(out, p.PluginObj)
}
return out, nil
@ -112,11 +113,11 @@ func (pm *Manager) List() ([]types.Plugin, error) {
// Push pushes a plugin to the store.
func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.AuthConfig) error {
p, err := pm.get(name)
p, err := pm.pluginStore.GetByName(name)
if err != nil {
return err
}
dest := filepath.Join(pm.libRoot, p.PluginObj.ID)
dest := filepath.Join(pm.libRoot, p.GetID())
config, err := ioutil.ReadFile(filepath.Join(dest, "manifest.json"))
if err != nil {
return err
@ -142,22 +143,28 @@ func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.A
// Remove deletes plugin's root directory.
func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
p, err := pm.get(name)
p, err := pm.pluginStore.GetByName(name)
if err != nil {
return err
}
if err := pm.remove(p, config.ForceRemove); err != nil {
return err
if p.IsEnabled() {
if !config.ForceRemove {
return fmt.Errorf("plugin %s is enabled", p.Name())
}
if err := pm.disable(p); err != nil {
logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
}
}
pm.pluginEventLogger(p.PluginObj.ID, name, "remove")
pm.pluginStore.Remove(p)
pm.pluginEventLogger(p.GetID(), name, "remove")
return nil
}
// Set sets plugin args
func (pm *Manager) Set(name string, args []string) error {
p, err := pm.get(name)
p, err := pm.pluginStore.GetByName(name)
if err != nil {
return err
}
return pm.set(p, args)
return p.Set(args)
}

View file

@ -1,10 +0,0 @@
package plugin
import "github.com/docker/docker/pkg/plugins"
// Plugin represents a plugin. It is used to abstract from an older plugin architecture (in pkg/plugins).
type Plugin interface {
Client() *plugins.Client
Name() string
IsLegacy() bool
}

View file

@ -4,7 +4,6 @@ package plugin
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
@ -14,16 +13,12 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/reference"
"github.com/docker/docker/plugin/store"
"github.com/docker/docker/plugin/v2"
"github.com/docker/docker/registry"
"github.com/docker/docker/restartmanager"
"github.com/docker/engine-api/types"
)
const defaultPluginRuntimeDestination = "/run/docker/plugins"
var (
manager *Manager
@ -34,71 +29,14 @@ 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)) }
// ErrInadequateCapability indicates that a plugin was found but did not have the requested capability.
type ErrInadequateCapability struct {
name string
capability string
}
func (e ErrInadequateCapability) Error() string {
return fmt.Sprintf("plugin %q found, but not with %q capability", e.name, e.capability)
}
type plugin struct {
//sync.RWMutex TODO
PluginObj types.Plugin `json:"plugin"`
client *plugins.Client
restartManager restartmanager.RestartManager
runtimeSourcePath string
exitChan chan bool
}
func (p *plugin) Client() *plugins.Client {
return p.client
}
// IsLegacy returns true for legacy plugins and false otherwise.
func (p *plugin) IsLegacy() bool {
return false
}
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
}
func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin {
p := &plugin{
PluginObj: types.Plugin{
Name: ref.Name(),
ID: id,
},
runtimeSourcePath: filepath.Join(pm.runRoot, id),
}
if ref, ok := ref.(reference.NamedTagged); ok {
p.PluginObj.Tag = ref.Tag()
}
return p
}
func (pm *Manager) restorePlugin(p *plugin) error {
p.runtimeSourcePath = filepath.Join(pm.runRoot, p.PluginObj.ID)
if p.PluginObj.Enabled {
func (pm *Manager) restorePlugin(p *v2.Plugin) error {
p.RuntimeSourcePath = filepath.Join(pm.runRoot, p.GetID())
if p.IsEnabled() {
return pm.restore(p)
}
return nil
}
type pluginMap map[string]*plugin
type eventLogger func(id, name, action string)
// Manager controls the plugin subsystem.
@ -106,8 +44,7 @@ type Manager struct {
sync.RWMutex
libRoot string
runRoot string
plugins pluginMap // TODO: figure out why save() doesn't json encode *plugin object
nameToID map[string]string
pluginStore *store.PluginStore
handlers map[string]func(string, *plugins.Client)
containerdClient libcontainerd.Client
registryService registry.Service
@ -132,8 +69,7 @@ func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRes
manager = &Manager{
libRoot: root,
runRoot: "/run/docker",
plugins: make(map[string]*plugin),
nameToID: make(map[string]string),
pluginStore: store.NewPluginStore(root),
handlers: make(map[string]func(string, *plugins.Client)),
registryService: rs,
liveRestore: liveRestore,
@ -162,105 +98,6 @@ func Handle(capability string, callback func(string, *plugins.Client)) {
}
}
func (pm *Manager) get(name string) (*plugin, error) {
pm.RLock()
defer pm.RUnlock()
id, nameOk := pm.nameToID[name]
if !nameOk {
return nil, ErrNotFound(name)
}
p, idOk := pm.plugins[id]
if !idOk {
return nil, ErrNotFound(name)
}
return p, nil
}
// FindWithCapability returns a list of plugins matching the given capability.
func FindWithCapability(capability string) ([]Plugin, error) {
result := make([]Plugin, 0, 1)
/* Daemon start always calls plugin.Init thereby initializing a manager.
* So manager on experimental builds can never be nil, even while
* handling legacy plugins. However, there are legacy plugin unit
* tests where volume subsystem directly talks with the plugin,
* bypassing the daemon. For such tests, this check is necessary.*/
if manager != nil {
manager.RLock()
for _, p := range manager.plugins {
for _, typ := range p.PluginObj.Manifest.Interface.Types {
if strings.EqualFold(typ.Capability, capability) && typ.Prefix == "docker" {
result = append(result, p)
break
}
}
}
manager.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
}
// LookupWithCapability returns a plugin matching the given name and capability.
func LookupWithCapability(name, capability string) (Plugin, error) {
var (
p *plugin
err error
)
// Lookup using new model.
if manager != 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 = manager.get(fullName)
if err == nil {
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{name, 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
}
// StateChanged updates plugin internals using libcontainerd events.
func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
logrus.Debugf("plugin state changed %s %#v", id, e)
@ -272,13 +109,11 @@ func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
shutdown = pm.shutdown
pm.RUnlock()
if shutdown {
pm.RLock()
p, idOk := pm.plugins[id]
pm.RUnlock()
if !idOk {
return ErrNotFound(id)
p, err := pm.pluginStore.GetByID(id)
if err != nil {
return err
}
close(p.exitChan)
close(p.ExitChan)
}
}
@ -313,24 +148,24 @@ func (pm *Manager) init() error {
}
defer dt.Close()
if err := json.NewDecoder(dt).Decode(&pm.plugins); err != nil {
plugins := make(map[string]*v2.Plugin)
if err := json.NewDecoder(dt).Decode(&plugins); err != nil {
return err
}
pm.pluginStore.SetAll(plugins)
var group sync.WaitGroup
group.Add(len(pm.plugins))
for _, p := range pm.plugins {
go func(p *plugin) {
group.Add(len(plugins))
for _, p := range plugins {
go func(p *v2.Plugin) {
defer group.Done()
if err := pm.restorePlugin(p); err != nil {
logrus.Errorf("failed to restore plugin '%s': %s", p.Name(), err)
return
}
pm.Lock()
pm.nameToID[p.Name()] = p.PluginObj.ID
requiresManualRestore := !pm.liveRestore && p.PluginObj.Enabled
pm.Unlock()
pm.pluginStore.Add(p)
requiresManualRestore := !pm.liveRestore && p.IsEnabled()
if requiresManualRestore {
// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
@ -341,80 +176,6 @@ func (pm *Manager) init() error {
}(p)
}
group.Wait()
return pm.save()
}
func (pm *Manager) initPlugin(p *plugin) error {
dt, err := os.Open(filepath.Join(pm.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(pm.libRoot, p.PluginObj.ID, "plugin-config.json"))
if err != nil {
return err
}
err = json.NewEncoder(f).Encode(&p.PluginObj.Config)
f.Close()
return err
}
func (pm *Manager) remove(p *plugin, force bool) error {
if p.PluginObj.Enabled {
if !force {
return fmt.Errorf("plugin %s is enabled", p.Name())
}
if err := pm.disable(p); err != nil {
logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
}
}
pm.Lock() // fixme: lock single record
defer pm.Unlock()
delete(pm.plugins, p.PluginObj.ID)
delete(pm.nameToID, p.Name())
pm.save()
return os.RemoveAll(filepath.Join(pm.libRoot, p.PluginObj.ID))
}
func (pm *Manager) set(p *plugin, 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")
}
// fixme: not safe
func (pm *Manager) save() error {
filePath := filepath.Join(pm.libRoot, "plugins.json")
jsonData, err := json.Marshal(pm.plugins)
if err != nil {
logrus.Debugf("failure in json.Marshal: %v", err)
return err
}
ioutils.AtomicWriteFile(filePath, jsonData, 0600)
return nil
}
@ -428,40 +189,3 @@ func (l logHook) Fire(entry *logrus.Entry) error {
entry.Data = logrus.Fields{"plugin": l.id}
return nil
}
func computePrivileges(m *types.PluginManifest) types.PluginPrivileges {
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
}

View file

@ -4,7 +4,6 @@ package plugin
import (
"fmt"
"os"
"path/filepath"
"syscall"
"time"
@ -13,45 +12,38 @@ import (
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/plugin/v2"
"github.com/docker/docker/restartmanager"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/opencontainers/runtime-spec/specs-go"
)
func (pm *Manager) enable(p *plugin, force bool) error {
if p.PluginObj.Enabled && !force {
func (pm *Manager) enable(p *v2.Plugin, force bool) error {
if p.IsEnabled() && !force {
return fmt.Errorf("plugin %s is already enabled", p.Name())
}
spec, err := pm.initSpec(p)
spec, err := p.InitSpec(oci.DefaultSpec(), pm.libRoot)
if err != nil {
return err
}
p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
if err := pm.containerdClient.Create(p.PluginObj.ID, libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.restartManager)); err != nil { // POC-only
if err := p.restartManager.Cancel(); err != nil {
p.RestartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
if err := pm.containerdClient.Create(p.GetID(), libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.RestartManager)); err != nil {
if err := p.RestartManager.Cancel(); err != nil {
logrus.Errorf("enable: restartManager.Cancel failed due to %v", err)
}
return err
}
socket := p.PluginObj.Manifest.Interface.Socket
p.client, err = plugins.NewClient("unix://"+filepath.Join(p.runtimeSourcePath, socket), nil)
p.PClient, err = plugins.NewClient("unix://"+filepath.Join(p.RuntimeSourcePath, p.GetSocket()), nil)
if err != nil {
if err := p.restartManager.Cancel(); err != nil {
if err := p.RestartManager.Cancel(); err != nil {
logrus.Errorf("enable: restartManager.Cancel failed due to %v", err)
}
return err
}
pm.Lock() // fixme: lock single record
p.PluginObj.Enabled = true
pm.save()
pm.Unlock()
for _, typ := range p.PluginObj.Manifest.Interface.Types {
pm.pluginStore.SetState(p, true)
for _, typ := range p.GetTypes() {
if handler := pm.handlers[typ.String()]; handler != nil {
handler(p.Name(), p.Client())
}
@ -60,90 +52,25 @@ func (pm *Manager) enable(p *plugin, force bool) error {
return nil
}
func (pm *Manager) restore(p *plugin) error {
p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
return pm.containerdClient.Restore(p.PluginObj.ID, libcontainerd.WithRestartManager(p.restartManager))
func (pm *Manager) restore(p *v2.Plugin) error {
p.RestartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
return pm.containerdClient.Restore(p.GetID(), libcontainerd.WithRestartManager(p.RestartManager))
}
func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) {
s := oci.DefaultSpec()
rootfs := filepath.Join(pm.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" {
/* Debugging issue #25511: Volumes and other content created under the
bind mount should be recursively propagated. rshared, not shared.
This could be the reason for EBUSY during removal. Override options
with rbind, rshared and see if CI errors are fixed. */
m.Options = []string{"rbind", "rshared"}
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
}
func (pm *Manager) disable(p *plugin) error {
if !p.PluginObj.Enabled {
func (pm *Manager) disable(p *v2.Plugin) error {
if !p.IsEnabled() {
return fmt.Errorf("plugin %s is already disabled", p.Name())
}
if err := p.restartManager.Cancel(); err != nil {
if err := p.RestartManager.Cancel(); err != nil {
logrus.Error(err)
}
if err := pm.containerdClient.Signal(p.PluginObj.ID, int(syscall.SIGKILL)); err != nil {
if err := pm.containerdClient.Signal(p.GetID(), int(syscall.SIGKILL)); err != nil {
logrus.Error(err)
}
os.RemoveAll(p.runtimeSourcePath)
pm.Lock() // fixme: lock single record
defer pm.Unlock()
p.PluginObj.Enabled = false
pm.save()
if err := p.RemoveFromDisk(); err != nil {
logrus.Error(err)
}
pm.pluginStore.SetState(p, false)
return nil
}
@ -155,34 +82,36 @@ func (pm *Manager) Shutdown() {
pm.RLock()
defer pm.RUnlock()
for _, p := range pm.plugins {
if pm.liveRestore && p.PluginObj.Enabled {
logrus.Debug("Plugin enabled when liveRestore is set, skipping shutdown")
plugins := pm.pluginStore.GetAll()
for _, p := range plugins {
if pm.liveRestore && p.IsEnabled() {
logrus.Debug("Plugin active when liveRestore is set, skipping shutdown")
continue
}
if p.restartManager != nil {
if err := p.restartManager.Cancel(); err != nil {
if p.RestartManager != nil {
if err := p.RestartManager.Cancel(); err != nil {
logrus.Error(err)
}
}
if pm.containerdClient != nil && p.PluginObj.Enabled {
p.exitChan = make(chan bool)
if pm.containerdClient != nil && p.IsEnabled() {
pluginID := p.GetID()
p.ExitChan = make(chan bool)
err := pm.containerdClient.Signal(p.PluginObj.ID, int(syscall.SIGTERM))
if err != nil {
logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err)
} else {
select {
case <-p.exitChan:
case <-p.ExitChan:
logrus.Debug("Clean shutdown of plugin")
case <-time.After(time.Second * 10):
logrus.Debug("Force shutdown plugin")
if err := pm.containerdClient.Signal(p.PluginObj.ID, int(syscall.SIGKILL)); err != nil {
if err := pm.containerdClient.Signal(pluginID, int(syscall.SIGKILL)); err != nil {
logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err)
}
}
}
}
if err := os.RemoveAll(p.runtimeSourcePath); err != nil {
if err := p.RemoveFromDisk(); err != nil {
logrus.Errorf("Remove plugin runtime failed with error: %v", err)
}
}

View file

@ -5,22 +5,23 @@ package plugin
import (
"fmt"
"github.com/docker/docker/plugin/v2"
"github.com/opencontainers/runtime-spec/specs-go"
)
func (pm *Manager) enable(p *plugin, force bool) error {
func (pm *Manager) enable(p *v2.Plugin, force bool) error {
return fmt.Errorf("Not implemented")
}
func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) {
func (pm *Manager) initSpec(p *v2.Plugin) (*specs.Spec, error) {
return nil, fmt.Errorf("Not implemented")
}
func (pm *Manager) disable(p *plugin) error {
func (pm *Manager) disable(p *v2.Plugin) error {
return fmt.Errorf("Not implemented")
}
func (pm *Manager) restore(p *plugin) error {
func (pm *Manager) restore(p *v2.Plugin) error {
return fmt.Errorf("Not implemented")
}

10
plugin/store/interface.go Normal file
View file

@ -0,0 +1,10 @@
package store
import "github.com/docker/docker/pkg/plugins"
// CompatPlugin is a abstraction to handle both new and legacy (v1) plugins.
type CompatPlugin interface {
Client() *plugins.Client
Name() string
IsLegacy() bool
}

View file

@ -1,16 +1,18 @@
// +build !experimental
package plugin
package store
import "github.com/docker/docker/pkg/plugins"
import (
"github.com/docker/docker/pkg/plugins"
)
// FindWithCapability returns a list of plugins matching the given capability.
func FindWithCapability(capability string) ([]Plugin, error) {
func FindWithCapability(capability string) ([]CompatPlugin, error) {
pl, err := plugins.GetAll(capability)
if err != nil {
return nil, err
}
result := make([]Plugin, len(pl))
result := make([]CompatPlugin, len(pl))
for i, p := range pl {
result[i] = p
}
@ -18,6 +20,6 @@ func FindWithCapability(capability string) ([]Plugin, error) {
}
// LookupWithCapability returns a plugin matching the given name and capability.
func LookupWithCapability(name, capability string) (Plugin, error) {
func LookupWithCapability(name, capability string) (CompatPlugin, error) {
return plugins.Get(name, capability)
}

224
plugin/store/store.go Normal file
View file

@ -0,0 +1,224 @@
// +build experimental
package store
import (
"encoding/json"
"fmt"
"path/filepath"
"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"
)
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
nameToID map[string]string
plugindb string
}
// NewPluginStore creates a PluginStore.
func NewPluginStore(libRoot string) *PluginStore {
store = &PluginStore{
plugins: make(map[string]*v2.Plugin),
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)
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) (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 {
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)
}
}
return result, nil
}

261
plugin/v2/plugin.go Normal file
View file

@ -0,0 +1,261 @@
// +build experimental
package v2
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/restartmanager"
"github.com/docker/engine-api/types"
"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
PluginObj types.Plugin `json:"plugin"`
PClient *plugins.Client `json:"-"`
RestartManager restartmanager.RestartManager `json:"-"`
RuntimeSourcePath string `json:"-"`
ExitChan chan bool `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
}

View file

@ -7,7 +7,7 @@ import (
"sync"
"github.com/docker/docker/pkg/locker"
"github.com/docker/docker/plugin"
pluginStore "github.com/docker/docker/plugin/store"
"github.com/docker/docker/volume"
)
@ -102,7 +102,7 @@ func lookup(name string) (volume.Driver, error) {
return ext, nil
}
p, err := plugin.LookupWithCapability(name, extName)
p, err := pluginStore.LookupWithCapability(name, extName)
if err != nil {
return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
}
@ -151,7 +151,7 @@ func GetDriverList() []string {
// GetAllDrivers lists all the registered drivers
func GetAllDrivers() ([]volume.Driver, error) {
plugins, err := plugin.FindWithCapability(extName)
plugins, err := pluginStore.FindWithCapability(extName)
if err != nil {
return nil, fmt.Errorf("error listing plugins: %v", err)
}