Skip to content

Commit 8f54fc6

Browse files
committed
ECS local tags support
Introduce ECS_CONTAINER_INSTANCE_TAGS environment variable, add tags option to the RegisterContainerInstance API, and register the tags extraced from the instance with the RCI call.
1 parent d888811 commit 8f54fc6

15 files changed

+243
-38
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ additional details on each available environment variable.
120120
| `ECS_NUM_IMAGES_DELETE_PER_CYCLE` | 5 | The maximum number of images to delete in a single automated image cleanup cycle. If set to less than 1, the value is ignored. | 5 | 5 |
121121
| `ECS_IMAGE_PULL_BEHAVIOR` | <default | always | once | prefer-cached > | The behavior used to customize the pull image process. If `default` is specified, the image will be pulled remotely, if the pull fails then the cached image in the instance will be used. If `always` is specified, the image will be pulled remotely, if the pull fails then the task will fail. If `once` is specified, the image will be pulled remotely if it has not been pulled before or if the image was removed by image cleanup, otherwise the cached image in the instance will be used. If `prefer-cached` is specified, the image will be pulled remotely if there is no cached image, otherwise the cached image in the instance will be used. | default | default |
122122
| `ECS_INSTANCE_ATTRIBUTES` | `{"stack": "prod"}` | These attributes take effect only during initial registration. After the agent has joined an ECS cluster, use the PutAttributes API action to add additional attributes. For more information, see [Amazon ECS Container Agent Configuration](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-config.html) in the Amazon ECS Developer Guide.| `{}` | `{}` |
123+
| `ECS_CONTAINER_INSTANCE_TAGS` | `{"tag_key": "tag_val"}` | Tags that will be registered when the instance starts. For more information, see [Amazon ECS Container Agent Configuration](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-config.html) in the Amazon ECS Developer Guide.| `{}` | `{}` |
123124
| `ECS_ENABLE_TASK_ENI` | `false` | Whether to enable task networking for task to be launched with its own network interface | `false` | Not applicable |
124125
| `ECS_CNI_PLUGINS_PATH` | `/ecs/cni` | The path where the cni binary file is located | `/amazon-ecs-cni-plugins` | Not applicable |
125126
| `ECS_AWSVPC_BLOCK_IMDS` | `true` | Whether to block access to [Instance Metadata](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) for Tasks started with `awsvpc` network mode | `false` | Not applicable |

