This guide documents the patterns and best practices for migrating SonarSource projects from Cirrus CI to GitHub Actions.
For human readable docs refer to xtranet.
When updating this migration guide:
- Line Length: Follow MD013 rule - maximum 140 characters per line
- Code Blocks: Use proper syntax highlighting (
yaml,bash, etc.) - Formatting: Use consistent header levels and bullet points
- Examples: Always test examples before adding them to the guide
- Links: Use proper markdown linking format
STOP: This documentation contains EXACT specifications that MUST be followed precisely. Do NOT:
- Guess action versions
- Mix this documentation with other sources
- Skip any required parameters
- Substitute similar-looking configurations from other workflows
# Checkout
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# Mise Setup - INCLUDES REQUIRED VERSION PARAMETER
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
version: 2025.7.12
# Upload Artifacts
- name: Upload coverage reports
if: always() && ! canceled()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage-reports
path: path/to/coverage.xml
# Download Artifacts
- name: Download coverage reports
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: coverage-reports
# SonarQube Scan
- name: SonarQube scan
uses: sonarsource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0
env:
SONAR_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SONAR_TOKEN }}
SONAR_HOST_URL: ${{ fromJSON(steps.secrets.outputs.vault).SONAR_HOST_URL }}sonarsource/sonarqube-scan-action requires to be running on github-ubuntu-latest-s runner.
After creating your workflow, verify EVERY item:
Update this section when newer versions are released:
-
actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 -
actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 -
actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
-
jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 -
sonarsource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0
-
SonarSource/vault-action-wrapper@v3 -
SonarSource/gh-action_pre-commit@v1 -
SonarSource/gh-action_release/.github/workflows/main.yaml@v6
-
version: 2025.7.12parameter included
- Concurrency: Defined at workflow level (not job level)
- Standard triggers: push, pull_request, workflow_dispatch
- Runner selection: Correct runner based on GitHub Actions Runner Selection section
- ✅ Identify the project type (Maven, Gradle, Poetry, etc.)
- ✅ CRITICAL: Verify repository visibility (public vs private)
- Auto-detection: Repository visibility is automatically inferred from
ARTIFACTORY_DEPLOY_REPOvalue:sonarsource-private-qa→ Private repository configurationsonarsource-public-qa→ Public repository configuration
- Auto-detection: Repository visibility is automatically inferred from
- ✅ Check for cirrus-modules usage: Look for
.cirrus.starfile - if present, see Cirrus-Modules Migration section - ✅ Check existing
.github/workflows/for conflicts - ✅ Understand the current Cirrus CI configuration patterns
- ✅ SECURITY: Review third-party actions and pin to commit SHAs
- Runner type selected (see GitHub Actions Runner Selection)
- All required action versions copied from Action Versions Table
- Tool versions identified from existing configuration
- ✅ Repository visibility detection: Check
.cirrus.ymlforARTIFACTORY_DEPLOY_REPOvalue
.cirrus.yml unchanged. Both Cirrus CI and GitHub Actions should coexist during the transition
period.
- Missing mise version parameter:
version: 2025.7.12is MANDATORY - Wrong runner type: You MUST select runners described in GitHub Actions Runner Selection section
- Mixing documentation sources: Use ONLY this guide, not other workflows
- Incomplete action configurations: Copy the COMPLETE blocks from this guide
- Missing dependencies: Convert Cirrus CI
depends_onto GitHub Actionsneeds - Forgetting documentation updates: Always update README.md badges and path references
- Leaving Cirrus CI path references: Update
/tmp/cirrus-ci-build/paths to GitHub Actions equivalents - Keeping manual workflow implementations: Replace manual pr-cleanup.yml with official SonarSource actions
If ANY step is unclear, STOP and ask for clarification. Do NOT proceed with assumptions.
Remember: Following this documentation EXACTLY is critical for security, consistency, and functionality. Every parameter and version listed here is required.
This repository uses the following linting rules:
- markdownlint (MD013): Line length must not exceed 140 characters
- YAML formatting: Consistent indentation and structure in code examples
- Spell checking: Ensure technical terms are correctly spelled
- MD013: Line too long → Break line at natural points (see example above)
- MD032: Lists should be surrounded by blank lines
- MD038: No spaces inside code span elements
- MD040: Code blocks should specify language
- MD041: First line should be a top-level header
When adding new patterns or updating existing ones:
- Document the "why": Explain reasoning behind recommendations
- Provide before/after examples: Show Cirrus CI → GitHub Actions transformations
- Update multiple sections: Ensure consistency across:
- Main examples
- "Common Pitfalls" section
- "Do These Instead" best practices
- Migration checklist items
- Version references: Always use stable versions (e.g.,
@v1) in final examples - Test thoroughly: Validate in both public and private repository contexts
ci-github-actions/
├── .cursor/
│ └── cirrus-github-migration.md # This guide
├── build-gradle/
├── build-poetry/
├── build-maven/ # Custom action implementations
├── cache/
├── get-build-number/
├── promote/
└── pr_cleanup/
The migration typically involves converting two main Cirrus CI tasks:
- build_task: Build, test, analyze, and deploy artifacts
- promote_task: Promote artifacts in Artifactory
- Script Injection Prevention: Never use untrusted input directly in shell commands
- Secure Third-Party Actions: Pin all third-party actions to full commit SHAs
- Least Privilege: Use minimal required permissions for
GITHUB_TOKEN - Secure Artifact Handling: Never include secrets in artifacts or cache
- Environment Protection: Validate all environment variables from external sources
✅ Always pin third-party actions to commit SHA:
# ✅ CORRECT - Pinned to commit SHA
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# ❌ WRONG - Unpinned versions
- uses: actions/checkout@v4 # Can be modified
- uses: actions/checkout@main # Can be modifiedException: SonarSource custom actions use semantic versioning (@v1) as they are internally managed and trusted.
✅ Use environment variables for untrusted input:
# ✅ CORRECT
- name: Echo PR Title
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run:
echo "PR Title: $PR_TITLE"
# ❌ WRONG - Script injection vulnerability
- name: Echo PR Title
run:
echo "PR Title: ${{ github.event.pull_request.title }}"Cirrus CI vs GitHub Actions vault path syntax:
# Cirrus CI format
SONAR_TOKEN: VAULT[development/kv/data/sonarcloud data.token]
# GitHub Actions format - remove 'data.' prefix, used by vault-action-wrapper
secrets: |
development/kv/data/sonarcloud token | SONAR_TOKEN;Key difference: In GitHub Actions, the vault-action-wrapper uses the field name directly (e.g., token, url) instead of
the Cirrus CI format (data.token, data.url). The vault-action-wrapper is automatically used by all SonarSource custom actions.
Always use mise.toml for consistent tool versions across environments:
[tools]
java = "21.0"
maven = "3.9"
# Add other tools as needed (hadolint, etc.) - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
version: 2025.7.12For promote jobs, disable cache saving:
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
cache_save: false
version: 2025.7.12name: Build
on:
push:
branches:
- master
- branch-*
- dogfood-*
pull_request:
merge_group:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: sonar-xs # For private repos; use github-ubuntu-latest-s for public repos
name: Build
permissions:
id-token: write # Required for Vault OIDC authentication
contents: write # Required for repository access and tagging
steps:
# NOTE: Check for latest releases and update commit SHAs for actions
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
version: 2025.7.12
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
deploy-pull-request: true
promote:
needs:
- build
runs-on: sonar-xs # Private repos default; use github-ubuntu-latest-s for public repos
name: Promote
permissions:
id-token: write
contents: write
steps:
- uses: SonarSource/ci-github-actions/promote@v1
with:
promote-pull-request: true📚 Always check the latest documentation: The SonarSource/ci-github-actions repository is public and contains the most up-to-date documentation, examples, and usage instructions for all custom actions.
All SonarSource custom actions use the vault-action-wrapper to securely fetch secrets from HashiCorp Vault using OIDC
authentication. You don't need to use this action directly - it's automatically integrated into all build and promote actions.
Fetches secrets from HashiCorp Vault using GitHub OIDC authentication.
- name: Vault
id: secrets
uses: SonarSource/vault-action-wrapper@v3 # v3.1.0
with:
secrets: |
development/artifactory/token/{REPO_OWNER_NAME_DASH}-private-reader access_token | ARTIFACTORY_ACCESS_TOKEN;
development/kv/data/next url | SONAR_HOST_URL;
development/kv/data/next token | SONAR_TOKEN;Key Features:
- OIDC Authentication: No stored secrets required - uses GitHub OIDC tokens
- Dynamic Path Resolution:
{REPO_OWNER_NAME_DASH}automatically replaced with repository path - Multi-Secret Fetch: Fetch multiple secrets in a single action call
- JSON Output: Secrets available via
fromJSON(steps.secrets.outputs.vault).SECRET_NAME - Required permissions:
id-token: write
BEFORE (Cirrus CI):
build_task:
name: Build
env:
# Direct vault references in environment
ARTIFACTORY_DEPLOY_USERNAME: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer username]
ARTIFACTORY_DEPLOY_PASSWORD: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer access_token]
ARTIFACTORY_USERNAME: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-private-reader username]
ARTIFACTORY_PASSWORD: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-private-reader access_token]
build_script:
- source cirrus-env BUILD
- regular_gradle_build_deploy_analyzeAFTER (GitHub Actions):
jobs:
build:
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# Step 1: Retrieve secrets from Vault
- name: Vault
id: secrets
uses: SonarSource/vault-action-wrapper@v3 # 3.1.0
with:
secrets: |
development/artifactory/token/{REPO_OWNER_NAME_DASH}-qa-deployer username | ARTIFACTORY_DEPLOY_USERNAME;
development/artifactory/token/{REPO_OWNER_NAME_DASH}-qa-deployer access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;
development/artifactory/token/{REPO_OWNER_NAME_DASH}-private-reader username | ARTIFACTORY_USERNAME;
development/artifactory/token/{REPO_OWNER_NAME_DASH}-private-reader access_token | ARTIFACTORY_ACCESS_TOKEN;
# Step 2: Use secrets in build action
- name: Build, Analyze and deploy
id: build
uses: SonarSource/ci-github-actions/build-gradle@v1
with:
artifactory-deploy-repo: "sonarsource-private-qa"
deploy-pull-request: true
env:
# Reference vault outputs using fromJSON
ARTIFACTORY_DEPLOY_USERNAME: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_DEPLOY_USERNAME }}
ARTIFACTORY_DEPLOY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_DEPLOY_ACCESS_TOKEN }}
ARTIFACTORY_USERNAME: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_USERNAME }}
ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN }}Variable Transformation Rules:
| Cirrus CI | GitHub Actions | Notes |
|---|---|---|
${CIRRUS_REPO_OWNER} |
{REPO_OWNER_NAME_DASH} |
Automatically replaced by vault-action-wrapper |
${CIRRUS_REPO_NAME} |
(removed) | Now included in {REPO_OWNER_NAME_DASH} |
VAULT[path field] |
path field | OUTPUT_NAME; |
New syntax with pipe separator |
| Direct env reference | fromJSON(steps.secrets.outputs.vault).OUTPUT_NAME |
Must use fromJSON to parse vault output |
Key Differences:
- Two-step process: Vault retrieval → Build execution
- JSON output: Secrets are returned as JSON and must be parsed with
fromJSON() - Step references: Use
steps.{step-id}.outputs.vaultto reference vault step - Repository naming:
{REPO_OWNER_NAME_DASH}combines owner and repo with dashes
Common Vault Paths Used by SonarSource Actions:
| Secret Type | Vault Path | Usage |
|---|---|---|
| Artifactory Reader | development/artifactory/token/{REPO_OWNER_NAME_DASH}-private-reader |
Reading dependencies |
| Artifactory Deployer | development/artifactory/token/{REPO_OWNER_NAME_DASH}-qa-deployer |
Deploying artifacts |
| Artifactory Promoter | development/artifactory/token/{REPO_OWNER_NAME_DASH}-promoter |
Promoting builds |
| SonarQube Next | development/kv/data/next |
Primary SonarQube platform |
| SonarCloud EU | development/kv/data/sonarcloud |
SonarCloud European platform |
| SonarQube US | development/kv/data/sonarqube-us |
SonarQube US platform |
| Code Signing | development/kv/data/sign |
JAR/artifact signing |
| GitHub Token | development/github/token/{REPO_OWNER_NAME_DASH}-promotion |
GitHub API operations |
❌ Manual Usage Not Recommended:
Since all SonarSource custom actions automatically handle vault secret fetching, you should avoid using vault-action-wrapper manually unless you have specific requirements not covered by the build actions.
# ❌ AVOID - Manual vault usage when build actions are sufficient
- name: Vault
id: secrets
uses: SonarSource/vault-action-wrapper@v3
with:
secrets: |
development/artifactory/token/{REPO_OWNER_NAME_DASH}-private-reader username | ARTIFACTORY_USERNAME;
development/artifactory/token/{REPO_OWNER_NAME_DASH}-private-reader access_token | ARTIFACTORY_ACCESS_TOKEN;
- name: Build
env:
ARTIFACTORY_USERNAME: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_USERNAME }}
ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN }}
uses: SonarSource/ci-github-actions/build-maven@v1
# ✅ PREFERRED - Use integrated build action
- uses: SonarSource/ci-github-actions/build-maven@v1
# Vault secrets handled automaticallyPromotes builds in JFrog Artifactory and updates GitHub status checks.
- uses: SonarSource/ci-github-actions/promote@v1Features:
- Creates GitHub status check named
repox-${GITHUB_REF_NAME} - Required permissions:
id-token: write,contents: write - Required vault permissions:
promoterArtifactory role,promotionGitHub token
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
deploy-pull-request: true
# All parameters below are optional with auto-detected defaults
artifactory-reader-role: private-reader # private-reader/public-reader
artifactory-deployer-role: qa-deployer # qa-deployer/public-deployer
maven-local-repository-path: .m2/repository # Maven cache path
scanner-java-opts: -Xmx512m # JVM options for SonarQube scanner
use-develocity: false # Enable Develocity build trackingFeatures:
- Automatic build context detection (master, maintenance, PR, dogfood, feature)
- SonarQube analysis with context-appropriate profiles
- Artifact signing and conditional deployment
- Required permissions:
id-token: write,contents: write
Some public repositories use private Artifactory credentials instead of the default public ones. This is common when the repository content is public but the project needs access to private Artifactory repositories.
# Override artifactory roles to match existing Cirrus CI configuration
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
deploy-pull-request: true
artifactory-reader-role: private-reader # Override default public-reader
artifactory-deployer-role: qa-deployer # Override default public-deployer🔍 Detection Steps - Follow These Exactly:
-
Check repository visibility: Verify that the repository is public
-
Examine
.cirrus.ymlfor private reader pattern: Look for:VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-private-reader access_token] -
Examine
.cirrus.ymlfor qa-deployer pattern: Look for:VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer access_token]
🎯 Decision Matrix:
| Repository Type | Reader Pattern Found | Deployer Pattern Found | Action Required |
|---|---|---|---|
| Public | ✅ private-reader |
✅ qa-deployer |
Override both roles |
| Public | ❌ No pattern | ❌ No pattern | Use defaults |
| Private | Any pattern | Any pattern | Use defaults |
🛠️ Implementation Examples:
# Example 1: Public repo with private artifactory access (OVERRIDE NEEDED)
# Found in .cirrus.yml: ARTIFACTORY_PRIVATE_PASSWORD: VAULT[...private-reader...]
# Found in .cirrus.yml: ARTIFACTORY_DEPLOY_PASSWORD: VAULT[...qa-deployer...]
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
artifactory-reader-role: private-reader # Override default public-reader
artifactory-deployer-role: qa-deployer # Override default public-deployer
# Example 2: Public repo with public artifactory access (NO OVERRIDE NEEDED)
# No private-reader or qa-deployer patterns found in .cirrus.yml
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
# artifactory roles auto-detected (public-reader, public-deployer)
# Example 3: Private repo (NO OVERRIDE NEEDED)
# Regardless of patterns in .cirrus.yml
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
# artifactory roles auto-detected (private-reader, qa-deployer)Available role options:
- Reader roles:
private-reader,public-reader - Deployer roles:
qa-deployer,public-deployer
- uses: SonarSource/ci-github-actions/build-gradle@v1
with:
deploy-pull-request: true
# All parameters below are optional with auto-detected defaults
public: false # Auto-detected from repo visibility
artifactory-deploy-repo: "" # Auto-detected: sonarsource-public-qa/sonarsource-private-qa
artifactory-reader-role: private-reader # private-reader/public-reader
artifactory-deployer-role: qa-deployer # qa-deployer/public-deployer
skip-tests: false # Skip running tests
gradle-args: "" # Additional Gradle arguments
gradle-version: "" # Gradle version (uses wrapper if not specified)
gradle-wrapper-validation: true # Validate Gradle wrapper
develocity-url: https://develocity.sonar.build/ # Develocity URL
repox-url: https://repox.jfrog.io # Repox URL
sonar-platform: next # SonarQube platform: next/sqc-eu/sqc-usWhen migrating from Cirrus CI, parameters passed to regular_gradle_build_deploy_analyze should be moved to the gradle-args parameter:
Cirrus CI Pattern:
# .cirrus.yml
build_script:
- source cirrus-env BUILD-PRIVATE
- regular_gradle_build_deploy_analyze -PexecuteSpotless -Dscan.tag.CIGitHub Actions Migration:
- uses: SonarSource/ci-github-actions/build-gradle@v1
with:
gradle-args: "-PexecuteSpotless -Dscan.tag.CI" # All parameters from regular_gradle_build_deploy_analyzeMigration Steps:
- Find
regular_gradle_build_deploy_analyzecall in.cirrus.yml - Copy all parameters after the function name
- Add them to
gradle-argsparameter in GitHub Actions
Features:
- Automated version management with build numbers
- SonarQube analysis with configurable platform
- Conditional deployment and automatic artifact signing
- Develocity integration for build optimization
- Required permissions:
id-token: write,contents: write - Outputs:
project-versionfrom gradle.properties
- uses: SonarSource/ci-github-actions/build-poetry@v1
with:
deploy-pull-request: true
# All parameters below are optional with auto-detected defaults
public: false # Auto-detected from repo visibility
artifactory-reader-role: private-reader # private-reader/public-reader
artifactory-deployer-role: qa-deployer # qa-deployer/public-deployer
poetry-virtualenvs-path: .cache/pypoetry/virtualenvs # Poetry virtual environments path
poetry-cache-dir: .cache/pypoetry # Poetry cache directory
repox-url: https://repox.jfrog.io # Repox URL
sonar-platform: next # SonarQube platform (next, sqc-eu, sqc-us)Features:
- Python project build and publish using Poetry
- SonarQube analysis integration
- Conditional deployment based on branch patterns
- Required permissions:
id-token: write,contents: write
- uses: SonarSource/ci-github-actions/build-npm@v1
with:
deploy-pull-request: false # Deploy pull request artifacts
# All parameters below are optional
artifactory-deploy-repo: "" # Artifactory repository name
skip-tests: false # Skip running tests
cache-npm: true # Cache NPM dependencies
repox-url: https://repox.jfrog.io # Repox URL
sonar-platform: next # SonarQube platform (next, sqc-eu, sqc-us)Features:
- Automated version management with build numbers and SNAPSHOT handling
- SonarQube analysis for code quality (credentials from Vault)
- Conditional deployment based on branch patterns (main, maintenance, dogfood branches)
- NPM dependency caching for faster builds (configurable)
- JFrog build info publishing with UI links
- Required permissions:
id-token: write,contents: write - Outputs:
project-versionfrom package.json
- uses: SonarSource/ci-github-actions/build-yarn@v1
with:
deploy-pull-request: false # Deploy pull request artifacts
# All parameters below are optional
artifactory-deploy-repo: "" # Artifactory repository name
skip-tests: false # Skip running tests
cache-yarn: true # Cache Yarn dependencies
repox-url: https://repox.jfrog.io # Repox URL
sonar-platform: next # SonarQube platform (next, sqc-eu, sqc-us)Features:
- Automated version management with build numbers and SNAPSHOT handling
- SonarQube analysis for code quality (credentials from Vault)
- Conditional deployment based on branch patterns (main, maintenance, dogfood branches)
- NPM dependency caching for faster builds (configurable)
- JFrog build info publishing with UI links
- Required permissions:
id-token: write,contents: write - Outputs:
project-versionfrom package.json
Certain repositories want to publish PR artifacts to repox and promote them to the builds repository from the initial qa repository.
This is useful if the project is being tested in another project and you want to reference it from another repository.
When to use this: If your Cirrus CI pipeline has the environment variable DEPLOY_PULL_REQUEST set to true, you need to configure
these parameters for both the build and promote actions.
GitHub Actions Configuration:
# .github/workflows/build.yml
jobs:
build:
# ... other configuration
steps:
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
deploy-pull-request: true # Deploy PR artifacts to qa repository
promote:
# ... other configuration
steps:
- uses: SonarSource/ci-github-actions/promote@v1
with:
promote-pull-request: true # Promote PR artifacts to builds repositoryCirrus CI Equivalent:
# .cirrus.yml
env:
DEPLOY_PULL_REQUEST: "true" # This triggers both deployment and promotion for PRsKey Points:
- Build Action:
deploy-pull-request: truedeploys PR artifacts to theqarepository - Promote Action:
promote-pull-request: truepromotes PR artifacts fromqatobuildsrepository - Cross-Repository Testing: Enables other projects to reference PR artifacts for testing
- Automatic Promotion: PR artifacts are automatically promoted, not just deployed
Migration Steps:
- Check Cirrus CI: Look for
DEPLOY_PULL_REQUEST: "true"in your.cirrus.yml - Configure Build Action: Add
deploy-pull-request: trueto your build action - Configure Promote Action: Add
promote-pull-request: trueto your promote action
The SonarQube platform used for analysis is based on the SONAR_HOST_URL in your Cirrus CI configuration.
🔍 Detection Steps - Follow These Exactly:
-
Examine
.cirrus.ymlfor SONAR_HOST_URL pattern: Look for:SONAR_HOST_URL: VAULT[development/kv/data/... data.url] -
Check the vault path to determine platform:
development/kv/data/next→ No action needed (default platform)development/kv/data/sonarcloud→ Setsonar-platform: sqc-eudevelopment/kv/data/sonarqube-us→ Setsonar-platform: sqc-us
🎯 Decision Matrix:
| Vault Path in .cirrus.yml | SONAR_HOST_URL Contains | Action Required |
|---|---|---|
development/kv/data/next |
next |
No override needed (default) |
development/kv/data/sonarcloud |
sonarcloud |
Set sonar-platform: sqc-eu |
development/kv/data/sonarqube-us |
sonarqube-us |
Set sonar-platform: sqc-us |
🛠️ Implementation Examples:
# Example 1: Using sonarcloud platform (EU)
# Found in .cirrus.yml: SONAR_HOST_URL: VAULT[development/kv/data/sonarcloud data.url]
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
sonar-platform: sqc-eu # Override default next platform
# Example 2: Using sonarqube-us platform (US)
# Found in .cirrus.yml: SONAR_HOST_URL: VAULT[development/kv/data/sonarqube-us data.url]
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
sonar-platform: sqc-us # Override default next platform
# Example 3: Using next platform (default)
# Found in .cirrus.yml: SONAR_HOST_URL: VAULT[development/kv/data/next data.url]
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
# sonar-platform auto-detected (next) - no override neededAvailable platform options:
next: Default SonarQube platform (auto-detected)sqc-eu: SonarCloud EU platformsqc-us: SonarCloud US platform
Migration Steps:
- Check Cirrus CI: Look for
SONAR_HOST_URLin your.cirrus.yml - Identify vault path: Determine which vault path is used for the SonarQube URL
- Configure platform: Add
sonar-platformparameter if needed (see decision matrix above) - Test analysis: Verify that SonarQube analysis works with the correct platform
Adaptive cache action that automatically chooses the appropriate caching backend based on repository visibility.
- uses: SonarSource/gh-action_cache@v1
with:
path: |
~/.m2/repository
~/.cache/pip
key: cache-${{ runner.os }}-${{ hashFiles('**/pom.xml', '**/requirements.txt') }}
restore-keys: cache-${{ runner.os }}-
# Optional parameters
upload-chunk-size: "" # Chunk size for large files (bytes)
enableCrossOsArchive: false # Allow cross-platform cache restore
fail-on-cache-miss: false # Fail if cache entry not found
lookup-only: false # Check cache existence without downloadingFeatures:
- Smart backend selection: GitHub Actions cache for public repos, SonarSource S3 cache for private repos
- Seamless API compatibility: Drop-in replacement for standard GitHub Actions cache
- Automatic detection: Repository visibility and ownership automatically detected
- Output:
cache-hitboolean indicating exact match found
Automatically cleans up GitHub Actions resources when pull requests are closed.
- uses: SonarSource/ci-github-actions/pr_cleanup@v1Features:
- Removes GitHub Actions caches associated with closed PRs
- Cleans up artifacts created during PR workflows
- Provides detailed output of deleted resources
- Shows before/after state of caches and artifacts
- Required permissions:
actions: write
Usage example in workflow:
name: Cleanup PR Resources
on:
pull_request:
types: [ closed ]
jobs:
cleanup:
runs-on: sonar-xs
permissions:
actions: write
steps:
- uses: SonarSource/ci-github-actions/pr_cleanup@v1Complete mapping table for ci-common-scripts compatibility:
| Cirrus CI Variable | GitHub Actions Variable | Purpose |
|---|---|---|
GIT_SHA1 or CIRRUS_CHANGE_IN_REPO |
GITHUB_SHA |
Git commit SHA |
GITHUB_BASE_BRANCH or CIRRUS_BASE_BRANCH |
GITHUB_BASE_REF |
Base branch for PRs |
CIRRUS_BASE_SHA |
- | Base SHA for PRs |
GITHUB_BRANCH or CIRRUS_BRANCH |
GITHUB_HEAD_REF (PR) / GITHUB_REF_NAME (branch) |
Current branch |
GITHUB_REPO or CIRRUS_REPO_FULL_NAME |
GITHUB_REPOSITORY |
Full repo name (owner/repo) |
CIRRUS_REPO_NAME or PROJECT |
"${GITHUB_REPOSITORY#*/}" |
Repository short name |
CIRRUS_BUILD_ID or PIPELINE_ID |
GITHUB_RUN_ID |
Cirrus CI build / GitHub workflow run identifier |
CIRRUS_TASK_ID |
GITHUB_JOB |
Cirrus CI task / GitHub job identifier |
BUILD_NUMBER, CI_BUILD_NUMBER, or BUILD_ID |
BUILD_NUMBER |
Build number |
CIRRUS_DEFAULT_BRANCH |
${{ github.event.repository.default_branch }} |
Default branch |
CIRRUS_ENV |
GITHUB_ENV |
Environment file path |
Cirrus CI configurations often include these patterns:
# Cirrus CI
env:
ARTIFACTORY_DEPLOY_REPO: sonarsource-private-qa # or sonarsource-public-qa
ARTIFACTORY_DEPLOY_USERNAME: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer username]
ARTIFACTORY_DEPLOY_PASSWORD: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer access_token]
ARTIFACTORY_PRIVATE_USERNAME: vault-${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-private-reader
SONAR_HOST_URL: VAULT[development/kv/data/next data.url]
SONAR_TOKEN: VAULT[development/kv/data/next data.token]
PGP_PASSPHRASE: VAULT[development/kv/data/sign data.passphrase]✅ GitHub Actions Solution: All these are handled automatically by build-maven@v1!
You don't need to specify any of these environment variables or vault secrets manually.
| Cirrus CI | GitHub Actions |
|---|---|
eks_container |
runs-on: sonar-xs (private) / github-ubuntu-latest-s (public) |
cpu: 2, memory: 2G |
Runner handles resources |
| Custom images | Use mise for tools |
# Cirrus CI
eks_container:
<<: *CONTAINER_DEFINITION
cpu: 2
memory: 2GGitHub Actions Runner Selection:
Used for:
- Public repositories
- Private repositories that require Docker-in-Docker
Available sizes:
github-ubuntu-latest-sgithub-ubuntu-24.04-arm-sgithub-windows-latest-sgithub-ubuntu-latest-mgithub-ubuntu-24.04-arm-mgithub-ubuntu-latest-l
Example:
jobs:
build:
runs-on: github-ubuntu-latest-s
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- run: echo "Custom GitHub-hosted runner"Self-Hosted Runners (default for private repos)
Best for:
- Default for private repositories
- Access to internal tools and private resources (unified connectivity)
Available sizes:
sonar-xssonar-ssonar-msonar-lsonar-xl
Example:
jobs:
build:
runs-on: sonar-m
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- run: echo "Private self-hosted (new generation)"Limitations:
- Docker-in-Docker is not supported (use Custom GitHub-hosted for DIND)
For Public Repositories:
- Standard builds:
github-ubuntu-latest-s
For Private Repositories:
- Standard builds:
sonar-xs(default) → scale up as needed - Large builds:
sonar-s,sonar-m,sonar-l,sonar-xl
# Cirrus CI
promote_task:
depends_on:
- build
# GitHub Actions
promote:
needs:
- build # Cleaner syntax| Cirrus CI Pattern | GitHub Actions Pattern |
|---|---|
only_if: $CIRRUS_USER_COLLABORATOR == 'true' |
Built into SonarSource custom actions |
only_if: $CIRRUS_TAG == "" |
Built into promotion logic |
only_if: $CIRRUS_PR != "" |
Use if: conditions on jobs |
Original Cirrus CI often has complex anchor patterns like:
only_sonarsource_qa: &ONLY_SONARSOURCE_QA
only_if: >
(
$CIRRUS_USER_COLLABORATOR == 'true' &&
$CIRRUS_TAG == "" && (
$CIRRUS_PR != "" ||
$CIRRUS_BRANCH == 'master' ||
$CIRRUS_BRANCH =~ "branch-.*" ||
$CIRRUS_BRANCH =~ "dogfood-on-.*" ||
$CIRRUS_BUILD_SOURCE == 'api'
)
) || $CIRRUS_CRON == 'nightly'✅ GitHub Actions Solution: Remove these entirely! The SonarSource custom actions
handle all this logic automatically. The workflow will run based on the on: triggers
and the actions will handle internal authorization and deployment logic.
| Cirrus CI | GitHub Actions |
|---|---|
source cirrus-env BUILD-PRIVATE |
Handled by custom actions |
regular_mvn_build_deploy_analyze |
build-maven@v1 action |
cleanup_maven_repository |
Automatic in custom actions |
Cirrus CI typically includes:
# Cirrus CI
maven_cache:
folder: ${CIRRUS_WORKING_DIR}/.m2/repository
build_script:
- source cirrus-env BUILD-PRIVATE
- regular_mvn_build_deploy_analyze
cleanup_before_cache_script: cleanup_maven_repository✅ GitHub Actions Solution: All cache management and cleanup is handled automatically by the custom actions. No need to specify cache folders or cleanup scripts.
Action Pinning:
# ✅ Pin third-party actions to commit SHA
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
version: 2025.7.12
# ✅ SonarSource actions use semantic versioning (trusted)
- uses: SonarSource/ci-github-actions/build-maven@v1Permissions Documentation:
permissions:
id-token: write # Required for Vault OIDC authentication
contents: write # Required for repository access and taggingSecure Input Handling:
# ✅ Never use untrusted input directly in commands
- name: Process PR data
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
echo "Processing PR: $PR_TITLE"
echo "Body length: ${#PR_BODY}"promote:
needs: [build] # Always make promote depend on build# ❌ WRONG - May leak secrets
- uses: actions/upload-artifact@v4
with:
path: . # Entire directory may contain .git/config with tokens
# ✅ CORRECT - Upload specific files only
- uses: actions/upload-artifact@v4
with:
path: |
dist/
build/libs/*.jarCache Security:
- Never cache sensitive information (tokens, keys, credentials)
- Be aware that cache is accessible to anyone with read access to the repository
- Consider cache poisoning risks when using third-party actions
RECOMMENDED: Define concurrency at workflow level (cleaner and simpler):
name: Build
on:
push:
branches: [master, branch-*, dogfood-*]
pull_request:
merge_group:
workflow_dispatch:
# ✅ Workflow-level concurrency - RECOMMENDED
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
# No job-level concurrency needed
runs-on: sonar-xs
promote:
# No job-level concurrency needed
needs: [build]
runs-on: sonar-xsAvoid: Job-level concurrency duplication (more complex, error-prone):
# ❌ Job-level concurrency - NOT RECOMMENDED
jobs:
build:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
promote:
concurrency: # Duplicated configuration
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: trueAll jobs need these permissions:
permissions:
id-token: write # For Vault authentication
contents: write # For repository accessStandard order:
actions/checkoutjdx/mise-action(tool setup)- Build action (
build-maven@v1, etc.) promote@v1(promote job only)
❌ Don't add unused env vars like:
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} # Usually not neededRepository visibility is automatically detected using multiple indicators:
- Primary Detection: GitHub repository visibility (public/private)
- Secondary Detection:
ARTIFACTORY_DEPLOY_REPOvalue in.cirrus.yml - Override Detection: Specific vault patterns for artifactory roles
# Detection from ARTIFACTORY_DEPLOY_REPO in .cirrus.yml
env:
ARTIFACTORY_DEPLOY_REPO: sonarsource-private-qa # → Detected as private repository
# OR
ARTIFACTORY_DEPLOY_REPO: sonarsource-public-qa # → Detected as public repositoryPrivate Repository (ARTIFACTORY_DEPLOY_REPO: sonarsource-private-qa):
# Auto-detected configuration - no overrides needed
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
deploy-pull-request: true
# Automatically uses:
# - Runner: sonar-xs
# - Artifactory reader role: private-reader
# - Artifactory deployer role: qa-deployer
# - public: falsePublic Repository (ARTIFACTORY_DEPLOY_REPO: sonarsource-public-qa):
# Auto-detected configuration - no overrides needed
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
deploy-pull-request: true
# Automatically uses:
# - Runner: github-ubuntu-latest-s (for auth actions)
# - Artifactory reader role: public-reader
# - Artifactory deployer role: public-deployer
# - public: truePublic Repository with Private Access (Mixed configuration):
# Manual override required when .cirrus.yml contains:
# ARTIFACTORY_DEPLOY_REPO: sonarsource-private-qa (but repo is public)
# ARTIFACTORY_PRIVATE_PASSWORD: VAULT[...private-reader...]
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
deploy-pull-request: true
artifactory-reader-role: private-reader # Override auto-detection
artifactory-deployer-role: qa-deployer # Override auto-detectionKey Point: Repository visibility is automatically detected from the ARTIFACTORY_DEPLOY_REPO value in .cirrus.yml.
The public parameter and Artifactory roles are determined based on this value and GitHub repository visibility.
Only override if you have specific requirements (e.g., public repo needing private Artifactory access).
- CRITICAL: Check repository visibility (Settings → General → Repository visibility)
- Select correct runner type based on repository visibility:
- Public repo →
github-ubuntu-latest-s - Private repo →
sonar-xs
- Public repo →
- SECURITY: Pin all third-party actions to commit SHA
- SECURITY: Verify permissions follow least-privilege principle
- Check similar dummy repository for your project type
- Create
mise.tomlwith required tool versions - Verify Vault permissions are configured
- Create
.github/workflows/build.yml - Verify no conflicts with existing workflows
- DOCUMENTATION: Identify all documentation files requiring updates (see Phase 6)
- Add standard triggers (push, PR, merge_group, workflow_dispatch)
- Select appropriate runner type:
- Private repos:
sonar-xs - Public repos:
github-ubuntu-latest-s - Docker-in-Docker needed:
github-ubuntu-latest-s(regardless of repo visibility) - Public repos needing internal tools:
sonar-*-public(requires approval)
- Private repos:
- Configure concurrency control:
- RECOMMENDED: Add workflow-level concurrency (cleaner)
- AVOID: Job-level concurrency duplication
- Verify concurrency is defined only at workflow level, not job level
- Add checkout, mise steps
- Add appropriate build action (maven/gradle/poetry)
- Verify Overriding Artifactory Roles:
- Check repository visibility (public/private)
- Search
.cirrus.ymlforprivate-readervault pattern - Search
.cirrus.ymlforqa-deployervault pattern - If public repo + both patterns found → Add
artifactory-reader-role: private-readerandartifactory-deployer-role: qa-deployer - If public repo + no patterns found → Use default auto-detection (no override needed)
- If private repo → Use default auto-detection (no override needed)
- Verify Overriding Pull Request Deployment and Promotion
- Verify Overriding SonarQube Platform:
- Search
.cirrus.ymlforSONAR_HOST_URLvault pattern - If vault path contains
sonarcloud→ Addsonar-platform: sqc-eu - If vault path contains
sonarqube-us→ Addsonar-platform: sqc-us - If vault path contains
next→ Use default auto-detection (no override needed)
- Search
- If using cirrus-modules: Verify all features are covered by SonarSource custom actions
- Test build job functionality
- Add promote job with proper dependencies
- Configure same concurrency control
- Add checkout, mise (with cache_save: false)
- Add promote action
- Verify Overriding Pull Request Deployment and Promotion
- Test promotion functionality
- Replace manual pr-cleanup.yml with official SonarSource action (see details below)
- Add
pr-cleanup.ymlfor automatic PR resource cleanup if not present - Consider stable-branch-update job if needed
- Set up any project-specific additional workflows
CRITICAL: If .github/workflows/pr-cleanup.yml exists, check if it uses manual implementation:
❌ Replace Manual Implementation:
# Manual implementation (64+ lines) - REPLACE THIS
name: Cleanup caches and artifacts on PR close
jobs:
cleanup:
steps:
- name: Cleanup caches
run: |
# Complex shell scripts with gh cache commands...
- name: Delete artifacts
run: |
# Complex shell scripts with gh api commands...✅ With Official SonarSource Action:
# Official implementation (13 lines) - USE THIS
name: Cleanup PR Resources
on:
pull_request:
types: [closed]
jobs:
cleanup:
runs-on: sonar-xs
permissions:
actions: write
steps:
- uses: SonarSource/ci-github-actions/pr_cleanup@v1.cirrus.yml the tasks that are now handled by GitHub Actions.
If no task remain, then remove the Cirrus CI files: .cirrus.yml, .cirrus.star, .cirrus/Dockerfile...
- Verify all Cirrus CI functionality is replicated
- Configure build number in repository settings (> latest Cirrus CI build)
- Test both PR and branch builds
- Keep
.cirrus.ymlas-is (DO NOT remove or comment out during migration)
CRITICAL: Always update documentation to reflect the migration. Search systematically:
- README.md: Replace Cirrus CI badges with GitHub Actions badges
# Before (Cirrus CI)  # After (GitHub Actions) [](https://github.com/SonarSource/REPO/actions/workflows/build.yml)
- Search workflows for Cirrus CI paths: Look for
/tmp/cirrus-ci-build/references - Replace with GitHub Actions paths: Use
$GITHUB_WORKSPACE/or relative paths# Before (Cirrus CI) sed "s|/tmp/cirrus-ci-build/src/|src/|g" # After (GitHub Actions) sed "s|$GITHUB_WORKSPACE/src/|src/|g"
- Check existing pr-cleanup.yml: Look for manual
gh cacheandgh apicommands - Replace with official action: Use
SonarSource/ci-github-actions/pr_cleanup@v1 - Verify correct permissions: Ensure
actions: writepermission is set - Search for other manual implementations that have official SonarSource equivalents
- Search all
.mdfiles for Cirrus CI references:grep -ri "cirrus\|\.cirrus" *.md
- Update CI setup instructions in CONTRIBUTE.md (if present)
- Update any workflow documentation that references
.cirrus.yml - Check for environment variable references that may need updating
- Run pre-commit on all changed files to ensure formatting compliance
- Fix any linting issues reported by pre-commit hooks
Ensure your repository has the required Vault permissions in re-terraform-aws-vault/orders:
some-repository:
auth:
github: {}
secrets:
artifactory:
roles:
- *artifactory_public-reader # For PUBLIC repository, reader token
- *artifactory_public-deployer # For PUBLIC repository, deployer token
- *artifactory_private-reader # For PRIVATE repository, reader token
- *artifactory_qa-deployer # For PRIVATE repository, deployer token
- *artifactory_promoter # For artifact promotion
github:
presets:
- licenses # Only for QA tests
customs:
- <<: *github_jira # For gh-action-lt-backlog
repositories: [some-repository]
- <<: *github_promotion # GitHub checks with build number
repositories: [some-repository]
kv_paths:
development/kv/data/datadog: {} # For gh-action_release
development/kv/data/jira: {} # For gh-action-lt-backlog
development/kv/data/ossrh: {} # For gh-action_release, if mavenCentralSync
development/kv/data/pypi-test: {} # For gh-action_release, if publishToTestPyPI
development/kv/data/pypi: {} # For gh-action_release, if publishToPyPI
development/kv/data/repox: {} # For gh-action_release
development/kv/data/slack: {} # For gh-action_release
development/kv/data/sonarcloud: {} # For manual scan with SCNote: Most secrets should already exist from Cirrus CI usage.
Critical Step: After migration, configure the GitHub build number: login to SPEED and run the Update GitHub Build Number action.
Reference these SonarSource dummy repositories for specific patterns:
| Repository | Type | Build System | Notes |
|---|---|---|---|
| sonar-dummy | Private Java | Maven | Standard private Maven project |
| sonar-dummy-maven-enterprise | Public+Private Java | Maven | Mixed public/private content |
| sonar-dummy-yarn | Private NodeJS | NPM+Yarn | Node.js with Yarn |
| sonar-dummy-js | Private JavaScript | NPM | JavaScript project |
| sonar-dummy-oss | Public Java | Gradle | Public Gradle project |
| sonar-dummy-python-oss | Public Python | Poetry | Public Python with Poetry |
Best Practice: Check the most similar dummy repository for your project type before starting migration.
Recommended: Add .github/workflows/pr-cleanup.yml to automatically clean up PR resources when PRs are closed.
See the pr_cleanup action documentation above for full details and usage example.
This workflow automatically:
- Removes GitHub Actions caches associated with closed PRs
- Cleans up artifacts created during PR workflows
- Provides detailed output of deleted resources
- Use Maven cache key format:
maven-${{ runner.os }}(better UI filtering) - Include
pr-cleanup.ymlfor automatic PR resource cleanup - SECURITY: Pin all third-party actions to commit SHA
- SECURITY: Use environment variables for untrusted input
- SECURITY: Document all permissions with comments explaining why they're needed
- DOCUMENTATION: Update README.md build badges from Cirrus CI to GitHub Actions
- DOCUMENTATION: Replace all Cirrus CI path references with GitHub Actions equivalents
- DOCUMENTATION: Search systematically for all CI system references in
.mdfiles - WORKFLOWS: Replace manual pr-cleanup.yml implementations with SonarSource/ci-github-actions/pr_cleanup@v1
- Don't specify
GH_TOKENenvironment variable in build job (auto-handled) - Don't trigger on
gh-readonly-queue/*branches - Don't upload
${{ github.event_path }}file as artifact - Don't use GitHub licenses token except for QA tests
- SECURITY: Don't use unpinned third-party actions (
@main,@v1) - SECURITY: Don't use untrusted input directly in shell commands
- SECURITY: Don't upload entire directories as artifacts (may contain secrets)
- SECURITY: Don't cache sensitive information (tokens, keys, credentials)
- DOCUMENTATION: Don't forget to update README.md build badges
- DOCUMENTATION: Don't leave Cirrus CI path references (e.g.,
/tmp/cirrus-ci-build/) - DOCUMENTATION: Don't skip systematic search for CI references in documentation files
- WORKFLOWS: Don't keep manual pr-cleanup implementations when official SonarSource actions exist
- Missing permissions: Ensure
id-token: writeandcontents: writeare set - Tool versions: Use mise.toml instead of manual setup actions
- Cache conflicts: Use
cache_save: falsein promote jobs - Branch conditions: Let custom actions handle most conditional logic
- Build number continuity: Set custom property > latest Cirrus CI build
- Artifactory role mismatch: If your Cirrus CI uses different roles than auto-detected, override them:
# Check .cirrus.yml for actual roles used and override if needed - uses: SonarSource/ci-github-actions/build-maven@v1 with: artifactory-reader-role: private-reader # Match Cirrus CI config artifactory-deployer-role: qa-deployer # Match Cirrus CI config
- Cirrus-modules migration: If migrating from cirrus-modules, don't try to recreate individual features manually - use the comprehensive SonarSource custom actions instead
- Security: Ensure third-party actions are pinned to commit SHA
- Script injection: Never use untrusted input directly in shell commands
- Vault authentication: Ensure
id-token: writepermission is set for OIDC authentication - Vault permissions: Check that repository has required vault permissions in
re-terraform-aws-vault/orders - Runner selection: Ensure you're using current runner names (see GitHub Actions Runner Selection section)
Script Injection Issues:
# ❌ If you see injection vulnerabilities
run: echo "Title: ${{ github.event.pull_request.title }}"
# ✅ Fix with environment variables
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "Title: $PR_TITLE"Action Pinning Issues:
- Check that all non-SonarSource actions use commit SHA
- Use GitHub's Dependabot to keep pinned actions updated
- Never use
@mainor@masterfor third-party actions
💡 Version Updates: Check the
releases page for the
latest stable versions. While @v1 is typically the current stable version, newer
major versions may be available with enhanced features.
Many SonarSource repositories use cirrus-modules, a centralized Starlark library that abstracts away CI
infrastructure complexity. You can identify these repositories by the presence of a .cirrus.star file:
# renovate: datasource=github-releases depName=SonarSource/cirrus-modules
load("github.com/SonarSource/cirrus-modules@74c00b08bd556f6f6f59cc244941f0a815d79e42", "load_features") # 3.3.0
def main(ctx):
return load_features(ctx)When migrating repositories that use cirrus-modules, it can be helpful to see the actual YAML configuration that the Starlark code
generates. This makes it easier to understand what features need to be replicated in GitHub Actions.
To see the expanded configuration that cirrus-modules generates, you can create a .cirrus.generated.yml file by running the Starlark
interpolation locally:
# Generate the expanded configuration
CIRRUS_REPO_NAME=$(basename $(git rev-parse --show-toplevel))
CIRRUS_DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq ".defaultBranchRef.name")
cat .cirrus.yml > .cirrus.generated.yml
cirrus validate -f .cirrus.star -p -e "CIRRUS_REPO_CLONE_TOKEN=$(gh auth token)" \
-e "CIRRUS_BRANCH=$CIRRUS_DEFAULT_BRANCH" -e "CIRRUS_DEFAULT_BRANCH=$CIRRUS_DEFAULT_BRANCH" \
-e "CIRRUS_REPO_OWNER=SonarSource" -e "CIRRUS_REPO_NAME=$CIRRUS_REPO_NAME" -e "CIRRUS_REPO_FULL_NAME=SonarSource/$CIRRUS_REPO_NAME" \
>> .cirrus.generated.ymlThe generated file shows you exactly what tasks, environments, and configurations the Starlark code creates. Use this file to:
- Identify all tasks and dependencies that need to be migrated
- Understand environment variables and vault secrets being used
- Map Cirrus CI patterns to their GitHub Actions equivalents
- Verify completeness of your migration by comparing features
Example workflow for migration:
# 1. Generate the expanded configuration
# 2. Review the generated tasks
grep -E "(task:|depends_on:|only_if:)" .cirrus.generated.yml
# 3. Identify environment variables and vault secrets
grep -E "(env:|VAULT\[)" .cirrus.generated.yml
# 4. Use this information to create your GitHub Actions workflows
# 5. Compare the generated file with your GitHub Actions implementation- The
.cirrus.generated.ymlfile is for migration reference only - Never commit this file to your repository (add to
.gitignore) - Always use the latest version by regenerating when cirrus-modules updates
- Remember: GitHub Actions workflows will be simpler than the generated Cirrus CI config
A typical .cirrus.generated.yml might expand your simple .cirrus.star into something like:
# This is what gets generated from load_features(ctx)
env:
CIRRUS_CLONE_DEPTH: "20"
CIRRUS_SHELL: bash
ARTIFACTORY_URL: VAULT[development/kv/data/repox data.url]
# ... many more environment variables
build_task:
only_if: $CIRRUS_USER_COLLABORATOR == 'true' && ...
eks_container:
image: ${CIRRUS_AWS_ACCOUNT}.dkr.ecr.eu-central-1.amazonaws.com/base:j17-latest
# ... container configuration
env:
# ... task-specific environment variables
maven_cache:
folder: ${CIRRUS_WORKING_DIR}/.m2/repository
build_script:
- source cirrus-env BUILD-PRIVATE
- regular_mvn_build_deploy_analyze
# ... more task configuration
promote_task:
depends_on: [ build ]
# ... promotion task configurationThis expanded view makes it clear what needs to be migrated to GitHub Actions workflows.
The cirrus-modules system provides several features that need to be handled during migration:
| Cirrus-Modules Feature | GitHub Actions Equivalent | Notes |
|---|---|---|
| AWS Infrastructure | runs-on: sonar-xs |
Runner selection handles infrastructure |
| Vault Authentication | SonarSource/vault-action-wrapper |
Direct vault integration |
| Build Numbers | SonarSource/ci-github-actions/get-build-number@v1 |
Continuous build numbering |
| Repox/Artifactory | SonarSource/ci-github-actions/build-*@v1 |
Handled by build actions |
| Conditional Execution | on: triggers + if: conditions |
GitHub Actions native conditions |
env:
CIRRUS_CLONE_DEPTH: "20"
CIRRUS_SHELL: bash
ARTIFACTORY_URL: VAULT[development/kv/data/repox data.url]
PGP_PASSPHRASE: VAULT[development/kv/data/sign data.passphrase]
container_definition: &CONTAINER_DEFINITION
image: ${CIRRUS_AWS_ACCOUNT}.dkr.ecr.eu-central-1.amazonaws.com/base:j17-latest
cluster_name: ${CIRRUS_CLUSTER_NAME}
region: eu-central-1
namespace: default
only_sonarsource_qa: &ONLY_SONARSOURCE_QA
only_if: >
(
$CIRRUS_USER_COLLABORATOR == 'true' &&
$CIRRUS_TAG == "" && (
$CIRRUS_PR != "" ||
$CIRRUS_BRANCH == 'master' ||
$CIRRUS_BRANCH =~ "branch-.*" ||
$CIRRUS_BRANCH =~ "dogfood-on-.*" ||
$CIRRUS_BUILD_SOURCE == 'api'
)
) || $CIRRUS_CRON == 'nightly'
build_task:
<<: *ONLY_SONARSOURCE_QA
eks_container:
<<: *CONTAINER_DEFINITION
cpu: 2
memory: 2G
env:
ARTIFACTORY_DEPLOY_PASSWORD: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer access_token]
ARTIFACTORY_DEPLOY_USERNAME: vault-${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-qa-deployer
ARTIFACTORY_PRIVATE_USERNAME: vault-${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-private-reader
ARTIFACTORY_DEPLOY_REPO: sonarsource-private-qa
SONAR_HOST_URL: VAULT[development/kv/data/next data.url]
DEPLOY_PULL_REQUEST: "true"
SONAR_TOKEN: VAULT[development/kv/data/next data.token]
maven_cache:
folder: ${CIRRUS_WORKING_DIR}/.m2/repository
build_script:
- source cirrus-env BUILD-PRIVATE
- regular_mvn_build_deploy_analyze
cleanup_before_cache_script: cleanup_maven_repository
promote_task:
depends_on:
- build
<<: *ONLY_SONARSOURCE_QA
eks_container:
<<: *CONTAINER_DEFINITION
cpu: 1
memory: 1G
env:
ARTIFACTORY_PROMOTE_ACCESS_TOKEN: VAULT[development/artifactory/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-promoter access_token]
GITHUB_TOKEN: VAULT[development/github/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-promotion token]
maven_cache:
folder: ${CIRRUS_WORKING_DIR}/.m2/repository
script: cirrus_promote_maven
cleanup_before_cache_script: cleanup_maven_repositoryname: Build
on:
push:
branches:
- master
- branch-*
- dogfood-*
pull_request:
merge_group:
workflow_dispatch:
jobs:
build:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
runs-on: sonar-xs # Private repos default; use github-ubuntu-latest-s for public repos
name: Build
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
version: 2025.7.12
- uses: SonarSource/ci-github-actions/build-maven@v1
with:
deploy-pull-request: true
promote:
needs: [build]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
runs-on: sonar-xs # Private repos default; use github-ubuntu-latest-s for public repos
name: Promote
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
cache_save: false
version: 2025.7.12
- uses: SonarSource/ci-github-actions/promote@v1
with:
promote-pull-request: true[tools]
java = "21.0"
maven = "3.9"Result: 17% reduction in configuration lines, 90% reduction in complexity, same functionality!
-
Don't manually fetch Vault secrets:
# ❌ WRONG - Don't do this - name: Vault id: secrets uses: SonarSource/vault-action-wrapper@3.0.2 with: secrets: | development/artifactory/token/... | ARTIFACTORY_TOKEN
✅ Correct: Let
build-maven@v1handle all secrets automatically. -
Don't manually set up caching:
# ❌ WRONG - Don't do this - uses: actions/cache@v4 with: path: ~/.m2/repository
✅ Correct: Custom actions handle caching automatically.
-
Don't copy complex Cirrus CI conditions:
# ❌ WRONG - Don't port these complex conditions if: ${{ github.actor == 'dependabot[bot]' || ... complex logic ... }}
✅ Correct: Use simple triggers and let custom actions handle authorization.
-
Don't specify unnecessary parameters:
# ❌ WRONG - These are auto-detected - uses: SonarSource/ci-github-actions/build-maven@v1 with: public: true # Auto-detected from repo visibility deploy-pull-request: true # ❌ WRONG - These environment variables are auto-detected env: ARTIFACTORY_URL: https://repox.jfrog.io/artifactory DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} # ❌ WRONG - GITHUB_TOKEN is already available by default - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 env: GITHUB_TOKEN: ${{ github.token }} # Not needed!
-
Don't use manual JDK setup when using mise:
# ❌ WRONG - Conflicts with mise - uses: actions/setup-java@v4 with: java-version: '21'
✅ Correct: Use
mise.tomlandjdx/mise-action. -
Don't modify
.cirrus.ymlduring migration:# ❌ WRONG - Don't comment out or modify .cirrus.yml during migration # env: # CIRRUS_CLONE_DEPTH: "20"
✅ Correct: Leave
.cirrus.ymlexactly as-is during migration for coexistence.
- Keep it simple - trust the custom actions
- Use standard triggers and let actions handle the rest
- Use
mise.tomlfor tool versions - Let parameters auto-detect from repository settings (public/private, Artifactory roles)
- Leave
.cirrus.ymlunchanged during migration - Test with feature branches first
When migrating from Cirrus CI, replace specific checkout depth configurations (like CIRRUS_CLONE_DEPTH) with standard checkout action without fetch-depth parameter.
Replace any manual SonarQube scanning implementations with the official SonarSource/sonarqube-scan-action unless explicitly asked not to do it. Manual implementations typically involve downloading sonar-scanner CLI, setting up paths, running sonar-scanner command directly, or using Docker containers.
Example of manual scanning that should be replaced:
- name: SonarQube Scan
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
run: |
# Download and setup sonar-scanner
wget -q https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip
unzip -q sonar-scanner-cli-5.0.1.3006-linux.zip
export PATH=$PATH:$(pwd)/sonar-scanner-5.0.1.3006-linux/bin
sonar-scannerExample from Cirrus CI that should be replaced:
scanner_task:
name: SonarQube Cloud Scan
depends_on:
- test
eks_container:
image: sonarsource/sonar-scanner-cli:5.0
cluster_name: ${CIRRUS_CLUSTER_NAME}
region: eu-central-1
namespace: default
cpu: 2
memory: 2G
env:
SONAR_TOKEN: VAULT[development/kv/data/sonarcloud data.token]
SONAR_HOST_URL: VAULT[development/kv/data/sonarcloud data.url]
analyze_script:
- sonar-scannerReplace with:
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SONAR_TOKEN }}
SONAR_HOST_URL: ${{ fromJSON(steps.secrets.outputs.vault).SONAR_HOST_URL }}