manager.go 11 KB

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