moby/integration-cli/docker_cli_plugins_test.go
Sebastiaan van Stijn c207947508
integration-cli: DockerPluginSuite: use gotest.tools compare utilities
Some of these tests are failing (but not enabled in CI), but the current output
doesn't provide any details on the failure, so this patch is just to improve the
test output to allow debugging the actual failure.

Before this, tests would fail like:

    make BIND_DIR=. TEST_FILTER=TestPluginInstallImage test-integration
    ...
    === FAIL: amd64.integration-cli TestDockerPluginSuite/TestPluginInstallImage (15.22s)
        docker_cli_plugins_test.go:220: assertion failed: expression is false: strings.Contains(out, `Encountered remote "application/vnd.docker.container.image.v1+json"(image) when fetching`)
        --- FAIL: TestDockerPluginSuite/TestPluginInstallImage (15.22s)

With this patch, tests provide more useful output:

    make BIND_DIR=. TEST_FILTER=TestPluginInstallImage test-integration
    ...
    === FAIL: amd64.integration-cli TestDockerPluginSuite/TestPluginInstallImage (1.15s)
    time="2022-10-18T10:21:22Z" level=warning msg="reference for unknown type: application/vnd.docker.plugin.v1+json"
    time="2022-10-18T10:21:22Z" level=warning msg="reference for unknown type: application/vnd.docker.plugin.v1+json" digest="sha256:bee151d3fef5c1f787e7846efe4fa42b25a02db4e7543e54e8c679cf19d78598"
mediatype=application/vnd.docker.plugin.v1+json size=522
    time="2022-10-18T10:21:22Z" level=warning msg="reference for unknown type: application/vnd.docker.plugin.v1+json"
    time="2022-10-18T10:21:22Z" level=warning msg="reference for unknown type: application/vnd.docker.plugin.v1+json" digest="sha256:bee151d3fef5c1f787e7846efe4fa42b25a02db4e7543e54e8c679cf19d78598"
mediatype=application/vnd.docker.plugin.v1+json size=522
        docker_cli_plugins_test.go:221: assertion failed: string "Error response from daemon: application/vnd.docker.distribution.manifest.v1+prettyjws not supported\n" does not contain "Encountered remote
\"application/vnd.docker.container.image.v1+json\"(image) when fetching"
        --- FAIL: TestDockerPluginSuite/TestPluginInstallImage (1.15s)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-11-01 17:27:18 +01:00

479 lines
17 KiB
Go

