Skip to content
This repository was archived by the owner on Nov 19, 2025. It is now read-only.

Commit 8ef2ceb

Browse files
committed
Setup ECS local network and endpoints container on local up
1 parent 2b24dc7 commit 8ef2ceb

File tree

6 files changed

+476
-1
lines changed

6 files changed

+476
-1
lines changed

ecs-cli/modules/cli/local/local_app.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ package local
1717

1818
import (
1919
"fmt"
20+
21+
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/local/network"
22+
"github.com/docker/docker/client"
23+
"github.com/sirupsen/logrus"
2024
"github.com/urfave/cli"
2125
)
2226

@@ -26,3 +30,17 @@ func Create(c *cli.Context) {
2630
// 3. write to docker-compose.local.yml file
2731
fmt.Println("foo") // placeholder
2832
}
33+
34+
// Up creates a Compose file from an ECS task definition and runs it locally.
35+
//
36+
// The Amazon ECS Local Endpoints container needs to be running already for any local ECS task to work
37+
// (see https://github.com/awslabs/amazon-ecs-local-container-endpoints).
38+
// If the container is not running, this command creates a new network for all local ECS tasks to join
39+
// and communicate with the Amazon ECS Local Endpoints container.
40+
func Up(c *cli.Context) {
41+
docker, err := client.NewEnvClient() // Temporary client created to test network.Setup()
42+
if err != nil {
43+
logrus.Fatal("Could not connect to docker", err)
44+
}
45+
network.Setup(docker)
46+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2015-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package network
15+
16+
//go:generate mockgen.sh github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/local/network LocalEndpointsStarter mock_network/network.go

ecs-cli/modules/cli/local/network/mock_network/network.go

Lines changed: 125 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright 2015-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
// Package network provides functionality to setup and teardown the ECS local network.
15+
package network
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"strings"
21+
"time"
22+
23+
"github.com/docker/docker/api/types"
24+
"github.com/docker/docker/api/types/container"
25+
"github.com/docker/docker/api/types/filters"
26+
"github.com/docker/docker/api/types/network"
27+
"github.com/docker/docker/client"
28+
"github.com/sirupsen/logrus"
29+
"golang.org/x/net/context"
30+
)
31+
32+
// LocalEndpointsStarter groups Docker functions to create the ECS local network and
33+
// the Local Container Endpoints container if they don't exist.
34+
type LocalEndpointsStarter interface {
35+
networkCreator
36+
containerStarter
37+
}
38+
39+
type networkCreator interface {
40+
NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error)
41+
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
42+
}
43+
44+
type containerStarter interface {
45+
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
46+
ContainerCreate(ctx context.Context, config *container.Config,
47+
hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig,
48+
containerName string) (container.ContainerCreateCreatedBody, error)
49+
ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error
50+
}
51+
52+
// Configuration for the ECS local network.
53+
const (
54+
// EcsLocalNetworkName is the name of the network created for local ECS tasks to join.
55+
EcsLocalNetworkName = "ecs-local-network"
56+
57+
// Range of IP addresses that containers in the network get assigned.
58+
ecsLocalNetworkSubnet = "169.254.170.0/24"
59+
)
60+
61+
// Configuration for the Local Endpoints container.
62+
const (
63+
// Name of the image used to pull from DockerHub, see https://hub.docker.com/r/amazon/amazon-ecs-local-container-endpoints
64+
localEndpointsImageName = "amazon/amazon-ecs-local-container-endpoints"
65+
66+
// Reserved IP address that the container listens to answer requests on metadata, creds, and stats.
67+
localEndpointsContainerIpAddr = "169.254.170.2"
68+
69+
// Name of the container, we need to give it a name so that we don't re-create a container every time we setup.
70+
localEndpointsContainerName = "amazon-ecs-local-container-endpoints"
71+
)
72+
73+
// Configuration for Docker requests
74+
const (
75+
// Wait duration for a response from the Docker daemon before returning an error to the user.
76+
dockerTimeout = 30 * time.Second
77+
)
78+
79+
// Setup creates a user-defined bridge network with a running Local Container Endpoints container. If the network
80+
// already exists or the container is already running then the operation is a no-op.
81+
//
82+
// If there is any unexpected errors, we exit the program with a fatal log.
83+
func Setup(dockerClient LocalEndpointsStarter) {
84+
setupLocalNetwork(dockerClient)
85+
setupLocalEndpointsContainer(dockerClient)
86+
}
87+
88+
func setupLocalNetwork(dockerClient networkCreator) {
89+
if localNetworkExists(dockerClient) {
90+
logrus.Infof("The network %s already exists", EcsLocalNetworkName)
91+
return
92+
}
93+
createLocalNetwork(dockerClient)
94+
}
95+
96+
func localNetworkExists(dockerClient networkCreator) bool {
97+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
98+
defer cancel()
99+
100+
_, err := dockerClient.NetworkInspect(ctx, EcsLocalNetworkName, types.NetworkInspectOptions{})
101+
if err != nil {
102+
if client.IsErrNotFound(err) {
103+
return false
104+
}
105+
// Unexpected error while inspecting docker networks, we want to crash the app.
106+
logrus.Fatalf("Failed to inspect docker network %s due to %v", EcsLocalNetworkName, err)
107+
}
108+
return true
109+
}
110+
111+
func createLocalNetwork(dockerClient networkCreator) {
112+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
113+
defer cancel()
114+
115+
resp, err := dockerClient.NetworkCreate(ctx, EcsLocalNetworkName, types.NetworkCreate{
116+
IPAM: &network.IPAM{
117+
Config: []network.IPAMConfig{
118+
{
119+
Subnet: ecsLocalNetworkSubnet,
120+
},
121+
},
122+
},
123+
})
124+
if err != nil {
125+
logrus.Fatalf("Failed to create network %s with subnet %s due to %v", EcsLocalNetworkName, ecsLocalNetworkSubnet, err)
126+
}
127+
logrus.Infof("Created network %s with ID %s", EcsLocalNetworkName, resp.ID)
128+
}
129+
130+
func setupLocalEndpointsContainer(docker containerStarter) {
131+
containerID := createLocalEndpointsContainer(docker)
132+
startContainer(docker, containerID)
133+
}
134+
135+
// createLocalEndpointsContainer returns the ID of the newly created container.
136+
// If the container already exists, returns the ID of the existing container.
137+
func createLocalEndpointsContainer(dockerClient containerStarter) string {
138+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
139+
defer cancel()
140+
141+
// See First Scenario in https://aws.amazon.com/blogs/compute/a-guide-to-locally-testing-containers-with-amazon-ecs-local-endpoints-and-docker-compose/
142+
// for an explanation of these fields.
143+
resp, err := dockerClient.ContainerCreate(ctx,
144+
&container.Config{
145+
Image: localEndpointsImageName,
146+
Env: []string{
147+
"AWS_PROFILE=default",
148+
"HOME=/home",
149+
},
150+
},
151+
&container.HostConfig{
152+
Binds: []string{
153+
"/var/run:/var/run",
154+
fmt.Sprintf("%s/.aws/:/home/.aws/", os.Getenv("HOME")),
155+
},
156+
},
157+
&network.NetworkingConfig{
158+
EndpointsConfig: map[string]*network.EndpointSettings{
159+
EcsLocalNetworkName: {
160+
NetworkID: EcsLocalNetworkName,
161+
IPAMConfig: &network.EndpointIPAMConfig{
162+
IPv4Address: localEndpointsContainerIpAddr,
163+
},
164+
},
165+
},
166+
},
167+
localEndpointsContainerName,
168+
)
169+
if err != nil {
170+
if strings.Contains(err.Error(), "Conflict") {
171+
// We already created this container before since there is a name conflict, fetch its ID and return it.
172+
containerID := localEndpointsContainerID(dockerClient)
173+
logrus.Infof("The %s container already exists with ID %s", localEndpointsContainerName, containerID)
174+
return containerID
175+
}
176+
logrus.Fatalf("Failed to create container %s due to %v", localEndpointsContainerName, err)
177+
}
178+
179+
logrus.Infof("Created the %s container with ID %s", localEndpointsContainerName, resp.ID)
180+
return resp.ID
181+
}
182+
183+
func localEndpointsContainerID(dockerClient containerStarter) string {
184+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
185+
defer cancel()
186+
187+
resp, err := dockerClient.ContainerList(ctx, types.ContainerListOptions{
188+
All: true,
189+
Filters: filters.NewArgs(
190+
filters.Arg("name", localEndpointsContainerName),
191+
),
192+
})
193+
if err != nil {
194+
logrus.Fatalf("Failed to list containers with name %s due to %v", localEndpointsContainerName, err)
195+
}
196+
if len(resp) != 1 {
197+
logrus.Fatalf("Expected to find one container named %s but found %d", localEndpointsContainerName, len(resp))
198+
}
199+
return resp[0].ID
200+
}
201+
202+
func startContainer(dockerClient containerStarter, containerID string) {
203+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
204+
defer cancel()
205+
206+
// If the container is already running, Docker does not return an error response.
207+
if err := dockerClient.ContainerStart(ctx, containerID, types.ContainerStartOptions{}); err != nil {
208+
logrus.Fatalf("Failed to start container with ID %s due to %v", containerID, err)
209+
}
210+
logrus.Infof("Started container with ID %s", containerID)
211+
}

0 commit comments

Comments
 (0)