Skip to content

Commit

Permalink
Pull Endpoints image if it doesn't exist (#786)
Browse files Browse the repository at this point in the history
  • Loading branch information
efekarakus committed Jun 13, 2019
1 parent 32f8010 commit a6b978a
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 8 deletions.
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

0 comments on commit a6b978a

Please sign in to comment.