package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/testutil/fixtures/plugin"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
var (
pluginProcessName = "sample-volume-plugin"
pName = "tiborvass/sample-volume-plugin"
npName = "tiborvass/test-docker-netplugin"
pTag = "latest"
pNameWithTag = pName + ":" + pTag
npNameWithTag = npName + ":" + pTag
)
type DockerCLIPluginsSuite struct {
ds *DockerSuite
}
func (s *DockerCLIPluginsSuite) TearDownTest(c *testing.T) {
s.ds.TearDownTest(c)
}
func (s *DockerCLIPluginsSuite) OnTimeout(c *testing.T) {
s.ds.OnTimeout(c)
}
func (ps *DockerPluginSuite) TestPluginBasicOps(c *testing.T) {
plugin := ps.getPluginRepoWithTag()
_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", plugin)
assert.NilError(c, err)
out, _, err := dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Check(c, is.Contains(out, plugin))
assert.Check(c, is.Contains(out, "true"))
id, _, err := dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", plugin)
id = strings.TrimSpace(id)
assert.NilError(c, err)
out, _, err = dockerCmdWithError("plugin", "remove", plugin)
assert.ErrorContains(c, err, "")
assert.Check(c, is.Contains(out, "is enabled"))
_, _, err = dockerCmdWithError("plugin", "disable", plugin)
assert.NilError(c, err)
out, _, err = dockerCmdWithError("plugin", "remove", plugin)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, plugin))
_, err = os.Stat(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "plugins", id))
if !os.IsNotExist(err) {
c.Fatal(err)
}
}
func (ps *DockerPluginSuite) TestPluginForceRemove(c *testing.T) {
pNameWithTag := ps.getPluginRepoWithTag()
_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
assert.NilError(c, err)
out, _, _ := dockerCmdWithError("plugin", "remove", pNameWithTag)
assert.Check(c, is.Contains(out, "is enabled"))
out, _, err = dockerCmdWithError("plugin", "remove", "--force", pNameWithTag)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pNameWithTag))
}
func (s *DockerCLIPluginsSuite) TestPluginActive(c *testing.T) {
testRequires(c, DaemonIsLinux, IsAmd64, Network)
_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
assert.NilError(c, err)
_, _, err = dockerCmdWithError("volume", "create", "-d", pNameWithTag, "--name", "testvol1")
assert.NilError(c, err)
out, _, _ := dockerCmdWithError("plugin", "disable", pNameWithTag)
assert.Check(c, is.Contains(out, "in use"))
_, _, err = dockerCmdWithError("volume", "rm", "testvol1")
assert.NilError(c, err)
_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)
assert.NilError(c, err)
out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pNameWithTag))
}
func (s *DockerCLIPluginsSuite) TestPluginActiveNetwork(c *testing.T) {
testRequires(c, DaemonIsLinux, IsAmd64, Network)
_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", npNameWithTag)
assert.NilError(c, err)
out, _, err := dockerCmdWithError("network", "create", "-d", npNameWithTag, "test")
assert.NilError(c, err)
nID := strings.TrimSpace(out)
out, _, _ = dockerCmdWithError("plugin", "remove", npNameWithTag)
assert.Check(c, is.Contains(out, "is in use"))
_, _, err = dockerCmdWithError("network", "rm", nID)
assert.NilError(c, err)
out, _, _ = dockerCmdWithError("plugin", "remove", npNameWithTag)
assert.Check(c, is.Contains(out, "is enabled"))
_, _, err = dockerCmdWithError("plugin", "disable", npNameWithTag)
assert.NilError(c, err)
out, _, err = dockerCmdWithError("plugin", "remove", npNameWithTag)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, npNameWithTag))
}
func (ps *DockerPluginSuite) TestPluginInstallDisable(c *testing.T) {
pName := ps.getPluginRepoWithTag()
out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", "--disable", pName)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pName))
out, _, err = dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Check(c, is.Contains(out, "false"))
out, _, err = dockerCmdWithError("plugin", "enable", pName)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pName))
out, _, err = dockerCmdWithError("plugin", "disable", pName)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pName))
out, _, err = dockerCmdWithError("plugin", "remove", pName)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pName))
}
func (s *DockerCLIPluginsSuite) TestPluginInstallDisableVolumeLs(c *testing.T) {
testRequires(c, DaemonIsLinux, IsAmd64, Network)
out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", "--disable", pName)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pName))
dockerCmd(c, "volume", "ls")
}
func (ps *DockerPluginSuite) TestPluginSet(c *testing.T) {
client := testEnv.APIClient()
name := "test"
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
initialValue := "0"
mntSrc := "foo"
devPath := "/dev/bar"
// Create a new plugin with extra settings
err := plugin.Create(ctx, client, name, func(cfg *plugin.Config) {
cfg.Env = []types.PluginEnv{{Name: "DEBUG", Value: &initialValue, Settable: []string{"value"}}}
cfg.Mounts = []types.PluginMount{
{Name: "pmount1", Settable: []string{"source"}, Type: "none", Source: &mntSrc},
{Name: "pmount2", Settable: []string{"source"}, Type: "none"}, // Mount without source is invalid.
}
cfg.Linux.Devices = []types.PluginDevice{
{Name: "pdev1", Path: &devPath, Settable: []string{"path"}},
{Name: "pdev2", Settable: []string{"path"}}, // Device without Path is invalid.
}
})
assert.Assert(c, err == nil, "failed to create test plugin")
env, _ := dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", name)
assert.Check(c, is.Equal(strings.TrimSpace(env), "[DEBUG=0]"))
dockerCmd(c, "plugin", "set", name, "DEBUG=1")
env, _ = dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", name)
assert.Check(c, is.Equal(strings.TrimSpace(env), "[DEBUG=1]"))
env, _ = dockerCmd(c, "plugin", "inspect", "-f", "{{with $mount := index .Settings.Mounts 0}}{{$mount.Source}}{{end}}", name)
assert.Check(c, is.Contains(env, mntSrc))
dockerCmd(c, "plugin", "set", name, "pmount1.source=bar")
env, _ = dockerCmd(c, "plugin", "inspect", "-f", "{{with $mount := index .Settings.Mounts 0}}{{$mount.Source}}{{end}}", name)
assert.Check(c, is.Contains(env, "bar"))
out, _, err := dockerCmdWithError("plugin", "set", name, "pmount2.source=bar2")
assert.ErrorContains(c, err, "")
assert.Check(c, is.Contains(out, "Plugin config has no mount source"))
out, _, err = dockerCmdWithError("plugin", "set", name, "pdev2.path=/dev/bar2")
assert.ErrorContains(c, err, "")
assert.Check(c, is.Contains(out, "Plugin config has no device path"))
}
func (ps *DockerPluginSuite) TestPluginInstallArgs(c *testing.T) {
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")
assert.Check(c, is.Contains(out, pName))
env, _ := dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", pName)
assert.Check(c, is.Equal(strings.TrimSpace(env), "[DEBUG=1]"))
}
func (ps *DockerPluginSuite) TestPluginInstallImage(c *testing.T) {
testRequires(c, IsAmd64)
skip.If(c, GitHubActions, "FIXME: https://github.com/moby/moby/issues/43996")
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
// tag the image to upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName)
// push the image to the registry
dockerCmd(c, "push", repoName)
out, _, err := dockerCmdWithError("plugin", "install", repoName)
assert.ErrorContains(c, err, "")
assert.Check(c, is.Contains(out, `Encountered remote "application/vnd.docker.container.image.v1+json"(image) when fetching`))
}
func (ps *DockerPluginSuite) TestPluginEnableDisableNegative(c *testing.T) {
pName := ps.getPluginRepoWithTag()
out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pName)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pName))
out, _, err = dockerCmdWithError("plugin", "enable", pName)
assert.ErrorContains(c, err, "")
assert.Check(c, is.Contains(out, "already enabled"))
_, _, err = dockerCmdWithError("plugin", "disable", pName)
assert.NilError(c, err)
out, _, err = dockerCmdWithError("plugin", "disable", pName)
assert.ErrorContains(c, err, "")
assert.Check(c, is.Contains(out, "already disabled"))
_, _, err = dockerCmdWithError("plugin", "remove", pName)
assert.NilError(c, err)
}
func (ps *DockerPluginSuite) TestPluginCreate(c *testing.T) {
name := "foo/bar-driver"
temp, err := os.MkdirTemp("", "foo")
assert.NilError(c, err)
defer os.RemoveAll(temp)
data := `{"description": "foo plugin"}`
err = os.WriteFile(filepath.Join(temp, "config.json"), []byte(data), 0644)
assert.NilError(c, err)
err = os.MkdirAll(filepath.Join(temp, "rootfs"), 0700)
assert.NilError(c, err)
out, _, err := dockerCmdWithError("plugin", "create", name, temp)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, name))
out, _, err = dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Check(c, is.Contains(out, name))
out, _, err = dockerCmdWithError("plugin", "create", name, temp)
assert.ErrorContains(c, err, "")
assert.Check(c, is.Contains(out, "already exist"))
out, _, err = dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Check(c, is.Contains(out, name))
// The output will consists of one HEADER line and one line of foo/bar-driver
assert.Equal(c, len(strings.Split(strings.TrimSpace(out), "\n")), 2)
}
func (ps *DockerPluginSuite) TestPluginInspect(c *testing.T) {
pNameWithTag := ps.getPluginRepoWithTag()
_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
assert.NilError(c, err)
out, _, err := dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pNameWithTag))
assert.Check(c, is.Contains(out, "true"))
// Find the ID first
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pNameWithTag)
assert.NilError(c, err)
id := strings.TrimSpace(out)
assert.Assert(c, id != "")
// Long form
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id)
assert.NilError(c, err)
assert.Check(c, is.Equal(strings.TrimSpace(out), id))
// Short form
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id[:5])
assert.NilError(c, err)
assert.Check(c, is.Equal(strings.TrimSpace(out), id))
// Name with tag form
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pNameWithTag)
assert.NilError(c, err)
assert.Check(c, is.Equal(strings.TrimSpace(out), id))
// Name without tag form
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", ps.getPluginRepo())
assert.NilError(c, err)
assert.Check(c, is.Equal(strings.TrimSpace(out), id))
_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)
assert.NilError(c, err)
out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
assert.NilError(c, err)
assert.Check(c, is.Contains(out, pNameWithTag))
// After remove nothing should be found
_, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id[:5])
assert.ErrorContains(c, err, "")
}
// Test case for https://github.com/docker/docker/pull/29186#discussion_r91277345
func (s *DockerCLIPluginsSuite) TestPluginInspectOnWindows(c *testing.T) {
// This test should work on Windows only
testRequires(c, DaemonIsWindows)
out, _, err := dockerCmdWithError("plugin", "inspect", "foobar")
assert.ErrorContains(c, err, "")
assert.Check(c, is.Contains(out, "plugins are not supported on this platform"))
assert.ErrorContains(c, err, "plugins are not supported on this platform")
}
func (ps *DockerPluginSuite) TestPluginIDPrefix(c *testing.T) {
name := "test"
client := testEnv.APIClient()
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()
assert.Assert(c, err == nil, "failed to create test plugin")
// Find ID first
id, _, err := dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", name)
id = strings.TrimSpace(id)
assert.NilError(c, err)
// List current state
out, _, err := dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Check(c, is.Contains(out, name))
assert.Check(c, is.Contains(out, "false"))
env, _ := dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", id[:5])
assert.Check(c, is.Equal(strings.TrimSpace(env), "[DEBUG=0]"))
dockerCmd(c, "plugin", "set", id[:5], "DEBUG=1")
env, _ = dockerCmd(c, "plugin", "inspect", "-f", "{{.Settings.Env}}", id[:5])
assert.Check(c, is.Equal(strings.TrimSpace(env), "[DEBUG=1]"))
// Enable
_, _, err = dockerCmdWithError("plugin", "enable", id[:5])
assert.NilError(c, err)
out, _, err = dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Check(c, is.Contains(out, name))
assert.Check(c, is.Contains(out, "true"))
// Disable
_, _, err = dockerCmdWithError("plugin", "disable", id[:5])
assert.NilError(c, err)
out, _, err = dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Check(c, is.Contains(out, name))
assert.Check(c, is.Contains(out, "false"))
// Remove
_, _, err = dockerCmdWithError("plugin", "remove", id[:5])
assert.NilError(c, err)
// List returns none
out, _, err = dockerCmdWithError("plugin", "ls")
assert.NilError(c, err)
assert.Assert(c, !strings.Contains(out, name))
}
func (ps *DockerPluginSuite) TestPluginListDefaultFormat(c *testing.T) {
config, err := os.MkdirTemp("", "config-file-")
assert.NilError(c, err)
defer os.RemoveAll(config)
err = os.WriteFile(filepath.Join(config, "config.json"), []byte(`{"pluginsFormat": "raw"}`), 0644)
assert.NilError(c, err)
name := "test:latest"
client := testEnv.APIClient()
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"
})
assert.Assert(c, err == nil, "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: test plugin
enabled: false`, id, name)
out, _ = dockerCmd(c, "--config", config, "plugin", "ls", "--no-trunc")
assert.Check(c, is.Contains(out, expectedOutput))
}
func (s *DockerCLIPluginsSuite) TestPluginUpgrade(c *testing.T) {
testRequires(c, DaemonIsLinux, Network, testEnv.IsLocalDaemon, IsAmd64, NotUserNamespace)
plugin := "cpuguy83/docker-volume-driver-plugin-local:latest"
pluginV2 := "cpuguy83/docker-volume-driver-plugin-local:v2"
dockerCmd(c, "plugin", "install", "--grant-all-permissions", plugin)
dockerCmd(c, "volume", "create", "--driver", plugin, "bananas")
dockerCmd(c, "run", "--rm", "-v", "bananas:/apple", "busybox", "sh", "-c", "touch /apple/core")
out, _, err := dockerCmdWithError("plugin", "upgrade", "--grant-all-permissions", plugin, pluginV2)
assert.ErrorContains(c, err, "", out)
assert.Check(c, is.Contains(out, "disabled before upgrading"))
out, _ = dockerCmd(c, "plugin", "inspect", "--format={{.ID}}", plugin)
id := strings.TrimSpace(out)
// make sure "v2" does not exists
_, err = os.Stat(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "plugins", id, "rootfs", "v2"))
assert.Assert(c, os.IsNotExist(err), out)
dockerCmd(c, "plugin", "disable", "-f", plugin)
dockerCmd(c, "plugin", "upgrade", "--grant-all-permissions", "--skip-remote-check", plugin, pluginV2)
// make sure "v2" file exists
_, err = os.Stat(filepath.Join(testEnv.DaemonInfo.DockerRootDir, "plugins", id, "rootfs", "v2"))
assert.NilError(c, err)
dockerCmd(c, "plugin", "enable", plugin)
dockerCmd(c, "volume", "inspect", "bananas")
dockerCmd(c, "run", "--rm", "-v", "bananas:/apple", "busybox", "sh", "-c", "ls -lh /apple/core")
}
func (s *DockerCLIPluginsSuite) TestPluginMetricsCollector(c *testing.T) {
testRequires(c, DaemonIsLinux, Network, testEnv.IsLocalDaemon, IsAmd64)
d := daemon.New(c, dockerBinary, dockerdBinary)
d.Start(c)
defer d.Stop(c)
name := "cpuguy83/docker-metrics-plugin-test:latest"
r := cli.Docker(cli.Args("plugin", "install", "--grant-all-permissions", name), cli.Daemon(d))
assert.Assert(c, r.Error == nil, r.Combined())
// plugin lisens on localhost:19393 and proxies the metrics
resp, err := http.Get("http://localhost:19393/metrics")
assert.NilError(c, err)
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
assert.NilError(c, err)
// check that a known metric is there... don't expect this metric to change over time.. probably safe
assert.Check(c, is.Contains(string(b), "container_actions"))
}