Browse Source

Add e2e test deploying a compose application to an ECS cluster

Signed-off-by: Guillaume Lours <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
Guillaume Lours 5 years ago
parent
commit
e9fe3b2864

+ 4 - 1
ecs/Makefile

@@ -7,10 +7,13 @@ build:
 test: build ## Run tests
 	go test ./... -v
 
+e2e: build ## Run tests
+	go test ./... -v -tags=e2e
+
 dev: build
 	ln -f -s "${PWD}/dist/docker-ecs" "${HOME}/.docker/cli-plugins/docker-ecs"
 
 lint: ## Verify Go files
 	golangci-lint run --config ./golangci.yaml ./...
 
-.PHONY: clean build test dev lint
+.PHONY: clean build test dev lint e2e

+ 5 - 0
ecs/pkg/amazon/api.go

@@ -1,5 +1,7 @@
 package amazon
 
+import "context"
+
 //go:generate mockgen -destination=./mock/api.go -package=mock . API
 
 type API interface {
@@ -7,4 +9,7 @@ type API interface {
 	upAPI
 	logsAPI
 	secretsAPI
+	GetTasks(ctx context.Context, cluster string, name string) ([]string, error)
+	GetNetworkInterfaces(ctx context.Context, cluster string, arns ...string) ([]string, error)
+	GetPublicIPs(ctx context.Context, interfaces ...string) ([]string, error)
 }

+ 70 - 31
ecs/pkg/amazon/mock/api.go

@@ -6,12 +6,11 @@ package mock
 
 import (
 	context "context"
-	reflect "reflect"
-
 	cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
 	cloudformation0 "github.com/awslabs/goformation/v4/cloudformation"
 	docker "github.com/docker/ecs-plugin/pkg/docker"
 	gomock "github.com/golang/mock/gomock"
+	reflect "reflect"
 )
 
 // MockAPI is a mock of API interface
@@ -52,21 +51,6 @@ func (mr *MockAPIMockRecorder) ClusterExists(arg0, arg1 interface{}) *gomock.Cal
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterExists", reflect.TypeOf((*MockAPI)(nil).ClusterExists), arg0, arg1)
 }
 
