Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
85e5dcf
pass release notes to publish action, and make it idempotent
jamis Jun 6, 2025
3e526e9
split build & publish
jamis Jun 9, 2025
84b5369
add PR-check action
jamis Jun 12, 2025
040fd64
add some debugging for troubleshooting
jamis Jun 13, 2025
fde9f93
more debugging
jamis Jun 13, 2025
d0e0dec
typo
jamis Jun 13, 2025
066352c
add `ref` input parameter
jamis Jun 16, 2025
057146a
permit write access to publish as well
jamis Jun 16, 2025
023a472
troubleshooting: show the permissions of the current user
jamis Jun 16, 2025
73e3905
fix indentation
jamis Jun 16, 2025
bdbbe4d
use role_name instead of permissions
jamis Jun 16, 2025
96dfd86
wrong quotes
jamis Jun 16, 2025
fb201ad
checkout submodules -- so the candidate.rake tasks can load
jamis Jun 16, 2025
ff75009
can't use pattern with merge-multiple
jamis Jun 16, 2025
1eb56ea
use `cat`, not `echo`, when writing a heredoc to a file
jamis Jun 16, 2025
3178251
build using a rake task, to accommodate gems that need a compile step
jamis Jun 17, 2025
2752ec6
pr-check action only works with push & workflow_dispatch
jamis Jun 19, 2025
f89b3ee
some debugging output to help troubleshooting
jamis Jun 19, 2025
cccd2b1
a bit more debug output
jamis Jun 19, 2025
d35fd27
okay, possibly checking the right things now
jamis Jun 19, 2025
5e58425
try again
jamis Jun 19, 2025
85472de
fix syntax error
jamis Jun 19, 2025
7c3926f
more debugging
jamis Jun 19, 2025
10239c3
need to get the actual PR record once we know its number
jamis Jun 19, 2025
339da5f
pulls/get doesn't return a list
jamis Jun 19, 2025
7c38a00
it's not a list, but it's still wrapped
jamis Jun 19, 2025
6da6fb7
remove debugging output
jamis Jun 19, 2025
e74b084
Merge branch 'main' into ruby-3643-update-release-process
jamis Jul 24, 2025
3eece64
remove trailing whitespace
jamis Jul 24, 2025
e8adfe6
pin to specific commit
jamis Jul 24, 2025
2947587
pin all action references
jamis Jul 24, 2025
83826e7
avoid potential template injection exploits
jamis Jul 24, 2025
d681705
trailing whitespace
jamis Jul 24, 2025
a385189
Add a comment to explain the obscure identifier
jamis Jul 28, 2025
e384925
document each commit SHA
jamis Jul 28, 2025
8fd60d1
Merge branch 'main' into ruby-3643-update-release-process
jamis Aug 20, 2025
7c367f3
allow variable interpolation in the heredoc
jamis Aug 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions ruby/build/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Build Gem
description: Build a gem for a DBX Ruby project
inputs:
app_id:
description: The APP_ID defined for this project
required: true
app_private_key:
description: The APP_PRIVATE_KEY defined for this project
required: true
artifact:
description: The name to give the generated artifact (e.g. "ruby" or "jruby")
required: false
default: ruby
bundler_cache_version:
description: The cache-version to use for the bundler cache
required: false
default: '0'
gem_name:
description: The name (sans extension) of the gemspec file (e.g. "mongo")
required: true
ref:
description: The reference to checkout (branch, tag, sha, etc)
required: true
ruby_version:
description: The version of Ruby to use (see setup-ruby/action.yml)
default: '3.2'
required: false
rubygems_version:
description: The version of Rubygems to use (see setup-ruby/action.yml)
required: false
default: latest

runs:
using: composite
steps:
- name: Check out the repository
uses: mongodb-labs/drivers-github-tools/secure-checkout@v2

Check failure

Code scanning / zizmor

unpinned action reference Error

unpinned action reference
with:
app_id: ${{ inputs.app_id }}
private_key: ${{ inputs.app_private_key }}
ref: ${{ inputs.ref }}
submodules: true

- name: Setup Ruby
uses: ruby/setup-ruby@v1

Check failure

Code scanning / zizmor

unpinned action reference Error

