github.com/portworx/docker@v1.12.1/plugin/manager.go (about) 1 // +build experimental 2 3 package plugin 4 5 import ( 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "strings" 13 "sync" 14 15 "github.com/Sirupsen/logrus" 16 "github.com/docker/docker/libcontainerd" 17 "github.com/docker/docker/pkg/ioutils" 18 "github.com/docker/docker/pkg/plugins" 19 "github.com/docker/docker/reference" 20 "github.com/docker/docker/registry" 21 "github.com/docker/docker/restartmanager" 22 "github.com/docker/engine-api/types" 23 ) 24 25 const defaultPluginRuntimeDestination = "/run/docker/plugins" 26 27 var manager *Manager 28 29 // ErrNotFound indicates that a plugin was not found locally. 30 type ErrNotFound string 31 32 func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) } 33 34 // ErrInadequateCapability indicates that a plugin was found but did not have the requested capability. 35 type ErrInadequateCapability struct { 36 name string 37 capability string 38 } 39 40 func (e ErrInadequateCapability) Error() string { 41 return fmt.Sprintf("plugin %q found, but not with %q capability", e.name, e.capability) 42 } 43 44 type plugin struct { 45 //sync.RWMutex TODO 46 PluginObj types.Plugin `json:"plugin"` 47 client *plugins.Client 48 restartManager restartmanager.RestartManager 49 runtimeSourcePath string 50 exitChan chan bool 51 } 52 53 func (p *plugin) Client() *plugins.Client { 54 return p.client 55 } 56 57 // IsLegacy returns true for legacy plugins and false otherwise. 58 func (p *plugin) IsLegacy() bool { 59 return false 60 } 61 62 func (p *plugin) Name() string { 63 name := p.PluginObj.Name 64 if len(p.PluginObj.Tag) > 0 { 65 // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these 66 name += ":" + p.PluginObj.Tag 67 } 68 return name 69 } 70 71 func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin { 72 p := &plugin{ 73 PluginObj: types.Plugin{ 74 Name: ref.Name(), 75 ID: id, 76 }, 77 runtimeSourcePath: filepath.Join(pm.runRoot, id), 78 } 79 if ref, ok := ref.(reference.NamedTagged); ok { 80 p.PluginObj.Tag = ref.Tag() 81 } 82 return p 83 } 84 85 func (pm *Manager) restorePlugin(p *plugin) error { 86 p.runtimeSourcePath = filepath.Join(pm.runRoot, p.PluginObj.ID) 87 if p.PluginObj.Active { 88 return pm.restore(p) 89 } 90 return nil 91 } 92 93 type pluginMap map[string]*plugin 94 type eventLogger func(id, name, action string) 95 96 // Manager controls the plugin subsystem. 97 type Manager struct { 98 sync.RWMutex 99 libRoot string 100 runRoot string 101 plugins pluginMap // TODO: figure out why save() doesn't json encode *plugin object 102 nameToID map[string]string 103 handlers map[string]func(string, *plugins.Client) 104 containerdClient libcontainerd.Client 105 registryService registry.Service 106 handleLegacy bool 107 liveRestore bool 108 shutdown bool 109 pluginEventLogger eventLogger 110 } 111 112 // GetManager returns the singleton plugin Manager 113 func GetManager() *Manager { 114 return manager 115 } 116 117 // Init (was NewManager) instantiates the singleton Manager. 118 // TODO: revert this to NewManager once we get rid of all the singletons. 119 func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) { 120 if manager != nil { 121 return nil 122 } 123 124 root = filepath.Join(root, "plugins") 125 manager = &Manager{ 126 libRoot: root, 127 runRoot: "/run/docker", 128 plugins: make(map[string]*plugin), 129 nameToID: make(map[string]string), 130 handlers: make(map[string]func(string, *plugins.Client)), 131 registryService: rs, 132 handleLegacy: true, 133 liveRestore: liveRestore, 134 pluginEventLogger: evL, 135 } 136 if err := os.MkdirAll(manager.runRoot, 0700); err != nil { 137 return err 138 } 139 manager.containerdClient, err = remote.Client(manager) 140 if err != nil { 141 return err 142 } 143 if err := manager.init(); err != nil { 144 return err 145 } 146 return nil 147 } 148 149 // Handle sets a callback for a given capability. The callback will be called for every plugin with a given capability. 150 // TODO: append instead of set? 151 func Handle(capability string, callback func(string, *plugins.Client)) { 152 pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability)) 153 manager.handlers[pluginType] = callback 154 if manager.handleLegacy { 155 plugins.Handle(capability, callback) 156 } 157 } 158 159 func (pm *Manager) get(name string) (*plugin, error) { 160 pm.RLock() 161 defer pm.RUnlock() 162 163 id, nameOk := pm.nameToID[name] 164 if !nameOk { 165 return nil, ErrNotFound(name) 166 } 167 168 p, idOk := pm.plugins[id] 169 if !idOk { 170 return nil, ErrNotFound(name) 171 } 172 173 return p, nil 174 } 175 176 // FindWithCapability returns a list of plugins matching the given capability. 177 func FindWithCapability(capability string) ([]Plugin, error) { 178 handleLegacy := true 179 result := make([]Plugin, 0, 1) 180 if manager != nil { 181 handleLegacy = manager.handleLegacy 182 manager.RLock() 183 defer manager.RUnlock() 184 pluginLoop: 185 for _, p := range manager.plugins { 186 for _, typ := range p.PluginObj.Manifest.Interface.Types { 187 if typ.Capability != capability || typ.Prefix != "docker" { 188 continue pluginLoop 189 } 190 } 191 result = append(result, p) 192 } 193 } 194 if handleLegacy { 195 pl, err := plugins.GetAll(capability) 196 if err != nil { 197 return nil, fmt.Errorf("legacy plugin: %v", err) 198 } 199 for _, p := range pl { 200 if _, ok := manager.nameToID[p.Name()]; !ok { 201 result = append(result, p) 202 } 203 } 204 } 205 return result, nil 206 } 207 208 // LookupWithCapability returns a plugin matching the given name and capability. 209 func LookupWithCapability(name, capability string) (Plugin, error) { 210 var ( 211 p *plugin 212 err error 213 ) 214 handleLegacy := true 215 if manager != nil { 216 fullName := name 217 if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate 218 if reference.IsNameOnly(named) { 219 named = reference.WithDefaultTag(named) 220 } 221 ref, ok := named.(reference.NamedTagged) 222 if !ok { 223 return nil, fmt.Errorf("invalid name: %s", named.String()) 224 } 225 fullName = ref.String() 226 } 227 p, err = manager.get(fullName) 228 if err != nil { 229 if _, ok := err.(ErrNotFound); !ok { 230 return nil, err 231 } 232 handleLegacy = manager.handleLegacy 233 } else { 234 handleLegacy = false 235 } 236 } 237 if handleLegacy { 238 p, err := plugins.Get(name, capability) 239 if err != nil { 240 return nil, fmt.Errorf("legacy plugin: %v", err) 241 } 242 return p, nil 243 } else if err != nil { 244 return nil, err 245 } 246 247 capability = strings.ToLower(capability) 248 for _, typ := range p.PluginObj.Manifest.Interface.Types { 249 if typ.Capability == capability && typ.Prefix == "docker" { 250 return p, nil 251 } 252 } 253 return nil, ErrInadequateCapability{name, capability} 254 } 255 256 // StateChanged updates plugin internals using from libcontainerd events. 257 func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error { 258 logrus.Debugf("plugin state changed %s %#v", id, e) 259 260 switch e.State { 261 case libcontainerd.StateExit: 262 pm.RLock() 263 p, idOk := pm.plugins[id] 264 pm.RUnlock() 265 if !idOk { 266 return ErrNotFound(id) 267 } 268 if pm.shutdown == true { 269 p.exitChan <- true 270 } 271 } 272 273 return nil 274 } 275 276 // AttachStreams attaches io streams to the plugin 277 func (pm *Manager) AttachStreams(id string, iop libcontainerd.IOPipe) error { 278 iop.Stdin.Close() 279 280 logger := logrus.New() 281 logger.Hooks.Add(logHook{id}) 282 // TODO: cache writer per id 283 w := logger.Writer() 284 go func() { 285 io.Copy(w, iop.Stdout) 286 }() 287 go func() { 288 // TODO: update logrus and use logger.WriterLevel 289 io.Copy(w, iop.Stderr) 290 }() 291 return nil 292 } 293 294 func (pm *Manager) init() error { 295 dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json")) 296 if err != nil { 297 if os.IsNotExist(err) { 298 return nil 299 } 300 return err 301 } 302 303 if err := json.NewDecoder(dt).Decode(&pm.plugins); err != nil { 304 return err 305 } 306 307 var group sync.WaitGroup 308 group.Add(len(pm.plugins)) 309 for _, p := range pm.plugins { 310 go func(p *plugin) { 311 defer group.Done() 312 if err := pm.restorePlugin(p); err != nil { 313 logrus.Errorf("Error restoring plugin '%s': %s", p.Name(), err) 314 return 315 } 316 317 pm.Lock() 318 pm.nameToID[p.Name()] = p.PluginObj.ID 319 requiresManualRestore := !pm.liveRestore && p.PluginObj.Active 320 pm.Unlock() 321 322 if requiresManualRestore { 323 // if liveRestore is not enabled, the plugin will be stopped now so we should enable it 324 if err := pm.enable(p, true); err != nil { 325 logrus.Errorf("Error enabling plugin '%s': %s", p.Name(), err) 326 } 327 } 328 }(p) 329 } 330 group.Wait() 331 return pm.save() 332 } 333 334 func (pm *Manager) initPlugin(p *plugin) error { 335 dt, err := os.Open(filepath.Join(pm.libRoot, p.PluginObj.ID, "manifest.json")) 336 if err != nil { 337 return err 338 } 339 err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest) 340 dt.Close() 341 if err != nil { 342 return err 343 } 344 345 p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts)) 346 for i, mount := range p.PluginObj.Manifest.Mounts { 347 p.PluginObj.Config.Mounts[i] = mount 348 } 349 p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env)) 350 for _, env := range p.PluginObj.Manifest.Env { 351 if env.Value != nil { 352 p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) 353 } 354 } 355 copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value) 356 357 f, err := os.Create(filepath.Join(pm.libRoot, p.PluginObj.ID, "plugin-config.json")) 358 if err != nil { 359 return err 360 } 361 err = json.NewEncoder(f).Encode(&p.PluginObj.Config) 362 f.Close() 363 return err 364 } 365 366 func (pm *Manager) remove(p *plugin) error { 367 if p.PluginObj.Active { 368 return fmt.Errorf("plugin %s is active", p.Name()) 369 } 370 pm.Lock() // fixme: lock single record 371 defer pm.Unlock() 372 delete(pm.plugins, p.PluginObj.ID) 373 delete(pm.nameToID, p.Name()) 374 pm.save() 375 return os.RemoveAll(filepath.Join(pm.libRoot, p.PluginObj.ID)) 376 } 377 378 func (pm *Manager) set(p *plugin, args []string) error { 379 m := make(map[string]string, len(args)) 380 for _, arg := range args { 381 i := strings.Index(arg, "=") 382 if i < 0 { 383 return fmt.Errorf("No equal sign '=' found in %s", arg) 384 } 385 m[arg[:i]] = arg[i+1:] 386 } 387 return errors.New("not implemented") 388 } 389 390 // fixme: not safe 391 func (pm *Manager) save() error { 392 filePath := filepath.Join(pm.libRoot, "plugins.json") 393 394 jsonData, err := json.Marshal(pm.plugins) 395 if err != nil { 396 logrus.Debugf("Error in json.Marshal: %v", err) 397 return err 398 } 399 ioutils.AtomicWriteFile(filePath, jsonData, 0600) 400 return nil 401 } 402 403 type logHook struct{ id string } 404 405 func (logHook) Levels() []logrus.Level { 406 return logrus.AllLevels 407 } 408 409 func (l logHook) Fire(entry *logrus.Entry) error { 410 entry.Data = logrus.Fields{"plugin": l.id} 411 return nil 412 } 413 414 func computePrivileges(m *types.PluginManifest) types.PluginPrivileges { 415 var privileges types.PluginPrivileges 416 if m.Network.Type != "null" && m.Network.Type != "bridge" { 417 privileges = append(privileges, types.PluginPrivilege{ 418 Name: "network", 419 Description: "", 420 Value: []string{m.Network.Type}, 421 }) 422 } 423 for _, mount := range m.Mounts { 424 if mount.Source != nil { 425 privileges = append(privileges, types.PluginPrivilege{ 426 Name: "mount", 427 Description: "", 428 Value: []string{*mount.Source}, 429 }) 430 } 431 } 432 for _, device := range m.Devices { 433 if device.Path != nil { 434 privileges = append(privileges, types.PluginPrivilege{ 435 Name: "device", 436 Description: "", 437 Value: []string{*device.Path}, 438 }) 439 } 440 } 441 if len(m.Capabilities) > 0 { 442 privileges = append(privileges, types.PluginPrivilege{ 443 Name: "capabilities", 444 Description: "", 445 Value: m.Capabilities, 446 }) 447 } 448 return privileges 449 }