-// CreateCluster mocks base method
-func (m *MockAPI) CreateCluster(arg0 context.Context, arg1 string) (string, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CreateCluster", arg0, arg1)
-	ret0, _ := ret[0].(string)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// CreateCluster indicates an expected call of CreateCluster
-func (mr *MockAPIMockRecorder) CreateCluster(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCluster", reflect.TypeOf((*MockAPI)(nil).CreateCluster), arg0, arg1)
-}
-
 // CreateSecret mocks base method
 func (m *MockAPI) CreateSecret(arg0 context.Context, arg1 docker.Secret) (string, error) {
 	m.ctrl.T.Helper()
@@ -168,6 +152,60 @@ func (mr *MockAPIMockRecorder) GetDefaultVPC(arg0 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultVPC", reflect.TypeOf((*MockAPI)(nil).GetDefaultVPC), arg0)
 }
 
+// GetLogs mocks base method
+func (m *MockAPI) GetLogs(arg0 context.Context, arg1 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetLogs", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// GetLogs indicates an expected call of GetLogs
+func (mr *MockAPIMockRecorder) GetLogs(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockAPI)(nil).GetLogs), arg0, arg1)
+}
+
+// GetNetworkInterfaces mocks base method
+func (m *MockAPI) GetNetworkInterfaces(arg0 context.Context, arg1 string, arg2 ...string) ([]string, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0, arg1}
+	for _, a := range arg2 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "GetNetworkInterfaces", varargs...)
+	ret0, _ := ret[0].([]string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetNetworkInterfaces indicates an expected call of GetNetworkInterfaces
+func (mr *MockAPIMockRecorder) GetNetworkInterfaces(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0, arg1}, arg2...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNetworkInterfaces", reflect.TypeOf((*MockAPI)(nil).GetNetworkInterfaces), varargs...)
+}
+
+// GetPublicIPs mocks base method
+func (m *MockAPI) GetPublicIPs(arg0 context.Context, arg1 ...string) ([]string, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0}
+	for _, a := range arg1 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "GetPublicIPs", varargs...)
+	ret0, _ := ret[0].([]string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetPublicIPs indicates an expected call of GetPublicIPs
+func (mr *MockAPIMockRecorder) GetPublicIPs(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0}, arg1...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicIPs", reflect.TypeOf((*MockAPI)(nil).GetPublicIPs), varargs...)
+}
+
 // GetStackID mocks base method
 func (m *MockAPI) GetStackID(arg0 context.Context, arg1 string) (string, error) {
 	m.ctrl.T.Helper()
@@ -198,6 +236,21 @@ func (mr *MockAPIMockRecorder) GetSubNets(arg0, arg1 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubNets", reflect.TypeOf((*MockAPI)(nil).GetSubNets), arg0, arg1)
 }
 
+// GetTasks mocks base method
+func (m *MockAPI) GetTasks(arg0 context.Context, arg1, arg2 string) ([]string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTasks", arg0, arg1, arg2)
+	ret0, _ := ret[0].([]string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetTasks indicates an expected call of GetTasks
+func (mr *MockAPIMockRecorder) GetTasks(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTasks", reflect.TypeOf((*MockAPI)(nil).GetTasks), arg0, arg1, arg2)
+}
+
 // InspectSecret mocks base method
 func (m *MockAPI) InspectSecret(arg0 context.Context, arg1 string) (docker.Secret, error) {
 	m.ctrl.T.Helper()
@@ -271,17 +324,3 @@ func (mr *MockAPIMockRecorder) WaitStackComplete(arg0, arg1, arg2 interface{}) *
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitStackComplete", reflect.TypeOf((*MockAPI)(nil).WaitStackComplete), arg0, arg1, arg2)
 }
-
-// GetLogs mocks base method
-func (m *MockAPI) GetLogs(arg0 context.Context, arg1 string) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetLogs", arg0, arg1)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// GetLogs mocks base method
-func (mr *MockAPIMockRecorder) GetLogs(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockAPI)(nil).GetLogs), arg0, arg1)
-}

+ 1 - 1
ecs/tests/command_test.go

