mirror of https://github.com/docker/compose.git
tests.e2e: Refactor
Signed-off-by: Chris Crone <christopher.crone@docker.com>
This commit is contained in:
parent
036190bd5e
commit
017053e19a
19
Makefile
19
Makefile
|
@ -23,7 +23,12 @@ ifeq ($(UNAME_S),Darwin)
|
|||
endif
|
||||
|
||||
GIT_TAG?=$(shell git describe --tags --match "v[0-9]*")
|
||||
TESTIFY_OPTS=$(if $(TESTIFY),-testify.m $(TESTIFY),)
|
||||
TEST_FLAGS?=
|
||||
E2E_TEST?=
|
||||
ifeq ($(E2E_TEST),)
|
||||
else
|
||||
TEST_FLAGS=-run $(E2E_TEST)
|
||||
endif
|
||||
|
||||
all: cli
|
||||
|
||||
|
@ -38,14 +43,14 @@ cli: ## Compile the cli
|
|||
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||
--output ./bin
|
||||
|
||||
e2e-local: ## Run End to end local tests. set env TESTIFY=Test1 for running single test
|
||||
go test -v ./tests/e2e ./tests/skip-win-ci-e2e ./local/e2e $(TESTIFY_OPTS)
|
||||
e2e-local: ## Run End to end local tests. Set E2E_TEST=TestName to run a single test
|
||||
go test -count=1 -v $(TEST_FLAGS) ./tests/e2e ./tests/skip-win-ci-e2e ./local/e2e
|
||||
|
||||
e2e-win-ci: ## Run End to end local tests on windows CI, no docker for linux containers available ATM. set env TESTIFY=Test1 for running single test
|
||||
go test -v ./tests/e2e $(TESTIFY_OPTS)
|
||||
e2e-win-ci: ## Run end to end local tests on Windows CI, no Docker for Linux containers available ATM. Set E2E_TEST=TestName to run a single test
|
||||
go test -count=1 -v $(TEST_FLAGS) ./tests/e2e
|
||||
|
||||
e2e-aci: ## Run End to end ACI tests. set env TESTIFY=Test1 for running single test
|
||||
go test -v ./tests/aci-e2e $(TESTIFY_OPTS)
|
||||
e2e-aci: ## Run End to end ACI tests. Set E2E_TEST=TestName to run a single test
|
||||
go test -count=1 -v $(TEST_FLAGS) ./tests/aci-e2e
|
||||
|
||||
cross: ## Compile the CLI for linux, darwin and windows
|
||||
@docker build . --target cross \
|
||||
|
|
|
@ -62,7 +62,7 @@ You might need to run again `docker login azure` to properly use the command lin
|
|||
|
||||
You can also run a single ACI test from the test suite:
|
||||
```
|
||||
TESTIFY=TestACIRunSingleContainer AZURE_TENANT_ID="xxx" AZURE_CLIENT_ID="yyy" AZURE_CLIENT_SECRET="yyy" make e2e-aci
|
||||
AZURE_TENANT_ID="xxx" AZURE_CLIENT_ID="yyy" AZURE_CLIENT_SECRET="yyy" make E2E_TEST=TestContainerRun e2e-aci
|
||||
```
|
||||
|
||||
## Release
|
||||
|
|
4
go.mod
4
go.mod
|
@ -41,11 +41,10 @@ require (
|
|||
github.com/hashicorp/go-version v1.2.1 // indirect
|
||||
github.com/moby/term v0.0.0-20200611042045-63b9a826fb74
|
||||
github.com/morikuni/aec v1.0.0
|
||||
github.com/onsi/gomega v1.10.1
|
||||
github.com/onsi/gomega v1.10.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/runc v0.1.1 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/robpike/filter v0.0.0-20150108201509-2984852a2183
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
|
@ -58,6 +57,5 @@ require (
|
|||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/ini.v1 v1.57.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.0.2
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -19,8 +19,6 @@ github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW
|
|||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest v0.11.0 h1:tnO41Uo+/0sxTMFY/U7aKg2abek3JOnnXcuSuba74jI=
|
||||
github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.2 h1:BR5GoSGobeiMwGOOIxXuvNKNPy+HMGdteKB8kJUDnBE=
|
||||
github.com/Azure/go-autorest/autorest v0.11.2/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.3 h1:fyYnmYujkIXUgv88D9/Wo2ybE4Zwd/TmQd5sSI5u2Ws=
|
||||
github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
|
@ -73,12 +71,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
|||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/aws/aws-sdk-go v1.30.22/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.33.18 h1:Ccy1SV2SsgJU3rfrD+SOhQ0jvuzfrFuja/oKI86ruPw=
|
||||
github.com/aws/aws-sdk-go v1.33.18/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.33.19 h1:SMna0QLInNqm+nNL9tb7OVWTqSfNYSxrCa2adnyVth4=
|
||||
github.com/aws/aws-sdk-go v1.33.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.33.20 h1:mtXKHmMQO6o0i2GTjyiVNZGlXqJDCUbiik0OQeMds/o=
|
||||
github.com/aws/aws-sdk-go v1.33.20/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.33.21 h1:ziUemjajvLABlnJFe+8sM3fpqlg/DNA4944rUZ05PhY=
|
||||
github.com/aws/aws-sdk-go v1.33.21/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/awslabs/goformation/v4 v4.8.0 h1:UiUhyokRy3suEqBXTnipvY8klqY3Eyl4GCH17brraEc=
|
||||
|
@ -452,8 +444,6 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
|
|||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/robpike/filter v0.0.0-20150108201509-2984852a2183 h1:qDhD/wJDGyWrXKLIKmEKpKK/ejaZlguyeEaLZzmrtzo=
|
||||
github.com/robpike/filter v0.0.0-20150108201509-2984852a2183/go.mod h1:3dvYi47BCPInRb2ILlNnrXfl++XpwTWLbIxPyJsUvCw=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
|
|
@ -17,61 +17,67 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"gotest.tools/v3/icmd"
|
||||
|
||||
"github.com/docker/api/tests/framework"
|
||||
. "github.com/docker/api/tests/framework"
|
||||
)
|
||||
|
||||
type LocalBackendTestSuite struct {
|
||||
framework.Suite
|
||||
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 (m *LocalBackendTestSuite) BeforeTest(suiteName string, testName string) {
|
||||
m.NewDockerCommand("context", "create", "local", "test-context").ExecOrDie()
|
||||
m.NewDockerCommand("context", "use", "test-context").ExecOrDie()
|
||||
}
|
||||
func TestLocalBackend(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c.RunDockerCmd("context", "create", "local", "test-context").Assert(t, icmd.Success)
|
||||
c.RunDockerCmd("context", "use", "test-context").Assert(t, icmd.Success)
|
||||
|
||||
func (m *LocalBackendTestSuite) AfterTest(suiteName string, testName string) {
|
||||
m.NewDockerCommand("context", "rm", "-f", "test-context").ExecOrDie()
|
||||
}
|
||||
t.Run("run", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("run", "-d", "nginx")
|
||||
res.Assert(t, icmd.Success)
|
||||
containerName := strings.TrimSpace(res.Combined())
|
||||
t.Cleanup(func() {
|
||||
_ = c.RunDockerCmd("rm", "-f", containerName)
|
||||
})
|
||||
res = c.RunDockerCmd("inspect", containerName)
|
||||
res.Assert(t, icmd.Expected{Out: `"Status": "running"`})
|
||||
})
|
||||
|
||||
func (m *LocalBackendTestSuite) TestPs() {
|
||||
out := m.NewDockerCommand("ps").ExecOrDie()
|
||||
require.Equal(m.T(), "CONTAINER ID IMAGE COMMAND STATUS PORTS\n", out)
|
||||
}
|
||||
t.Run("run with ports", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("run", "-d", "-p", "8080:80", "nginx")
|
||||
res.Assert(t, icmd.Success)
|
||||
containerName := strings.TrimSpace(res.Combined())
|
||||
t.Cleanup(func() {
|
||||
_ = c.RunDockerCmd("rm", "-f", containerName)
|
||||
})
|
||||
res = c.RunDockerCmd("inspect", containerName)
|
||||
res.Assert(t, icmd.Expected{Out: `"Status": "running"`})
|
||||
res = c.RunDockerCmd("ps")
|
||||
res.Assert(t, icmd.Expected{Out: "0.0.0.0:8080->80/tcp"})
|
||||
})
|
||||
|
||||
func (m *LocalBackendTestSuite) TestRun() {
|
||||
_, err := m.NewDockerCommand("run", "-d", "--name", "nginx", "nginx").Exec()
|
||||
require.Nil(m.T(), err)
|
||||
out := m.NewDockerCommand("ps").ExecOrDie()
|
||||
defer func() {
|
||||
m.NewDockerCommand("rm", "-f", "nginx").ExecOrDie()
|
||||
}()
|
||||
assert.Contains(m.T(), out, "nginx")
|
||||
}
|
||||
|
||||
func (m *LocalBackendTestSuite) TestRunWithPorts() {
|
||||
_, err := m.NewDockerCommand("run", "-d", "--name", "nginx", "-p", "8080:80", "nginx").Exec()
|
||||
require.Nil(m.T(), err)
|
||||
out := m.NewDockerCommand("ps").ExecOrDie()
|
||||
defer func() {
|
||||
m.NewDockerCommand("rm", "-f", "nginx").ExecOrDie()
|
||||
}()
|
||||
assert.Contains(m.T(), out, "8080")
|
||||
|
||||
out = m.NewDockerCommand("inspect", "nginx").ExecOrDie()
|
||||
assert.Contains(m.T(), out, "\"Status\": \"running\"")
|
||||
}
|
||||
|
||||
func (m *LocalBackendTestSuite) TestInspectNotFound() {
|
||||
out, _ := m.NewDockerCommand("inspect", "nonexistentcontainer").Exec()
|
||||
assert.Contains(m.T(), out, "Error: No such container: nonexistentcontainer")
|
||||
}
|
||||
|
||||
func TestLocalBackendTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(LocalBackendTestSuite))
|
||||
t.Run("inspect not found", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("inspect", "nonexistentcontainer")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "Error: No such container: nonexistentcontainer",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,460 +18,595 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/api/errdefs"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/icmd"
|
||||
"gotest.tools/v3/poll"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources"
|
||||
azure_storage "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/storage/mgmt/storage"
|
||||
"github.com/Azure/azure-storage-file-go/azfile"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
. "github.com/onsi/gomega"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
azure "github.com/docker/api/aci"
|
||||
"github.com/docker/api/aci"
|
||||
"github.com/docker/api/aci/login"
|
||||
"github.com/docker/api/containers"
|
||||
"github.com/docker/api/context/store"
|
||||
"github.com/docker/api/errdefs"
|
||||
"github.com/docker/api/tests/aci-e2e/storage"
|
||||
. "github.com/docker/api/tests/framework"
|
||||
)
|
||||
|
||||
const (
|
||||
location = "westeurope"
|
||||
contextName = "acitest"
|
||||
testContainerName = "testcontainername"
|
||||
testShareName = "dockertestshare"
|
||||
testFileContent = "Volume mounted with success!"
|
||||
testFileName = "index.html"
|
||||
contextName = "aci-test"
|
||||
location = "westeurope"
|
||||
)
|
||||
|
||||
var (
|
||||
subscriptionID string
|
||||
)
|
||||
var binDir string
|
||||
|
||||
type E2eACISuite struct {
|
||||
Suite
|
||||
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 (s *E2eACISuite) TestLoginLogoutCreateContextError() {
|
||||
s.Step("Logs in azure using service principal credentials", azureLogin)
|
||||
// Cannot be parallelized as login/logout is global.
|
||||
func TestLoginLogout(t *testing.T) {
|
||||
startTime := strconv.Itoa(int(time.Now().UnixNano()))
|
||||
c := NewE2eCLI(t, binDir)
|
||||
rg := "E2E-" + startTime
|
||||
|
||||
s.Step("logout from azure", func() {
|
||||
output := s.NewDockerCommand("logout", "azure").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring(""))
|
||||
t.Run("login", func(t *testing.T) {
|
||||
azureLogin(t)
|
||||
})
|
||||
|
||||
t.Run("create context", func(t *testing.T) {
|
||||
sID := getSubscriptionID(t)
|
||||
err := createResourceGroup(sID, rg)
|
||||
assert.Check(t, is.Nil(err))
|
||||
t.Cleanup(func() {
|
||||
_ = deleteResourceGroup(rg)
|
||||
})
|
||||
|
||||
res := c.RunDockerCmd("context", "create", "aci", contextName, "--subscription-id", sID, "--resource-group", rg, "--location", location)
|
||||
res.Assert(t, icmd.Success)
|
||||
res = c.RunDockerCmd("context", "use", contextName)
|
||||
res.Assert(t, icmd.Expected{Out: contextName})
|
||||
res = c.RunDockerCmd("context", "ls")
|
||||
res.Assert(t, icmd.Expected{Out: contextName + " *"})
|
||||
})
|
||||
|
||||
t.Run("delete context", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("context", "use", "default")
|
||||
res.Assert(t, icmd.Expected{Out: "default"})
|
||||
|
||||
res = c.RunDockerCmd("context", "rm", contextName)
|
||||
res.Assert(t, icmd.Expected{Out: contextName})
|
||||
})
|
||||
|
||||
t.Run("logout", func(t *testing.T) {
|
||||
_, err := os.Stat(login.GetTokenStorePath())
|
||||
Expect(os.IsNotExist(err)).To(BeTrue())
|
||||
assert.NilError(t, err)
|
||||
res := c.RunDockerCmd("logout", "azure")
|
||||
res.Assert(t, icmd.Expected{Out: "Removing login credentials for Azure"})
|
||||
_, err = os.Stat(login.GetTokenStorePath())
|
||||
assert.ErrorContains(t, err, "no such file or directory")
|
||||
})
|
||||
|
||||
s.Step("check context create fails with an explicit error and returns a specific error code", func() {
|
||||
cmd := exec.Command("docker", "context", "create", "aci", "someContext")
|
||||
bytes, err := cmd.CombinedOutput()
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(string(bytes)).To(ContainSubstring("not logged in to azure, you need to run \"docker login azure\" first"))
|
||||
Expect(cmd.ProcessState.ExitCode()).To(Equal(errdefs.ExitCodeLoginRequired))
|
||||
t.Run("create context fail", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("context", "create", "aci", "fail-context")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: errdefs.ExitCodeLoginRequired,
|
||||
Err: `not logged in to azure, you need to run "docker login azure" first`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *E2eACISuite) TestACIRunSingleContainer() {
|
||||
var containerName string
|
||||
resourceGroupName := s.setupTestResourceGroup()
|
||||
defer deleteResourceGroup(resourceGroupName)
|
||||
func TestContainerRun(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
sID, rg := setupTestResourceGroup(t, c)
|
||||
|
||||
var nginxExposedURL string
|
||||
var containerID string
|
||||
s.Step("runs nginx on port 80", func() {
|
||||
aciContext := store.AciContext{
|
||||
SubscriptionID: subscriptionID,
|
||||
Location: location,
|
||||
ResourceGroup: resourceGroupName,
|
||||
const (
|
||||
testShareName = "dockertestshare"
|
||||
testFileContent = "Volume mounted successfully!"
|
||||
testFileName = "index.html"
|
||||
)
|
||||
|
||||
// Bootstrap volume
|
||||
aciContext := store.AciContext{
|
||||
SubscriptionID: sID,
|
||||
Location: location,
|
||||
ResourceGroup: rg,
|
||||
}
|
||||
saName := "e2e" + strconv.Itoa(int(time.Now().UnixNano()))
|
||||
_, cleanupSa := createStorageAccount(t, aciContext, saName)
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupSa(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
keys := getStorageKeys(t, aciContext, saName)
|
||||
assert.Assert(t, len(keys) > 0)
|
||||
k := *keys[0].Value
|
||||
cred, u := createFileShare(t, k, testShareName, saName)
|
||||
uploadFile(t, *cred, u.String(), testFileName, testFileContent)
|
||||
|
||||
testStorageAccountName := "storageteste2e" + RandStringBytes(6) // "between 3 and 24 characters in length and use numbers and lower-case letters only"
|
||||
createStorageAccount(aciContext, testStorageAccountName)
|
||||
defer deleteStorageAccount(aciContext, testStorageAccountName)
|
||||
keys := getStorageKeys(aciContext, testStorageAccountName)
|
||||
firstKey := *keys[0].Value
|
||||
credential, u := createFileShare(firstKey, testShareName, testStorageAccountName)
|
||||
uploadFile(credential, u.String(), testFileName, testFileContent)
|
||||
// Used in subtests
|
||||
var (
|
||||
container string
|
||||
hostIP string
|
||||
endpoint string
|
||||
)
|
||||
|
||||
t.Run("run", func(t *testing.T) {
|
||||
mountTarget := "/usr/share/nginx/html"
|
||||
output := s.NewDockerCommand("run", "-d", "nginx",
|
||||
"-v", fmt.Sprintf("%s:%s@%s:%s",
|
||||
testStorageAccountName, firstKey, testShareName, mountTarget),
|
||||
res := c.RunDockerCmd(
|
||||
"run", "-d",
|
||||
"-v", fmt.Sprintf("%s:%s@%s:%s", saName, k, testShareName, mountTarget),
|
||||
"-p", "80:80",
|
||||
).ExecOrDie()
|
||||
runOutput := Lines(output)
|
||||
containerName = runOutput[len(runOutput)-1]
|
||||
|
||||
output = s.NewDockerCommand("ps").ExecOrDie()
|
||||
lines := Lines(output)
|
||||
Expect(len(lines)).To(Equal(2))
|
||||
|
||||
containerFields := Columns(lines[1])
|
||||
Expect(containerFields[1]).To(Equal("nginx"))
|
||||
Expect(containerFields[2]).To(Equal("Running"))
|
||||
exposedIP := containerFields[3]
|
||||
containerID = containerFields[0]
|
||||
Expect(exposedIP).To(ContainSubstring(":80->80/tcp"))
|
||||
|
||||
nginxExposedURL = strings.ReplaceAll(exposedIP, "->80/tcp", "")
|
||||
output = s.NewCommand("curl", nginxExposedURL).ExecOrDie()
|
||||
Expect(output).To(ContainSubstring(testFileContent))
|
||||
|
||||
output = s.NewDockerCommand("logs", containerID).ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("GET"))
|
||||
"nginx",
|
||||
)
|
||||
res.Assert(t, icmd.Success)
|
||||
container = getContainerName(res.Stdout())
|
||||
t.Logf("Container name: %s", container)
|
||||
})
|
||||
|
||||
s.Step("inspect command", func() {
|
||||
inspect := s.NewDockerCommand("inspect", containerID).ExecOrDie()
|
||||
Expect(inspect).To(ContainSubstring("\"Platform\": \"Linux\""))
|
||||
Expect(inspect).To(ContainSubstring("\"CPULimit\": 1"))
|
||||
Expect(inspect).To(ContainSubstring("\"RestartPolicyCondition\": \"none\""))
|
||||
t.Run("inspect", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("inspect", container)
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
containerInspect, err := ParseContainerInspect(res.Stdout())
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, containerInspect.Platform, "Linux")
|
||||
assert.Equal(t, containerInspect.CPULimit, 1.0)
|
||||
assert.Equal(t, containerInspect.RestartPolicyCondition, containers.RestartPolicyNone)
|
||||
|
||||
assert.Assert(t, is.Len(containerInspect.Ports, 1))
|
||||
hostIP = containerInspect.Ports[0].HostIP
|
||||
endpoint = fmt.Sprintf("http://%s:%d", containerInspect.Ports[0].HostIP, containerInspect.Ports[0].HostPort)
|
||||
t.Logf("Endpoint: %s", endpoint)
|
||||
})
|
||||
|
||||
s.Step("exec command", func() {
|
||||
output := s.NewDockerCommand("exec", containerName, "pwd").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("/"))
|
||||
|
||||
_, err := s.NewDockerCommand("exec", containerName, "echo", "fail_with_argument").Exec()
|
||||
Expect(err.Error()).To(ContainSubstring("ACI exec command does not accept arguments to the command. " +
|
||||
"Only the binary should be specified"))
|
||||
t.Run("ps", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("ps")
|
||||
res.Assert(t, icmd.Success)
|
||||
out := strings.Split(strings.TrimSpace(res.Stdout()), "\n")
|
||||
l := out[len(out)-1]
|
||||
assert.Assert(t, strings.Contains(l, container))
|
||||
assert.Assert(t, strings.Contains(l, "nginx"))
|
||||
assert.Assert(t, strings.Contains(l, "Running"))
|
||||
assert.Assert(t, strings.Contains(l, hostIP+":80->80/tcp"))
|
||||
})
|
||||
|
||||
s.Step("follow logs from nginx", func() {
|
||||
timeChan := make(chan time.Time)
|
||||
|
||||
ctx := s.NewDockerCommand("logs", "--follow", containerName).WithTimeout(timeChan)
|
||||
outChan := make(chan string)
|
||||
|
||||
go func() {
|
||||
output, err := ctx.Exec()
|
||||
// check the process is cancelled by the test, not another unexpected error
|
||||
Expect(err.Error()).To(ContainSubstring("timed out"))
|
||||
outChan <- output
|
||||
}()
|
||||
// Ensure logs -- follow is strated before we curl nginx
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
s.NewCommand("curl", nginxExposedURL+"/test").ExecOrDie()
|
||||
// Give the `logs --follow` a little time to get logs of the curl call
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Trigger a timeout to make ctx.Exec exit
|
||||
timeChan <- time.Now()
|
||||
|
||||
output := <-outChan
|
||||
|
||||
Expect(output).To(ContainSubstring("/test"))
|
||||
t.Run("http get", func(t *testing.T) {
|
||||
r, err := http.Get(endpoint)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, r.StatusCode, http.StatusOK)
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, strings.Contains(string(b), testFileContent), "Actual content: "+string(b))
|
||||
})
|
||||
|
||||
s.Step("removes container nginx", func() {
|
||||
output := s.NewDockerCommand("rm", containerName).ExecOrDie()
|
||||
Expect(Lines(output)[0]).To(Equal(containerName))
|
||||
t.Run("logs", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("logs", container)
|
||||
res.Assert(t, icmd.Expected{Out: "GET"})
|
||||
})
|
||||
|
||||
s.Step("re-run nginx with modified cpu/mem, and without --detach and follow logs", func() {
|
||||
shutdown := make(chan time.Time)
|
||||
errs := make(chan error)
|
||||
outChan := make(chan string)
|
||||
cmd := s.NewDockerCommand("run", "nginx", "--restart", "on-failure", "--memory", "0.1G", "--cpus", "0.1", "-p", "80:80", "--name", testContainerName).WithTimeout(shutdown)
|
||||
go func() {
|
||||
output, err := cmd.Exec()
|
||||
outChan <- output
|
||||
errs <- err
|
||||
}()
|
||||
err := WaitFor(time.Second, 100*time.Second, errs, func() bool {
|
||||
output := s.NewDockerCommand("ps").ExecOrDie()
|
||||
lines := Lines(output)
|
||||
if len(lines) != 2 {
|
||||
return false
|
||||
}
|
||||
containerFields := Columns(lines[1])
|
||||
if containerFields[2] != "Running" {
|
||||
return false
|
||||
}
|
||||
containerID = containerFields[0]
|
||||
nginxExposedURL = strings.ReplaceAll(containerFields[3], "->80/tcp", "")
|
||||
return true
|
||||
t.Run("exec", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("exec", container, "pwd")
|
||||
res.Assert(t, icmd.Expected{Out: "/"})
|
||||
|
||||
res = c.RunDockerCmd("exec", container, "echo", "fail_with_argument")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "ACI exec command does not accept arguments to the command. Only the binary should be specified",
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
s.NewCommand("curl", nginxExposedURL+"/test").ExecOrDie()
|
||||
inspect := s.NewDockerCommand("inspect", containerID).ExecOrDie()
|
||||
Expect(inspect).To(ContainSubstring("\"CPULimit\": 0.1"))
|
||||
Expect(inspect).To(ContainSubstring("\"MemoryLimit\": 107374182"))
|
||||
Expect(inspect).To(ContainSubstring("\"RestartPolicyCondition\": \"on-failure\""))
|
||||
|
||||
// Give a little time to get logs of the curl call
|
||||
time.Sleep(5 * time.Second)
|
||||
// Kill
|
||||
close(shutdown)
|
||||
|
||||
output := <-outChan
|
||||
Expect(output).To(ContainSubstring("/test"))
|
||||
})
|
||||
|
||||
s.Step("removes container nginx", func() {
|
||||
output := s.NewDockerCommand("rm", testContainerName).ExecOrDie()
|
||||
Expect(Lines(output)[0]).To(Equal(testContainerName))
|
||||
})
|
||||
}
|
||||
t.Run("logs follow", func(t *testing.T) {
|
||||
cmd := c.NewDockerCmd("logs", "--follow", container)
|
||||
res := icmd.StartCmd(cmd)
|
||||
|
||||
func (s *E2eACISuite) TestACIComposeApplication() {
|
||||
defer deleteResourceGroup(s.setupTestResourceGroup())
|
||||
|
||||
var exposedURL string
|
||||
const composeFile = "../composefiles/aci-demo/aci_demo_port.yaml"
|
||||
const composeFileMultiplePorts = "../composefiles/aci-demo/aci_demo_multi_port.yaml"
|
||||
const composeProjectName = "acie2e"
|
||||
const serverContainer = composeProjectName + "_web"
|
||||
const wordsContainer = composeProjectName + "_words"
|
||||
|
||||
s.Step("deploys a compose app", func() {
|
||||
// specifically do not specify project name here, it will be derived from current folder "acie2e"
|
||||
s.NewDockerCommand("compose", "up", "-f", composeFile).ExecOrDie()
|
||||
output := s.NewDockerCommand("ps").ExecOrDie()
|
||||
Lines := Lines(output)
|
||||
Expect(len(Lines)).To(Equal(4))
|
||||
webChecked := false
|
||||
|
||||
for _, line := range Lines[1:] {
|
||||
Expect(line).To(ContainSubstring("Running"))
|
||||
if strings.Contains(line, serverContainer) {
|
||||
webChecked = true
|
||||
containerFields := Columns(line)
|
||||
exposedIP := containerFields[3]
|
||||
Expect(exposedIP).To(ContainSubstring(":80->80/tcp"))
|
||||
|
||||
exposedURL = strings.ReplaceAll(exposedIP, "->80/tcp", "")
|
||||
output = s.NewCommand("curl", exposedURL).ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("Docker Compose demo"))
|
||||
output = s.NewCommand("curl", exposedURL+"/words/noun").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("\"word\":"))
|
||||
checkUp := func(t poll.LogT) poll.Result {
|
||||
r, _ := http.Get(endpoint + "/is_up")
|
||||
if r != nil && r.StatusCode == http.StatusNotFound {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for container to serve request")
|
||||
}
|
||||
poll.WaitOn(t, checkUp, poll.WithDelay(1*time.Second), poll.WithTimeout(60*time.Second))
|
||||
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "/test"))
|
||||
|
||||
checkLogs := func(t poll.LogT) poll.Result {
|
||||
if strings.Contains(res.Stdout(), "/test") {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for logs to contain /test")
|
||||
}
|
||||
|
||||
Expect(webChecked).To(BeTrue())
|
||||
// Do request on /test
|
||||
go func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
_, _ = http.Get(endpoint + "/test")
|
||||
}()
|
||||
|
||||
poll.WaitOn(t, checkLogs, poll.WithDelay(3*time.Second), poll.WithTimeout(20*time.Second))
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
err := res.Cmd.Process.Kill()
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
err := res.Cmd.Process.Signal(syscall.SIGTERM)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
s.Step("get logs from web service", func() {
|
||||
output := s.NewDockerCommand("logs", serverContainer).ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("Listening on port 80"))
|
||||
})
|
||||
|
||||
s.Step("updates a compose app", func() {
|
||||
s.NewDockerCommand("compose", "up", "-f", composeFileMultiplePorts, "--project-name", composeProjectName).ExecOrDie()
|
||||
// Expect(output).To(ContainSubstring("Successfully deployed"))
|
||||
output := s.NewDockerCommand("ps").ExecOrDie()
|
||||
Lines := Lines(output)
|
||||
Expect(len(Lines)).To(Equal(4))
|
||||
webChecked := false
|
||||
wordsChecked := false
|
||||
|
||||
for _, line := range Lines[1:] {
|
||||
Expect(line).To(ContainSubstring("Running"))
|
||||
if strings.Contains(line, serverContainer) {
|
||||
webChecked = true
|
||||
containerFields := Columns(line)
|
||||
exposedIP := containerFields[3]
|
||||
Expect(exposedIP).To(ContainSubstring(":80->80/tcp"))
|
||||
|
||||
url := strings.ReplaceAll(exposedIP, "->80/tcp", "")
|
||||
Expect(exposedURL).To(Equal(url))
|
||||
t.Run("rm", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("rm", container)
|
||||
res.Assert(t, icmd.Expected{Out: container})
|
||||
checkStopped := func(t poll.LogT) poll.Result {
|
||||
res := c.RunDockerCmd("inspect", container)
|
||||
if res.ExitCode == 1 {
|
||||
return poll.Success()
|
||||
}
|
||||
if strings.Contains(line, wordsContainer) {
|
||||
wordsChecked = true
|
||||
containerFields := Columns(line)
|
||||
exposedIP := containerFields[3]
|
||||
Expect(exposedIP).To(ContainSubstring(":8080->8080/tcp"))
|
||||
return poll.Continue("waiting for container to stop")
|
||||
}
|
||||
poll.WaitOn(t, checkStopped, poll.WithDelay(5*time.Second), poll.WithTimeout(60*time.Second))
|
||||
})
|
||||
}
|
||||
|
||||
url := strings.ReplaceAll(exposedIP, "->8080/tcp", "")
|
||||
output = s.NewCommand("curl", url+"/noun").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("\"word\":"))
|
||||
func TestContainerRunAttached(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
_, _ = setupTestResourceGroup(t, c)
|
||||
|
||||
// Used in subtests
|
||||
var (
|
||||
container string
|
||||
endpoint string
|
||||
)
|
||||
|
||||
t.Run("run attached limits", func(t *testing.T) {
|
||||
container = "test-container"
|
||||
cmd := c.NewDockerCmd(
|
||||
"run",
|
||||
"--name", container,
|
||||
"--restart", "on-failure",
|
||||
"--memory", "0.1G", "--cpus", "0.1",
|
||||
"-p", "80:80",
|
||||
"nginx",
|
||||
)
|
||||
runRes := icmd.StartCmd(cmd)
|
||||
|
||||
checkRunning := func(t poll.LogT) poll.Result {
|
||||
res := c.RunDockerCmd("inspect", container)
|
||||
if res.ExitCode == 0 {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for container to be running")
|
||||
}
|
||||
poll.WaitOn(t, checkRunning, poll.WithDelay(5*time.Second), poll.WithTimeout(60*time.Second))
|
||||
|
||||
inspectRes := c.RunDockerCmd("inspect", container)
|
||||
inspectRes.Assert(t, icmd.Success)
|
||||
|
||||
containerInspect, err := ParseContainerInspect(inspectRes.Stdout())
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, containerInspect.Platform, "Linux")
|
||||
assert.Equal(t, containerInspect.CPULimit, 0.1)
|
||||
assert.Equal(t, containerInspect.MemoryLimit, uint64(107374182))
|
||||
assert.Equal(t, containerInspect.RestartPolicyCondition, containers.RestartPolicyOnFailure)
|
||||
|
||||
assert.Assert(t, is.Len(containerInspect.Ports, 1))
|
||||
endpoint = fmt.Sprintf("http://%s:%d", containerInspect.Ports[0].HostIP, containerInspect.Ports[0].HostPort)
|
||||
t.Logf("Endpoint: %s", endpoint)
|
||||
|
||||
assert.Assert(t, !strings.Contains(runRes.Stdout(), "/test"))
|
||||
checkRequest := func(t poll.LogT) poll.Result {
|
||||
r, _ := http.Get(endpoint + "/test")
|
||||
if r != nil && r.StatusCode == http.StatusNotFound {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for container to serve request")
|
||||
}
|
||||
poll.WaitOn(t, checkRequest, poll.WithDelay(1*time.Second), poll.WithTimeout(60*time.Second))
|
||||
|
||||
checkLog := func(t poll.LogT) poll.Result {
|
||||
if strings.Contains(runRes.Stdout(), "/test") {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for logs to contain /test")
|
||||
}
|
||||
poll.WaitOn(t, checkLog, poll.WithDelay(1*time.Second), poll.WithTimeout(20*time.Second))
|
||||
})
|
||||
|
||||
t.Run("rm attached", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("rm", container)
|
||||
res.Assert(t, icmd.Expected{Out: container})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompose(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
_, _ = setupTestResourceGroup(t, c)
|
||||
|
||||
const (
|
||||
composeFile = "../composefiles/aci-demo/aci_demo_port.yaml"
|
||||
composeFileMultiplePorts = "../composefiles/aci-demo/aci_demo_multi_port.yaml"
|
||||
composeProjectName = "acie2e"
|
||||
serverContainer = composeProjectName + "_web"
|
||||
wordsContainer = composeProjectName + "_words"
|
||||
)
|
||||
|
||||
t.Run("compose up", func(t *testing.T) {
|
||||
// Name of Compose project is taken from current folder "acie2e"
|
||||
res := c.RunDockerCmd("compose", "up", "-f", composeFile)
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerCmd("ps")
|
||||
res.Assert(t, icmd.Success)
|
||||
out := strings.Split(strings.TrimSpace(res.Stdout()), "\n")
|
||||
// Check three containers are running
|
||||
assert.Assert(t, is.Len(out, 4))
|
||||
webRunning := false
|
||||
for _, l := range out {
|
||||
if strings.Contains(l, serverContainer) {
|
||||
webRunning = true
|
||||
strings.Contains(l, ":80->80/tcp")
|
||||
}
|
||||
}
|
||||
assert.Assert(t, webRunning, "web container not running")
|
||||
|
||||
Expect(webChecked).To(BeTrue())
|
||||
Expect(wordsChecked).To(BeTrue())
|
||||
res = c.RunDockerCmd("inspect", serverContainer)
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
containerInspect, err := ParseContainerInspect(res.Stdout())
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, is.Len(containerInspect.Ports, 1))
|
||||
endpoint := fmt.Sprintf("http://%s:%d", containerInspect.Ports[0].HostIP, containerInspect.Ports[0].HostPort)
|
||||
t.Logf("Endpoint: %s", endpoint)
|
||||
|
||||
r, err := http.Get(endpoint + "/words/noun")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, r.StatusCode, http.StatusOK)
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, strings.Contains(string(b), `"word":`))
|
||||
})
|
||||
|
||||
s.Step("shutdown compose app", func() {
|
||||
s.NewDockerCommand("compose", "down", "--project-name", composeProjectName).ExecOrDie()
|
||||
t.Run("logs web", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("logs", serverContainer)
|
||||
res.Assert(t, icmd.Expected{Out: "Listening on port 80"})
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "up", "-f", composeFileMultiplePorts, "--project-name", composeProjectName)
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerCmd("ps")
|
||||
res.Assert(t, icmd.Success)
|
||||
out := strings.Split(strings.TrimSpace(res.Stdout()), "\n")
|
||||
// Check three containers are running
|
||||
assert.Assert(t, is.Len(out, 4))
|
||||
|
||||
for _, cName := range []string{serverContainer, wordsContainer} {
|
||||
res = c.RunDockerCmd("inspect", cName)
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
containerInspect, err := ParseContainerInspect(res.Stdout())
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, is.Len(containerInspect.Ports, 1))
|
||||
endpoint := fmt.Sprintf("http://%s:%d", containerInspect.Ports[0].HostIP, containerInspect.Ports[0].HostPort)
|
||||
t.Logf("Endpoint: %s", endpoint)
|
||||
var route string
|
||||
switch cName {
|
||||
case serverContainer:
|
||||
route = "/words/noun"
|
||||
assert.Equal(t, containerInspect.Ports[0].HostPort, uint32(80))
|
||||
assert.Equal(t, containerInspect.Ports[0].ContainerPort, uint32(80))
|
||||
case wordsContainer:
|
||||
route = "/noun"
|
||||
assert.Equal(t, containerInspect.Ports[0].HostPort, uint32(8080))
|
||||
assert.Equal(t, containerInspect.Ports[0].ContainerPort, uint32(8080))
|
||||
}
|
||||
checkUp := func(t poll.LogT) poll.Result {
|
||||
r, _ := http.Get(endpoint + route)
|
||||
if r != nil && r.StatusCode == http.StatusOK {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("Waiting for container to serve request")
|
||||
}
|
||||
poll.WaitOn(t, checkUp, poll.WithDelay(1*time.Second), poll.WithTimeout(60*time.Second))
|
||||
|
||||
res = c.RunDockerCmd("ps")
|
||||
p := containerInspect.Ports[0]
|
||||
res.Assert(t, icmd.Expected{
|
||||
Out: fmt.Sprintf("%s:%d->%d/tcp", p.HostIP, p.HostPort, p.ContainerPort),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "down", "--project-name", composeProjectName)
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerCmd("ps")
|
||||
res.Assert(t, icmd.Success)
|
||||
out := strings.Split(strings.TrimSpace(res.Stdout()), "\n")
|
||||
assert.Equal(t, len(out), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *E2eACISuite) TestACIDeployMySQlwithEnvVars() {
|
||||
defer deleteResourceGroup(s.setupTestResourceGroup())
|
||||
func TestRunEnvVars(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
_, _ = setupTestResourceGroup(t, c)
|
||||
|
||||
s.Step("runs mysql with env variables", func() {
|
||||
err := os.Setenv("MYSQL_USER", "user1")
|
||||
Expect(err).To(BeNil())
|
||||
s.NewDockerCommand("run", "-d", "mysql:5.7", "-e", "MYSQL_ROOT_PASSWORD=rootpwd", "-e", "MYSQL_DATABASE=mytestdb", "-e", "MYSQL_USER", "-e", "MYSQL_PASSWORD=userpwd").ExecOrDie()
|
||||
t.Run("run", func(t *testing.T) {
|
||||
cmd := c.NewDockerCmd(
|
||||
"run", "-d",
|
||||
"-e", "MYSQL_ROOT_PASSWORD=rootpwd",
|
||||
"-e", "MYSQL_DATABASE=mytestdb",
|
||||
"-e", "MYSQL_USER",
|
||||
"-e", "MYSQL_PASSWORD=userpwd",
|
||||
"mysql:5.7",
|
||||
)
|
||||
cmd.Env = append(cmd.Env, "MYSQL_USER=user1")
|
||||
res := icmd.RunCmd(cmd)
|
||||
res.Assert(t, icmd.Success)
|
||||
out := strings.Split(strings.TrimSpace(res.Stdout()), "\n")
|
||||
container := strings.TrimSpace(out[len(out)-1])
|
||||
t.Logf("Container name: %s", container)
|
||||
|
||||
output := s.NewDockerCommand("ps").ExecOrDie()
|
||||
lines := Lines(output)
|
||||
Expect(len(lines)).To(Equal(2))
|
||||
res = c.RunDockerCmd("inspect", container)
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
containerFields := Columns(lines[1])
|
||||
containerID := containerFields[0]
|
||||
Expect(containerFields[1]).To(Equal("mysql:5.7"))
|
||||
Expect(containerFields[2]).To(Equal("Running"))
|
||||
containerInspect, err := ParseContainerInspect(res.Stdout())
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, containerInspect.Image, "mysql:5.7")
|
||||
|
||||
errs := make(chan error)
|
||||
err = WaitFor(time.Second, 100*time.Second, errs, func() bool {
|
||||
output = s.NewDockerCommand("logs", containerID).ExecOrDie()
|
||||
return strings.Contains(output, "Giving user user1 access to schema mytestdb")
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
|
||||
s.Step("switches back to default context", func() {
|
||||
output := s.NewCommand("docker", "context", "use", "default").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("default"))
|
||||
})
|
||||
|
||||
s.Step("deletes test context", func() {
|
||||
output := s.NewCommand("docker", "context", "rm", contextName).ExecOrDie()
|
||||
Expect(output).To(ContainSubstring(contextName))
|
||||
check := func(t poll.LogT) poll.Result {
|
||||
res := c.RunDockerCmd("logs", container)
|
||||
if strings.Contains(res.Stdout(), "Giving user user1 access to schema mytestdb") {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for DB container to be up")
|
||||
}
|
||||
poll.WaitOn(t, check, poll.WithDelay(5*time.Second), poll.WithTimeout(60*time.Second))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *E2eACISuite) setupTestResourceGroup() string {
|
||||
var resourceGroupName = randomResourceGroup()
|
||||
s.Step("should be initialized with default context", s.checkDefaultContext)
|
||||
s.Step("Logs in azure using service principal credentials", azureLogin)
|
||||
s.Step("creates a new aci context for tests and use it", s.createAciContextAndUseIt(resourceGroupName))
|
||||
s.Step("ensures no container is running initially", s.checkNoContainnersRunning)
|
||||
return resourceGroupName
|
||||
func setupTestResourceGroup(t *testing.T, c *E2eCLI) (string, string) {
|
||||
startTime := strconv.Itoa(int(time.Now().UnixNano()))
|
||||
name := "E2E-" + startTime
|
||||
azureLogin(t)
|
||||
sID := getSubscriptionID(t)
|
||||
t.Logf("Create resource group %q", name)
|
||||
err := createResourceGroup(sID, name)
|
||||
assert.Check(t, is.Nil(err))
|
||||
t.Cleanup(func() {
|
||||
if err := deleteResourceGroup(name); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
createAciContextAndUseIt(t, c, sID, name)
|
||||
// Check nothing is running
|
||||
res := c.RunDockerCmd("ps")
|
||||
res.Assert(t, icmd.Success)
|
||||
assert.Assert(t, is.Len(strings.Split(strings.TrimSpace(res.Stdout()), "\n"), 1))
|
||||
return sID, name
|
||||
}
|
||||
|
||||
func (s *E2eACISuite) checkDefaultContext() {
|
||||
output := s.NewCommand("docker", "context", "ls").ExecOrDie()
|
||||
Expect(output).To(Not(ContainSubstring(contextName)))
|
||||
Expect(output).To(ContainSubstring("default *"))
|
||||
func deleteResourceGroup(rgName string) error {
|
||||
ctx := context.TODO()
|
||||
helper := aci.NewACIResourceGroupHelper()
|
||||
models, err := helper.GetSubscriptionIDs(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(models) == 0 {
|
||||
return errors.New("unable to delete resource group: no models")
|
||||
}
|
||||
return helper.DeleteAsync(ctx, *models[0].SubscriptionID, rgName)
|
||||
}
|
||||
|
||||
func azureLogin() {
|
||||
func azureLogin(t *testing.T) {
|
||||
t.Log("Log in to Azure")
|
||||
login, err := login.NewAzureLoginService()
|
||||
Expect(err).To(BeNil())
|
||||
assert.NilError(t, err)
|
||||
|
||||
// in order to create new service principal and get these 3 values : `az ad sp create-for-rbac --name 'TestServicePrincipal' --sdk-auth`
|
||||
clientID := os.Getenv("AZURE_CLIENT_ID")
|
||||
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
|
||||
tenantID := os.Getenv("AZURE_TENANT_ID")
|
||||
err = login.TestLoginFromServicePrincipal(clientID, clientSecret, tenantID)
|
||||
Expect(err).To(BeNil())
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func (s *E2eACISuite) createAciContextAndUseIt(resourceGroupName string) func() {
|
||||
return func() {
|
||||
setupTestResourceGroup(resourceGroupName)
|
||||
helper := azure.NewACIResourceGroupHelper()
|
||||
models, err := helper.GetSubscriptionIDs(context.TODO())
|
||||
Expect(err).To(BeNil())
|
||||
subscriptionID = *models[0].SubscriptionID
|
||||
|
||||
s.NewDockerCommand("context", "create", "aci", contextName, "--subscription-id", subscriptionID, "--resource-group", resourceGroupName, "--location", location).ExecOrDie()
|
||||
|
||||
currentContext := s.NewCommand("docker", "context", "use", contextName).ExecOrDie()
|
||||
Expect(currentContext).To(ContainSubstring(contextName))
|
||||
output := s.NewCommand("docker", "context", "ls").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("acitest *"))
|
||||
}
|
||||
func getSubscriptionID(t *testing.T) string {
|
||||
ctx := context.TODO()
|
||||
helper := aci.NewACIResourceGroupHelper()
|
||||
models, err := helper.GetSubscriptionIDs(ctx)
|
||||
assert.Check(t, is.Nil(err))
|
||||
assert.Check(t, len(models) == 1)
|
||||
return *models[0].SubscriptionID
|
||||
}
|
||||
|
||||
func (s *E2eACISuite) checkNoContainnersRunning() {
|
||||
output := s.NewDockerCommand("ps").ExecOrDie()
|
||||
Expect(len(Lines(output))).To(Equal(1))
|
||||
func createResourceGroup(sID, rgName string) error {
|
||||
helper := aci.NewACIResourceGroupHelper()
|
||||
_, err := helper.CreateOrUpdate(context.TODO(), sID, rgName, resources.Group{Location: to.StringPtr(location)})
|
||||
return err
|
||||
}
|
||||
|
||||
func randomResourceGroup() string {
|
||||
return "resourceGroupTestE2E-" + RandStringBytes(10)
|
||||
func createAciContextAndUseIt(t *testing.T, c *E2eCLI, sID, rgName string) {
|
||||
t.Log("Create ACI context")
|
||||
res := c.RunDockerCmd("context", "create", "aci", contextName, "--subscription-id", sID, "--resource-group", rgName, "--location", location)
|
||||
res.Assert(t, icmd.Success)
|
||||
res = c.RunDockerCmd("context", "use", contextName)
|
||||
res.Assert(t, icmd.Expected{Out: contextName})
|
||||
res = c.RunDockerCmd("context", "ls")
|
||||
res.Assert(t, icmd.Expected{Out: contextName + " *"})
|
||||
}
|
||||
|
||||
func createStorageAccount(aciContext store.AciContext, accountName string) azure_storage.Account {
|
||||
log.Println("Creating storage account " + accountName)
|
||||
storageAccount, err := storage.CreateStorageAccount(context.TODO(), aciContext, accountName)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(*storageAccount.Name).To(Equal(accountName))
|
||||
return storageAccount
|
||||
func createStorageAccount(t *testing.T, aciContext store.AciContext, name string) (azure_storage.Account, func() error) {
|
||||
t.Logf("Create storage account %q", name)
|
||||
account, err := storage.CreateStorageAccount(context.TODO(), aciContext, name)
|
||||
assert.Check(t, is.Nil(err))
|
||||
assert.Check(t, is.Equal(*(account.Name), name))
|
||||
return account, func() error { return deleteStorageAccount(aciContext, name) }
|
||||
}
|
||||
|
||||
func getStorageKeys(aciContext store.AciContext, storageAccountName string) []azure_storage.AccountKey {
|
||||
list, err := storage.ListKeys(context.TODO(), aciContext, storageAccountName)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(list.Keys).ToNot(BeNil())
|
||||
Expect(len(*list.Keys)).To(BeNumerically(">", 0))
|
||||
|
||||
return *list.Keys
|
||||
func deleteStorageAccount(aciContext store.AciContext, name string) error {
|
||||
_, err := storage.DeleteStorageAccount(context.TODO(), aciContext, name)
|
||||
return err
|
||||
}
|
||||
|
||||
func deleteStorageAccount(aciContext store.AciContext, testStorageAccountName string) {
|
||||
log.Println("Deleting storage account " + testStorageAccountName)
|
||||
_, err := storage.DeleteStorageAccount(context.TODO(), aciContext, testStorageAccountName)
|
||||
Expect(err).To(BeNil())
|
||||
func getStorageKeys(t *testing.T, aciContext store.AciContext, saName string) []azure_storage.AccountKey {
|
||||
l, err := storage.ListKeys(context.TODO(), aciContext, saName)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, l.Keys != nil)
|
||||
return *l.Keys
|
||||
}
|
||||
|
||||
func createFileShare(key, shareName string, testStorageAccountName string) (azfile.SharedKeyCredential, url.URL) {
|
||||
func createFileShare(t *testing.T, key, share, storageAccount string) (*azfile.SharedKeyCredential, *url.URL) {
|
||||
// Create a ShareURL object that wraps a soon-to-be-created share's URL and a default pipeline.
|
||||
u, _ := url.Parse(fmt.Sprintf("https://%s.file.core.windows.net/%s", testStorageAccountName, shareName))
|
||||
credential, err := azfile.NewSharedKeyCredential(testStorageAccountName, key)
|
||||
Expect(err).To(BeNil())
|
||||
u, _ := url.Parse(fmt.Sprintf("https://%s.file.core.windows.net/%s", storageAccount, share))
|
||||
cred, err := azfile.NewSharedKeyCredential(storageAccount, key)
|
||||
assert.NilError(t, err)
|
||||
|
||||
shareURL := azfile.NewShareURL(*u, azfile.NewPipeline(credential, azfile.PipelineOptions{}))
|
||||
shareURL := azfile.NewShareURL(*u, azfile.NewPipeline(cred, azfile.PipelineOptions{}))
|
||||
_, err = shareURL.Create(context.TODO(), azfile.Metadata{}, 0)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
return *credential, *u
|
||||
assert.NilError(t, err)
|
||||
return cred, u
|
||||
}
|
||||
|
||||
func uploadFile(credential azfile.SharedKeyCredential, baseURL, fileName, fileContent string) {
|
||||
func uploadFile(t *testing.T, cred azfile.SharedKeyCredential, baseURL, fileName, content string) {
|
||||
fURL, err := url.Parse(baseURL + "/" + fileName)
|
||||
Expect(err).To(BeNil())
|
||||
fileURL := azfile.NewFileURL(*fURL, azfile.NewPipeline(&credential, azfile.PipelineOptions{}))
|
||||
err = azfile.UploadBufferToAzureFile(context.TODO(), []byte(fileContent), fileURL, azfile.UploadToAzureFileOptions{})
|
||||
Expect(err).To(BeNil())
|
||||
assert.NilError(t, err)
|
||||
fileURL := azfile.NewFileURL(*fURL, azfile.NewPipeline(&cred, azfile.PipelineOptions{}))
|
||||
err = azfile.UploadBufferToAzureFile(context.TODO(), []byte(content), fileURL, azfile.UploadToAzureFileOptions{})
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestE2eACI(t *testing.T) {
|
||||
suite.Run(t, new(E2eACISuite))
|
||||
}
|
||||
|
||||
func setupTestResourceGroup(resourceGroupName string) {
|
||||
log.Println("Creating resource group " + resourceGroupName)
|
||||
ctx := context.TODO()
|
||||
helper := azure.NewACIResourceGroupHelper()
|
||||
models, err := helper.GetSubscriptionIDs(ctx)
|
||||
Expect(err).To(BeNil())
|
||||
_, err = helper.CreateOrUpdate(ctx, *models[0].SubscriptionID, resourceGroupName, resources.Group{
|
||||
Location: to.StringPtr(location),
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
}
|
||||
|
||||
func deleteResourceGroup(resourceGroupName string) {
|
||||
log.Println("Deleting resource group " + resourceGroupName)
|
||||
ctx := context.TODO()
|
||||
helper := azure.NewACIResourceGroupHelper()
|
||||
models, err := helper.GetSubscriptionIDs(ctx)
|
||||
Expect(err).To(BeNil())
|
||||
err = helper.DeleteAsync(ctx, *models[0].SubscriptionID, resourceGroupName)
|
||||
Expect(err).To(BeNil())
|
||||
}
|
||||
|
||||
func RandStringBytes(n int) string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
const digits = "0123456789"
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = digits[rand.Intn(len(digits))]
|
||||
}
|
||||
return string(b)
|
||||
func getContainerName(stdout string) string {
|
||||
out := strings.Split(strings.TrimSpace(stdout), "\n")
|
||||
return strings.TrimSpace(out[len(out)-1])
|
||||
}
|
||||
|
|
|
@ -17,293 +17,444 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"gotest.tools/golden"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
"gotest.tools/v3/icmd"
|
||||
|
||||
. "github.com/docker/api/tests/framework"
|
||||
)
|
||||
|
||||
type E2eSuite struct {
|
||||
Suite
|
||||
}
|
||||
var binDir string
|
||||
|
||||
func (s *E2eSuite) TestContextHelp() {
|
||||
output := s.NewDockerCommand("context", "create", "aci", "--help").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("docker context create aci CONTEXT [flags]"))
|
||||
Expect(output).To(ContainSubstring("--location"))
|
||||
Expect(output).To(ContainSubstring("--subscription-id"))
|
||||
Expect(output).To(ContainSubstring("--resource-group"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestListAndShowDefaultContext() {
|
||||
output := s.NewDockerCommand("context", "show").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("default"))
|
||||
output = s.NewCommand("docker", "context", "ls").ExecOrDie()
|
||||
golden.Assert(s.T(), output, GoldenFile("ls-out-default"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestCreateDockerContextAndListIt() {
|
||||
s.NewDockerCommand("context", "create", "test-docker", "--from", "default").ExecOrDie()
|
||||
output := s.NewCommand("docker", "context", "ls").ExecOrDie()
|
||||
golden.Assert(s.T(), output, GoldenFile("ls-out-test-docker"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestContextListQuiet() {
|
||||
s.NewDockerCommand("context", "create", "test-docker", "--from", "default").ExecOrDie()
|
||||
output := s.NewCommand("docker", "context", "ls", "-q").ExecOrDie()
|
||||
Expect(output).To(Equal(`default
|
||||
test-docker
|
||||
`))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestInspectDefaultContext() {
|
||||
output := s.NewDockerCommand("context", "inspect", "default").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring(`"Name": "default"`))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestInspectContextNoArgs() {
|
||||
output := s.NewDockerCommand("context", "inspect").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring(`"Name": "default"`))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestInspectContextRegardlessCurrentContext() {
|
||||
s.NewDockerCommand("context", "create", "local", "localCtx").ExecOrDie()
|
||||
s.NewDockerCommand("context", "use", "localCtx").ExecOrDie()
|
||||
output := s.NewDockerCommand("context", "inspect").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring(`"Name": "localCtx"`))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestContextLsFormat() {
|
||||
output, err := s.NewDockerCommand("context", "ls", "--format", "{{ json . }}").Exec()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(output).To(ContainSubstring(`"Name":"default"`))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestComposeOnDefaultContext() {
|
||||
s.NewDockerCommand("context", "use", "default").ExecOrDie()
|
||||
output := s.NewDockerCommand("context", "inspect").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring(`"Name": "default"`))
|
||||
output, err := s.NewDockerCommand("compose", "up").Exec()
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(output).To(ContainSubstring(`compose command not supported on context type`))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestContextCreateParseErrorDoesNotDelegateToLegacy() {
|
||||
s.Step("should dispay new cli error when parsing context create flags", func() {
|
||||
_, err := s.NewDockerCommand("context", "create", "aci", "--subscription-id", "titi").Exec()
|
||||
Expect(err.Error()).NotTo(ContainSubstring("unknown flag"))
|
||||
Expect(err.Error()).To(ContainSubstring("accepts 1 arg(s), received 0"))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestCannotRemoveCurrentContext() {
|
||||
s.NewDockerCommand("context", "create", "test-context-rm", "--from", "default").ExecOrDie()
|
||||
s.NewDockerCommand("context", "use", "test-context-rm").ExecOrDie()
|
||||
_, err := s.NewDockerCommand("context", "rm", "test-context-rm").Exec()
|
||||
Expect(err.Error()).To(ContainSubstring("cannot delete current context"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestCanForceRemoveCurrentContext() {
|
||||
s.NewDockerCommand("context", "create", "test-context-rmf", "--from", "default").ExecOrDie()
|
||||
s.NewDockerCommand("context", "use", "test-context-rmf").ExecOrDie()
|
||||
s.NewDockerCommand("context", "rm", "-f", "test-context-rmf").ExecOrDie()
|
||||
out := s.NewDockerCommand("context", "ls").ExecOrDie()
|
||||
Expect(out).To(ContainSubstring("default *"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestContextCreateAciChecksContextNameBeforeInteractivePart() {
|
||||
s.NewDockerCommand("context", "create", "mycontext", "--from", "default").ExecOrDie()
|
||||
_, err := s.NewDockerCommand("context", "create", "aci", "mycontext").Exec()
|
||||
Expect(err.Error()).To(ContainSubstring("context mycontext: already exists"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestClassicLoginWithparameters() {
|
||||
output, err := s.NewDockerCommand("login", "-u", "nouser", "-p", "wrongpasword").Exec()
|
||||
Expect(output).To(ContainSubstring("Get https://registry-1.docker.io/v2/: unauthorized: incorrect username or password"))
|
||||
Expect(err).NotTo(BeNil())
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestClassicLoginRegardlessCurrentContext() {
|
||||
s.NewDockerCommand("context", "create", "local", "localCtx").ExecOrDie()
|
||||
s.NewDockerCommand("context", "use", "localCtx").ExecOrDie()
|
||||
output, err := s.NewDockerCommand("login", "-u", "nouser", "-p", "wrongpasword").Exec()
|
||||
Expect(output).To(ContainSubstring("Get https://registry-1.docker.io/v2/: unauthorized: incorrect username or password"))
|
||||
Expect(err).NotTo(BeNil())
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestClassicLogin() {
|
||||
output, err := s.NewDockerCommand("login", "someregistry.docker.io").Exec()
|
||||
Expect(output).To(ContainSubstring("Cannot perform an interactive login from a non TTY device"))
|
||||
Expect(err).NotTo(BeNil())
|
||||
output, err = s.NewDockerCommand("logout", "someregistry.docker.io").Exec()
|
||||
Expect(output).To(ContainSubstring("someregistry.docker.io"))
|
||||
Expect(err).To(BeNil())
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestCloudLogin() {
|
||||
output, err := s.NewDockerCommand("login", "mycloudbackend").Exec()
|
||||
Expect(output).To(ContainSubstring("unknown backend type for cloud login: mycloudbackend"))
|
||||
Expect(err).NotTo(BeNil())
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestSetupError() {
|
||||
s.Step("should display an error if cannot shell out to com.docker.cli", func() {
|
||||
err := os.Setenv("PATH", s.BinDir)
|
||||
Expect(err).To(BeNil())
|
||||
err = os.Remove(filepath.Join(s.BinDir, DockerClassicExecutable()))
|
||||
Expect(err).To(BeNil())
|
||||
output, err := s.NewDockerCommand("ps").Exec()
|
||||
Expect(output).To(ContainSubstring("com.docker.cli"))
|
||||
Expect(output).To(ContainSubstring("not found"))
|
||||
Expect(err).NotTo(BeNil())
|
||||
})
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestLegacy() {
|
||||
s.Step("should list all legacy commands", func() {
|
||||
output := s.NewDockerCommand("--help").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("swarm"))
|
||||
})
|
||||
|
||||
s.Step("should execute legacy commands", func() {
|
||||
output, _ := s.NewDockerCommand("swarm", "join").Exec()
|
||||
Expect(output).To(ContainSubstring("\"docker swarm join\" requires exactly 1 argument."))
|
||||
})
|
||||
|
||||
s.Step("should run local container in less than 10 secs", func() {
|
||||
s.NewDockerCommand("pull", "hello-world").ExecOrDie()
|
||||
output := s.NewDockerCommand("run", "--rm", "hello-world").WithTimeout(time.NewTimer(20 * time.Second).C).ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("Hello from Docker!"))
|
||||
})
|
||||
|
||||
s.Step("should execute legacy commands in other moby contexts", func() {
|
||||
s.NewDockerCommand("context", "create", "mobyCtx", "--from=default").ExecOrDie()
|
||||
s.NewDockerCommand("context", "use", "mobyCtx").ExecOrDie()
|
||||
output, _ := s.NewDockerCommand("swarm", "join").Exec()
|
||||
Expect(output).To(ContainSubstring("\"docker swarm join\" requires exactly 1 argument."))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestLeaveLegacyErrorMessagesUnchanged() {
|
||||
output, err := s.NewDockerCommand("foo").Exec()
|
||||
golden.Assert(s.T(), output, "unknown-foo-command.golden")
|
||||
Expect(err).NotTo(BeNil())
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestPassThroughRootLegacyFlags() {
|
||||
output, err := s.NewDockerCommand("-H", "tcp://localhost:123", "version").Exec()
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(output).NotTo(ContainSubstring("unknown shorthand flag"))
|
||||
Expect(output).To(ContainSubstring("localhost:123"))
|
||||
|
||||
output, _ = s.NewDockerCommand("-H", "tcp://localhost:123", "login", "-u", "nouser", "-p", "wrongpasword").Exec()
|
||||
Expect(output).NotTo(ContainSubstring("unknown shorthand flag"))
|
||||
Expect(output).To(ContainSubstring("WARNING! Using --password via the CLI is insecure"))
|
||||
|
||||
output, _ = s.NewDockerCommand("--log-level", "debug", "login", "-u", "nouser", "-p", "wrongpasword").Exec()
|
||||
Expect(output).NotTo(ContainSubstring("unknown shorthand flag"))
|
||||
Expect(output).To(ContainSubstring("WARNING! Using --password via the CLI is insecure"))
|
||||
|
||||
output, _ = s.NewDockerCommand("login", "--help").Exec()
|
||||
Expect(output).NotTo(ContainSubstring("--log-level"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestDisplayFriendlyErrorMessageForLegacyCommands() {
|
||||
s.NewDockerCommand("context", "create", "example", "test-example").ExecOrDie()
|
||||
output, err := s.NewDockerCommand("--context", "test-example", "images").Exec()
|
||||
Expect(output).To(Equal("Command \"images\" not available in current context (test-example), you can use the \"default\" context to run this command\n"))
|
||||
Expect(err).NotTo(BeNil())
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestExecMobyIfUsingHostFlag() {
|
||||
s.NewDockerCommand("context", "create", "example", "test-example").ExecOrDie()
|
||||
s.NewDockerCommand("context", "use", "test-example").ExecOrDie()
|
||||
output, err := s.NewDockerCommand("-H", defaultEndpoint(), "ps").Exec()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(output).To(ContainSubstring("CONTAINER ID"))
|
||||
}
|
||||
|
||||
func defaultEndpoint() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return "npipe:////./pipe/docker_engine"
|
||||
func TestMain(m *testing.M) {
|
||||
p, cleanup, err := SetupExistingCLI()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return "unix:///var/run/docker.sock"
|
||||
binDir = p
|
||||
exitCode := m.Run()
|
||||
cleanup()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestExecMobyIfUsingversionFlag() {
|
||||
s.NewDockerCommand("context", "create", "example", "test-example").ExecOrDie()
|
||||
s.NewDockerCommand("context", "use", "test-example").ExecOrDie()
|
||||
output, err := s.NewDockerCommand("-v").Exec()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(output).To(ContainSubstring("Docker version"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestDisplaysAdditionalLineInDockerVersion() {
|
||||
output := s.NewDockerCommand("version").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("Azure integration"))
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestAllowsFormatFlagInVersion() {
|
||||
s.NewDockerCommand("version", "-f", "{{ json . }}").ExecOrDie()
|
||||
s.NewDockerCommand("version", "--format", "{{ json . }}").ExecOrDie()
|
||||
}
|
||||
|
||||
func (s *E2eSuite) TestMockBackend() {
|
||||
s.Step("creates a new test context to hardcoded example backend", func() {
|
||||
s.NewDockerCommand("context", "create", "example", "test-example").ExecOrDie()
|
||||
// Expect(output).To(ContainSubstring("test-example context acitest created"))
|
||||
})
|
||||
|
||||
s.Step("uses the test context", func() {
|
||||
currentContext := s.NewDockerCommand("context", "use", "test-example").ExecOrDie()
|
||||
Expect(currentContext).To(ContainSubstring("test-example"))
|
||||
output := s.NewDockerCommand("context", "ls").ExecOrDie()
|
||||
golden.Assert(s.T(), output, GoldenFile("ls-out-test-example"))
|
||||
output = s.NewDockerCommand("context", "show").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("test-example"))
|
||||
})
|
||||
|
||||
s.Step("can run ps command", func() {
|
||||
output := s.NewDockerCommand("ps").ExecOrDie()
|
||||
lines := Lines(output)
|
||||
Expect(len(lines)).To(Equal(3))
|
||||
Expect(lines[2]).To(ContainSubstring("1234 alpine"))
|
||||
})
|
||||
|
||||
s.Step("can run quiet ps command", func() {
|
||||
output := s.NewDockerCommand("ps", "-q").ExecOrDie()
|
||||
lines := Lines(output)
|
||||
Expect(len(lines)).To(Equal(2))
|
||||
Expect(lines[0]).To(Equal("id"))
|
||||
Expect(lines[1]).To(Equal("1234"))
|
||||
})
|
||||
|
||||
s.Step("can run ps command with all ", func() {
|
||||
output := s.NewDockerCommand("ps", "-q", "--all").ExecOrDie()
|
||||
lines := Lines(output)
|
||||
Expect(len(lines)).To(Equal(3))
|
||||
Expect(lines[0]).To(Equal("id"))
|
||||
Expect(lines[1]).To(Equal("1234"))
|
||||
Expect(lines[2]).To(Equal("stopped"))
|
||||
})
|
||||
|
||||
s.Step("can run inspect command on container", func() {
|
||||
golden.Assert(s.T(), s.NewDockerCommand("inspect", "id").ExecOrDie(), "inspect-id.golden")
|
||||
})
|
||||
|
||||
s.Step("can run 'run' command", func() {
|
||||
output := s.NewDockerCommand("run", "-d", "nginx", "-p", "80:80").ExecOrDie()
|
||||
Expect(output).To(ContainSubstring("Running container \"nginx\" with name"))
|
||||
func TestComposeNotImplemented(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
res := c.RunDockerCmd("context", "show")
|
||||
res.Assert(t, icmd.Expected{Out: "default"})
|
||||
res = c.RunDockerCmd("compose", "up")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: `compose command not supported on context type "moby": not implemented`,
|
||||
})
|
||||
}
|
||||
|
||||
func TestE2e(t *testing.T) {
|
||||
suite.Run(t, new(E2eSuite))
|
||||
func TestContextDefault(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
t.Run("show", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("context", "show")
|
||||
res.Assert(t, icmd.Expected{Out: "default"})
|
||||
})
|
||||
|
||||
t.Run("ls", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("context", "ls")
|
||||
res.Assert(t, icmd.Success)
|
||||
golden.Assert(t, res.Stdout(), GoldenFile("ls-out-default"))
|
||||
})
|
||||
|
||||
t.Run("inspect", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("context", "inspect", "default")
|
||||
res.Assert(t, icmd.Expected{Out: `"Name": "default"`})
|
||||
})
|
||||
|
||||
t.Run("inspect current", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("context", "ls")
|
||||
res.Assert(t, icmd.Success)
|
||||
golden.Assert(t, res.Stdout(), GoldenFile("ls-out-test-docker"))
|
||||
})
|
||||
|
||||
t.Run("ls quiet", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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) {
|
||||
t.Parallel()
|
||||
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) {
|
||||
t.Parallel()
|
||||
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) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("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 TestContextDuplicateACI(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
c.RunDockerCmd("context", "create", "mycontext", "--from", "default").Assert(t, icmd.Success)
|
||||
res := c.RunDockerCmd("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").Assert(t, icmd.Success)
|
||||
res := c.RunDockerCmd("context", "use", "test-context-rm")
|
||||
res.Assert(t, icmd.Expected{Out: "test-context-rm"})
|
||||
res = c.RunDockerCmd("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").Assert(t, icmd.Success)
|
||||
c.RunDockerCmd("context", "use", "test-context-rmf").Assert(t, icmd.Success)
|
||||
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) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("login", "-u", "nouser", "-p", "wrongpasword")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "Get https://registry-1.docker.io/v2/: unauthorized: incorrect username or password",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("interactive", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("login", "someregistry.docker.io")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "Cannot perform an interactive login from a non TTY device",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("logout", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("logout", "someregistry.docker.io")
|
||||
res.Assert(t, icmd.Expected{Out: "someregistry.docker.io"})
|
||||
})
|
||||
|
||||
t.Run("existing context", func(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c.RunDockerCmd("context", "create", "local", "local").Assert(t, icmd.Success)
|
||||
c.RunDockerCmd("context", "use", "local").Assert(t, icmd.Success)
|
||||
res := c.RunDockerCmd("login", "-u", "nouser", "-p", "wrongpasword")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "Get https://registry-1.docker.io/v2/: unauthorized: incorrect username or password",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloudLogin(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
t.Run("unknown backend", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("login", "mycloudbackend")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "unknown backend type for cloud login: mycloudbackend",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
c := icmd.Cmd{
|
||||
Env: []string{"HOME=" + home, "PATH=" + bin},
|
||||
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) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("--help")
|
||||
res.Assert(t, icmd.Expected{Out: "swarm"})
|
||||
})
|
||||
|
||||
t.Run("swarm", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("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) {
|
||||
t.Parallel()
|
||||
cmd := c.NewDockerCmd("run", "--rm", "hello-world")
|
||||
cmd.Timeout = 20 * time.Second
|
||||
res := icmd.RunCmd(cmd)
|
||||
res.Assert(t, icmd.Expected{Out: "Hello from Docker!"})
|
||||
})
|
||||
|
||||
t.Run("error messages", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("foo")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "docker: 'foo' is not a docker command.",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("host flag", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("-H", "tcp://localhost:123", "version")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "Cannot connect to the Docker daemon at tcp://localhost:123",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("existing contexts delegate", func(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c.RunDockerCmd("context", "create", "moby-ctx", "--from=default").Assert(t, icmd.Success)
|
||||
c.RunDockerCmd("context", "use", "moby-ctx").Assert(t, icmd.Success)
|
||||
res := c.RunDockerCmd("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 := NewParallelE2eCLI(t, binDir)
|
||||
c.RunDockerCmd("context", "create", "example", "test-example").Assert(t, icmd.Success)
|
||||
c.RunDockerCmd("context", "use", "test-example").Assert(t, icmd.Success)
|
||||
endpoint := "unix:///var/run/docker.sock"
|
||||
if runtime.GOOS == "windows" {
|
||||
endpoint = "npipe:////./pipe/docker_engine"
|
||||
}
|
||||
res := c.RunDockerCmd("-H", endpoint, "ps")
|
||||
res.Assert(t, icmd.Success)
|
||||
// Example backend's ps output includes these strings
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "id"))
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "1234"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestLegacyLogin(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
t.Run("host flag login", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("-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) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("--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) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("login", "--help")
|
||||
res.Assert(t, icmd.Success)
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "--log-level"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnsupportedCommand(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
res := c.RunDockerCmd("context", "create", "example", "test-example")
|
||||
res.Assert(t, icmd.Success)
|
||||
res = c.RunDockerCmd("--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) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("version")
|
||||
res.Assert(t, icmd.Expected{Out: "Azure integration"})
|
||||
})
|
||||
|
||||
t.Run("format", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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("delegate version flag", func(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c.RunDockerCmd("context", "create", "example", "test-example").Assert(t, icmd.Success)
|
||||
c.RunDockerCmd("context", "use", "test-example").Assert(t, icmd.Success)
|
||||
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").Assert(t, icmd.Success)
|
||||
res := c.RunDockerCmd("context", "use", "test-example")
|
||||
res.Assert(t, icmd.Expected{Out: "test-example"})
|
||||
|
||||
t.Run("use", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("ps")
|
||||
res.Assert(t, icmd.Success)
|
||||
golden.Assert(t, res.Stdout(), "ps-out-example.golden")
|
||||
})
|
||||
|
||||
t.Run("ps quiet", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("ps", "-q")
|
||||
res.Assert(t, icmd.Success)
|
||||
golden.Assert(t, res.Stdout(), "ps-quiet-out-example.golden")
|
||||
})
|
||||
|
||||
t.Run("ps quiet all", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("ps", "-q", "--all")
|
||||
res.Assert(t, icmd.Success)
|
||||
golden.Assert(t, res.Stdout(), "ps-quiet-all-out-example.golden")
|
||||
})
|
||||
|
||||
t.Run("inspect", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("inspect", "id")
|
||||
res.Assert(t, icmd.Success)
|
||||
golden.Assert(t, res.Stdout(), "inspect-id.golden")
|
||||
})
|
||||
|
||||
t.Run("run", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res := c.RunDockerCmd("run", "-d", "nginx", "-p", "80:80")
|
||||
res.Assert(t, icmd.Expected{
|
||||
Out: `Running container "nginx" with name`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
default
|
||||
test-docker
|
|
@ -0,0 +1,3 @@
|
|||
CONTAINER ID IMAGE COMMAND STATUS PORTS
|
||||
id nginx
|
||||
1234 alpine
|
|
@ -0,0 +1,3 @@
|
|||
id
|
||||
1234
|
||||
stopped
|
|
@ -0,0 +1,2 @@
|
|||
id
|
||||
1234
|
|
@ -1,2 +0,0 @@
|
|||
docker: 'foo' is not a docker command.
|
||||
See 'docker --help'
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
Copyright 2020 Docker, Inc.
|
||||
|
||||
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 framework
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/icmd"
|
||||
|
||||
"github.com/docker/api/containers"
|
||||
)
|
||||
|
||||
var (
|
||||
// DockerExecutableName is the OS dependent Docker CLI binary name
|
||||
DockerExecutableName = "docker"
|
||||
existingExectuableName = "com.docker.cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
DockerExecutableName = DockerExecutableName + ".exe"
|
||||
existingExectuableName = existingExectuableName + ".exe"
|
||||
}
|
||||
}
|
||||
|
||||
// E2eCLI is used to wrap the CLI for end to end testing
|
||||
type E2eCLI struct {
|
||||
BinDir string
|
||||
ConfigDir string
|
||||
}
|
||||
|
||||
// NewParallelE2eCLI returns a configured TestE2eCLI with t.Parallel() set
|
||||
func NewParallelE2eCLI(t *testing.T, binDir string) *E2eCLI {
|
||||
t.Parallel()
|
||||
return newE2eCLI(t, binDir)
|
||||
}
|
||||
|
||||
// NewE2eCLI returns a configured TestE2eCLI
|
||||
func NewE2eCLI(t *testing.T, binDir string) *E2eCLI {
|
||||
return newE2eCLI(t, binDir)
|
||||
}
|
||||
|
||||
func newE2eCLI(t *testing.T, binDir string) *E2eCLI {
|
||||
d, err := ioutil.TempDir("", "")
|
||||
assert.Check(t, is.Nil(err))
|
||||
|
||||
t.Cleanup(func() {
|
||||
if t.Failed() {
|
||||
conf, _ := ioutil.ReadFile(filepath.Join(d, "config.json"))
|
||||
t.Errorf("Config: %s\n", string(conf))
|
||||
t.Error("Contents of config dir:")
|
||||
for _, p := range dirContents(d) {
|
||||
t.Errorf(p)
|
||||
}
|
||||
}
|
||||
_ = os.RemoveAll(d)
|
||||
})
|
||||
|
||||
return &E2eCLI{binDir, d}
|
||||
}
|
||||
|
||||
func dirContents(dir string) []string {
|
||||
res := []string{}
|
||||
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
res = append(res, filepath.Join(dir, path))
|
||||
return nil
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// SetupExistingCLI copies the existing CLI in a temporary directory so that the
|
||||
// new CLI can be configured to use it
|
||||
func SetupExistingCLI() (string, func(), error) {
|
||||
p, err := exec.LookPath(existingExectuableName)
|
||||
if err != nil {
|
||||
p, err = exec.LookPath(DockerExecutableName)
|
||||
if err != nil {
|
||||
return "", nil, errors.New("existing CLI not found in PATH")
|
||||
}
|
||||
}
|
||||
d, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := CopyFile(p, filepath.Join(d, existingExectuableName)); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
bin, err := filepath.Abs("../../bin/" + DockerExecutableName)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := CopyFile(bin, filepath.Join(d, DockerExecutableName)); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
cleanup := func() {
|
||||
_ = os.RemoveAll(d)
|
||||
}
|
||||
return d, cleanup, nil
|
||||
}
|
||||
|
||||
// CopyFile copies a file from a path to a path setting permissions to 0777
|
||||
func CopyFile(sourceFile string, destinationFile string) error {
|
||||
input, err := ioutil.ReadFile(sourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(destinationFile, input, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewCmd creates a cmd object configured with the test environment set
|
||||
func (c *E2eCLI) NewCmd(command string, args ...string) icmd.Cmd {
|
||||
path := c.BinDir + ":" + os.Getenv("PATH")
|
||||
if runtime.GOOS == "windows" {
|
||||
path = c.BinDir + ";" + os.Getenv("PATH")
|
||||
}
|
||||
env := append(os.Environ(),
|
||||
"DOCKER_CONFIG="+c.ConfigDir,
|
||||
"KUBECONFIG=invalid",
|
||||
"PATH="+path,
|
||||
)
|
||||
return icmd.Cmd{
|
||||
Command: append([]string{command}, args...),
|
||||
Env: env,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDockerCmd creates a docker cmd without running it
|
||||
func (c *E2eCLI) NewDockerCmd(args ...string) icmd.Cmd {
|
||||
return c.NewCmd(filepath.Join(c.BinDir, DockerExecutableName), args...)
|
||||
}
|
||||
|
||||
// RunDockerCmd runs a docker command and returns a result
|
||||
func (c *E2eCLI) RunDockerCmd(args ...string) *icmd.Result {
|
||||
return icmd.RunCmd(c.NewDockerCmd(args...))
|
||||
}
|
||||
|
||||
// GoldenFile golden file specific to platform
|
||||
func GoldenFile(name string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return name + "-windows.golden"
|
||||
}
|
||||
return name + ".golden"
|
||||
}
|
||||
|
||||
// ParseContainerInspect parses the output of a `docker inspect` command for a
|
||||
// container
|
||||
func ParseContainerInspect(stdout string) (*containers.Container, error) {
|
||||
var res containers.Container
|
||||
rdr := bytes.NewReader([]byte(stdout))
|
||||
if err := json.NewDecoder(rdr).Decode(&res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 Docker, Inc.
|
||||
|
||||
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 framework
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (b CmdContext) makeCmd() *exec.Cmd {
|
||||
return exec.Command(b.command, b.args...)
|
||||
}
|
||||
|
||||
// CmdContext is used to build, customize and execute a command.
|
||||
// Add more functions to customize the context as needed.
|
||||
type CmdContext struct {
|
||||
command string
|
||||
args []string
|
||||
envs []string
|
||||
dir string
|
||||
stdin io.Reader
|
||||
timeout <-chan time.Time
|
||||
retries RetriesContext
|
||||
}
|
||||
|
||||
// RetriesContext is used to tweak retry loop.
|
||||
type RetriesContext struct {
|
||||
count int
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
// WithinDirectory tells Docker the cwd.
|
||||
func (b *CmdContext) WithinDirectory(path string) *CmdContext {
|
||||
b.dir = path
|
||||
return b
|
||||
}
|
||||
|
||||
// WithEnvs set envs in context.
|
||||
func (b *CmdContext) WithEnvs(envs []string) *CmdContext {
|
||||
b.envs = envs
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTimeout controls maximum duration.
|
||||
func (b *CmdContext) WithTimeout(t <-chan time.Time) *CmdContext {
|
||||
b.timeout = t
|
||||
return b
|
||||
}
|
||||
|
||||
// WithRetries sets how many times to retry the command before issuing an error
|
||||
func (b *CmdContext) WithRetries(count int) *CmdContext {
|
||||
b.retries.count = count
|
||||
return b
|
||||
}
|
||||
|
||||
// Every interval between 2 retries
|
||||
func (b *CmdContext) Every(interval time.Duration) *CmdContext {
|
||||
b.retries.interval = interval
|
||||
return b
|
||||
}
|
||||
|
||||
// WithStdinData feeds via stdin.
|
||||
func (b CmdContext) WithStdinData(data string) *CmdContext {
|
||||
b.stdin = strings.NewReader(data)
|
||||
return &b
|
||||
}
|
||||
|
||||
// WithStdinReader feeds via stdin.
|
||||
func (b CmdContext) WithStdinReader(reader io.Reader) *CmdContext {
|
||||
b.stdin = reader
|
||||
return &b
|
||||
}
|
||||
|
||||
// ExecOrDie runs a docker command.
|
||||
func (b CmdContext) ExecOrDie() string {
|
||||
str, err := b.Exec()
|
||||
logrus.Debugf("stdout: %s", str)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
return str
|
||||
}
|
||||
|
||||
// Exec runs a docker command.
|
||||
func (b CmdContext) Exec() (string, error) {
|
||||
retry := b.retries.count
|
||||
for ; ; retry-- {
|
||||
cmd := b.makeCmd()
|
||||
cmd.Dir = b.dir
|
||||
cmd.Stdin = b.stdin
|
||||
if b.envs != nil {
|
||||
cmd.Env = b.envs
|
||||
}
|
||||
stdout, err := Execute(cmd, b.timeout)
|
||||
if err == nil || retry < 1 {
|
||||
return stdout, err
|
||||
}
|
||||
time.Sleep(b.retries.interval)
|
||||
}
|
||||
}
|
||||
|
||||
//WaitFor waits for a condition to be true
|
||||
func WaitFor(interval, duration time.Duration, abort <-chan error, condition func() bool) error {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
timeout := make(chan int)
|
||||
go func() {
|
||||
time.Sleep(duration)
|
||||
close(timeout)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case err := <-abort:
|
||||
return err
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timeout after %v", duration)
|
||||
case <-ticker.C:
|
||||
if condition() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute executes a command.
|
||||
// The command cannot be re-used afterwards.
|
||||
func Execute(cmd *exec.Cmd, timeout <-chan time.Time) (string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = mergeWriter(cmd.Stdout, &stdout)
|
||||
cmd.Stderr = mergeWriter(cmd.Stderr, &stderr)
|
||||
|
||||
logrus.Infof("Execute '%s %s'", cmd.Path, strings.Join(cmd.Args[1:], " ")) // skip arg[0] as it is printed separately
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", fmt.Errorf("error starting %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, stdout.String(), stderr.String(), err)
|
||||
}
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- cmd.Wait()
|
||||
}()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
logrus.Debugf("%s %s failed: %v", cmd.Path, strings.Join(cmd.Args[1:], " "), err)
|
||||
return stderr.String(), fmt.Errorf("error running %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, stdout.String(), stderr.String(), err)
|
||||
}
|
||||
case <-timeout:
|
||||
logrus.Debugf("%s %s timed-out", cmd.Path, strings.Join(cmd.Args[1:], " "))
|
||||
if err := terminateProcess(cmd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return stdout.String(), fmt.Errorf(
|
||||
"timed out waiting for command %v:\nCommand stdout:\n%v\nstderr:\n%v",
|
||||
cmd.Args, stdout.String(), stderr.String())
|
||||
}
|
||||
if stderr.String() != "" {
|
||||
logrus.Debugf("stderr: %s", stderr.String())
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
func terminateProcess(cmd *exec.Cmd) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
return cmd.Process.Kill()
|
||||
}
|
||||
return cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
|
||||
func mergeWriter(other io.Writer, buf io.Writer) io.Writer {
|
||||
if other != nil {
|
||||
return io.MultiWriter(other, buf)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// Powershell runs a powershell command.
|
||||
func Powershell(input string) (string, error) {
|
||||
output, err := Execute(exec.Command("powershell", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Unrestricted", "-Command", input), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fail to execute %s: %s", input, err)
|
||||
}
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 Docker, Inc.
|
||||
|
||||
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 framework
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/robpike/filter"
|
||||
)
|
||||
|
||||
func nonEmptyString(s string) bool {
|
||||
return strings.TrimSpace(s) != ""
|
||||
}
|
||||
|
||||
// Lines get lines from a raw string
|
||||
func Lines(output string) []string {
|
||||
return filter.Choose(strings.Split(output, "\n"), nonEmptyString).([]string)
|
||||
}
|
||||
|
||||
// Columns get columns from a line
|
||||
func Columns(line string) []string {
|
||||
return filter.Choose(strings.Split(line, " "), nonEmptyString).([]string)
|
||||
}
|
||||
|
||||
// GoldenFile golden file specific to platform
|
||||
func GoldenFile(name string) string {
|
||||
if IsWindows() {
|
||||
return name + "-windows.golden"
|
||||
}
|
||||
return name + ".golden"
|
||||
}
|
||||
|
||||
// IsWindows windows or other GOOS
|
||||
func IsWindows() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 Docker, Inc.
|
||||
|
||||
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 framework
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
// Suite is used to store context information for e2e tests
|
||||
type Suite struct {
|
||||
suite.Suite
|
||||
ConfigDir string
|
||||
BinDir string
|
||||
}
|
||||
|
||||
// SetupSuite is run before running any tests
|
||||
func (s *Suite) SetupSuite() {
|
||||
d, _ := ioutil.TempDir("", "")
|
||||
s.BinDir = d
|
||||
gomega.RegisterFailHandler(func(message string, callerSkip ...int) {
|
||||
log.Error(message)
|
||||
cp := filepath.Join(s.ConfigDir, "config.json")
|
||||
d, _ := ioutil.ReadFile(cp)
|
||||
fmt.Printf("Bin dir:%s\n", s.BinDir)
|
||||
fmt.Printf("Contents of %s:\n%s\n\nContents of config dir:\n", cp, string(d))
|
||||
for _, p := range dirContents(s.ConfigDir) {
|
||||
fmt.Println(p)
|
||||
}
|
||||
s.T().Fail()
|
||||
})
|
||||
s.copyExecutablesInBinDir()
|
||||
}
|
||||
|
||||
// TearDownSuite is run after all tests
|
||||
func (s *Suite) TearDownSuite() {
|
||||
_ = os.RemoveAll(s.BinDir)
|
||||
}
|
||||
|
||||
func dirContents(dir string) []string {
|
||||
res := []string{}
|
||||
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
res = append(res, filepath.Join(dir, path))
|
||||
return nil
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Suite) copyExecutablesInBinDir() {
|
||||
p, err := exec.LookPath(DockerClassicExecutable())
|
||||
if err != nil {
|
||||
p, err = exec.LookPath(dockerExecutable())
|
||||
}
|
||||
gomega.Expect(err).To(gomega.BeNil())
|
||||
err = copyFile(p, filepath.Join(s.BinDir, DockerClassicExecutable()))
|
||||
gomega.Expect(err).To(gomega.BeNil())
|
||||
dockerPath, err := filepath.Abs("../../bin/" + dockerExecutable())
|
||||
gomega.Expect(err).To(gomega.BeNil())
|
||||
err = copyFile(dockerPath, filepath.Join(s.BinDir, dockerExecutable()))
|
||||
gomega.Expect(err).To(gomega.BeNil())
|
||||
err = os.Setenv("PATH", concatenatePath(s.BinDir))
|
||||
gomega.Expect(err).To(gomega.BeNil())
|
||||
}
|
||||
|
||||
func concatenatePath(path string) string {
|
||||
if IsWindows() {
|
||||
return fmt.Sprintf("%s;%s", path, os.Getenv("PATH"))
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", path, os.Getenv("PATH"))
|
||||
}
|
||||
|
||||
func copyFile(sourceFile string, destinationFile string) error {
|
||||
input, err := ioutil.ReadFile(sourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(destinationFile, input, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeTest is run before each test
|
||||
func (s *Suite) BeforeTest(suite, test string) {
|
||||
d, _ := ioutil.TempDir("", "")
|
||||
s.ConfigDir = d
|
||||
_ = os.Setenv("DOCKER_CONFIG", s.ConfigDir)
|
||||
}
|
||||
|
||||
// AfterTest is run after each test
|
||||
func (s *Suite) AfterTest(suite, test string) {
|
||||
_ = os.RemoveAll(s.ConfigDir)
|
||||
}
|
||||
|
||||
// ListProcessesCommand creates a command to list processes, "tasklist" on windows, "ps" otherwise.
|
||||
func (s *Suite) ListProcessesCommand() *CmdContext {
|
||||
if IsWindows() {
|
||||
return s.NewCommand("tasklist")
|
||||
}
|
||||
return s.NewCommand("ps", "-x")
|
||||
}
|
||||
|
||||
// NewCommand creates a command context.
|
||||
func (s *Suite) NewCommand(command string, args ...string) *CmdContext {
|
||||
return &CmdContext{
|
||||
command: command,
|
||||
args: args,
|
||||
retries: RetriesContext{interval: time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
// Step runs a step in a test, with an identified name and output in test results
|
||||
func (s *Suite) Step(name string, test func()) {
|
||||
s.T().Run(name, func(t *testing.T) {
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
func dockerExecutable() string {
|
||||
if IsWindows() {
|
||||
return "docker.exe"
|
||||
}
|
||||
return "docker"
|
||||
}
|
||||
|
||||
// DockerClassicExecutable binary name based on platform
|
||||
func DockerClassicExecutable() string {
|
||||
const comDockerCli = "com.docker.cli"
|
||||
if IsWindows() {
|
||||
return comDockerCli + ".exe"
|
||||
}
|
||||
return comDockerCli
|
||||
}
|
||||
|
||||
// NewDockerCommand creates a docker builder.
|
||||
func (s *Suite) NewDockerCommand(args ...string) *CmdContext {
|
||||
return s.NewCommand(dockerExecutable(), args...)
|
||||
}
|
|
@ -17,58 +17,89 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/api/cli/mobycli"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
"gotest.tools/v3/poll"
|
||||
|
||||
. "github.com/docker/api/tests/framework"
|
||||
)
|
||||
|
||||
type NonWinCIE2eSuite struct {
|
||||
Suite
|
||||
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 (s *NonWinCIE2eSuite) TestKillChildOnCancel() {
|
||||
s.Step("should kill com.docker.cli if parent command is cancelled", func() {
|
||||
imageName := "test-sleep-image"
|
||||
out := s.ListProcessesCommand().ExecOrDie()
|
||||
Expect(out).NotTo(ContainSubstring(imageName))
|
||||
func TestKillChildProcess(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
dir := s.ConfigDir
|
||||
Expect(ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), []byte(`FROM alpine:3.10
|
||||
RUN sleep 100`), 0644)).To(Succeed())
|
||||
shutdown := make(chan time.Time)
|
||||
errs := make(chan error)
|
||||
ctx := s.NewDockerCommand("build", "--no-cache", "-t", imageName, ".").WithinDirectory(dir).WithTimeout(shutdown)
|
||||
go func() {
|
||||
_, err := ctx.Exec()
|
||||
errs <- err
|
||||
}()
|
||||
mobyBuild := mobycli.ComDockerCli + " build --no-cache -t " + imageName
|
||||
err := WaitFor(time.Second, 10*time.Second, errs, func() bool {
|
||||
out := s.ListProcessesCommand().ExecOrDie()
|
||||
return strings.Contains(out, mobyBuild)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
log.Println("Killing docker process")
|
||||
image := "test-sleep-image"
|
||||
pCmd := icmd.Command("ps", "-x")
|
||||
if runtime.GOOS == "windows" {
|
||||
pCmd = icmd.Command("tasklist")
|
||||
}
|
||||
pRes := icmd.RunCmd(pCmd)
|
||||
pRes.Assert(t, icmd.Success)
|
||||
assert.Assert(t, !strings.Contains(pRes.Combined(), image))
|
||||
|
||||
close(shutdown)
|
||||
err = WaitFor(time.Second, 12*time.Second, nil, func() bool {
|
||||
out := s.ListProcessesCommand().ExecOrDie()
|
||||
return !strings.Contains(out, mobyBuild)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
d := writeDockerfile(t)
|
||||
buildArgs := []string{"build", "--no-cache", "-t", image, "."}
|
||||
cmd := c.NewDockerCmd(buildArgs...)
|
||||
cmd.Dir = d
|
||||
res := icmd.StartCmd(cmd)
|
||||
|
||||
buildRunning := func(t poll.LogT) poll.Result {
|
||||
res := icmd.RunCmd(pCmd)
|
||||
if strings.Contains(res.Combined(), strings.Join(buildArgs, " ")) {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for child process to be running")
|
||||
}
|
||||
poll.WaitOn(t, buildRunning, poll.WithDelay(1*time.Second))
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
err := res.Cmd.Process.Kill()
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
err := res.Cmd.Process.Signal(syscall.SIGTERM)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
buildStopped := func(t poll.LogT) poll.Result {
|
||||
res := icmd.RunCmd(pCmd)
|
||||
if !strings.Contains(res.Combined(), strings.Join(buildArgs, " ")) {
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("waiting for child process to be killed")
|
||||
}
|
||||
poll.WaitOn(t, buildStopped, poll.WithDelay(1*time.Second), poll.WithTimeout(60*time.Second))
|
||||
}
|
||||
|
||||
func writeDockerfile(t *testing.T) string {
|
||||
d, err := ioutil.TempDir("", "")
|
||||
assert.NilError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = os.RemoveAll(d)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNonWinCIE2(t *testing.T) {
|
||||
suite.Run(t, new(NonWinCIE2eSuite))
|
||||
err = ioutil.WriteFile(filepath.Join(d, "Dockerfile"), []byte(`FROM alpine:3.10
|
||||
RUN sleep 100`), 0644)
|
||||
assert.NilError(t, err)
|
||||
return d
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue