Setting up the mounts on the host increases chances of mount leakage and makes for more cleanup after the plugin has stopped. With this change all mounts for the plugin are performed by the container runtime and automatically cleaned up when the container exits. Signed-off-by: Brian Goff <cpuguy83@gmail.com> Upstream-commit: a53930a04fa81b082aa78e66b342ff19cc63cc5f Component: engine
263 lines
6.9 KiB
Go
263 lines
6.9 KiB
Go
package v2
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/pkg/plugingetter"
|
|
"github.com/docker/docker/pkg/plugins"
|
|
"github.com/opencontainers/go-digest"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
)
|
|
|
|
// Plugin represents an individual plugin.
|
|
type Plugin struct {
|
|
mu sync.RWMutex
|
|
PluginObj types.Plugin `json:"plugin"` // todo: embed struct
|
|
pClient *plugins.Client
|
|
refCount int
|
|
Rootfs string // TODO: make private
|
|
|
|
Config digest.Digest
|
|
Blobsums []digest.Digest
|
|
|
|
modifyRuntimeSpec func(*specs.Spec)
|
|
|
|
SwarmServiceID string
|
|
}
|
|
|
|
const defaultPluginRuntimeDestination = "/run/docker/plugins"
|
|
|
|
// ErrInadequateCapability indicates that the plugin did not have the requested capability.
|
|
type ErrInadequateCapability struct {
|
|
cap string
|
|
}
|
|
|
|
func (e ErrInadequateCapability) Error() string {
|
|
return fmt.Sprintf("plugin does not provide %q capability", e.cap)
|
|
}
|
|
|
|
// BasePath returns the path to which all paths returned by the plugin are relative to.
|
|
// For Plugin objects this returns the host path of the plugin container's rootfs.
|
|
func (p *Plugin) BasePath() string {
|
|
return p.Rootfs
|
|
}
|
|
|
|
// Client returns the plugin client.
|
|
func (p *Plugin) Client() *plugins.Client {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
return p.pClient
|
|
}
|
|
|
|
// SetPClient set the plugin client.
|
|
func (p *Plugin) SetPClient(client *plugins.Client) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
p.pClient = client
|
|
}
|
|
|
|
// 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 {
|
|
return p.PluginObj.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.Config.Interface.Types {
|
|
if typ.Capability == capability && typ.Prefix == "docker" {
|
|
return p, nil
|
|
}
|
|
}
|
|
return nil, ErrInadequateCapability{capability}
|
|
}
|
|
|
|
// InitEmptySettings initializes empty settings for a plugin.
|
|
func (p *Plugin) InitEmptySettings() {
|
|
p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts))
|
|
copy(p.PluginObj.Settings.Mounts, p.PluginObj.Config.Mounts)
|
|
p.PluginObj.Settings.Devices = make([]types.PluginDevice, len(p.PluginObj.Config.Linux.Devices))
|
|
copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
|
|
p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
|
|
for _, env := range p.PluginObj.Config.Env {
|
|
if env.Value != nil {
|
|
p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
|
|
}
|
|
}
|
|
p.PluginObj.Settings.Args = make([]string, len(p.PluginObj.Config.Args.Value))
|
|
copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value)
|
|
}
|
|
|
|
// Set is used to pass arguments to the plugin.
|
|
func (p *Plugin) Set(args []string) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.PluginObj.Enabled {
|
|
return fmt.Errorf("cannot set on an active plugin, disable plugin before setting")
|
|
}
|
|
|
|
sets, err := newSettables(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// TODO(vieux): lots of code duplication here, needs to be refactored.
|
|
|
|
next:
|
|
for _, s := range sets {
|
|
// range over all the envs in the config
|
|
for _, env := range p.PluginObj.Config.Env {
|
|
// found the env in the config
|
|
if env.Name == s.name {
|
|
// is it settable ?
|
|
if ok, err := s.isSettable(allowedSettableFieldsEnv, env.Settable); err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return fmt.Errorf("%q is not settable", s.prettyName())
|
|
}
|
|
// is it, so lets update the settings in memory
|
|
updateSettingsEnv(&p.PluginObj.Settings.Env, &s)
|
|
continue next
|
|
}
|
|
}
|
|
|
|
// range over all the mounts in the config
|
|
for _, mount := range p.PluginObj.Config.Mounts {
|
|
// found the mount in the config
|
|
if mount.Name == s.name {
|
|
// is it settable ?
|
|
if ok, err := s.isSettable(allowedSettableFieldsMounts, mount.Settable); err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return fmt.Errorf("%q is not settable", s.prettyName())
|
|
}
|
|
|
|
// it is, so lets update the settings in memory
|
|
if mount.Source == nil {
|
|
return fmt.Errorf("Plugin config has no mount source")
|
|
}
|
|
*mount.Source = s.value
|
|
continue next
|
|
}
|
|
}
|
|
|
|
// range over all the devices in the config
|
|
for _, device := range p.PluginObj.Config.Linux.Devices {
|
|
// found the device in the config
|
|
if device.Name == s.name {
|
|
// is it settable ?
|
|
if ok, err := s.isSettable(allowedSettableFieldsDevices, device.Settable); err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return fmt.Errorf("%q is not settable", s.prettyName())
|
|
}
|
|
|
|
// it is, so lets update the settings in memory
|
|
if device.Path == nil {
|
|
return fmt.Errorf("Plugin config has no device path")
|
|
}
|
|
*device.Path = s.value
|
|
continue next
|
|
}
|
|
}
|
|
|
|
// found the name in the config
|
|
if p.PluginObj.Config.Args.Name == s.name {
|
|
// is it settable ?
|
|
if ok, err := s.isSettable(allowedSettableFieldsArgs, p.PluginObj.Config.Args.Settable); err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return fmt.Errorf("%q is not settable", s.prettyName())
|
|
}
|
|
|
|
// it is, so lets update the settings in memory
|
|
p.PluginObj.Settings.Args = strings.Split(s.value, " ")
|
|
continue next
|
|
}
|
|
|
|
return fmt.Errorf("setting %q not found in the plugin configuration", s.name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsEnabled returns the active state of the plugin.
|
|
func (p *Plugin) IsEnabled() bool {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
return p.PluginObj.Enabled
|
|
}
|
|
|
|
// GetID returns the plugin's ID.
|
|
func (p *Plugin) GetID() string {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
return p.PluginObj.ID
|
|
}
|
|
|
|
// GetSocket returns the plugin socket.
|
|
func (p *Plugin) GetSocket() string {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
return p.PluginObj.Config.Interface.Socket
|
|
}
|
|
|
|
// GetTypes returns the interface types of a plugin.
|
|
func (p *Plugin) GetTypes() []types.PluginInterfaceType {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
return p.PluginObj.Config.Interface.Types
|
|
}
|
|
|
|
// GetRefCount returns the reference count.
|
|
func (p *Plugin) GetRefCount() int {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
return p.refCount
|
|
}
|
|
|
|
// AddRefCount adds to reference count.
|
|
func (p *Plugin) AddRefCount(count int) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
p.refCount += count
|
|
}
|
|
|
|
// Acquire increments the plugin's reference count
|
|
// This should be followed up by `Release()` when the plugin is no longer in use.
|
|
func (p *Plugin) Acquire() {
|
|
p.AddRefCount(plugingetter.Acquire)
|
|
}
|
|
|
|
// Release decrements the plugin's reference count
|
|
// This should only be called when the plugin is no longer in use, e.g. with
|
|
// via `Acquire()` or getter.Get("name", "type", plugingetter.Acquire)
|
|
func (p *Plugin) Release() {
|
|
p.AddRefCount(plugingetter.Release)
|
|
}
|
|
|
|
// SetSpecOptModifier sets the function to use to modify the the generated
|
|
// runtime spec.
|
|
func (p *Plugin) SetSpecOptModifier(f func(*specs.Spec)) {
|
|
p.mu.Lock()
|
|
p.modifyRuntimeSpec = f
|
|
p.mu.Unlock()
|
|
}
|