Skip to content

Release Ruby custom-build (bt-publishing-test) #20

Release Ruby custom-build (bt-publishing-test)

Release Ruby custom-build (bt-publishing-test) #20

#
# Reference implementation of the v2 Ruby CUSTOM-BUILD ("build-ownership") shape, using
# bt-publishing-test. This does NOT release a real gem.
#
# Same pipeline as turnkey, cut at the build⟷publish seam:
# configure (facts + manifest) → validate (gate) → pack (unprivileged build) →
# request-approval → [gate] → ship-package
# pack builds + attests the gem with NO publish credentials and uploads it; ship-package
# (gated) verifies its attestations and `gem push`es THAT exact gem.
#
# SETUP: RubyGems Trusted Publishing must list THIS workflow filename
# (release-ruby-custom.yml) + the `publish` environment. Only ship-package publishes.
name: Release Ruby custom-build (bt-publishing-test)
on:
pull_request:
paths:
- 'actions/**'
- '.github/workflows/**'
workflow_dispatch:
inputs:
release_type:
description: "Release type — maps to the github_release default"
type: choice
default: stable
options: [stable, prerelease]
sha:
description: "Commit SHA (of the version bump) to release"
required: true
type: string
prev_release:
description: "Anchor for release notes: a tag name or commit SHA."
type: string
required: false
default: '58a397193105516446c3042361913655bcece509'
dry_run:
description: "Dry run: build + pack (no attest, no publish)"
type: boolean
default: false
jobs:
bump:
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions: {}
outputs:
version: ${{ steps.bump.outputs.version }}
steps:
- name: Bump version
id: bump
run: |
CURRENT=$(curl -sf https://rubygems.org/api/v1/gems/bt-publishing-test.json 2>/dev/null | jq -r '.version // empty' 2>/dev/null || true)
CURRENT=${CURRENT:-0.0.0}
IFS='.' read -r major minor patch <<< "$CURRENT"
echo "version=$major.$minor.$((patch + 1))" >> $GITHUB_OUTPUT
configure:
needs: bump
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: write
outputs:
version: ${{ steps.configure.outputs.version }}
release_tag: ${{ steps.configure.outputs.release_tag }}
prev_release: ${{ steps.configure.outputs.prev_release }}
branch: ${{ steps.configure.outputs.branch }}
on_release_branch: ${{ steps.configure.outputs.on_release_branch }}
already_published: ${{ steps.configure.outputs.already_published }}
notes: ${{ steps.configure.outputs.notes }}
package: ${{ steps.configure.outputs.package }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Configure release
id: configure
uses: ./actions/release/lang/ruby/configure
with:
version: ${{ needs.bump.outputs.version }}
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
working_directory: test/release/ruby
release_type: ${{ inputs.release_type || 'stable' }}
rubygems_package_name: bt-publishing-test
package_label: 'bt-publishing-test'
emoji: ':ruby:'
prev_release: ${{ inputs.prev_release || github.event.pull_request.head.sha }}
validate:
needs: [bump, configure]
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Validate release
uses: ./actions/release/lang/ruby/validate
with:
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }}
working_directory: test/release/ruby
release_tag: ${{ needs.configure.outputs.release_tag }}
on_release_branch: ${{ needs.configure.outputs.on_release_branch }}
notes: ${{ needs.configure.outputs.notes }}
already_published: ${{ needs.configure.outputs.already_published }}
build: 'false' # the pack job owns the build (lint still runs)
sbom: 'false' # pack generates + attests the SBOM
# BUILD side — unprivileged: NO publish credentials.
pack:
needs: [bump, configure, validate]
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.sha || github.event.pull_request.head.sha }}
fetch-depth: 0
# bt-publishing-test: write the resolved version into version.rb + Gemfile.lock.
- name: Apply version to version.rb and Gemfile.lock
env:
VERSION: ${{ needs.configure.outputs.version }}
run: |
sed -i "s|VERSION = \".*\"|VERSION = \"$VERSION\"|" \
test/release/ruby/lib/bt_publishing_test/version.rb
sed -i "s|bt-publishing-test ([0-9]*\.[0-9]*\.[0-9]*)|bt-publishing-test ($VERSION)|" \
test/release/ruby/Gemfile.lock
- name: Pack + attest
uses: ./actions/release/lang/ruby/pack
with:
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
checkout: 'false'
working_directory: test/release/ruby
dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }}
# Stage the exact built gem + SBOM for the gated ship job.
- name: Stage artifact
run: |
mkdir -p artifact
cp test/release/ruby/pkg/*.gem artifact/
cp test/release/ruby/sbom.json artifact/ 2>/dev/null || true
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-package
path: artifact/
retention-days: 1
if-no-files-found: error
request-approval:
needs: [configure, validate, pack]
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions: {}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Request release approval
uses: ./actions/release/request-approval
with:
packages: '[${{ needs.configure.outputs.package }}]'
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
branch: ${{ needs.configure.outputs.branch }}
on_release_branch: ${{ needs.configure.outputs.on_release_branch }}
commit_message: ${{ needs.configure.outputs.commit_message }}
dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }}
slack_token: ${{ github.event_name != 'pull_request' && secrets.SLACK_BOT_TOKEN || '' }}
slack_channel: ${{ github.event_name != 'pull_request' && vars.SLACK_SDK_RELEASE_CHANNEL || '' }}
# PUBLISH side — gated.
ship-package:
needs: [configure, validate, pack, request-approval]
runs-on: ubuntu-24.04
timeout-minutes: 15
environment: >-
${{ github.event_name != 'pull_request'
&& (inputs.dry_run && 'publish-dry-run' || 'publish')
|| '' }}
permissions:
contents: write
id-token: write # OIDC trusted publishing
attestations: read # gh attestation verify
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-package
path: dl
- name: Ship package
uses: ./actions/release/lang/ruby/ship-package
with:
working_directory: dl # holds the .gem + sbom.json
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }}
release_tag: ${{ needs.configure.outputs.release_tag }}
already_published: ${{ needs.configure.outputs.already_published }}
# ⚠️ TEST-BED EXCEPTION — do NOT copy. Real consumers wire the derived value:
# github_release: ${{ needs.configure.outputs.github_release }}
github_release: 'false'
gem_name: bt-publishing-test
version: ${{ needs.configure.outputs.version }}
label: bt-publishing-test
notes: ${{ needs.configure.outputs.notes }}
prev_release: ${{ needs.configure.outputs.prev_release }}
branch: ${{ needs.configure.outputs.branch }}
on_release_branch: ${{ needs.configure.outputs.on_release_branch }}
slack_token: ${{ github.event_name != 'pull_request' && secrets.SLACK_BOT_TOKEN || '' }}
slack_channel: ${{ github.event_name != 'pull_request' && vars.SLACK_SDK_RELEASE_CHANNEL || '' }}
emoji: ':ruby:'