Skip to content

ci: publish releases to trunk.io/releases/analytics-cli as well as GitHub - #1216

Merged
trunk-io[bot] merged 5 commits into
mainfrom
dylan/publish-releases-to-s3
Oct 5, 2026
Merged

trunk-io[bot] merged 5 commits into
mainfrom
dylan/publish-releases-to-s3

Conversation

@dfrankland

@dfrankland dfrankland commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR publishes every analytics-cli release to our own bucket as well as to GitHub. The S3 copy is served at https://trunk.io/releases/analytics-cli/prod/. The analytics-uploader action and the docs switch to it in follow-up PRs, so they stop depending on GitHub's release download and API rate limits. GitHub releases keep being created, because the frozen legacy trunk launcher and customers' own scripts download from them.

Contract

Everything lives in bucket trunk-releases (account 443897627245, us-west-2) under prefix releases/analytics-cli/prod/:

Object Shape Mutability
<version>/<asset> Every *.tar.gz / *.zip the GitHub release carries, with the same filename (trunk-analytics-cli-<target>.tar.gz, trunk-analytics-cli-x86_64-pc-windows-gnu-experimental.zip, and the proto-bin-to-json, xcresult-to-junit and check-codeowners tarballs). Each has sha256 + release-version object metadata. Immutable. An existing key is skipped when its recorded sha256 matches and is an error when it differs.
<version>/manifest.json {"version":"<ver>","artifacts":{"<filename>":{"sha256":"<hex>"}}} Uploaded last, so its presence means the version is complete.
channel.json {"latest":"<ver>"}, Content-Type: application/json, Cache-Control: max-age=60 The only mutable object. It is written only on promotion, and only if <ver>/manifest.json exists.

Signing is deliberately left out. The release KMS key also signs trunk-cli's manifests, and those signatures are not bound to a path. Giving this public repo kms:Sign on that key would let it produce trunk-cli signatures. Today consumers trust TLS to GitHub, and this keeps the same trust with TLS to trunk.io instead.

