Skip to content

Renovate

Renovate #42

Workflow file for this run

name: Renovate
on:
schedule:
- cron: "37 2 * * 1-5" # Every Monday to Friday at 02:37 UTC
workflow_dispatch: # Manual runs from the Actions tab
inputs:
dry_run:
description: "Dry run (log only, create no branches/PRs)"
type: boolean
default: false
log_level:
description: "Renovate log level"
type: choice
options: [info, debug]
default: info
base_branch:
description: "Base branch to scan + target PRs against (blank = default branch). Set a feature branch to test config/hook changes that only live on that branch."
type: string
default: ""
add_unique_branch_prefix:
description: "Add a unique section to every Renovate branch name (renovate/<dep> -> renovate/<unique>/<dep>). Useful for testing, so each run creates new branches instead of skipping existing ones."
type: boolean
default: false
pr_hourly_limit:
description: "Maximum Renovate PRs per hour. Positive values only, no 0, Renovate default = 2. Increase if you need more for testing!"
type: number
default: 10
recreate_when:
description: "Re-raise update closed/merged PRs?. 'always' = propose again every run even after revert (repeat testing). 'auto' (Renovate default) = only when the update changed. 'never' = fully respect closed PRs."
type: choice
options: [auto, always, never]
default: auto
# Empty block required.
# When using GITHUB_TOKEN instead of App, scope permission here!
# Restrictions apply on possible permission here.
permissions: {}
jobs:
renovate:
runs-on: ubuntu-latest
environment: renovate-vidispine-content-wf
steps:
# IMPORTANT: Keep version comments after action digests here, required by Renovate!
#
# Using GitHub App instead of GITHUB_TOKEN, to get all necessary permissions.
# The GitHub App must be installed in GitHub, and
# the secret AppID and private key must be in the environment.
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
# Least privilege: only permissions requested here, must be set for GitHub app.
# HTTP 422 error, if the app doesn't hold the requested permission.
permission-contents: write # push renovate/* branches + commits
permission-pull-requests: write # open/update/close update PRs
permission-issues: write # maintain the Dependency Dashboard issue
# NOTE: "Checks: write" in the GitHub app, and 'permission-checks: write' here, may
# be necessary if automerge is enabled in the future.
permission-statuses: write # write Renovate's own renovate/* commit statuses
permission-workflows: write # REQUIRED to commit changes to .github/workflows/*
# (the github-actions manager bumps action versions).
# This does NOT affect whether CI is triggered.
permission-administration: read # read branch protection / required checks
permission-vulnerability-alerts: read # "Dependabot alerts" - drives vulnerabilityAlerts
permission-metadata: read # implicit on every installation; listed for clarity
- name: Run Renovate
id: renovate
uses: renovatebot/github-action@39b914146caeff8cd512e61c8992f1d5913af85c # v46.2.5
with:
token: ${{ steps.app-token.outputs.token }}
env:
# Only look at this repository.
RENOVATE_REPOSITORIES: ${{ github.repository }}
RENOVATE_PR_HOURLY_LIMIT: ${{ inputs.pr_hourly_limit || 10 }}
# Allowlist for postUpgradeTasks commands (self-hosted requirement). Only commands
# matching one of these regexes may run. Keeps the checksum-refresh hook (see
# renovate.json -> postUpgradeTasks) in sync with each version bump.
RENOVATE_ALLOWED_COMMANDS: '["^bash \.github/renovate/update-checksum\.sh "]'
# Manual-run options (empty/ignored on scheduled runs).
RENOVATE_DRY_RUN: ${{ inputs.dry_run && 'full' || '' }}
LOG_LEVEL: ${{ inputs.log_level || 'info' }}
RENOVATE_REPORT_TYPE: 'file'
RENOVATE_REPORT_PATH: '/tmp/renovate-report.json'
# config breakage -> hard exit.
RENOVATE_CONFIG_VALIDATION_ERROR: 'true'
# Which branch Renovate reads its config from AND targets update PRs against. The
# workflow_dispatch "Use workflow from" selector only picks which YAML runs - it does
# NOT change this. Blank -> repo default branch (normal operation); set a feature
# branch to test config/hook changes that only exist on that branch.
RENOVATE_BASE_BRANCHES: ${{ inputs.base_branch != '' && format('["{0}"]', inputs.base_branch) || format('["{0}"]', github.event.repository.default_branch) }}
# Only set a branch prefix with timestamp 'renovate-yyyyymmddhhmmss' if the input is true. Otherwise, leave it blank to use Renovate's default 'renovate/<dep>'.
RENOVATE_BRANCH_PREFIX: ${{ inputs.add_unique_branch_prefix && format('renovate/{0}/', github.run_id) || 'renovate/' }}
# Defeat Renovate's memory of CLOSED/MERGED PRs. Renovate does NOT read git history -
# it compares the current file contents on the base branch to the datasource - but it
# WILL refuse to re-raise an update whose PR you previously closed. recreateWhen=auto
# (the default) only recreates when the update content changed; 'always' re-proposes
# every run, so a reverted version comes back and repeat testing works; 'never' fully
# respects closed PRs. Scheduled runs get the default 'auto'.
RENOVATE_RECREATE_WHEN: ${{ inputs.recreate_when || 'auto' }}
# Namespace every Renovate branch AND PR by its target (base) branch, always -
# for the default branch too, not just test branches. This keeps each base
# branch's updates fully separate so they can't cross-contaminate:
# * ADDITIONAL_BRANCH_PREFIX folds the base branch into the head-branch name
# (renovate/master-<dep> vs renovate/<test-branch>-<dep>). Renovate detects
# "already handled" updates by head-branch name, so a run targeting one base
# branch never sees - and is never skipped/blocked by - another branch's PRs.
# Testing against a feature branch therefore leaves the master namespace
# untouched; delete the test branch + its renovate/<test-branch>-* branches
# to clean up afterwards.
# * COMMIT_MESSAGE_SUFFIX tags the PR title with the base branch (e.g. "[master]")
# so PRs are also distinguishable by name, on top of the branch separation.
# NOTE: enabling this renames master's head branches from renovate/<dep> to
# renovate/master-<dep>; on the first run any pre-existing renovate/<dep> PRs are
# recreated once under the new names.
RENOVATE_ADDITIONAL_BRANCH_PREFIX: ${{ inputs.base_branch != '' && format('{0}-', inputs.base_branch) || format('{0}-', github.event.repository.default_branch) }}
RENOVATE_COMMIT_MESSAGE_SUFFIX: ${{ inputs.base_branch != '' && format('[{0}]', inputs.base_branch) || format('[{0}]', github.event.repository.default_branch) }}
# Credentials for cr.vidinet.net. A single host rule (no hostType) covers
# both Docker images and Helm charts pulled from this registry.
RENOVATE_HOST_RULES: >-
[{"matchHost":"cr.vidinet.net","username":"${{ secrets.VIDINET_REGISTRY_USERNAME }}","password":"${{ secrets.VIDINET_REGISTRY_PASSWORD }}"}]
# Keep the JSON report as a downloadable run artifact (like an Azure DevOps pipeline
# artifact) so it can be inspected after the run. Uploaded before the check step and
# with if: always() so it survives even when the check below fails or Renovate errored.
# if-no-files-found: ignore -> no failure when Renovate crashed before writing a report.
- name: Upload Renovate report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: renovate-report
path: /tmp/renovate-report.json
if-no-files-found: ignore
retention-days: 30
# Renovate almost always exits 0, even when parts of a run silently fail (a failed
# postUpgradeTask/artifact update, a branch it couldn't push). This step reads the
# structured JSON report (RENOVATE_REPORT_TYPE=file above) and turns real problems
# into a red job. GitHub has no "warning"/partially-succeeded status like Azure DevOps:
# a run is only green or red, so soft issues are emitted as yellow ::warning::
# annotations (run stays green) and only the three hard signals below fail the job.
- name: Check Renovate report
if: always()
env:
REPORT: /tmp/renovate-report.json
# Branch results that mean Renovate could not do its job. 'error' is the real
# failure result; add '*-limit-reached' here only if you want throttling to be red.
FAILURE_RESULTS: '["error"]'
run: |
set -uo pipefail # not -e: collect all issues first, then decide
repo="$GITHUB_REPOSITORY"
issues=0
# 1) Renovate process itself failed. The action step already goes red on a
# non-zero exit; surface it explicitly so this step is one source of truth.
if [ "${{ steps.renovate.outcome }}" = "failure" ]; then
echo "::error::Renovate exited non-zero for ${repo}."
issues=$((issues+1))
fi
if [ ! -f "$REPORT" ]; then
echo "::error::No report at ${REPORT} - Renovate crashed before reporting."
exit 1
fi
# 2) PR-creation / branch failures: branches whose result is in FAILURE_RESULTS.
failed=$(jq -r --arg repo "$repo" --argjson fails "$FAILURE_RESULTS" '
(.repositories[$repo].branches // [])
| map(select(.result as $r | $fails | index($r)))
| .[] | "\(.branchName // .prTitle // "?") -> \(.result)"
' "$REPORT")
if [ -n "$failed" ]; then
echo "::error::Renovate could not create/update branch(es) for ${repo}:"
echo "$failed" | while IFS= read -r line; do echo "::error:: $line"; done
issues=$((issues+1))
fi
# 3) Artifact / postUpgradeTask failures (e.g. update-checksum.sh), read
# structurally from the report instead of log-scraping - no regex false positives.
artifacts=$(jq --arg repo "$repo" '
[ (.repositories[$repo] // {}) | .. | objects
| select(has("artifactErrors")) | .artifactErrors[] ] | length
' "$REPORT")
if [ "$artifacts" -gt 0 ]; then
echo "::error::${artifacts} artifact/postUpgradeTask error(s) for ${repo} (e.g. update-checksum.sh)."
issues=$((issues+1))
fi
# 4) Everything else worth seeing but not failing -> yellow annotations (stays green).
jq -r --arg repo "$repo" '
def lvl: if type=="number" then . else ({"warn":40,"error":50,"fatal":60}[.] // 0) end;
[ (.problems // [])[], ((.repositories[$repo] // {}).problems // [])[] ]
| .[] | select((.level|lvl) == 40) | "::warning::" + (.msg // "warning")
' "$REPORT" | tee -a "$GITHUB_STEP_SUMMARY"
[ "$issues" -eq 0 ] || exit 1