2019-08-29 16:52:40 -04:00
|
|
|
package plugin // import "github.com/docker/docker/testutil/fixtures/plugin"
|
2017-06-07 13:07:01 -04:00
|
|
|
|
|
|
|
import (
|
2018-04-19 18:30:59 -04:00
|
|
|
"context"
|
2017-07-14 16:45:32 -04:00
|
|
|
"encoding/json"
|
2017-06-07 13:07:01 -04:00
|
|
|
"io"
|
2017-07-14 16:45:32 -04:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"time"
|
2017-06-07 13:07:01 -04:00
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
2022-03-03 04:33:08 -05:00
|
|
|
"github.com/docker/docker/api/types/registry"
|
2017-07-14 16:45:32 -04:00
|
|
|
"github.com/docker/docker/pkg/archive"
|
|
|
|
"github.com/docker/docker/plugin"
|
2022-03-03 04:33:08 -05:00
|
|
|
registrypkg "github.com/docker/docker/registry"
|
2017-07-14 16:45:32 -04:00
|
|
|
"github.com/pkg/errors"
|
2017-06-07 13:07:01 -04:00
|
|
|
)
|
|
|
|
|
2018-09-16 01:18:24 -04:00
|
|
|
// CreateOpt is passed used to change the default plugin config before
|
2017-06-07 13:07:01 -04:00
|
|
|
// 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
|
2020-02-10 19:31:04 -05:00
|
|
|
binPath string
|
2022-03-03 04:33:08 -05:00
|
|
|
RegistryConfig registrypkg.ServiceOptions
|
2020-02-10 19:31:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
2017-06-07 13:07:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2017-07-14 16:45:32 -04:00
|
|
|
|
|
|
|
// Create creates a new plugin with the specified name
|
|
|
|
func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error {
|
2021-08-24 06:10:50 -04:00
|
|
|
tmpDir, err := os.MkdirTemp("", "create-test-plugin")
|
2017-07-14 16:45:32 -04:00
|
|
|
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.
|
2022-03-03 04:33:08 -05:00
|
|
|
func CreateInRegistry(ctx context.Context, repo string, auth *registry.AuthConfig, opts ...CreateOpt) error {
|
2021-08-24 06:10:50 -04:00
|
|
|
tmpDir, err := os.MkdirTemp("", "create-test-plugin-local")
|
2017-07-14 16:45:32 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
|
|
|
|
inPath := filepath.Join(tmpDir, "plugin")
|
|
|
|
if err := os.MkdirAll(inPath, 0755); err != nil {
|
|
|
|
return errors.Wrap(err, "error creating plugin root")
|
|
|
|
}
|
|
|
|
|
2020-02-10 19:31:04 -05:00
|
|
|
var cfg Config
|
2020-10-07 18:27:59 -04:00
|
|
|
cfg.PluginConfig = &types.PluginConfig{}
|
2020-02-10 19:31:04 -05:00
|
|
|
for _, o := range opts {
|
|
|
|
o(&cfg)
|
|
|
|
}
|
|
|
|
|
2017-07-14 16:45:32 -04:00
|
|
|
tar, err := makePluginBundle(inPath, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tar.Close()
|
|
|
|
|
|
|
|
dummyExec := func(m *plugin.Manager) (plugin.Executor, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2022-03-03 04:33:08 -05:00
|
|
|
regService, err := registrypkg.NewService(cfg.RegistryConfig)
|
2017-07-14 16:45:32 -04:00
|
|
|
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, action string) {}, // 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 {
|
2022-03-03 04:33:08 -05:00
|
|
|
auth = ®istry.AuthConfig{}
|
2017-07-14 16:45:32 -04:00
|
|
|
}
|
2021-08-24 06:10:50 -04:00
|
|
|
err = manager.Push(ctx, repo, nil, auth, io.Discard)
|
2017-07-14 16:45:32 -04:00
|
|
|
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
|
|
|
|
}
|
2021-08-24 06:10:50 -04:00
|
|
|
if err := os.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0644); err != nil {
|
2017-07-14 16:45:32 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(p.Entrypoint[0])), 0755); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "error creating plugin rootfs dir")
|
|
|
|
}
|
2018-03-27 14:03:23 -04:00
|
|
|
|
|
|
|
// 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 = 0755
|
|
|
|
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)), 0755); 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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-14 16:45:32 -04:00
|
|
|
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)
|
2019-08-29 16:52:40 -04:00
|
|
|
sourcePath := filepath.Join("github.com", "docker", "docker", "testutil", "fixtures", "plugin", "basic")
|
2018-04-16 04:48:58 -04:00
|
|
|
cmd := exec.Command(goBin, "build", "-o", installPath, sourcePath)
|
2021-05-17 08:12:49 -04:00
|
|
|
cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GO111MODULE=off")
|
2017-07-14 16:45:32 -04:00
|
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
|
|
return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out))
|
|
|
|
}
|
|
|
|
return installPath, nil
|
|
|
|
}
|