@@ -7,7 +7,7 @@ import (
 )
 
 func TestExitErrorCode(t *testing.T) {
-	cmd, cleanup := dockerCli.createTestCmd()
+	cmd, cleanup, _ := dockerCli.createTestCmd()
 	defer cleanup()
 
 	cmd.Command = dockerCli.Command("ecs", "unknown_command")

+ 65 - 0
ecs/tests/e2e_deploy_services_test.go

@@ -0,0 +1,65 @@
+// +build e2e
+
+package tests
+
+import (
+	"context"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/docker/ecs-plugin/pkg/amazon"
+	"github.com/docker/ecs-plugin/pkg/docker"
+	"gotest.tools/assert"
+	"gotest.tools/v3/fs"
+	"gotest.tools/v3/golden"
+	"gotest.tools/v3/icmd"
+)
+
+const (
+	composeFileName = "compose.yaml"
+)
+
+func TestE2eDeployServices(t *testing.T) {
+	cmd, cleanup, awsContext := dockerCli.createTestCmd()
+	defer cleanup()
+
+	composeUpSimpleService(t, cmd, awsContext)
+}
+
+func composeUpSimpleService(t *testing.T, cmd icmd.Cmd, awsContext docker.AwsContext) {
+	bgContext := context.Background()
+	composeYAML := golden.Get(t, "input/simple-single-service.yaml")
+	tmpDir := fs.NewDir(t, t.Name(),
+		fs.WithFile(composeFileName, "", fs.WithBytes(composeYAML)),
+	)
+	// We can't use the file added in the tmp directory because it will drop if an assertion fails
+	defer composeDown(t, cmd, golden.Path("input/simple-single-service.yaml"))
+	defer tmpDir.Remove()
+
+	cmd.Command = dockerCli.Command("ecs", "compose", "--file="+tmpDir.Join(composeFileName), "--project-name", t.Name(), "up")
+	icmd.RunCmd(cmd).Assert(t, icmd.Success)
+
+	session, err := session.NewSessionWithOptions(session.Options{
+		Profile: awsContext.Profile,
+		Config: aws.Config{
+			Region: aws.String(awsContext.Region),
+		},
+	})
+	assert.NilError(t, err)
+	sdk := amazon.NewAPI(session)
+	arns, err := sdk.GetTasks(bgContext, t.Name(), "simple")
+	assert.NilError(t, err)
+	networkInterfaces, err := sdk.GetNetworkInterfaces(bgContext, t.Name(), arns...)
+	publicIps, err := sdk.GetPublicIPs(context.Background(), networkInterfaces...)
+	assert.NilError(t, err)
+	for _, ip := range publicIps {
+		icmd.RunCommand("curl", "-I", "http://"+ip).Assert(t, icmd.Success)
+	}
+
+}
+
+func composeDown(t *testing.T, cmd icmd.Cmd, composeFile string) {
+	cmd.Command = dockerCli.Command("ecs", "compose", "--file="+composeFile, "--project-name", t.Name(), "down")
+	icmd.RunCmd(cmd).Assert(t, icmd.Success)
+}

+ 4 - 5
ecs/tests/main_test.go

@@ -29,7 +29,7 @@ type dockerCliCommand struct {
 
 type ConfigFileOperator func(configFile *dockerConfigFile.ConfigFile)
 
-func (d dockerCliCommand) createTestCmd(ops ...ConfigFileOperator) (icmd.Cmd, func()) {
+func (d dockerCliCommand) createTestCmd(ops ...ConfigFileOperator) (icmd.Cmd, func(), docker.AwsContext) {
 	configDir, err := ioutil.TempDir("", "config")
 	if err != nil {
 		panic(err)
@@ -55,9 +55,8 @@ func (d dockerCliCommand) createTestCmd(ops ...ConfigFileOperator) (icmd.Cmd, fu
 	}
 
 	awsContext := docker.AwsContext{
-		Profile: "TestProfile",
-		Cluster: "TestCluster",
-		Region:  "TestRegion",
+		Profile: "sandbox.devtools.developer",
+		Region:  "eu-west-3",
 	}
 	testStore, err := docker.NewContextWithStore(testContextName, &awsContext, filepath.Join(configDir, "contexts"))
 	if err != nil {
@@ -71,7 +70,7 @@ func (d dockerCliCommand) createTestCmd(ops ...ConfigFileOperator) (icmd.Cmd, fu
 	env := append(os.Environ(),
 		"DOCKER_CONFIG="+configDir,
 		"DOCKER_CLI_EXPERIMENTAL=enabled") // TODO: Remove this once docker ecs plugin is no more experimental
-	return icmd.Cmd{Env: env}, cleanup
+	return icmd.Cmd{Env: env}, cleanup, awsContext
 }
 
 func (d dockerCliCommand) Command(args ...string) []string {

+ 1 - 1
ecs/tests/plugin_test.go

@@ -10,7 +10,7 @@ import (
 )
 
 func TestInvokePluginFromCLI(t *testing.T) {
-	cmd, cleanup := dockerCli.createTestCmd()
+	cmd, cleanup, _ := dockerCli.createTestCmd()
 	defer cleanup()
 	// docker --help should list app as a top command
 	cmd.Command = dockerCli.Command("--help")

+ 1 - 1
ecs/tests/setup_command_test.go

@@ -10,7 +10,7 @@ import (
 )
 
 func TestDefaultAwsContextName(t *testing.T) {
-	cmd, cleanup := dockerCli.createTestCmd()
+	cmd, cleanup, _ := dockerCli.createTestCmd()
 	defer cleanup()
 
 	cmd.Command = dockerCli.Command("ecs", "setup", "--cluster", "clusterName", "--profile", "profileName",

+ 6 - 0
ecs/tests/testdata/input/simple-single-service.yaml

@@ -0,0 +1,6 @@
+version: "3"
+services:
+  simple:
+    image: nginx
+    ports:
+    - 80:80