Skip to content

Commit

Permalink
Support "local ps" to list running local ECS task containers (#779)
Browse files Browse the repository at this point in the history
  • Loading branch information
efekarakus authored May 29, 2019
1 parent 9b16825 commit 0faf4cf
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 68 deletions.
90 changes: 90 additions & 0 deletions ecs-cli/integ/e2e/local_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// +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 (
"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"
)

func TestECSLocal(t *testing.T) {
t.Parallel()

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) {
// 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.TestHasAllSubstrings(t, []string{
"CONTAINER ID",
"IMAGE",
"STATUS",
"PORTS",
"NAMES",
"TASKDEFINITIONARN",
"TASKFILEPATH",
})
},
},
{
args: []string{"local", "ps", "--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{"[]"})
},
},
},
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
for _, cmd := range tc.sequence {
cmd.execute(t, cmd.args)
}
})
}
}
29 changes: 29 additions & 0 deletions ecs-cli/modules/cli/local/create_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 implements the subcommands to run ECS task definitions locally
// (See: https://github.com/aws/containers-roadmap/issues/180).
package local

import (
"fmt"

"github.com/urfave/cli"
)

func Create(c *cli.Context) {
// 1. read in task def (from file or arn)
// 2. parse task def into go object
// 3. write to docker-compose.local.yml file
fmt.Println("foo") // placeholder
}
42 changes: 42 additions & 0 deletions ecs-cli/modules/cli/local/docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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 (
"os"

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

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 {
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.
_ = os.Setenv("DOCKER_API_VERSION", minDockerAPIVersion)
}

client, err := client.NewEnvClient()
if err != nil {
logrus.Fatalf("Could not create a docker client due to %v", err)
}
return client
}
63 changes: 0 additions & 63 deletions ecs-cli/modules/cli/local/local_app.go

This file was deleted.

129 changes: 129 additions & 0 deletions ecs-cli/modules/cli/local/ps_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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 (
"encoding/json"
"fmt"
"os"
"strings"
"text/tabwriter"

"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.
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"
)

// Table formatting settings used by the Docker CLI.
// See https://github.com/docker/cli/blob/0904fbfc77dbd4b6296c56e68be573b889d049e3/cli/command/formatter/formatter.go#L74
const (
cellWidthInSpaces = 20
widthBetweenCellsInSpaces = 1
cellPaddingInSpaces = 3
paddingCharacter = ' '
noFormatting = 0

maxContainerIDLength = 12
)

// JSON formatting settings.
const (
jsonPrefix = ""
jsonIndent = " "
)

// Ps lists the status of the ECS task containers running locally.
//
// 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.
func Ps(c *cli.Context) {
docker := newDockerClient()

containers := listECSLocalContainers(docker)
if c.Bool(flags.JsonFlag) {
displayAsJSON(containers)
} else {
displayAsTable(containers)
}
}

func listECSLocalContainers(docker *client.Client) []types.Container {
// ECS Task containers running locally all have an ECS local label
containers, err := docker.ContainerList(context.Background(), types.ContainerListOptions{
Filters: filters.NewArgs(
filters.Arg("label", ecsLocalLabelKey),
),
})
if err != nil {
logrus.Fatalf("Failed to list containers with label=%s due to %v", ecsLocalLabelKey, err)
}
return containers
}

func displayAsJSON(containers []types.Container) {
data, err := json.MarshalIndent(containers, jsonPrefix, jsonIndent)
if err != nil {
logrus.Fatalf("Failed to marshal containers to JSON due to %v", err)
}
fmt.Fprintln(os.Stdout, string(data))
}

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")
for _, container := range containers {
row := fmt.Sprintf("%s\t%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])
fmt.Fprintln(w, row)
}
w.Flush()
}

func prettifyPorts(containerPorts []types.Port) string {
var prettyPorts []string
for _, port := range containerPorts {
// See https://github.com/docker/cli/blob/0904fbfc77dbd4b6296c56e68be573b889d049e3/cli/command/formatter/container.go#L268
prettyPorts = append(prettyPorts, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
}
return strings.Join(prettyPorts, ", ")
}

func prettifyNames(containerNames []string) string {
return strings.Join(containerNames, ", ")
}
27 changes: 27 additions & 0 deletions ecs-cli/modules/cli/local/stop_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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 (
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/local/network"
"github.com/urfave/cli"
)

// Stop stops a running local ECS task.
//
// If the user stops the last running task in the local network then also remove the network.
func Stop(c *cli.Context) {
docker := newDockerClient()
defer network.Teardown(docker)
}
Loading

0 comments on commit 0faf4cf

Please sign in to comment.