plugin.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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/pkg/plugins"
  11. "github.com/docker/docker/pkg/system"
  12. specs "github.com/opencontainers/runtime-spec/specs-go"
  13. )
  14. // Plugin represents an individual plugin.
  15. type Plugin struct {
  16. sync.RWMutex
  17. PluginObj types.Plugin `json:"plugin"`
  18. PClient *plugins.Client `json:"-"`
  19. RuntimeSourcePath string `json:"-"`
  20. RefCount int `json:"-"`
  21. Restart bool `json:"-"`
  22. ExitChan chan bool `json:"-"`
  23. LibRoot string `json:"-"`
  24. }
  25. const defaultPluginRuntimeDestination = "/run/docker/plugins"
  26. // ErrInadequateCapability indicates that the plugin did not have the requested capability.
  27. type ErrInadequateCapability struct {
  28. cap string
  29. }
  30. func (e ErrInadequateCapability) Error() string {
  31. return fmt.Sprintf("plugin does not provide %q capability", e.cap)
  32. }
  33. func newPluginObj(name, id, tag string) types.Plugin {
  34. return types.Plugin{Name: name, ID: id, Tag: tag}
  35. }
  36. // NewPlugin creates a plugin.
  37. func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin {
  38. return &Plugin{
  39. PluginObj: newPluginObj(name, id, tag),
  40. RuntimeSourcePath: filepath.Join(runRoot, id),
  41. LibRoot: libRoot,
  42. }
  43. }
  44. // Client returns the plugin client.
  45. func (p *Plugin) Client() *plugins.Client {
  46. return p.PClient
  47. }
  48. // IsV1 returns true for V1 plugins and false otherwise.
  49. func (p *Plugin) IsV1() bool {
  50. return false
  51. }
  52. // Name returns the plugin name.
  53. func (p *Plugin) Name() string {
  54. name := p.PluginObj.Name
  55. if len(p.PluginObj.Tag) > 0 {
  56. // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
  57. name += ":" + p.PluginObj.Tag
  58. }
  59. return name
  60. }
  61. // FilterByCap query the plugin for a given capability.
  62. func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
  63. capability = strings.ToLower(capability)
  64. for _, typ := range p.PluginObj.Config.Interface.Types {
  65. if typ.Capability == capability && typ.Prefix == "docker" {
  66. return p, nil
  67. }
  68. }
  69. return nil, ErrInadequateCapability{capability}
  70. }
  71. // RemoveFromDisk deletes the plugin's runtime files from disk.
  72. func (p *Plugin) RemoveFromDisk() error {
  73. return os.RemoveAll(p.RuntimeSourcePath)
  74. }
  75. // InitPlugin populates the plugin object from the plugin config file.
  76. func (p *Plugin) InitPlugin() error {
  77. dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "config.json"))
  78. if err != nil {
  79. return err
  80. }
  81. err = json.NewDecoder(dt).Decode(&p.PluginObj.Config)
  82. dt.Close()
  83. if err != nil {
  84. return err
  85. }
  86. p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts))
  87. for i, mount := range p.PluginObj.Config.Mounts {
  88. p.PluginObj.Settings.Mounts[i] = mount
  89. }
  90. p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
  91. for _, env := range p.PluginObj.Config.Env {
  92. if env.Value != nil {
  93. p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
  94. }
  95. }
  96. copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value)
  97. return p.writeSettings()
  98. }
  99. func (p *Plugin) writeSettings() error {
  100. f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-settings.json"))
  101. if err != nil {
  102. return err
  103. }
  104. err = json.NewEncoder(f).Encode(&p.PluginObj.Settings)
  105. f.Close()
  106. return err
  107. }
  108. // Set is used to pass arguments to the plugin.
  109. func (p *Plugin) Set(args []string) error {
  110. p.Lock()
  111. defer p.Unlock()
  112. if p.PluginObj.Enabled {
  113. return fmt.Errorf("cannot set on an active plugin, disable plugin before setting")
  114. }
  115. sets, err := newSettables(args)
  116. if err != nil {
  117. return err
  118. }
  119. next:
  120. for _, s := range sets {
  121. // range over all the envs in the config
  122. for _, env := range p.PluginObj.Config.Env {
  123. // found the env in the config
  124. if env.Name == s.name {
  125. // is it settable ?
  126. if ok, err := s.isSettable(allowedSettableFieldsEnv, env.Settable); err != nil {
  127. return err
  128. } else if !ok {
  129. return fmt.Errorf("%q is not settable", s.prettyName())
  130. }
  131. // is it, so lets update the settings in memory
  132. updateConfigEnv(&p.PluginObj.Settings.Env, &s)
  133. continue next
  134. }
  135. }
  136. //TODO: check devices, mount and args
  137. return fmt.Errorf("setting %q not found in the plugin configuration", s.name)
  138. }
  139. // update the settings on disk
  140. return p.writeSettings()
  141. }
  142. // ComputePrivileges takes the config file and computes the list of access necessary
  143. // for the plugin on the host.
  144. func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
  145. m := p.PluginObj.Config
  146. var privileges types.PluginPrivileges
  147. if m.Network.Type != "null" && m.Network.Type != "bridge" {
  148. privileges = append(privileges, types.PluginPrivilege{
  149. Name: "network",
  150. Description: "",
  151. Value: []string{m.Network.Type},
  152. })
  153. }
  154. for _, mount := range m.Mounts {
  155. if mount.Source != nil {
  156. privileges = append(privileges, types.PluginPrivilege{
  157. Name: "mount",
  158. Description: "",
  159. Value: []string{*mount.Source},
  160. })
  161. }
  162. }
  163. for _, device := range m.Devices {
  164. if device.Path != nil {
  165. privileges = append(privileges, types.PluginPrivilege{
  166. Name: "device",
  167. Description: "",
  168. Value: []string{*device.Path},
  169. })
  170. }
  171. }
  172. if len(m.Capabilities) > 0 {
  173. privileges = append(privileges, types.PluginPrivilege{
  174. Name: "capabilities",
  175. Description: "",
  176. Value: m.Capabilities,
  177. })
  178. }
  179. return privileges
  180. }
  181. // IsEnabled returns the active state of the plugin.
  182. func (p *Plugin) IsEnabled() bool {
  183. p.RLock()
  184. defer p.RUnlock()
  185. return p.PluginObj.Enabled
  186. }
  187. // GetID returns the plugin's ID.
  188. func (p *Plugin) GetID() string {
  189. p.RLock()
  190. defer p.RUnlock()
  191. return p.PluginObj.ID
  192. }
  193. // GetSocket returns the plugin socket.
  194. func (p *Plugin) GetSocket() string {
  195. p.RLock()
  196. defer p.RUnlock()
  197. return p.PluginObj.Config.Interface.Socket
  198. }
  199. // GetTypes returns the interface types of a plugin.
  200. func (p *Plugin) GetTypes() []types.PluginInterfaceType {
  201. p.RLock()
  202. defer p.RUnlock()
  203. return p.PluginObj.Config.Interface.Types
  204. }
  205. // InitSpec creates an OCI spec from the plugin's config.
  206. func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
  207. rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
  208. s.Root = specs.Root{
  209. Path: rootfs,
  210. Readonly: false, // TODO: all plugins should be readonly? settable in config?
  211. }
  212. mounts := append(p.PluginObj.Settings.Mounts, types.PluginMount{
  213. Source: &p.RuntimeSourcePath,
  214. Destination: defaultPluginRuntimeDestination,
  215. Type: "bind",
  216. Options: []string{"rbind", "rshared"},
  217. })
  218. for _, mount := range mounts {
  219. m := specs.Mount{
  220. Destination: mount.Destination,
  221. Type: mount.Type,
  222. Options: mount.Options,
  223. }
  224. // TODO: if nil, then it's required and user didn't set it
  225. if mount.Source != nil {
  226. m.Source = *mount.Source
  227. }
  228. if m.Source != "" && m.Type == "bind" {
  229. fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks
  230. if err != nil {
  231. return nil, err
  232. }
  233. if fi.IsDir() {
  234. if err := os.MkdirAll(m.Source, 0700); err != nil {
  235. return nil, err
  236. }
  237. }
  238. }
  239. s.Mounts = append(s.Mounts, m)
  240. }
  241. envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
  242. envs[0] = "PATH=" + system.DefaultPathEnv
  243. envs = append(envs, p.PluginObj.Settings.Env...)
  244. args := append(p.PluginObj.Config.Entrypoint, p.PluginObj.Settings.Args...)
  245. cwd := p.PluginObj.Config.Workdir
  246. if len(cwd) == 0 {
  247. cwd = "/"
  248. }
  249. s.Process = specs.Process{
  250. Terminal: false,
  251. Args: args,
  252. Cwd: cwd,
  253. Env: envs,
  254. }
  255. return &s, nil
  256. }