agent/api/ecsclient/client.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ func (client *APIECSClient) CreateCluster(clusterName string) (string, error) {
107107
// ContainerInstanceARN if successful. Supplying a non-empty container
108108
// instance ARN allows a container instance to update its registered
109109
// resources.
110-
func (client *APIECSClient) RegisterContainerInstance(containerInstanceArn string, attributes []*ecs.Attribute) (string, error) {
110+
func (client *APIECSClient) RegisterContainerInstance(containerInstanceArn string,
111+
attributes []*ecs.Attribute, tags []*ecs.Tag) (string, error) {
111112
clusterRef := client.config.Cluster
112113
// If our clusterRef is empty, we should try to create the default
113114
if clusterRef == "" {
@@ -118,7 +119,7 @@ func (client *APIECSClient) RegisterContainerInstance(containerInstanceArn strin
118119
}()
119120
// Attempt to register without checking existence of the cluster so we don't require
120121
// excess permissions in the case where the cluster already exists and is active
121-
containerInstanceArn, err := client.registerContainerInstance(clusterRef, containerInstanceArn, attributes)
122+
containerInstanceArn, err := client.registerContainerInstance(clusterRef, containerInstanceArn, attributes, tags)
122123
if err == nil {
123124
return containerInstanceArn, nil
124125
}
@@ -129,10 +130,11 @@ func (client *APIECSClient) RegisterContainerInstance(containerInstanceArn strin
129130
return "", err
130131
}
131132
}
132-
return client.registerContainerInstance(clusterRef, containerInstanceArn, attributes)
133+
return client.registerContainerInstance(clusterRef, containerInstanceArn, attributes, tags)
133134
}
134135

135-
func (client *APIECSClient) registerContainerInstance(clusterRef string, containerInstanceArn string, attributes []*ecs.Attribute) (string, error) {
136+
func (client *APIECSClient) registerContainerInstance(clusterRef string, containerInstanceArn string,
137+
attributes []*ecs.Attribute, tags []*ecs.Tag) (string, error) {
136138
registerRequest := ecs.RegisterContainerInstanceInput{Cluster: &clusterRef}
137139
var registrationAttributes []*ecs.Attribute
138140
if containerInstanceArn != "" {
@@ -155,6 +157,7 @@ func (client *APIECSClient) registerContainerInstance(clusterRef string, contain
155157
// Add additional attributes such as the os type
156158
registrationAttributes = append(registrationAttributes, client.getAdditionalAttributes()...)
157159
registerRequest.Attributes = registrationAttributes
160+
registerRequest.Tags = tags
158161
registerRequest = client.setInstanceIdentity(registerRequest)
159162

160163
resources, err := client.getResources()

agent/api/ecsclient/client_test.go

+41-7
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,16 @@ const (
5050
)
5151

5252
var (
53-
iidResponse = []byte(iid)
54-
iidSignatureResponse = []byte(iidSignature)
53+
iidResponse = []byte(iid)
54+
iidSignatureResponse = []byte(iidSignature)
55+
containerInstanceTags = []*ecs.Tag{
56+
{Key: aws.String("my_key1"), Value: aws.String("my_val1")},
57+
{Key: aws.String("my_key2"), Value: aws.String("my_val2")},
58+
}
59+
containerInstanceTagsMap = map[string]string{
60+
"my_key1": "my_val1",
61+
"my_key2": "my_val2",
62+
}
5563
)
5664

5765
func NewMockClient(ctrl *gomock.Controller,
@@ -347,6 +355,12 @@ func TestReRegisterContainerInstance(t *testing.T) {
347355
assert.Contains(t, expectedAttributes, k)
348356
assert.Equal(t, expectedAttributes[k], v)
349357
}
358+
assert.Equal(t, len(containerInstanceTags), len(req.Tags), "Wrong number of Tags")
359+
reqTags := extractTagsMapFromRegisterContainerInstanceInput(req)
360+
for k, v := range reqTags {
361+
assert.Contains(t, containerInstanceTagsMap, k)
362+
assert.Equal(t, containerInstanceTagsMap[k], v)
363+
}
350364
}).Return(&ecs.RegisterContainerInstanceOutput{
351365
ContainerInstance: &ecs.ContainerInstance{
352366
ContainerInstanceArn: aws.String("registerArn"),
@@ -355,7 +369,7 @@ func TestReRegisterContainerInstance(t *testing.T) {
355369
nil),
356370
)
357371

358-
arn, err := client.RegisterContainerInstance("arn:test", capabilities)
372+
arn, err := client.RegisterContainerInstance("arn:test", capabilities, containerInstanceTags)
359373
if err != nil {
360374
t.Errorf("Should not be an error: %v", err)
361375
}
@@ -402,14 +416,20 @@ func TestRegisterContainerInstance(t *testing.T) {
402416
assert.Equal(t, expectedAttributes[*req.Attributes[i].Name], *req.Attributes[i].Value)
403417
}
404418
}
419+
assert.Equal(t, len(containerInstanceTags), len(req.Tags), "Wrong number of Tags")
420+
reqTags := extractTagsMapFromRegisterContainerInstanceInput(req)
421+
for k, v := range reqTags {
422+
assert.Contains(t, containerInstanceTagsMap, k)
423+
assert.Equal(t, containerInstanceTagsMap[k], v)
424+
}
405425
}).Return(&ecs.RegisterContainerInstanceOutput{
406426
ContainerInstance: &ecs.ContainerInstance{
407427
ContainerInstanceArn: aws.String("registerArn"),
408428
Attributes: buildAttributeList(fakeCapabilities, expectedAttributes)}},
409429
nil),
410430
)
411431

412-
arn, err := client.RegisterContainerInstance("", capabilities)
432+
arn, err := client.RegisterContainerInstance("", capabilities, containerInstanceTags)
413433
assert.NoError(t, err)
414434
assert.Equal(t, "registerArn", arn)
415435
}
@@ -456,14 +476,20 @@ func TestRegisterContainerInstanceNoIID(t *testing.T) {
456476
assert.Equal(t, expectedAttributes[*req.Attributes[i].Name], *req.Attributes[i].Value)
457477
}
458478
}
479+
assert.Equal(t, len(containerInstanceTags), len(req.Tags), "Wrong number of Tags")
480+
reqTags := extractTagsMapFromRegisterContainerInstanceInput(req)
481+
for k, v := range reqTags {
482+
assert.Contains(t, containerInstanceTagsMap, k)
483+
assert.Equal(t, containerInstanceTagsMap[k], v)
484+
}
459485
}).Return(&ecs.RegisterContainerInstanceOutput{
460486
ContainerInstance: &ecs.ContainerInstance{
461487
ContainerInstanceArn: aws.String("registerArn"),
462488
Attributes: buildAttributeList(fakeCapabilities, expectedAttributes)}},
463489
nil),
464490
)
465491

