github.com/hms58/moby@v1.13.1/plugin/manager.go (about)

     1  package plugin
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"regexp"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/Sirupsen/logrus"
    15  	"github.com/docker/distribution/digest"
    16  	"github.com/docker/docker/api/types"
    17  	"github.com/docker/docker/image"
    18  	"github.com/docker/docker/layer"
    19  	"github.com/docker/docker/libcontainerd"
    20  	"github.com/docker/docker/pkg/ioutils"
    21  	"github.com/docker/docker/pkg/mount"
    22  	"github.com/docker/docker/plugin/v2"
    23  	"github.com/docker/docker/reference"
    24  	"github.com/docker/docker/registry"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  const configFileName = "config.json"
    29  const rootFSFileName = "rootfs"
    30  
    31  var validFullID = regexp.MustCompile(`^([a-f0-9]{64})$`)
    32  
    33  func (pm *Manager) restorePlugin(p *v2.Plugin) error {
    34  	if p.IsEnabled() {
    35  		return pm.restore(p)
    36  	}
    37  	return nil
    38  }
    39  
    40  type eventLogger func(id, name, action string)
    41  
    42  // ManagerConfig defines configuration needed to start new manager.
    43  type ManagerConfig struct {
    44  	Store              *Store // remove
    45  	Executor           libcontainerd.Remote
    46  	RegistryService    registry.Service
    47  	LiveRestoreEnabled bool // TODO: remove
    48  	LogPluginEvent     eventLogger
    49  	Root               string
    50  	ExecRoot           string
    51  }
    52  
    53  // Manager controls the plugin subsystem.
    54  type Manager struct {
    55  	config           ManagerConfig
    56  	mu               sync.RWMutex // protects cMap
    57  	muGC             sync.RWMutex // protects blobstore deletions
    58  	cMap             map[*v2.Plugin]*controller
    59  	containerdClient libcontainerd.Client
    60  	blobStore        *basicBlobStore
    61  }
    62  
    63  // controller represents the manager's control on a plugin.
    64  type controller struct {
    65  	restart       bool
    66  	exitChan      chan bool
    67  	timeoutInSecs int
    68  }
    69  
    70  // pluginRegistryService ensures that all resolved repositories
    71  // are of the plugin class.
    72  type pluginRegistryService struct {
    73  	registry.Service
    74  }
    75  
    76  func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) {
    77  	repoInfo, err = s.Service.ResolveRepository(name)
    78  	if repoInfo != nil {
    79  		repoInfo.Class = "plugin"
    80  	}
    81  	return
    82  }
    83  
    84  // NewManager returns a new plugin manager.
    85  func NewManager(config ManagerConfig) (*Manager, error) {
    86  	if config.RegistryService != nil {
    87  		config.RegistryService = pluginRegistryService{config.RegistryService}
    88  	}
    89  	manager := &Manager{
    90  		config: config,
    91  	}
    92  	if err := os.MkdirAll(manager.config.Root, 0700); err != nil {
    93  		return nil, errors.Wrapf(err, "failed to mkdir %v", manager.config.Root)
    94  	}
    95  	if err := os.MkdirAll(manager.config.ExecRoot, 0700); err != nil {
    96  		return nil, errors.Wrapf(err, "failed to mkdir %v", manager.config.ExecRoot)
    97  	}
    98  	if err := os.MkdirAll(manager.tmpDir(), 0700); err != nil {
    99  		return nil, errors.Wrapf(err, "failed to mkdir %v", manager.tmpDir())
   100  	}
   101  	var err error
   102  	manager.containerdClient, err = config.Executor.Client(manager) // todo: move to another struct
   103  	if err != nil {
   104  		return nil, errors.Wrap(err, "failed to create containerd client")
   105  	}
   106  	manager.blobStore, err = newBasicBlobStore(filepath.Join(manager.config.Root, "storage/blobs"))
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	manager.cMap = make(map[*v2.Plugin]*controller)
   112  	if err := manager.reload(); err != nil {
   113  		return nil, errors.Wrap(err, "failed to restore plugins")
   114  	}
   115  	return manager, nil
   116  }
   117  
   118  func (pm *Manager) tmpDir() string {
   119  	return filepath.Join(pm.config.Root, "tmp")
   120  }
   121  
   122  // StateChanged updates plugin internals using libcontainerd events.
   123  func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
   124  	logrus.Debugf("plugin state changed %s %#v", id, e)
   125  
   126  	switch e.State {
   127  	case libcontainerd.StateExit:
   128  		p, err := pm.config.Store.GetV2Plugin(id)
   129  		if err != nil {
   130  			return err
   131  		}
   132  
   133  		pm.mu.RLock()
   134  		c := pm.cMap[p]
   135  
   136  		if c.exitChan != nil {
   137  			close(c.exitChan)
   138  		}
   139  		restart := c.restart
   140  		pm.mu.RUnlock()
   141  
   142  		os.RemoveAll(filepath.Join(pm.config.ExecRoot, id))
   143  
   144  		if p.PropagatedMount != "" {
   145  			if err := mount.Unmount(p.PropagatedMount); err != nil {
   146  				logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
   147  			}
   148  			propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount")
   149  			if err := mount.Unmount(propRoot); err != nil {
   150  				logrus.Warn("Could not unmount %s: %v", propRoot, err)
   151  			}
   152  		}
   153  
   154  		if restart {
   155  			pm.enable(p, c, true)
   156  		}
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  func (pm *Manager) reload() error { // todo: restore
   163  	dir, err := ioutil.ReadDir(pm.config.Root)
   164  	if err != nil {
   165  		return errors.Wrapf(err, "failed to read %v", pm.config.Root)
   166  	}
   167  	plugins := make(map[string]*v2.Plugin)
   168  	for _, v := range dir {
   169  		if validFullID.MatchString(v.Name()) {
   170  			p, err := pm.loadPlugin(v.Name())
   171  			if err != nil {
   172  				return err
   173  			}
   174  			plugins[p.GetID()] = p
   175  		}
   176  	}
   177  
   178  	pm.config.Store.SetAll(plugins)
   179  
   180  	var wg sync.WaitGroup
   181  	wg.Add(len(plugins))
   182  	for _, p := range plugins {
   183  		c := &controller{} // todo: remove this
   184  		pm.cMap[p] = c
   185  		go func(p *v2.Plugin) {
   186  			defer wg.Done()
   187  			if err := pm.restorePlugin(p); err != nil {
   188  				logrus.Errorf("failed to restore plugin '%s': %s", p.Name(), err)
   189  				return
   190  			}
   191  
   192  			if p.Rootfs != "" {
   193  				p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs")
   194  			}
   195  
   196  			// We should only enable rootfs propagation for certain plugin types that need it.
   197  			for _, typ := range p.PluginObj.Config.Interface.Types {
   198  				if (typ.Capability == "volumedriver" || typ.Capability == "graphdriver") && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") {
   199  					if p.PluginObj.Config.PropagatedMount != "" {
   200  						propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount")
   201  
   202  						// check if we need to migrate an older propagated mount from before
   203  						// these mounts were stored outside the plugin rootfs
   204  						if _, err := os.Stat(propRoot); os.IsNotExist(err) {
   205  							if _, err := os.Stat(p.PropagatedMount); err == nil {
   206  								// make sure nothing is mounted here
   207  								// don't care about errors
   208  								mount.Unmount(p.PropagatedMount)
   209  								if err := os.Rename(p.PropagatedMount, propRoot); err != nil {
   210  									logrus.WithError(err).WithField("dir", propRoot).Error("error migrating propagated mount storage")
   211  								}
   212  								if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil {
   213  									logrus.WithError(err).WithField("dir", p.PropagatedMount).Error("error migrating propagated mount storage")
   214  								}
   215  							}
   216  						}
   217  
   218  						if err := os.MkdirAll(propRoot, 0755); err != nil {
   219  							logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err)
   220  						}
   221  						// TODO: sanitize PropagatedMount and prevent breakout
   222  						p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount)
   223  						if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil {
   224  							logrus.Errorf("failed to create PropagatedMount directory at %s: %v", p.PropagatedMount, err)
   225  							return
   226  						}
   227  					}
   228  				}
   229  			}
   230  
   231  			pm.save(p)
   232  			requiresManualRestore := !pm.config.LiveRestoreEnabled && p.IsEnabled()
   233  
   234  			if requiresManualRestore {
   235  				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
   236  				if err := pm.enable(p, c, true); err != nil {
   237  					logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err)
   238  				}
   239  			}
   240  		}(p)
   241  	}
   242  	wg.Wait()
   243  	return nil
   244  }
   245  
   246  func (pm *Manager) loadPlugin(id string) (*v2.Plugin, error) {
   247  	p := filepath.Join(pm.config.Root, id, configFileName)
   248  	dt, err := ioutil.ReadFile(p)
   249  	if err != nil {
   250  		return nil, errors.Wrapf(err, "error reading %v", p)
   251  	}
   252  	var plugin v2.Plugin
   253  	if err := json.Unmarshal(dt, &plugin); err != nil {
   254  		return nil, errors.Wrapf(err, "error decoding %v", p)
   255  	}
   256  	return &plugin, nil
   257  }
   258  
   259  func (pm *Manager) save(p *v2.Plugin) error {
   260  	pluginJSON, err := json.Marshal(p)
   261  	if err != nil {
   262  		return errors.Wrap(err, "failed to marshal plugin json")
   263  	}
   264  	if err := ioutils.AtomicWriteFile(filepath.Join(pm.config.Root, p.GetID(), configFileName), pluginJSON, 0600); err != nil {
   265  		return errors.Wrap(err, "failed to write atomically plugin json")
   266  	}
   267  	return nil
   268  }
   269  
   270  // GC cleans up unrefrenced blobs. This is recommended to run in a goroutine
   271  func (pm *Manager) GC() {
   272  	pm.muGC.Lock()
   273  	defer pm.muGC.Unlock()
   274  
   275  	whitelist := make(map[digest.Digest]struct{})
   276  	for _, p := range pm.config.Store.GetAll() {
   277  		whitelist[p.Config] = struct{}{}
   278  		for _, b := range p.Blobsums {
   279  			whitelist[b] = struct{}{}
   280  		}
   281  	}
   282  
   283  	pm.blobStore.gc(whitelist)
   284  }
   285  
   286  type logHook struct{ id string }
   287  
   288  func (logHook) Levels() []logrus.Level {
   289  	return logrus.AllLevels
   290  }
   291  
   292  func (l logHook) Fire(entry *logrus.Entry) error {
   293  	entry.Data = logrus.Fields{"plugin": l.id}
   294  	return nil
   295  }
   296  
   297  func attachToLog(id string) func(libcontainerd.IOPipe) error {
   298  	return func(iop libcontainerd.IOPipe) error {
   299  		iop.Stdin.Close()
   300  
   301  		logger := logrus.New()
   302  		logger.Hooks.Add(logHook{id})
   303  		// TODO: cache writer per id
   304  		w := logger.Writer()
   305  		go func() {
   306  			io.Copy(w, iop.Stdout)
   307  		}()
   308  		go func() {
   309  			// TODO: update logrus and use logger.WriterLevel
   310  			io.Copy(w, iop.Stderr)
   311  		}()
   312  		return nil
   313  	}
   314  }
   315  
   316  func validatePrivileges(requiredPrivileges, privileges types.PluginPrivileges) error {
   317  	// todo: make a better function that doesn't check order
   318  	if !reflect.DeepEqual(privileges, requiredPrivileges) {
   319  		return errors.New("incorrect privileges")
   320  	}
   321  	return nil
   322  }
   323  
   324  func configToRootFS(c []byte) (*image.RootFS, error) {
   325  	var pluginConfig types.PluginConfig
   326  	if err := json.Unmarshal(c, &pluginConfig); err != nil {
   327  		return nil, err
   328  	}
   329  	// validation for empty rootfs is in distribution code
   330  	if pluginConfig.Rootfs == nil {
   331  		return nil, nil
   332  	}
   333  
   334  	return rootFSFromPlugin(pluginConfig.Rootfs), nil
   335  }
   336  
   337  func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS {
   338  	rootFS := image.RootFS{
   339  		Type:    pluginfs.Type,
   340  		DiffIDs: make([]layer.DiffID, len(pluginfs.DiffIds)),
   341  	}
   342  	for i := range pluginfs.DiffIds {
   343  		rootFS.DiffIDs[i] = layer.DiffID(pluginfs.DiffIds[i])
   344  	}
   345  
   346  	return &rootFS
   347  }