plugin.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. package v2
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "sync"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/oci"
  11. "github.com/docker/docker/pkg/plugins"
  12. "github.com/docker/docker/pkg/system"
  13. specs "github.com/opencontainers/runtime-spec/specs-go"
  14. )
  15. // Plugin represents an individual plugin.
  16. type Plugin struct {
  17. sync.RWMutex
  18. PluginObj types.Plugin `json:"plugin"`
  19. PClient *plugins.Client `json:"-"`
  20. RuntimeSourcePath string `json:"-"`
  21. RefCount int `json:"-"`
  22. Restart bool `json:"-"`
  23. ExitChan chan bool `json:"-"`
  24. LibRoot string `json:"-"`
  25. TimeoutInSecs int `json:"-"`
  26. }
  27. const defaultPluginRuntimeDestination = "/run/docker/plugins"
  28. // ErrInadequateCapability indicates that the plugin did not have the requested capability.
  29. type ErrInadequateCapability struct {
  30. cap string
  31. }
  32. func (e ErrInadequateCapability) Error() string {
  33. return fmt.Sprintf("plugin does not provide %q capability", e.cap)
  34. }
  35. func newPluginObj(name, id, tag string) types.Plugin {
  36. return types.Plugin{Name: name, ID: id, Tag: tag}
  37. }
  38. // NewPlugin creates a plugin.
  39. func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin {
  40. return &Plugin{
  41. PluginObj: newPluginObj(name, id, tag),
  42. RuntimeSourcePath: filepath.Join(runRoot, id),
  43. LibRoot: libRoot,
  44. }
  45. }
  46. // Client returns the plugin client.
  47. func (p *Plugin) Client() *plugins.Client {
  48. return p.PClient
  49. }
  50. // IsV1 returns true for V1 plugins and false otherwise.
  51. func (p *Plugin) IsV1() bool {
  52. return false
  53. }
  54. // Name returns the plugin name.
  55. func (p *Plugin) Name() string {
  56. name := p.PluginObj.Name
  57. if len(p.PluginObj.Tag) > 0 {
  58. // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
  59. name += ":" + p.PluginObj.Tag
  60. }
  61. return name
  62. }
  63. // FilterByCap query the plugin for a given capability.
  64. func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
  65. capability = strings.ToLower(capability)
  66. for _, typ := range p.PluginObj.Config.Interface.Types {
  67. if typ.Capability == capability && typ.Prefix == "docker" {
  68. return p, nil
  69. }
  70. }
  71. return nil, ErrInadequateCapability{capability}
  72. }
  73. // RemoveFromDisk deletes the plugin's runtime files from disk.
  74. func (p *Plugin) RemoveFromDisk() error {
  75. return os.RemoveAll(p.RuntimeSourcePath)
  76. }
  77. // InitPlugin populates the plugin object from the plugin config file.
  78. func (p *Plugin) InitPlugin() error {
  79. dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "config.json"))
  80. if err != nil {
  81. return err
  82. }
  83. err = json.NewDecoder(dt).Decode(&p.PluginObj.Config)
  84. dt.Close()
  85. if err != nil {
  86. return err
  87. }
  88. p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts))
  89. for i, mount := range p.PluginObj.Config.Mounts {
  90. p.PluginObj.Settings.Mounts[i] = mount
  91. }
  92. p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
  93. p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices))
  94. copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
  95. for _, env := range p.PluginObj.Config.Env {
  96. if env.Value != nil {
  97. p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
  98. }
  99. }
  100. copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value)
  101. return p.writeSettings()
  102. }
  103. func (p *Plugin) writeSettings() error {
  104. f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-settings.json"))
  105. if err != nil {
  106. return err
  107. }
  108. err = json.NewEncoder(f).Encode(&p.PluginObj.Settings)
  109. f.Close()
  110. return err
  111. }
  112. // Set is used to pass arguments to the plugin.
  113. func (p *Plugin) Set(args []string) error {
  114. p.Lock()
  115. defer p.Unlock()
  116. if p.PluginObj.Enabled {
  117. return fmt.Errorf("cannot set on an active plugin, disable plugin before setting")
  118. }
  119. sets, err := newSettables(args)
  120. if err != nil {
  121. return err
  122. }
  123. // TODO(vieux): lots of code duplication here, needs to be refactored.
  124. next:
  125. for _, s := range sets {
  126. // range over all the envs in the config
  127. for _, env := range p.PluginObj.Config.Env {
  128. // found the env in the config
  129. if env.Name == s.name {
  130. // is it settable ?
  131. if ok, err := s.isSettable(allowedSettableFieldsEnv, env.Settable); err != nil {
  132. return err
  133. } else if !ok {
  134. return fmt.Errorf("%q is not settable", s.prettyName())
  135. }
  136. // is it, so lets update the settings in memory
  137. updateSettingsEnv(&p.PluginObj.Settings.Env, &s)
  138. continue next
  139. }
  140. }
  141. // range over all the mounts in the config
  142. for _, mount := range p.PluginObj.Config.Mounts {
  143. // found the mount in the config
  144. if mount.Name == s.name {
  145. // is it settable ?
  146. if ok, err := s.isSettable(allowedSettableFieldsMounts, mount.Settable); err != nil {
  147. return err
  148. } else if !ok {
  149. return fmt.Errorf("%q is not settable", s.prettyName())
  150. }
  151. // it is, so lets update the settings in memory
  152. *mount.Source = s.value
  153. continue next
  154. }
  155. }
  156. // range over all the devices in the config
  157. for _, device := range p.PluginObj.Config.Linux.Devices {
  158. // found the device in the config
  159. if device.Name == s.name {
  160. // is it settable ?
  161. if ok, err := s.isSettable(allowedSettableFieldsDevices, device.Settable); err != nil {
  162. return err
  163. } else if !ok {
  164. return fmt.Errorf("%q is not settable", s.prettyName())
  165. }
  166. // it is, so lets update the settings in memory
  167. *device.Path = s.value
  168. continue next
  169. }
  170. }
  171. // found the name in the config
  172. if p.PluginObj.Config.Args.Name == s.name {
  173. // is it settable ?
  174. if ok, err := s.isSettable(allowedSettableFieldsArgs, p.PluginObj.Config.Args.Settable); err != nil {
  175. return err
  176. } else if !ok {
  177. return fmt.Errorf("%q is not settable", s.prettyName())
  178. }
  179. // it is, so lets update the settings in memory
  180. p.PluginObj.Settings.Args = strings.Split(s.value, " ")
  181. continue next
  182. }
  183. return fmt.Errorf("setting %q not found in the plugin configuration", s.name)
  184. }
  185. // update the settings on disk
  186. return p.writeSettings()
  187. }
  188. // ComputePrivileges takes the config file and computes the list of access necessary
  189. // for the plugin on the host.
  190. func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
  191. c := p.PluginObj.Config
  192. var privileges types.PluginPrivileges
  193. if c.Network.Type != "null" && c.Network.Type != "bridge" {
  194. privileges = append(privileges, types.PluginPrivilege{
  195. Name: "network",
  196. Description: "permissions to access a network",
  197. Value: []string{c.Network.Type},
  198. })
  199. }
  200. for _, mount := range c.Mounts {
  201. if mount.Source != nil {
  202. privileges = append(privileges, types.PluginPrivilege{
  203. Name: "mount",
  204. Description: "host path to mount",
  205. Value: []string{*mount.Source},
  206. })
  207. }
  208. }
  209. for _, device := range c.Linux.Devices {
  210. if device.Path != nil {
  211. privileges = append(privileges, types.PluginPrivilege{
  212. Name: "device",
  213. Description: "host device to access",
  214. Value: []string{*device.Path},
  215. })
  216. }
  217. }
  218. if c.Linux.DeviceCreation {
  219. privileges = append(privileges, types.PluginPrivilege{
  220. Name: "device-creation",
  221. Description: "allow creating devices inside plugin",
  222. Value: []string{"true"},
  223. })
  224. }
  225. if len(c.Linux.Capabilities) > 0 {
  226. privileges = append(privileges, types.PluginPrivilege{
  227. Name: "capabilities",
  228. Description: "list of additional capabilities required",
  229. Value: c.Linux.Capabilities,
  230. })
  231. }
  232. return privileges
  233. }
  234. // IsEnabled returns the active state of the plugin.
  235. func (p *Plugin) IsEnabled() bool {
  236. p.RLock()
  237. defer p.RUnlock()
  238. return p.PluginObj.Enabled
  239. }
  240. // GetID returns the plugin's ID.
  241. func (p *Plugin) GetID() string {
  242. p.RLock()
  243. defer p.RUnlock()
  244. return p.PluginObj.ID
  245. }
  246. // GetSocket returns the plugin socket.
  247. func (p *Plugin) GetSocket() string {
  248. p.RLock()
  249. defer p.RUnlock()
  250. return p.PluginObj.Config.Interface.Socket
  251. }
  252. // GetTypes returns the interface types of a plugin.
  253. func (p *Plugin) GetTypes() []types.PluginInterfaceType {
  254. p.RLock()
  255. defer p.RUnlock()
  256. return p.PluginObj.Config.Interface.Types
  257. }
  258. // InitSpec creates an OCI spec from the plugin's config.
  259. func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
  260. rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
  261. s.Root = specs.Root{
  262. Path: rootfs,
  263. Readonly: false, // TODO: all plugins should be readonly? settable in config?
  264. }
  265. userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts))
  266. for _, m := range p.PluginObj.Config.Mounts {
  267. userMounts[m.Destination] = struct{}{}
  268. }
  269. mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
  270. Source: &p.RuntimeSourcePath,
  271. Destination: defaultPluginRuntimeDestination,
  272. Type: "bind",
  273. Options: []string{"rbind", "rshared"},
  274. })
  275. if p.PluginObj.Config.Network.Type != "" {
  276. // TODO: if net == bridge, use libnetwork controller to create a new plugin-specific bridge, bind mount /etc/hosts and /etc/resolv.conf look at the docker code (allocateNetwork, initialize)
  277. if p.PluginObj.Config.Network.Type == "host" {
  278. oci.RemoveNamespace(&s, specs.NamespaceType("network"))
  279. }
  280. etcHosts := "/etc/hosts"
  281. resolvConf := "/etc/resolv.conf"
  282. mounts = append(mounts,
  283. types.PluginMount{
  284. Source: &etcHosts,
  285. Destination: etcHosts,
  286. Type: "bind",
  287. Options: []string{"rbind", "ro"},
  288. },
  289. types.PluginMount{
  290. Source: &resolvConf,
  291. Destination: resolvConf,
  292. Type: "bind",
  293. Options: []string{"rbind", "ro"},
  294. })
  295. }
  296. for _, mount := range mounts {
  297. m := specs.Mount{
  298. Destination: mount.Destination,
  299. Type: mount.Type,
  300. Options: mount.Options,
  301. }
  302. // TODO: if nil, then it's required and user didn't set it
  303. if mount.Source != nil {
  304. m.Source = *mount.Source
  305. }
  306. if m.Source != "" && m.Type == "bind" {
  307. fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks
  308. if err != nil {
  309. return nil, err
  310. }
  311. if fi.IsDir() {
  312. if err := os.MkdirAll(m.Source, 0700); err != nil {
  313. return nil, err
  314. }
  315. }
  316. }
  317. s.Mounts = append(s.Mounts, m)
  318. }
  319. for i, m := range s.Mounts {
  320. if strings.HasPrefix(m.Destination, "/dev/") {
  321. if _, ok := userMounts[m.Destination]; ok {
  322. s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
  323. }
  324. }
  325. }
  326. if p.PluginObj.Config.Linux.DeviceCreation {
  327. rwm := "rwm"
  328. s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
  329. }
  330. for _, dev := range p.PluginObj.Config.Linux.Devices {
  331. path := *dev.Path
  332. d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
  333. if err != nil {
  334. return nil, err
  335. }
  336. s.Linux.Devices = append(s.Linux.Devices, d...)
  337. s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...)
  338. }
  339. envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
  340. envs[0] = "PATH=" + system.DefaultPathEnv
  341. envs = append(envs, p.PluginObj.Settings.Env...)
  342. args := append(p.PluginObj.Config.Entrypoint, p.PluginObj.Settings.Args...)
  343. cwd := p.PluginObj.Config.Workdir
  344. if len(cwd) == 0 {
  345. cwd = "/"
  346. }
  347. s.Process.Terminal = false
  348. s.Process.Args = args
  349. s.Process.Cwd = cwd
  350. s.Process.Env = envs
  351. s.Process.Capabilities = append(s.Process.Capabilities, p.PluginObj.Config.Linux.Capabilities...)
  352. return &s, nil
  353. }