Skip to content

E2E workflow: deploy the template into a throwaway AWS account and read telemetry back - #6

Merged
PetrHeinz merged 12 commits into
mainfrom
claude/t-3853-e2e
Oct 16, 2026
Merged

PetrHeinz merged 12 commits into
mainfrom
claude/t-3853-e2e

Conversation

@PetrHeinz

@PetrHeinz PetrHeinz commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

A new E2E workflow deploys the template into a throwaway AWS account the way a customer sets it up and reads the resulting telemetry back from Better Stack. It runs on every pull request and push to main with the template from the checkout, and daily with the published S3 template, which must then equal main.

What a run does:

  • checks that the docs article names every true/false template parameter, both outputs and this repository
  • creates an aws source in the E2E testing team, warms it up until the ingesters accept its token, and pre-creates two log groups: one named after the run, and /e2e/static, which every run recreates under the same name
  • deploys cloudformation/full/better-stack-full.yaml with that source's ClusterId, token and id, pastes the IntegrationRoleArn and ExternalId outputs back into the source, and waits until the sync has put the BetterStackLogs subscription filter on the run's log group
  • checks that the sync leaves /e2e/static, aws/spans and the CloudTrail group unsubscribed, because Better Stack remembers them from earlier runs, then subscribes them through the API. This is current behaviour, and the run fails with a pointer to the internal ticket once it changes.
  • writes 20 log lines into each log group, 3 datapoints of a custom metric, a two-segment X-Ray trace and a PutRetentionPolicy call, then reads all of them back through the team's SQL API connection with exact counts. It counts again a minute later to catch duplicate delivery and checks that the stored trace id matches the X-Ray one.
  • deletes the source, the stack, the two buckets the stack retains by design, aws/spans, /aws/application-signals/data and the /e2e/* log groups, and fails if it cannot list or delete something

A run within an hour of the previous run's stack deletion checks the static group, aws/spans and CloudTrail leniently. It waits only for the run's own log group and the metric, and when one of those three delivered nothing, it adds a warning to the log and the job summary instead of failing. Any other wrong count still fails. The template names its IAM roles per account, so every run recreates them under the same names, and AWS keeps using sessions of the deleted roles for up to an hour:

  • CloudWatch Logs holds the subscription role's session per log group name. Delivery with a deleted role's session fails with a non-retryable error, so the filter pauses for 10 minutes and skips whatever arrives meanwhile. It fails again on every retry until the session expires, even when the log group is deleted and recreated with a new filter. aws/spans, the CloudTrail group and /e2e/static have the same names in every run.
  • CloudTrail holds its role's session for writing into /aws/cloudtrail/better-stack the same way, and writes nothing there until that session expires.

Runs in quick succession lose exactly this data: the run-named group always arrives, while the fixed-name groups fail at random. A run more than an hour after the previous one checks everything strictly. Customers keep their roles and are not affected. A customer who deletes and redeploys the stack would lose up to an hour of logs from the groups that were subscribed before.

When something does not arrive within 20 minutes, the run prints the incoming, forwarded and delivery-error counts of each subscribed log group.

The account is better-stack-e2e-aws-stack (251917137055), a member of the Better Stack organization that holds nothing else. AWS access is GitHub OIDC through the github-actions-e2e role of a one-time stack that is already deployed in the account, so no AWS keys live in GitHub. The role has administrator access because the template creates IAM roles, Lambdas, Firehose streams, CloudTrail and S3, and the account is otherwise empty. The template's role and bucket names are fixed per account, so a concurrency group keeps it to one deploy at a time. Pull requests from forks are skipped, since they get no OIDC token.

The job summary shows the counts, sample rows and the cleanup. Secrets TEST_TEAM_TELEMETRY_API, TEST_TEAM_CONNECTION_USERNAME and TEST_TEAM_CONNECTION_PASSWORD are set. The LINEAR_* secrets are set as well: a failure on main or on the schedule opens a High-priority issue in the Telemetry team's Plan state, in the Ingestion for everything project, assigned to Petr.

🤖 Generated with Claude Code

PetrHeinz and others added 9 commits October 15, 2026 18:55
…d read telemetry back

The workflow creates an AWS source, deploys the template with that
source's ClusterId, token and id, pastes the outputs back into the
source, waits for the sync's subscription filters, produces logs, a
custom metric, an X-Ray trace and a CloudTrail management event, and
reads all four back through the team's SQL API connection with exact
counts. Pull requests and pushes to main deploy the checkout's template;
the daily schedule deploys the published one and fails when it differs
from main. Everything is deleted afterwards, plus leftovers older than
two hours. AWS access is OIDC through the role in e2e/bootstrap.yaml.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A cancelled job gets only a short grace period, so its cleanup can be
killed while the stack is still deleting; the next run then collided
with the leftover for up to two hours. The cleanup now lives in
e2e/cleanup.sh, runs before and after every run, and deletes everything
of ours regardless of age, since the concurrency group allows one run
per account. It matches run stacks by their e2e-gh prefix: the earlier
e2e- prefix also matched the standing e2e-bootstrap stack and only its
age had protected it. configure-aws-credentials goes to v5.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A fresh customer account gets both subscribed by default. The sandbox
account is permanent in Better Stack (one AwsAccount per organization
and AWS account id, kept across source deletions), and each earlier
run's stack deletion looked like an external unsubscribe to the sync's
reconcile, which then never re-subscribed them. The run now creates
explicit subscriptions for the two through the log-group-subscriptions
API once X-Ray has created aws/spans; the run's own log group still
relies on auto-subscribe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… both

Two log groups are created before the stack: one named after the run and
one always named /e2e/static, which every run deletes and recreates. The
sync must auto-subscribe the new one on its own. The static group,
aws/spans and the CloudTrail group must stay unsubscribed, because the
sandbox account record is permanent in Better Stack (T-21948) and a
deleted and recreated group counts as explicitly unsubscribed (T-21949);
that step names both issues and fails when either is fixed. The three are
then subscribed through the log-group-subscriptions API, and 20 lines are
written into each of the two groups and counted per group.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… stored trace id

The mapper stores an X-Ray span as {dt, resource, span: {...}}, so the
trace id and name live under span. Spans are counted by their names and
the sample's stored trace ids must equal the X-Ray trace id in its
32-hex form. CloudTrail delivers management events within 15 minutes
and the trail is minutes old at that point, so the read-back waits up
to 25 minutes and, on a timeout, saves the newest CloudTrail and span
rows for the summary.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he API subscriptions

CloudTrail writes its events into /aws/cloudtrail/better-stack and the
subscription filter is in place, but CloudWatch Logs forwards none of
CloudTrail's batches to the Firehose, while events written into the
same group by hand are forwarded and a CloudTrail record pushed into the
Firehose by hand reaches the source (T-21951). The read-back no longer
waits for CloudTrail; it asserts that no CloudTrail row arrived and
fails, naming the issue, once rows start flowing. The frozen-subscription
check and the API subscriptions are one step now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Production accounts with older stacks do receive CloudTrail rows
(T-21951 updated); the sandbox only ever sees a stack's first half hour.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every run deletes the template's IAM roles and the next one recreates them
under the same names, while AWS keeps using sessions of the deleted roles for
up to an hour. CloudWatch Logs holds the subscription role's session per log
group name, so delivery from aws/spans, /aws/cloudtrail/better-stack and
/e2e/static failed with a non-retryable error, the filter paused for 10
minutes and skipped what arrived meanwhile, even for a log group recreated
with a new filter. CloudTrail holds its delivery role's session the same way
and wrote nothing into its log group. Measured in the sandbox account: stale
filters recovered exactly one hour after CloudWatch Logs had assumed the role,
and a stack deployed an hour after the previous one forwarded all 123
CloudTrail events with no delivery error.

A run now waits until 65 minutes after the latest e2e-gh* stack deletion,
then refreshes its AWS credentials. The CloudTrail check requires this run's
PutRetentionPolicy event as exactly one row instead of freezing CloudTrail at
none, the read-back allows 20 minutes and prints each group's incoming,
forwarded and delivery-error counts when something is missing. The sweep also
deletes /aws/application-signals/data, which X-Ray Transaction Search creates
outside the stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hour-long wait goes. A step measures how long ago the latest e2e-gh*
stack was deleted. More than 65 minutes ago, the read-back is strict as
before and waits for the static group, aws/spans and the CloudTrail event.
Within that hour it waits only for the run's own log group and the metric;
the static group, aws/spans or CloudTrail delivering nothing becomes a
warning in the log and the job summary, with the forwarding metrics printed.
Any other wrong count still fails, and the span trace id is checked whenever
the spans arrived.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PetrHeinz
PetrHeinz marked this pull request as ready for review October 16, 2026 10:26
PetrHeinz and others added 3 commits October 16, 2026 12:28
This workflow only ever assigns Petr, so the assignee input, the Pawel and
Collectors secrets and the branch choosing between them go. The issue lands
in the Telemetry team, state Plan, project Ingestion for everything, priority
High, assigned to Petr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The read-back only counted metric rows and the sample showed name and tags,
so wrong values would have passed. The three datapoints now carry 1, 2 and 3
in three consecutive minutes. The mapper stores each minute's aggregate as
the metric plus _max, _min, _sum and _count, and all 15 points must hold
exactly what was sent (1 for _count), comparing the lowest and highest stored
value per point and grouping by minute, since AWS does not document whether
the metric stream stamps a minute's start. The sample shows the metric's
three minutes with their values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bootstrap stack is deployed once per account and already exists, so the
template does not need to live in this repository; the header still names
the standing e2e-bootstrap stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PetrHeinz
PetrHeinz merged commit 5384ff4 into main Oct 16, 2026
6 checks passed
@PetrHeinz
PetrHeinz deleted the claude/t-3853-e2e branch October 16, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant