| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- /*
- Copyright 2020 Docker Compose CLI authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package main
- import (
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "testing"
- "time"
- "gotest.tools/v3/assert"
- "gotest.tools/v3/golden"
- "gotest.tools/v3/icmd"
- . "github.com/docker/compose-cli/tests/framework"
- )
- var binDir string
- func TestMain(m *testing.M) {
- p, cleanup, err := SetupExistingCLI()
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- binDir = p
- exitCode := m.Run()
- cleanup()
- os.Exit(exitCode)
- }
- func TestComposeNotImplemented(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- res := c.RunDockerCmd("context", "show")
- res.Assert(t, icmd.Expected{Out: "default"})
- res = c.RunDockerOrExitError("compose", "up")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: `Command "compose up" not available in current context (default)`,
- })
- res = c.RunDockerOrExitError("compose", "-f", "titi.yaml", "up")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: `Command "compose up" not available in current context (default)`,
- })
- }
- func TestContextDefault(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- t.Run("show", func(t *testing.T) {
- res := c.RunDockerCmd("context", "show")
- res.Assert(t, icmd.Expected{Out: "default"})
- })
- t.Run("ls", func(t *testing.T) {
- res := c.RunDockerCmd("context", "ls")
- golden.Assert(t, res.Stdout(), GoldenFile("ls-out-default"))
- res = c.RunDockerCmd("context", "ls", "--format", "pretty")
- golden.Assert(t, res.Stdout(), GoldenFile("ls-out-default"))
- res = c.RunDockerCmd("context", "ls", "--format", "json")
- golden.Assert(t, res.Stdout(), GoldenFile("ls-out-json"))
- res = c.RunDockerCmd("context", "ls", "--json")
- golden.Assert(t, res.Stdout(), GoldenFile("ls-out-json"))
- res = c.RunDockerCmd("context", "ls", "--format", "{{ json . }}")
- golden.Assert(t, res.Stdout(), GoldenFile("ls-out-json"))
- })
- t.Run("inspect", func(t *testing.T) {
- res := c.RunDockerCmd("context", "inspect", "default")
- res.Assert(t, icmd.Expected{Out: `"Name": "default"`})
- })
- t.Run("inspect current", func(t *testing.T) {
- res := c.RunDockerCmd("context", "inspect")
- res.Assert(t, icmd.Expected{Out: `"Name": "default"`})
- })
- }
- func TestContextCreateDocker(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- res := c.RunDockerCmd("context", "create", "test-docker", "--from", "default")
- res.Assert(t, icmd.Expected{Out: "test-docker"})
- t.Run("ls", func(t *testing.T) {
- res := c.RunDockerCmd("context", "ls")
- golden.Assert(t, res.Stdout(), GoldenFile("ls-out-test-docker"))
- })
- t.Run("ls quiet", func(t *testing.T) {
- res := c.RunDockerCmd("context", "ls", "-q")
- golden.Assert(t, res.Stdout(), "ls-out-test-docker-quiet.golden")
- })
- t.Run("ls format", func(t *testing.T) {
- res := c.RunDockerCmd("context", "ls", "--format", "{{ json . }}")
- res.Assert(t, icmd.Expected{Out: `"Name":"default"`})
- })
- }
- func TestContextInspect(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- res := c.RunDockerCmd("context", "create", "test-docker", "--from", "default")
- res.Assert(t, icmd.Expected{Out: "test-docker"})
- t.Run("inspect current", func(t *testing.T) {
- // Cannot be run in parallel because of "context use"
- res := c.RunDockerCmd("context", "use", "test-docker")
- res.Assert(t, icmd.Expected{Out: "test-docker"})
- res = c.RunDockerCmd("context", "inspect")
- res.Assert(t, icmd.Expected{Out: `"Name": "test-docker"`})
- })
- }
- func TestContextHelpACI(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- t.Run("help", func(t *testing.T) {
- res := c.RunDockerCmd("context", "create", "aci", "--help")
- // Can't use golden here as the help prints the config directory which changes
- res.Assert(t, icmd.Expected{Out: "docker context create aci CONTEXT [flags]"})
- res.Assert(t, icmd.Expected{Out: "--location"})
- res.Assert(t, icmd.Expected{Out: "--subscription-id"})
- res.Assert(t, icmd.Expected{Out: "--resource-group"})
- })
- t.Run("check exec", func(t *testing.T) {
- res := c.RunDockerOrExitError("context", "create", "aci", "--subscription-id", "invalid-id")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "accepts 1 arg(s), received 0",
- })
- assert.Assert(t, !strings.Contains(res.Combined(), "unknown flag"))
- })
- }
- func TestContextMetrics(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- s := NewMetricsServer(c.MetricsSocket())
- s.Start()
- defer s.Stop()
- t.Run("do not send metrics on help commands", func(t *testing.T) {
- s.ResetUsage()
- c.RunDockerCmd("--help")
- c.RunDockerCmd("ps", "--help")
- c.RunDockerCmd("run", "--help")
- usage := s.GetUsage()
- assert.Equal(t, 0, len(usage))
- })
- t.Run("metrics on default context", func(t *testing.T) {
- s.ResetUsage()
- c.RunDockerCmd("ps")
- c.RunDockerCmd("version")
- c.RunDockerOrExitError("version", "--xxx")
- usage := s.GetUsage()
- assert.Equal(t, 3, len(usage))
- assert.Equal(t, `{"command":"ps","context":"moby","source":"cli","status":"success"}`, usage[0])
- assert.Equal(t, `{"command":"version","context":"moby","source":"cli","status":"success"}`, usage[1])
- assert.Equal(t, `{"command":"version","context":"moby","source":"cli","status":"failure"}`, usage[2])
- })
- t.Run("metrics on other context type", func(t *testing.T) {
- s.ResetUsage()
- c.RunDockerCmd("context", "create", "example", "test-example")
- c.RunDockerCmd("ps")
- c.RunDockerCmd("context", "use", "test-example")
- c.RunDockerCmd("ps")
- c.RunDockerOrExitError("stop", "unknown")
- c.RunDockerCmd("context", "use", "default")
- c.RunDockerCmd("--context", "test-example", "ps")
- usage := s.GetUsage()
- assert.Equal(t, 7, len(usage))
- assert.Equal(t, `{"command":"context create","context":"moby","source":"cli","status":"success"}`, usage[0])
- assert.Equal(t, `{"command":"ps","context":"moby","source":"cli","status":"success"}`, usage[1])
- assert.Equal(t, `{"command":"context use","context":"moby","source":"cli","status":"success"}`, usage[2])
- assert.Equal(t, `{"command":"ps","context":"example","source":"cli","status":"success"}`, usage[3])
- assert.Equal(t, `{"command":"stop","context":"example","source":"cli","status":"failure"}`, usage[4])
- assert.Equal(t, `{"command":"context use","context":"example","source":"cli","status":"success"}`, usage[5])
- assert.Equal(t, `{"command":"ps","context":"example","source":"cli","status":"success"}`, usage[6])
- })
- }
- func TestContextDuplicateACI(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- c.RunDockerCmd("context", "create", "mycontext", "--from", "default")
- res := c.RunDockerOrExitError("context", "create", "aci", "mycontext")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "context mycontext: already exists",
- })
- }
- func TestContextRemove(t *testing.T) {
- t.Run("remove current", func(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- c.RunDockerCmd("context", "create", "test-context-rm", "--from", "default")
- res := c.RunDockerCmd("context", "use", "test-context-rm")
- res.Assert(t, icmd.Expected{Out: "test-context-rm"})
- res = c.RunDockerOrExitError("context", "rm", "test-context-rm")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "cannot delete current context",
- })
- })
- t.Run("force remove current", func(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- c.RunDockerCmd("context", "create", "test-context-rmf")
- c.RunDockerCmd("context", "use", "test-context-rmf")
- res := c.RunDockerCmd("context", "rm", "-f", "test-context-rmf")
- res.Assert(t, icmd.Expected{Out: "test-context-rmf"})
- res = c.RunDockerCmd("context", "ls")
- res.Assert(t, icmd.Expected{Out: "default *"})
- })
- }
- func TestLoginCommandDelegation(t *testing.T) {
- // These tests just check that the existing CLI is called in various cases.
- // They do not test actual login functionality.
- c := NewParallelE2eCLI(t, binDir)
- t.Run("default context", func(t *testing.T) {
- res := c.RunDockerOrExitError("login", "-u", "nouser", "-p", "wrongpasword")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "unauthorized: incorrect username or password",
- })
- })
- t.Run("interactive", func(t *testing.T) {
- res := c.RunDockerOrExitError("login", "someregistry.docker.io")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "Cannot perform an interactive login from a non TTY device",
- })
- })
- t.Run("localhost registry interactive", func(t *testing.T) {
- res := c.RunDockerOrExitError("login", "localhost:443")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "Cannot perform an interactive login from a non TTY device",
- })
- })
- t.Run("localhost registry", func(t *testing.T) {
- res := c.RunDockerOrExitError("login", "localhost", "-u", "user", "-p", "password")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "http://localhost/v2/",
- })
- })
- t.Run("logout", func(t *testing.T) {
- res := c.RunDockerCmd("logout", "someregistry.docker.io")
- res.Assert(t, icmd.Expected{Out: "Removing login credentials for someregistry.docker.io"})
- })
- t.Run("logout", func(t *testing.T) {
- res := c.RunDockerCmd("logout", "localhost:443")
- res.Assert(t, icmd.Expected{Out: "Removing login credentials for localhost:443"})
- })
- t.Run("existing context", func(t *testing.T) {
- c.RunDockerCmd("context", "create", "local", "local")
- c.RunDockerCmd("context", "use", "local")
- res := c.RunDockerOrExitError("login", "-u", "nouser", "-p", "wrongpasword")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "unauthorized: incorrect username or password",
- })
- })
- }
- func TestMissingExistingCLI(t *testing.T) {
- t.Parallel()
- home, err := ioutil.TempDir("", "")
- assert.NilError(t, err)
- t.Cleanup(func() {
- _ = os.RemoveAll(home)
- })
- bin, err := ioutil.TempDir("", "")
- assert.NilError(t, err)
- t.Cleanup(func() {
- _ = os.RemoveAll(bin)
- })
- err = CopyFile(filepath.Join(binDir, DockerExecutableName), filepath.Join(bin, DockerExecutableName))
- assert.NilError(t, err)
- env := []string{"PATH=" + bin}
- if runtime.GOOS == "windows" {
- env = append(env, "USERPROFILE="+home)
- } else {
- env = append(env, "HOME="+home)
- }
- c := icmd.Cmd{
- Env: env,
- Command: []string{filepath.Join(bin, "docker")},
- }
- res := icmd.RunCmd(c)
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: `"com.docker.cli": executable file not found`,
- })
- }
- func TestLegacy(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- t.Run("help", func(t *testing.T) {
- res := c.RunDockerCmd("--help")
- res.Assert(t, icmd.Expected{Out: "swarm"})
- })
- t.Run("swarm", func(t *testing.T) {
- res := c.RunDockerOrExitError("swarm", "join")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: `"docker swarm join" requires exactly 1 argument.`,
- })
- })
- t.Run("local run", func(t *testing.T) {
- cmd := c.NewDockerCmd("run", "--rm", "hello-world")
- cmd.Timeout = 40 * time.Second
- res := icmd.RunCmd(cmd)
- res.Assert(t, icmd.Expected{Out: "Hello from Docker!"})
- })
- t.Run("error messages", func(t *testing.T) {
- res := c.RunDockerOrExitError("foo")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "docker: 'foo' is not a docker command.",
- })
- })
- t.Run("run without HOME defined", func(t *testing.T) {
- cmd := c.NewDockerCmd("ps")
- cmd.Env = []string{"PATH=" + c.PathEnvVar()}
- res := icmd.RunCmd(cmd)
- res.Assert(t, icmd.Expected{
- ExitCode: 0,
- Out: "CONTAINER ID",
- })
- assert.Equal(t, res.Stderr(), "")
- })
- t.Run("run without write access to context store", func(t *testing.T) {
- cmd := c.NewDockerCmd("ps")
- cmd.Env = []string{"PATH=" + c.PathEnvVar(), "HOME=/doesnotexist/"}
- res := icmd.RunCmd(cmd)
- res.Assert(t, icmd.Expected{
- ExitCode: 0,
- Out: "CONTAINER ID",
- })
- })
- t.Run("host flag", func(t *testing.T) {
- stderr := "Cannot connect to the Docker daemon at tcp://localhost:123"
- if runtime.GOOS == "windows" {
- stderr = "error during connect: Get http://localhost:123"
- }
- res := c.RunDockerOrExitError("-H", "tcp://localhost:123", "version")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: stderr,
- })
- })
- t.Run("existing contexts delegate", func(t *testing.T) {
- c.RunDockerCmd("context", "create", "moby-ctx", "--from=default")
- c.RunDockerCmd("context", "use", "moby-ctx")
- res := c.RunDockerOrExitError("swarm", "join")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: `"docker swarm join" requires exactly 1 argument.`,
- })
- })
- t.Run("host flag overrides context", func(t *testing.T) {
- c.RunDockerCmd("context", "create", "example", "test-example")
- c.RunDockerCmd("context", "use", "test-example")
- endpoint := "unix:///var/run/docker.sock"
- if runtime.GOOS == "windows" {
- endpoint = "npipe:////./pipe/docker_engine"
- }
- res := c.RunDockerCmd("-H", endpoint, "ps")
- // Example backend's ps output includes these strings
- assert.Assert(t, !strings.Contains(res.Stdout(), "id"), "%q does not contains %q", res.Stdout(), "id")
- assert.Assert(t, !strings.Contains(res.Stdout(), "1234"), "%q does not contains %q", res.Stdout(), "1234")
- })
- }
- func TestLegacyLogin(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- t.Run("host flag login", func(t *testing.T) {
- res := c.RunDockerOrExitError("-H", "tcp://localhost:123", "login", "-u", "nouser", "-p", "wrongpasword")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "WARNING! Using --password via the CLI is insecure. Use --password-stdin.",
- })
- })
- t.Run("log level flag login", func(t *testing.T) {
- res := c.RunDockerOrExitError("--log-level", "debug", "login", "-u", "nouser", "-p", "wrongpasword")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: "WARNING! Using --password via the CLI is insecure",
- })
- })
- t.Run("login help global flags", func(t *testing.T) {
- res := c.RunDockerCmd("login", "--help")
- assert.Assert(t, !strings.Contains(res.Combined(), "--log-level"))
- })
- }
- func TestUnsupportedCommand(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- c.RunDockerCmd("context", "create", "example", "test-example")
- res := c.RunDockerOrExitError("--context", "test-example", "images")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Err: `Command "images" not available in current context (test-example), you can use the "default" context to run this command`,
- })
- }
- func TestVersion(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- t.Run("azure version", func(t *testing.T) {
- res := c.RunDockerCmd("version")
- res.Assert(t, icmd.Expected{Out: "Cloud integration"})
- })
- t.Run("format", func(t *testing.T) {
- res := c.RunDockerCmd("version", "-f", "{{ json . }}")
- res.Assert(t, icmd.Expected{Out: `"Client":`})
- res = c.RunDockerCmd("version", "--format", "{{ json . }}")
- res.Assert(t, icmd.Expected{Out: `"Client":`})
- })
- t.Run("format legacy", func(t *testing.T) {
- res := c.RunDockerCmd("version", "-f", "{{ json .Client }}")
- res.Assert(t, icmd.Expected{Out: `"DefaultAPIVersion":`})
- res = c.RunDockerCmd("version", "--format", "{{ json .Server }}")
- res.Assert(t, icmd.Expected{Out: `"KernelVersion":`})
- })
- t.Run("format cloud integration", func(t *testing.T) {
- res := c.RunDockerCmd("version", "-f", "pretty")
- res.Assert(t, icmd.Expected{Out: `Cloud integration:`})
- res = c.RunDockerCmd("version", "-f", "")
- res.Assert(t, icmd.Expected{Out: `Cloud integration:`})
- res = c.RunDockerCmd("version", "-f", "json")
- res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`})
- res = c.RunDockerCmd("version", "-f", "{{ json . }}")
- res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`})
- res = c.RunDockerCmd("version", "--format", "{{json .}}")
- res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`})
- res = c.RunDockerCmd("version", "--format", "{{json . }}")
- res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`})
- res = c.RunDockerCmd("version", "--format", "{{ json .}}")
- res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`})
- res = c.RunDockerCmd("version", "--format", "{{ json . }}")
- res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`})
- })
- t.Run("delegate version flag", func(t *testing.T) {
- c.RunDockerCmd("context", "create", "example", "test-example")
- c.RunDockerCmd("context", "use", "test-example")
- res := c.RunDockerCmd("-v")
- res.Assert(t, icmd.Expected{Out: "Docker version"})
- })
- }
- func TestMockBackend(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- c.RunDockerCmd("context", "create", "example", "test-example")
- res := c.RunDockerCmd("context", "use", "test-example")
- res.Assert(t, icmd.Expected{Out: "test-example"})
- t.Run("use", func(t *testing.T) {
- res := c.RunDockerCmd("context", "show")
- res.Assert(t, icmd.Expected{Out: "test-example"})
- res = c.RunDockerCmd("context", "ls")
- golden.Assert(t, res.Stdout(), GoldenFile("ls-out-test-example"))
- })
- t.Run("ps", func(t *testing.T) {
- res := c.RunDockerCmd("ps")
- golden.Assert(t, res.Stdout(), "ps-out-example.golden")
- res = c.RunDockerCmd("ps", "--format", "pretty")
- golden.Assert(t, res.Stdout(), "ps-out-example.golden")
- res = c.RunDockerCmd("ps", "--format", "json")
- golden.Assert(t, res.Stdout(), "ps-out-example-json.golden")
- })
- t.Run("ps quiet", func(t *testing.T) {
- res := c.RunDockerCmd("ps", "-q")
- golden.Assert(t, res.Stdout(), "ps-quiet-out-example.golden")
- })
- t.Run("ps quiet all", func(t *testing.T) {
- res := c.RunDockerCmd("ps", "-q", "--all")
- golden.Assert(t, res.Stdout(), "ps-quiet-all-out-example.golden")
- })
- t.Run("inspect", func(t *testing.T) {
- res := c.RunDockerCmd("inspect", "id")
- golden.Assert(t, res.Stdout(), "inspect-id.golden")
- })
- t.Run("run", func(t *testing.T) {
- res := c.RunDockerCmd("run", "-d", "nginx", "-p", "80:80")
- res.Assert(t, icmd.Expected{
- Out: `Running container "nginx" with name`,
- })
- })
- }
- func TestFailOnEcsUsageAsPlugin(t *testing.T) {
- c := NewParallelE2eCLI(t, binDir)
- res := c.RunDockerCmd("context", "create", "local", "local")
- res.Assert(t, icmd.Expected{})
- t.Run("fail on ecs usage as plugin", func(t *testing.T) {
- res := c.RunDockerOrExitError("--context", "local", "ecs", "compose", "up")
- res.Assert(t, icmd.Expected{
- ExitCode: 1,
- Out: "",
- Err: "The ECS integration is now part of the CLI. Use `docker compose` with an ECS context.",
- })
- })
- }
|