123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- // +build experimental
- package plugin
- import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "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/registry"
- "github.com/docker/docker/restartmanager"
- "github.com/docker/engine-api/types"
- )
- const defaultPluginRuntimeDestination = "/run/docker/plugins"
- var manager *Manager
- // 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.Active {
- return pm.restore(p)
- }
- return nil
- }
- type pluginMap map[string]*plugin
- type eventLogger func(id, name, action string)
- // Manager controls the plugin subsystem.
- 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
- handlers map[string]func(string, *plugins.Client)
- containerdClient libcontainerd.Client
- registryService registry.Service
- handleLegacy bool
- liveRestore bool
- shutdown bool
- pluginEventLogger eventLogger
- }
- // GetManager returns the singleton plugin Manager
- func GetManager() *Manager {
- return manager
- }
- // Init (was NewManager) instantiates the singleton Manager.
- // TODO: revert this to NewManager once we get rid of all the singletons.
- func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) {
- if manager != nil {
- return nil
- }
- root = filepath.Join(root, "plugins")
- manager = &Manager{
- libRoot: root,
- runRoot: "/run/docker",
- plugins: make(map[string]*plugin),
- nameToID: make(map[string]string),
- handlers: make(map[string]func(string, *plugins.Client)),
- registryService: rs,
- handleLegacy: true,
- liveRestore: liveRestore,
- pluginEventLogger: evL,
- }
- if err := os.MkdirAll(manager.runRoot, 0700); err != nil {
- return err
- }
- manager.containerdClient, err = remote.Client(manager)
- if err != nil {
- return err
- }
- if err := manager.init(); err != nil {
- return err
- }
- return nil
- }
- // Handle sets a callback for a given capability. The callback will be called for every plugin with a given capability.
- // TODO: append instead of set?
- func Handle(capability string, callback func(string, *plugins.Client)) {
- pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
- manager.handlers[pluginType] = callback
- if manager.handleLegacy {
- plugins.Handle(capability, callback)
- }
- }
- 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) {
- handleLegacy := true
- result := make([]Plugin, 0, 1)
- if manager != nil {
- handleLegacy = manager.handleLegacy
- manager.RLock()
- defer manager.RUnlock()
- pluginLoop:
- for _, p := range manager.plugins {
- for _, typ := range p.PluginObj.Manifest.Interface.Types {
- if typ.Capability != capability || typ.Prefix != "docker" {
- continue pluginLoop
- }
- }
- result = append(result, p)
- }
- }
- if handleLegacy {
- pl, err := plugins.GetAll(capability)
- if err != nil {
- return nil, fmt.Errorf("legacy plugin: %v", err)
- }
- for _, p := range pl {
- if _, ok := manager.nameToID[p.Name()]; !ok {
- 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
- )
- handleLegacy := true
- 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 {
- if _, ok := err.(ErrNotFound); !ok {
- return nil, err
- }
- handleLegacy = manager.handleLegacy
- } else {
- handleLegacy = false
- }
- }
- if handleLegacy {
- p, err := plugins.Get(name, capability)
- if err != nil {
- return nil, fmt.Errorf("legacy plugin: %v", err)
- }
- return p, nil
- } else if err != nil {
- return nil, err
- }
- 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}
- }
- // StateChanged updates plugin internals using from libcontainerd events.
- func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
- logrus.Debugf("plugin state changed %s %#v", id, e)
- switch e.State {
- case libcontainerd.StateExit:
- pm.RLock()
- p, idOk := pm.plugins[id]
- pm.RUnlock()
- if !idOk {
- return ErrNotFound(id)
- }
- if pm.shutdown == true {
- p.exitChan <- true
- }
- }
- return nil
- }
- // AttachStreams attaches io streams to the plugin
- func (pm *Manager) AttachStreams(id string, iop libcontainerd.IOPipe) error {
- iop.Stdin.Close()
- logger := logrus.New()
- logger.Hooks.Add(logHook{id})
- // TODO: cache writer per id
- w := logger.Writer()
- go func() {
- io.Copy(w, iop.Stdout)
- }()
- go func() {
- // TODO: update logrus and use logger.WriterLevel
- io.Copy(w, iop.Stderr)
- }()
- return nil
- }
- func (pm *Manager) init() error {
- dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
- if err != nil {
- if os.IsNotExist(err) {
- return nil
- }
- return err
- }
- if err := json.NewDecoder(dt).Decode(&pm.plugins); err != nil {
- return err
- }
- var group sync.WaitGroup
- group.Add(len(pm.plugins))
- for _, p := range pm.plugins {
- go func(p *plugin) {
- defer group.Done()
- if err := pm.restorePlugin(p); err != nil {
- logrus.Errorf("Error restoring plugin '%s': %s", p.Name(), err)
- return
- }
- pm.Lock()
- pm.nameToID[p.Name()] = p.PluginObj.ID
- requiresManualRestore := !pm.liveRestore && p.PluginObj.Active
- pm.Unlock()
- if requiresManualRestore {
- // if liveRestore is not enabled, the plugin will be stopped now so we should enable it
- if err := pm.enable(p); err != nil {
- logrus.Errorf("Error enabling plugin '%s': %s", p.Name(), err)
- }
- }
- }(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) error {
- if p.PluginObj.Active {
- return fmt.Errorf("plugin %s is active", p.Name())
- }
- 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("Error in json.Marshal: %v", err)
- return err
- }
- ioutils.AtomicWriteFile(filePath, jsonData, 0600)
- return nil
- }
- type logHook struct{ id string }
- func (logHook) Levels() []logrus.Level {
- return logrus.AllLevels
- }
- 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
- }
|