Skip to content

Commit aafb931

Browse files
authored
Merge branch 'main' into fix/querycache-no-cache-empty
2 parents 740eff0 + 0903a74 commit aafb931

10 files changed

Lines changed: 529 additions & 265 deletions

File tree

generator/resources/ec2_linux_test_matrix.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@
3232
"binaryName": "amazon-cloudwatch-agent.deb",
3333
"family": "linux"
3434
},
35+
{
36+
"os": "ubuntu-26",
37+
"username": "ubuntu",
38+
"instanceType":"t3a.medium",
39+
"installAgentCommand": "go run ./install/install_agent.go deb",
40+
"ami": "cloudwatch-agent-integration-test-ubuntu-26*",
41+
"caCertPath": "/etc/ssl/certs/ca-certificates.crt",
42+
"arc": "amd64",
43+
"binaryName": "amazon-cloudwatch-agent.deb",
44+
"family": "linux"
45+
},
3546
{
3647
"os": "ubuntu-22.04",
3748
"username": "ubuntu",

generator/test_case_generator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ var testTypeToTestConfig = map[string][]testConfig{
432432
{
433433
testDir: "./test/entity", terraformDir: "terraform/eks/daemon/entity",
434434
targets: map[string]map[string]struct{}{"arc": {"amd64": {}}},
435+
wip: true,
435436
},
436437
{
437438
testDir: "./test/efa", terraformDir: "terraform/eks/daemon/efa",

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ require (
2525
github.com/aws/aws-sdk-go-v2/service/sts v1.26.2
2626
github.com/aws/aws-sdk-go-v2/service/xray v1.23.2
2727
github.com/aws/aws-xray-sdk-go v1.8.3
28+
github.com/aws/smithy-go v1.18.1
2829
github.com/cenkalti/backoff/v4 v4.2.1
2930
github.com/google/uuid v1.4.0
3031
github.com/mitchellh/mapstructure v1.5.0
@@ -64,7 +65,6 @@ require (
6465
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.8 // indirect
6566
github.com/aws/aws-sdk-go-v2/service/sso v1.18.2 // indirect
6667
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.2 // indirect
67-
github.com/aws/smithy-go v1.18.1 // indirect
6868
github.com/davecgh/go-spew v1.1.1 // indirect
6969
github.com/go-logr/logr v1.3.0 // indirect
7070
github.com/go-logr/stdr v1.2.2 // indirect

test/ssm_document/constants.go

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,59 @@ const (
77
testManageAgentDocument = "Test-AmazonCloudWatch-ManageAgent-"
88

99
// Actions
10-
actionStart = "start"
11-
actionStop = "stop"
12-
actionConfigure = "configure"
13-
actionConfigureAppend = "configure (append)"
14-
actionConfigureRemove = "configure (remove)"
10+
actionStart = "start"
11+
actionStop = "stop"
12+
actionConfigure = "configure"
13+
actionConfigureAppend = "configure (append)"
14+
actionConfigureRemove = "configure (remove)"
15+
actionSetEnv = "set-env"
16+
actionSetEnvMerge = "set-env (merge)"
17+
actionSetEnvOverwrite = "set-env (overwrite)"
18+
actionSetEnvEmpty = "set-env (empty)"
19+
actionSetEnvInvalidPattern = "set-env (invalid pattern)"
20+
21+
// actionSetEnvInvalidBacktick exercises rejection of a value containing a backtick.
22+
actionSetEnvInvalidBacktick = "set-env (invalid backtick)"
23+
24+
// actionSetEnvInvalidKey exercises rejection of a malformed key starting with a digit.
25+
actionSetEnvInvalidKey = "set-env (invalid key)"
1526

1627
// Parameters
1728
paramAction = "action"
1829
paramOptionalConfigurationSource = "optionalConfigurationSource"
1930
paramOptionalConfigurationLocation = "optionalConfigurationLocation"
2031
paramOptionalRestart = "optionalRestart"
32+
paramOptionalEnvironmentVariable = "optionalEnvironmentVariable"
33+
34+
// set-env test environment variables. Custom (non translator-managed) key names so
35+
// configure actions and agent restarts do not overwrite them. Values include spaces
36+
// to exercise quoting through the document -> ctl -> agent binary chain.
37+
setEnvKey1 = "CWA_TEST_VAR_ONE"
38+
setEnvValue1 = "value with spaces"
39+
setEnvKey2 = "CWA_TEST_VAR_TWO"
40+
setEnvValue2 = "another value"
41+
42+
// setEnvOverwriteValue is used to overwrite setEnvKey1 with a new value.
43+
setEnvOverwriteValue = "overwritten value"
44+
45+
// setEnvInvalidPatternValue contains a '$' which violates the document's
46+
// optionalEnvironmentVariable allowedPattern and is rejected by SSM at SendCommand.
47+
setEnvInvalidPatternValue = "CWA_TEST_INVALID=$not_allowed"
48+
49+
// setEnvInvalidBacktickValue contains a backtick which violates the document's
50+
// optionalEnvironmentVariable allowedPattern and is rejected by SSM at SendCommand.
51+
setEnvInvalidBacktickValue = "CWA_TEST_BACKTICK=val`ue"
52+
53+
// setEnvInvalidKeyValue has a malformed key starting with a digit, violating the
54+
// allowedPattern's key portion ([A-Za-z_][A-Za-z0-9_]*=...) and rejected at SendCommand.
55+
setEnvInvalidKeyValue = "1FOO=bar"
56+
57+
// setEnvOutputPrefix is printed by the ctl on a successful set-env ("Set <KEY>").
58+
setEnvOutputPrefix = "Set "
59+
60+
// setEnvEmptyErrorMessage is printed by the SSM document when optionalEnvironmentVariable
61+
// is empty for the set-env action.
62+
setEnvEmptyErrorMessage = "optionalEnvironmentVariable must be specified for the set-env action"
2163

2264
// Parameter Values
2365
configSourceSSM = "ssm"
@@ -45,7 +87,10 @@ type agentStatus struct {
4587
}
4688

4789
type testCase struct {
48-
parameters map[string][]string
90+
parameters map[string][]string
91+
// actionName is a human-readable test label used only in log messages and error
92+
// output. It is NOT the SSM action parameter value (which is carried in
93+
// parameters[paramAction]).
4994
actionName string
5095
expectedAgentStatus string
5196
expectedConfigStatus string

test/ssm_document/helper.go

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ package ssm_document
55

66
import (
77
"encoding/json"
8+
"errors"
89
"fmt"
910
"log"
11+
"os"
12+
"strings"
1013

1114
"github.com/aws/aws-sdk-go-v2/service/ssm"
1215
"github.com/aws/aws-sdk-go-v2/service/ssm/types"
16+
"github.com/aws/smithy-go"
1317

1418
"github.com/aws/amazon-cloudwatch-agent-test/util/awsservice"
1519
)
@@ -24,23 +28,23 @@ func cleanupSSMParameter(name string) {
2428
}
2529
}
2630

27-
func RunAndVerifySSMAction(documentName string, instanceIds []string, tc testCase) error {
31+
func runAndVerifySSMAction(documentName string, instanceIds []string, tc testCase) error {
2832
log.Printf("Testing %s action", tc.actionName)
2933

3034
out, err := awsservice.RunSSMDocument(documentName, instanceIds, tc.parameters)
3135
if err != nil {
3236
return fmt.Errorf("%s action failed: %v", tc.actionName, err)
3337
}
3438

35-
if err := VerifyAgentAction(out, instanceIds[0], documentName, tc); err != nil {
39+
if err := verifyAgentAction(out, instanceIds[0], documentName, tc); err != nil {
3640
return fmt.Errorf("%s verification failed: %v", tc.actionName, err)
3741
}
3842

3943
log.Printf("%s action completed successfully", tc.actionName)
4044
return nil
4145
}
4246

43-
func VerifyAgentAction(out *ssm.SendCommandOutput, instanceId, documentName string, tc testCase) error {
47+
func verifyAgentAction(out *ssm.SendCommandOutput, instanceId, documentName string, tc testCase) error {
4448
var status agentStatus
4549

4650
//Wait for command completion
@@ -63,6 +67,10 @@ func VerifyAgentAction(out *ssm.SendCommandOutput, instanceId, documentName stri
6367
return fmt.Errorf("failed to get status result: %v", err)
6468
}
6569

70+
if len(statusResult.CommandInvocations) == 0 {
71+
return fmt.Errorf("no command invocations returned for status check")
72+
}
73+
6674
for _, plugin := range statusResult.CommandInvocations[0].CommandPlugins {
6775
if plugin.Status == types.CommandPluginStatusFailed {
6876
return fmt.Errorf("command plugin failed: %s", *plugin.Name)
@@ -88,3 +96,127 @@ func VerifyAgentAction(out *ssm.SendCommandOutput, instanceId, documentName stri
8896

8997
return nil
9098
}
99+
100+
// runAndVerifySSMActionWithOutput behaves like runAndVerifySSMAction and additionally
101+
// asserts that expectedOutput appears in the command's output.
102+
func runAndVerifySSMActionWithOutput(documentName string, instanceIds []string, tc testCase, expectedOutput string) error {
103+
log.Printf("Testing %s action", tc.actionName)
104+
105+
out, err := awsservice.RunSSMDocument(documentName, instanceIds, tc.parameters)
106+
if err != nil {
107+
return fmt.Errorf("%s action failed: %v", tc.actionName, err)
108+
}
109+
110+
result, err := awsservice.WaitForCommandCompletion(*out.Command.CommandId, instanceIds[0])
111+
if err != nil {
112+
commandOutput := awsservice.GetCommandInvocationDetails(*out.Command.CommandId, instanceIds[0])
113+
return fmt.Errorf("%s action failed to complete: %v\nCommand output:\n%s", tc.actionName, err, commandOutput)
114+
}
115+
116+
if !commandOutputContains(result, expectedOutput) {
117+
commandOutput := awsservice.GetCommandInvocationDetails(*out.Command.CommandId, instanceIds[0])
118+
return fmt.Errorf("%s output verification failed: expected output %q not found\nCommand output:\n%s", tc.actionName, expectedOutput, commandOutput)
119+
}
120+
121+
if err := verifyAgentAction(out, instanceIds[0], documentName, tc); err != nil {
122+
return fmt.Errorf("%s verification failed: %v", tc.actionName, err)
123+
}
124+
125+
log.Printf("%s action completed successfully", tc.actionName)
126+
return nil
127+
}
128+
129+
// runAndVerifySSMActionFailure runs the document action and expects the command invocation
130+
// to reach the terminal Failed state (e.g. document-level parameter validation errors).
131+
// If expectedOutput is non-empty, it must appear in the failed command's output.
132+
func runAndVerifySSMActionFailure(documentName string, instanceIds []string, tc testCase, expectedOutput string) error {
133+
log.Printf("Testing %s action (expecting failure)", tc.actionName)
134+
135+
out, err := awsservice.RunSSMDocument(documentName, instanceIds, tc.parameters)
136+
if err != nil {
137+
return fmt.Errorf("%s action failed to send: %v", tc.actionName, err)
138+
}
139+
140+
commandId := *out.Command.CommandId
141+
_, err = awsservice.WaitForCommandCompletion(commandId, instanceIds[0])
142+
commandOutput := awsservice.GetCommandInvocationDetails(commandId, instanceIds[0])
143+
if err == nil {
144+
return fmt.Errorf("%s action was expected to fail but succeeded\nCommand output:\n%s", tc.actionName, commandOutput)
145+
}
146+
// WaitForCommandCompletion returns a *CommandTerminalError for Failed/Cancelled/TimedOut;
147+
// require specifically the Failed status.
148+
var termErr *awsservice.CommandTerminalError
149+
if !errors.As(err, &termErr) || termErr.Status != types.CommandInvocationStatusFailed {
150+
return fmt.Errorf("%s action reached an unexpected terminal state: %v\nCommand output:\n%s", tc.actionName, err, commandOutput)
151+
}
152+
if expectedOutput != "" && !strings.Contains(commandOutput, expectedOutput) {
153+
return fmt.Errorf("%s failure output verification failed: expected output %q not found\nCommand output:\n%s", tc.actionName, expectedOutput, commandOutput)
154+
}
155+
156+
log.Printf("%s action failed as expected", tc.actionName)
157+
return nil
158+
}
159+
160+
// verifyEnvConfigContent reads the agent's env-config.json directly from the local
161+
// filesystem (the test runs on the instance) and asserts that the file contains every
162+
// expected key/value pair. Additional keys in the file are ignored.
163+
func verifyEnvConfigContent(expected map[string]string) error {
164+
log.Printf("Verifying env-config.json content via direct file read: %s", envConfigPath)
165+
166+
data, err := os.ReadFile(envConfigPath)
167+
if err != nil {
168+
return fmt.Errorf("failed to read env-config.json at %s: %v", envConfigPath, err)
169+
}
170+
171+
var envConfig map[string]string
172+
if err := json.Unmarshal(data, &envConfig); err != nil {
173+
return fmt.Errorf("failed to unmarshal env-config.json: %v\nContent:\n%s", err, string(data))
174+
}
175+
176+
for key, want := range expected {
177+
got, ok := envConfig[key]
178+
if !ok {
179+
return fmt.Errorf("env-config.json is missing expected key %q. Content: %v", key, envConfig)
180+
}
181+
if got != want {
182+
return fmt.Errorf("env-config.json key %q verification failed. Expected: %q, Got: %q", key, want, got)
183+
}
184+
}
185+
186+
log.Println("env-config.json content verified successfully")
187+
return nil
188+
}
189+
190+
// commandOutputContains reports whether any command plugin's output contains expected.
191+
func commandOutputContains(result *ssm.ListCommandInvocationsOutput, expected string) bool {
192+
if len(result.CommandInvocations) == 0 {
193+
return false
194+
}
195+
for _, plugin := range result.CommandInvocations[0].CommandPlugins {
196+
if plugin.Output != nil && strings.Contains(*plugin.Output, expected) {
197+
return true
198+
}
199+
}
200+
return false
201+
}
202+
203+
// verifySSMSendCommandRejection runs the document action and expects SendCommand itself
204+
// to reject the request (e.g. because a parameter value violates an allowedPattern).
205+
// The AWS SDK returns an InvalidParameters error from SendCommand in this case.
206+
func verifySSMSendCommandRejection(documentName string, instanceIds []string, tc testCase) error {
207+
log.Printf("Testing %s action (expecting SendCommand rejection)", tc.actionName)
208+
209+
_, err := awsservice.RunSSMDocument(documentName, instanceIds, tc.parameters)
210+
if err == nil {
211+
return fmt.Errorf("%s action was expected to be rejected at SendCommand but succeeded", tc.actionName)
212+
}
213+
214+
// SSM returns an InvalidParameters API error when allowedPattern validation fails.
215+
var apiErr smithy.APIError
216+
if !errors.As(err, &apiErr) || apiErr.ErrorCode() != "InvalidParameters" {
217+
return fmt.Errorf("%s action failed with unexpected error (expected InvalidParameters API error): %v", tc.actionName, err)
218+
}
219+
220+
log.Printf("%s action rejected at SendCommand as expected: %v", tc.actionName, err)
221+
return nil
222+
}

0 commit comments

Comments
 (0)