Skip to content

Commit

Permalink
Merge branch 'ecs-local' into local-create
Browse files Browse the repository at this point in the history
  • Loading branch information
SoManyHs committed Jun 18, 2019
2 parents 4825679 + 8ca733c commit 86efe88
Show file tree
Hide file tree
Showing 18 changed files with 495 additions and 134 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ $(LOCAL_BINARY): $(SOURCES)

.PHONY: test
test:
env -i PATH=$$PATH GOPATH=$$GOPATH GOROOT=$$GOROOT GOCACHE=$$GOCACHE go test -timeout=120s -v -cover ./ecs-cli/modules/...
env -i PATH=$$PATH GOPATH=$$GOPATH GOROOT=$$GOROOT GOCACHE=$$GOCACHE go test -race -timeout=120s -v -cover ./ecs-cli/modules/...

.PHONY: integ-test
integ-test: integ-test-build integ-test-run-with-coverage
Expand Down
1 change: 1 addition & 0 deletions ecs-cli/integ/cmd/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func TestServiceUp(t *testing.T, p *Project) {
"up",
"--cluster-config",
p.ConfigName,
"--create-log-groups",
}
cmd := integ.GetCommand(args)

Expand Down
81 changes: 56 additions & 25 deletions ecs-cli/integ/e2e/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
package e2e

import (
"fmt"
"testing"

"github.com/aws/amazon-ecs-cli/ecs-cli/integ"
"github.com/aws/amazon-ecs-cli/ecs-cli/integ/stdout"
"github.com/stretchr/testify/require"
)

Expand All @@ -40,40 +38,73 @@ func TestECSLocal(t *testing.T) {
{
args: []string{"local", "ps"},
execute: func(t *testing.T, args []string) {
// Given
cmd := integ.GetCommand(args)

// When
out, err := cmd.Output()
require.NoErrorf(t, err, "Failed local ps", fmt.Sprintf("args=%v, stdout=%s, err=%v", args, string(out), err))

// Then
stdout := stdout.Stdout(out)
require.Equal(t, 1, len(stdout.Lines()), "Expected only the table header")
stdout, err := integ.RunCmd(t, args)
require.Error(t, err, "expected args=%v to fail", args)
stdout.TestHasAllSubstrings(t, []string{
"docker-compose.local.yml does not exist",
})
},
},
{
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",
"TASKDEFINITIONARN",
"TASKFILEPATH",
"TASKDEFINITION",
})
},
},
{
args: []string{"local", "ps", "--json"},
args: []string{"local", "ps", "--all", "--json"},
execute: func(t *testing.T, args []string) {
// Given
cmd := integ.GetCommand(args)

// When
out, err := cmd.Output()
require.NoErrorf(t, err, "Failed local ps", fmt.Sprintf("args=%v, stdout=%s, err=%v", args, string(out), err))

// Then
stdout := stdout.Stdout(out)
stdout.TestHasAllSubstrings(t, []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.Error(t, err, "expected args=%v to fail", args)
stdout.TestHasAllSubstrings(t, []string{
"docker-compose.local.yml does not exist",
})
},
},
},
},
"run a single local ECS task": {
sequence: []commandTest{
{
args: []string{"local", "up"},
execute: func(t *testing.T, args []string) {
stdout, err := integ.RunCmd(t, args)
require.NoError(t, err)
stdout.TestHasAllSubstrings(t, []string{
"Created network ecs-local-network",
"Created the amazon-ecs-local-container-endpoints container",
})
},
},
{
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{
"Stopped container with name amazon-ecs-local-container-endpoints",
"Removed container with name amazon-ecs-local-container-endpoints",
"Removed network with name ecs-local-network",
})
},
},
},
Expand Down
9 changes: 9 additions & 0 deletions ecs-cli/integ/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"strings"
"testing"
"time"

"github.com/aws/amazon-ecs-cli/ecs-cli/integ/stdout"
)