Changes

  • .github/scripts/publish-release-to-s3.sh [--skip-complete] <version> <dir> handles uploads, the immutability check and the manifest. --skip-complete turns an already-complete version into a no-op, so a backfill can be re-run. A version left partial (no manifest) re-uploads only the missing objects. A head-object failure other than 404 aborts the run, so a 403 caused by a missing s3:ListBucket is never read as "absent".
  • .github/scripts/set-latest-in-s3.sh <version> writes channel.json, and refuses when the version has no manifest.
  • release_cli.yml: tag_and_release publishes ./build/*.tar.gz ./build/*.zip to S3 after gh release create and leaves the pointer alone. The version stays a prerelease, matching --prerelease --latest=false. release.yml now grants id-token: write to the called workflow.
  • Promote Release (new) replaces the manual "Set as latest release" step in the GitHub UI. It points channel.json at the version and marks it latest on GitHub. It refuses prerelease semver and draft releases. S3 goes first because its preconditions (the role and a complete manifest) are the ones that fail in practice, and when they fail GitHub has not moved either. Both steps are idempotent, so if the GitHub edit fails, a re-run converges.
  • Mirror Releases to S3 (new) runs gh release download, checks each asset against the sha256 digest GitHub recorded for it (verify-github-digests.sh; GitHub has digests only for releases from 0.10.0-beta.0, June 2025, onward, so older ones are copied unchecked), then runs the publish script with --skip-complete, for space-separated tags or all (247 releases, about 45 MB each). Its optional set-latest-to-github-latest input points channel.json at GitHub's current latest release.
  • CONTRIBUTING.md gains a "Releasing" section.
  • smoke_test.yml is unchanged. It runs every 2 hours against GitHub's latest release, and between this merge and the backfill nothing exists on S3, so switching it now would page every two hours. Moving it to channel.json is a small follow-up once the backfill has run.

Prerequisites (not done by this PR)

  1. Create the IAM role trunk-analytics-cli-release-role in account 443897627245, following trunk2's docs/runbooks/trunk-releases-publish-roles.md.
    • Trust: repo:trunk-io/analytics-cli:ref:refs/heads/main. This is a public repo, so do not use :*.
    • Grants: s3:PutObject/GetObject/AbortMultipartUpload on releases/analytics-cli/*, plus s3:ListBucket conditioned on that prefix, which the scripts need to tell a 404 from a 403. No DeleteObject and no KMS.
  2. Set the repository variable TRUNK_RELEASES_PUBLISH_ROLE_ARN to the role's ARN.

Rollout order

  1. Create the role and set the repository variable.
  2. Merge this PR.
  3. Run Mirror Releases to S3 with tags: all and set-latest-to-github-latest: true, then check https://trunk.io/releases/analytics-cli/prod/channel.json.
  4. Only then merge the analytics-uploader and docs PRs. Pinned cli-versions need the backfilled history.
  5. From the next release on, cut with Release and promote with Promote Release.

Testing

  • I exercised both scripts against a fake aws that emulates S3. Covered cases: fresh publish, republish refused, --skip-complete no-op, resuming a partial version, refusing an asset whose sha differs, aborting on a 403 from head-object, set-latest refused before publish, and set-latest after publish.
  • shellcheck and shfmt pass on the scripts, and actionlint and yamllint pass on the new and changed workflows. release_cli.yml's compress step already had shellcheck findings, and I did not touch them.
  • Not run: any workflow, any AWS write, or trunk check itself (rust@SYSTEM was not resolvable on my machine, so I ran the linters directly).

🤖 Generated with Claude Code

…tHub

Each release's assets are also uploaded to the trunk-releases bucket under
releases/analytics-cli/prod/<version>/, with a manifest of sha256s written last
and a channel.json pointer that only the new Promote Release workflow moves.
Mirror Releases to S3 backfills releases cut before this lane existed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

@trunk-staging-io

trunk-staging-io Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

Failed Test Failure Summary Logs
pending_quarantine_test should be quarantined when run with variant A test marked as pending was expected to fail but unexpectedly passed. Logs ↗︎
variant_quarantine_test should be quarantined when run with variant A test expected the sum of 2 + 2 to be 5, but it was actually 4, indicating a failed assertion. Logs ↗︎

View Full Report ↗︎ ⋅ Docs

@codecov-commenter

codecov-commenter commented Oct 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.08%. Comparing base (be7bb49) to head (352ccd2).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1216      +/-   ##
==========================================
+ Coverage   83.86%   84.08%   +0.21%     
==========================================
  Files          74       74              
  Lines       17884    17884              
==========================================
+ Hits        14998    15037      +39     
+ Misses       2886     2847      -39     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trunk-io

trunk-io Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fi

jq -nc --arg v "${version}" '{latest: $v}' |
aws s3 cp - "s3://${bucket}/${prefix}/channel.json" --only-show-errors \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting the potential for accidentally overwriting other things here. Acceptable for now, just noting the one potential hole

role-skip-session-tagging: true
aws-region: us-west-2

# S3 moves first: its preconditions (the role, a complete manifest) are the ones that fail in

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

precondition: I would like to eventually do a FT_VERSION=foo trunk flaky-tests version here as one final check before promoting

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The launcher can't pin a version yet (trunk flakytests only fetches GitHub's latest), so before promoting this runs the published launcher with just that download URL pinned to the version and asserts trunk flakytests --version reports it. There's a TODO to switch to FT_VERSION=… trunk flakytests --version once the launcher supports it. (f0ae2eb)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I moved this check to #1217, which is stacked on this PR, and reverted the URL-rewrite version here. #1217 runs TRUNK_ANALYTICS_CLI_VERSION=<version> trunk flakytests --version through both the bash launcher and the new CLI, and fails unless both report the version being promoted. The variable is being added to the launcher in trunk-io/trunk#34317 and to the new CLI in trunk-io/trunk2#6728, so #1217 stays a draft until both are released.

dfrankland and others added 3 commits October 5, 2026 17:43
Fixes checkov CKV2_GHA_1. release_ruby_gem now gets its id-token
explicitly instead of from the repository default.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The launcher only installs GitHub's latest release, so run the published
launcher with its download URL pinned to the version being promoted and
require `trunk flakytests --version` to report it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ng it"

This reverts commit f0ae2eb. The precondition moves to its own PR, which
pins the version with TRUNK_ANALYTICS_CLI_VERSION instead of rewriting the
launcher's download URL.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@trunk-io
trunk-io Bot merged commit 23cdf28 into main Oct 5, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants