github.com/OpenFlowLabs/moby@v17.12.1-ce-rc2+incompatible/integration-cli/fixtures/plugin/plugin.go (about)

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