const (
Expand All @@ -46,6 +48,13 @@ func GetCommand(args []string) *exec.Cmd {
return exec.Command(cmdPath, args...)
}

// RunCmd runs a command with args and returns its Stdout.
func RunCmd(t *testing.T, args []string) (stdout.Stdout, error) {
cmd := GetCommand(args)
out, err := cmd.CombinedOutput()
return stdout.Stdout(out), err
}

// createTempCoverageFile creates a coverage file for a CLI command under $TMPDIR.
func createTempCoverageFile() (string, error) {
tmpfile, err := ioutil.TempFile("", "coverage-*.out")
Expand Down
3 changes: 2 additions & 1 deletion ecs-cli/integ/stdout/stdout.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

// Package stdout implements testing wrappers on the standard output stream.
package stdout

import (
Expand Down Expand Up @@ -40,7 +41,7 @@ func (b Stdout) Lines() []string {

// TestHasAllSubstrings returns true if stdout contains each snippet in wantedSnippets, false otherwise.
func (b Stdout) TestHasAllSubstrings(t *testing.T, wantedSubstrings []string) {
s := strings.Join(b.Lines(), "\n")
s := string(b)
for _, substring := range wantedSubstrings {
require.Contains(t, s, substring)
}
Expand Down
28 changes: 28 additions & 0 deletions ecs-cli/modules/cli/local/create_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,30 @@ import (
"fmt"

"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/local/project"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags"

log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)

const (
// taskDefinitionLabelType represents the type of option used to
// transform a task definition to a compose file e.g. remoteFile, localFile.
// taskDefinitionLabelValue represents the value of the option
// e.g. file path, arn, family.
taskDefinitionLabelType = "ecsLocalTaskDefType"
taskDefinitionLabelValue = "ecsLocalTaskDefVal"
)

const (
localTaskDefType = "localFile"
remoteTaskDefType = "remoteFile"
)

const (
ecsLocalDockerComposeFileName = "docker-compose.local.yml"
)

// Create reads in an ECS task definition, converts and writes it to a local
// Docker Compose file
func Create(c *cli.Context) {
Expand Down Expand Up @@ -57,3 +77,11 @@ func createLocal(project localproject.LocalProject) error {

return nil
}

func validateOptions(c *cli.Context) error {
if (c.String(flags.TaskDefinitionFileFlag) != "") && (c.String(flags.TaskDefinitionTaskFlag) != "") {
return fmt.Errorf("%s and %s can not be used together",
flags.TaskDefinitionTaskFlag, flags.TaskDefinitionFileFlag)
}
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,34 @@
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package local
package docker

import (
"os"
"time"

"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
)

// Wait durations for a response from the Docker daemon before returning an error to the caller.
const (
// TimeoutInS is the wait duration for common operations.
TimeoutInS = 30 * time.Second

// LongTimeoutInS is the wait duration for long operations such as PullImage.
LongTimeoutInS = 120 * time.Second
)

const (
// minDockerAPIVersion is the minimum Docker API version that supports
// both the Local Endpoints container and the Docker API operations used by "local" sub-commands.
// See https://github.com/awslabs/amazon-ecs-local-container-endpoints/blob/3417a48b676c5b215fb9583bcbdc8a0b0e23aa8e/local-container-endpoints/clients/docker/client.go#L30.
minDockerAPIVersion = "1.27"
)

func newDockerClient() *client.Client {
// NewClient returns an object to communicate with the Docker Engine API.
func NewClient() *client.Client {
if os.Getenv("DOCKER_API_VERSION") == "" {
// If the user does not explicitly set the API version, then the SDK can choose
// an API version that's too new for the user's Docker engine.
Expand Down
116 changes: 116 additions & 0 deletions ecs-cli/modules/cli/local/down_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2015-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 local

import (
"fmt"
"os"
"os/exec"
"path/filepath"

"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/local/docker"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/local/network"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/net/context"
)

// Down stops and removes running local ECS tasks.
// If the user stops the last running task in the local network then also remove the network.
func Down(c *cli.Context) error {
defer func() {
client := docker.NewClient()
network.Teardown(client)
}()

if err := validateOptions(c); err != nil {
logrus.Fatal(err.Error())
}

if c.String(flags.TaskDefinitionFileFlag) != "" {
return downLocalContainersWithFilters(filters.NewArgs(
filters.Arg("label", fmt.Sprintf("%s=%s", taskDefinitionLabelValue,
c.String(flags.TaskDefinitionFileFlag))),
filters.Arg("label", fmt.Sprintf("%s=%s", taskDefinitionLabelType, localTaskDefType)),
))
}
if c.String(flags.TaskDefinitionTaskFlag) != "" {
return downLocalContainersWithFilters(filters.NewArgs(
filters.Arg("label", fmt.Sprintf("%s=%s", taskDefinitionLabelValue,
c.String(flags.TaskDefinitionTaskFlag))),
filters.Arg("label", fmt.Sprintf("%s=%s", taskDefinitionLabelType, remoteTaskDefType)),
))
}
if c.Bool(flags.AllFlag) {
return downLocalContainersWithFilters(filters.NewArgs(
filters.Arg("label", taskDefinitionLabelValue),
))
}
return downComposeLocalContainers()
}

func downComposeLocalContainers() error {
wd, _ := os.Getwd()
if _, err := os.Stat(filepath.Join(wd, ecsLocalDockerComposeFileName)); os.IsNotExist(err) {
logrus.Fatalf("Compose file %s does not exist in current directory", ecsLocalDockerComposeFileName)
}

logrus.Infof("Running Compose down on %s", ecsLocalDockerComposeFileName)
cmd := exec.Command("docker-compose", "-f", ecsLocalDockerComposeFileName, "down")
_, err := cmd.CombinedOutput()
if err != nil {
logrus.Fatalf("Failed to run docker-compose down due to %v", err)
}

logrus.Info("Stopped and removed containers successfully")
return nil
}

func downLocalContainersWithFilters(args filters.Args) error {
ctx, cancel := context.WithTimeout(context.Background(), docker.TimeoutInS)

client := docker.NewClient()
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Filters: args,
All: true,
})
if err != nil {
logrus.Fatalf("Failed to list containers with filters %v due to %v", args, err)
}
cancel()

if len(containers) == 0 {
logrus.Warn("No running ECS local tasks found")
return nil
}

logrus.Infof("Stop and remove %d container(s)", len(containers))
for _, container := range containers {
ctx, cancel = context.WithTimeout(context.Background(), docker.TimeoutInS)
if err = client.ContainerStop(ctx, container.ID, nil); err != nil {
logrus.Fatalf("Failed to stop container %s due to %v", container.ID[:maxContainerIDLength], err)
}
logrus.Infof("Stopped container with id %s", container.ID[:maxContainerIDLength])

if err = client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{}); err != nil {
logrus.Fatalf("Failed to remove container %s due to %v", container.ID[:maxContainerIDLength], err)
}
logrus.Infof("Removed container with id %s", container.ID[:maxContainerIDLength])
cancel()
}
return nil
}
Loading

0 comments on commit 86efe88

Please sign in to comment.