Skip to content

Commit 387142e

Browse files
committed
Add IPv6 dual-stack endpoint support for CloudWatch Agent
- Add use_dualstack_endpoint configuration option to agent section - Set AWS_USE_DUALSTACK_ENDPOINT environment variable when enabled - Support dual-stack endpoints for CloudWatch, AMP, and other AWS services - Add dual-stack flag (-d) to config-downloader for SSM parameter retrieval - Integrate with new cmdwrapper architecture while maintaining compatibility - Add comprehensive tests for dual-stack functionality This enables IPv6 connectivity for CloudWatch Agent in dual-stack network environments.
1 parent a437da8 commit 387142e

10 files changed

Lines changed: 338 additions & 14 deletions

File tree

packaging/dependencies/amazon-cloudwatch-agent-ctl

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ UsageString="
3434
[-m ec2|onPremise|onPrem|auto]
3535
[-c default|all|ssm:<parameter-store-name>|file:<file-path>]
3636
[-s]
37+
[-d]
3738
[-l INFO|DEBUG|WARN|ERROR|OFF]
3839
3940
e.g.
@@ -43,6 +44,8 @@ UsageString="
4344
amazon-cloudwatch-agent-ctl -a append-config -m onPremise -c file:/tmp/config.json -s
4445
3. query agent status:
4546
amazon-cloudwatch-agent-ctl -a status
47+
4. apply a SSM parameter store config with dual-stack endpoints:
48+
amazon-cloudwatch-agent-ctl -d -a fetch-config -m ec2 -c ssm:AmazonCloudWatch-Config.json -s
4649
4750
-a: action
4851
stop: stop the agent process.
@@ -67,6 +70,9 @@ UsageString="
6770
-s: optionally restart after configuring the agent configuration
6871
this parameter is used for 'fetch-config', 'append-config', 'remove-config' action only.
6972
73+
-d: enable dual-stack endpoints for AWS API calls during config download
74+
this parameter is used for 'fetch-config', 'append-config', 'remove-config' actions only.
75+
7076
-l: log level to set the agent to INFO, DEBUG, WARN, ERROR, or OFF
7177
this parameter is used for 'set-log-level' only.
7278
@@ -91,7 +97,7 @@ agent_start() {
9197

9298
if [ "${agent_name}" = "${CWA_NAME}" ] && [ ! -f "${TOML}" ]; then
9399
echo "${CWA_NAME} is not configured. Applying amazon-cloudwatch-agent default configuration."
94-
cwa_config 'default' 'false' "${mode}" 'default'
100+
cwa_config 'default' 'false' "${mode}" 'default' 'false'
95101
fi
96102

97103
if [ "${SYSTEMD}" = 'true' ]; then
@@ -236,6 +242,7 @@ config_all() {
236242
restart="${2:-}"
237243
mode="${3:-}"
238244
multi_config="${4:-}"
245+
use_dualstack="${5:-}"
239246

240247
if [ -z "${cwa_config_location}" ]; then
241248
cwa_config_location='default'
@@ -245,7 +252,7 @@ config_all() {
245252

246253
if [ -n "${cwa_config_location}" ]; then
247254
echo "****** processing amazon-cloudwatch-agent ******"
248-
cwa_config "${cwa_config_location}" "${restart}" "${mode}" "${multi_config}"
255+
cwa_config "${cwa_config_location}" "${restart}" "${mode}" "${multi_config}" "${use_dualstack}"
249256
fi
250257
}
251258

@@ -254,6 +261,7 @@ cwa_config() {
254261
restart="${2:-}"
255262
param_mode="${3:-}"
256263
multi_config="${4:-}"
264+
use_dualstack="${5:-}"
257265

258266
if [ "${cwa_config_location}" = "${ALL_CONFIG}" ] && [ "${multi_config}" != 'remove' ]; then
259267
echo "ignore cwa configuration \"${ALL_CONFIG}\" as it is only supported by action \"remove-config\""
@@ -263,8 +271,12 @@ cwa_config() {
263271
if [ "${cwa_config_location}" = "${ALL_CONFIG}" ]; then
264272
rm -rf "${JSON_DIR}"/*
265273
else
266-
runDownloaderCommand=$("${CMDDIR}/config-downloader" --output-dir "${JSON_DIR}" --download-source "${cwa_config_location}" --mode ${param_mode} --config "${COMMON_CONIG}" --multi-config ${multi_config})
267-
echo ${runDownloaderCommand} || return
274+
downloader_cmd="${CMDDIR}/config-downloader --output-dir ${JSON_DIR} --download-source ${cwa_config_location} --mode ${param_mode} --config ${COMMON_CONIG} --multi-config ${multi_config}"
275+
if [ "${use_dualstack}" = 'true' ]; then
276+
downloader_cmd="${downloader_cmd} -d"
277+
fi
278+
runDownloaderCommand=$(${downloader_cmd})
279+
echo "${runDownloaderCommand}" || return
268280
fi
269281

270282
if [ ! "$(ls ${JSON_DIR})" ]; then
@@ -354,6 +366,7 @@ main() {
354366
cwa_config_location=''
355367
restart='false'
356368
mode='ec2'
369+
use_dualstack='false'
357370

358371
# detect which init system is in use
359372
if [ "$(/sbin/init --version 2>/dev/null | grep -c upstart)" = 1 ]; then
@@ -369,7 +382,7 @@ main() {
369382
fi
370383

371384
OPTIND=1
372-
while getopts ":hsa:c:m:l:" opt; do
385+
while getopts ":hsa:c:m:l:d" opt; do
373386
case "${opt}" in
374387
h)
375388
echo "${UsageString}"
@@ -380,6 +393,7 @@ main() {
380393
c) cwa_config_location="${OPTARG}" ;;
381394
m) mode="${OPTARG}" ;;
382395
l) log_level="${OPTARG}" ;;
396+
d) use_dualstack='true' ;;
383397
\?)
384398
echo "Invalid option: -${OPTARG} ${UsageString}" >&2
385399
;;
@@ -405,9 +419,9 @@ main() {
405419
case "${action}" in
406420
stop) stop_all ;;
407421
start) start_all "${mode}" ;;
408-
fetch-config) config_all "${cwa_config_location}" "${restart}" "${mode}" 'default' ;;
409-
append-config) config_all "${cwa_config_location}" "${restart}" "${mode}" 'append' ;;
410-
remove-config) config_all "${cwa_config_location}" "${restart}" "${mode}" 'remove' ;;
422+
fetch-config) config_all "${cwa_config_location}" "${restart}" "${mode}" 'default' "${use_dualstack}" ;;
423+
append-config) config_all "${cwa_config_location}" "${restart}" "${mode}" 'append' "${use_dualstack}" ;;
424+
remove-config) config_all "${cwa_config_location}" "${restart}" "${mode}" 'remove' "${use_dualstack}" ;;
411425
status) status_all ;;
412426
# helpers for ssm package scripts to workaround fact that it can't determine if invocation is due to
413427
# upgrade or install

tool/downloader/downloader.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,18 @@ const (
3131
)
3232

3333
func RunDownloaderFromFlags(flags map[string]*string) error {
34+
useDualStack := *flags["d"] == "true"
3435
return RunDownloader(
3536
*flags["mode"],
3637
*flags["download-source"],
3738
*flags["output-dir"],
3839
*flags["config"],
3940
*flags["multi-config"],
41+
useDualStack,
4042
)
4143
}
4244

43-
func RunDownloader(mode, downloadLocation, outputDir, inputConfig, multiConfig string) (err error) {
45+
func RunDownloader(mode, downloadLocation, outputDir, inputConfig, multiConfig string, useDualStack bool) (err error) {
4446
defer func() {
4547
if r := recover(); r != nil {
4648
err = fmt.Errorf("Fail to fetch the config")
@@ -64,6 +66,11 @@ func RunDownloader(mode, downloadLocation, outputDir, inputConfig, multiConfig s
6466
util.SetProxyEnv(cc.ProxyMap())
6567
util.SetSSLEnv(cc.SSLMap())
6668

69+
// Set dual-stack endpoint if requested
70+
if useDualStack {
71+
os.Setenv("AWS_USE_DUALSTACK_ENDPOINT", "true")
72+
}
73+
6774
// Validate required parameters
6875
if downloadLocation == "" || outputDir == "" {
6976
executable, err := os.Executable()

tool/downloader/flags/flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ var DownloaderFlags = map[string]cmdwrapper.Flag{
1313
"output-dir": {DefaultValue: "", Description: "Path of output json config directory."},
1414
"config": {DefaultValue: "", Description: "Please provide the common-config file"},
1515
"multi-config": {DefaultValue: "default", Description: "valid values: default, append, remove"},
16+
"d": {DefaultValue: "false", Description: "Use dual-stack endpoints for AWS API calls for config-downloader", IsBool: true},
1617
}

translator/tocwconfig/toenvconfig/toEnvConfig.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import (
1515
)
1616

1717
const (
18-
userAgentKey = "user_agent"
19-
debugKey = "debug"
20-
awsSdkLogLevelKey = "aws_sdk_log_level"
21-
usageDataKey = "usage_data"
18+
userAgentKey = "user_agent"
19+
debugKey = "debug"
20+
awsSdkLogLevelKey = "aws_sdk_log_level"
21+
usageDataKey = "usage_data"
22+
useDualStackEndpointKey = "use_dualstack_endpoint"
2223
)
2324

2425
func ToEnvConfig(jsonConfigValue map[string]interface{}) []byte {
@@ -41,6 +42,15 @@ func ToEnvConfig(jsonConfigValue map[string]interface{}) []byte {
4142
if usageData, ok := agentMap[usageDataKey].(bool); ok && !usageData {
4243
envVars[envconfig.CWAGENT_USAGE_DATA] = "FALSE"
4344
}
45+
46+
if useDualStack, ok := agentMap[useDualStackEndpointKey].(bool); ok {
47+
if useDualStack {
48+
envVars["AWS_USE_DUALSTACK_ENDPOINT"] = "true"
49+
} else {
50+
envVars["AWS_USE_DUALSTACK_ENDPOINT"] = "false"
51+
}
52+
} else {
53+
}
4454
}
4555

4656
proxy := util.GetHttpProxy(context.CurrentContext().Proxy())

translator/tocwconfig/toenvconfig/toEnvConfig_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,106 @@ func TestToEnvConfig(t *testing.T) {
5656
context.CurrentContext().SetSSL(map[string]string{})
5757
},
5858
},
59+
{
60+
name: "agent section with dual-stack endpoint enabled",
61+
input: map[string]interface{}{
62+
agent.SectionKey: map[string]interface{}{
63+
useDualStackEndpointKey: true,
64+
},
65+
},
66+
envVars: map[string]string{},
67+
expectedEnv: map[string]string{
68+
"AWS_USE_DUALSTACK_ENDPOINT": "true",
69+
},
70+
contextSetup: func() {
71+
context.CurrentContext().SetProxy(map[string]string{})
72+
context.CurrentContext().SetSSL(map[string]string{})
73+
},
74+
},
75+
{
76+
name: "agent section with dual-stack endpoint disabled",
77+
input: map[string]interface{}{
78+
agent.SectionKey: map[string]interface{}{
79+
useDualStackEndpointKey: false,
80+
},
81+
},
82+
envVars: map[string]string{},
83+
expectedEnv: map[string]string{
84+
"AWS_USE_DUALSTACK_ENDPOINT": "false",
85+
},
86+
contextSetup: func() {
87+
context.CurrentContext().SetProxy(map[string]string{})
88+
context.CurrentContext().SetSSL(map[string]string{})
89+
},
90+
},
91+
{
92+
name: "combined configuration with dual-stack",
93+
input: map[string]interface{}{
94+
agent.SectionKey: map[string]interface{}{
95+
userAgentKey: "custom-agent",
96+
debugKey: true,
97+
useDualStackEndpointKey: true,
98+
awsSdkLogLevelKey: "INFO",
99+
},
100+
},
101+
envVars: map[string]string{},
102+
expectedEnv: map[string]string{
103+
envconfig.CWAGENT_USER_AGENT: "custom-agent",
104+
envconfig.CWAGENT_LOG_LEVEL: "DEBUG",
105+
envconfig.AWS_SDK_LOG_LEVEL: "INFO",
106+
"AWS_USE_DUALSTACK_ENDPOINT": "true",
107+
envconfig.HTTP_PROXY: "http://proxy.test",
108+
envconfig.AWS_CA_BUNDLE: "/test/ca-bundle.pem",
109+
},
110+
contextSetup: func() {
111+
context.CurrentContext().SetProxy(map[string]string{
112+
"http_proxy": "http://proxy.test",
113+
})
114+
context.CurrentContext().SetSSL(map[string]string{
115+
"ca_bundle_path": "/test/ca-bundle.pem",
116+
})
117+
},
118+
},
119+
{
120+
name: "invalid dual-stack type string",
121+
input: map[string]interface{}{
122+
agent.SectionKey: map[string]interface{}{
123+
useDualStackEndpointKey: "true",
124+
},
125+
},
126+
expectedEnv: map[string]string{},
127+
contextSetup: func() {
128+
context.CurrentContext().SetProxy(map[string]string{})
129+
context.CurrentContext().SetSSL(map[string]string{})
130+
},
131+
},
132+
{
133+
name: "invalid dual-stack type number",
134+
input: map[string]interface{}{
135+
agent.SectionKey: map[string]interface{}{
136+
useDualStackEndpointKey: 1,
137+
},
138+
},
139+
expectedEnv: map[string]string{},
140+
contextSetup: func() {
141+
context.CurrentContext().SetProxy(map[string]string{})
142+
context.CurrentContext().SetSSL(map[string]string{})
143+
},
144+
},
145+
{
146+
name: "invalid dual-stack type nil",
147+
input: map[string]interface{}{
148+
agent.SectionKey: map[string]interface{}{
149+
useDualStackEndpointKey: nil,
150+
},
151+
},
152+
expectedEnv: map[string]string{},
153+
contextSetup: func() {
154+
context.CurrentContext().SetProxy(map[string]string{})
155+
context.CurrentContext().SetSSL(map[string]string{})
156+
},
157+
},
158+
59159
{
60160
name: "proxy configuration",
61161
input: map[string]interface{}{},

translator/translate/agent/agent.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Agent struct {
3434
Role_arn string
3535
ServiceName string
3636
DeploymentEnvironment string
37+
UseDualStackEndpoint bool
3738
}
3839

3940
var (
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package agent
5+
6+
import (
7+
"github.com/aws/amazon-cloudwatch-agent/translator"
8+
)
9+
10+
type UseDualStackEndpoint struct {
11+
}
12+
13+
func (r *UseDualStackEndpoint) ApplyRule(input interface{}) (returnKey string, returnVal interface{}) {
14+
agentMap, ok := input.(map[string]interface{})
15+
if !ok {
16+
returnKey, returnVal = "", translator.ErrorMessages
17+
return
18+
}
19+
20+
dualStackValue, exists := agentMap["use_dualstack_endpoint"]
21+
if !exists {
22+
returnKey, returnVal = "", nil
23+
return
24+
}
25+
26+
val, ok := dualStackValue.(bool)
27+
if !ok {
28+
returnKey, returnVal = "", translator.ErrorMessages
29+
return
30+
}
31+
32+
Global_Config.UseDualStackEndpoint = val
33+
returnKey, returnVal = "use_dualstack_endpoint", val
34+
return
35+
}
36+
37+
func init() {
38+
RegisterRule("use_dualstack_endpoint", new(UseDualStackEndpoint))
39+
}

0 commit comments

Comments
 (0)