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