-
Notifications
You must be signed in to change notification settings - Fork 601
/
manager.go
176 lines (153 loc) · 7.22 KB
/
manager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright 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 containermetadata
import (
"context"
"encoding/json"
"fmt"
"os"
apitask "github.com/aws/amazon-ecs-agent/agent/api/task"
"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/dockerclient"
dockercontainer "github.com/docker/docker/api/types/container"
)
const (
// metadataEnvironmentVariable is the environment variable passed to the
// container for the metadata file path.
metadataEnvironmentVariable = "ECS_CONTAINER_METADATA_FILE"
metadataFile = "ecs-container-metadata.json"
metadataPerm = 0644
)
// Manager is an interface that allows us to abstract away the metadata
// operations
type Manager interface {
SetContainerInstanceARN(string)
SetAvailabilityZone(string)
SetHostPrivateIPv4Address(string)
SetHostPublicIPv4Address(string)
Create(*dockercontainer.Config, *dockercontainer.HostConfig, *apitask.Task, string, []string) error
Update(context.Context, string, *apitask.Task, string) error
Clean(string) error
}
// metadataManager implements the Manager interface
type metadataManager struct {
// client is the Docker API Client that the metadata manager uses. It defaults
// to 1.21 on Linux and 1.24 on Windows
client DockerMetadataClient
// cluster is the cluster where this agent is run
cluster string
// dataDir is the directory where the metadata is being written. For Linux
// this is a container directory
dataDir string
// dataDirOnHost is the directory from which dataDir is mounted for Linux
// version of the agent
dataDirOnHost string
// containerInstanceARN is the Container Instance ARN registered for this agent
containerInstanceARN string
// availabilityZone is the availabiltyZone where task is in
availabilityZone string
// hostPrivateIPv4Address is the private IPv4 address associated with the EC2 instance
hostPrivateIPv4Address string
// hostPublicIPv4Address is the public IPv4 address associated with the EC2 instance
hostPublicIPv4Address string
}
// NewManager creates a metadataManager for a given DockerTaskEngine settings.
func NewManager(client DockerMetadataClient, cfg *config.Config) Manager {
return &metadataManager{
client: client,
cluster: cfg.Cluster,
dataDir: cfg.DataDir,
dataDirOnHost: cfg.DataDirOnHost,
}
}
// SetContainerInstanceARN sets the metadataManager's ContainerInstanceArn which is not available
// at its creation as this information is not present immediately at the agent's startup
func (manager *metadataManager) SetContainerInstanceARN(containerInstanceARN string) {
manager.containerInstanceARN = containerInstanceARN
}
// SetAvailabilityzone sets the metadataManager's AvailabilityZone which is not available
// at its creation as this information is not present immediately at the agent's startup
func (manager *metadataManager) SetAvailabilityZone(availabilityZone string) {
manager.availabilityZone = availabilityZone
}
// SetHostPrivateIPv4Address sets the metadataManager's hostPrivateIPv4Address which is not available
// at its creation as this information is not present immediately at the agent's startup
func (manager *metadataManager) SetHostPrivateIPv4Address(ipv4address string) {
manager.hostPrivateIPv4Address = ipv4address
}
// SetHostPublicIPv4Address sets the metadataManager's hostPublicIPv4Address which is not available
// at its creation as this information is not present immediately at the agent's startup
func (manager *metadataManager) SetHostPublicIPv4Address(ipv4address string) {
manager.hostPublicIPv4Address = ipv4address
}
var mkdirAll = os.MkdirAll
// Create creates the metadata file and adds the metadata directory to
// the container's mounted host volumes
// Pointer hostConfig is modified directly so there is risk of concurrency errors.
func (manager *metadataManager) Create(config *dockercontainer.Config, hostConfig *dockercontainer.HostConfig,
task *apitask.Task, containerName string, dockerSecurityOptions []string) error {
// Create task and container directories if they do not yet exist
metadataDirectoryPath, err := getMetadataFilePath(task.Arn, containerName, manager.dataDir)
// Stop metadata creation if path is malformed for any reason
if err != nil {
return fmt.Errorf("container metadata create for task %s container %s: %v", task.Arn, containerName, err)
}
err = mkdirAll(metadataDirectoryPath, os.ModePerm)
if err != nil {
return fmt.Errorf("creating metadata directory for task %s: %v", task.Arn, err)
}
// Acquire the metadata then write it in JSON format to the file
metadata := manager.parseMetadataAtContainerCreate(task, containerName)
err = manager.marshalAndWrite(metadata, task.Arn, containerName)
if err != nil {
return err
}
// Add the directory of this container's metadata to the container's mount binds
// Then add the destination directory as an environment variable in the container $METADATA
binds, env := createBindsEnv(hostConfig.Binds, config.Env, manager.dataDirOnHost, metadataDirectoryPath, dockerSecurityOptions)
config.Env = env
hostConfig.Binds = binds
return nil
}
// Update updates the metadata file after container starts and dynamic metadata is available
func (manager *metadataManager) Update(ctx context.Context, dockerID string, task *apitask.Task, containerName string) error {
// Get docker container information through api call
dockerContainer, err := manager.client.InspectContainer(ctx, dockerID, dockerclient.InspectContainerTimeout)
if err != nil {
return err
}
// Ensure we do not update a container that is invalid or is not running
if dockerContainer == nil || !dockerContainer.State.Running {
return fmt.Errorf("container metadata update for container %s in task %s: container not running or invalid", containerName, task.Arn)
}
// Acquire the metadata then write it in JSON format to the file
metadata := manager.parseMetadata(dockerContainer, task, containerName)
return manager.marshalAndWrite(metadata, task.Arn, containerName)
}
var removeAll = os.RemoveAll
// Clean removes the metadata files of all containers associated with a task
func (manager *metadataManager) Clean(taskARN string) error {
metadataPath, err := getTaskMetadataDir(taskARN, manager.dataDir)
if err != nil {
return fmt.Errorf("clean task metadata: unable to get metadata directory for task %s: %v", taskARN, err)
}
return removeAll(metadataPath)
}
func (manager *metadataManager) marshalAndWrite(metadata Metadata, taskARN string, containerName string) error {
data, err := json.MarshalIndent(metadata, "", "\t")
if err != nil {
return fmt.Errorf("create metadata for container %s in task %s: failed to marshal metadata: %v", containerName, taskARN, err)
}
// Write the metadata to file
return writeToMetadataFile(data, taskARN, containerName, manager.dataDir)
}