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
58 changes: 58 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,64 @@ concurrency:
cancel-in-progress: true

jobs:
reusable_contract_changes:
name: reusable workflow smoke scope
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
outputs:
changed: ${{ steps.filter.outputs.changed }}
steps:
- uses: actions/checkout@v7
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4
id: filter
with:
filters: |
changed:
- '.github/workflows/rg-ci.yml'
- '.github/workflows/rg-security.yml'
- '.github/workflows/rg-release.yml'
- '.github/workflows/reusable-workflow-smoke.yml'
- 'test/reusable-workflow-contract.test.ts'
- 'test/fixtures/reusable-workflow.Dockerfile'

reusable_ci_smoke:
name: reusable rg-ci caller smoke
needs: reusable_contract_changes
if: needs.reusable_contract_changes.outputs.changed == 'true'
uses: ./.github/workflows/rg-ci.yml
with:
runner-class: hosted

reusable_security_smoke:
name: reusable rg-security caller smoke
needs: reusable_contract_changes
if: needs.reusable_contract_changes.outputs.changed == 'true'
permissions:
contents: read
actions: read
pull-requests: read
security-events: write
uses: ./.github/workflows/rg-security.yml
with:
enable-dependency-review: false
enforce-osv: false

reusable_release_smoke:
name: reusable rg-release caller smoke
needs: reusable_contract_changes
if: needs.reusable_contract_changes.outputs.changed == 'true'
permissions:
contents: read
packages: write
id-token: write
attestations: write
uses: ./.github/workflows/rg-release.yml
with:
image-ref: local/reusable-workflow-smoke:${{ github.sha }}
dockerfile: test/fixtures/reusable-workflow.Dockerfile
platforms: linux/amd64
publish: false

test_self_hosted_trusted:
name: test
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/reusable-workflow-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Reusable Workflow Caller Smoke

on:
workflow_call:
workflow_dispatch:

permissions:
contents: read

jobs:
ci:
uses: ./.github/workflows/rg-ci.yml
with:
runner-class: hosted

security:
permissions:
contents: read
actions: read
pull-requests: read
security-events: write
uses: ./.github/workflows/rg-security.yml
with:
enable-dependency-review: false
enforce-osv: false

release:
permissions:
contents: read
uses: ./.github/workflows/rg-release.yml
with:
image-ref: local/reusable-workflow-smoke:${{ github.sha }}
dockerfile: test/fixtures/reusable-workflow.Dockerfile
platforms: linux/amd64
publish: false
83 changes: 69 additions & 14 deletions .github/workflows/rg-ci.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
# Reusable rg-ci workflow.
# Required secrets: none. Optional secrets are inherited by callers for their own scripts.
# Required secrets: none.
# Inputs: runner-class selects hosted (default, safe for forks) or the public shell-only fleet.
name: rg-ci

on:
workflow_call:
inputs:
runner-class:
description: "Execution boundary: hosted or shell-safe-public."
required: false
type: string
default: hosted
node-version:
description: Node.js version used by hosted lanes.
description: Exact Node.js version.
required: false
type: string
default: "24"
default: "24.14.1"
package-manager-version:
description: pnpm version used by hosted lanes.
description: pnpm version.
required: false
type: string
default: "10.32.1"
Expand All @@ -28,24 +34,73 @@ env:
SHELL: /bin/bash

jobs:
ci:
name: rg-ci
runs-on:
- self-hosted
- linux
- shell-only
- public
validate-inputs:
name: rg-ci input contract
runs-on: ubuntu-24.04
steps:
- name: Validate runner class
env:
RUNNER_CLASS: ${{ inputs.runner-class }}
run: |
case "${RUNNER_CLASS}" in
hosted|shell-safe-public) ;;
*) echo "runner-class must be hosted or shell-safe-public" >&2; exit 2 ;;
esac

hosted:
name: rg-ci hosted
needs: validate-inputs
if: inputs.runner-class == 'hosted'
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ inputs.node-version }}
- uses: pnpm/action-setup@v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6
with:
version: ${{ inputs.package-manager-version }}
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm test
- if: inputs.run-build
run: pnpm build

