Skip to content

Commit b5916a4

Browse files
author
David Elner
committed
Added: Release test for Ruby
1 parent 5d6537c commit b5916a4

6 files changed

Lines changed: 317 additions & 0 deletions

File tree

.github/workflows/release-ruby.yml

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#
2+
# Releases bt-fake — a dummy Ruby gem used to validate Braintrust release
3+
# workflow tooling end-to-end. This does NOT release the braintrust Ruby SDK.
4+
#
5+
# This workflow serves two purposes:
6+
# 1. End-to-end testing of the composite actions in actions/release/
7+
# 2. Reference implementation of the Ruby-specific release template
8+
#
9+
# Generic steps are provided by composite actions in actions/release/.
10+
# Only the sections marked with inline comments need to change for other Ruby gems.
11+
#
12+
# ─────────────────────────────────────────────────────────────────────────────
13+
# SETUP REQUIREMENTS
14+
# ─────────────────────────────────────────────────────────────────────────────
15+
#
16+
# GitHub Environments (repo Settings → Environments):
17+
# rubygems-publish
18+
# - Required reviewers: sdk-eng team or named maintainers
19+
# - Prevent self-review: enabled
20+
# - Deployment branches: main only
21+
# rubygems-publish-dry-run
22+
# - Required reviewers: at least yourself (to test the approval gate)
23+
# - Allow administrators to bypass: enabled (for testing flexibility)
24+
# - Deployment branches: no restriction
25+
#
26+
# Repository Variables (Settings → Secrets and variables → Variables):
27+
# SLACK_SDK_RELEASE_CHANNEL Slack channel ID for release notifications
28+
#
29+
# Repository Secrets (Settings → Secrets and variables → Secrets):
30+
# SLACK_BOT_TOKEN Org-level secret — Brainbot Slack app token
31+
# Must be invited to SLACK_SDK_RELEASE_CHANNEL
32+
#
33+
# RubyGems.org Trusted Publishing:
34+
# Configure for your gem at rubygems.org → gem settings → Trusted Publishers
35+
# Repository: braintrustdata/sdk-actions (or your SDK repo)
36+
# Workflow: release-ruby.yml (or your workflow filename)
37+
# Environment: rubygems-publish
38+
#
39+
# ─────────────────────────────────────────────────────────────────────────────
40+
41+
name: Release Ruby (bt-fake)
42+
43+
on:
44+
workflow_dispatch:
45+
inputs:
46+
_instructions:
47+
description: "⚠️ Before starting: Merge a version bump PR. Version is read from the SHA."
48+
type: string
49+
default: "I have merged a version bump PR"
50+
required: false
51+
sha:
52+
description: "Commit SHA (of the version bump) to release"
53+
required: true
54+
type: string
55+
dry_run:
56+
description: "Dry run: Build without tagging or publishing"
57+
type: boolean
58+
default: false
59+
60+
jobs:
61+
bump:
62+
runs-on: ubuntu-24.04
63+
timeout-minutes: 5
64+
permissions: {}
65+
outputs:
66+
version: ${{ steps.bump.outputs.version }}
67+
steps:
68+
# bt-fake: generates a version from the run number — no version bump PR needed.
69+
# Remove this job in real SDK workflows; version comes from the version bump PR.
70+
- name: Bump version
71+
id: bump
72+
run: echo "version=0.0.${{ github.run_number }}" >> $GITHUB_OUTPUT
73+
74+
validate:
75+
needs: bump
76+
runs-on: ubuntu-24.04
77+
timeout-minutes: 10
78+
permissions:
79+
contents: read
80+
outputs:
81+
release_tag: ${{ steps.validate-release.outputs.release_tag }}
82+
prev_tag: ${{ steps.validate-release.outputs.prev_tag }}
83+
branch: ${{ steps.validate-release.outputs.branch }}
84+
on_main: ${{ steps.validate-release.outputs.on_main }}
85+
commit_message: ${{ steps.validate-release.outputs.commit_message }}
86+
steps:
87+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
88+
with:
89+
ref: ${{ inputs.sha }}
90+
fetch-depth: 0
91+
92+
# Update working-directory to your gem's root
93+
- name: Set up language runtime
94+
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
95+
with:
96+
ruby-version: '3.4'
97+
bundler-cache: true
98+
working-directory: test/release/ruby
99+
100+
# bt-fake: sources from the bump job instead of a version file.
101+
# In a real Ruby project: replace with the path to your version.rb and module name.
102+
# e.g. ruby -r "./lib/your_gem/version.rb" -e "puts YourGem::VERSION"
103+
- name: Read version
104+
id: read-version
105+
run: echo "version=${{ needs.bump.outputs.version }}" >> $GITHUB_OUTPUT
106+
107+
- name: Validate release
108+
id: validate-release
109+
uses: ./actions/release/validate
110+
with:
111+
version: ${{ steps.read-version.outputs.version }}
112+
sha: ${{ inputs.sha }}
113+
dry_run: ${{ inputs.dry_run }}
114+
115+
prepare:
116+
needs: validate
117+
runs-on: ubuntu-24.04
118+
timeout-minutes: 5
119+
permissions:
120+
contents: write # required for releases/generate-notes API
121+
outputs:
122+
pr_list: ${{ steps.prepare.outputs.pr_list }}
123+
notes: ${{ steps.prepare.outputs.notes }}
124+
steps:
125+
- name: Prepare release
126+
id: prepare
127+
uses: ./actions/release/prepare
128+
with:
129+
release_tag: ${{ needs.validate.outputs.release_tag }}
130+
sha: ${{ inputs.sha }}
131+
prev_tag: ${{ needs.validate.outputs.prev_tag }}
132+
133+
notify-pending:
134+
needs: [validate, prepare]
135+
runs-on: ubuntu-24.04
136+
timeout-minutes: 5
137+
permissions: {}
138+
steps:
139+
- name: Notify release pending
140+
uses: ./actions/release/notify-pending
141+
with:
142+
sha: ${{ inputs.sha }}
143+
release_tag: ${{ needs.validate.outputs.release_tag }}
144+
prev_tag: ${{ needs.validate.outputs.prev_tag }}
145+
branch: ${{ needs.validate.outputs.branch }}
146+
on_main: ${{ needs.validate.outputs.on_main }}
147+
commit_message: ${{ needs.validate.outputs.commit_message }}
148+
pr_list: ${{ needs.prepare.outputs.pr_list }}
149+
notes: ${{ needs.prepare.outputs.notes }}
150+
dry_run: ${{ inputs.dry_run }}
151+
slack_token: ${{ secrets.SLACK_BOT_TOKEN }}
152+
slack_channel: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }}
153+
154+
publish:
155+
needs: [bump, validate, prepare, notify-pending]
156+
runs-on: ubuntu-24.04
157+
timeout-minutes: 15
158+
environment: ${{ inputs.dry_run && 'rubygems-publish-dry-run' || 'rubygems-publish' }}
159+
permissions:
160+
contents: write
161+
id-token: write
162+
steps:
163+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
164+
with:
165+
ref: ${{ inputs.sha }}
166+
fetch-depth: 0
167+
168+
# bt-fake: write the run-number version into version.rb before building.
169+
# The publish job checks out fresh, so version.rb still has the placeholder.
170+
# Real SDK workflows omit this — version.rb is already correct from the bump PR.
171+
- name: Apply version to version.rb
172+
run: |
173+
VERSION="${{ needs.bump.outputs.version }}"
174+
sed -i "s|VERSION = \".*\"|VERSION = \"$VERSION\"|" \
175+
test/release/ruby/lib/bt_fake/version.rb
176+
177+
# Update working-directory to your gem's root
178+
- name: Publish gem
179+
uses: ./actions/release/ruby/publish
180+
with:
181+
working-directory: test/release/ruby
182+
dry_run: ${{ inputs.dry_run }}
183+
184+
- name: Create GitHub release
185+
uses: ./actions/release/create-github-release
186+
with:
187+
release_tag: ${{ needs.validate.outputs.release_tag }}
188+
sha: ${{ inputs.sha }}
189+
notes: ${{ needs.prepare.outputs.notes }}
190+
dry_run: ${{ inputs.dry_run }}
191+
192+
- name: Notify release complete
193+
uses: ./actions/release/notify-published
194+
with:
195+
release_tag: ${{ needs.validate.outputs.release_tag }}
196+
prev_tag: ${{ needs.validate.outputs.prev_tag }}
197+
branch: ${{ needs.validate.outputs.branch }}
198+
on_main: ${{ needs.validate.outputs.on_main }}
199+
pr_list: ${{ needs.prepare.outputs.pr_list }}
200+
dry_run: ${{ inputs.dry_run }}
201+
slack_token: ${{ secrets.SLACK_BOT_TOKEN }}
202+
slack_channel: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Publish Ruby Gem
2+
description: >
3+
Sets up Ruby and publishes a gem to RubyGems with OIDC trusted publishing
4+
and SLSA attestation via rubygems/release-gem. Reads DRY_RUN from the
5+
environment — set DRY_RUN=true to lint and build without pushing.
6+
7+
inputs:
8+
working-directory:
9+
description: "Path to the gem root (where the Gemfile and gemspec live)"
10+
required: false
11+
default: '.'
12+
ruby-version:
13+
description: "Ruby version to use (defaults to .ruby-version file if present)"
14+
required: false
15+
default: '3.4'
16+
dry_run:
17+
description: "Skip gem push and tag push"
18+
required: false
19+
default: 'false'
20+
21+
runs:
22+
using: composite
23+
steps:
24+
- name: Set up Ruby
25+
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
26+
with:
27+
ruby-version: ${{ inputs.ruby-version }}
28+
bundler-cache: true
29+
working-directory: ${{ inputs.working-directory }}
30+
31+
- name: Publish gem with attestation
32+
uses: rubygems/release-gem@6317d8d1f7e28c24d28f6eff169ea854948bd9f7 # v1.2.0
33+
with:
34+
await-release: ${{ inputs.dry_run == 'true' && 'false' || 'true' }}
35+
setup-trusted-publisher: ${{ inputs.dry_run == 'true' && 'false' || 'true' }}
36+
attestations: ${{ inputs.dry_run == 'true' && 'false' || 'true' }}
37+
working-directory: ${{ inputs.working-directory }}
38+
env:
39+
GITHUB_TOKEN: ${{ github.token }}
40+
DRY_RUN: ${{ inputs.dry_run }}

