github.com/portworx/docker@v1.12.1/plugin/manager.go (about)

     1  // +build experimental
     2  
     3  package plugin
     4  
     5  import (
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/Sirupsen/logrus"
    16  	"github.com/docker/docker/libcontainerd"
    17  	"github.com/docker/docker/pkg/ioutils"
    18  	"github.com/docker/docker/pkg/plugins"
    19  	"github.com/docker/docker/reference"
    20  	"github.com/docker/docker/registry"
    21  	"github.com/docker/docker/restartmanager"
    22  	"github.com/docker/engine-api/types"
    23  )
    24  
    25  const defaultPluginRuntimeDestination = "/run/docker/plugins"
    26  
    27  var manager *Manager
    28  
    29  // ErrNotFound indicates that a plugin was not found locally.
    30  type ErrNotFound string
    31  
    32  func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
    33  
    34  // ErrInadequateCapability indicates that a plugin was found but did not have the requested capability.
    35  type ErrInadequateCapability struct {
    36  	name       string
    37  	capability string
    38  }
    39  
    40  func (e ErrInadequateCapability) Error() string {
    41  	return fmt.Sprintf("plugin %q found, but not with %q capability", e.name, e.capability)
    42  }
    43  
    44  type plugin struct {
    45  	//sync.RWMutex TODO
    46  	PluginObj         types.Plugin `json:"plugin"`
    47  	client            *plugins.Client
    48  	restartManager    restartmanager.RestartManager
    49  	runtimeSourcePath string
    50  	exitChan          chan bool
    51  }
    52  
    53  func (p *plugin) Client() *plugins.Client {
    54  	return p.client
    55  }
    56  
    57  // IsLegacy returns true for legacy plugins and false otherwise.
    58  func (p *plugin) IsLegacy() bool {
    59  	return false
    60  }
    61  
    62  func (p *plugin) Name() string {
    63  	name := p.PluginObj.Name
    64  	if len(p.PluginObj.Tag) > 0 {
    65  		// TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
    66  		name += ":" + p.PluginObj.Tag
    67  	}
    68  	return name
    69  }
    70  
    71  func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin {
    72  	p := &plugin{
    73  		PluginObj: types.Plugin{
    74  			Name: ref.Name(),
    75  			ID:   id,
    76  		},
    77  		runtimeSourcePath: filepath.Join(pm.runRoot, id),
    78  	}
    79  	if ref, ok := ref.(reference.NamedTagged); ok {
    80  		p.PluginObj.Tag = ref.Tag()
    81  	}
    82  	return p
    83  }
    84  
    85  func (pm *Manager) restorePlugin(p *plugin) error {
    86  	p.runtimeSourcePath = filepath.Join(pm.runRoot, p.PluginObj.ID)
    87  	if p.PluginObj.Active {
    88  		return pm.restore(p)
    89  	}
    90  	return nil
    91  }
    92  
    93  type pluginMap map[string]*plugin
    94  type eventLogger func(id, name, action string)
    95  
    96  // Manager controls the plugin subsystem.
    97  type Manager struct {
    98  	sync.RWMutex
    99  	libRoot           string
   100  	runRoot           string
   101  	plugins           pluginMap // TODO: figure out why save() doesn't json encode *plugin object
   102  	nameToID          map[string]string
   103  	handlers          map[string]func(string, *plugins.Client)
   104  	containerdClient  libcontainerd.Client
   105  	registryService   registry.Service
   106  	handleLegacy      bool
   107  	liveRestore       bool
   108  	shutdown          bool
   109  	pluginEventLogger eventLogger
   110  }
   111  
   112  // GetManager returns the singleton plugin Manager
   113  func GetManager() *Manager {
   114  	return manager
   115  }
   116  
   117  // Init (was NewManager) instantiates the singleton Manager.
   118  // TODO: revert this to NewManager once we get rid of all the singletons.
   119  func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) {
   120  	if manager != nil {
   121  		return nil
   122  	}
   123  
   124  	root = filepath.Join(root, "plugins")
   125  	manager = &Manager{
   126  		libRoot:           root,
   127  		runRoot:           "/run/docker",
   128  		plugins:           make(map[string]*plugin),
   129  		nameToID:          make(map[string]string),
   130  		handlers:          make(map[string]func(string, *plugins.Client)),
   131  		registryService:   rs,
   132  		handleLegacy:      true,
   133  		liveRestore:       liveRestore,
   134  		pluginEventLogger: evL,
   135  	}
   136  	if err := os.MkdirAll(manager.runRoot, 0700); err != nil {
   137  		return err
   138  	}
   139  	manager.containerdClient, err = remote.Client(manager)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if err := manager.init(); err != nil {
   144  		return err
   145  	}
   146  	return nil
   147  }
   148  
   149  // Handle sets a callback for a given capability. The callback will be called for every plugin with a given capability.
   150  // TODO: append instead of set?
   151  func Handle(capability string, callback func(string, *plugins.Client)) {
   152  	pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
   153  	manager.handlers[pluginType] = callback
   154  	if manager.handleLegacy {
   155  		plugins.Handle(capability, callback)
   156  	}
   157  }
   158  
   159  func (pm *Manager) get(name string) (*plugin, error) {
   160  	pm.RLock()
   161  	defer pm.RUnlock()
   162  
   163  	id, nameOk := pm.nameToID[name]
   164  	if !nameOk {
   165  		return nil, ErrNotFound(name)
   166  	}
   167  
   168  	p, idOk := pm.plugins[id]
   169  	if !idOk {
   170  		return nil, ErrNotFound(name)
   171  	}
   172  
   173  	return p, nil
   174  }
   175  
   176  // FindWithCapability returns a list of plugins matching the given capability.
   177  func FindWithCapability(capability string) ([]Plugin, error) {
   178  	handleLegacy := true
   179  	result := make([]Plugin, 0, 1)
   180  	if manager != nil {
   181  		handleLegacy = manager.handleLegacy
   182  		manager.RLock()
   183  		defer manager.RUnlock()
   184  	pluginLoop:
   185  		for _, p := range manager.plugins {
   186  			for _, typ := range p.PluginObj.Manifest.Interface.Types {
   187  				if typ.Capability != capability || typ.Prefix != "docker" {
   188  					continue pluginLoop
   189  				}
   190  			}
   191  			result = append(result, p)
   192  		}
   193  	}
   194  	if handleLegacy {
   195  		pl, err := plugins.GetAll(capability)
   196  		if err != nil {
   197  			return nil, fmt.Errorf("legacy plugin: %v", err)
   198  		}
   199  		for _, p := range pl {
   200  			if _, ok := manager.nameToID[p.Name()]; !ok {
   201  				result = append(result, p)
   202  			}
   203  		}
   204  	}
   205  	return result, nil
   206  }
   207  
   208  // LookupWithCapability returns a plugin matching the given name and capability.
   209  func LookupWithCapability(name, capability string) (Plugin, error) {
   210  	var (
   211  		p   *plugin
   212  		err error
   213  	)
   214  	handleLegacy := true
   215  	if manager != nil {
   216  		fullName := name
   217  		if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate
   218  			if reference.IsNameOnly(named) {
   219  				named = reference.WithDefaultTag(named)
   220  			}
   221  			ref, ok := named.(reference.NamedTagged)
   222  			if !ok {
   223  				return nil, fmt.Errorf("invalid name: %s", named.String())
   224  			}
   225  			fullName = ref.String()
   226  		}
   227  		p, err = manager.get(fullName)
   228  		if err != nil {
   229  			if _, ok := err.(ErrNotFound); !ok {
   230  				return nil, err
   231  			}
   232  			handleLegacy = manager.handleLegacy
   233  		} else {
   234  			handleLegacy = false
   235  		}
   236  	}
   237  	if handleLegacy {
   238  		p, err := plugins.Get(name, capability)
   239  		if err != nil {
   240  			return nil, fmt.Errorf("legacy plugin: %v", err)
   241  		}
   242  		return p, nil
   243  	} else if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	capability = strings.ToLower(capability)
   248  	for _, typ := range p.PluginObj.Manifest.Interface.Types {
   249  		if typ.Capability == capability && typ.Prefix == "docker" {
   250  			return p, nil
   251  		}
   252  	}
   253  	return nil, ErrInadequateCapability{name, capability}
   254  }
   255  
   256  // StateChanged updates plugin internals using from libcontainerd events.
   257  func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
   258  	logrus.Debugf("plugin state changed %s %#v", id, e)
   259  
   260  	switch e.State {
   261  	case libcontainerd.StateExit:
   262  		pm.RLock()
   263  		p, idOk := pm.plugins[id]
   264  		pm.RUnlock()
   265  		if !idOk {
   266  			return ErrNotFound(id)
   267  		}
   268  		if pm.shutdown == true {
   269  			p.exitChan <- true
   270  		}
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  // AttachStreams attaches io streams to the plugin
   277  func (pm *Manager) AttachStreams(id string, iop libcontainerd.IOPipe) error {
   278  	iop.Stdin.Close()
   279  
   280  	logger := logrus.New()
   281  	logger.Hooks.Add(logHook{id})
   282  	// TODO: cache writer per id
   283  	w := logger.Writer()
   284  	go func() {
   285  		io.Copy(w, iop.Stdout)
   286  	}()
   287  	go func() {
   288  		// TODO: update logrus and use logger.WriterLevel
   289  		io.Copy(w, iop.Stderr)
   290  	}()
   291  	return nil
   292  }
   293  
   294  func (pm *Manager) init() error {
   295  	dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
   296  	if err != nil {
   297  		if os.IsNotExist(err) {
   298  			return nil
   299  		}
   300  		return err
   301  	}
   302  
   303  	if err := json.NewDecoder(dt).Decode(&pm.plugins); err != nil {
   304  		return err
   305  	}
   306  
   307  	var group sync.WaitGroup
   308  	group.Add(len(pm.plugins))
   309  	for _, p := range pm.plugins {
   310  		go func(p *plugin) {
   311  			defer group.Done()
   312  			if err := pm.restorePlugin(p); err != nil {
   313  				logrus.Errorf("Error restoring plugin '%s': %s", p.Name(), err)
   314  				return
   315  			}
   316  
   317  			pm.Lock()
   318  			pm.nameToID[p.Name()] = p.PluginObj.ID
   319  			requiresManualRestore := !pm.liveRestore && p.PluginObj.Active
   320  			pm.Unlock()
   321  
   322  			if requiresManualRestore {
   323  				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
   324  				if err := pm.enable(p, true); err != nil {
   325  					logrus.Errorf("Error enabling plugin '%s': %s", p.Name(), err)
   326  				}
   327  			}
   328  		}(p)
   329  	}
   330  	group.Wait()
   331  	return pm.save()
   332  }
   333  
   334  func (pm *Manager) initPlugin(p *plugin) error {
   335  	dt, err := os.Open(filepath.Join(pm.libRoot, p.PluginObj.ID, "manifest.json"))
   336  	if err != nil {
   337  		return err
   338  	}
   339  	err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest)
   340  	dt.Close()
   341  	if err != nil {
   342  		return err
   343  	}
   344  
   345  	p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts))
   346  	for i, mount := range p.PluginObj.Manifest.Mounts {
   347  		p.PluginObj.Config.Mounts[i] = mount
   348  	}
   349  	p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env))
   350  	for _, env := range p.PluginObj.Manifest.Env {
   351  		if env.Value != nil {
   352  			p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
   353  		}
   354  	}
   355  	copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)
   356  
   357  	f, err := os.Create(filepath.Join(pm.libRoot, p.PluginObj.ID, "plugin-config.json"))
   358  	if err != nil {
   359  		return err
   360  	}
   361  	err = json.NewEncoder(f).Encode(&p.PluginObj.Config)
   362  	f.Close()
   363  	return err
   364  }
   365  
   366  func (pm *Manager) remove(p *plugin) error {
   367  	if p.PluginObj.Active {
   368  		return fmt.Errorf("plugin %s is active", p.Name())
   369  	}
   370  	pm.Lock() // fixme: lock single record
   371  	defer pm.Unlock()
   372  	delete(pm.plugins, p.PluginObj.ID)
   373  	delete(pm.nameToID, p.Name())
   374  	pm.save()
   375  	return os.RemoveAll(filepath.Join(pm.libRoot, p.PluginObj.ID))
   376  }
   377  
   378  func (pm *Manager) set(p *plugin, args []string) error {
   379  	m := make(map[string]string, len(args))
   380  	for _, arg := range args {
   381  		i := strings.Index(arg, "=")
   382  		if i < 0 {
   383  			return fmt.Errorf("No equal sign '=' found in %s", arg)
   384  		}
   385  		m[arg[:i]] = arg[i+1:]
   386  	}
   387  	return errors.New("not implemented")
   388  }
   389  
   390  // fixme: not safe
   391  func (pm *Manager) save() error {
   392  	filePath := filepath.Join(pm.libRoot, "plugins.json")
   393  
   394  	jsonData, err := json.Marshal(pm.plugins)
   395  	if err != nil {
   396  		logrus.Debugf("Error in json.Marshal: %v", err)
   397  		return err
   398  	}
   399  	ioutils.AtomicWriteFile(filePath, jsonData, 0600)
   400  	return nil
   401  }
   402  
   403  type logHook struct{ id string }
   404  
   405  func (logHook) Levels() []logrus.Level {
   406  	return logrus.AllLevels
   407  }
   408  
   409  func (l logHook) Fire(entry *logrus.Entry) error {
   410  	entry.Data = logrus.Fields{"plugin": l.id}
   411  	return nil
   412  }
   413  
   414  func computePrivileges(m *types.PluginManifest) types.PluginPrivileges {
   415  	var privileges types.PluginPrivileges
   416  	if m.Network.Type != "null" && m.Network.Type != "bridge" {
   417  		privileges = append(privileges, types.PluginPrivilege{
   418  			Name:        "network",
   419  			Description: "",
   420  			Value:       []string{m.Network.Type},
   421  		})
   422  	}
   423  	for _, mount := range m.Mounts {
   424  		if mount.Source != nil {
   425  			privileges = append(privileges, types.PluginPrivilege{
   426  				Name:        "mount",
   427  				Description: "",
   428  				Value:       []string{*mount.Source},
   429  			})
   430  		}
   431  	}
   432  	for _, device := range m.Devices {
   433  		if device.Path != nil {
   434  			privileges = append(privileges, types.PluginPrivilege{
   435  				Name:        "device",
   436  				Description: "",
   437  				Value:       []string{*device.Path},
   438  			})
   439  		}
   440  	}
   441  	if len(m.Capabilities) > 0 {
   442  		privileges = append(privileges, types.PluginPrivilege{
   443  			Name:        "capabilities",
   444  			Description: "",
   445  			Value:       m.Capabilities,
   446  		})
   447  	}
   448  	return privileges
   449  }