123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- 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
- }
|