github.com/rish1988/moby@v25.0.2+incompatible/daemon/cluster/controllers/plugin/controller.go (about)

     1  package plugin // import "github.com/docker/docker/daemon/cluster/controllers/plugin"
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net/http"
     7  
     8  	"github.com/containerd/log"
     9  	"github.com/distribution/reference"
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/backend"
    12  	"github.com/docker/docker/api/types/registry"
    13  	"github.com/docker/docker/api/types/swarm/runtime"
    14  	"github.com/docker/docker/errdefs"
    15  	"github.com/docker/docker/plugin"
    16  	v2 "github.com/docker/docker/plugin/v2"
    17  	"github.com/gogo/protobuf/proto"
    18  	"github.com/moby/swarmkit/v2/api"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  // Controller is the controller for the plugin backend.
    23  // Plugins are managed as a singleton object with a desired state (different from containers).
    24  // With the plugin controller instead of having a strict create->start->stop->remove
    25  // task lifecycle like containers, we manage the desired state of the plugin and let
    26  // the plugin manager do what it already does and monitor the plugin.
    27  // We'll also end up with many tasks all pointing to the same plugin ID.
    28  //
    29  // TODO(@cpuguy83): registry auth is intentionally not supported until we work out
    30  // the right way to pass registry credentials via secrets.
    31  type Controller struct {
    32  	backend Backend
    33  	spec    runtime.PluginSpec
    34  	logger  *log.Entry
    35  
    36  	pluginID  string
    37  	serviceID string
    38  
    39  	// hook used to signal tests that `Wait()` is actually ready and waiting
    40  	signalWaitReady func()
    41  }
    42  
    43  // Backend is the interface for interacting with the plugin manager
    44  // Controller actions are passed to the configured backend to do the real work.
    45  type Backend interface {
    46  	Disable(name string, config *backend.PluginDisableConfig) error
    47  	Enable(name string, config *backend.PluginEnableConfig) error
    48  	Remove(name string, config *backend.PluginRmConfig) error
    49  	Pull(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *registry.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer, opts ...plugin.CreateOpt) error
    50  	Upgrade(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *registry.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) error
    51  	Get(name string) (*v2.Plugin, error)
    52  	SubscribeEvents(buffer int, events ...plugin.Event) (eventCh <-chan interface{}, cancel func())
    53  }
    54  
    55  // NewController returns a new cluster plugin controller
    56  func NewController(backend Backend, t *api.Task) (*Controller, error) {
    57  	spec, err := readSpec(t)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	return &Controller{
    62  		backend:   backend,
    63  		spec:      spec,
    64  		serviceID: t.ServiceID,
    65  		logger: log.G(context.TODO()).WithFields(log.Fields{
    66  			"controller": "plugin",
    67  			"task":       t.ID,
    68  			"plugin":     spec.Name,
    69  		}),
    70  	}, nil
    71  }
    72  
    73  func readSpec(t *api.Task) (runtime.PluginSpec, error) {
    74  	var cfg runtime.PluginSpec
    75  
    76  	generic := t.Spec.GetGeneric()
    77  	if err := proto.Unmarshal(generic.Payload.Value, &cfg); err != nil {
    78  		return cfg, errors.Wrap(err, "error reading plugin spec")
    79  	}
    80  	return cfg, nil
    81  }
    82  
    83  // Update is the update phase from swarmkit
    84  func (p *Controller) Update(ctx context.Context, t *api.Task) error {
    85  	p.logger.Debug("Update")
    86  	return nil
    87  }
    88  
    89  // Prepare is the prepare phase from swarmkit
    90  func (p *Controller) Prepare(ctx context.Context) (err error) {
    91  	p.logger.Debug("Prepare")
    92  
    93  	remote, err := reference.ParseNormalizedNamed(p.spec.Remote)
    94  	if err != nil {
    95  		return errors.Wrapf(err, "error parsing remote reference %q", p.spec.Remote)
    96  	}
    97  
    98  	if p.spec.Name == "" {
    99  		p.spec.Name = remote.String()
   100  	}
   101  
   102  	var authConfig registry.AuthConfig
   103  	privs := convertPrivileges(p.spec.Privileges)
   104  
   105  	pl, err := p.backend.Get(p.spec.Name)
   106  
   107  	defer func() {
   108  		if pl != nil && err == nil {
   109  			pl.Acquire()
   110  		}
   111  	}()
   112  
   113  	if err == nil && pl != nil {
   114  		if pl.SwarmServiceID != p.serviceID {
   115  			return errors.Errorf("plugin already exists: %s", p.spec.Name)
   116  		}
   117  		if pl.IsEnabled() {
   118  			if err := p.backend.Disable(pl.GetID(), &backend.PluginDisableConfig{ForceDisable: true}); err != nil {
   119  				p.logger.WithError(err).Debug("could not disable plugin before running upgrade")
   120  			}
   121  		}
   122  		p.pluginID = pl.GetID()
   123  		return p.backend.Upgrade(ctx, remote, p.spec.Name, nil, &authConfig, privs, io.Discard)
   124  	}
   125  
   126  	if err := p.backend.Pull(ctx, remote, p.spec.Name, nil, &authConfig, privs, io.Discard, plugin.WithSwarmService(p.serviceID), plugin.WithEnv(p.spec.Env)); err != nil {
   127  		return err
   128  	}
   129  	pl, err = p.backend.Get(p.spec.Name)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	p.pluginID = pl.GetID()
   134  
   135  	return nil
   136  }
   137  
   138  // Start is the start phase from swarmkit
   139  func (p *Controller) Start(ctx context.Context) error {
   140  	p.logger.Debug("Start")
   141  
   142  	pl, err := p.backend.Get(p.pluginID)
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	if p.spec.Disabled {
   148  		if pl.IsEnabled() {
   149  			return p.backend.Disable(p.pluginID, &backend.PluginDisableConfig{ForceDisable: false})
   150  		}
   151  		return nil
   152  	}
   153  	if !pl.IsEnabled() {
   154  		return p.backend.Enable(p.pluginID, &backend.PluginEnableConfig{Timeout: 30})
   155  	}
   156  	return nil
   157  }
   158  
   159  // Wait causes the task to wait until returned
   160  func (p *Controller) Wait(ctx context.Context) error {
   161  	p.logger.Debug("Wait")
   162  
   163  	pl, err := p.backend.Get(p.pluginID)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	events, cancel := p.backend.SubscribeEvents(1, plugin.EventDisable{Plugin: pl.PluginObj}, plugin.EventRemove{Plugin: pl.PluginObj}, plugin.EventEnable{Plugin: pl.PluginObj})
   169  	defer cancel()
   170  
   171  	if p.signalWaitReady != nil {
   172  		p.signalWaitReady()
   173  	}
   174  
   175  	if !p.spec.Disabled != pl.IsEnabled() {
   176  		return errors.New("mismatched plugin state")
   177  	}
   178  
   179  	for {
   180  		select {
   181  		case <-ctx.Done():
   182  			return ctx.Err()
   183  		case e := <-events:
   184  			p.logger.Debugf("got event %T", e)
   185  
   186  			switch e.(type) {
   187  			case plugin.EventEnable:
   188  				if p.spec.Disabled {
   189  					return errors.New("plugin enabled")
   190  				}
   191  			case plugin.EventRemove:
   192  				return errors.New("plugin removed")
   193  			case plugin.EventDisable:
   194  				if !p.spec.Disabled {
   195  					return errors.New("plugin disabled")
   196  				}
   197  			}
   198  		}
   199  	}
   200  }
   201  
   202  func isNotFound(err error) bool {
   203  	return errdefs.IsNotFound(err)
   204  }
   205  
   206  // Shutdown is the shutdown phase from swarmkit
   207  func (p *Controller) Shutdown(ctx context.Context) error {
   208  	p.logger.Debug("Shutdown")
   209  	return nil
   210  }
   211  
   212  // Terminate is the terminate phase from swarmkit
   213  func (p *Controller) Terminate(ctx context.Context) error {
   214  	p.logger.Debug("Terminate")
   215  	return nil
   216  }
   217  
   218  // Remove is the remove phase from swarmkit
   219  func (p *Controller) Remove(ctx context.Context) error {
   220  	p.logger.Debug("Remove")
   221  
   222  	pl, err := p.backend.Get(p.pluginID)
   223  	if err != nil {
   224  		if isNotFound(err) {
   225  			return nil
   226  		}
   227  		return err
   228  	}
   229  
   230  	pl.Release()
   231  	if pl.GetRefCount() > 0 {
   232  		p.logger.Debug("skipping remove due to ref count")
   233  		return nil
   234  	}
   235  
   236  	// This may error because we have exactly 1 plugin, but potentially multiple
   237  	// tasks which are calling remove.
   238  	err = p.backend.Remove(p.pluginID, &backend.PluginRmConfig{ForceRemove: true})
   239  	if isNotFound(err) {
   240  		return nil
   241  	}
   242  	return err
   243  }
   244  
   245  // Close is the close phase from swarmkit
   246  func (p *Controller) Close() error {
   247  	p.logger.Debug("Close")
   248  	return nil
   249  }
   250  
   251  func convertPrivileges(ls []*runtime.PluginPrivilege) types.PluginPrivileges {
   252  	var out types.PluginPrivileges
   253  	for _, p := range ls {
   254  		pp := types.PluginPrivilege{
   255  			Name:        p.Name,
   256  			Description: p.Description,
   257  			Value:       p.Value,
   258  		}
   259  		out = append(out, pp)
   260  	}
   261  	return out
   262  }