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