github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/testutil/fixtures/plugin/plugin.go (about)

     1  package plugin // import "github.com/docker/docker/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/docker/docker/api/types"
    14  	"github.com/docker/docker/pkg/archive"
    15  	"github.com/docker/docker/plugin"
    16  	"github.com/docker/docker/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  	cfg.PluginConfig = &types.PluginConfig{}
    95  	for _, o := range opts {
    96  		o(&cfg)
    97  	}
    98  
    99  	tar, err := makePluginBundle(inPath, opts...)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	defer tar.Close()
   104  
   105  	dummyExec := func(m *plugin.Manager) (plugin.Executor, error) {
   106  		return nil, nil
   107  	}
   108  
   109  	regService, err := registry.NewService(cfg.RegistryConfig)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	managerConfig := plugin.ManagerConfig{
   115  		Store:           plugin.NewStore(),
   116  		RegistryService: regService,
   117  		Root:            filepath.Join(tmpDir, "root"),
   118  		ExecRoot:        "/run/docker", // manager init fails if not set
   119  		CreateExecutor:  dummyExec,
   120  		LogPluginEvent:  func(id, name, action string) {}, // panics when not set
   121  	}
   122  	manager, err := plugin.NewManager(managerConfig)
   123  	if err != nil {
   124  		return errors.Wrap(err, "error creating plugin manager")
   125  	}
   126  
   127  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
   128  	defer cancel()
   129  	if err := manager.CreateFromContext(ctx, tar, &types.PluginCreateOptions{RepoName: repo}); err != nil {
   130  		return err
   131  	}
   132  
   133  	if auth == nil {
   134  		auth = &types.AuthConfig{}
   135  	}
   136  	err = manager.Push(ctx, repo, nil, auth, ioutil.Discard)
   137  	return errors.Wrap(err, "error pushing plugin")
   138  }
   139  
   140  func makePluginBundle(inPath string, opts ...CreateOpt) (io.ReadCloser, error) {
   141  	p := &types.PluginConfig{
   142  		Interface: types.PluginConfigInterface{
   143  			Socket: "basic.sock",
   144  			Types:  []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}},
   145  		},
   146  		Entrypoint: []string{"/basic"},
   147  	}
   148  	cfg := &Config{
   149  		PluginConfig: p,
   150  	}
   151  	for _, o := range opts {
   152  		o(cfg)
   153  	}
   154  	if cfg.binPath == "" {
   155  		binPath, err := ensureBasicPluginBin()
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  		cfg.binPath = binPath
   160  	}
   161  
   162  	configJSON, err := json.Marshal(p)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	if err := ioutil.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0644); err != nil {
   167  		return nil, err
   168  	}
   169  	if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(p.Entrypoint[0])), 0755); err != nil {
   170  		return nil, errors.Wrap(err, "error creating plugin rootfs dir")
   171  	}
   172  
   173  	// Ensure the mount target paths exist
   174  	for _, m := range p.Mounts {
   175  		var stat os.FileInfo
   176  		if m.Source != nil {
   177  			stat, err = os.Stat(*m.Source)
   178  			if err != nil && !os.IsNotExist(err) {
   179  				return nil, err
   180  			}
   181  		}
   182  
   183  		if stat == nil || stat.IsDir() {
   184  			var mode os.FileMode = 0755
   185  			if stat != nil {
   186  				mode = stat.Mode()
   187  			}
   188  			if err := os.MkdirAll(filepath.Join(inPath, "rootfs", m.Destination), mode); err != nil {
   189  				return nil, errors.Wrap(err, "error preparing plugin mount destination path")
   190  			}
   191  		} else {
   192  			if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(m.Destination)), 0755); err != nil {
   193  				return nil, errors.Wrap(err, "error preparing plugin mount destination dir")
   194  			}
   195  			f, err := os.Create(filepath.Join(inPath, "rootfs", m.Destination))
   196  			if err != nil && !os.IsExist(err) {
   197  				return nil, errors.Wrap(err, "error preparing plugin mount destination file")
   198  			}
   199  			if f != nil {
   200  				f.Close()
   201  			}
   202  		}
   203  	}
   204  	if err := archive.NewDefaultArchiver().CopyFileWithTar(cfg.binPath, filepath.Join(inPath, "rootfs", p.Entrypoint[0])); err != nil {
   205  		return nil, errors.Wrap(err, "error copying plugin binary to rootfs path")
   206  	}
   207  	tar, err := archive.Tar(inPath, archive.Uncompressed)
   208  	return tar, errors.Wrap(err, "error making plugin archive")
   209  }
   210  
   211  func ensureBasicPluginBin() (string, error) {
   212  	name := "docker-basic-plugin"
   213  	p, err := exec.LookPath(name)
   214  	if err == nil {
   215  		return p, nil
   216  	}
   217  
   218  	goBin, err := exec.LookPath("go")
   219  	if err != nil {
   220  		return "", err
   221  	}
   222  	installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name)
   223  	sourcePath := filepath.Join("github.com", "docker", "docker", "testutil", "fixtures", "plugin", "basic")
   224  	cmd := exec.Command(goBin, "build", "-o", installPath, sourcePath)
   225  	cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GO111MODULE=off")
   226  	if out, err := cmd.CombinedOutput(); err != nil {
   227  		return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out))
   228  	}
   229  	return installPath, nil
   230  }