// +build integ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file 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 e2e import ( "context" "fmt" "io/ioutil" "os" "path" "testing" "github.com/aws/amazon-ecs-cli/ecs-cli/integ" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/stretchr/testify/require" ) func TestECSLocal(t *testing.T) { type commandTest struct { args []string execute func(t *testing.T, args []string) } tests := map[string]struct { sequence []commandTest }{ "clean state": { sequence: []commandTest{ { args: []string{"local", "ps"}, execute: func(t *testing.T, args []string) { stdout, err := integ.RunCmd(t, args) require.NoError(t, err) stdout.TestHasAllSubstrings(t, []string{ "CONTAINER ID", "IMAGE", "STATUS", "PORTS", "NAMES", "TASKDEFINITION", }) }, }, { args: []string{"local", "ps", "--all"}, execute: func(t *testing.T, args []string) { stdout, err := integ.RunCmd(t, args) require.NoError(t, err) stdout.TestHasAllSubstrings(t, []string{ "CONTAINER ID", "IMAGE", "STATUS", "PORTS", "NAMES", "TASKDEFINITION", }) }, }, { args: []string{"local", "ps", "--all", "--json"}, execute: func(t *testing.T, args []string) { stdout, err := integ.RunCmd(t, args) require.NoError(t, err) stdout.TestHasAllSubstrings(t, []string{ "[]", }) }, }, { args: []string{"local", "down"}, execute: func(t *testing.T, args []string) { stdout, err := integ.RunCmd(t, args) require.NoError(t, err) stdout.TestHasAllSubstrings(t, []string{ "No running ECS local tasks found", }) }, }, { args: []string{"local", "down", "--all"}, execute: func(t *testing.T, args []string) { stdout, err := integ.RunCmd(t, args) require.NoError(t, err) stdout.TestHasAllSubstrings(t, []string{ "No running ECS local tasks found", }) }, }, { args: []string{"local", "down", "-f", "task-definition.json"}, execute: func(t *testing.T, args []string) { stdout, err := integ.RunCmd(t, args) require.NoError(t, err) stdout.TestHasAllSubstrings(t, []string{ "Searching for containers from local file task-definition.json", "No running ECS local tasks found", }) }, }, }, }, "from task def": { sequence: []commandTest{ { args: []string{"local", "create", "-f", ""}, execute: func(t *testing.T, args []string) { tempFileName := createLocalTaskDefFile(t) defer cleanUp(tempFileName) args[3] = tempFileName stdout, err := integ.RunCmd(t, args) require.NoError(t, err) stdout.TestHasAllSubstrings(t, []string{ "Successfully wrote docker-compose", }) checkDockerComposeContents( t, "docker-compose.ecs-local.yml", "docker-compose.ecs-local.override.yml", tempFileName, ) }, }, { args: []string{"local", "up", "-f", ""}, execute: func(t *testing.T, args []string) { tempFileName := createLocalTaskDefFile(t) defer cleanUp(tempFileName) args[3] = tempFileName stdout, err := integ.RunCmd(t, args) require.NoError(t, err) stdout.TestHasAllSubstrings(t, []string{ "Started container with ID", }) checkDockerComposeContents(t, "docker-compose.ecs-local.yml", "docker-compose.ecs-local.override.yml", tempFileName, ) dockerCli := getDockerClient(t) containers := checkNumberRunningContainers(t, dockerCli, 2) containerNames := getContainerNames(t, containers) require.Contains(t, containerNames, "httpd:2.4") downArgs := []string{"local", "down", "-f", tempFileName} stdout, err = integ.RunCmd(t, downArgs) checkNumberRunningContainers(t, dockerCli, 0) }, }, { args: []string{"local", "up", "-c", "docker-compose.ecs-local.yml"}, execute: func(t *testing.T, args []string) { tempFileName := createLocalTaskDefFile(t) defer cleanUp(tempFileName) createArgs := []string{"local", "create", "-f", tempFileName} stdout, err := integ.RunCmd(t, createArgs) stdout, err = integ.RunCmd(t, args) require.NoError(t, err) checkDockerComposeContents(t, "docker-compose.ecs-local.yml", "docker-compose.ecs-local.override.yml", tempFileName, ) stdout.TestHasAllSubstrings(t, []string{ "Created the amazon-ecs-local-container-endpoints container", }) dockerCli := getDockerClient(t) containers := checkNumberRunningContainers(t, dockerCli, 2) containerNames := getContainerNames(t, containers) require.Contains(t, containerNames, "httpd:2.4") downArgs := []string{"local", "down", "-f", tempFileName} stdout, err = integ.RunCmd(t, downArgs) }, }, { args: []string{"local", "up"}, execute: func(t *testing.T, args []string) { tempFileName := createLocalTaskDefFile(t) os.Rename(tempFileName, "task-definition.json") defer cleanUp("task-definition.json") stdout, err := integ.RunCmd(t, args) stdout.TestHasAllSubstrings(t, []string{ "Successfully wrote docker-compose.ecs-local.yml", "Started container with ID", }) checkDockerComposeContents( t, "docker-compose.ecs-local.yml", "docker-compose.ecs-local.override.yml", "task-definition.json", ) dockerCli := getDockerClient(t) containers := checkNumberRunningContainers(t, dockerCli, 2) containerNames := getContainerNames(t, containers) require.Contains(t, containerNames, "httpd:2.4") downArgs := []string{"local", "down", "--all"} stdout, err = integ.RunCmd(t, downArgs) stdout.TestHasAllSubstrings(t, []string{ "Stopped container with name", "Removed container with name", "Removed network with name", }) require.NoError(t, err) checkNumberRunningContainers(t, dockerCli, 0) }, }, }, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { for _, cmd := range tc.sequence { cmd.execute(t, cmd.args) } }) } } func cleanUp(taskDefFile string) { err := os.Remove(taskDefFile) err = os.Remove("docker-compose.ecs-local.yml") err = os.Remove("docker-compose.ecs-local.override.yml") if err != nil { panic(fmt.Sprintf("error %v removing files!", err)) } } func createLocalTaskDefFile(t *testing.T) string { content := ` { "containerDefinitions": [ { "entryPoint": [ "sh", "-c" ], "essential": true, "image": "httpd:2.4", "name": "simple-test-app", "portMappings": [ { "containerPort": 80, "hostPort": 80, "protocol": "tcp" } ] } ], "cpu": "256", "memory": "512", "networkMode": "bridge" }` tmpfile, err := ioutil.TempFile(".", "task-definition-*.json") require.NoError(t, err, "Failed to create task-definition-*.json") _, err = tmpfile.Write([]byte(content)) require.NoError(t, err, "Failed to write to %s", tmpfile.Name()) err = tmpfile.Close() require.NoError(t, err, "Failed to close %s", tmpfile.Name()) return tmpfile.Name() } func getFullTaskDefPath(t *testing.T, taskDefinitionFilename string) string { workingDirectory, err := os.Getwd() require.NoError(t, err, "error getting working directory") fullPath := path.Join(workingDirectory, taskDefinitionFilename) _, err = os.Stat(fullPath) require.NoError(t, err, "Could not resolve full task definition path. File does not exist at the provided location") return fullPath } func checkDockerComposeContents(t *testing.T, composeFileName string, overrideFileName string, taskDefinitionFile string) { taskDefinitionFullPath := getFullTaskDefPath(t, taskDefinitionFile) contentsCompose := fmt.Sprintf( `version: "3.4" services: simple-test-app: entrypoint: - sh - -c environment: AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: /creds ECS_CONTAINER_METADATA_URI: http://169.254.170.2/v3 image: httpd:2.4 labels: ecs-local.task-definition-input.type: local ecs-local.task-definition-input.value: %s networks: ecs-local-network: null ports: - target: 80 published: 80 protocol: tcp networks: ecs-local-network: external: true `, taskDefinitionFullPath) contentsComposeOverride := `version: "3.4" services: simple-test-app: logging: driver: json-file ` checkFileContents(t, composeFileName, contentsCompose) checkFileContents(t, overrideFileName, contentsComposeOverride) } func checkFileContents(t *testing.T, fileName string, testData string) { t.Helper() file, err := os.Open(fileName) require.NoError(t, err, "could not open file to check its contents") defer file.Close() data, err := ioutil.ReadAll(file) require.Equal(t, string(data), testData, "Docker compose contents do not match desired test data!") } func getContainerNames(t *testing.T, containers []types.Container) []string { t.Helper() var names []string for _, ctr := range containers { names = append(names, ctr.Image) } return names } func getDockerClient(t *testing.T) *client.Client { t.Helper() dockerCli, err := client.NewClientWithOpts( client.WithVersion("1.39"), ) require.NoError(t, err) return dockerCli } func checkNumberRunningContainers(t *testing.T, cli *client.Client, expectedContainers int) []types.Container { t.Helper() containers, err := cli.ContainerList( context.Background(), types.ContainerListOptions{All: true}, ) require.NoError(t, err) require.Equal( t, len(containers), expectedContainers, "Expected %d containers to be found by Docker SDK.", expectedContainers, ) return containers }