466-
arn, err := client.RegisterContainerInstance("", capabilities)
492+
arn, err := client.RegisterContainerInstance("", capabilities, containerInstanceTags)
467493
assert.NoError(t, err)
468494
assert.Equal(t, "registerArn", arn)
469495
}
@@ -489,7 +515,7 @@ func TestRegisterContainerInstanceWithNegativeResource(t *testing.T) {
489515
mockEC2Metadata.EXPECT().GetDynamicData(ec2.InstanceIdentityDocumentResource).Return("instanceIdentityDocument", nil),
490516
mockEC2Metadata.EXPECT().GetDynamicData(ec2.InstanceIdentityDocumentSignatureResource).Return("signature", nil),
491517
)
492-
_, err := client.RegisterContainerInstance("", nil)
518+
_, err := client.RegisterContainerInstance("", nil, nil)
493519
assert.Error(t, err, "Register resource with negative value should cause registration fail")
494520
}
495521

@@ -563,7 +589,7 @@ func TestRegisterBlankCluster(t *testing.T) {
563589
nil),
564590
)
565591

566-
arn, err := client.RegisterContainerInstance("", nil)
592+
arn, err := client.RegisterContainerInstance("", nil, nil)
567593
if err != nil {
568594
t.Errorf("Should not be an error: %v", err)
569595
}
@@ -823,3 +849,11 @@ func TestSubmitContainerStateChangeWhileTaskInPending(t *testing.T) {
823849
})
824850
}
825851
}
852+
853+
func extractTagsMapFromRegisterContainerInstanceInput(req *ecs.RegisterContainerInstanceInput) map[string]string {
854+
tagsMap := make(map[string]string, len(req.Tags))
855+
for i := range req.Tags {
856+
tagsMap[*req.Tags[i].Key] = aws.StringValue(req.Tags[i].Value)
857+
}
858+
return tagsMap
859+
}

