Skip to content

AWS SDK for Go v2 migration followups - #2309

Merged
lorespiz merged 6 commits into
mainfrom
sdkv2-fixes
Sep 28, 2026
Merged

lorespiz merged 6 commits into
mainfrom
sdkv2-fixes

Conversation

@sky333999

@sky333999 sky333999 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Description of the issue

Followups from the AWS SDK for Go v2 migration (#2222).

  1. Log batches are discarded on transport and credential errors. The log sender retried only errors
    it recognized as CloudWatch Logs API errors. Under SDK v2 a connection failure, DNS failure, TLS error,
    client timeout, or credential refresh failure is not an API error, so the batch was dropped and the file
    offset advanced past it. SDK v1 retried these.

  2. endpoint_override requires an explicit scheme. SDK v1 accepted a bare host such as
    logs.us-east-1.amazonaws.com; SDK v2 fails endpoint resolution with unsupported protocol scheme "".
    Documented examples are scheme-less.

  3. On-premises region resolution fails. The shared credentials file was passed in the shared config
    file slot, and a named-but-missing profile was treated as fatal. A profile-only on-premises setup
    resolved no region, and the agent exited with Region info is missing.

  4. EC2 hosts can publish to the wrong region. With the same resolution failure, the EC2 path fell
    through to instance metadata, so a host configured for one region published to the region it was running
    in.

  5. CheckAndSetHomeDir does not affect SDK default file paths. SDK v2 computes its default shared
    credentials and config paths at package initialization; the agent sets HOME afterwards. Reachable on
    Windows running as SYSTEM with a profile-only configuration.

  6. Configuration wizard selects the wrong default region and delays ~15 seconds. Same resolution
    root cause, plus a credential probe and sleep on the EC2 path.

  7. Retry-configured clients make one fewer attempt. SDK v1's MaxRetries counted retries after the
    first attempt; SDK v2's MaxAttempts counts every attempt. The value 3 was carried over unchanged,
    reducing the CloudWatch metrics output, the CloudWatch Logs clients, and the ECS service discovery
    clients from 4 attempts to 3.

Description of changes

One commit per issue.

fix(cloudwatchlogs): Retry log batches on non-API errors (1) —
plugins/outputs/cloudwatchlogs/internal/pusher/sender.go: replaced the allowlist of retryable errors
with a denylist of terminal ones (InvalidParameterException, DataAlreadyAcceptedException,
client-side parameter validation, serialization failures). All other errors retry through the sender's
existing bounded retry loop.

fix(translator): Add https:// to scheme-less endpoint_override (2) — new NormalizeEndpointURL
and GetEndpointOverride in translator/translate/otel/common/endpoints.go, applied at all nine sites
that read endpoint_override. An explicitly specified scheme is preserved; otherwise https:// is
prepended. Normalizing at the read site means the forked exporter components receive a valid value and
need no change. One golden sample config was switched to the scheme-less form to exercise the path.

fix(region): Restore env and shared-file region resolution (3, 4, 6) — new cfg/aws/region.go
with SharedConfigRegion, which resolves the region from the environment first, then from the named
profile with credentials and config files in their correct slots. A missing profile yields an empty
region instead of an error, so callers can still fall back to instance metadata. translator/util and
tool/util now use it, and DefaultEC2Region queries instance metadata directly, removing the
credential probe and 15-second sleep.

fix(region): Honor CheckAndSetHomeDir for SDK default shared files (5) — when no explicit file
list is supplied, resolve defaults through config.DefaultSharedCredentialsFilename() and
config.DefaultSharedConfigFilename(), which read the environment at call time. Explicit file lists and
AWS_SHARED_CREDENTIALS_FILE / AWS_CONFIG_FILE keep precedence.

fix(retryer): Restore SDK v1 attempt count for agent-built clients (7) —
internal/retryer/logthrottle.go and internal/ecsservicediscovery/servicediscovery.go add one when
setting MaxAttempts, and the constants document their unit. This matches the conversion the forked
contrib already applies in awsutil and its IMDS retryer.

License

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this
contribution, under the terms of your choice.

Tests

Each new test was confirmed to fail against the unfixed code by reverting only the source file.

  • Issue 1: six retry cases in sender_test.go using realistic SDK v2 error chains (connection refused,
    DNS failure, TLS error, client timeout, credential refresh failure) plus the terminal cases;
    queue_test.go asserts only terminal errors stop the resend. Verified under -race.
  • Issue 2: endpoints_test.go covers empty, scheme-less, https://, http://, other schemes, and
    surrounding whitespace. Each of the nine call sites asserts the resulting endpoint value.
  • Issues 3, 4, 6: resolution table in cfg/aws/region_test.go, plus tests in translator/util and
    tool/util that exercise the real resolution path rather than the stub that previously replaced it. A
    shared helper isolates the AWS shared-config environment, since an existing helper overwrites HOME.
  • Issue 5: covered by the region table with no explicit file list supplied.
  • Issue 7: attempt counts verified at the wire level against a local test server for all three affected
    clients.

Requirements

  1. make fmt / make fmt-sh — gofmt -l reports nothing on the changed files.
  2. make lint — golangci-lint run ./... reports 0 issues.

The pusher only retried PutLogEvents failures that satisfied
smithy.APIError and dropped every other error after a single attempt,
advancing the state-file offset so the events were never resent.

With AWS SDK for Go v1, every SDK error implemented awserr.Error, so
network failures (RequestError), credential failures
(NoCredentialProviders) and (de)serialization failures all passed that
gate and were retried for up to maxRetryTimeout. With SDK v2 these
surface as *smithy.OperationError wrapping *smithyhttp.RequestSendError,
an untyped credential error, or *smithy.DeserializationError, none of
which implement smithy.APIError. Connection refused/reset, DNS and TLS
failures, client timeouts, credential refresh failures and malformed
5xx bodies therefore caused permanent log loss after roughly one to
three seconds.

Invert the decision: retry every error except those that can never
succeed. Terminal errors are InvalidParameterException,
DataAlreadyAcceptedException, smithy.InvalidParamsError (client-side
validation) and smithy.SerializationError (request marshalling), all
matched by type. Everything else falls through to the existing bounded
retry loop, which also makes the long backoff for connection errors in
retry.go reachable again.

Add sender tests that feed realistic SDK v2 error chains for transport,
credential and deserialization failures and assert they are retried,
and tests that the two client-side terminal errors are dropped.
Retarget the queue no-resend test at a terminal error; it previously
asserted the buggy behaviour with a generic error.
SDK v1 prepended "https://" to a custom endpoint that had no URL scheme
(endpoints.AddScheme), so the documented form
"vpce-xxx.logs.us-east-1.vpce.amazonaws.com" worked. SDK v2 assigns the
value to BaseEndpoint and parses it as-is; a bare hostname becomes a URL
path and every request fails with `unsupported protocol scheme ""`.

Normalize the value once in the translator, at every site that reads
endpoint_override from the JSON config: the two TOML rules that feed
plugins/outputs/cloudwatch and cloudwatchlogs, and the OTel translators
for awscloudwatch, awscloudwatchlogs, awsemf, awsxray, journald, the
awsproxy extension and the awsxray receiver. This also fixes the forked
exporters, which set BaseEndpoint from the same string without
normalization, without needing a change in the fork.

A value that already carries a scheme is preserved, so an explicit
http:// endpoint keeps working. Whitespace is trimmed; ports, paths and
trailing slashes are left as-is.

Add unit tests for the helper and the OTel translators, and switch the
log_ecs_metric_only golden config to the documented scheme-less form so
the TOML and YAML outputs lock in the behavior.
SDK v1 tolerated a named shared-config profile that did not exist and
still resolved the region from AWS_REGION/AWS_DEFAULT_REGION, and it
accepted a credentials-style "[name]" section from any shared file. SDK
v2 differs on both: naming a profile selects a strict loader whose
missing-profile error makes LoadDefaultConfig return a zero config, and
a file passed via WithSharedConfigFiles has its bare "[name]" sections
dropped. SDKRegionWithCredsMap passed the operator's credentials file in
the config slot and returned "" on any error, so on-prem hosts failed
with "Region info is missing" and EC2 hosts using a named profile
silently fell back to the instance region.

- Add cfg/aws.SharedConfigRegion: environment region first, then the
  profile's region read with LoadSharedConfigProfile from explicit
  credentials/config files. A missing profile yields "" instead of an
  error. Used by the translator and the wizard's SDKRegionWithProfile.
- SDKRegionWithCredsMap passes the credentials file in the credentials
  slot and only the sibling config in the config slot. This also stops
  the SDK from filling the credentials slot with ~/.aws/credentials, a
  file the operator did not configure.
- DefaultEC2Region (wizard) builds an IMDS-only client instead of a
  credential-backed config, removing an IMDS credential probe and a 15s
  retry sleep that could misreport an EC2 host as on-premises.
- Add tests against the real resolution path, which every existing test
  stubbed out via util.DetectRegion.

Deliberate differences from SDK v1: an environment region is returned
even when the shared file is broken; when a profile sets a region in
both files, the credentials file wins (SDK v2 merge order); a profile
with partial static credentials yields no region and a warning.
CheckAndSetHomeDir sets HOME/USERPROFILE so the shared credentials and
config files resolve under the intended home (C:\Users\Administrator on
Windows, the real user's home elsewhere) rather than the service
account's. SDK v1 computed the default file paths when a session was
created and saw the change. SDK v2 computes DefaultSharedCredentialsFiles
and DefaultSharedConfigFiles once at package init, before main runs, so
the change was ignored and a profile-only common-config on such a host
could not find its region.

Resolve the default paths in SharedConfigRegion at call time using the
SDK's DefaultShared*Filename functions, which read the environment when
called. Explicit file lists and the AWS_SHARED_CREDENTIALS_FILE /
AWS_CONFIG_FILE variables keep precedence.
SDK v1's MaxRetries counted retries after the first attempt; SDK v2's
MaxAttempts counts every attempt. The value 3 was carried over
unchanged, so the CloudWatch metrics output, CloudWatch Logs clients,
and ECS service discovery clients made 3 HTTP attempts per call
instead of 4. Apply the same +1 the forked contrib already uses in
awsutil and the IMDS retryer so both halves keep the v1 contract.
@sky333999
sky333999 requested a review from a team as a code owner September 28, 2026 06:35
@sky333999 sky333999 changed the title Fix AWS SDK for Go v2 migration regressions AWS SDK for Go v2 migration followups Sep 28, 2026
@sky333999 sky333999 added the ready for testing Indicates this PR is ready for integration tests to run label Sep 28, 2026
The lint job runs golangci-lint with new-from-rev, so it flagged four
issues in lines this branch added:

- cfg/aws/region_test.go built the shared-credentials path by reading
  the env var back out of the environment, which gosec flags as a
  tainted path (G703). The path now comes from new
  testutil.SharedCredentialsPath/SharedConfigPath helpers, which also
  removes the duplicated file names and the test's dependence on the
  isolation helper's internals.
- The awscloudwatchlogs test fixture map contains a
  shared_credentials_file path, which gosec reads as a hardcoded
  credential (G101). Suppressed with a reason; the value is a path.
- Two awsemf selectors spelled out the embedded AWSSessionSettings
  field, which staticcheck flags as removable (QF1008).

golangci-lint run ./... reports 0 issues.
@github-actions

Copy link
Copy Markdown
Contributor

Binary Size Report

linux/amd64

Binary PR vs main (92213f4) vs v1.300073.0
amazon-cloudwatch-agent 236.6 MB +0 B ${\color{red}▲}$ +41.7 MB (+21.4%)
amazon-cloudwatch-agent-config-wizard 2.0 MB +0 B +0 B
config-downloader 2.0 MB +0 B +0 B
config-translator 2.0 MB +0 B +0 B
start-amazon-cloudwatch-agent 2.5 MB +0 B +0 B
workload-discovery 3.0 MB +0 B +0 B
Total 248.1 MB +0 B ${\color{red}▲}$ +41.7 MB (+20.2%)
linux/amd64 amazon-cloudwatch-agent (last 9 main commits + this PR)

 239 ┤                                        
     ┤                        ███ ███ ███ ███ 
     ┤                        ███ ███ ███ ███ 
     ┤                        ███ ███ ███ ███ 
     ┤                        ███ ███ ███ ███ 
     ┤                        ███ ███ ███ ███ 
     ┤                        ███ ███ ███ ███ 
     ┤                        ███ ███ ███ ███ 
 193 ┤▄▄▄ ▄▄▄ ▄▄▄ ▄▄▄ ▄▄▄ ▄▄▄ ███ ███ ███ ███ 
 MB  └────────────────────────────────────────
      v1.300073.0                         PR

Notable changes:

linux/arm64
Binary PR vs main (92213f4) vs v1.300073.0
amazon-cloudwatch-agent 214.4 MB +0 B ${\color{red}▲}$ +35.8 MB (+20.0%)
amazon-cloudwatch-agent-config-wizard 2.0 MB +0 B +0 B
config-downloader 2.0 MB +0 B +0 B
config-translator 2.0 MB +0 B +0 B
start-amazon-cloudwatch-agent 2.4 MB +0 B +0 B
workload-discovery 2.9 MB +0 B +0 B
Total 225.6 MB +0 B ${\color{red}▲}$ +35.8 MB (+18.8%)
windows/amd64
Binary PR vs main (92213f4) vs v1.300073.0
amazon-cloudwatch-agent.exe 240.1 MB ${\color{red}▲}$ +7.7 KB (+0.0%) ${\color{red}▲}$ +42.5 MB (+21.5%)
amazon-cloudwatch-agent-config-wizard.exe 2.1 MB +0 B +0 B
config-downloader.exe 2.1 MB +0 B +0 B
config-translator.exe 2.1 MB +0 B +0 B
start-amazon-cloudwatch-agent.exe 2.3 MB +0 B +0 B
workload-discovery.exe 3.1 MB +0 B ${\color{red}▲}$ +192.5 KB (+6.6%)
Total 251.9 MB ${\color{red}▲}$ +7.7 KB (+0.0%) ${\color{red}▲}$ +42.7 MB (+20.4%)
Investigating size changes

Use go-size-analyzer to compare binaries:

GOEXPERIMENT=jsonv2 go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest
gsa diff --old <baseline-binary> --new <new-binary>

@lorespiz
lorespiz merged commit ae26599 into main Sep 28, 2026
2475 of 2493 checks passed
@lorespiz
lorespiz deleted the sdkv2-fixes branch September 28, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for testing Indicates this PR is ready for integration tests to run

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants