github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/pkg/plugins/plugins.go (about)

     1  // Package plugins provides structures and helper functions to manage Docker
     2  // plugins.
     3  //
     4  // Docker discovers plugins by looking for them in the plugin directory whenever
     5  // a user or container tries to use one by name. UNIX domain socket files must
     6  // be located under /run/docker/plugins, whereas spec files can be located
     7  // either under /etc/docker/plugins or /usr/lib/docker/plugins. This is handled
     8  // by the Registry interface, which lets you list all plugins or get a plugin by
     9  // its name if it exists.
    10  //
    11  // The plugins need to implement an HTTP server and bind this to the UNIX socket
    12  // or the address specified in the spec files.
    13  // A handshake is send at /Plugin.Activate, and plugins are expected to return
    14  // a Manifest with a list of Docker subsystems which this plugin implements.
    15  //
    16  // In order to use a plugins, you can use the `Get` with the name of the
    17  // plugin and the subsystem it implements.
    18  //
    19  //	plugin, err := plugins.Get("example", "VolumeDriver")
    20  //	if err != nil {
    21  //		return fmt.Errorf("Error looking up volume plugin example: %v", err)
    22  //	}
    23  package plugins // import "github.com/Prakhar-Agarwal-byte/moby/pkg/plugins"
    24  
    25  import (
    26  	"context"
    27  	"errors"
    28  	"fmt"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/containerd/log"
    33  	"github.com/docker/go-connections/tlsconfig"
    34  )
    35  
    36  // ProtocolSchemeHTTPV1 is the name of the protocol used for interacting with plugins using this package.
    37  const ProtocolSchemeHTTPV1 = "moby.plugins.http/v1"
    38  
    39  // ErrNotImplements is returned if the plugin does not implement the requested driver.
    40  var ErrNotImplements = errors.New("Plugin does not implement the requested driver")
    41  
    42  type plugins struct {
    43  	sync.Mutex
    44  	plugins map[string]*Plugin
    45  }
    46  
    47  type extpointHandlers struct {
    48  	sync.RWMutex
    49  	extpointHandlers map[string][]func(string, *Client)
    50  }
    51  
    52  var (
    53  	storage  = plugins{plugins: make(map[string]*Plugin)}
    54  	handlers = extpointHandlers{extpointHandlers: make(map[string][]func(string, *Client))}
    55  )
    56  
    57  // Manifest lists what a plugin implements.
    58  type Manifest struct {
    59  	// List of subsystem the plugin implements.
    60  	Implements []string
    61  }
    62  
    63  // Plugin is the definition of a docker plugin.
    64  type Plugin struct {
    65  	// Name of the plugin
    66  	name string
    67  	// Address of the plugin
    68  	Addr string
    69  	// TLS configuration of the plugin
    70  	TLSConfig *tlsconfig.Options
    71  	// Client attached to the plugin
    72  	client *Client
    73  	// Manifest of the plugin (see above)
    74  	Manifest *Manifest `json:"-"`
    75  
    76  	// wait for activation to finish
    77  	activateWait *sync.Cond
    78  	// error produced by activation
    79  	activateErr error
    80  	// keeps track of callback handlers run against this plugin
    81  	handlersRun bool
    82  }
    83  
    84  // Name returns the name of the plugin.
    85  func (p *Plugin) Name() string {
    86  	return p.name
    87  }
    88  
    89  // Client returns a ready-to-use plugin client that can be used to communicate with the plugin.
    90  func (p *Plugin) Client() *Client {
    91  	return p.client
    92  }
    93  
    94  // Protocol returns the protocol name/version used for plugins in this package.
    95  func (p *Plugin) Protocol() string {
    96  	return ProtocolSchemeHTTPV1
    97  }
    98  
    99  // IsV1 returns true for V1 plugins and false otherwise.
   100  func (p *Plugin) IsV1() bool {
   101  	return true
   102  }
   103  
   104  // ScopedPath returns the path scoped to the plugin's rootfs.
   105  // For v1 plugins, this always returns the path unchanged as v1 plugins run directly on the host.
   106  func (p *Plugin) ScopedPath(s string) string {
   107  	return s
   108  }
   109  
   110  // NewLocalPlugin creates a new local plugin.
   111  func NewLocalPlugin(name, addr string) *Plugin {
   112  	return &Plugin{
   113  		name: name,
   114  		Addr: addr,
   115  		// TODO: change to nil
   116  		TLSConfig:    &tlsconfig.Options{InsecureSkipVerify: true},
   117  		activateWait: sync.NewCond(&sync.Mutex{}),
   118  	}
   119  }
   120  
   121  func (p *Plugin) activate() error {
   122  	p.activateWait.L.Lock()
   123  
   124  	if p.activated() {
   125  		p.runHandlers()
   126  		p.activateWait.L.Unlock()
   127  		return p.activateErr
   128  	}
   129  
   130  	p.activateErr = p.activateWithLock()
   131  
   132  	p.runHandlers()
   133  	p.activateWait.L.Unlock()
   134  	p.activateWait.Broadcast()
   135  	return p.activateErr
   136  }
   137  
   138  // runHandlers runs the registered handlers for the implemented plugin types
   139  // This should only be run after activation, and while the activation lock is held.
   140  func (p *Plugin) runHandlers() {
   141  	if !p.activated() {
   142  		return
   143  	}
   144  
   145  	handlers.RLock()
   146  	if !p.handlersRun {
   147  		for _, iface := range p.Manifest.Implements {
   148  			hdlrs, handled := handlers.extpointHandlers[iface]
   149  			if !handled {
   150  				continue
   151  			}
   152  			for _, handler := range hdlrs {
   153  				handler(p.name, p.client)
   154  			}
   155  		}
   156  		p.handlersRun = true
   157  	}
   158  	handlers.RUnlock()
   159  }
   160  
   161  // activated returns if the plugin has already been activated.
   162  // This should only be called with the activation lock held
   163  func (p *Plugin) activated() bool {
   164  	return p.Manifest != nil
   165  }
   166  
   167  func (p *Plugin) activateWithLock() error {
   168  	c, err := NewClient(p.Addr, p.TLSConfig)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	p.client = c
   173  
   174  	m := new(Manifest)
   175  	if err = p.client.Call("Plugin.Activate", nil, m); err != nil {
   176  		return err
   177  	}
   178  
   179  	p.Manifest = m
   180  	return nil
   181  }
   182  
   183  func (p *Plugin) waitActive() error {
   184  	p.activateWait.L.Lock()
   185  	for !p.activated() && p.activateErr == nil {
   186  		p.activateWait.Wait()
   187  	}
   188  	p.activateWait.L.Unlock()
   189  	return p.activateErr
   190  }
   191  
   192  func (p *Plugin) implements(kind string) bool {
   193  	if p.Manifest == nil {
   194  		return false
   195  	}
   196  	for _, driver := range p.Manifest.Implements {
   197  		if driver == kind {
   198  			return true
   199  		}
   200  	}
   201  	return false
   202  }
   203  
   204  func loadWithRetry(name string, retry bool) (*Plugin, error) {
   205  	registry := NewLocalRegistry()
   206  	start := time.Now()
   207  	var testTimeOut int
   208  	if name == testNonExistingPlugin {
   209  		// override the timeout in tests
   210  		testTimeOut = 2
   211  	}
   212  	var retries int
   213  	for {
   214  		pl, err := registry.Plugin(name)
   215  		if err != nil {
   216  			if !retry {
   217  				return nil, err
   218  			}
   219  
   220  			timeOff := backoff(retries)
   221  			if abort(start, timeOff, testTimeOut) {
   222  				return nil, err
   223  			}
   224  			retries++
   225  			log.G(context.TODO()).Warnf("Unable to locate plugin: %s, retrying in %v", name, timeOff)
   226  			time.Sleep(timeOff)
   227  			continue
   228  		}
   229  
   230  		storage.Lock()
   231  		if pl, exists := storage.plugins[name]; exists {
   232  			storage.Unlock()
   233  			return pl, pl.activate()
   234  		}
   235  		storage.plugins[name] = pl
   236  		storage.Unlock()
   237  
   238  		err = pl.activate()
   239  
   240  		if err != nil {
   241  			storage.Lock()
   242  			delete(storage.plugins, name)
   243  			storage.Unlock()
   244  		}
   245  
   246  		return pl, err
   247  	}
   248  }
   249  
   250  func get(name string) (*Plugin, error) {
   251  	storage.Lock()
   252  	pl, ok := storage.plugins[name]
   253  	storage.Unlock()
   254  	if ok {
   255  		return pl, pl.activate()
   256  	}
   257  	return loadWithRetry(name, true)
   258  }
   259  
   260  // Get returns the plugin given the specified name and requested implementation.
   261  func Get(name, imp string) (*Plugin, error) {
   262  	if name == "" {
   263  		return nil, errors.New("Unable to find plugin without name")
   264  	}
   265  	pl, err := get(name)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	if err := pl.waitActive(); err == nil && pl.implements(imp) {
   270  		log.G(context.TODO()).Debugf("%s implements: %s", name, imp)
   271  		return pl, nil
   272  	}
   273  	return nil, fmt.Errorf("%w: plugin=%q, requested implementation=%q", ErrNotImplements, name, imp)
   274  }
   275  
   276  // Handle adds the specified function to the extpointHandlers.
   277  func Handle(iface string, fn func(string, *Client)) {
   278  	handlers.Lock()
   279  	hdlrs, ok := handlers.extpointHandlers[iface]
   280  	if !ok {
   281  		hdlrs = []func(string, *Client){}
   282  	}
   283  
   284  	hdlrs = append(hdlrs, fn)
   285  	handlers.extpointHandlers[iface] = hdlrs
   286  
   287  	storage.Lock()
   288  	for _, p := range storage.plugins {
   289  		p.activateWait.L.Lock()
   290  		if p.activated() && p.implements(iface) {
   291  			p.handlersRun = false
   292  		}
   293  		p.activateWait.L.Unlock()
   294  	}
   295  	storage.Unlock()
   296  
   297  	handlers.Unlock()
   298  }
   299  
   300  // GetAll returns all the plugins for the specified implementation
   301  func (l *LocalRegistry) GetAll(imp string) ([]*Plugin, error) {
   302  	pluginNames, err := l.Scan()
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	type plLoad struct {
   308  		pl  *Plugin
   309  		err error
   310  	}
   311  
   312  	chPl := make(chan *plLoad, len(pluginNames))
   313  	var wg sync.WaitGroup
   314  	for _, name := range pluginNames {
   315  		storage.Lock()
   316  		pl, ok := storage.plugins[name]
   317  		storage.Unlock()
   318  		if ok {
   319  			chPl <- &plLoad{pl, nil}
   320  			continue
   321  		}
   322  
   323  		wg.Add(1)
   324  		go func(name string) {
   325  			defer wg.Done()
   326  			pl, err := loadWithRetry(name, false)
   327  			chPl <- &plLoad{pl, err}
   328  		}(name)
   329  	}
   330  
   331  	wg.Wait()
   332  	close(chPl)
   333  
   334  	var out []*Plugin
   335  	for pl := range chPl {
   336  		if pl.err != nil {
   337  			log.G(context.TODO()).Error(pl.err)
   338  			continue
   339  		}
   340  		if err := pl.pl.waitActive(); err == nil && pl.pl.implements(imp) {
   341  			out = append(out, pl.pl)
   342  		}
   343  	}
   344  	return out, nil
   345  }