agent/api/interface.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
1+
// Copyright 2014-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License"). You may
44
// not use this file except in compliance with the License. A copy of the
@@ -26,7 +26,8 @@ type ECSClient interface {
2626
// ContainerInstanceARN if successful. Supplying a non-empty container
2727
// instance ARN allows a container instance to update its registered
2828
// resources.
29-
RegisterContainerInstance(existingContainerInstanceArn string, attributes []*ecs.Attribute) (string, error)
29+
RegisterContainerInstance(existingContainerInstanceArn string,
30+
attributes []*ecs.Attribute, tags []*ecs.Tag) (string, error)
3031
// SubmitTaskStateChange sends a state change and returns an error
3132
// indicating if it was submitted
3233
SubmitTaskStateChange(change TaskStateChange) error

agent/api/mocks/api_mocks.go

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/app/agent.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -464,13 +464,15 @@ func (agent *ecsAgent) registerContainerInstance(
464464
}
465465
capabilities := append(agentCapabilities, additionalAttributes...)
466466

467+
tags := agent.getContainerInstanceTagsFromLocal()
468+
467469
if agent.containerInstanceARN != "" {
468470
seelog.Infof("Restored from checkpoint file. I am running as '%s' in cluster '%s'", agent.containerInstanceARN, agent.cfg.Cluster)
469-
return agent.reregisterContainerInstance(client, capabilities)
471+
return agent.reregisterContainerInstance(client, capabilities, tags)
470472
}
471473

472474
seelog.Info("Registering Instance with ECS")
473-
containerInstanceArn, err := client.RegisterContainerInstance("", capabilities)
475+
containerInstanceArn, err := client.RegisterContainerInstance("", capabilities, tags)
474476
if err != nil {
475477
seelog.Errorf("Error registering: %v", err)
476478
if retriable, ok := err.(apierrors.Retriable); ok && !retriable.Retry() {
@@ -496,8 +498,9 @@ func (agent *ecsAgent) registerContainerInstance(
496498
// reregisterContainerInstance registers a container instance that has already been
497499
// registered with ECS. This is for cases where the ECS Agent is being restored
498500
// from a check point.
499-
func (agent *ecsAgent) reregisterContainerInstance(client api.ECSClient, capabilities []*ecs.Attribute) error {
500-
_, err := client.RegisterContainerInstance(agent.containerInstanceARN, capabilities)
501+
func (agent *ecsAgent) reregisterContainerInstance(client api.ECSClient,
502+
capabilities []*ecs.Attribute, tags []*ecs.Tag) error {
503+
_, err := client.RegisterContainerInstance(agent.containerInstanceARN, capabilities, tags)
501504
if err == nil {
502505
return nil
503506
}
@@ -614,3 +617,15 @@ func (agent *ecsAgent) verifyRequiredDockerVersion() (int, bool) {
614617
dockerclient.Version_1_21)
615618
return exitcodes.ExitTerminal, false
616619
}
620+
621+
// Get the tags of this container instance defined in config file
622+
func (agent *ecsAgent) getContainerInstanceTagsFromLocal() []*ecs.Tag {
623+
var tags []*ecs.Tag
624+
for key, value := range agent.cfg.ContainerInstanceTags {
625+
tags = append(tags, &ecs.Tag{
626+
Key: aws.String(key),
627+
Value: aws.String(value),
628+
})
629+
}
630+
return tags
631+
}

agent/app/agent_test.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func TestDoStartRegisterContainerInstanceErrorTerminal(t *testing.T) {
232232
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{""}, nil),
233233
dockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(), gomock.Any(),
234234
gomock.Any()).AnyTimes().Return([]string{}, nil),
235-
client.EXPECT().RegisterContainerInstance(gomock.Any(), gomock.Any()).Return(
235+
client.EXPECT().RegisterContainerInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(
236236
"", apierrors.NewAttributeError("error")),
237237
)
238238

@@ -269,7 +269,7 @@ func TestDoStartRegisterContainerInstanceErrorNonTerminal(t *testing.T) {
269269
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{""}, nil),
270270
dockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(), gomock.Any(),
271271
gomock.Any()).AnyTimes().Return([]string{}, nil),
272-
client.EXPECT().RegisterContainerInstance(gomock.Any(), gomock.Any()).Return(
272+
client.EXPECT().RegisterContainerInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(
273273
"", errors.New("error")),
274274
)
275275

@@ -590,7 +590,7 @@ func TestReregisterContainerInstanceHappyPath(t *testing.T) {
590590
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{""}, nil),
591591
mockDockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(),
592592
gomock.Any(), gomock.Any()).AnyTimes().Return([]string{}, nil),
593-
client.EXPECT().RegisterContainerInstance(containerInstanceARN, gomock.Any()).Return(containerInstanceARN, nil),
593+
client.EXPECT().RegisterContainerInstance(containerInstanceARN, gomock.Any(), gomock.Any()).Return(containerInstanceARN, nil),
594594
)
595595
cfg := getTestConfig()
596596
cfg.Cluster = clusterName
@@ -627,7 +627,7 @@ func TestReregisterContainerInstanceInstanceTypeChanged(t *testing.T) {
627627
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{""}, nil),
628628
mockDockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(),
629629
gomock.Any(), gomock.Any()).AnyTimes().Return([]string{}, nil),
630-
client.EXPECT().RegisterContainerInstance(containerInstanceARN, gomock.Any()).Return(
630+
client.EXPECT().RegisterContainerInstance(containerInstanceARN, gomock.Any(), gomock.Any()).Return(
631631
"", awserr.New("", apierrors.InstanceTypeChangedErrorMessage, errors.New(""))),
632632
)
633633

