Skip to content

Commit c397d64

Browse files
shogondodanehlim
andauthored
Introduce ECS_PAUSE_LABELS env var to apply labels to pause containers (#4789)
* Introduce ECS_PAUSE_LABELS env var to apply labels to pause containers (#4427) --------- Co-authored-by: Dane H Lim <slimdane@amazon.com>
1 parent 44f8eb6 commit c397d64

File tree

160 files changed

+67674
-34
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

160 files changed

+67674
-34
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ additional details about how to configure the agent.
259259
| `ECS_EBSTA_SUPPORTED` | `true` | Whether to use the container instance with EBS Task Attach support. This variable is set properly by ecs-init. Its value indicates if correct environment to support EBS volumes by instance has been set up or not. ECS only schedules EBSTA tasks if this feature is supported by the platform type. Check [EBS Volume considerations](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ebs-volumes.html#ebs-volume-considerations) for other EBS support details | `true` | Not Supported on Windows |
260260
| `ECS_ENABLE_FIRELENS_ASYNC` | `true` | Whether the log driver connects to the Firelens container in the background. | `true` | `true` |
261261
| `ECS_DETAILED_OS_FAMILY` | `debian_11` | Sets detailed OS information for Linux-based ECS instances by parsing /etc/os-release. This variable is set properly by ecs-init during system initialization. | `linux` | Not supported on Windows |
262+
| `ECS_PAUSE_LABELS` | `{"test.pause.label.1":"value1","test.pause.label.2":"value2"}` | The labels to add to the pause container. | | |
262263

263264
Additionally, the following environment variable(s) can be used to configure the behavior of the ecs-init service. When using ECS-Init, all env variables, including the ECS Agent variables above, are read from path `/etc/ecs/ecs.config`:
264265
| Environment Variable Name | Example Value(s) | Description | Default value |

agent/api/task/task.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"context"
1818
"encoding/json"
1919
"fmt"
20+
"maps"
21+
"os"
2022
"path/filepath"
2123
"reflect"
2224
"strconv"
@@ -163,6 +165,8 @@ const (
163165
serviceConnectAttachmentType = "serviceconnectdetail"
164166

165167
ipv6LoopbackAddress = "::1"
168+
169+
pauseLabelsEnvVar = "ECS_PAUSE_LABELS"
166170
)
167171

168172
// TaskOverrides are the overrides applied to a task
@@ -1811,6 +1815,14 @@ func (task *Task) dockerConfig(container *apicontainer.Container, apiVersion doc
18111815
containerConfig.Labels = make(map[string]string)
18121816
}
18131817

1818+
switch container.Type {
1819+
case apicontainer.ContainerCNIPause, apicontainer.ContainerNamespacePause:
1820+
if pauseLabels := os.Getenv(pauseLabelsEnvVar); pauseLabels != "" {
1821+
// Set labels to pause container if it's provided as env var.
1822+
setLabelsFromJSONString(containerConfig, pauseLabels)
1823+
}
1824+
}
1825+
18141826
if container.Type == apicontainer.ContainerCNIPause && task.IsNetworkModeAWSVPC() {
18151827
// apply hostname to pause container's docker config
18161828
return task.applyENIHostname(containerConfig), nil
@@ -1819,6 +1831,23 @@ func (task *Task) dockerConfig(container *apicontainer.Container, apiVersion doc
18191831
return containerConfig, nil
18201832
}
18211833

1834+
// Parse label string and set them to the given container configuration.
1835+
// This function is intended to only be used with container configuration whose `Labels` field is not nil.
1836+
func setLabelsFromJSONString(config *dockercontainer.Config, labelsString string) {
1837+
if len(labelsString) > 0 {
1838+
labels, err := commonutils.JsonBlockToStringToStringMap(labelsString)
1839+
if err != nil {
1840+
logger.Warn("Skipping setting labels because received error decoding labels string", logger.Fields{
1841+
field.Error: err,
1842+
})
1843+
return
1844+
}
1845+
if len(labels) > 0 {
1846+
maps.Copy(config.Labels, labels)
1847+
}
1848+
}
1849+
}
1850+
18221851
// dockerExposedPorts returns the container ports that need to be exposed for a container
18231852
// 1. For bridge-mode ServiceConnect-enabled tasks:
18241853
// 1a. Pause containers need to expose the port(s) for their associated task container. In particular, SC pause container

agent/api/task/task_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5924,3 +5924,120 @@ func TestGenerateENIExtraHosts(t *testing.T) {
59245924
})
59255925
}
59265926
}
5927+
5928+
func TestDockerConfigPauseContainerLabelsWithoutEnvVar_ECS_PAUSE_LABELS(t *testing.T) {
5929+
testTask := &Task{
5930+
Arn: "arn:aws:ecs:us-east-1:012345678910:task/c09f0188-7f87-4b0f-bfc3-16296622b6fe",
5931+
Family: "myFamily",
5932+
Version: "1",
5933+
Containers: []*apicontainer.Container{
5934+
{
5935+
Name: "pause",
5936+
Type: apicontainer.ContainerCNIPause,
5937+
},
5938+
},
5939+
}
5940+
5941+
config, configErr := testTask.DockerConfig(testTask.Containers[0], defaultDockerClientAPIVersion)
5942+
if configErr != nil {
5943+
t.Fatal(configErr)
5944+
}
5945+
5946+
assert.Equal(t, 0, len(config.Labels))
5947+
}
5948+
5949+
func TestDockerConfigPauseContainerLabelsWithEnvVar_ECS_PAUSE_LABELS(t *testing.T) {
5950+
testTask := &Task{
5951+
Arn: "arn:aws:ecs:us-east-1:012345678910:task/c09f0188-7f87-4b0f-bfc3-16296622b6fe",
5952+
Family: "myFamily",
5953+
Version: "1",
5954+
Containers: []*apicontainer.Container{
5955+
{
5956+
Name: "pause",
5957+
Type: apicontainer.ContainerCNIPause,
5958+
},
5959+
},
5960+
}
5961+
labelsString := "{\"test.label.1\":\"test_a\",\"test.label.2\":\"test_b\"}"
5962+
t.Setenv(pauseLabelsEnvVar, labelsString)
5963+
5964+
config, configErr := testTask.DockerConfig(testTask.Containers[0], defaultDockerClientAPIVersion)
5965+
if configErr != nil {
5966+
t.Fatal(configErr)
5967+
}
5968+
5969+
assert.Equal(t, 2, len(config.Labels))
5970+
assert.Equal(t, "test_a", config.Labels["test.label.1"])
5971+
assert.Equal(t, "test_b", config.Labels["test.label.2"])
5972+
}
5973+
5974+
func TestDockerConfigNamespacePauseContainerLabelsWithEnvVar_ECS_PAUSE_LABELS(t *testing.T) {
5975+
testTask := &Task{
5976+
Arn: "arn:aws:ecs:us-east-1:012345678910:task/c09f0188-7f87-4b0f-bfc3-16296622b6fe",
5977+
Family: "myFamily",
5978+
Version: "1",
5979+
Containers: []*apicontainer.Container{
5980+
{
5981+
Name: "pause",
5982+
Type: apicontainer.ContainerNamespacePause,
5983+
},
5984+
},
5985+
}
5986+
labelsString := "{\"test.label.1\":\"test_a\",\"test.label.2\":\"test_b\"}"
5987+
t.Setenv(pauseLabelsEnvVar, labelsString)
5988+
5989+
config, configErr := testTask.DockerConfig(testTask.Containers[0], defaultDockerClientAPIVersion)
5990+
if configErr != nil {
5991+
t.Fatal(configErr)
5992+
}
5993+
5994+
assert.Equal(t, 2, len(config.Labels))
5995+
assert.Equal(t, "test_a", config.Labels["test.label.1"])
5996+
assert.Equal(t, "test_b", config.Labels["test.label.2"])
5997+
}
5998+
5999+
func TestDockerConfigPauseContainerLabelsWithInvalidEnvVar_ECS_PAUSE_LABELS(t *testing.T) {
6000+
testTask := &Task{
6001+
Arn: "arn:aws:ecs:us-east-1:012345678910:task/c09f0188-7f87-4b0f-bfc3-16296622b6fe",
6002+
Family: "myFamily",
6003+
Version: "1",
6004+
Containers: []*apicontainer.Container{
6005+
{
6006+
Name: "pause",
6007+
Type: apicontainer.ContainerCNIPause,
6008+
},
6009+
},
6010+
}
6011+
// Invalid format.
6012+
labelsString := "{\"test.label\":\"test\""
6013+
t.Setenv(pauseLabelsEnvVar, labelsString)
6014+
6015+
config, configErr := testTask.DockerConfig(testTask.Containers[0], defaultDockerClientAPIVersion)
6016+
if configErr != nil {
6017+
t.Fatal(configErr)
6018+
}
6019+
6020+
assert.Equal(t, 0, len(config.Labels))
6021+
}
6022+
6023+
func TestDockerConfigContainerLabelsWithEnvVar_ECS_PAUSE_LABELS(t *testing.T) {
6024+
testTask := &Task{
6025+
Arn: "arn:aws:ecs:us-east-1:012345678910:task/c09f0188-7f87-4b0f-bfc3-16296622b6fe",
6026+
Family: "myFamily",
6027+
Version: "1",
6028+
Containers: []*apicontainer.Container{
6029+
{
6030+
Name: "c1",
6031+
CPU: uint(10),
6032+
Memory: uint(256),
6033+
},
6034+
},
6035+
}
6036+
6037+
config, configErr := testTask.DockerConfig(testTask.Containers[0], defaultDockerClientAPIVersion)
6038+
if configErr != nil {
6039+
t.Fatal(configErr)
6040+
}
6041+
6042+
assert.Equal(t, 0, len(config.Labels))
6043+
}

agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/utils/utils.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ecs-agent/utils/utils.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package utils
1414

1515
import (
16+
"encoding/json"
1617
"math"
1718
"reflect"
1819
"regexp"
@@ -158,3 +159,10 @@ func ToPtrSlice[V any](xs []V) []*V {
158159
}
159160
return result
160161
}
162+
163+
// JsonBlockToStringToStringMap converts a JSON block string to a string-to-string map.
164+
func JsonBlockToStringToStringMap(jsonBlock string) (map[string]string, error) {
165+
out := map[string]string{}
166+
err := json.Unmarshal([]byte(jsonBlock), &out)
167+
return out, err
168+
}

ecs-agent/utils/utils_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,20 @@ func TestToPtrSlice(t *testing.T) {
393393
}
394394
})
395395
}
396+
397+
func TestJsonBlockToStringToStringMap(t *testing.T) {
398+
testData := `{"test.label.1":"value1","test.label.2":"value2"}`
399+
out, err := JsonBlockToStringToStringMap(testData)
400+
require.NoError(t, err, "Expected no error while decoding JSON block")
401+
require.Equal(t, "value1", out["test.label.1"])
402+
403+
for key, value := range out {
404+
t.Logf("Key: %s %T | Value: %s %T", key, key, value, value)
405+
}
406+
}
407+
408+
func TestJsonBlockToStringToStringMapBadData(t *testing.T) {
409+
testData := `{"something":[{"test.label.1":"value1"},{"test.label.2":"value2"}]}`
410+
_, err := JsonBlockToStringToStringMap(testData)
411+
require.Error(t, err, "Expected an error on badly formatted data as input")
412+
}