unpinned action reference
with:
ruby-version: ${{ inputs.ruby_version }}
rubygems: ${{ inputs.rubygems_version }}
bundler-cache: true
cache-version: ${{ inputs.bundler_cache_version }}

- name: Get the release version
id: release_version
shell: bash
run: echo "version=$(bundle exec rake version)" >> "$GITHUB_OUTPUT"

- name: Get the gem file name
shell: bash
id: gem_name
run: echo "name=$(ruby ${{ github.action_path }}/gem_name.rb ${{ inputs.gem_name }} ${{ steps.release_version.outputs.version }})" >> "$GITHUB_OUTPUT"

Check failure

Code scanning / zizmor

code injection via template expansion Error

code injection via template expansion

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion

- name: Build the gem
shell: bash
run: |
bundle exec rake build GEMSPEC="${{ inputs.gem_name }}.gemspec" GEM_FILE_NAME="${{ steps.gem_name.outputs.name }}"

Check failure

Code scanning / zizmor

code injection via template expansion Error

code injection via template expansion

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion

- name: Save the generated gem file for later
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact }}
path: ${{ steps.gem_name.outputs.name }}
retention-days: 1
overwrite: true
21 changes: 21 additions & 0 deletions ruby/build/gem_name.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

# This script generates the name of a gem file based on the provided
# gem name and version. It takes into account whether it is running
# under JRuby to append "-java" to the gem name if necessary.
#
# Usage:
# ruby gem_name.rb <gem_name> <gem_version>

if ARGV.length != 2
puts "Usage: ruby gem_name.rb <gem_name> <gem_version>"
exit 1
end

gem_name = ARGV.first
gem_version = ARGV.last

base_name = "#{gem_name}-#{gem_version}"
base_name = "#{base_name}-java" if defined?(JRUBY_VERSION)

puts "#{base_name}.gem"
112 changes: 112 additions & 0 deletions ruby/pr-check/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# PRs are only eligible for release if they are merged and have
# the `release-candidate` label.
#
# The only events allowed to trigger this action are:
# - push (in which case the commit sha is used to find the corresponding
# PR)
# - workflow_dispatch (in which case the PR is found from the inputs
# on the event)

name: PR Check
description: Check that a PR is eligible for release

outputs:
message:
description: The body of the pull request that is being released.
value: ${{ steps.check_pr.outputs.message }}
ref:
description: The ref of the pull request that is being released.
value: ${{ steps.check_pr.outputs.ref }}

runs:
using: composite
steps:
- name: "Check PR Eligibility"
id: check_pr
uses: actions/github-script@v7
with:
script: |
let pr;

// was this triggered by a push event?
if (context.eventName == 'push') {
// if so, we need to find the PR that corresponds to the commit
// that was pushed.
//
// because only maintainers can push to protected branches,
// we can assume the user has the correct permissions to do
// this.
const { data: listing } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.payload.after,
});

if (listing.length == 0) {
throw new Error(`Workflow aborted: No pull request found for the pushed commit (${context.payload.after}).`);
}

const response = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: listing[0].number,
});

pr = response.data;

// if it wasn't triggered by a push event, was it triggered by
// a workflow_dispatch event?
} else if (context.eventName == 'workflow_dispatch') {
// it is technically possible for users with only write access
// to trigger workflows; we need to make sure that the user
// who triggered this has either admin or maintain access to the
// repository.
const username = context.triggering_actor || context.actor;

const { data: perms } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username,
});

if (perms.role_name !== 'admin' && perms.role_name !== 'maintain') {
throw new Error(`User ${username} must have 'admin' or 'maintain' role to initiate the release process. (${perms.role_name})`);
}

// if so, we grab the PR with the number that was passed in with
// the inputs.
const number = context.payload.inputs.pr;
if (!number) {
throw new Error('Workflow aborted: No pull request number provided. (need `pr` input)');
}

const response = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: number,
});

pr = response.data;

// workflow was triggered by an unrecognized/unsupported event
} else {
throw new Error(`Workflow aborted: Unsupported event type: ${context.eventName}.`);
}

if (!pr) {
throw new Error('No pull request found for the triggered event.');
}

