Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
67 changes: 67 additions & 0 deletions .github/actions/setup-ruby/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: "Setup Ruby"
description: "Put a Ruby from the runner's tool cache on PATH"

# The org Actions policy allows GitHub-owned actions/* only — no Marketplace, no
# verified creators — so ruby/setup-ruby is unavailable here. The runner image
# already ships a Ruby tool cache, so nothing is downloaded: this only selects
# from what is on the box.
#
# Deliberately does NOT install gems. Bundler must run after the Artifactory OIDC
# step has pointed it at the curated mirror; ruby/setup-ruby's `bundler-cache`
# ran `bundle install` before that step, resolving straight from rubygems.org.

inputs:
ruby-version:
description: "MAJOR.MINOR to select, e.g. '3.3'. The newest matching patch in the tool cache wins."
required: true

outputs:
ruby-version:
description: "Exact version selected, e.g. '3.3.12'"
value: ${{ steps.select.outputs.ruby-version }}

runs:
using: "composite"
steps:
- name: Select Ruby from the tool cache
id: select
shell: bash
env:
REQUESTED: ${{ inputs.ruby-version }}
run: |
set -euo pipefail

case "$(uname -m)" in
x86_64) ARCH=x64 ;;
aarch64|arm64) ARCH=arm64 ;;
*) echo "::error::Unsupported runner architecture $(uname -m)"; exit 1 ;;
esac

CACHE="${RUNNER_TOOL_CACHE}/Ruby"
if [ ! -d "${CACHE}" ]; then
echo "::error::No Ruby tool cache at ${CACHE}. This runner image does not ship one; the matrix must be pinned to a version it does provide."
exit 1
fi

# sort -V so 3.3.10 ranks above 3.3.9.
SELECTED=""
while IFS= read -r candidate; do
[ -x "${candidate}/${ARCH}/bin/ruby" ] && SELECTED="${candidate}"
done < <(find "${CACHE}" -mindepth 1 -maxdepth 1 -type d -name "${REQUESTED}.*" | sort -V)

if [ -z "${SELECTED}" ]; then
echo "::error::Ruby ${REQUESTED}.x is not in the tool cache. Available: $(find "${CACHE}" -mindepth 1 -maxdepth 1 -type d -printf '%f ' 2>/dev/null)"
exit 1
fi

echo "${SELECTED}/${ARCH}/bin" >> "${GITHUB_PATH}"
echo "ruby-version=$(basename "${SELECTED}")" >> "${GITHUB_OUTPUT}"
echo "Selected Ruby $(basename "${SELECTED}") (${ARCH}) from the tool cache"

- name: Verify toolchain
shell: bash
run: |
set -euo pipefail
ruby --version
gem --version
bundle --version
140 changes: 140 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]

permissions:
id-token: write
contents: read

env:
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}

jobs:
lint:
name: Lint
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up Ruby
uses: ./.github/actions/setup-ruby
with:
ruby-version: '3.3'

- name: Restore gem cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: vendor/bundle
key: bundle-${{ runner.os }}-ruby3.3-${{ hashFiles('Gemfile.lock') }}
restore-keys: bundle-${{ runner.os }}-ruby3.3-

- name: Authenticate with Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
with:
ecosystem: ruby
provider-name: github-actions-segmentio

- name: Install dependencies
run: |
bundle config set --local path vendor/bundle
bundle install --jobs 4

- name: Run RuboCop
run: bundle exec rubocop

test:
name: Test (Ruby ${{ matrix.ruby-version }})
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}
strategy:
fail-fast: false
matrix:
# Bounded by what the runner image caches — see .github/actions/setup-ruby.
# 3.1 left upstream support in March 2025 and the image does not carry it.
# 3.4 is cached and selectable, but activesupport 5.2 (a test-only dep)
# requires base64, which 3.4 removed from the default gems. Adding the
# base64 gem and regenerating Gemfile.lock would admit it.
ruby-version: ['3.2', '3.3']

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up Ruby
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version }}

- name: Restore gem cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: vendor/bundle
key: bundle-${{ runner.os }}-ruby${{ matrix.ruby-version }}-${{ hashFiles('Gemfile.lock') }}
restore-keys: bundle-${{ runner.os }}-ruby${{ matrix.ruby-version }}-

- name: Authenticate with Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
with:
ecosystem: ruby
provider-name: github-actions-segmentio

- name: Install dependencies
run: |
bundle config set --local path vendor/bundle
bundle install --jobs 4

- name: Run tests
run: bundle exec rake

build:
name: Build
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}
needs: [lint, test]

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up Ruby
uses: ./.github/actions/setup-ruby
with:
ruby-version: '3.3'

