123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- package plugin // import "github.com/docker/docker/testutil/fixtures/plugin"
- import (
- "context"
- "encoding/json"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "time"
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/api/types/events"
- "github.com/docker/docker/api/types/registry"
- "github.com/docker/docker/pkg/archive"
- "github.com/docker/docker/plugin"
- registrypkg "github.com/docker/docker/registry"
- "github.com/pkg/errors"
- )
- // CreateOpt is passed used to change the default plugin config before
- // creating it
- type CreateOpt func(*Config)
- // Config wraps types.PluginConfig to provide some extra state for options
- // extra customizations on the plugin details, such as using a custom binary to
- // create the plugin with.
- type Config struct {
- *types.PluginConfig
- binPath string
- RegistryConfig registrypkg.ServiceOptions
- }
- // WithInsecureRegistry specifies that the given registry can skip host-key checking as well as fall back to plain http
- func WithInsecureRegistry(url string) CreateOpt {
- return func(cfg *Config) {
- cfg.RegistryConfig.InsecureRegistries = append(cfg.RegistryConfig.InsecureRegistries, url)
- }
- }
- // WithBinary is a CreateOpt to set an custom binary to create the plugin with.
- // This binary must be statically compiled.
- func WithBinary(bin string) CreateOpt {
- return func(cfg *Config) {
- cfg.binPath = bin
- }
- }
- // CreateClient is the interface used for `BuildPlugin` to interact with the
- // daemon.
- type CreateClient interface {
- PluginCreate(context.Context, io.Reader, types.PluginCreateOptions) error
- }
- // Create creates a new plugin with the specified name
- func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error {
- tmpDir, err := os.MkdirTemp("", "create-test-plugin")
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpDir)
- tar, err := makePluginBundle(tmpDir, opts...)
- if err != nil {
- return err
- }
- defer tar.Close()
- ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
- defer cancel()
- return c.PluginCreate(ctx, tar, types.PluginCreateOptions{RepoName: name})
- }
- // CreateInRegistry makes a plugin (locally) and pushes it to a registry.
- // This does not use a dockerd instance to create or push the plugin.
- // If you just want to create a plugin in some daemon, use `Create`.
- //
- // This can be useful when testing plugins on swarm where you don't really want
- // the plugin to exist on any of the daemons (immediately) and there needs to be
- // some way to distribute the plugin.
- func CreateInRegistry(ctx context.Context, repo string, auth *registry.AuthConfig, opts ...CreateOpt) error {
- tmpDir, err := os.MkdirTemp("", "create-test-plugin-local")
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpDir)
- inPath := filepath.Join(tmpDir, "plugin")
- if err := os.MkdirAll(inPath, 0o755); err != nil {
- return errors.Wrap(err, "error creating plugin root")
- }
- var cfg Config
- cfg.PluginConfig = &types.PluginConfig{}
- for _, o := range opts {
- o(&cfg)
- }
- tar, err := makePluginBundle(inPath, opts...)
- if err != nil {
- return err
- }
- defer tar.Close()
- dummyExec := func(m *plugin.Manager) (plugin.Executor, error) {
- return nil, nil
- }
- regService, err := registrypkg.NewService(cfg.RegistryConfig)
- if err != nil {
- return err
- }
- managerConfig := plugin.ManagerConfig{
- Store: plugin.NewStore(),
- RegistryService: regService,
- Root: filepath.Join(tmpDir, "root"),
- ExecRoot: "/run/docker", // manager init fails if not set
- CreateExecutor: dummyExec,
- LogPluginEvent: func(id, name string, action events.Action) {}, // panics when not set
- }
- manager, err := plugin.NewManager(managerConfig)
- if err != nil {
- return errors.Wrap(err, "error creating plugin manager")
- }
- ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
- defer cancel()
- if err := manager.CreateFromContext(ctx, tar, &types.PluginCreateOptions{RepoName: repo}); err != nil {
- return err
- }
- if auth == nil {
- auth = ®istry.AuthConfig{}
- }
- err = manager.Push(ctx, repo, nil, auth, io.Discard)
- return errors.Wrap(err, "error pushing plugin")
- }
- func makePluginBundle(inPath string, opts ...CreateOpt) (io.ReadCloser, error) {
- p := &types.PluginConfig{
- Interface: types.PluginConfigInterface{
- Socket: "basic.sock",
- Types: []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}},
- },
- Entrypoint: []string{"/basic"},
- }
- cfg := &Config{
- PluginConfig: p,
- }
- for _, o := range opts {
- o(cfg)
- }
- if cfg.binPath == "" {
- binPath, err := ensureBasicPluginBin()
- if err != nil {
- return nil, err
- }
- cfg.binPath = binPath
- }
- configJSON, err := json.Marshal(p)
- if err != nil {
- return nil, err
- }
- if err := os.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0o644); err != nil {
- return nil, err
- }
- if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(p.Entrypoint[0])), 0o755); err != nil {
- return nil, errors.Wrap(err, "error creating plugin rootfs dir")
- }
- // Ensure the mount target paths exist
- for _, m := range p.Mounts {
- var stat os.FileInfo
- if m.Source != nil {
- stat, err = os.Stat(*m.Source)
- if err != nil && !os.IsNotExist(err) {
- return nil, err
- }
- }
- if stat == nil || stat.IsDir() {
- var mode os.FileMode = 0o755
- if stat != nil {
- mode = stat.Mode()
- }
- if err := os.MkdirAll(filepath.Join(inPath, "rootfs", m.Destination), mode); err != nil {
- return nil, errors.Wrap(err, "error preparing plugin mount destination path")
- }
- } else {
- if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(m.Destination)), 0o755); err != nil {
- return nil, errors.Wrap(err, "error preparing plugin mount destination dir")
- }
- f, err := os.Create(filepath.Join(inPath, "rootfs", m.Destination))
- if err != nil && !os.IsExist(err) {
- return nil, errors.Wrap(err, "error preparing plugin mount destination file")
- }
- if f != nil {
- f.Close()
- }
- }
- }
- if err := archive.NewDefaultArchiver().CopyFileWithTar(cfg.binPath, filepath.Join(inPath, "rootfs", p.Entrypoint[0])); err != nil {
- return nil, errors.Wrap(err, "error copying plugin binary to rootfs path")
- }
- tar, err := archive.Tar(inPath, archive.Uncompressed)
- return tar, errors.Wrap(err, "error making plugin archive")
- }
- func ensureBasicPluginBin() (string, error) {
- name := "docker-basic-plugin"
- p, err := exec.LookPath(name)
- if err == nil {
- return p, nil
- }
- goBin, err := exec.LookPath("go")
- if err != nil {
- return "", err
- }
- installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name)
- sourcePath := filepath.Join("github.com", "docker", "docker", "testutil", "fixtures", "plugin", "basic")
- cmd := exec.Command(goBin, "build", "-o", installPath, sourcePath)
- cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GO111MODULE=off")
- if out, err := cmd.CombinedOutput(); err != nil {
- return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out))
- }
- return installPath, nil
- }
|