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