@@ -667,7 +667,7 @@ func TestReregisterContainerInstanceAttributeError(t *testing.T) {
667667
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{}, nil),
668668
mockDockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(),
669669
gomock.Any(), gomock.Any()).AnyTimes().Return([]string{}, nil),
670-
client.EXPECT().RegisterContainerInstance(containerInstanceARN, gomock.Any()).Return(
670+
client.EXPECT().RegisterContainerInstance(containerInstanceARN, gomock.Any(), gomock.Any()).Return(
671671
"", apierrors.NewAttributeError("error")),
672672
)
673673

@@ -707,7 +707,7 @@ func TestReregisterContainerInstanceNonTerminalError(t *testing.T) {
707707
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{}, nil),
708708
mockDockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(),
709709
gomock.Any(), gomock.Any()).AnyTimes().Return([]string{}, nil),
710-
client.EXPECT().RegisterContainerInstance(containerInstanceARN, gomock.Any()).Return(
710+
client.EXPECT().RegisterContainerInstance(containerInstanceARN, gomock.Any(), gomock.Any()).Return(
711711
"", errors.New("error")),
712712
)
713713

@@ -747,7 +747,7 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetHappyPath(t *t
747747
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{}, nil),
748748
mockDockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(),
749749
gomock.Any(), gomock.Any()).AnyTimes().Return([]string{}, nil),
750-
client.EXPECT().RegisterContainerInstance("", gomock.Any()).Return(containerInstanceARN, nil),
750+
client.EXPECT().RegisterContainerInstance("", gomock.Any(), gomock.Any()).Return(containerInstanceARN, nil),
751751
stateManager.EXPECT().Save(),
752752
)
753753

@@ -787,7 +787,7 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetCanRetryError(
787787
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{}, nil),
788788
mockDockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(),
789789
gomock.Any(), gomock.Any()).AnyTimes().Return([]string{}, nil),
790-
client.EXPECT().RegisterContainerInstance("", gomock.Any()).Return("", retriableError),
790+
client.EXPECT().RegisterContainerInstance("", gomock.Any(), gomock.Any()).Return("", retriableError),
791791
)
792792

793793
cfg := getTestConfig()
@@ -826,7 +826,7 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetCannotRetryErr
826826
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{}, nil),
827827
mockDockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(),
828828
gomock.Any(), gomock.Any()).AnyTimes().Return([]string{}, nil),
829-
client.EXPECT().RegisterContainerInstance("", gomock.Any()).Return("", cannotRetryError),
829+
client.EXPECT().RegisterContainerInstance("", gomock.Any(), gomock.Any()).Return("", cannotRetryError),
830830
)
831831

832832
cfg := getTestConfig()
@@ -864,7 +864,7 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetAttributeError
864864
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{}, nil),
865865
mockDockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(),
866866
gomock.Any(), gomock.Any()).AnyTimes().Return([]string{}, nil),
867-
client.EXPECT().RegisterContainerInstance("", gomock.Any()).Return(
867+
client.EXPECT().RegisterContainerInstance("", gomock.Any(), gomock.Any()).Return(
868868
"", apierrors.NewAttributeError("error")),
869869
)
870870

@@ -902,7 +902,7 @@ func TestRegisterContainerInstanceInvalidParameterTerminalError(t *testing.T) {
902902
mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{}, nil),
903903
dockerClient.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(), gomock.Any(),
904904
gomock.Any()).AnyTimes().Return([]string{}, nil),
905-
client.EXPECT().RegisterContainerInstance(gomock.Any(), gomock.Any()).Return(
905+
client.EXPECT().RegisterContainerInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(
906906
"", awserr.New("InvalidParameterException", "", nil)),
907907
)
908908

0 commit comments

Comments
 (0)