plugin.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package v2
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "sync"
  10. "github.com/docker/docker/api/types"
  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. }
  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, tag string) *Plugin {
  38. return &Plugin{
  39. PluginObj: newPluginObj(name, id, tag),
  40. RuntimeSourcePath: filepath.Join(runRoot, id),
  41. }
  42. }
  43. // Client returns the plugin client.
  44. func (p *Plugin) Client() *plugins.Client {
  45. return p.PClient
  46. }
  47. // IsV1 returns true for V1 plugins and false otherwise.
  48. func (p *Plugin) IsV1() bool {
  49. return false
  50. }
  51. // Name returns the plugin name.
  52. func (p *Plugin) Name() string {
  53. name := p.PluginObj.Name
  54. if len(p.PluginObj.Tag) > 0 {
  55. // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
  56. name += ":" + p.PluginObj.Tag
  57. }
  58. return name
  59. }
  60. // FilterByCap query the plugin for a given capability.
  61. func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
  62. capability = strings.ToLower(capability)
  63. for _, typ := range p.PluginObj.Manifest.Interface.Types {
  64. if typ.Capability == capability && typ.Prefix == "docker" {
  65. return p, nil
  66. }
  67. }
  68. return nil, ErrInadequateCapability{capability}
  69. }
  70. // RemoveFromDisk deletes the plugin's runtime files from disk.
  71. func (p *Plugin) RemoveFromDisk() error {
  72. return os.RemoveAll(p.RuntimeSourcePath)
  73. }
  74. // InitPlugin populates the plugin object from the plugin manifest file.
  75. func (p *Plugin) InitPlugin(libRoot string) error {
  76. dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json"))
  77. if err != nil {
  78. return err
  79. }
  80. err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest)
  81. dt.Close()
  82. if err != nil {
  83. return err
  84. }
  85. p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts))
  86. for i, mount := range p.PluginObj.Manifest.Mounts {
  87. p.PluginObj.Config.Mounts[i] = mount
  88. }
  89. p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env))
  90. for _, env := range p.PluginObj.Manifest.Env {
  91. if env.Value != nil {
  92. p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
  93. }
  94. }
  95. copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)
  96. f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json"))
  97. if err != nil {
  98. return err
  99. }
  100. err = json.NewEncoder(f).Encode(&p.PluginObj.Config)
  101. f.Close()
  102. return err
  103. }
  104. // Set is used to pass arguments to the plugin.
  105. func (p *Plugin) Set(args []string) error {
  106. m := make(map[string]string, len(args))
  107. for _, arg := range args {
  108. i := strings.Index(arg, "=")
  109. if i < 0 {
  110. return fmt.Errorf("No equal sign '=' found in %s", arg)
  111. }
  112. m[arg[:i]] = arg[i+1:]
  113. }
  114. return errors.New("not implemented")
  115. }
  116. // ComputePrivileges takes the manifest file and computes the list of access necessary
  117. // for the plugin on the host.
  118. func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
  119. m := p.PluginObj.Manifest
  120. var privileges types.PluginPrivileges
  121. if m.Network.Type != "null" && m.Network.Type != "bridge" {
  122. privileges = append(privileges, types.PluginPrivilege{
  123. Name: "network",
  124. Description: "",
  125. Value: []string{m.Network.Type},
  126. })
  127. }
  128. for _, mount := range m.Mounts {
  129. if mount.Source != nil {
  130. privileges = append(privileges, types.PluginPrivilege{
  131. Name: "mount",
  132. Description: "",
  133. Value: []string{*mount.Source},
  134. })
  135. }
  136. }
  137. for _, device := range m.Devices {
  138. if device.Path != nil {
  139. privileges = append(privileges, types.PluginPrivilege{
  140. Name: "device",
  141. Description: "",
  142. Value: []string{*device.Path},
  143. })
  144. }
  145. }
  146. if len(m.Capabilities) > 0 {
  147. privileges = append(privileges, types.PluginPrivilege{
  148. Name: "capabilities",
  149. Description: "",
  150. Value: m.Capabilities,
  151. })
  152. }
  153. return privileges
  154. }
  155. // IsEnabled returns the active state of the plugin.
  156. func (p *Plugin) IsEnabled() bool {
  157. p.RLock()
  158. defer p.RUnlock()
  159. return p.PluginObj.Enabled
  160. }
  161. // GetID returns the plugin's ID.
  162. func (p *Plugin) GetID() string {
  163. p.RLock()
  164. defer p.RUnlock()
  165. return p.PluginObj.ID
  166. }
  167. // GetSocket returns the plugin socket.
  168. func (p *Plugin) GetSocket() string {
  169. p.RLock()
  170. defer p.RUnlock()
  171. return p.PluginObj.Manifest.Interface.Socket
  172. }
  173. // GetTypes returns the interface types of a plugin.
  174. func (p *Plugin) GetTypes() []types.PluginInterfaceType {
  175. p.RLock()
  176. defer p.RUnlock()
  177. return p.PluginObj.Manifest.Interface.Types
  178. }
  179. // InitSpec creates an OCI spec from the plugin's config.
  180. func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
  181. rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
  182. s.Root = specs.Root{
  183. Path: rootfs,
  184. Readonly: false, // TODO: all plugins should be readonly? settable in manifest?
  185. }
  186. mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
  187. Source: &p.RuntimeSourcePath,
  188. Destination: defaultPluginRuntimeDestination,
  189. Type: "bind",
  190. Options: []string{"rbind", "rshared"},
  191. })
  192. for _, mount := range mounts {
  193. m := specs.Mount{
  194. Destination: mount.Destination,
  195. Type: mount.Type,
  196. Options: mount.Options,
  197. }
  198. // TODO: if nil, then it's required and user didn't set it
  199. if mount.Source != nil {
  200. m.Source = *mount.Source
  201. }
  202. if m.Source != "" && m.Type == "bind" {
  203. fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks
  204. if err != nil {
  205. return nil, err
  206. }
  207. if fi.IsDir() {
  208. if err := os.MkdirAll(m.Source, 0700); err != nil {
  209. return nil, err
  210. }
  211. }
  212. }
  213. s.Mounts = append(s.Mounts, m)
  214. }
  215. envs := make([]string, 1, len(p.PluginObj.Config.Env)+1)
  216. envs[0] = "PATH=" + system.DefaultPathEnv
  217. envs = append(envs, p.PluginObj.Config.Env...)
  218. args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...)
  219. cwd := p.PluginObj.Manifest.Workdir
  220. if len(cwd) == 0 {
  221. cwd = "/"
  222. }
  223. s.Process = specs.Process{
  224. Terminal: false,
  225. Args: args,
  226. Cwd: cwd,
  227. Env: envs,
  228. }
  229. return &s, nil
  230. }