plugin_linux.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package plugin
  2. import (
  3. "encoding/json"
  4. "io"
  5. "io/ioutil"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "time"
  10. "github.com/docker/docker/api/types"
  11. "github.com/docker/docker/libcontainerd"
  12. "github.com/docker/docker/pkg/archive"
  13. "github.com/docker/docker/plugin"
  14. "github.com/docker/docker/registry"
  15. "github.com/pkg/errors"
  16. "golang.org/x/net/context"
  17. )
  18. // Create creates a new plugin with the specified name
  19. func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error {
  20. tmpDir, err := ioutil.TempDir("", "create-test-plugin")
  21. if err != nil {
  22. return err
  23. }
  24. defer os.RemoveAll(tmpDir)
  25. tar, err := makePluginBundle(tmpDir, opts...)
  26. if err != nil {
  27. return err
  28. }
  29. defer tar.Close()
  30. ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
  31. defer cancel()
  32. return c.PluginCreate(ctx, tar, types.PluginCreateOptions{RepoName: name})
  33. }
  34. // TODO(@cpuguy83): we really shouldn't have to do this...
  35. // The manager panics on init when `Executor` is not set.
  36. type dummyExecutor struct{}
  37. func (dummyExecutor) Client(libcontainerd.Backend) (libcontainerd.Client, error) { return nil, nil }
  38. func (dummyExecutor) Cleanup() {}
  39. func (dummyExecutor) UpdateOptions(...libcontainerd.RemoteOption) error { return nil }
  40. // CreateInRegistry makes a plugin (locally) and pushes it to a registry.
  41. // This does not use a dockerd instance to create or push the plugin.
  42. // If you just want to create a plugin in some daemon, use `Create`.
  43. //
  44. // This can be useful when testing plugins on swarm where you don't really want
  45. // the plugin to exist on any of the daemons (immediately) and there needs to be
  46. // some way to distribute the plugin.
  47. func CreateInRegistry(ctx context.Context, repo string, auth *types.AuthConfig, opts ...CreateOpt) error {
  48. tmpDir, err := ioutil.TempDir("", "create-test-plugin-local")
  49. if err != nil {
  50. return err
  51. }
  52. defer os.RemoveAll(tmpDir)
  53. inPath := filepath.Join(tmpDir, "plugin")
  54. if err := os.MkdirAll(inPath, 0755); err != nil {
  55. return errors.Wrap(err, "error creating plugin root")
  56. }
  57. tar, err := makePluginBundle(inPath, opts...)
  58. if err != nil {
  59. return err
  60. }
  61. defer tar.Close()
  62. regService, err := registry.NewService(registry.ServiceOptions{V2Only: true})
  63. if err != nil {
  64. return err
  65. }
  66. managerConfig := plugin.ManagerConfig{
  67. Store: plugin.NewStore(),
  68. RegistryService: regService,
  69. Root: filepath.Join(tmpDir, "root"),
  70. ExecRoot: "/run/docker", // manager init fails if not set
  71. Executor: dummyExecutor{},
  72. LogPluginEvent: func(id, name, action string) {}, // panics when not set
  73. }
  74. manager, err := plugin.NewManager(managerConfig)
  75. if err != nil {
  76. return errors.Wrap(err, "error creating plugin manager")
  77. }
  78. ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
  79. defer cancel()
  80. if err := manager.CreateFromContext(ctx, tar, &types.PluginCreateOptions{RepoName: repo}); err != nil {
  81. return err
  82. }
  83. if auth == nil {
  84. auth = &types.AuthConfig{}
  85. }
  86. err = manager.Push(ctx, repo, nil, auth, ioutil.Discard)
  87. return errors.Wrap(err, "error pushing plugin")
  88. }
  89. func makePluginBundle(inPath string, opts ...CreateOpt) (io.ReadCloser, error) {
  90. p := &types.PluginConfig{
  91. Interface: types.PluginConfigInterface{
  92. Socket: "basic.sock",
  93. Types: []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}},
  94. },
  95. Entrypoint: []string{"/basic"},
  96. }
  97. cfg := &Config{
  98. PluginConfig: p,
  99. }
  100. for _, o := range opts {
  101. o(cfg)
  102. }
  103. if cfg.binPath == "" {
  104. binPath, err := ensureBasicPluginBin()
  105. if err != nil {
  106. return nil, err
  107. }
  108. cfg.binPath = binPath
  109. }
  110. configJSON, err := json.Marshal(p)
  111. if err != nil {
  112. return nil, err
  113. }
  114. if err := ioutil.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0644); err != nil {
  115. return nil, err
  116. }
  117. if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(p.Entrypoint[0])), 0755); err != nil {
  118. return nil, errors.Wrap(err, "error creating plugin rootfs dir")
  119. }
  120. if err := archive.NewDefaultArchiver().CopyFileWithTar(cfg.binPath, filepath.Join(inPath, "rootfs", p.Entrypoint[0])); err != nil {
  121. return nil, errors.Wrap(err, "error copying plugin binary to rootfs path")
  122. }
  123. tar, err := archive.Tar(inPath, archive.Uncompressed)
  124. return tar, errors.Wrap(err, "error making plugin archive")
  125. }
  126. func ensureBasicPluginBin() (string, error) {
  127. name := "docker-basic-plugin"
  128. p, err := exec.LookPath(name)
  129. if err == nil {
  130. return p, nil
  131. }
  132. goBin, err := exec.LookPath("go")
  133. if err != nil {
  134. return "", err
  135. }
  136. installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name)
  137. cmd := exec.Command(goBin, "build", "-o", installPath, "./"+filepath.Join("fixtures", "plugin", "basic"))
  138. cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
  139. if out, err := cmd.CombinedOutput(); err != nil {
  140. return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out))
  141. }
  142. return installPath, nil
  143. }