ecs-init/docker/docker.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package docker
1515

1616
import (
1717
"bytes"
18-
"encoding/json"
1918
"errors"
2019
"io"
2120
"os"
@@ -25,6 +24,7 @@ import (
2524
"sync"
2625
"time"
2726

27+
"github.com/aws/amazon-ecs-agent/ecs-agent/utils"
2828
"github.com/aws/amazon-ecs-agent/ecs-init/backoff"
2929
"github.com/aws/amazon-ecs-agent/ecs-init/config"
3030
"github.com/aws/amazon-ecs-agent/ecs-init/gpu"
@@ -384,7 +384,7 @@ func (c *client) getContainerConfig(envVarsFromFiles map[string]string) *godocke
384384
func setLabels(cfg *godocker.Config, labelsStringRaw string) {
385385
// Is there labels to add?
386386
if len(labelsStringRaw) > 0 {
387-
labels, err := generateLabelMap(labelsStringRaw)
387+
labels, err := utils.JsonBlockToStringToStringMap(labelsStringRaw)
388388
if err != nil {
389389
// Are the labels valid?
390390
log.Errorf("Failed to decode the container labels, skipping labels. Error: %s", err)
@@ -455,12 +455,6 @@ func (c *client) getEnvVars(filename string) map[string]string {
455455
return envVariables
456456
}
457457

458-
func generateLabelMap(jsonBlock string) (map[string]string, error) {
459-
out := map[string]string{}
460-
err := json.Unmarshal([]byte(jsonBlock), &out)
461-
return out, err
462-
}
463-
464458
func (c *client) getHostConfig(envVarsFromFiles map[string]string) *godocker.HostConfig {
465459
dockerSocketBind := getDockerSocketBind(envVarsFromFiles)
466460

ecs-init/docker/docker_test.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -754,23 +754,6 @@ func TestStopAgent(t *testing.T) {
754754
}
755755
}
756756

757-
func TestContainerLabels(t *testing.T) {
758-
testData := `{"test.label.1":"value1","test.label.2":"value2"}`
759-
out, err := generateLabelMap(testData)
760-
if err != nil {
761-
t.Logf("Got an error while decoding labels, error: %s", err)
762-
t.Fail()
763-
}
764-
if out["test.label.1"] != "value1" {
765-
t.Logf("Label did not give the correct value out.")
766-
t.Fail()
767-
}
768-
769-
for key, value := range out {
770-
t.Logf("Key: %s %T | Value: %s %T", key, key, value, value)
771-
}
772-
}
773-
774757
func TestContainerLabelsNoData(t *testing.T) {
775758
tests := []struct {
776759
name string
@@ -800,15 +783,6 @@ func TestContainerLabelsNoData(t *testing.T) {
800783
}
801784
}
802785

803-
func TestContainerLabelsBadData(t *testing.T) {
804-
testData := `{"something":[{"test.label.1":"value1"},{"test.label.2":"value2"}]}`
805-
_, err := generateLabelMap(testData)
806-
if err == nil {
807-
t.Logf("Didn't get a error while getting lables on badly formatted data, error: %s", err)
808-
t.Fail()
809-
}
810-
}
811-
812786
func TestGetDockerSocketBind(t *testing.T) {
813787
testCases := []struct {
814788
name string

ecs-init/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ require (
2626
require (
2727
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
2828
github.com/Microsoft/go-winio v0.6.2 // indirect
29+
github.com/aws/aws-sdk-go v1.55.7 // indirect
2930
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect
3031
github.com/aws/aws-sdk-go-v2/credentials v1.17.42 // indirect
3132
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect
@@ -46,6 +47,7 @@ require (
4647
github.com/docker/go-connections v0.4.0 // indirect
4748
github.com/docker/go-units v0.5.0 // indirect
4849
github.com/gogo/protobuf v1.3.2 // indirect
50+
github.com/jmespath/go-jmespath v0.4.0 // indirect
4951
github.com/klauspost/compress v1.16.7 // indirect
5052
github.com/kr/text v0.2.0 // indirect
5153
github.com/moby/patternmatcher v0.6.0 // indirect
@@ -60,6 +62,7 @@ require (
6062
github.com/pmezard/go-difflib v1.0.0 // indirect
6163
github.com/sirupsen/logrus v1.9.3 // indirect
6264
github.com/vishvananda/netns v0.0.4 // indirect
65+
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
6366
golang.org/x/net v0.38.0 // indirect
6467
golang.org/x/sys v0.31.0 // indirect
6568
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect

ecs-init/go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/Microsoft/hcsshim v0.12.0 h1:rbICA+XZFwrBef2Odk++0LjFvClNCJGRK+fsrP25
88
github.com/Microsoft/hcsshim v0.12.0/go.mod h1:RZV12pcHCXQ42XnlQ3pz6FZfmrC1C+R4gaOHhRNML1g=
99
github.com/NVIDIA/go-nvml v0.12.4-0 h1:4tkbB3pT1O77JGr0gQ6uD8FrsUPqP1A/EOEm2wI1TUg=
1010
github.com/NVIDIA/go-nvml v0.12.4-0/go.mod h1:8Llmj+1Rr+9VGGwZuRer5N/aCjxGuR5nPb/9ebBiIEQ=
11+
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
12+
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
1113
github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU=
1214
github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
1315
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 h1:xDAuZTn4IMm8o1LnBZvmrL8JA1io4o3YWNXgohbf20g=
@@ -79,6 +81,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
7981
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
8082
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
8183
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
84+
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
85+
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
8286
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
8387
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
8488
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
@@ -128,6 +132,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
128132
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
129133
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
130134
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
135+
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
136+
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
131137
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
132138
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
133139
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=

0 commit comments

Comments
 (0)