diff --git a/components/engine/integration-cli/check_test.go b/components/engine/integration-cli/check_test.go index cc3b80c94f..f05b6504e9 100644 --- a/components/engine/integration-cli/check_test.go +++ b/components/engine/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) +} diff --git a/components/engine/integration-cli/docker_cli_plugins_test.go b/components/engine/integration-cli/docker_cli_plugins_test.go index e1fcaf2c3e..38b4af8f1e 100644 --- a/components/engine/integration-cli/docker_cli_plugins_test.go +++ b/components/engine/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")) - out, _ = dockerCmd(c, "plugin", "inspect", "--format", "{{.ID}}", pNameWithTag) + 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}}", 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) diff --git a/components/engine/integration-cli/fixtures/plugin/plugin.go b/components/engine/integration-cli/fixtures/plugin/plugin.go index 1be6169735..c8259be1a7 100644 --- a/components/engine/integration-cli/fixtures/plugin/plugin.go +++ b/components/engine/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 -} diff --git a/components/engine/integration-cli/fixtures/plugin/plugin_linux.go b/components/engine/integration-cli/fixtures/plugin/plugin_linux.go new file mode 100644 index 0000000000..757694cd37 --- /dev/null +++ b/components/engine/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 +} diff --git a/components/engine/integration-cli/fixtures/plugin/plugin_unsuported.go b/components/engine/integration-cli/fixtures/plugin/plugin_unsuported.go new file mode 100644 index 0000000000..7c272a317f --- /dev/null +++ b/components/engine/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") +} diff --git a/components/engine/integration-cli/trust_server_test.go b/components/engine/integration-cli/trust_server_test.go index e3f0674cf3..9a999323f8 100644 --- a/components/engine/integration-cli/trust_server_test.go +++ b/components/engine/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 }