- name: Restore gem cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: vendor/bundle
key: bundle-${{ runner.os }}-ruby3.3-${{ hashFiles('Gemfile.lock') }}
restore-keys: bundle-${{ runner.os }}-ruby3.3-

- name: Authenticate with Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
with:
ecosystem: ruby
provider-name: github-actions-segmentio

- name: Install dependencies
run: |
bundle config set --local path vendor/bundle
bundle install --jobs 4

- name: Build gem
run: gem build analytics-ruby.gemspec

- name: Verify gem is installable
run: gem install ./analytics-ruby-*.gem

- name: Upload gem artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: analytics-ruby-gem
path: analytics-ruby-*.gem
if-no-files-found: error
112 changes: 112 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: Release

on:
release:
types: [published]

permissions:
id-token: write
contents: read

env:
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}

jobs:
test:
name: Verify
runs-on: ubuntu-x64

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up Ruby
uses: ./.github/actions/setup-ruby
with:
ruby-version: '3.3'

- name: Authenticate with Artifactory
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
with:
ecosystem: ruby
provider-name: github-actions-segmentio

- name: Install dependencies
run: bundle install

- name: Run tests
run: bundle exec rake

deploy:
name: Publish to RubyGems
runs-on: ubuntu-x64
needs: [test]
# Must name an environment that already exists with its protection rules;
# GitHub silently creates an unprotected one for any name it does not know.
# `production` carries required_reviewers: libraries-web-team.
environment: production
permissions:
id-token: write
contents: read

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up Ruby
uses: ./.github/actions/setup-ruby
with:
ruby-version: '3.3'

- name: Verify tag matches the gem version
run: |
set -euo pipefail
TAG="${GITHUB_REF#refs/tags/}"
TAG="${TAG#v}"
VERSION=$(sed -nE "s/.*VERSION *= *'([^']+)'.*/\\1/p" lib/segment/analytics/version.rb)
if [ "$TAG" != "$VERSION" ]; then
echo "::error::Release tag $TAG does not match VERSION $VERSION in lib/segment/analytics/version.rb"
exit 1
fi
echo "Releasing $VERSION"

- name: Build gem
run: gem build analytics-ruby.gemspec

# RubyGems trusted publishing, done inline rather than via
# rubygems/release-gem. That action is not on the org allow-list (it also
# nests rubygems/configure-rubygems-credentials, so it would need two
# entries), and it drives `rake release`, whose tag name is always
# "v#{version}" — this gem has always tagged bare, e.g. 2.5.0.
- name: Publish to RubyGems (trusted publishing)
run: |
set -euo pipefail

# aud must equal the RubyGems host exactly; the exchange enforces it.
JWT=$(curl -sS \
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=rubygems.org" \
| jq -r '.value')

if [ -z "$JWT" ] || [ "$JWT" = "null" ]; then
echo "::error::No GitHub OIDC token. The job needs 'permissions: id-token: write'."
exit 1
fi

# Returns a push-scoped key that expires in 15 minutes. Requires a
# trusted publisher registered on the gem for this repo, workflow
# filename and environment.
RESP=$(curl -sS -X POST \
--data-urlencode "jwt=${JWT}" \
https://rubygems.org/api/v1/oidc/trusted_publisher/exchange_token)

KEY=$(echo "$RESP" | jq -r '.rubygems_api_key // empty')
if [ -z "$KEY" ]; then
echo "::error::RubyGems token exchange failed."
echo "$RESP" | jq 'del(.rubygems_api_key)' 2>/dev/null \
|| echo "::error::(response withheld - not valid JSON)"
exit 1
fi
echo "::add-mask::$KEY"

GEM_HOST_API_KEY="$KEY" gem push analytics-ruby-*.gem
24 changes: 15 additions & 9 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,38 @@ on:
required: false
default: 'main'

permissions:
id-token: write
contents: read

env:
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}

jobs:
e2e-tests:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }}
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}

steps:
- name: Checkout SDK
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
path: sdk

- name: Checkout sdk-e2e-tests
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
repository: segmentio/sdk-e2e-tests
ref: ${{ inputs.e2e_tests_ref || 'main' }}
token: ${{ secrets.E2E_TESTS_TOKEN }}
path: sdk-e2e-tests

- name: Setup Ruby
uses: ruby/setup-ruby@v1
uses: ./.github/actions/setup-ruby
with:
ruby-version: '3.2'
ruby-version: '3.3'

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'

Expand All @@ -50,7 +56,7 @@ jobs:

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: e2e-test-results
path: sdk-e2e-tests/test-results/
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
*.gem
Gemfile.lock
.ruby-version
coverage/
.claude/
.bundle/
vendor/
*-plan.md
Loading
Loading