//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type volumeDriver -name VolumeDriver package drivers // import "github.com/docker/docker/volume/drivers" import ( "fmt" "sort" "sync" "github.com/docker/docker/errdefs" getter "github.com/docker/docker/pkg/plugingetter" "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/volume" "github.com/moby/locker" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) const extName = "VolumeDriver" // volumeDriver defines the available functions that volume plugins must implement. // This interface is only defined to generate the proxy objects. // It's not intended to be public or reused. //nolint: deadcode type volumeDriver interface { // Create a volume with the given name Create(name string, opts map[string]string) (err error) // Remove the volume with the given name Remove(name string) (err error) // Get the mountpoint of the given volume Path(name string) (mountpoint string, err error) // Mount the given volume and return the mountpoint Mount(name, id string) (mountpoint string, err error) // Unmount the given volume Unmount(name, id string) (err error) // List lists all the volumes known to the driver List() (volumes []*proxyVolume, err error) // Get retrieves the volume with the requested name Get(name string) (volume *proxyVolume, err error) // Capabilities gets the list of capabilities of the driver Capabilities() (capabilities volume.Capability, err error) } // Store is an in-memory store for volume drivers type Store struct { extensions map[string]volume.Driver mu sync.Mutex driverLock *locker.Locker pluginGetter getter.PluginGetter } // NewStore creates a new volume driver store func NewStore(pg getter.PluginGetter) *Store { return &Store{ extensions: make(map[string]volume.Driver), driverLock: locker.New(), pluginGetter: pg, } } type driverNotFoundError string func (e driverNotFoundError) Error() string { return "volume driver not found: " + string(e) } func (driverNotFoundError) NotFound() {} // lookup returns the driver associated with the given name. If a // driver with the given name has not been registered it checks if // there is a VolumeDriver plugin available with the given name. func (s *Store) lookup(name string, mode int) (volume.Driver, error) { if name == "" { return nil, errdefs.InvalidParameter(errors.New("driver name cannot be empty")) } s.driverLock.Lock(name) defer s.driverLock.Unlock(name) s.mu.Lock() ext, ok := s.extensions[name] s.mu.Unlock() if ok { return ext, nil } if s.pluginGetter != nil { p, err := s.pluginGetter.Get(name, extName, mode) if err != nil { return nil, errors.Wrap(err, "error looking up volume plugin "+name) } d, err := makePluginAdapter(p) if err != nil { return nil, errors.Wrap(err, "error making plugin client") } if err := validateDriver(d); err != nil { if mode > 0 { // Undo any reference count changes from the initial `Get` if _, err := s.pluginGetter.Get(name, extName, mode*-1); err != nil { logrus.WithError(err).WithField("action", "validate-driver").WithField("plugin", name).Error("error releasing reference to plugin") } } return nil, err } if p.IsV1() { s.mu.Lock() s.extensions[name] = d s.mu.Unlock() } return d, nil } return nil, driverNotFoundError(name) } func validateDriver(vd volume.Driver) error { scope := vd.Scope() if scope != volume.LocalScope && scope != volume.GlobalScope { return fmt.Errorf("Driver %q provided an invalid capability scope: %s", vd.Name(), scope) } return nil } // Register associates the given driver to the given name, checking if // the name is already associated func (s *Store) Register(d volume.Driver, name string) bool { if name == "" { return false } s.mu.Lock() defer s.mu.Unlock() if _, exists := s.extensions[name]; exists { return false } if err := validateDriver(d); err != nil { return false } s.extensions[name] = d return true } // GetDriver returns a volume driver by its name. // If the driver is empty, it looks for the local driver. func (s *Store) GetDriver(name string) (volume.Driver, error) { return s.lookup(name, getter.Lookup) } // CreateDriver returns a volume driver by its name and increments RefCount. // If the driver is empty, it looks for the local driver. func (s *Store) CreateDriver(name string) (volume.Driver, error) { return s.lookup(name, getter.Acquire) } // ReleaseDriver returns a volume driver by its name and decrements RefCount.. // If the driver is empty, it looks for the local driver. func (s *Store) ReleaseDriver(name string) (volume.Driver, error) { return s.lookup(name, getter.Release) } // GetDriverList returns list of volume drivers registered. // If no driver is registered, empty string list will be returned. func (s *Store) GetDriverList() []string { var driverList []string s.mu.Lock() defer s.mu.Unlock() for driverName := range s.extensions { driverList = append(driverList, driverName) } sort.Strings(driverList) return driverList } // GetAllDrivers lists all the registered drivers func (s *Store) GetAllDrivers() ([]volume.Driver, error) { var plugins []getter.CompatPlugin if s.pluginGetter != nil { var err error plugins, err = s.pluginGetter.GetAllByCap(extName) if err != nil { return nil, fmt.Errorf("error listing plugins: %v", err) } } var ds []volume.Driver s.mu.Lock() defer s.mu.Unlock() for _, d := range s.extensions { ds = append(ds, d) } for _, p := range plugins { name := p.Name() if _, ok := s.extensions[name]; ok { continue } ext, err := makePluginAdapter(p) if err != nil { return nil, errors.Wrap(err, "error making plugin client") } if p.IsV1() { s.extensions[name] = ext } ds = append(ds, ext) } return ds, nil } func makePluginAdapter(p getter.CompatPlugin) (*volumeDriverAdapter, error) { if pc, ok := p.(getter.PluginWithV1Client); ok { return &volumeDriverAdapter{name: p.Name(), scopePath: p.ScopedPath, proxy: &volumeDriverProxy{pc.Client()}}, nil } pa, ok := p.(getter.PluginAddr) if !ok { return nil, errdefs.System(errors.Errorf("got unknown plugin instance %T", p)) } if pa.Protocol() != plugins.ProtocolSchemeHTTPV1 { return nil, errors.Errorf("plugin protocol not supported: %s", p) } addr := pa.Addr() client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout()) if err != nil { return nil, errors.Wrap(err, "error creating plugin client") } return &volumeDriverAdapter{name: p.Name(), scopePath: p.ScopedPath, proxy: &volumeDriverProxy{client}}, nil }