Skip to content

Commit

Permalink
Update "local ps" to default to compose project (#784)
Browse files Browse the repository at this point in the history
  • Loading branch information
efekarakus committed Jun 11, 2019
1 parent 6d8674e commit 32f8010
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 55 deletions.
38 changes: 26 additions & 12 deletions ecs-cli/integ/e2e/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,45 @@ func TestECSLocal(t *testing.T) {
{
args: []string{"local", "ps"},
execute: func(t *testing.T, args []string) {
stdout := integ.RunCmd(t, args)
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) {
stdout := integ.RunCmd(t, args)
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 := integ.RunCmd(t, args)
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",
"ecs-local-network not found",
})
},
},
Expand All @@ -75,17 +87,19 @@ func TestECSLocal(t *testing.T) {
{
args: []string{"local", "up"},
execute: func(t *testing.T, args []string) {
stdout := integ.RunCmd(t, args)
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"},
args: []string{"local", "down", "--all"},
execute: func(t *testing.T, args []string) {
stdout := integ.RunCmd(t, args)
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",
Expand Down
10 changes: 3 additions & 7 deletions ecs-cli/integ/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"time"

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

const (
Expand All @@ -50,13 +49,10 @@ func GetCommand(args []string) *exec.Cmd {
}

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

out, err := cmd.Output()
require.NoErrorf(t, err, "Failed running command", fmt.Sprintf("args=%v, stdout=%s, err=%v", args, string(out), err))

return stdout.Stdout(out)
out, err := cmd.CombinedOutput()
return stdout.Stdout(out), err
}

// createTempCoverageFile creates a coverage file for a CLI command under $TMPDIR.
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
7 changes: 3 additions & 4 deletions ecs-cli/modules/cli/local/down_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ func Down(c *cli.Context) error {
func downComposeLocalContainers() error {
wd, _ := os.Getwd()
if _, err := os.Stat(filepath.Join(wd, ecsLocalDockerComposeFileName)); os.IsNotExist(err) {
logrus.Warnf("Compose file %s does not exist in current directory", ecsLocalDockerComposeFileName)
return nil
logrus.Fatalf("Compose file %s does not exist in current directory", ecsLocalDockerComposeFileName)
}

logrus.Infof("Running Compose down on %s", ecsLocalDockerComposeFileName)
Expand All @@ -73,12 +72,12 @@ func downAllLocalContainers() error {
client := docker.NewClient()
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Filters: filters.NewArgs(
filters.Arg("label", ecsLocalLabelKey),
filters.Arg("label", taskDefinitionLabelKey),
),
All: true,
})
if err != nil {
logrus.Fatalf("Failed to list containers with label=%s due to %v", ecsLocalLabelKey, err)
logrus.Fatalf("Failed to list containers with label=%s due to %v", taskDefinitionLabelKey, err)
}
if len(containers) == 0 {
logrus.Warn("No running ECS local tasks found")
Expand Down
93 changes: 62 additions & 31 deletions ecs-cli/modules/cli/local/ps_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,27 @@ import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"text/tabwriter"

"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/local/docker"
"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/docker/docker/client"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/net/context"
)

// TODO These labels should be defined part of the local.Create workflow.
// Refactor to import these constants instead of re-defining them here.
// Docker object labels associated with containers created with "ecs-cli local".
const (
// ecsLocalLabelKey is the Docker object label associated with containers created with "ecs-cli local".
ecsLocalLabelKey = "ECSLocalTask"

// taskDefinitionARNLabelKey is the Docker object label present if the container was created with a task def ARN.
taskDefinitionARNLabelKey = "taskDefinitionARN"

// taskFilePathLabelKey is the Docker object label present if the container was created from a file.
taskFilePathLabelKey = "taskFilePath"
// taskDefinitionLabelKey represents the value of the option used to
// transform a task definition to a compose file e.g. file path, arn, family.
taskDefinitionLabelKey = "ecsLocalTaskDefinition"
)

// Table formatting settings used by the Docker CLI.
Expand All @@ -51,8 +48,7 @@ const (
cellPaddingInSpaces = 3
paddingCharacter = ' '
noFormatting = 0

maxContainerIDLength = 12
maxContainerIDLength = 12
)

// JSON formatting settings.
Expand All @@ -61,37 +57,73 @@ const (
jsonIndent = " "
)

// Ps lists the status of the ECS task containers running locally.
// Ps lists the status of the ECS task containers running locally as a table.
//
// Defaults to listing the container metadata in a table format to stdout. If the --json flag is provided,
// then output the content as JSON instead.
// Defaults to listing containers from the local Compose file.
// If the --all flag is provided, then list all local ECS task containers.
// If the --json flag is provided, then output the format as JSON instead.
func Ps(c *cli.Context) {
client := docker.NewClient()
containers := listContainers(c)
displayContainers(c, containers)
}

containers := listECSLocalContainers(client)
if c.Bool(flags.JsonFlag) {
displayAsJSON(containers)
} else {
displayAsTable(containers)
func listContainers(c *cli.Context) []types.Container {
if !c.Bool(flags.AllFlag) {
return listLocalComposeContainers()
}
// Task containers running locally all have a local label
return listContainersWithFilters(filters.NewArgs(
filters.Arg("label", taskDefinitionLabelKey),
))
}

func listECSLocalContainers(client *client.Client) []types.Container {
func listLocalComposeContainers() []types.Container {
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)
}

// The -q flag displays the ID of the containers instead of the default "Name, Command, State, Ports" metadata.
cmd := exec.Command("docker-compose", "-f", ecsLocalDockerComposeFileName, "ps", "-q")
composeOut, err := cmd.Output()
if err != nil {
logrus.Fatalf("Failed to run docker-compose ps due to %v", err)
}

containerIDs := strings.Split(string(composeOut), "\n")
if len(containerIDs) == 0 {
return []types.Container{}
}

var args []filters.KeyValuePair
for _, containerID := range containerIDs {
args = append(args, filters.Arg("id", containerID))
}
return listContainersWithFilters(filters.NewArgs(args...))
}

func listContainersWithFilters(args filters.Args) []types.Container {
ctx, cancel := context.WithTimeout(context.Background(), docker.TimeoutInS)
defer cancel()

// ECS Task containers running locally all have an ECS local label
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Filters: filters.NewArgs(
filters.Arg("label", ecsLocalLabelKey),
),
cl := docker.NewClient()
containers, err := cl.ContainerList(ctx, types.ContainerListOptions{
Filters: args,
})
if err != nil {
logrus.Fatalf("Failed to list containers with label=%s due to %v", ecsLocalLabelKey, err)
logrus.Fatalf("Failed to list containers with args=%v due to %v", args, err)
}
return containers
}

func displayContainers(c *cli.Context, containers []types.Container) {
if c.Bool(flags.JsonFlag) {
displayAsJSON(containers)
} else {
displayAsTable(containers)
}
}

func displayAsJSON(containers []types.Container) {
data, err := json.MarshalIndent(containers, jsonPrefix, jsonIndent)
if err != nil {
Expand All @@ -104,16 +136,15 @@ func displayAsTable(containers []types.Container) {
w := new(tabwriter.Writer)

w.Init(os.Stdout, cellWidthInSpaces, widthBetweenCellsInSpaces, cellPaddingInSpaces, paddingCharacter, noFormatting)
fmt.Fprintln(w, "CONTAINER ID\tIMAGE\tSTATUS\tPORTS\tNAMES\tTASKDEFINITIONARN\tTASKFILEPATH")
fmt.Fprintln(w, "CONTAINER ID\tIMAGE\tSTATUS\tPORTS\tNAMES\tTASKDEFINITION")
for _, container := range containers {
row := fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\t%s",
row := fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s",
container.ID[:maxContainerIDLength],
container.Image,
container.Status,
prettifyPorts(container.Ports),
prettifyNames(container.Names),
container.Labels[taskDefinitionARNLabelKey],
container.Labels[taskFilePathLabelKey])
container.Labels[taskDefinitionLabelKey])
fmt.Fprintln(w, row)
}
w.Flush()
Expand Down
4 changes: 4 additions & 0 deletions ecs-cli/modules/commands/local/local_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func psCommand() cli.Command {
Usage: "List locally running ECS task containers.",
Action: local.Ps,
Flags: []cli.Flag{
cli.BoolFlag{
Name: flags.AllFlag,
Usage: "Lists all running local ECS tasks.",
},
cli.BoolFlag{
Name: flags.JsonFlag,
Usage: "Output in JSON format.",
Expand Down

0 comments on commit 32f8010

Please sign in to comment.