if (!pr.merged) {
throw new Error('Pull request is not merged.');
}

if (!pr.labels.some(label => label.name == 'release-candidate')) {
throw new Error('Pull request is not a release candidate.');
}

console.log('body: >>', pr.body, '<<');
console.log('ref: >>', pr.merge_commit_sha, '<<');

core.setOutput('message', pr.body);
core.setOutput('ref', pr.merge_commit_sha);
118 changes: 82 additions & 36 deletions ruby/publish/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Publish Ruby
description: Generate and publish gems, signatures, and assets for MongoDB Ruby projects
description: Publish gems, signatures, and assets for MongoDB Ruby projects
inputs:
app_id:
description: The APP_ID defined for this project
Expand Down Expand Up @@ -32,13 +32,20 @@
product_id:
description: The identifier of the product being published (e.g. "mongo-ruby-driver")
required: true
release_message_template:
description: The template for the release message. Use "{0}" in the text to refer to the current version.
ref:
description: The reference to checkout (branch, tag, sha, etc)
required: true
release_message:
description: The (markdown-formatted) text to post as the description of the new release
required: true
rubygems_version:
description: The version of Rubygems to use (see setup-ruby/action.yml)
required: false
default: latest
ruby_version:
description: The version of Ruby to use (see setup-ruby/action.yml)
default: '3.2'
required: false
silk_asset_group:
description: The Silk asset group for the project
required: true
Expand All @@ -51,18 +58,21 @@
with:
app_id: ${{ inputs.app_id }}
private_key: ${{ inputs.app_private_key }}
ref: ${{ inputs.ref }}
submodules: true

- name: Setup Ruby
uses: ruby/setup-ruby@dffc446db9ba5a0c4446edb5bca1c5c473a806c5 # v1
uses: ruby/setup-ruby@v1

Check failure

Code scanning / zizmor

unpinned action reference Error

unpinned action reference
with:
ruby-version: '3.2'
ruby-version: ${{ inputs.ruby_version }}
rubygems: ${{ inputs.rubygems_version }}
bundler-cache: true
cache-version: ${{ inputs.bundler_cache_version }}

- name: Get the release version
id: release_version
shell: bash
run: echo "RELEASE_VERSION=$(bundle exec rake version)" >> "$GITHUB_ENV"
run: echo "version=$(bundle exec rake version)" >> "$GITHUB_OUTPUT"

- name: Setup GitHub tooling for DBX Drivers
uses: mongodb-labs/drivers-github-tools/setup@v2
Expand All @@ -71,68 +81,104 @@
aws_region_name: ${{ inputs.aws_region_name }}
aws_secret_id: ${{ inputs.aws_secret_id }}

- name: Set output gem file name
shell: bash
run: |
echo "GEM_FILE_NAME=${{ inputs.gem_name }}-${{ env.RELEASE_VERSION }}.gem" >> "$GITHUB_ENV"

- name: Build the gem
shell: bash
run: |
gem build --output=${{ env.GEM_FILE_NAME }} ${{ inputs.gem_name }}.gemspec
- name: Fetch the gem artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true

- name: Sign the gem
- name: Sign the gems
uses: mongodb-labs/drivers-github-tools/gpg-sign@v2
with:
filenames: '${{ env.GEM_FILE_NAME }}'
filenames: '*.gem'

- name: Generate SSDLC Reports
uses: mongodb-labs/drivers-github-tools/full-report@v2
with:
product_name: ${{ inputs.product_name }}
release_version: ${{ env.RELEASE_VERSION }}
dist_filenames: ${{ env.GEM_FILE_NAME }}
release_version: ${{ steps.release_version.outputs.version }}
dist_filenames: '*.gem'
silk_asset_group: ${{ inputs.silk_asset_group }}

- name: Look for existing tag
id: tag_exists
shell: bash
run: |
if git rev-parse "v${{ steps.release_version.outputs.version }}" >/dev/null 2>&1; then

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "Tag v${{ steps.release_version.outputs.version }} already exists."

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "Tag v${{ steps.release_version.outputs.version }} does not exist."

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Create the tag
uses: mongodb-labs/drivers-github-tools/tag-version@v2
if: steps.tag_exists.outputs.exists == 'false'
with:
version: ${{ env.RELEASE_VERSION }}
version: ${{ steps.release_version.outputs.version }}
tag_template: "v${VERSION}"
tag_message_template: "Release tag for v${VERSION}"

