github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/plugin/store.go (about) 1 package plugin // import "github.com/docker/docker/plugin" 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/docker/distribution/reference" 8 "github.com/docker/docker/errdefs" 9 "github.com/docker/docker/pkg/plugingetter" 10 "github.com/docker/docker/pkg/plugins" 11 v2 "github.com/docker/docker/plugin/v2" 12 specs "github.com/opencontainers/runtime-spec/specs-go" 13 "github.com/pkg/errors" 14 "github.com/sirupsen/logrus" 15 ) 16 17 // allowV1PluginsFallback determines daemon's support for V1 plugins. 18 // When the time comes to remove support for V1 plugins, flipping 19 // this bool is all that will be needed. 20 const allowV1PluginsFallback = true 21 22 // defaultAPIVersion is the version of the plugin API for volume, network, 23 // IPAM and authz. This is a very stable API. When we update this API, then 24 // pluginType should include a version. e.g. "networkdriver/2.0". 25 const defaultAPIVersion = "1.0" 26 27 // GetV2Plugin retrieves a plugin by name, id or partial ID. 28 func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) { 29 ps.RLock() 30 defer ps.RUnlock() 31 32 id, err := ps.resolvePluginID(refOrID) 33 if err != nil { 34 return nil, err 35 } 36 37 p, idOk := ps.plugins[id] 38 if !idOk { 39 return nil, errors.WithStack(errNotFound(id)) 40 } 41 42 return p, nil 43 } 44 45 // validateName returns error if name is already reserved. always call with lock and full name 46 func (ps *Store) validateName(name string) error { 47 for _, p := range ps.plugins { 48 if p.Name() == name { 49 return alreadyExistsError(name) 50 } 51 } 52 return nil 53 } 54 55 // GetAll retrieves all plugins. 56 func (ps *Store) GetAll() map[string]*v2.Plugin { 57 ps.RLock() 58 defer ps.RUnlock() 59 return ps.plugins 60 } 61 62 // SetAll initialized plugins during daemon restore. 63 func (ps *Store) SetAll(plugins map[string]*v2.Plugin) { 64 ps.Lock() 65 defer ps.Unlock() 66 67 for _, p := range plugins { 68 ps.setSpecOpts(p) 69 } 70 ps.plugins = plugins 71 } 72 73 func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin { 74 ps.RLock() 75 defer ps.RUnlock() 76 77 result := make([]plugingetter.CompatPlugin, 0, 1) 78 for _, p := range ps.plugins { 79 if p.IsEnabled() { 80 if _, err := p.FilterByCap(capability); err == nil { 81 result = append(result, p) 82 } 83 } 84 } 85 return result 86 } 87 88 // SetState sets the active state of the plugin and updates plugindb. 89 func (ps *Store) SetState(p *v2.Plugin, state bool) { 90 ps.Lock() 91 defer ps.Unlock() 92 93 p.PluginObj.Enabled = state 94 } 95 96 func (ps *Store) setSpecOpts(p *v2.Plugin) { 97 var specOpts []SpecOpt 98 for _, typ := range p.GetTypes() { 99 opts, ok := ps.specOpts[typ.String()] 100 if ok { 101 specOpts = append(specOpts, opts...) 102 } 103 } 104 105 p.SetSpecOptModifier(func(s *specs.Spec) { 106 for _, o := range specOpts { 107 o(s) 108 } 109 }) 110 } 111 112 // Add adds a plugin to memory and plugindb. 113 // An error will be returned if there is a collision. 114 func (ps *Store) Add(p *v2.Plugin) error { 115 ps.Lock() 116 defer ps.Unlock() 117 118 if v, exist := ps.plugins[p.GetID()]; exist { 119 return fmt.Errorf("plugin %q has the same ID %s as %q", p.Name(), p.GetID(), v.Name()) 120 } 121 122 ps.setSpecOpts(p) 123 124 ps.plugins[p.GetID()] = p 125 return nil 126 } 127 128 // Remove removes a plugin from memory and plugindb. 129 func (ps *Store) Remove(p *v2.Plugin) { 130 ps.Lock() 131 delete(ps.plugins, p.GetID()) 132 ps.Unlock() 133 } 134 135 // Get returns an enabled plugin matching the given name and capability. 136 func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) { 137 // Lookup using new model. 138 if ps != nil { 139 p, err := ps.GetV2Plugin(name) 140 if err == nil { 141 if p.IsEnabled() { 142 fp, err := p.FilterByCap(capability) 143 if err != nil { 144 return nil, err 145 } 146 p.AddRefCount(mode) 147 return fp, nil 148 } 149 150 // Plugin was found but it is disabled, so we should not fall back to legacy plugins 151 // but we should error out right away 152 return nil, errDisabled(name) 153 } 154 var ierr errNotFound 155 if !errors.As(err, &ierr) { 156 return nil, err 157 } 158 } 159 160 if !allowV1PluginsFallback { 161 return nil, errNotFound(name) 162 } 163 164 p, err := plugins.Get(name, capability) 165 if err == nil { 166 return p, nil 167 } 168 if errors.Is(err, plugins.ErrNotFound) { 169 return nil, errNotFound(name) 170 } 171 return nil, errors.Wrap(errdefs.System(err), "legacy plugin") 172 } 173 174 // GetAllManagedPluginsByCap returns a list of managed plugins matching the given capability. 175 func (ps *Store) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin { 176 return ps.getAllByCap(capability) 177 } 178 179 // GetAllByCap returns a list of enabled plugins matching the given capability. 180 func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) { 181 result := make([]plugingetter.CompatPlugin, 0, 1) 182 183 /* Daemon start always calls plugin.Init thereby initializing a store. 184 * So store on experimental builds can never be nil, even while 185 * handling legacy plugins. However, there are legacy plugin unit 186 * tests where the volume subsystem directly talks with the plugin, 187 * bypassing the daemon. For such tests, this check is necessary. 188 */ 189 if ps != nil { 190 result = ps.getAllByCap(capability) 191 } 192 193 // Lookup with legacy model 194 if allowV1PluginsFallback { 195 pl, err := plugins.GetAll(capability) 196 if err != nil { 197 return nil, errors.Wrap(errdefs.System(err), "legacy plugin") 198 } 199 for _, p := range pl { 200 result = append(result, p) 201 } 202 } 203 return result, nil 204 } 205 206 func pluginType(cap string) string { 207 return fmt.Sprintf("docker.%s/%s", strings.ToLower(cap), defaultAPIVersion) 208 } 209 210 // Handle sets a callback for a given capability. It is only used by network 211 // and ipam drivers during plugin registration. The callback registers the 212 // driver with the subsystem (network, ipam). 213 func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) { 214 typ := pluginType(capability) 215 216 // Register callback with new plugin model. 217 ps.Lock() 218 handlers, ok := ps.handlers[typ] 219 if !ok { 220 handlers = []func(string, *plugins.Client){} 221 } 222 handlers = append(handlers, callback) 223 ps.handlers[typ] = handlers 224 ps.Unlock() 225 226 // Register callback with legacy plugin model. 227 if allowV1PluginsFallback { 228 plugins.Handle(capability, callback) 229 } 230 } 231 232 // RegisterRuntimeOpt stores a list of SpecOpts for the provided capability. 233 // These options are applied to the runtime spec before a plugin is started for the specified capability. 234 func (ps *Store) RegisterRuntimeOpt(cap string, opts ...SpecOpt) { 235 ps.Lock() 236 defer ps.Unlock() 237 typ := pluginType(cap) 238 ps.specOpts[typ] = append(ps.specOpts[typ], opts...) 239 } 240 241 // CallHandler calls the registered callback. It is invoked during plugin enable. 242 func (ps *Store) CallHandler(p *v2.Plugin) { 243 for _, typ := range p.GetTypes() { 244 for _, handler := range ps.handlers[typ.String()] { 245 handler(p.Name(), p.Client()) 246 } 247 } 248 } 249 250 // resolvePluginID must be protected by ps.RLock 251 func (ps *Store) resolvePluginID(idOrName string) (string, error) { 252 if validFullID.MatchString(idOrName) { 253 return idOrName, nil 254 } 255 256 ref, err := reference.ParseNormalizedNamed(idOrName) 257 if err != nil { 258 return "", errors.WithStack(errNotFound(idOrName)) 259 } 260 if _, ok := ref.(reference.Canonical); ok { 261 logrus.Warnf("canonical references cannot be resolved: %v", reference.FamiliarString(ref)) 262 return "", errors.WithStack(errNotFound(idOrName)) 263 } 264 265 ref = reference.TagNameOnly(ref) 266 267 for _, p := range ps.plugins { 268 if p.PluginObj.Name == reference.FamiliarString(ref) { 269 return p.PluginObj.ID, nil 270 } 271 } 272 273 var found *v2.Plugin 274 for id, p := range ps.plugins { // this can be optimized 275 if strings.HasPrefix(id, idOrName) { 276 if found != nil { 277 return "", errors.WithStack(errAmbiguous(idOrName)) 278 } 279 found = p 280 } 281 } 282 if found == nil { 283 return "", errors.WithStack(errNotFound(idOrName)) 284 } 285 return found.PluginObj.ID, nil 286 }