Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .shellspec
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# kcov (coverage) options
--kcov-options "--include-pattern=build-poetry,config-uv,config-poetry,get-build-number,pr_cleanup,promote,build-gradle,config-maven,build-maven,config-npm,build-npm,build-yarn,shared,config-gradle,config-pip,update-release-channel,report-ci-metrics,config-nuget,config-bun"
--kcov-options "--include-pattern=build-poetry,config-uv,config-poetry,get-build-number,pr_cleanup,promote,build-gradle,config-maven,build-maven,config-npm,build-npm,build-yarn,shared,config-gradle,config-pip,update-release-channel,report-ci-metrics,config-nuget,config-bun,renovate"
# --kcov-options "--exclude-pattern=.github,.idea,.git"

# define minimum coverage (fail otherwise)
Expand Down
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ These badges show the status of workflows in dummy repositories that use (or sho
- [`config-uv`](#config-uv)
- [`config-bun`](#config-bun)
- [`config-nuget`](#config-nuget)
- [`renovate`](#renovate)
- [`promote`](#promote)
- [`pr_cleanup`](#pr_cleanup)
- [`code-signing`](#code-signing)
Expand Down Expand Up @@ -1612,6 +1613,96 @@ steps:

---

## `renovate`

Run self-hosted [Renovate](https://docs.renovatebot.com/) on the current repository.

Use it instead of the Mend-hosted Renovate app when lockfiles pin the production JFrog Edge: Mend can't reach the Edge, so the
lockfile updates it makes fail or write SaaS URLs back. Run it on a self-hosted runner, and disable the Mend app for the
repository so the two don't open competing pull requests.

The action installs the `renovate` npm package from the selected Repox and runs it on Node.js installed with mise. It passes
the selected Repox reader to the commands Renovate runs (lockfile updates and `postUpgradeTasks`). Package lookups follow the
repository's Renovate configuration; the shared `SonarSource/renovate-config` presets use SaaS Repox with the `REPOX_TOKEN`
secret, which the action sets.

Renovate runs with `binarySource: global`, so the tools needed for lockfile updates must be on `PATH`. The action runs `mise`,
which installs the tools declared in the repository's mise configuration.

### Requirements

#### Required GitHub Permissions

- `id-token: write`

The other permissions come from the GitHub token used by Renovate.
Comment on lines +1634 to +1638

@gitar-bot gitar-bot Bot Oct 27, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Bug: Usage example omits contents: read, so checkout fails on private repos

The usage example's job-level permissions: block grants only id-token: write. Once any permission is listed, every permission that isn't listed is set to none. That leaves the job's GITHUB_TOKEN without contents: read, so the actions/checkout step fails on private repositories. Private repos are a primary target here, since private-reader is the default role. The action needs that checkout because mise installs tools from the repository's mise config. Other examples in this README that run checkout grant contents: read alongside id-token: write. Add it here as well, and list it under Required GitHub Permissions.

Grant contents: read so actions/checkout can clone the repository:

permissions:
  id-token: write
  contents: read

Was this helpful? React with 👍 / 👎


#### Required Vault Permissions

- `renovate`: GitHub App token for the repository, unless `github-token` is set. It needs `checks`, `contents`, `issues`,
`pull_requests`, `statuses` and `workflows` write access, and `metadata` read access.
- `public-reader` or `private-reader`: Artifactory role for reading dependencies, for both SaaS Repox and the selected Repox

#### Other Dependencies

The tools used by the repository's lockfile updates and `postUpgradeTasks`, declared in its mise configuration.

### Usage

```yaml
on:
workflow_dispatch:
schedule:
- cron: "0 21 * * 1-5"

jobs:
renovate:
runs-on: sonar-xs
permissions:
id-token: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: SonarSource/ci-github-actions/renovate@v2
env:
RENOVATE_ALLOWED_COMMANDS: '["^bun install$"]'
```

Global-only Renovate options, such as `allowedCommands` or `force`, can't be set in the repository's Renovate configuration. Set
them as `RENOVATE_*` environment variables on the step, as above.

### Inputs

| Input | Description | Default |
|---------------------------|-------------------------------------------------------|----------------------------------------------------------------------|
| `renovate-version` | Version of the `renovate` npm package to run | `43.257.5` |
| `node-version` | Node.js version used to run Renovate | `24.11.0` |
| `github-token` | GitHub token used by Renovate | The repository's `renovate` GitHub App token from Vault |
| `artifactory-reader-role` | Suffix for the Artifactory reader role in Vault | `private-reader` for private repos, `public-reader` for public repos |
| `repox-url` | URL for Repox | Runner-dependent (see Edge section) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: Edge section still scopes runner-based Repox selection to config-/build-

The renovate inputs table sends readers to the Edge section for the repox-url default (README.md:1681), and renovate/action.yml:22 uses the same runner-based default. The Edge section still says only "the config-* and build-* actions" pick the endpoint from runner.environment (README.md:74). It also tells self-hosted runners without Edge connectivity to set repox-url: https://repox.jfrog.io on every affected config-* and build-* action (README.md:84-86). An operator who follows that guidance will skip renovate, which still defaults to repox-internal.sonar.build on self-hosted runners, so its install from the Edge fails. Mention renovate in that section.

Was this helpful? React with 👍 / 👎


Pull requests opened with `GITHUB_TOKEN` don't trigger workflows, so CI doesn't run on them.

### Input Environment Variables

| Environment Variable | Description |
|---------------------------------|------------------------------------------------------------------------------------------------|
| `RENOVATE_*`, `LOG_LEVEL` | Passed to Renovate |
| `RENOVATE_REPOSITORIES` | Repositories to process. Defaults to the current repository |
| `RENOVATE_SECRETS` | JSON object merged with the `REPOX_TOKEN` secret set by the action |
| `RENOVATE_CUSTOM_ENV_VARIABLES` | JSON object merged with the variables the action sets for Renovate commands |

### Environment Variables for Renovate Commands

| Environment Variable | Description |
|----------------------------|------------------------------------------------|
| `ARTIFACTORY_URL` | Selected Repox Artifactory URL |
| `ARTIFACTORY_USERNAME` | Selected Repox reader username |
| `ARTIFACTORY_ACCESS_TOKEN` | Selected Repox reader access token |
| `BUN_CONFIG_REGISTRY` | npm registry URL under the selected Repox |
| `BUN_CONFIG_TOKEN` | Selected Repox reader access token |

---

## `promote`

This action promotes a build in JFrog Artifactory and updates the GitHub status check accordingly.
Expand Down
62 changes: 62 additions & 0 deletions renovate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
name: Renovate
description: GitHub Action to run self-hosted Renovate on the current repository, installed from Repox and resolving lockfile updates
through the selected Repox
inputs:
renovate-version:
description: Version of the `renovate` npm package to run
default: 43.257.5
node-version:
description: Node.js version used to run Renovate, installed with mise
default: 24.11.0
github-token:
description: GitHub token used by Renovate. Defaults to the repository's `renovate` GitHub App token from Vault.
default: ''
artifactory-reader-role:
description: Suffix for the Artifactory reader role in Vault. Defaults to `private-reader` for private repositories, and `public-reader`
for public repositories.
default: ''
repox-url:
description: URL for Repox used to install Renovate and by the commands Renovate runs. Defaults to SaaS on GitHub-hosted runners and
the production Edge on self-hosted runners. Explicit URLs override runner selection.
default: ${{ runner.environment == 'github-hosted' && 'https://repox.jfrog.io' || 'https://repox-internal.sonar.build' }}

runs:
using: composite
steps:
- name: Set local action paths
shell: bash
env:
ARTIFACTORY_READER_ROLE: ${{ inputs.artifactory-reader-role != '' && inputs.artifactory-reader-role ||
(github.event.repository.visibility == 'public' && 'public-reader' || 'private-reader') }}
run: |
echo "ACTION_PATH_RENOVATE=${{ github.action_path }}" >> "$GITHUB_ENV"
echo "ARTIFACTORY_READER_ROLE=${ARTIFACTORY_READER_ROLE}" >> "$GITHUB_ENV"

- uses: step-security/mise-action@a225bbd7c202ddf57dd76dff22bb7992f6314173 # v5.1.2
with:
version: 2026.10.17

- uses: SonarSource/vault-action-wrapper@a7fa5fd4a4e4e0f2593f6137c0812cac196cf4ce # 3.7.0
id: secrets
with:
# yamllint disable rule:line-length
url: ${{ contains(inputs.repox-url, '.dev.sonar.build') && 'https://vault.dev.sonar.build' || 'https://vault.sonar.build' }}
secrets: |
${{ inputs.github-token == '' && 'development/github/token/{REPO_OWNER_NAME_DASH}-renovate token | RENOVATE_TOKEN;' || '' }}
development/artifactory/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} access_token | REPOX_TOKEN;
development/${{ contains(inputs.repox-url, 'jfrog.io') && 'artifactory' || (contains(inputs.repox-url, '.dev.sonar.build') && 'artifactory-edge-dev' || 'artifactory-edge-prod') }}/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} username | ARTIFACTORY_USERNAME;
development/${{ contains(inputs.repox-url, 'jfrog.io') && 'artifactory' || (contains(inputs.repox-url, '.dev.sonar.build') && 'artifactory-edge-dev' || 'artifactory-edge-prod') }}/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} access_token | ARTIFACTORY_ACCESS_TOKEN;
# yamllint enable rule:line-length

- name: Run Renovate
shell: bash
env:
RENOVATE_VERSION: ${{ inputs.renovate-version }}
NODE_VERSION: ${{ inputs.node-version }}
ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-url) }}
ARTIFACTORY_USERNAME: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_USERNAME }}
ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN }}
REPOX_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).REPOX_TOKEN }}
RENOVATE_TOKEN: ${{ inputs.github-token != '' && inputs.github-token || fromJSON(steps.secrets.outputs.vault).RENOVATE_TOKEN }}
run: $ACTION_PATH_RENOVATE/renovate.sh
72 changes: 72 additions & 0 deletions renovate/renovate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash
# Run self-hosted Renovate on the current repository.
#
# Renovate is installed with npm from the selected Repox, and the commands it runs (lockfile updates, post-upgrade tasks)
# get the selected Repox reader through customEnvVariables.
#
# Required environment variables (must be explicitly provided):
# - RENOVATE_VERSION: Version of the renovate npm package
# - NODE_VERSION: Node.js version used to run Renovate
# - ARTIFACTORY_URL: URL to the selected Artifactory
# - ARTIFACTORY_USERNAME: Reader username for the selected Artifactory
# - ARTIFACTORY_ACCESS_TOKEN: Reader access token for the selected Artifactory
# - REPOX_TOKEN: SaaS Repox reader access token, exposed as the REPOX_TOKEN Renovate secret
# - RENOVATE_TOKEN: GitHub token used by Renovate
#
# Optional environment variables:
# - RENOVATE_SECRETS, RENOVATE_CUSTOM_ENV_VARIABLES: JSON objects merged with the values set by this script
# - Any other RENOVATE_* variable or LOG_LEVEL is passed to Renovate unchanged
#
# GitHub Actions auto-provided:
# - GITHUB_REPOSITORY: Repository processed when RENOVATE_REPOSITORIES is not set
# - RUNNER_TEMP: Directory where Renovate is installed

