diff --git a/daemon/graphdriver/plugin.go b/daemon/graphdriver/plugin.go index 2b69f54856..d8058d9236 100644 --- a/daemon/graphdriver/plugin.go +++ b/daemon/graphdriver/plugin.go @@ -23,7 +23,7 @@ func newPluginDriver(name string, pl plugingetter.CompatPlugin, config Options) home := config.Root if !pl.IsV1() { if p, ok := pl.(*v2.Plugin); ok { - if p.PropagatedMount != "" { + if p.PluginObj.Config.PropagatedMount != "" { home = p.PluginObj.Config.PropagatedMount } } diff --git a/daemon/graphdriver/proxy.go b/daemon/graphdriver/proxy.go index d9c8639977..10a7a527ae 100644 --- a/daemon/graphdriver/proxy.go +++ b/daemon/graphdriver/proxy.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "io" - "path/filepath" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/containerfs" @@ -143,7 +142,7 @@ func (d *graphDriverProxy) Get(id, mountLabel string) (containerfs.ContainerFS, if ret.Err != "" { err = errors.New(ret.Err) } - return containerfs.NewLocalContainerFS(filepath.Join(d.p.BasePath(), ret.Dir)), err + return containerfs.NewLocalContainerFS(d.p.ScopedPath(ret.Dir)), err } func (d *graphDriverProxy) Put(id string) error { diff --git a/daemon/logger/adapter.go b/daemon/logger/adapter.go index 016e0c2353..71d033f0a9 100644 --- a/daemon/logger/adapter.go +++ b/daemon/logger/adapter.go @@ -3,7 +3,7 @@ package logger // import "github.com/docker/docker/daemon/logger" import ( "io" "os" - "strings" + "path/filepath" "sync" "time" @@ -19,7 +19,6 @@ type pluginAdapter struct { driverName string id string plugin logPlugin - basePath string fifoPath string capabilities Capability logInfo Info @@ -58,7 +57,7 @@ func (a *pluginAdapter) Close() error { a.mu.Lock() defer a.mu.Unlock() - if err := a.plugin.StopLogging(strings.TrimPrefix(a.fifoPath, a.basePath)); err != nil { + if err := a.plugin.StopLogging(filepath.Join("/", "run", "docker", "logging", a.id)); err != nil { return err } diff --git a/daemon/logger/plugin.go b/daemon/logger/plugin.go index 209bec3e8a..cd0e60b7cd 100644 --- a/daemon/logger/plugin.go +++ b/daemon/logger/plugin.go @@ -5,7 +5,6 @@ import ( "io" "os" "path/filepath" - "strings" "github.com/docker/docker/api/types/plugins/logdriver" getter "github.com/docker/docker/pkg/plugingetter" @@ -39,18 +38,20 @@ func getPlugin(name string, mode int) (Creator, error) { } d := &logPluginProxy{p.Client()} - return makePluginCreator(name, d, p.BasePath()), nil + return makePluginCreator(name, d, p.ScopedPath), nil } -func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator { +func makePluginCreator(name string, l *logPluginProxy, scopePath func(s string) string) Creator { return func(logCtx Info) (logger Logger, err error) { defer func() { if err != nil { pluginGetter.Get(name, extName, getter.Release) } }() - root := filepath.Join(basePath, "run", "docker", "logging") - if err := os.MkdirAll(root, 0700); err != nil { + + unscopedPath := filepath.Join("/", "run", "docker", "logging") + logRoot := scopePath(unscopedPath) + if err := os.MkdirAll(logRoot, 0700); err != nil { return nil, err } @@ -59,8 +60,7 @@ func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator driverName: name, id: id, plugin: l, - basePath: basePath, - fifoPath: filepath.Join(root, id), + fifoPath: filepath.Join(logRoot, id), logInfo: logCtx, } @@ -77,7 +77,7 @@ func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator a.stream = stream a.enc = logdriver.NewLogEntryEncoder(a.stream) - if err := l.StartLogging(strings.TrimPrefix(a.fifoPath, basePath), logCtx); err != nil { + if err := l.StartLogging(filepath.Join(unscopedPath, id), logCtx); err != nil { return nil, errors.Wrapf(err, "error creating logger") } diff --git a/daemon/metrics.go b/daemon/metrics.go index b3f603c137..b283526d9a 100644 --- a/daemon/metrics.go +++ b/daemon/metrics.go @@ -1,10 +1,8 @@ package daemon // import "github.com/docker/docker/daemon" import ( - "path/filepath" "sync" - "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/plugingetter" metrics "github.com/docker/go-metrics" "github.com/pkg/errors" @@ -132,18 +130,6 @@ func (d *Daemon) cleanupMetricsPlugins() { } } -type metricsPlugin struct { - plugingetter.CompatPlugin -} - -func (p metricsPlugin) sock() string { - return "metrics.sock" -} - -func (p metricsPlugin) sockBase() string { - return filepath.Join(p.BasePath(), "run", "docker") -} - func pluginStartMetricsCollection(p plugingetter.CompatPlugin) error { type metricsPluginResponse struct { Err string @@ -162,12 +148,4 @@ func pluginStopMetricsCollection(p plugingetter.CompatPlugin) { if err := p.Client().Call(metricsPluginType+".StopMetrics", nil, nil); err != nil { logrus.WithError(err).WithField("name", p.Name()).Error("error stopping metrics collector") } - - mp := metricsPlugin{p} - sockPath := filepath.Join(mp.sockBase(), mp.sock()) - if err := mount.Unmount(sockPath); err != nil { - if mounted, _ := mount.Mounted(sockPath); mounted { - logrus.WithError(err).WithField("name", p.Name()).WithField("socket", sockPath).Error("error unmounting metrics socket for plugin") - } - } } diff --git a/daemon/metrics_unix.go b/daemon/metrics_unix.go index ed40406c3e..fbf561e917 100644 --- a/daemon/metrics_unix.go +++ b/daemon/metrics_unix.go @@ -5,13 +5,13 @@ package daemon // import "github.com/docker/docker/daemon" import ( "net" "net/http" - "os" "path/filepath" - "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/plugingetter" "github.com/docker/docker/pkg/plugins" + "github.com/docker/docker/plugin" metrics "github.com/docker/go-metrics" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" @@ -34,52 +34,22 @@ func (daemon *Daemon) listenMetricsSock() (string, error) { return path, nil } -func registerMetricsPluginCallback(getter plugingetter.PluginGetter, sockPath string) { - getter.Handle(metricsPluginType, func(name string, client *plugins.Client) { +func registerMetricsPluginCallback(store *plugin.Store, sockPath string) { + store.RegisterRuntimeOpt(metricsPluginType, func(s *specs.Spec) { + f := plugin.WithSpecMounts([]specs.Mount{ + {Type: "bind", Source: sockPath, Destination: "/run/docker/metrics.sock", Options: []string{"bind", "ro"}}, + }) + f(s) + }) + store.Handle(metricsPluginType, func(name string, client *plugins.Client) { // Use lookup since nothing in the system can really reference it, no need // to protect against removal - p, err := getter.Get(name, metricsPluginType, plugingetter.Lookup) + p, err := store.Get(name, metricsPluginType, plugingetter.Lookup) if err != nil { return } - mp := metricsPlugin{p} - sockBase := mp.sockBase() - if err := os.MkdirAll(sockBase, 0755); err != nil { - logrus.WithError(err).WithField("name", name).WithField("path", sockBase).Error("error creating metrics plugin base path") - return - } - - defer func() { - if err != nil { - os.RemoveAll(sockBase) - } - }() - - pluginSockPath := filepath.Join(sockBase, mp.sock()) - _, err = os.Stat(pluginSockPath) - if err == nil { - mount.Unmount(pluginSockPath) - } else { - logrus.WithField("path", pluginSockPath).Debugf("creating plugin socket") - f, err := os.OpenFile(pluginSockPath, os.O_CREATE, 0600) - if err != nil { - return - } - f.Close() - } - - if err := mount.Mount(sockPath, pluginSockPath, "none", "bind,ro"); err != nil { - logrus.WithError(err).WithField("name", name).Error("could not mount metrics socket to plugin") - return - } - if err := pluginStartMetricsCollection(p); err != nil { - if err := mount.Unmount(pluginSockPath); err != nil { - if mounted, _ := mount.Mounted(pluginSockPath); mounted { - logrus.WithError(err).WithField("sock_path", pluginSockPath).Error("error unmounting metrics socket from plugin during cleanup") - } - } logrus.WithError(err).WithField("name", name).Error("error while initializing metrics plugin") } }) diff --git a/integration-cli/docker_cli_daemon_plugins_test.go b/integration-cli/docker_cli_daemon_plugins_test.go index 10aa514fe0..c527cb1d8e 100644 --- a/integration-cli/docker_cli_daemon_plugins_test.go +++ b/integration-cli/docker_cli_daemon_plugins_test.go @@ -3,8 +3,6 @@ package main import ( - "os" - "path/filepath" "strings" "github.com/docker/docker/integration-cli/checker" @@ -199,12 +197,6 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) { if err != nil { c.Fatalf("Could not install plugin: %v %s", err, out) } - pluginID, err := s.d.Cmd("plugin", "inspect", "-f", "{{.Id}}", pName) - pluginID = strings.TrimSpace(pluginID) - if err != nil { - c.Fatalf("Could not retrieve plugin ID: %v %s", err, pluginID) - } - mountpointPrefix := filepath.Join(s.d.RootDir(), "plugins", pluginID, "rootfs") defer func() { if out, err := s.d.Cmd("plugin", "disable", pName); err != nil { c.Fatalf("Could not disable plugin: %v %s", err, out) @@ -213,11 +205,6 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) { if out, err := s.d.Cmd("plugin", "remove", pName); err != nil { c.Fatalf("Could not remove plugin: %v %s", err, out) } - - exists, err := existsMountpointWithPrefix(mountpointPrefix) - c.Assert(err, checker.IsNil) - c.Assert(exists, checker.Equals, false) - }() out, err = s.d.Cmd("volume", "create", "-d", pName, volName) @@ -237,21 +224,11 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) { c.Assert(out, checker.Contains, volName) c.Assert(out, checker.Contains, pName) - mountPoint, err := s.d.Cmd("volume", "inspect", volName, "--format", "{{.Mountpoint}}") - if err != nil { - c.Fatalf("Could not inspect volume: %v %s", err, mountPoint) - } - mountPoint = strings.TrimSpace(mountPoint) - out, err = s.d.Cmd("run", "--rm", "-v", volName+":"+destDir, "busybox", "touch", destDir+destFile) c.Assert(err, checker.IsNil, check.Commentf(out)) - path := filepath.Join(s.d.RootDir(), "plugins", pluginID, "rootfs", mountPoint, destFile) - _, err = os.Lstat(path) - c.Assert(err, checker.IsNil) - exists, err := existsMountpointWithPrefix(mountpointPrefix) - c.Assert(err, checker.IsNil) - c.Assert(exists, checker.Equals, true) + out, err = s.d.Cmd("run", "--rm", "-v", volName+":"+destDir, "busybox", "ls", destDir+destFile) + c.Assert(err, checker.IsNil, check.Commentf(out)) } func (s *DockerDaemonSuite) TestGraphdriverPlugin(c *check.C) { diff --git a/pkg/plugingetter/getter.go b/pkg/plugingetter/getter.go index e0a3256779..0e1699d913 100644 --- a/pkg/plugingetter/getter.go +++ b/pkg/plugingetter/getter.go @@ -17,7 +17,7 @@ const ( type CompatPlugin interface { Client() *plugins.Client Name() string - BasePath() string + ScopedPath(string) string IsV1() bool } diff --git a/pkg/plugins/plugins_unix.go b/pkg/plugins/plugins_unix.go index 9d8b51d205..cdfbe93458 100644 --- a/pkg/plugins/plugins_unix.go +++ b/pkg/plugins/plugins_unix.go @@ -2,8 +2,8 @@ package plugins // import "github.com/docker/docker/pkg/plugins" -// BasePath returns the path to which all paths returned by the plugin are relative to. -// For v1 plugins, this always returns the host's root directory. -func (p *Plugin) BasePath() string { - return "/" +// ScopedPath returns the path scoped to the plugin's rootfs. +// For v1 plugins, this always returns the path unchanged as v1 plugins run directly on the host. +func (p *Plugin) ScopedPath(s string) string { + return s } diff --git a/pkg/plugins/plugins_windows.go b/pkg/plugins/plugins_windows.go index c27b4c61e7..ddf1d786c6 100644 --- a/pkg/plugins/plugins_windows.go +++ b/pkg/plugins/plugins_windows.go @@ -1,8 +1,7 @@ package plugins // import "github.com/docker/docker/pkg/plugins" -// BasePath returns the path to which all paths returned by the plugin are relative to. -// For Windows v1 plugins, this returns an empty string, since the plugin is already aware -// of the absolute path of the mount. -func (p *Plugin) BasePath() string { - return "" +// ScopedPath returns the path scoped to the plugin's rootfs. +// For v1 plugins, this always returns the path unchanged as v1 plugins run directly on the host. +func (p *Plugin) ScopedPath(s string) string { + return s } diff --git a/plugin/defs.go b/plugin/defs.go index beb880102b..c158bbbfe5 100644 --- a/plugin/defs.go +++ b/plugin/defs.go @@ -5,12 +5,14 @@ import ( "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/plugin/v2" + specs "github.com/opencontainers/runtime-spec/specs-go" ) // Store manages the plugin inventory in memory and on-disk type Store struct { sync.RWMutex - plugins map[string]*v2.Plugin + plugins map[string]*v2.Plugin + specOpts map[string][]SpecOpt /* handlers are necessary for transition path of legacy plugins * to the new model. Legacy plugins use Handle() for registering an * activation callback.*/ @@ -21,10 +23,14 @@ type Store struct { func NewStore() *Store { return &Store{ plugins: make(map[string]*v2.Plugin), + specOpts: make(map[string][]SpecOpt), handlers: make(map[string][]func(string, *plugins.Client)), } } +// SpecOpt is used for subsystems that need to modify the runtime spec of a plugin +type SpecOpt func(*specs.Spec) + // CreateOpt is used to configure specific plugin details when created type CreateOpt func(p *v2.Plugin) @@ -35,3 +41,10 @@ func WithSwarmService(id string) CreateOpt { p.SwarmServiceID = id } } + +// WithSpecMounts is a SpecOpt which appends the provided mounts to the runtime spec +func WithSpecMounts(mounts []specs.Mount) SpecOpt { + return func(s *specs.Spec) { + s.Mounts = append(s.Mounts, mounts...) + } +} diff --git a/plugin/manager.go b/plugin/manager.go index b2051cb621..1879a78903 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -112,11 +112,6 @@ func NewManager(config ManagerConfig) (*Manager, error) { return nil, errors.Wrapf(err, "failed to mkdir %v", dirName) } } - - if err := setupRoot(manager.config.Root); err != nil { - return nil, err - } - var err error manager.executor, err = config.CreateExecutor(manager) if err != nil { @@ -151,16 +146,6 @@ func (pm *Manager) HandleExitEvent(id string) error { os.RemoveAll(filepath.Join(pm.config.ExecRoot, id)) - if p.PropagatedMount != "" { - if err := mount.Unmount(p.PropagatedMount); err != nil { - logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err) - } - propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") - if err := mount.Unmount(propRoot); err != nil { - logrus.Warn("Could not unmount %s: %v", propRoot, err) - } - } - pm.mu.RLock() c := pm.cMap[p] if c.exitChan != nil { @@ -171,6 +156,10 @@ func (pm *Manager) HandleExitEvent(id string) error { if restart { pm.enable(p, c, true) + } else { + if err := mount.RecursiveUnmount(filepath.Join(pm.config.Root, id)); err != nil { + return errors.Wrap(err, "error cleaning up plugin mounts") + } } return nil } @@ -239,28 +228,17 @@ func (pm *Manager) reload() error { // todo: restore // check if we need to migrate an older propagated mount from before // these mounts were stored outside the plugin rootfs if _, err := os.Stat(propRoot); os.IsNotExist(err) { - if _, err := os.Stat(p.PropagatedMount); err == nil { - // make sure nothing is mounted here - // don't care about errors - mount.Unmount(p.PropagatedMount) - if err := os.Rename(p.PropagatedMount, propRoot); err != nil { + rootfsProp := filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount) + if _, err := os.Stat(rootfsProp); err == nil { + if err := os.Rename(rootfsProp, propRoot); err != nil { logrus.WithError(err).WithField("dir", propRoot).Error("error migrating propagated mount storage") } - if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil { - logrus.WithError(err).WithField("dir", p.PropagatedMount).Error("error migrating propagated mount storage") - } } } if err := os.MkdirAll(propRoot, 0755); err != nil { logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err) } - // TODO: sanitize PropagatedMount and prevent breakout - p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount) - if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil { - logrus.Errorf("failed to create PropagatedMount directory at %s: %v", p.PropagatedMount, err) - return - } } } } diff --git a/plugin/manager_linux.go b/plugin/manager_linux.go index 256b095a5c..3fc6be4ce0 100644 --- a/plugin/manager_linux.go +++ b/plugin/manager_linux.go @@ -22,7 +22,7 @@ import ( "golang.org/x/sys/unix" ) -func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) (err error) { +func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error { p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs") if p.IsEnabled() && !force { return errors.Wrap(enabledError(p.Name()), "plugin already enabled") @@ -40,20 +40,16 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) (err error) { pm.mu.Unlock() var propRoot string - if p.PropagatedMount != "" { + if p.PluginObj.Config.PropagatedMount != "" { propRoot = filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") - if err = os.MkdirAll(propRoot, 0755); err != nil { + if err := os.MkdirAll(propRoot, 0755); err != nil { logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err) } - if err = mount.MakeRShared(propRoot); err != nil { + if err := mount.MakeRShared(propRoot); err != nil { return errors.Wrap(err, "error setting up propagated mount dir") } - - if err = mount.Mount(propRoot, p.PropagatedMount, "none", "rbind"); err != nil { - return errors.Wrap(err, "error creating mount for propagated mount") - } } rootFS := containerfs.NewLocalContainerFS(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName)) @@ -63,16 +59,12 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) (err error) { stdout, stderr := makeLoggerStreams(p.GetID()) if err := pm.executor.Create(p.GetID(), *spec, stdout, stderr); err != nil { - if p.PropagatedMount != "" { - if err := mount.Unmount(p.PropagatedMount); err != nil { - logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err) - } + if p.PluginObj.Config.PropagatedMount != "" { if err := mount.Unmount(propRoot); err != nil { logrus.Warnf("Could not unmount %s: %v", propRoot, err) } } } - return pm.pluginPostStart(p, c) } @@ -167,13 +159,6 @@ func shutdownPlugin(p *v2.Plugin, c *controller, executor Executor) { } } -func setupRoot(root string) error { - if err := mount.MakePrivate(root); err != nil { - return errors.Wrap(err, "error setting plugin manager root to private") - } - return nil -} - func (pm *Manager) disable(p *v2.Plugin, c *controller) error { if !p.IsEnabled() { return errors.Wrap(errDisabled(p.Name()), "plugin is already disabled") @@ -202,7 +187,9 @@ func (pm *Manager) Shutdown() { shutdownPlugin(p, c, pm.executor) } } - mount.Unmount(pm.config.Root) + if err := mount.RecursiveUnmount(pm.config.Root); err != nil { + logrus.WithError(err).Warn("error cleaning up plugin mounts") + } } func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest digest.Digest, blobsums []digest.Digest, tmpRootFSDir string, privileges *types.PluginPrivileges) (err error) { diff --git a/plugin/manager_windows.go b/plugin/manager_windows.go index 309a31c2cf..9fafea5c22 100644 --- a/plugin/manager_windows.go +++ b/plugin/manager_windows.go @@ -26,5 +26,3 @@ func (pm *Manager) restore(p *v2.Plugin) error { // Shutdown plugins func (pm *Manager) Shutdown() { } - -func setupRoot(root string) error { return nil } diff --git a/plugin/store.go b/plugin/store.go index 735eea423d..5cb994b759 100644 --- a/plugin/store.go +++ b/plugin/store.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/pkg/plugingetter" "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/plugin/v2" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -64,6 +65,10 @@ func (ps *Store) GetAll() map[string]*v2.Plugin { func (ps *Store) SetAll(plugins map[string]*v2.Plugin) { ps.Lock() defer ps.Unlock() + + for _, p := range plugins { + ps.setSpecOpts(p) + } ps.plugins = plugins } @@ -90,6 +95,22 @@ func (ps *Store) SetState(p *v2.Plugin, state bool) { p.PluginObj.Enabled = state } +func (ps *Store) setSpecOpts(p *v2.Plugin) { + var specOpts []SpecOpt + for _, typ := range p.GetTypes() { + opts, ok := ps.specOpts[typ.String()] + if ok { + specOpts = append(specOpts, opts...) + } + } + + p.SetSpecOptModifier(func(s *specs.Spec) { + for _, o := range specOpts { + o(s) + } + }) +} + // Add adds a plugin to memory and plugindb. // An error will be returned if there is a collision. func (ps *Store) Add(p *v2.Plugin) error { @@ -99,6 +120,9 @@ func (ps *Store) Add(p *v2.Plugin) error { if v, exist := ps.plugins[p.GetID()]; exist { return fmt.Errorf("plugin %q has the same ID %s as %q", p.Name(), p.GetID(), v.Name()) } + + ps.setSpecOpts(p) + ps.plugins[p.GetID()] = p return nil } @@ -182,20 +206,24 @@ func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, er return result, nil } +func pluginType(cap string) string { + return fmt.Sprintf("docker.%s/%s", strings.ToLower(cap), defaultAPIVersion) +} + // Handle sets a callback for a given capability. It is only used by network // and ipam drivers during plugin registration. The callback registers the // driver with the subsystem (network, ipam). func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) { - pluginType := fmt.Sprintf("docker.%s/%s", strings.ToLower(capability), defaultAPIVersion) + typ := pluginType(capability) // Register callback with new plugin model. ps.Lock() - handlers, ok := ps.handlers[pluginType] + handlers, ok := ps.handlers[typ] if !ok { handlers = []func(string, *plugins.Client){} } handlers = append(handlers, callback) - ps.handlers[pluginType] = handlers + ps.handlers[typ] = handlers ps.Unlock() // Register callback with legacy plugin model. @@ -204,6 +232,15 @@ func (ps *Store) Handle(capability string, callback func(string, *plugins.Client } } +// RegisterRuntimeOpt stores a list of SpecOpts for the provided capability. +// These options are applied to the runtime spec before a plugin is started for the specified capability. +func (ps *Store) RegisterRuntimeOpt(cap string, opts ...SpecOpt) { + ps.Lock() + defer ps.Unlock() + typ := pluginType(cap) + ps.specOpts[typ] = append(ps.specOpts[typ], opts...) +} + // CallHandler calls the registered callback. It is invoked during plugin enable. func (ps *Store) CallHandler(p *v2.Plugin) { for _, typ := range p.GetTypes() { diff --git a/plugin/v2/plugin.go b/plugin/v2/plugin.go index 42028be5de..c00a9d0169 100644 --- a/plugin/v2/plugin.go +++ b/plugin/v2/plugin.go @@ -2,6 +2,7 @@ package v2 // import "github.com/docker/docker/plugin/v2" import ( "fmt" + "path/filepath" "strings" "sync" @@ -9,20 +10,22 @@ import ( "github.com/docker/docker/pkg/plugingetter" "github.com/docker/docker/pkg/plugins" "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/runtime-spec/specs-go" ) // Plugin represents an individual plugin. type Plugin struct { - mu sync.RWMutex - PluginObj types.Plugin `json:"plugin"` // todo: embed struct - pClient *plugins.Client - refCount int - PropagatedMount string // TODO: make private - Rootfs string // TODO: make private + mu sync.RWMutex + PluginObj types.Plugin `json:"plugin"` // todo: embed struct + pClient *plugins.Client + refCount int + Rootfs string // TODO: make private Config digest.Digest Blobsums []digest.Digest + modifyRuntimeSpec func(*specs.Spec) + SwarmServiceID string } @@ -37,10 +40,13 @@ func (e ErrInadequateCapability) Error() string { return fmt.Sprintf("plugin does not provide %q capability", e.cap) } -// BasePath returns the path to which all paths returned by the plugin are relative to. -// For Plugin objects this returns the host path of the plugin container's rootfs. -func (p *Plugin) BasePath() string { - return p.Rootfs +// ScopedPath returns the path scoped to the plugin rootfs +func (p *Plugin) ScopedPath(s string) string { + if p.PluginObj.Config.PropagatedMount != "" && strings.HasPrefix(s, p.PluginObj.Config.PropagatedMount) { + // re-scope to the propagated mount path on the host + return filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount", strings.TrimPrefix(s, p.PluginObj.Config.PropagatedMount)) + } + return filepath.Join(p.Rootfs, s) } // Client returns the plugin client. @@ -250,3 +256,11 @@ func (p *Plugin) Acquire() { func (p *Plugin) Release() { p.AddRefCount(plugingetter.Release) } + +// SetSpecOptModifier sets the function to use to modify the the generated +// runtime spec. +func (p *Plugin) SetSpecOptModifier(f func(*specs.Spec)) { + p.mu.Lock() + p.modifyRuntimeSpec = f + p.mu.Unlock() +} diff --git a/plugin/v2/plugin_linux.go b/plugin/v2/plugin_linux.go index 05d0f0e3b5..d0fc97268d 100644 --- a/plugin/v2/plugin_linux.go +++ b/plugin/v2/plugin_linux.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "runtime" + "sort" "strings" "github.com/docker/docker/api/types" @@ -16,6 +17,7 @@ import ( // InitSpec creates an OCI spec from the plugin's config. func (p *Plugin) InitSpec(execRoot string) (*specs.Spec, error) { s := oci.DefaultSpec() + s.Root = &specs.Root{ Path: p.Rootfs, Readonly: false, // TODO: all plugins should be readonly? settable in config? @@ -31,6 +33,17 @@ func (p *Plugin) InitSpec(execRoot string) (*specs.Spec, error) { return nil, errors.WithStack(err) } + if p.PluginObj.Config.PropagatedMount != "" { + pRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") + s.Mounts = append(s.Mounts, specs.Mount{ + Source: pRoot, + Destination: p.PluginObj.Config.PropagatedMount, + Type: "bind", + Options: []string{"rbind", "rw", "rshared"}, + }) + s.Linux.RootfsPropagation = "rshared" + } + mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ Source: &execRoot, Destination: defaultPluginRuntimeDestination, @@ -88,11 +101,6 @@ func (p *Plugin) InitSpec(execRoot string) (*specs.Spec, error) { } } - if p.PluginObj.Config.PropagatedMount != "" { - p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount) - s.Linux.RootfsPropagation = "rshared" - } - if p.PluginObj.Config.Linux.AllowAllDevices { s.Linux.Resources.Devices = []specs.LinuxDeviceCgroup{{Allow: true, Access: "rwm"}} } @@ -126,5 +134,13 @@ func (p *Plugin) InitSpec(execRoot string) (*specs.Spec, error) { caps.Inheritable = append(caps.Inheritable, p.PluginObj.Config.Linux.Capabilities...) caps.Effective = append(caps.Effective, p.PluginObj.Config.Linux.Capabilities...) + if p.modifyRuntimeSpec != nil { + p.modifyRuntimeSpec(&s) + } + + sort.Slice(s.Mounts, func(i, j int) bool { + return s.Mounts[i].Destination < s.Mounts[j].Destination + }) + return &s, nil } diff --git a/volume/drivers/adapter.go b/volume/drivers/adapter.go index 76ed39bda3..8415c7f6c9 100644 --- a/volume/drivers/adapter.go +++ b/volume/drivers/adapter.go @@ -2,7 +2,6 @@ package volumedrivers // import "github.com/docker/docker/volume/drivers" import ( "errors" - "path/filepath" "strings" "time" @@ -16,7 +15,7 @@ var ( type volumeDriverAdapter struct { name string - baseHostPath string + scopePath func(s string) string capabilities *volume.Capability proxy *volumeDriverProxy } @@ -30,10 +29,10 @@ func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volum return nil, err } return &volumeAdapter{ - proxy: a.proxy, - name: name, - driverName: a.name, - baseHostPath: a.baseHostPath, + proxy: a.proxy, + name: name, + driverName: a.name, + scopePath: a.scopePath, }, nil } @@ -41,13 +40,6 @@ func (a *volumeDriverAdapter) Remove(v volume.Volume) error { return a.proxy.Remove(v.Name()) } -func hostPath(baseHostPath, path string) string { - if baseHostPath != "" { - path = filepath.Join(baseHostPath, path) - } - return path -} - func (a *volumeDriverAdapter) List() ([]volume.Volume, error) { ls, err := a.proxy.List() if err != nil { @@ -57,11 +49,11 @@ func (a *volumeDriverAdapter) List() ([]volume.Volume, error) { var out []volume.Volume for _, vp := range ls { out = append(out, &volumeAdapter{ - proxy: a.proxy, - name: vp.Name, - baseHostPath: a.baseHostPath, - driverName: a.name, - eMount: hostPath(a.baseHostPath, vp.Mountpoint), + proxy: a.proxy, + name: vp.Name, + scopePath: a.scopePath, + driverName: a.name, + eMount: a.scopePath(vp.Mountpoint), }) } return out, nil @@ -79,13 +71,13 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) { } return &volumeAdapter{ - proxy: a.proxy, - name: v.Name, - driverName: a.Name(), - eMount: v.Mountpoint, - createdAt: v.CreatedAt, - status: v.Status, - baseHostPath: a.baseHostPath, + proxy: a.proxy, + name: v.Name, + driverName: a.Name(), + eMount: v.Mountpoint, + createdAt: v.CreatedAt, + status: v.Status, + scopePath: a.scopePath, }, nil } @@ -122,13 +114,13 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability { } type volumeAdapter struct { - proxy *volumeDriverProxy - name string - baseHostPath string - driverName string - eMount string // ephemeral host volume path - createdAt time.Time // time the directory was created - status map[string]interface{} + proxy *volumeDriverProxy + name string + scopePath func(string) string + driverName string + eMount string // ephemeral host volume path + createdAt time.Time // time the directory was created + status map[string]interface{} } type proxyVolume struct { @@ -149,7 +141,7 @@ func (a *volumeAdapter) DriverName() string { func (a *volumeAdapter) Path() string { if len(a.eMount) == 0 { mountpoint, _ := a.proxy.Path(a.name) - a.eMount = hostPath(a.baseHostPath, mountpoint) + a.eMount = a.scopePath(mountpoint) } return a.eMount } @@ -160,7 +152,7 @@ func (a *volumeAdapter) CachedPath() string { func (a *volumeAdapter) Mount(id string) (string, error) { mountpoint, err := a.proxy.Mount(a.name, id) - a.eMount = hostPath(a.baseHostPath, mountpoint) + a.eMount = a.scopePath(mountpoint) return a.eMount, err } diff --git a/volume/drivers/extpoint.go b/volume/drivers/extpoint.go index a239035478..15cf850d74 100644 --- a/volume/drivers/extpoint.go +++ b/volume/drivers/extpoint.go @@ -25,9 +25,9 @@ var drivers = &driverExtpoint{ const extName = "VolumeDriver" // NewVolumeDriver returns a driver has the given name mapped on the given client. -func NewVolumeDriver(name string, baseHostPath string, c client) volume.Driver { +func NewVolumeDriver(name string, scopePath func(string) string, c client) volume.Driver { proxy := &volumeDriverProxy{c} - return &volumeDriverAdapter{name: name, baseHostPath: baseHostPath, proxy: proxy} + return &volumeDriverAdapter{name: name, scopePath: scopePath, proxy: proxy} } // volumeDriver defines the available functions that volume plugins must implement. @@ -129,7 +129,7 @@ func lookup(name string, mode int) (volume.Driver, error) { return nil, errors.Wrap(err, "error looking up volume plugin "+name) } - d := NewVolumeDriver(p.Name(), p.BasePath(), p.Client()) + d := NewVolumeDriver(p.Name(), p.ScopedPath, p.Client()) if err := validateDriver(d); err != nil { if mode > 0 { // Undo any reference count changes from the initial `Get` @@ -224,7 +224,7 @@ func GetAllDrivers() ([]volume.Driver, error) { continue } - ext := NewVolumeDriver(name, p.BasePath(), p.Client()) + ext := NewVolumeDriver(name, p.ScopedPath, p.Client()) if p.IsV1() { drivers.extensions[name] = ext } diff --git a/volume/testutils/testutils.go b/volume/testutils/testutils.go index 45f445981e..a328db460d 100644 --- a/volume/testutils/testutils.go +++ b/volume/testutils/testutils.go @@ -178,8 +178,8 @@ func (p *fakePlugin) IsV1() bool { return false } -func (p *fakePlugin) BasePath() string { - return "" +func (p *fakePlugin) ScopedPath(s string) string { + return s } type fakePluginGetter struct {