فهرست منبع

Merge pull request #34085 from cpuguy83/tests_use_locally_built_plugin

Use local plugins, not from hub for tests
Vincent Demeester 8 سال پیش
والد
کامیت
f909119582

+ 52 - 0
integration-cli/check_test.go

@@ -5,21 +5,26 @@ import (
 	"net/http/httptest"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
 	"strings"
 	"sync"
 	"syscall"
 	"testing"
+	"time"
 
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli/config"
+	"github.com/docker/docker/integration-cli/checker"
 	"github.com/docker/docker/integration-cli/cli"
 	"github.com/docker/docker/integration-cli/cli/build/fakestorage"
 	"github.com/docker/docker/integration-cli/daemon"
 	"github.com/docker/docker/integration-cli/environment"
+	"github.com/docker/docker/integration-cli/fixtures/plugin"
 	"github.com/docker/docker/integration-cli/registry"
 	"github.com/docker/docker/pkg/reexec"
 	"github.com/go-check/check"
+	"golang.org/x/net/context"
 )
 
 const (
@@ -442,3 +447,50 @@ func (s *DockerTrustedSwarmSuite) TearDownTest(c *check.C) {
 func (s *DockerTrustedSwarmSuite) OnTimeout(c *check.C) {
 	s.swarmSuite.OnTimeout(c)
 }
+
+func init() {
+	check.Suite(&DockerPluginSuite{
+		ds: &DockerSuite{},
+	})
+}
+
+type DockerPluginSuite struct {
+	ds       *DockerSuite
+	registry *registry.V2
+}
+
+func (ps *DockerPluginSuite) registryHost() string {
+	return privateRegistryURL
+}
+
+func (ps *DockerPluginSuite) getPluginRepo() string {
+	return path.Join(ps.registryHost(), "plugin", "basic")
+}
+func (ps *DockerPluginSuite) getPluginRepoWithTag() string {
+	return ps.getPluginRepo() + ":" + "latest"
+}
+
+func (ps *DockerPluginSuite) SetUpSuite(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	ps.registry = setupRegistry(c, false, "", "")
+
+	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+	defer cancel()
+
+	err := plugin.CreateInRegistry(ctx, ps.getPluginRepo(), nil)
+	c.Assert(err, checker.IsNil, check.Commentf("failed to create plugin"))
+}
+
+func (ps *DockerPluginSuite) TearDownSuite(c *check.C) {
+	if ps.registry != nil {
+		ps.registry.Close()
+	}
+}
+
+func (ps *DockerPluginSuite) TearDownTest(c *check.C) {
+	ps.ds.TearDownTest(c)
+}
+
+func (ps *DockerPluginSuite) OnTimeout(c *check.C) {
+	ps.ds.OnTimeout(c)
+}

+ 91 - 56
integration-cli/docker_cli_plugins_test.go

@@ -5,14 +5,20 @@ import (
 	"io/ioutil"
 	"net/http"
 	"os"
+	"path"
 	"path/filepath"
 	"strings"
+	"time"
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/integration-cli/checker"
 	"github.com/docker/docker/integration-cli/cli"
 	"github.com/docker/docker/integration-cli/daemon"
+	"github.com/docker/docker/integration-cli/fixtures/plugin"
+	"github.com/docker/docker/integration-cli/request"
 	icmd "github.com/docker/docker/pkg/testutil/cmd"
 	"github.com/go-check/check"
+	"golang.org/x/net/context"
 )
 
 var (
@@ -24,31 +30,30 @@ var (
 	npNameWithTag     = npName + ":" + pTag
 )
 
-func (s *DockerSuite) TestPluginBasicOps(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
-	_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
+func (ps *DockerPluginSuite) TestPluginBasicOps(c *check.C) {
+	plugin := ps.getPluginRepoWithTag()
+	_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", plugin)
 	c.Assert(err, checker.IsNil)
 
 	out, _, err := dockerCmdWithError("plugin", "ls")
 	c.Assert(err, checker.IsNil)
-	c.Assert(out, checker.Contains, pName)
-	c.Assert(out, checker.Contains, pTag)
+	c.Assert(out, checker.Contains, plugin)
 	c.Assert(out, checker.Contains, "true")
 
-	id, _, err := dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pNameWithTag)
+	id, _, err := dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", plugin)
 	id = strings.TrimSpace(id)
 	c.Assert(err, checker.IsNil)
 
-	out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
+	out, _, err = dockerCmdWithError("plugin", "remove", plugin)
 	c.Assert(err, checker.NotNil)
 	c.Assert(out, checker.Contains, "is enabled")
 
-	_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)
+	_, _, err = dockerCmdWithError("plugin", "disable", plugin)
 	c.Assert(err, checker.IsNil)
 
-	out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
+	out, _, err = dockerCmdWithError("plugin", "remove", plugin)
 	c.Assert(err, checker.IsNil)
-	c.Assert(out, checker.Contains, pNameWithTag)
+	c.Assert(out, checker.Contains, plugin)
 
 	_, err = os.Stat(filepath.Join(testEnv.DockerBasePath(), "plugins", id))
 	if !os.IsNotExist(err) {
@@ -56,8 +61,9 @@ func (s *DockerSuite) TestPluginBasicOps(c *check.C) {
 	}
 }
 
-func (s *DockerSuite) TestPluginForceRemove(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
+func (ps *DockerPluginSuite) TestPluginForceRemove(c *check.C) {
+	pNameWithTag := ps.getPluginRepoWithTag()
+
 	out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
 	c.Assert(err, checker.IsNil)
 
@@ -71,6 +77,7 @@ func (s *DockerSuite) TestPluginForceRemove(c *check.C) {
 
 func (s *DockerSuite) TestPluginActive(c *check.C) {
 	testRequires(c, DaemonIsLinux, IsAmd64, Network)
+
 	_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
 	c.Assert(err, checker.IsNil)
 
@@ -118,8 +125,9 @@ func (s *DockerSuite) TestPluginActiveNetwork(c *check.C) {
 	c.Assert(out, checker.Contains, npNameWithTag)
 }
 
-func (s *DockerSuite) TestPluginInstallDisable(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
+func (ps *DockerPluginSuite) TestPluginInstallDisable(c *check.C) {
+	pName := ps.getPluginRepoWithTag()
+
 	out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", "--disable", pName)
 	c.Assert(err, checker.IsNil)
 	c.Assert(strings.TrimSpace(out), checker.Contains, pName)
@@ -150,22 +158,39 @@ func (s *DockerSuite) TestPluginInstallDisableVolumeLs(c *check.C) {
 	dockerCmd(c, "volume", "ls")
 }
 
-func (s *DockerSuite) TestPluginSet(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
-	out, _ := dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--disable", pName)
-	c.Assert(strings.TrimSpace(out), checker.Contains, pName)
+func (ps *DockerPluginSuite) TestPluginSet(c *check.C) {
+	// Create a new plugin with extra settings
+	client, err := request.NewClient()
+	c.Assert(err, checker.IsNil, check.Commentf("failed to create test client"))
 
-	env, _ := dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", pName)
+	name := "test"
+	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+	defer cancel()
+
+	initialValue := "0"
+	err = plugin.Create(ctx, client, name, func(cfg *plugin.Config) {
+		cfg.Env = []types.PluginEnv{{Name: "DEBUG", Value: &initialValue, Settable: []string{"value"}}}
+	})
+	c.Assert(err, checker.IsNil, check.Commentf("failed to create test plugin"))
+
+	env, _ := dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", name)
 	c.Assert(strings.TrimSpace(env), checker.Equals, "[DEBUG=0]")
 
-	dockerCmd(c, "plugin", "set", pName, "DEBUG=1")
+	dockerCmd(c, "plugin", "set", name, "DEBUG=1")
 
-	env, _ = dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", pName)
+	env, _ = dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", name)
 	c.Assert(strings.TrimSpace(env), checker.Equals, "[DEBUG=1]")
 }
 
-func (s *DockerSuite) TestPluginInstallArgs(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
+func (ps *DockerPluginSuite) TestPluginInstallArgs(c *check.C) {
+	pName := path.Join(ps.registryHost(), "plugin", "testplugininstallwithargs")
+	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+	defer cancel()
+
+	plugin.CreateInRegistry(ctx, pName, nil, func(cfg *plugin.Config) {
+		cfg.Env = []types.PluginEnv{{Name: "DEBUG", Settable: []string{"value"}}}
+	})
+
 	out, _ := dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--disable", pName, "DEBUG=1")
 	c.Assert(strings.TrimSpace(out), checker.Contains, pName)
 
@@ -173,8 +198,8 @@ func (s *DockerSuite) TestPluginInstallArgs(c *check.C) {
 	c.Assert(strings.TrimSpace(env), checker.Equals, "[DEBUG=1]")
 }
 
-func (s *DockerRegistrySuite) TestPluginInstallImage(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64)
+func (ps *DockerPluginSuite) TestPluginInstallImage(c *check.C) {
+	testRequires(c, IsAmd64)
 
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
 	// tag the image to upload it to the private registry
@@ -187,8 +212,9 @@ func (s *DockerRegistrySuite) TestPluginInstallImage(c *check.C) {
 	c.Assert(out, checker.Contains, `Encountered remote "application/vnd.docker.container.image.v1+json"(image) when fetching`)
 }
 
-func (s *DockerSuite) TestPluginEnableDisableNegative(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
+func (ps *DockerPluginSuite) TestPluginEnableDisableNegative(c *check.C) {
+	pName := ps.getPluginRepoWithTag()
+
 	out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pName)
 	c.Assert(err, checker.IsNil)
 	c.Assert(strings.TrimSpace(out), checker.Contains, pName)
@@ -208,9 +234,7 @@ func (s *DockerSuite) TestPluginEnableDisableNegative(c *check.C) {
 	c.Assert(err, checker.IsNil)
 }
 
-func (s *DockerSuite) TestPluginCreate(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
-
+func (ps *DockerPluginSuite) TestPluginCreate(c *check.C) {
 	name := "foo/bar-driver"
 	temp, err := ioutil.TempDir("", "foo")
 	c.Assert(err, checker.IsNil)
@@ -242,15 +266,15 @@ func (s *DockerSuite) TestPluginCreate(c *check.C) {
 	c.Assert(len(strings.Split(strings.TrimSpace(out), "\n")), checker.Equals, 2)
 }
 
-func (s *DockerSuite) TestPluginInspect(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
+func (ps *DockerPluginSuite) TestPluginInspect(c *check.C) {
+	pNameWithTag := ps.getPluginRepoWithTag()
+
 	_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
 	c.Assert(err, checker.IsNil)
 
 	out, _, err := dockerCmdWithError("plugin", "ls")
 	c.Assert(err, checker.IsNil)
-	c.Assert(out, checker.Contains, pName)
-	c.Assert(out, checker.Contains, pTag)
+	c.Assert(out, checker.Contains, pNameWithTag)
 	c.Assert(out, checker.Contains, "true")
 
 	// Find the ID first
@@ -275,7 +299,7 @@ func (s *DockerSuite) TestPluginInspect(c *check.C) {
 	c.Assert(strings.TrimSpace(out), checker.Equals, id)
 
 	// Name without tag form
-	out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pName)
+	out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", ps.getPluginRepo())
 	c.Assert(err, checker.IsNil)
 	c.Assert(strings.TrimSpace(out), checker.Equals, id)
 
@@ -347,21 +371,29 @@ func (s *DockerTrustSuite) TestPluginUntrustedInstall(c *check.C) {
 	})
 }
 
-func (s *DockerSuite) TestPluginIDPrefix(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
-	_, _, err := dockerCmdWithError("plugin", "install", "--disable", "--grant-all-permissions", pNameWithTag)
-	c.Assert(err, checker.IsNil)
+func (ps *DockerPluginSuite) TestPluginIDPrefix(c *check.C) {
+	name := "test"
+	client, err := request.NewClient()
+	c.Assert(err, checker.IsNil, check.Commentf("error creating test client"))
+
+	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+	initialValue := "0"
+	err = plugin.Create(ctx, client, name, func(cfg *plugin.Config) {
+		cfg.Env = []types.PluginEnv{{Name: "DEBUG", Value: &initialValue, Settable: []string{"value"}}}
+	})
+	cancel()
+
+	c.Assert(err, checker.IsNil, check.Commentf("failed to create test plugin"))
 
 	// Find ID first
-	id, _, err := dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pNameWithTag)
+	id, _, err := dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", name)
 	id = strings.TrimSpace(id)
 	c.Assert(err, checker.IsNil)
 
 	// List current state
 	out, _, err := dockerCmdWithError("plugin", "ls")
 	c.Assert(err, checker.IsNil)
-	c.Assert(out, checker.Contains, pName)
-	c.Assert(out, checker.Contains, pTag)
+	c.Assert(out, checker.Contains, name)
 	c.Assert(out, checker.Contains, "false")
 
 	env, _ := dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", id[:5])
@@ -377,8 +409,7 @@ func (s *DockerSuite) TestPluginIDPrefix(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	out, _, err = dockerCmdWithError("plugin", "ls")
 	c.Assert(err, checker.IsNil)
-	c.Assert(out, checker.Contains, pName)
-	c.Assert(out, checker.Contains, pTag)
+	c.Assert(out, checker.Contains, name)
 	c.Assert(out, checker.Contains, "true")
 
 	// Disable
@@ -386,8 +417,7 @@ func (s *DockerSuite) TestPluginIDPrefix(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	out, _, err = dockerCmdWithError("plugin", "ls")
 	c.Assert(err, checker.IsNil)
-	c.Assert(out, checker.Contains, pName)
-	c.Assert(out, checker.Contains, pTag)
+	c.Assert(out, checker.Contains, name)
 	c.Assert(out, checker.Contains, "false")
 
 	// Remove
@@ -396,13 +426,10 @@ func (s *DockerSuite) TestPluginIDPrefix(c *check.C) {
 	// List returns none
 	out, _, err = dockerCmdWithError("plugin", "ls")
 	c.Assert(err, checker.IsNil)
-	c.Assert(out, checker.Not(checker.Contains), pName)
-	c.Assert(out, checker.Not(checker.Contains), pTag)
+	c.Assert(out, checker.Not(checker.Contains), name)
 }
 
-func (s *DockerSuite) TestPluginListDefaultFormat(c *check.C) {
-	testRequires(c, DaemonIsLinux, Network, IsAmd64)
-
+func (ps *DockerPluginSuite) TestPluginListDefaultFormat(c *check.C) {
 	config, err := ioutil.TempDir("", "config-file-")
 	c.Assert(err, check.IsNil)
 	defer os.RemoveAll(config)
@@ -410,17 +437,25 @@ func (s *DockerSuite) TestPluginListDefaultFormat(c *check.C) {
 	err = ioutil.WriteFile(filepath.Join(config, "config.json"), []byte(`{"pluginsFormat": "raw"}`), 0644)
 	c.Assert(err, check.IsNil)
 
-	out, _ := dockerCmd(c, "plugin", "install", "--grant-all-permissions", pName)
-	c.Assert(strings.TrimSpace(out), checker.Contains, pName)
+	name := "test:latest"
+	client, err := request.NewClient()
+	c.Assert(err, checker.IsNil, check.Commentf("error creating test client"))
+
+	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+	defer cancel()
+	err = plugin.Create(ctx, client, name, func(cfg *plugin.Config) {
+		cfg.Description = "test plugin"
+	})
+	c.Assert(err, checker.IsNil, check.Commentf("failed to create test plugin"))
 
-	out, _ = dockerCmd(c, "plugin", "inspect", "--format", "{{.ID}}", pNameWithTag)
+	out, _ := dockerCmd(c, "plugin", "inspect", "--format", "{{.ID}}", name)
 	id := strings.TrimSpace(out)
 
 	// We expect the format to be in `raw + --no-trunc`
 	expectedOutput := fmt.Sprintf(`plugin_id: %s
 name: %s
-description: A sample volume plugin for Docker
-enabled: true`, id, pNameWithTag)
+description: test plugin
+enabled: false`, id, name)
 
 	out, _ = dockerCmd(c, "--config", config, "plugin", "ls", "--no-trunc")
 	c.Assert(strings.TrimSpace(out), checker.Contains, expectedOutput)

+ 0 - 149
integration-cli/fixtures/plugin/plugin.go

@@ -1,20 +1,9 @@
 package plugin
 
 import (
-	"encoding/json"
 	"io"
-	"io/ioutil"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"time"
 
 	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/libcontainerd"
-	"github.com/docker/docker/pkg/archive"
-	"github.com/docker/docker/plugin"
-	"github.com/docker/docker/registry"
-	"github.com/pkg/errors"
 	"golang.org/x/net/context"
 )
 
@@ -43,141 +32,3 @@ func WithBinary(bin string) CreateOpt {
 type CreateClient interface {
 	PluginCreate(context.Context, io.Reader, types.PluginCreateOptions) error
 }
-
-// Create creates a new plugin with the specified name
-func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error {
-	tmpDir, err := ioutil.TempDir("", "create-test-plugin")
-	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})
-}
-
-// TODO(@cpuguy83): we really shouldn't have to do this...
-// The manager panics on init when `Executor` is not set.
-type dummyExecutor struct{}
-
-func (dummyExecutor) Client(libcontainerd.Backend) (libcontainerd.Client, error) { return nil, nil }
-func (dummyExecutor) Cleanup()                                                   {}
-func (dummyExecutor) UpdateOptions(...libcontainerd.RemoteOption) error          { return nil }
-
-// 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.
-func CreateInRegistry(ctx context.Context, repo string, auth *types.AuthConfig, opts ...CreateOpt) error {
-	tmpDir, err := ioutil.TempDir("", "create-test-plugin-local")
-	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")
-	}
-
-	tar, err := makePluginBundle(inPath, opts...)
-	if err != nil {
-		return err
-	}
-	defer tar.Close()
-
-	managerConfig := plugin.ManagerConfig{
-		Store:           plugin.NewStore(),
-		RegistryService: registry.NewService(registry.ServiceOptions{V2Only: true}),
-		Root:            filepath.Join(tmpDir, "root"),
-		ExecRoot:        "/run/docker", // manager init fails if not set
-		Executor:        dummyExecutor{},
-		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 {
-		auth = &types.AuthConfig{}
-	}
-	err = manager.Push(ctx, repo, nil, auth, ioutil.Discard)
-	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
-	}
-	if err := ioutil.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0644); err != nil {
-		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")
-	}
-	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)
-	cmd := exec.Command(goBin, "build", "-o", installPath, "./"+filepath.Join("fixtures", "plugin", "basic"))
-	cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
-	if out, err := cmd.CombinedOutput(); err != nil {
-		return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out))
-	}
-	return installPath, nil
-}

+ 157 - 0
integration-cli/fixtures/plugin/plugin_linux.go

@@ -0,0 +1,157 @@
+package plugin
+
+import (
+	"encoding/json"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"time"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/libcontainerd"
+	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/plugin"
+	"github.com/docker/docker/registry"
+	"github.com/pkg/errors"
+	"golang.org/x/net/context"
+)
+
+// Create creates a new plugin with the specified name
+func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error {
+	tmpDir, err := ioutil.TempDir("", "create-test-plugin")
+	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})
+}
+
+// TODO(@cpuguy83): we really shouldn't have to do this...
+// The manager panics on init when `Executor` is not set.
+type dummyExecutor struct{}
+
+func (dummyExecutor) Client(libcontainerd.Backend) (libcontainerd.Client, error) { return nil, nil }
+func (dummyExecutor) Cleanup()                                                   {}
+func (dummyExecutor) UpdateOptions(...libcontainerd.RemoteOption) error          { return nil }
+
+// 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.
+func CreateInRegistry(ctx context.Context, repo string, auth *types.AuthConfig, opts ...CreateOpt) error {
+	tmpDir, err := ioutil.TempDir("", "create-test-plugin-local")
+	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")
+	}
+
+	tar, err := makePluginBundle(inPath, opts...)
+	if err != nil {
+		return err
+	}
+	defer tar.Close()
+
+	managerConfig := plugin.ManagerConfig{
+		Store:           plugin.NewStore(),
+		RegistryService: registry.NewService(registry.ServiceOptions{V2Only: true}),
+		Root:            filepath.Join(tmpDir, "root"),
+		ExecRoot:        "/run/docker", // manager init fails if not set
+		Executor:        dummyExecutor{},
+		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 {
+		auth = &types.AuthConfig{}
+	}
+	err = manager.Push(ctx, repo, nil, auth, ioutil.Discard)
+	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
+	}
+	if err := ioutil.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0644); err != nil {
+		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")
+	}
+	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)
+	cmd := exec.Command(goBin, "build", "-o", installPath, "./"+filepath.Join("fixtures", "plugin", "basic"))
+	cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
+	if out, err := cmd.CombinedOutput(); err != nil {
+		return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out))
+	}
+	return installPath, nil
+}

+ 19 - 0
integration-cli/fixtures/plugin/plugin_unsuported.go

@@ -0,0 +1,19 @@
+// +build !linux
+
+package plugin
+
+import (
+	"github.com/docker/docker/api/types"
+	"github.com/pkg/errors"
+	"golang.org/x/net/context"
+)
+
+// Create is not supported on this platform
+func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error {
+	return errors.New("not supported on this platform")
+}
+
+// CreateInRegistry is not supported on this platform
+func CreateInRegistry(ctx context.Context, repo string, auth *types.AuthConfig, opts ...CreateOpt) error {
+	return errors.New("not supported on this platform")
+}

+ 19 - 2
integration-cli/trust_server_test.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"fmt"
 	"io/ioutil"
 	"net"
@@ -11,9 +12,12 @@ import (
 	"strings"
 	"time"
 
+	"github.com/docker/docker/api/types"
 	cliconfig "github.com/docker/docker/cli/config"
 	"github.com/docker/docker/integration-cli/checker"
 	"github.com/docker/docker/integration-cli/cli"
+	"github.com/docker/docker/integration-cli/fixtures/plugin"
+	"github.com/docker/docker/integration-cli/request"
 	icmd "github.com/docker/docker/pkg/testutil/cmd"
 	"github.com/docker/go-connections/tlsconfig"
 	"github.com/go-check/check"
@@ -225,10 +229,23 @@ func (s *DockerTrustSuite) setupTrustedImage(c *check.C, name string) string {
 
 func (s *DockerTrustSuite) setupTrustedplugin(c *check.C, source, name string) string {
 	repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
+
+	client, err := request.NewClient()
+	c.Assert(err, checker.IsNil, check.Commentf("could not create test client"))
+
+	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+	err = plugin.Create(ctx, client, repoName)
+	cancel()
+	c.Assert(err, checker.IsNil, check.Commentf("could not create test plugin"))
+
 	// tag the image and upload it to the private registry
-	cli.DockerCmd(c, "plugin", "install", "--grant-all-permissions", "--alias", repoName, source)
+	// TODO: shouldn't need to use the CLI to do trust
 	cli.Docker(cli.Args("plugin", "push", repoName), trustedCmd).Assert(c, SuccessSigningAndPushing)
-	cli.DockerCmd(c, "plugin", "rm", "-f", repoName)
+
+	ctx, cancel = context.WithTimeout(context.Background(), 60*time.Second)
+	err = client.PluginRemove(ctx, repoName, types.PluginRemoveOptions{Force: true})
+	cancel()
+	c.Assert(err, checker.IsNil, check.Commentf("failed to cleanup test plugin for trust suite"))
 	return repoName
 }