shell-safe-public:
name: rg-ci shell-safe public
needs: validate-inputs
if: inputs.runner-class == 'shell-safe-public'
runs-on: [self-hosted, linux, shell-only, public]
timeout-minutes: 20
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install shell-safe Node
env:
NODE_VERSION: ${{ inputs.node-version }}
run: |
set -euo pipefail
case "$(uname -m)" in
x86_64) node_arch=x64 ;;
aarch64|arm64) node_arch=arm64 ;;
*) echo "unsupported architecture: $(uname -m)" >&2; exit 2 ;;
esac
runner_temp="${RUNNER_TEMP:-/tmp/github-runner-temp}"
archive="node-v${NODE_VERSION}-linux-${node_arch}.tar.xz"
install_dir="${runner_temp}/node-${NODE_VERSION}-${node_arch}"
mkdir -p "${runner_temp}" "${install_dir}"
curl --fail --silent --show-error --location --connect-timeout 10 --max-time 120 \
"https://nodejs.org/dist/v${NODE_VERSION}/${archive}" --output "${runner_temp}/${archive}"
tar -xJf "${runner_temp}/${archive}" --strip-components=1 --no-same-owner -C "${install_dir}"
echo "${install_dir}/bin" >> "${GITHUB_PATH}"
- name: Enable pnpm
env:
PNPM_VERSION: ${{ inputs.package-manager-version }}
run: |
corepack enable
corepack prepare "pnpm@${PNPM_VERSION}" --activate
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm test
- if: inputs.run-build
run: pnpm build
64 changes: 31 additions & 33 deletions .github/workflows/rg-release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Reusable rg-release workflow.
# Required secrets: registry credentials via secrets.GITHUB_TOKEN or inherited registry token.
# Required secrets: GITHUB_TOKEN, supplied automatically by GitHub. No long-lived registry key.
# The caller must grant packages: write, id-token: write, and attestations: write when publish=true.
name: rg-release

on:
Expand All @@ -19,6 +20,16 @@ on:
required: false
type: string
default: .
platforms:
description: Comma-separated Docker platforms.
required: false
type: string
default: linux/amd64,linux/arm64
publish:
description: Push, sign, and attest the image. Set false only for caller contract smoke tests.
required: false
type: boolean
default: true

permissions:
contents: read
Expand All @@ -29,49 +40,36 @@ permissions:
jobs:
release:
name: rg-release
runs-on:
- self-hosted
- linux
- shell-only
- public
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
if: inputs.publish
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v7
- uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
id: build
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
platforms: ${{ inputs.platforms }}
push: ${{ inputs.publish }}
tags: ${{ inputs.image-ref }}
- name: Ensure envsubst is available
run: |
set -euo pipefail
if command -v envsubst >/dev/null 2>&1; then
exit 0
fi

if [[ "$(id -u)" == "0" ]] && command -v apt-get >/dev/null 2>&1; then
apt-get update
apt-get install -y --no-install-recommends gettext-base
elif command -v sudo >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y --no-install-recommends gettext-base
else
echo "envsubst is required by cosign-installer." >&2
exit 1
fi
- uses: sigstore/cosign-installer@v4.1.2
- run: cosign sign --yes ${{ inputs.image-ref }}@${{ steps.build.outputs.digest }}
- uses: actions/attest-build-provenance@v4
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
if: inputs.publish
- name: Sign immutable image digest
if: inputs.publish
env:
IMAGE_REF: ${{ inputs.image-ref }}
IMAGE_DIGEST: ${{ steps.build.outputs.digest }}
run: cosign sign --yes "${IMAGE_REF}@${IMAGE_DIGEST}"
- uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4
if: inputs.publish
with:
subject-name: ${{ inputs.image-ref }}
subject-digest: ${{ steps.build.outputs.digest }}
Expand Down
42 changes: 30 additions & 12 deletions .github/workflows/rg-security.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Reusable rg-security workflow.
# Required secrets: none. Runs only on GitHub-hosted runners.
# Required secrets: none. The caller must grant security-events: write for SARIF upload.
name: rg-security

on:
Expand All @@ -10,6 +10,21 @@ on:
required: false
type: string
default: pnpm-lock.yaml
codeql-languages:
description: Comma-separated CodeQL languages.
required: false
type: string
default: javascript-typescript
enable-dependency-review:
description: Run dependency review on pull requests; requires the caller repository dependency graph.
required: false
type: boolean
default: true
enforce-osv:
description: Fail when OSV reports vulnerable dependencies; SARIF is uploaded in either mode.
required: false
type: boolean
default: true

permissions:
contents: read
Expand All @@ -20,27 +35,30 @@ permissions:
jobs:
security:
name: rg-security
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: github/codeql-action/init@v4
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
with:
languages: javascript-typescript,python
- uses: github/codeql-action/analyze@v4
- uses: actions/dependency-review-action@v5
if: github.event_name == 'pull_request'
continue-on-error: true
languages: ${{ inputs.codeql-languages }}
- uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
if: inputs.enable-dependency-review && github.event_name == 'pull_request'
with:
fail-on-severity: high
- uses: google/osv-scanner-action/osv-scanner-action@v2.3.8
- uses: google/osv-scanner-action/osv-scanner-action@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8
id: osv
continue-on-error: true
with:
scan-args: |-
--lockfile=${{ inputs.lockfile }}
--format=sarif
--output=osv-results.sarif
- uses: github/codeql-action/upload-sarif@v4
if: always()
- uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
if: always() && hashFiles('osv-results.sarif') != ''
with:
sarif_file: osv-results.sarif
- name: Enforce OSV result
if: inputs.enforce-osv && steps.osv.outcome == 'failure'
run: exit 1
Loading
Loading