test/release/ruby/Gemfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
gemspec
6+
7+
gem "rake", "~> 13.0"
8+
gem "standard", "~> 1.0"

test/release/ruby/Rakefile

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
require "rake/testtask"
4+
5+
desc "Run StandardRB linter"
6+
task :lint do
7+
sh "bundle exec standardrb"
8+
end
9+
10+
desc "Build gem into pkg/"
11+
task :build do
12+
sh "gem build bt-fake.gemspec --output pkg/bt-fake-$(ruby -r './lib/bt_fake/version' -e 'print BtFake::VERSION').gem"
13+
end
14+
15+
namespace :release do
16+
task publish: [:lint, :build] do
17+
gem_files = FileList["pkg/bt-fake-*.gem"]
18+
raise "No gem file found after build" if gem_files.empty?
19+
20+
if ENV["DRY_RUN"] == "true"
21+
puts "DRY RUN: would push #{gem_files.first} to RubyGems (skipped)"
22+
else
23+
sh "gem push #{gem_files.first}"
24+
puts "✓ Gem pushed to RubyGems"
25+
end
26+
end
27+
28+
task :push_tag do
29+
require_relative "lib/bt_fake/version"
30+
tag = "v#{BtFake::VERSION}"
31+
if ENV["DRY_RUN"] == "true"
32+
puts "DRY RUN: would push tag #{tag} to origin (skipped)"
33+
else
34+
sh "git tag #{tag}"
35+
sh "git push origin #{tag}"
36+
puts "✓ Tag #{tag} pushed"
37+
end
38+
end
39+
end
40+
41+
# Called by rubygems/release-gem in the release workflow.
42+
# Follows Bundler convention: build, push gem, push tag.
43+
# GitHub release creation is handled separately by the workflow.
44+
task release: ["release:publish", "release:push_tag"] do
45+
puts "✓ Release complete"
46+
end

test/release/ruby/bt-fake.gemspec

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "lib/bt_fake/version"
4+
5+
Gem::Specification.new do |spec|
6+
spec.name = "bt-fake"
7+
spec.version = BtFake::VERSION
8+
spec.authors = ["Braintrust"]
9+
spec.email = ["info@braintrust.dev"]
10+
spec.summary = "Braintrust internal tooling."
11+
spec.license = "Apache-2.0"
12+
spec.required_ruby_version = ">= 3.2.0"
13+
14+
spec.files = Dir.glob("lib/**/*.rb")
15+
spec.require_paths = ["lib"]
16+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
module BtFake
4+
VERSION = "0.0.1"
5+
end

0 commit comments

Comments
 (0)