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 }