Skip to content

THRIFT-6068: Auto-publish Rust crate on GitHub Release - #3588

Closed
Jens-G wants to merge 3 commits into
apache:masterfrom
Jens-G:THRIFT-6068
Closed

Jens-G wants to merge 3 commits into
apache:masterfrom
Jens-G:THRIFT-6068

Conversation

@Jens-G

@Jens-G Jens-G commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

Fixes the release_rust.yml workflow so the Rust crate is automatically published to crates.io whenever a GitHub Release is published. Also supersedes #3587.

Changes:

  • Remove if: false gate that disabled the entire job (added in lieu of THRIFT-5917)
  • Switch trigger from push: tags: to release: types: [published] — consistent with pypi.yml and release_ruby.yml, and prevents publishing before the PMC vote passes
  • Add environment: release — required for OIDC trusted publishing (same as pypi/ruby)
  • Set RUSTUP_TOOLCHAIN: stable on cargo steps — the repo-pinned rust-toolchain (1.83) predates edition2024 support required by transitive dependencies (getrandom >= 0.4); stable Rust is pre-installed on ubuntu-latest runners
  • Fix publish condition to github.event_name == 'release' && !github.event.release.prerelease — dry-run runs on every trigger, actual publish only on non-prerelease GitHub Releases
  • Fix Cargo.toml wildcard dev-dependency uuid = "*" → uuid = "1" — crates.io rejects wildcard version constraints (this blocked the 0.23.0 publish; fixed separately in Fix wildcard dev-dependency version for uuid in Rust crate #3587 which this supersedes)

Prerequisites (one-time manual step — cannot be done in CI)

Trusted publishing on crates.io must be configured before the publish step can authenticate:

  1. Go to https://crates.io/crates/thrift → Settings → Trusted Publishing
  2. Add a trusted publisher:
    • Owner: apache
    • Repository: thrift
    • Workflow: release_rust.yml
    • Environment: release

This is the same OIDC mechanism already used for the existing workflow structure.

Limitations

The OIDC publish path cannot be end-to-end tested without merging and cutting a real release. The --dry-run step validates packaging; auth and upload can only be confirmed on first use.

Test plan

  • Dry-run step passes on this PR (validates packaging and toolchain)
  • Trusted publisher registered on crates.io (prerequisite, see above)
  • Verify publish fires on next GitHub Release (non-prerelease)

🤖 Generated with Claude Code

Client: rs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mergeable mergeable Bot added rust Pull requests that update Rust code github_actions Pull requests that update GitHub Actions code labels Jun 8, 2026
@Jens-G

Jens-G commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

@santiagomed Any comments from your side?

@santiagomed santiagomed left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Jens-G thanks for doing this. Launched an agent review above with 1 suggestion and 2 nits. Overall though seems GTM so will leave it up to you if you want to address the suggestion.

Comment thread .github/workflows/release_rust.yml Outdated
publish:
runs-on: ubuntu-latest
if: false # currently broken and no maintainers around -> see THRIFT-5917
environment: release

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[suggestion] The single publish job carries environment: release (line 35) and id-token: write (line 38) but still runs under the pull_request trigger and workflow_dispatch. The sibling release workflows in this repo (pypi.yml, release_ruby.yml) deliberately trigger their publish jobs only on release and have no pull_request trigger. Because this is pull_request (not pull_request_target), fork PRs get a read-only token and the auth/publish steps are additionally gated by github.event_name == 'release', so this is not directly exploitable. However, it means every PR that modifies this workflow file enters the protected release environment (potentially queuing a deployment / required-reviewer approval as noise) and is granted id-token: write for a context where it is never needed.

Suggestion: Split the PR/dispatch validation (cargo publish --dry-run) into its own job with no environment and minimal permissions: { contents: read }, and keep a separate publish job that is the only one carrying environment: release + id-token: write and is scoped to the release event (e.g. if: github.event_name == 'release' at the job level, mirroring pypi.yml/release_ruby.yml).

Comment thread .github/workflows/release_rust.yml Outdated
# required by transitive dependencies (getrandom >= 0.4).
run: cargo publish --dry-run
env:
RUSTUP_TOOLCHAIN: stable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] RUSTUP_TOOLCHAIN: stable (lines 50 and 64) floats the toolchain to whatever "stable" resolves to at run time. This makes both the dry-run validation and the actual published build non-reproducible, and means the artifact pushed to crates.io may be built against a different compiler than the rest of the repo's CI (which pins 1.83, per the added comment). A floating channel can silently change behavior between a passing dry-run and a later publish.

Suggestion: Consider pinning a specific stable version (e.g. RUSTUP_TOOLCHAIN: 1.XX.0) so the dry-run and publish use an identical, reproducible toolchain, and bump it intentionally.

Comment thread .github/workflows/release_rust.yml Outdated

- name: Publish
# Only publish on a non-prerelease GitHub Release event
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] The publish path (gated on github.event_name == 'release' && !github.event.release.prerelease) does not verify that the released tag/ref corresponds to the version declared in lib/rs/Cargo.toml. The prerelease guard is correct and draft releases are implicitly handled (a published release is never a draft). But if a maintainer publishes a Release whose tag does not match the Cargo.toml version, cargo publish will either republish/fail on an existing version or publish an unexpected version. Low risk (cargo rejects duplicate versions) but there is no early, explicit consistency check.

Suggestion: Optionally add a guard step that asserts the Cargo.toml version matches github.event.release.tag_name (stripping any leading v) before authenticating/publishing, failing fast on mismatch.

Client: rs

- Split single job into dry-run (no env/OIDC) + publish (release only)
- Pin RUSTUP_TOOLCHAIN to 1.85 instead of floating stable
- Add version consistency check between Cargo.toml and release tag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Jens-G

Jens-G commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review @santiagomed! Addressed all three points in the latest commit:

  • Suggestion: Split into two jobs — dry-run (runs on all triggers, no environment / no id-token: write) and publish (job-level if: release && !prerelease, keeps environment: release and OIDC)
  • Nit 1: Pinned RUSTUP_TOOLCHAIN to "1.85" (the minimum verified to work with getrandom >= 0.4) with an explicit rustup toolchain install step; added a comment explaining why
  • Nit 2: Added a version-consistency check step in the publish job that extracts the version from lib/rs/Cargo.toml and compares it against the release tag (stripping a leading v), failing early before auth if they differ

Comment thread .github/workflows/release_rust.yml Fixed
Client: rs

Pass release tag name via env var instead of direct template expansion
in the run block to prevent shell injection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Jens-G

Jens-G commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

Fixed the zizmor finding: moved github.event.release.tag_name out of the run: block and into an env: key (RELEASE_TAG), so it is passed as an environment variable rather than being shell-interpolated directly. That eliminates the injection surface.

@Jens-G Jens-G closed this in 878db58 Jun 9, 2026
@Jens-G
Jens-G deleted the THRIFT-6068 branch June 9, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

github_actions Pull requests that update GitHub Actions code rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants