diff --git a/ecs/Makefile b/ecs/Makefile index 126d5bc5..1033acd2 100644 --- a/ecs/Makefile +++ b/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 diff --git a/ecs/pkg/amazon/api.go b/ecs/pkg/amazon/api.go index 4fa6ddc4..d32e7b7f 100644 --- a/ecs/pkg/amazon/api.go +++ b/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) } diff --git a/ecs/pkg/amazon/mock/api.go b/ecs/pkg/amazon/mock/api.go index c592162b..d834ec62 100644 --- a/ecs/pkg/amazon/mock/api.go +++ b/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) -} diff --git a/ecs/tests/command_test.go b/ecs/tests/command_test.go index a027fbae..3ca7433d 100644 --- a/ecs/tests/command_test.go +++ b/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") diff --git a/ecs/tests/e2e_deploy_services_test.go b/ecs/tests/e2e_deploy_services_test.go new file mode 100644 index 00000000..80d77f31 --- /dev/null +++ b/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) +} diff --git a/ecs/tests/main_test.go b/ecs/tests/main_test.go index a093c6c9..b8a75814 100644 --- a/ecs/tests/main_test.go +++ b/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 { diff --git a/ecs/tests/plugin_test.go b/ecs/tests/plugin_test.go index f519c252..48d7e2f5 100644 --- a/ecs/tests/plugin_test.go +++ b/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") diff --git a/ecs/tests/setup_command_test.go b/ecs/tests/setup_command_test.go index a0cc9e46..d36393f4 100644 --- a/ecs/tests/setup_command_test.go +++ b/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", diff --git a/ecs/tests/testdata/input/simple-single-service.yaml b/ecs/tests/testdata/input/simple-single-service.yaml new file mode 100644 index 00000000..95f30b1d --- /dev/null +++ b/ecs/tests/testdata/input/simple-single-service.yaml @@ -0,0 +1,6 @@ +version: "3" +services: + simple: + image: nginx + ports: + - 80:80 \ No newline at end of file