Skip to content
This repository was archived by the owner on Nov 19, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions buildspec.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
version: 0.2

phases:
install: # TODO remove this step after supporting pulling the image part of "ecs-cli local up". See ECS-8445.
commands:
- docker pull amazon/amazon-ecs-local-container-endpoints
build:
commands:
- make integ-test
Expand Down
6 changes: 5 additions & 1 deletion ecs-cli/modules/cli/local/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ import (
"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 a response from the Docker daemon before returning an error to the user.
// 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 (
Expand Down
31 changes: 31 additions & 0 deletions ecs-cli/modules/cli/local/network/mock_network/setup.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 59 additions & 2 deletions ecs-cli/modules/cli/local/network/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package network

import (
"fmt"
"io"
"io/ioutil"
"os"
"strings"

Expand All @@ -33,6 +35,7 @@ import (
// the Local Container Endpoints container if they don't exist.
type LocalEndpointsStarter interface {
networkCreator
imagePuller
containerStarter
}

Expand All @@ -41,6 +44,11 @@ type networkCreator interface {
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
}

type imagePuller interface {
ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error)
ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error)
}

type containerStarter interface {
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
ContainerCreate(ctx context.Context, config *container.Config,
Expand Down Expand Up @@ -70,12 +78,14 @@ const (
localEndpointsContainerName = "amazon-ecs-local-container-endpoints"
)

// Setup creates a user-defined bridge network with a running Local Container Endpoints container. If the network
// already exists or the container is already running then this function does nothing.
// Setup creates a user-defined bridge network with a running Local Container Endpoints container. It will pull
// the Local Endpoints image if it doesn't exist.
//
// If the network, image, and container already exist, then do nothing.
// If there is any unexpected errors, we exit the program with a fatal log.
func Setup(dockerClient LocalEndpointsStarter) {
setupLocalNetwork(dockerClient)
setupLocalEndpointsImage(dockerClient)
setupLocalEndpointsContainer(dockerClient)
}

Expand Down Expand Up @@ -122,6 +132,53 @@ func createLocalNetwork(dockerClient networkCreator) {
logrus.Infof("Created network %s with ID %s", EcsLocalNetworkName, resp.ID)
}

func setupLocalEndpointsImage(dockerClient imagePuller) {
if localEndpointsImageExists(dockerClient) {
return
}
pullLocalEndpointsImage(dockerClient)
}

func localEndpointsImageExists(dockerClient imagePuller) bool {
ctx, cancel := context.WithTimeout(context.Background(), docker.TimeoutInS)
defer cancel()

args := filters.NewArgs(filters.Arg("reference", localEndpointsImageName))
imgs, err := dockerClient.ImageList(ctx, types.ImageListOptions{
Filters: args,
})
if err != nil {
logrus.Fatalf("Failed to list images with filters %v due to %v", args, err)
}

for _, img := range imgs {
for _, repotag := range img.RepoTags {
if strings.HasPrefix(repotag, localEndpointsImageName) {
return true
}
}
}
return false
}

func pullLocalEndpointsImage(dockerClient imagePuller) {
ctx, cancel := context.WithTimeout(context.Background(), docker.LongTimeoutInS)
defer cancel()

logrus.Infof("Pulling image %s", localEndpointsImageName)
rc, err := dockerClient.ImagePull(ctx, localEndpointsImageName, types.ImagePullOptions{})
if err != nil {
logrus.Fatalf("Failed to pull image %s due to %v", localEndpointsImageName, err)
}
defer rc.Close()

_, err = ioutil.ReadAll(rc)
if err != nil {
logrus.Fatalf("Failed to download the image %s due to %v", localEndpointsImageName, err)
}
logrus.Infof("Pulled image %s", localEndpointsImageName)
}

func setupLocalEndpointsContainer(docker containerStarter) {
containerID := createLocalEndpointsContainer(docker)
startContainer(docker, containerID)
Expand Down
24 changes: 22 additions & 2 deletions ecs-cli/modules/cli/local/network/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package network

import (
"bytes"
"io/ioutil"
"testing"

"github.com/pkg/errors"
Expand Down Expand Up @@ -45,14 +47,22 @@ func TestSetup(t *testing.T) {
tests := map[string]struct {
configureCalls mockStarterCalls
}{
"new network and new container": {
"new network, new image, and new container": {
configureCalls: func(docker *mock_network.MockLocalEndpointsStarter) *mock_network.MockLocalEndpointsStarter {
containerID := "1234"
pullImgResp := ioutil.NopCloser(bytes.NewReader([]byte("Pulled image successfully")))
gomock.InOrder(
// We expect to create the network if it doesn't exist already.
docker.EXPECT().NetworkInspect(gomock.Any(), EcsLocalNetworkName, gomock.Any()).Return(types.NetworkResource{}, notFoundErr{}),
docker.EXPECT().NetworkCreate(gomock.Any(), EcsLocalNetworkName, gomock.Any()).Return(types.NetworkCreateResponse{}, nil),

// Pull the image if it's not available locally
docker.EXPECT().
ImageList(gomock.Any(), gomock.Any()).
Return([]types.ImageSummary{}, nil),
docker.EXPECT().
ImagePull(gomock.Any(), localEndpointsImageName, gomock.Any()).Return(pullImgResp, nil),

// Don't fetch the ID of the Local Container endpoints if it's the first time we are creating it.
docker.EXPECT().
ContainerCreate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), localEndpointsContainerName).
Expand All @@ -65,7 +75,7 @@ func TestSetup(t *testing.T) {
return docker
},
},
"existing network and existing container": {
"existing network, existing image, and existing container": {
configureCalls: func(docker *mock_network.MockLocalEndpointsStarter) *mock_network.MockLocalEndpointsStarter {
networkID := "abcd"
containerID := "1234"
Expand All @@ -75,6 +85,16 @@ func TestSetup(t *testing.T) {
Return(types.NetworkResource{ID: networkID}, nil),
docker.EXPECT().NetworkCreate(gomock.Any(), gomock.Any(), gomock.Any()).Times(0),

// Don't pull the image if it's downloaded
docker.EXPECT().
ImageList(gomock.Any(), gomock.Any()).
Return([]types.ImageSummary{
{
RepoTags: []string{"amazon/amazon-ecs-local-container-endpoints:latest"},
},
}, nil),
docker.EXPECT().ImagePull(gomock.Any(), gomock.Any(), gomock.Any()).Times(0),

// Retrieve the container ID if it already exists
docker.EXPECT().
ContainerCreate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), localEndpointsContainerName).
Expand Down