set -euo pipefail

: "${RENOVATE_VERSION:?}" "${NODE_VERSION:?}" "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_USERNAME:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}"
: "${REPOX_TOKEN:?}" "${RENOVATE_TOKEN:?}" "${GITHUB_REPOSITORY:?}" "${RUNNER_TEMP:?}"

install_renovate() {
local registry="${ARTIFACTORY_URL}/api/npm/npm/"
RENOVATE_PREFIX="$RUNNER_TEMP/renovate"
NPM_CONFIG_USERCONFIG="$RUNNER_TEMP/renovate.npmrc"
export NPM_CONFIG_USERCONFIG
{
echo "registry=$registry"
echo "//${registry#https://}:_authToken=$ARTIFACTORY_ACCESS_TOKEN"
} > "$NPM_CONFIG_USERCONFIG"
echo "Installing renovate@$RENOVATE_VERSION from $registry"
mise exec "node@$NODE_VERSION" -- npm install --prefix "$RENOVATE_PREFIX" --no-audit --no-fund "renovate@$RENOVATE_VERSION"
return 0
}

export_renovate_config() {
local registry="${ARTIFACTORY_URL}/api/npm/npm/"
export RENOVATE_REPOSITORIES="${RENOVATE_REPOSITORIES:-$GITHUB_REPOSITORY}"
export RENOVATE_ONBOARDING="${RENOVATE_ONBOARDING:-false}"
export RENOVATE_BINARY_SOURCE="${RENOVATE_BINARY_SOURCE:-global}"
RENOVATE_SECRETS=$(jq -cn --arg token "$REPOX_TOKEN" --argjson extra "${RENOVATE_SECRETS:-"{}"}" \
'{REPOX_TOKEN: $token} + $extra')
RENOVATE_CUSTOM_ENV_VARIABLES=$(jq -cn --arg url "$ARTIFACTORY_URL" --arg registry "$registry" \
--arg username "$ARTIFACTORY_USERNAME" --arg token "$ARTIFACTORY_ACCESS_TOKEN" \
--argjson extra "${RENOVATE_CUSTOM_ENV_VARIABLES:-"{}"}" \
'{ARTIFACTORY_URL: $url, ARTIFACTORY_USERNAME: $username, ARTIFACTORY_ACCESS_TOKEN: $token,
BUN_CONFIG_REGISTRY: $registry, BUN_CONFIG_TOKEN: $token} + $extra')
export RENOVATE_SECRETS RENOVATE_CUSTOM_ENV_VARIABLES
echo "Renovate repositories: $RENOVATE_REPOSITORIES"
echo "Dependency registry for Renovate commands: $registry"
return 0
}