- name: Create a new release
- name: Look for existing release
id: release_exists
shell: bash
run: gh release create v${{ env.RELEASE_VERSION }} --title ${{ env.RELEASE_VERSION }} --generate-notes --draft

- name: Capture the changelog
run: |
if gh release view "v${{ steps.release_version.outputs.version }}" >/dev/null 2>&1; then

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "Release v${{ steps.release_version.outputs.version }} already exists."

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "Release v${{ steps.release_version.outputs.version }} does not exist."

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Write release notes to file
shell: bash
run: gh release view v${{ env.RELEASE_VERSION }} --json body --template '{{ .body }}' >> changelog
run: |
cat <<'__RELEASE_NOTES_IKDJAIELD__' > release_notes.txt
Copy link
Collaborator

Choose a reason for hiding this comment

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

Cat on keyboard or intentional identifier that's guaranteed to not create conflicts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Intentional; I've added a comment to justify it.

${{ inputs.release_message }}

Check failure

Code scanning / zizmor

code injection via template expansion Error

code injection via template expansion
__RELEASE_NOTES_IKDJAIELD__

- name: Prepare release message
- name: Create a new release
if: steps.release_exists.outputs.exists == 'false'
shell: bash
run: |
echo "${{ format(inputs.release_message_template, env.RELEASE_VERSION) }}" > release-message
cat changelog >> release-message
run: gh release create v${{ steps.release_version.outputs.version }} --title ${{ steps.release_version.outputs.version }} --notes-file release_notes.txt --draft

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion

- name: Update release information
- name: Else update the existing release
if: steps.release_exists.outputs.exists == 'true'
shell: bash
run: |
echo "RELEASE_URL=$(gh release edit v${{ env.RELEASE_VERSION }} --notes-file release-message)" >> "$GITHUB_ENV"
run: gh release edit v${{ steps.release_version.outputs.version }} --notes-file release_notes.txt

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion

- name: Upload release artifacts
shell: bash
run: gh release upload v${{ env.RELEASE_VERSION }} ${{ env.GEM_FILE_NAME }} ${{ env.RELEASE_ASSETS }}/${{ env.GEM_FILE_NAME }}.sig
run: gh release upload --clobber v${{ steps.release_version.outputs.version }} *.gem ${{ env.RELEASE_ASSETS }}/*.sig

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion

Check warning

Code scanning / zizmor

code injection via template expansion Warning

code injection via template expansion

- name: Upload S3 assets
uses: mongodb-labs/drivers-github-tools/upload-s3-assets@v2
with:
version: ${{ env.RELEASE_VERSION }}
version: ${{ steps.release_version.outputs.version }}
product_name: ${{ inputs.product_id }}
dry_run: ${{ inputs.dry_run }}

- name: Look for existing gem
id: gem_exists
shell: bash
run: |
if gem search --remote ${{ inputs.gem_name }} --version ${{ steps.release_version.outputs.version }} | grep -q "${{ steps.release_version.outputs.version }}"; then

Check failure

Code scanning / zizmor

code injection via template expansion Error

code injection via template expansion

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "Gem ${{ inputs.gem_name }} version ${{ steps.release_version.outputs.version }} already exists."

Check failure

Code scanning / zizmor

code injection via template expansion Error

code injection via template expansion

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "Gem ${{ inputs.gem_name }} version ${{ steps.release_version.outputs.version }} does not exist."

Check failure

Code scanning / zizmor

code injection via template expansion Error

code injection via template expansion

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Publish the gem
uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1
if: inputs.dry_run == 'false'
uses: rubygems/release-gem@v1

Check failure

Code scanning / zizmor

unpinned action reference Error

unpinned action reference
if: inputs.dry_run == 'false' && steps.gem_exists.outputs.exists == 'false'
with:
await-release: false

- name: Publish the release
if: inputs.dry_run == 'false'
shell: bash
run: gh release edit v${{ steps.release_version.outputs.version }} --draft=false

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion

Loading