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:
commit
e3bc989a9d
10 changed files with 595 additions and 447 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
10
plugin/store/interface.go
Normal 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
|
||||
}
|
|
@ -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
224
plugin/store/store.go
Normal 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
261
plugin/v2/plugin.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue