mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
23ea9e45fd
Plugin miscellaneous fixes
403 lines
11 KiB
Go
403 lines
11 KiB
Go
package v2
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/oci"
|
|
"github.com/docker/docker/pkg/plugins"
|
|
"github.com/docker/docker/pkg/system"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
)
|
|
|
|
// Plugin represents an individual plugin.
|
|
type Plugin struct {
|
|
sync.RWMutex
|
|
PluginObj types.Plugin `json:"plugin"`
|
|
PClient *plugins.Client `json:"-"`
|
|
RuntimeSourcePath string `json:"-"`
|
|
RefCount int `json:"-"`
|
|
Restart bool `json:"-"`
|
|
ExitChan chan bool `json:"-"`
|
|
LibRoot string `json:"-"`
|
|
TimeoutInSecs int `json:"-"`
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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, libRoot, tag string) *Plugin {
|
|
return &Plugin{
|
|
PluginObj: newPluginObj(name, id, tag),
|
|
RuntimeSourcePath: filepath.Join(runRoot, id),
|
|
LibRoot: libRoot,
|
|
}
|
|
}
|
|
|
|
// 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.Config.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 config file.
|
|
func (p *Plugin) InitPlugin() error {
|
|
dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "config.json"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = json.NewDecoder(dt).Decode(&p.PluginObj.Config)
|
|
dt.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts))
|
|
for i, mount := range p.PluginObj.Config.Mounts {
|
|
p.PluginObj.Settings.Mounts[i] = mount
|
|
}
|
|
p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
|
|
p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices))
|
|
copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
|
|
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))
|
|
}
|
|
}
|
|
copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value)
|
|
|
|
return p.writeSettings()
|
|
}
|
|
|
|
func (p *Plugin) writeSettings() error {
|
|
f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-settings.json"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = json.NewEncoder(f).Encode(&p.PluginObj.Settings)
|
|
f.Close()
|
|
return err
|
|
}
|
|
|
|
// Set is used to pass arguments to the plugin.
|
|
func (p *Plugin) Set(args []string) error {
|
|
p.Lock()
|
|
defer p.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
|
|
*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
|
|
*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)
|
|
}
|
|
|
|
// update the settings on disk
|
|
return p.writeSettings()
|
|
}
|
|
|
|
// ComputePrivileges takes the config file and computes the list of access necessary
|
|
// for the plugin on the host.
|
|
func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
|
|
c := p.PluginObj.Config
|
|
var privileges types.PluginPrivileges
|
|
if c.Network.Type != "null" && c.Network.Type != "bridge" {
|
|
privileges = append(privileges, types.PluginPrivilege{
|
|
Name: "network",
|
|
Description: "permissions to access a network",
|
|
Value: []string{c.Network.Type},
|
|
})
|
|
}
|
|
for _, mount := range c.Mounts {
|
|
if mount.Source != nil {
|
|
privileges = append(privileges, types.PluginPrivilege{
|
|
Name: "mount",
|
|
Description: "host path to mount",
|
|
Value: []string{*mount.Source},
|
|
})
|
|
}
|
|
}
|
|
for _, device := range c.Linux.Devices {
|
|
if device.Path != nil {
|
|
privileges = append(privileges, types.PluginPrivilege{
|
|
Name: "device",
|
|
Description: "host device to access",
|
|
Value: []string{*device.Path},
|
|
})
|
|
}
|
|
}
|
|
if c.Linux.DeviceCreation {
|
|
privileges = append(privileges, types.PluginPrivilege{
|
|
Name: "device-creation",
|
|
Description: "allow creating devices inside plugin",
|
|
Value: []string{"true"},
|
|
})
|
|
}
|
|
if len(c.Linux.Capabilities) > 0 {
|
|
privileges = append(privileges, types.PluginPrivilege{
|
|
Name: "capabilities",
|
|
Description: "list of additional capabilities required",
|
|
Value: c.Linux.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.Config.Interface.Socket
|
|
}
|
|
|
|
// GetTypes returns the interface types of a plugin.
|
|
func (p *Plugin) GetTypes() []types.PluginInterfaceType {
|
|
p.RLock()
|
|
defer p.RUnlock()
|
|
|
|
return p.PluginObj.Config.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 config?
|
|
}
|
|
|
|
userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts))
|
|
for _, m := range p.PluginObj.Config.Mounts {
|
|
userMounts[m.Destination] = struct{}{}
|
|
}
|
|
|
|
mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
|
|
Source: &p.RuntimeSourcePath,
|
|
Destination: defaultPluginRuntimeDestination,
|
|
Type: "bind",
|
|
Options: []string{"rbind", "rshared"},
|
|
})
|
|
|
|
if p.PluginObj.Config.Network.Type != "" {
|
|
// TODO: if net == bridge, use libnetwork controller to create a new plugin-specific bridge, bind mount /etc/hosts and /etc/resolv.conf look at the docker code (allocateNetwork, initialize)
|
|
if p.PluginObj.Config.Network.Type == "host" {
|
|
oci.RemoveNamespace(&s, specs.NamespaceType("network"))
|
|
}
|
|
etcHosts := "/etc/hosts"
|
|
resolvConf := "/etc/resolv.conf"
|
|
mounts = append(mounts,
|
|
types.PluginMount{
|
|
Source: &etcHosts,
|
|
Destination: etcHosts,
|
|
Type: "bind",
|
|
Options: []string{"rbind", "ro"},
|
|
},
|
|
types.PluginMount{
|
|
Source: &resolvConf,
|
|
Destination: resolvConf,
|
|
Type: "bind",
|
|
Options: []string{"rbind", "ro"},
|
|
})
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
for i, m := range s.Mounts {
|
|
if strings.HasPrefix(m.Destination, "/dev/") {
|
|
if _, ok := userMounts[m.Destination]; ok {
|
|
s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
|
|
}
|
|
}
|
|
}
|
|
|
|
if p.PluginObj.Config.Linux.DeviceCreation {
|
|
rwm := "rwm"
|
|
s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
|
|
}
|
|
for _, dev := range p.PluginObj.Config.Linux.Devices {
|
|
path := *dev.Path
|
|
d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.Linux.Devices = append(s.Linux.Devices, d...)
|
|
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...)
|
|
}
|
|
|
|
envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
|
|
envs[0] = "PATH=" + system.DefaultPathEnv
|
|
envs = append(envs, p.PluginObj.Settings.Env...)
|
|
|
|
args := append(p.PluginObj.Config.Entrypoint, p.PluginObj.Settings.Args...)
|
|
cwd := p.PluginObj.Config.Workdir
|
|
if len(cwd) == 0 {
|
|
cwd = "/"
|
|
}
|
|
s.Process.Terminal = false
|
|
s.Process.Args = args
|
|
s.Process.Cwd = cwd
|
|
s.Process.Env = envs
|
|
|
|
s.Process.Capabilities = append(s.Process.Capabilities, p.PluginObj.Config.Linux.Capabilities...)
|
|
|
|
return &s, nil
|
|
}
|