main() {
echo "::group::Install Renovate"
install_renovate
echo "::endgroup::"
export_renovate_config
mise exec "node@$NODE_VERSION" -- "$RENOVATE_PREFIX/node_modules/.bin/renovate"
return 0
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main
fi
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ sonar.projectName=ci-github-actions

sonar.sourceEncoding=UTF-8

sonar.sources=build-poetry,get-build-number,pr_cleanup,promote,build-gradle,build-maven,config-npm,build-npm,build-yarn,shared,config-pip,config-poetry,cache,code-signing,config-gradle,config-maven,check-sca,update-release-channel,report-ci-metrics,config-nuget,config-bun
sonar.sources=build-poetry,get-build-number,pr_cleanup,promote,build-gradle,build-maven,config-npm,build-npm,build-yarn,shared,config-pip,config-poetry,cache,code-signing,config-gradle,config-maven,check-sca,update-release-channel,report-ci-metrics,config-nuget,config-bun,renovate
sonar.tests=spec

sonar.coverageReportPaths=coverage/coverage_data/sonar_coverage.xml
90 changes: 90 additions & 0 deletions spec/renovate_spec.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
eval "$(shellspec - -c) exit 1"

Mock mise
echo "mise $*"
End

export RENOVATE_VERSION="43.257.5"
export NODE_VERSION="24.11.0"
export ARTIFACTORY_URL="https://repox-internal.sonar.build/artifactory"
export ARTIFACTORY_USERNAME="test-user"
export ARTIFACTORY_ACCESS_TOKEN="edge-token"
export REPOX_TOKEN="saas-token"
export RENOVATE_TOKEN="github-token"
export GITHUB_REPOSITORY="my-org/my-repo"
RUNNER_TEMP=$(mktemp -d)
export RUNNER_TEMP

EDGE_REGISTRY="https://repox-internal.sonar.build/artifactory/api/npm/npm/"

Describe 'renovate/renovate.sh'
It 'does not run main when sourced'
When run source renovate/renovate.sh
The status should be success
The lines of output should equal 0
The lines of error should equal 0
End
End

Include renovate/renovate.sh

Describe 'install_renovate()'
It 'installs the pinned Renovate version from the selected Repox'
When call install_renovate
The status should be success
The line 1 should equal "Installing renovate@43.257.5 from $EDGE_REGISTRY"
The line 2 should equal \
"mise exec node@24.11.0 -- npm install --prefix $RUNNER_TEMP/renovate --no-audit --no-fund renovate@43.257.5"
The contents of file "$RUNNER_TEMP/renovate.npmrc" should include "registry=$EDGE_REGISTRY"
The contents of file "$RUNNER_TEMP/renovate.npmrc" should include \
"//repox-internal.sonar.build/artifactory/api/npm/npm/:_authToken=edge-token"
End
End

Describe 'export_renovate_config()'
It 'sets the defaults, the REPOX_TOKEN secret and the reader for Renovate commands'
When call export_renovate_config
The status should be success
The line 1 should equal "Renovate repositories: my-org/my-repo"
The line 2 should equal "Dependency registry for Renovate commands: $EDGE_REGISTRY"
The variable RENOVATE_REPOSITORIES should equal "my-org/my-repo"
The variable RENOVATE_ONBOARDING should equal "false"
The variable RENOVATE_BINARY_SOURCE should equal "global"
The variable RENOVATE_SECRETS should equal '{"REPOX_TOKEN":"saas-token"}'
The variable RENOVATE_CUSTOM_ENV_VARIABLES should include "\"BUN_CONFIG_REGISTRY\":\"$EDGE_REGISTRY\""
The variable RENOVATE_CUSTOM_ENV_VARIABLES should include '"BUN_CONFIG_TOKEN":"edge-token"'
The variable RENOVATE_CUSTOM_ENV_VARIABLES should include '"ARTIFACTORY_ACCESS_TOKEN":"edge-token"'
The variable RENOVATE_CUSTOM_ENV_VARIABLES should include '"ARTIFACTORY_USERNAME":"test-user"'
End

It 'keeps values set by the caller'
export RENOVATE_REPOSITORIES="my-org/other-repo"
export RENOVATE_ONBOARDING="true"
export RENOVATE_SECRETS='{"OTHER":"value"}'
export RENOVATE_CUSTOM_ENV_VARIABLES='{"BUN_CONFIG_TOKEN":"override"}'
When call export_renovate_config
The status should be success
The output should include "Renovate repositories: my-org/other-repo"
The variable RENOVATE_ONBOARDING should equal "true"
The variable RENOVATE_SECRETS should equal '{"REPOX_TOKEN":"saas-token","OTHER":"value"}'
The variable RENOVATE_CUSTOM_ENV_VARIABLES should include '"BUN_CONFIG_TOKEN":"override"'
End
End

Describe 'main()'
It 'installs and runs Renovate'
When run script renovate/renovate.sh
The status should be success
The line 1 should equal "::group::Install Renovate"
The output should include "::endgroup::"
The output should include "mise exec node@24.11.0 -- $RUNNER_TEMP/renovate/node_modules/.bin/renovate"
End

It 'fails without a GitHub token'
export RENOVATE_TOKEN=""
When run script renovate/renovate.sh
The status should be failure
The error should include "RENOVATE_TOKEN"
End
End
Loading