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