github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/testutil/fixtures/plugin/plugin.go (about)

     1  package plugin // import "github.com/demonoid81/moby/testutil/fixtures/plugin"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"time"
    12  
    13  	"github.com/demonoid81/moby/api/types"
    14  	"github.com/demonoid81/moby/pkg/archive"
    15  	"github.com/demonoid81/moby/plugin"
    16  	"github.com/demonoid81/moby/registry"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // CreateOpt is passed used to change the default plugin config before
    21  // creating it
    22  type CreateOpt func(*Config)
    23  
    24  // Config wraps types.PluginConfig to provide some extra state for options
    25  // extra customizations on the plugin details, such as using a custom binary to
    26  // create the plugin with.
    27  type Config struct {
    28  	*types.PluginConfig
    29  	binPath        string
    30  	RegistryConfig registry.ServiceOptions
    31  }
    32  
    33  // WithInsecureRegistry specifies that the given registry can skip host-key checking as well as fall back to plain http
    34  func WithInsecureRegistry(url string) CreateOpt {
    35  	return func(cfg *Config) {
    36  		cfg.RegistryConfig.InsecureRegistries = append(cfg.RegistryConfig.InsecureRegistries, url)
    37  	}
    38  }
    39  
    40  // WithBinary is a CreateOpt to set an custom binary to create the plugin with.
    41  // This binary must be statically compiled.
    42  func WithBinary(bin string) CreateOpt {
    43  	return func(cfg *Config) {
    44  		cfg.binPath = bin
    45  	}
    46  }
    47  
    48  // CreateClient is the interface used for `BuildPlugin` to interact with the
    49  // daemon.
    50  type CreateClient interface {
    51  	PluginCreate(context.Context, io.Reader, types.PluginCreateOptions) error
    52  }
    53  
    54  // Create creates a new plugin with the specified name
    55  func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error {
    56  	tmpDir, err := ioutil.TempDir("", "create-test-plugin")
    57  	if err != nil {
    58  		return err
    59  	}
    60  	defer os.RemoveAll(tmpDir)
    61  
    62  	tar, err := makePluginBundle(tmpDir, opts...)
    63  	if err != nil {
    64  		return err
    65  	}
    66  	defer tar.Close()
    67  
    68  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
    69  	defer cancel()
    70  
    71  	return c.PluginCreate(ctx, tar, types.PluginCreateOptions{RepoName: name})
    72  }
    73  
    74  // CreateInRegistry makes a plugin (locally) and pushes it to a registry.
    75  // This does not use a dockerd instance to create or push the plugin.
    76  // If you just want to create a plugin in some daemon, use `Create`.
    77  //
    78  // This can be useful when testing plugins on swarm where you don't really want
    79  // the plugin to exist on any of the daemons (immediately) and there needs to be
    80  // some way to distribute the plugin.
    81  func CreateInRegistry(ctx context.Context, repo string, auth *types.AuthConfig, opts ...CreateOpt) error {
    82  	tmpDir, err := ioutil.TempDir("", "create-test-plugin-local")
    83  	if err != nil {
    84  		return err
    85  	}
    86  	defer os.RemoveAll(tmpDir)
    87  
    88  	inPath := filepath.Join(tmpDir, "plugin")
    89  	if err := os.MkdirAll(inPath, 0755); err != nil {
    90  		return errors.Wrap(err, "error creating plugin root")
    91  	}
    92  
    93  	var cfg Config
    94  	for _, o := range opts {
    95  		o(&cfg)
    96  	}
    97  
    98  	tar, err := makePluginBundle(inPath, opts...)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	defer tar.Close()
   103  
   104  	dummyExec := func(m *plugin.Manager) (plugin.Executor, error) {
   105  		return nil, nil
   106  	}
   107  
   108  	regService, err := registry.NewService(cfg.RegistryConfig)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	managerConfig := plugin.ManagerConfig{
   114  		Store:           plugin.NewStore(),
   115  		RegistryService: regService,
   116  		Root:            filepath.Join(tmpDir, "root"),
   117  		ExecRoot:        "/run/docker", // manager init fails if not set
   118  		CreateExecutor:  dummyExec,
   119  		LogPluginEvent:  func(id, name, action string) {}, // panics when not set
   120  	}
   121  	manager, err := plugin.NewManager(managerConfig)
   122  	if err != nil {
   123  		return errors.Wrap(err, "error creating plugin manager")
   124  	}
   125  
   126  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
   127  	defer cancel()
   128  	if err := manager.CreateFromContext(ctx, tar, &types.PluginCreateOptions{RepoName: repo}); err != nil {
   129  		return err
   130  	}
   131  
   132  	if auth == nil {
   133  		auth = &types.AuthConfig{}
   134  	}
   135  	err = manager.Push(ctx, repo, nil, auth, ioutil.Discard)
   136  	return errors.Wrap(err, "error pushing plugin")
   137  }
   138  
   139  func makePluginBundle(inPath string, opts ...CreateOpt) (io.ReadCloser, error) {
   140  	p := &types.PluginConfig{
   141  		Interface: types.PluginConfigInterface{
   142  			Socket: "basic.sock",
   143  			Types:  []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}},
   144  		},
   145  		Entrypoint: []string{"/basic"},
   146  	}
   147  	cfg := &Config{
   148  		PluginConfig: p,
   149  	}
   150  	for _, o := range opts {
   151  		o(cfg)
   152  	}
   153  	if cfg.binPath == "" {
   154  		binPath, err := ensureBasicPluginBin()
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  		cfg.binPath = binPath
   159  	}
   160  
   161  	configJSON, err := json.Marshal(p)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	if err := ioutil.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0644); err != nil {
   166  		return nil, err
   167  	}
   168  	if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(p.Entrypoint[0])), 0755); err != nil {
   169  		return nil, errors.Wrap(err, "error creating plugin rootfs dir")
   170  	}
   171  
   172  	// Ensure the mount target paths exist
   173  	for _, m := range p.Mounts {
   174  		var stat os.FileInfo
   175  		if m.Source != nil {
   176  			stat, err = os.Stat(*m.Source)
   177  			if err != nil && !os.IsNotExist(err) {
   178  				return nil, err
   179  			}
   180  		}
   181  
   182  		if stat == nil || stat.IsDir() {
   183  			var mode os.FileMode = 0755
   184  			if stat != nil {
   185  				mode = stat.Mode()
   186  			}
   187  			if err := os.MkdirAll(filepath.Join(inPath, "rootfs", m.Destination), mode); err != nil {
   188  				return nil, errors.Wrap(err, "error preparing plugin mount destination path")
   189  			}
   190  		} else {
   191  			if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(m.Destination)), 0755); err != nil {
   192  				return nil, errors.Wrap(err, "error preparing plugin mount destination dir")
   193  			}
   194  			f, err := os.Create(filepath.Join(inPath, "rootfs", m.Destination))
   195  			if err != nil && !os.IsExist(err) {
   196  				return nil, errors.Wrap(err, "error preparing plugin mount destination file")
   197  			}
   198  			if f != nil {
   199  				f.Close()
   200  			}
   201  		}
   202  	}
   203  	if err := archive.NewDefaultArchiver().CopyFileWithTar(cfg.binPath, filepath.Join(inPath, "rootfs", p.Entrypoint[0])); err != nil {
   204  		return nil, errors.Wrap(err, "error copying plugin binary to rootfs path")
   205  	}
   206  	tar, err := archive.Tar(inPath, archive.Uncompressed)
   207  	return tar, errors.Wrap(err, "error making plugin archive")
   208  }
   209  
   210  func ensureBasicPluginBin() (string, error) {
   211  	name := "docker-basic-plugin"
   212  	p, err := exec.LookPath(name)
   213  	if err == nil {
   214  		return p, nil
   215  	}
   216  
   217  	goBin, err := exec.LookPath("go")
   218  	if err != nil {
   219  		return "", err
   220  	}
   221  	installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name)
   222  	sourcePath := filepath.Join("github.com", "docker", "docker", "testutil", "fixtures", "plugin", "basic")
   223  	cmd := exec.Command(goBin, "build", "-o", installPath, sourcePath)
   224  	cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
   225  	if out, err := cmd.CombinedOutput(); err != nil {
   226  		return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out))
   227  	}
   228  	return installPath, nil
   229  }