tests.e2e: Refactor

Signed-off-by: Chris Crone <christopher.crone@docker.com>
This commit is contained in:
Chris Crone 2020-08-06 15:40:36 +02:00
parent 036190bd5e
commit 017053e19a
18 changed files with 1238 additions and 1148 deletions

View File

@ -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 \

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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",
})
})
}

View File

@ -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])
}

View File

@ -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`,
})
})
}

View File

@ -0,0 +1,2 @@
default
test-docker

View File

@ -0,0 +1,3 @@
CONTAINER ID IMAGE COMMAND STATUS PORTS
id nginx
1234 alpine

View File

@ -0,0 +1,3 @@
id
1234
stopped

View File

@ -0,0 +1,2 @@
id
1234

View File

@ -1,2 +0,0 @@
docker: 'foo' is not a docker command.
See 'docker --help'

183
tests/framework/e2e.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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"
}

View File

@ -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...)
}

View File

@ -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
}