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

Commit c7c4620

Browse files
committed
Create the ECS local network on local up
1 parent afb3aae commit c7c4620

File tree

7 files changed

+531
-0
lines changed

7 files changed

+531
-0
lines changed

ecs-cli/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"os"
1818
"strings"
1919

20+
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/local"
21+
2022
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags"
2123

2224
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/factory"
@@ -80,6 +82,7 @@ func main() {
8082
attributecheckercommand.AttributecheckerCommand(),
8183
logsCommand.LogCommand(),
8284
regcredsCommand.RegistryCredsCommand(),
85+
local.LocalCommand(),
8386
}
8487

8588
app.Flags = []cli.Flag{
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 IdempotentLocalNetworkStarter 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: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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/client"
24+
25+
"github.com/docker/docker/api/types/container"
26+
27+
"github.com/docker/docker/api/types/filters"
28+
29+
"github.com/sirupsen/logrus"
30+
"golang.org/x/net/context"
31+
32+
"github.com/docker/docker/api/types/network"
33+
34+
"github.com/docker/docker/api/types"
35+
)
36+
37+
// IdempotentLocalNetworkStarter groups Docker functions to create the ECS local network and
38+
// the Local Container Endpoints container if they don't exist.
39+
type IdempotentLocalNetworkStarter interface {
40+
idempotentNetworkCreator
41+
idempotentContainerStarter
42+
}
43+
44+
type idempotentNetworkCreator interface {
45+
NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error)
46+
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
47+
}
48+
49+
type idempotentContainerStarter interface {
50+
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
51+
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error)
52+
ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error
53+
}
54+
55+
// Configuration for the ECS local network.
56+
const (
57+
// EcsLocalNetworkName is the name of the network created for local ECS tasks to join.
58+
EcsLocalNetworkName = "ecs-local-network"
59+
60+
// Range of IP addresses that containers in the network get assigned.
61+
ecsLocalNetworkSubnet = "169.254.170.0/24"
62+
)
63+
64+
// Configuration for the Local Endpoints container.
65+
const (
66+
// Name of the image used to pull from DockerHub, see https://hub.docker.com/r/amazon/amazon-ecs-local-container-endpoints
67+
localEndpointsImageName = "amazon/amazon-ecs-local-container-endpoints"
68+
69+
// Reserved IP address that the container listens to answer requests on metadata, creds, and stats.
70+
localEndpointsContainerIpAddr = "169.254.170.2"
71+
72+
// Name of the container, we need to give it a name so that we don't re-create a container every time we setup.
73+
localEndpointsContainerName = "amazon-ecs-local-container-endpoints"
74+
)
75+
76+
// Configuration for Docker requests
77+
const (
78+
// Wait duration for a response from the Docker daemon before returning an error to the user.
79+
dockerTimeout = 30 * time.Second
80+
)
81+
82+
// Setup creates a user-defined bridge network with a running Local Container Endpoints container. If the network
83+
// already exists or the container is already running then the operation is a no-op.
84+
//
85+
// If there is any unexpected errors, we exit the program with a fatal log.
86+
func Setup(docker IdempotentLocalNetworkStarter) {
87+
setupLocalNetwork(docker)
88+
setupLocalEndpointsContainer(docker)
89+
}
90+
91+
// setupLocalNetwork creates the local network if it doesn't already exist.
92+
func setupLocalNetwork(docker idempotentNetworkCreator) {
93+
if localNetworkExists(docker) {
94+
logrus.Infof("The network %s already exists", EcsLocalNetworkName)
95+
return
96+
}
97+
createLocalNetwork(docker)
98+
}
99+
100+
// localNetworkExists returns true if the local network already exists, false otherwise.
101+
func localNetworkExists(docker idempotentNetworkCreator) bool {
102+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
103+
defer cancel()
104+
105+
_, err := docker.NetworkInspect(ctx, EcsLocalNetworkName, types.NetworkInspectOptions{})
106+
if err != nil {
107+
if client.IsErrNotFound(err) {
108+
return false
109+
}
110+
// Unexpected error while inspecting docker networks, we want to crash the app.
111+
logrus.Fatalf("Failed to inspect docker network %s due to %v", EcsLocalNetworkName, err)
112+
}
113+
return true
114+
}
115+
116+
// createLocalNetwork creates the local network.
117+
func createLocalNetwork(docker idempotentNetworkCreator) {
118+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
119+
defer cancel()
120+
121+
resp, err := docker.NetworkCreate(ctx, EcsLocalNetworkName, types.NetworkCreate{
122+
IPAM: &network.IPAM{
123+
Config: []network.IPAMConfig{
124+
{
125+
Subnet: ecsLocalNetworkSubnet,
126+
},
127+
},
128+
},
129+
})
130+
if err != nil {
131+
logrus.Fatalf("Failed to create network %s with subnet %s due to %v", EcsLocalNetworkName, ecsLocalNetworkSubnet, err)
132+
}
133+
logrus.Infof("Created network %s with ID %s", EcsLocalNetworkName, resp.ID)
134+
}
135+
136+
// setupLocalEndpointsContainer creates the Local Endpoints container if one doesn't exist already.
137+
func setupLocalEndpointsContainer(docker idempotentContainerStarter) {
138+
containerID := createLocalEndpointsContainer(docker)
139+
startContainer(docker, containerID)
140+
}
141+
142+
// createLocalEndpointsContainer returns the ID of the newly created container.
143+
// If the container already exists, returns the ID of the existing container.
144+
func createLocalEndpointsContainer(docker idempotentContainerStarter) string {
145+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
146+
defer cancel()
147+
148+
// See First Scenario in https://aws.amazon.com/blogs/compute/a-guide-to-locally-testing-containers-with-amazon-ecs-local-endpoints-and-docker-compose/
149+
// for an explanation of these fields.
150+
resp, err := docker.ContainerCreate(ctx,
151+
&container.Config{
152+
Image: localEndpointsImageName,
153+
Env: []string{
154+
"AWS_PROFILE=default",
155+
"HOME=/home",
156+
},
157+
},
158+
&container.HostConfig{
159+
Binds: []string{
160+
"/var/run:/var/run",
161+
fmt.Sprintf("%s/.aws/:/home/.aws/", os.Getenv("HOME")),
162+
},
163+
},
164+
&network.NetworkingConfig{
165+
EndpointsConfig: map[string]*network.EndpointSettings{
166+
EcsLocalNetworkName: {
167+
NetworkID: EcsLocalNetworkName,
168+
IPAMConfig: &network.EndpointIPAMConfig{
169+
IPv4Address: localEndpointsContainerIpAddr,
170+
},
171+
},
172+
},
173+
},
174+
localEndpointsContainerName,
175+
)
176+
if err != nil {
177+
if strings.Contains(err.Error(), "Conflict") {
178+
// We already created this container before since there is a name conflict, fetch its ID and return it.
179+
containerID := localEndpointsContainerID(docker)
180+
logrus.Infof("The %s container already exists with ID %s", localEndpointsContainerName, containerID)
181+
return containerID
182+
}
183+
logrus.Fatalf("Failed to create container %s due to %v", localEndpointsContainerName, err)
184+
}
185+
186+
logrus.Infof("Created the %s container with ID %s", localEndpointsContainerName, resp.ID)
187+
return resp.ID
188+
}
189+
190+
// localEndpointsContainerID returns the ID of the Local Endpoints container.
191+
func localEndpointsContainerID(docker idempotentContainerStarter) string {
192+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
193+
defer cancel()
194+
195+
resp, err := docker.ContainerList(ctx, types.ContainerListOptions{
196+
All: true,
197+
Filters: filters.NewArgs(
198+
filters.Arg("name", localEndpointsContainerName),
199+
),
200+
})
201+
if err != nil {
202+
logrus.Fatalf("Failed to list containers with name %s due to %v", localEndpointsContainerName, err)
203+
}
204+
return resp[0].ID
205+
}
206+
207+
// startContainer starts the container with the containerID.
208+
// If the container is already running, Docker does not return an error response.
209+
func startContainer(docker idempotentContainerStarter, containerID string) {
210+
ctx, cancel := context.WithTimeout(context.Background(), dockerTimeout)
211+
defer cancel()
212+
213+
if err := docker.ContainerStart(ctx, containerID, types.ContainerStartOptions{}); err != nil {
214+
logrus.Fatalf("Failed to start container with ID %s due to %v", containerID, err)
215+
}
216+
logrus.Infof("Started container with ID %s", containerID)
217+
}

0 commit comments

Comments
 (0)