Compare commits

...

6 Commits

Author SHA1 Message Date
dependabot[bot] 7624ed30ed NO-JIRA Bump actions/checkout from 6.0.2 to 6.0.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-08 12:03:57 +00:00
Julien HENRY 3581139216 SQSCANGHA-135 Fix scanner binaries always re-downloaded due to incompatible 4-part version (#250)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 10:53:34 +02:00
Julien HENRY c9d327c024 SQSCANGHA-84 Remove outdated wget/curl references
The action was refactored to use Node.js (@actions/tool-cache) for
downloads, which doesn't rely on wget or curl. Update the README and
QA workflow to reflect this.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 17:25:27 +02:00
Julien HENRY b243e5198f SQSCANGHA-88 Deprecate the SONARCLOUD_URL env variable support
Emit a warning when SONARCLOUD_URL is set, directing users to either
pass nothing, use SONAR_REGION=us for the US region, or pass
-Dsonar.scanner.sonarcloudUrl and -Dsonar.scanner.apiBaseUrl via args
for advanced needs. Backward compatibility is preserved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 16:34:28 +02:00
Julien HENRY 375c3f5c03 SQSCANGHA-149 Add scannerBinariesAuthHeader input for authenticated binary downloads
Organisations using private Artifactory mirrors require authentication to
download the SonarScanner CLI. This adds an optional scannerBinariesAuthHeader
input whose value is forwarded as the Authorization HTTP header to both the
binary and GPG signature downloads via tc.downloadTool's built-in auth
parameter. No new dependencies are introduced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 14:19:55 +02:00
Julien HENRY 9c783232fe SQSCANGHA-144 Add gate jobs to QA workflows for branch protection
Add a non-matrix gate job to qa-main, qa-deprecated-c-cpp, and
qa-install-build-wrapper workflows. Each gate job depends on all
other jobs in its workflow and provides a single stable check context
that can be used in GitHub branch protection required status checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 09:32:35 +02:00
18 changed files with 641 additions and 79 deletions
+15 -1
View File
@@ -34,7 +34,7 @@ jobs:
exit 1 exit 1
fi fi
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
@@ -88,3 +88,17 @@ jobs:
BINARY: ${{ steps.run-action.outputs.build-wrapper-binary }} BINARY: ${{ steps.run-action.outputs.build-wrapper-binary }}
run: | run: |
("$BINARY" || true) | grep "build-wrapper, version " ("$BINARY" || true) | grep "build-wrapper, version "
qa-gate:
name: QA Deprecated C and C++ - gate
runs-on: ubuntu-latest
needs: [output-test]
if: always()
steps:
- name: Check all jobs passed
run: |
if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One or more required jobs failed or were cancelled."
exit 1
fi
echo "All checks passed."
+15 -1
View File
@@ -34,7 +34,7 @@ jobs:
exit 1 exit 1
fi fi
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
@@ -70,3 +70,17 @@ jobs:
BINARY: ${{ steps.run-action.outputs.build-wrapper-binary }} BINARY: ${{ steps.run-action.outputs.build-wrapper-binary }}
run: | run: |
("$BINARY" || true) | grep "build-wrapper, version " ("$BINARY" || true) | grep "build-wrapper, version "
qa-gate:
name: QA Install Build Wrapper - gate
runs-on: ubuntu-latest
needs: [output-test]
if: always()
steps:
- name: Check all jobs passed
run: |
if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One or more required jobs failed or were cancelled."
exit 1
fi
echo "All checks passed."
+68 -47
View File
@@ -17,7 +17,7 @@ jobs:
os: [github-ubuntu-latest-s, macos-latest] os: [github-ubuntu-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action without args - name: Run action without args
@@ -37,7 +37,7 @@ jobs:
os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest] os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with args - name: Run action with args
@@ -66,7 +66,7 @@ jobs:
] ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with args - name: Run action with args
@@ -93,7 +93,7 @@ jobs:
os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest] os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with args - name: Run action with args
@@ -121,7 +121,7 @@ jobs:
os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest] os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with args - name: Run action with args
@@ -148,7 +148,7 @@ jobs:
os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest] os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with args - name: Run action with args
@@ -178,7 +178,7 @@ jobs:
os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest] os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- run: mkdir -p ./baseDir - run: mkdir -p ./baseDir
@@ -198,7 +198,7 @@ jobs:
'scannerVersion' input 'scannerVersion' input
runs-on: github-ubuntu-latest-s # assumes default RUNNER_ARCH for linux is X64 runs-on: github-ubuntu-latest-s # assumes default RUNNER_ARCH for linux is X64
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with scannerVersion - name: Run action with scannerVersion
@@ -222,7 +222,7 @@ jobs:
'scannerBinariesUrl' input with invalid URL 'scannerBinariesUrl' input with invalid URL
runs-on: github-ubuntu-latest-s # assumes default RUNNER_ARCH for linux is X64 runs-on: github-ubuntu-latest-s # assumes default RUNNER_ARCH for linux is X64
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with scannerBinariesUrl - name: Run action with scannerBinariesUrl
@@ -245,12 +245,12 @@ jobs:
- name: Assert Sonar Scanner CLI was not executed - name: Assert Sonar Scanner CLI was not executed
run: | run: |
./test/assertFileDoesntExist ./output.properties ./test/assertFileDoesntExist ./output.properties
scannerBinariesUrlIsEscapedWithWget: scannerBinariesUrlCommandInjectionTest:
name: > name: >
'scannerBinariesUrl' is escaped with wget so special chars are not injected in the download command 'scannerBinariesUrl' does not allow command injection via semicolons
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with scannerBinariesUrl - name: Run action with scannerBinariesUrl
@@ -266,22 +266,14 @@ jobs:
- name: Assert file.txt does not exist - name: Assert file.txt does not exist
run: | run: |
./test/assertFileDoesntExist "$RUNNER_TEMP/sonarscanner/file.txt" ./test/assertFileDoesntExist "$RUNNER_TEMP/sonarscanner/file.txt"
scannerBinariesUrlIsEscapedWithCurl: scannerBinariesUrlCommandInjectionWithSpacesTest:
name: > name: >
'scannerBinariesUrl' is escaped with curl so special chars are not injected in the download command 'scannerBinariesUrl' does not allow command injection via spaces and quotes
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Remove wget
run: sudo apt-get remove -y wget
- name: Assert wget is not available
run: |
if command -v wget 2>&1 >/dev/null
then
exit 1
fi
- name: Run action with scannerBinariesUrl - name: Run action with scannerBinariesUrl
id: runTest id: runTest
uses: ./ uses: ./
@@ -300,7 +292,7 @@ jobs:
Don't fail on Gradle project Don't fail on Gradle project
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action on Gradle project - name: Run action on Gradle project
@@ -321,7 +313,7 @@ jobs:
Don't fail on Kotlin Gradle project Don't fail on Kotlin Gradle project
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action on Kotlin Gradle project - name: Run action on Kotlin Gradle project
@@ -342,7 +334,7 @@ jobs:
Don't fail on Maven project Don't fail on Maven project
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action on Maven project - name: Run action on Maven project
@@ -375,7 +367,7 @@ jobs:
--health-timeout 5s --health-timeout 5s
--health-retries 10 --health-retries 10
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action on sample project - name: Run action on sample project
@@ -398,7 +390,7 @@ jobs:
os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest] os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with debug mode - name: Run action with debug mode
@@ -429,7 +421,7 @@ jobs:
--health-timeout 5s --health-timeout 5s
--health-retries 10 --health-retries 10
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: SonarQube Cache - name: SonarQube Cache
@@ -451,17 +443,17 @@ jobs:
./test/assertFileExists ./test/example-project/.scannerwork/report-task.txt ./test/assertFileExists ./test/example-project/.scannerwork/report-task.txt
overrideSonarcloudUrlTest: overrideSonarcloudUrlTest:
name: > name: >
'SONARCLOUD_URL' is used Deprecated 'SONARCLOUD_URL' still works and emits a deprecation warning
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest] os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with SONARCLOUD_URL - name: Run action with deprecated SONARCLOUD_URL
uses: ./ uses: ./
with: with:
args: -Dsonar.scanner.apiBaseUrl=api.mirror.sonarcloud.io -Dsonar.scanner.internal.dumpToFile=./output.properties args: -Dsonar.scanner.apiBaseUrl=api.mirror.sonarcloud.io -Dsonar.scanner.internal.dumpToFile=./output.properties
@@ -472,22 +464,14 @@ jobs:
run: | run: |
./test/assertFileContains ./output.properties "sonar.host.url=mirror.sonarcloud.io" ./test/assertFileContains ./output.properties "sonar.host.url=mirror.sonarcloud.io"
./test/assertFileContains ./output.properties "sonar.scanner.sonarcloudUrl=mirror.sonarcloud.io" ./test/assertFileContains ./output.properties "sonar.scanner.sonarcloudUrl=mirror.sonarcloud.io"
curlPerformsRedirect: scannerBinariesUrlRedirectFollowed:
name: > name: >
curl performs redirect when scannerBinariesUrl returns 3xx scannerBinariesUrl redirect (3xx) is followed
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Remove wget
run: sudo apt-get remove -y wget
- name: Assert wget is not available
run: |
if command -v wget 2>&1 >/dev/null
then
exit 1
fi
- name: Generate SSL certificates for nginx - name: Generate SSL certificates for nginx
run: ./generate-ssl.sh run: ./generate-ssl.sh
working-directory: .github/qa-nginx-redirecting working-directory: .github/qa-nginx-redirecting
@@ -521,7 +505,7 @@ jobs:
os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest] os: [github-ubuntu-latest-s, github-windows-latest-s, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with SSL certificate - name: Run action with SSL certificate
@@ -572,7 +556,7 @@ jobs:
Analysis takes into account 'SONAR_ROOT_CERT' Analysis takes into account 'SONAR_ROOT_CERT'
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Generate server certificate - name: Generate server certificate
@@ -680,7 +664,7 @@ jobs:
truststore.p12 is updated when present truststore.p12 is updated when present
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Create SONAR_SSL_FOLDER with a file in it (not-truststore.p12) - name: Create SONAR_SSL_FOLDER with a file in it (not-truststore.p12)
@@ -809,7 +793,7 @@ jobs:
'scannerVersion' input validation 'scannerVersion' input validation
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Run action with invalid scannerVersion - name: Run action with invalid scannerVersion
@@ -827,3 +811,40 @@ jobs:
run: | run: |
echo "Action with invalid scannerVersion should have failed but succeeded" echo "Action with invalid scannerVersion should have failed but succeeded"
exit 1 exit 1
qa-gate:
name: QA Main - gate
runs-on: ubuntu-latest
needs:
- noInputsTest
- argsInputTest
- argsInputInjectionTest
- backtickCommandInjectionTest
- dollarSymbolCommandInjectionTest
- otherCommandInjectionVariantsTest
- projectBaseDirInputTest
- scannerVersionTest
- scannerBinariesUrlTest
- scannerBinariesUrlCommandInjectionTest
- scannerBinariesUrlCommandInjectionWithSpacesTest
- dontFailGradleTest
- dontFailGradleKotlinTest
- dontFailMavenTest
- runAnalysisTest
- runnerDebugUsedTest
- runAnalysisWithCacheTest
- overrideSonarcloudUrlTest
- scannerBinariesUrlRedirectFollowed
- useSslCertificate
- analysisWithSslCertificate
- updateTruststoreWhenPresent
- scannerVersionValidationTest
if: always()
steps:
- name: Check all jobs passed
run: |
if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One or more required jobs failed or were cancelled."
exit 1
fi
echo "All checks passed."
+4 -4
View File
@@ -12,7 +12,7 @@ jobs:
name: create_install_path.sh name: create_install_path.sh
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
@@ -123,7 +123,7 @@ jobs:
SONAR_SCANNER_URL_MACOSX_AARCH64: 'https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-vX.Y.Z.MMMM-macosx-aarch64.zip' SONAR_SCANNER_URL_MACOSX_AARCH64: 'https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-vX.Y.Z.MMMM-macosx-aarch64.zip'
SONAR_SCANNER_SHA_MACOSX_AARCH64: 'DOWNLOAD-SHA-MACOSX-AARCH64' SONAR_SCANNER_SHA_MACOSX_AARCH64: 'DOWNLOAD-SHA-MACOSX-AARCH64'
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
@@ -252,7 +252,7 @@ jobs:
name: download.sh name: download.sh
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
@@ -321,7 +321,7 @@ jobs:
name: fetch_latest_version.sh name: fetch_latest_version.sh
runs-on: github-ubuntu-latest-s runs-on: github-ubuntu-latest-s
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Test script - name: Test script
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
contents: read contents: read
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e #v6.4.0 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e #v6.4.0
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Parse semver - name: Parse semver
uses: madhead/semver-utils@4cf918affe9106ea59f86c6250e5ec4570ac4389 # v5.0.0 uses: madhead/semver-utils@4cf918affe9106ea59f86c6250e5ec4570ac4389 # v5.0.0
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
new-version: ${{ steps.latest-version.outputs.sonar-scanner-version }} new-version: ${{ steps.latest-version.outputs.sonar-scanner-version }}
steps: steps:
- run: sudo apt install -y jq - run: sudo apt install -y jq
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
ref: master ref: master
fetch-depth: 0 fetch-depth: 0
@@ -49,7 +49,7 @@ jobs:
pull-requests: write pull-requests: write
if: needs.check-version.outputs.should_update == 'true' if: needs.check-version.outputs.should_update == 'true'
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
ref: master ref: master
persist-credentials: true persist-credentials: true
+16 -2
View File
@@ -200,6 +200,20 @@ This can be useful when the runner executing the action is self-hosted and has r
scannerBinariesUrl: https://my.custom.binaries.url.com/Distribution/sonar-scanner-cli/ scannerBinariesUrl: https://my.custom.binaries.url.com/Distribution/sonar-scanner-cli/
``` ```
#### `scannerBinariesAuthHeader`
If the server specified by `scannerBinariesUrl` requires authentication, you can provide an `Authorization` header value using the `scannerBinariesAuthHeader` option.
The value is passed directly as the `Authorization` HTTP header, so you must include the scheme (e.g. `Bearer`, `Basic`):
```yaml
- uses: SonarSource/sonarqube-scan-action@<action version>
with:
scannerBinariesUrl: https://my.custom.binaries.url.com/Distribution/sonar-scanner-cli/
scannerBinariesAuthHeader: ${{ secrets.BINARIES_AUTH_HEADER }}
```
Store the full header value (e.g. `Bearer mytoken`) in the GitHub secret to avoid exposing credentials.
#### `skipSignatureVerification` #### `skipSignatureVerification`
By default, the action verifies the OpenPGP signature of the SonarScanner CLI binary before executing it. You can disable this verification using the `skipSignatureVerification` option: By default, the action verifies the OpenPGP signature of the SonarScanner CLI binary before executing it. You can disable this verification using the `skipSignatureVerification` option:
@@ -469,11 +483,11 @@ See also [example configurations of C++ projects for SonarQube Server](https://g
When running the action in a self-hosted runner or container, please ensure that the following programs are installed: When running the action in a self-hosted runner or container, please ensure that the following programs are installed:
* **curl** or **wget**
* **unzip**
* **gpg** * **gpg**
* **dirmngr** * **dirmngr**
Note: `gpg` and `dirmngr` are only required for GPG signature verification (enabled by default). They can be omitted when setting `skipSignatureVerification: true`.
### Additional information ### Additional information
The `sonarqube-scan-action/install-build-wrapper` action installs `coreutils` if run on macOS. The `sonarqube-scan-action/install-build-wrapper` action installs `coreutils` if run on macOS.
+7
View File
@@ -28,6 +28,13 @@ inputs:
description: Skip GPG signature verification (not recommended for security) description: Skip GPG signature verification (not recommended for security)
required: false required: false
default: "false" default: "false"
scannerBinariesAuthHeader:
description: >
Authorization header value to use when downloading the SonarScanner CLI binaries
(e.g. 'Bearer mytoken' or 'Basic base64creds'). Use this when scannerBinariesUrl
points to a private server that requires authentication.
required: false
default: ""
runs: runs:
using: node24 using: node24
main: dist/index.js main: dist/index.js
+42 -7
View File
@@ -3503,6 +3503,13 @@ function downloadToolAttempt(url, dest, auth, headers) {
const http = new HttpClient(userAgent, [], { const http = new HttpClient(userAgent, [], {
allowRetries: false allowRetries: false
}); });
if (auth) {
debug('set auth');
if (headers === undefined) {
headers = {};
}
headers.authorization = auth;
}
const response = yield http.get(url, headers); const response = yield http.get(url, headers);
if (response.message.statusCode !== 200) { if (response.message.statusCode !== 200) {
const err = new HTTPError(response.message.statusCode); const err = new HTTPError(response.message.statusCode);
@@ -3855,6 +3862,19 @@ function getScannerDownloadURL({
const scannerDirName = (version, flavor) => const scannerDirName = (version, flavor) =>
`sonar-scanner-${version}-${flavor}`; `sonar-scanner-${version}-${flavor}`;
/**
* Converts a 4-part version string (e.g. "8.0.1.6346") to a SemVer 2.0 compatible
* string (e.g. "8.0.1-build.6346") for use with GitHub's tool-cache library,
* which requires SemVer-compliant version strings.
*/
function toSemVer(version) {
const parts = version.split(".");
if (parts.length === 4) {
return `${parts[0]}.${parts[1]}.${parts[2]}-build.${parts[3]}`;
}
return version;
}
/* /*
* sonarqube-scan-action * sonarqube-scan-action
* Copyright (C) 2025 SonarSource SA * Copyright (C) 2025 SonarSource SA
@@ -4140,12 +4160,14 @@ const TOOLNAME = "sonar-scanner-cli";
async function installSonarScanner({ async function installSonarScanner({
scannerVersion, scannerVersion,
scannerBinariesUrl, scannerBinariesUrl,
scannerBinariesAuthHeader,
skipSignatureVerification = false, skipSignatureVerification = false,
}) { }) {
const flavor = getPlatformFlavor(os$1.platform(), os$1.arch()); const flavor = getPlatformFlavor(os$1.platform(), os$1.arch());
const semVerVersion = toSemVer(scannerVersion);
// Check if tool is already cached // Check if tool is already cached
let toolDir = find(TOOLNAME, scannerVersion, flavor); let toolDir = find(TOOLNAME, semVerVersion, flavor);
if (!toolDir) { if (!toolDir) {
info( info(
@@ -4160,7 +4182,7 @@ async function installSonarScanner({
info(`Downloading from: ${downloadUrl}`); info(`Downloading from: ${downloadUrl}`);
const downloadPath = await downloadTool(downloadUrl); const downloadPath = await downloadTool(downloadUrl, undefined, scannerBinariesAuthHeader);
if (skipSignatureVerification) { if (skipSignatureVerification) {
warning("⚠ Skipping GPG signature verification (not recommended)"); warning("⚠ Skipping GPG signature verification (not recommended)");
@@ -4170,7 +4192,7 @@ async function installSonarScanner({
let signaturePath; let signaturePath;
try { try {
signaturePath = await downloadTool(signatureUrl); signaturePath = await downloadTool(signatureUrl, undefined, scannerBinariesAuthHeader);
} catch (error) { } catch (error) {
throw new Error( throw new Error(
`Failed to download signature file from ${signatureUrl}: ${error.message}` `Failed to download signature file from ${signatureUrl}: ${error.message}`
@@ -4188,7 +4210,7 @@ async function installSonarScanner({
scannerDirName(scannerVersion, flavor) scannerDirName(scannerVersion, flavor)
); );
toolDir = await cacheDir(scannerPath, TOOLNAME, scannerVersion, flavor); toolDir = await cacheDir(scannerPath, TOOLNAME, semVerVersion, flavor);
info(`Sonar Scanner CLI cached to: ${toolDir}`); info(`Sonar Scanner CLI cached to: ${toolDir}`);
} else { } else {
@@ -4489,10 +4511,14 @@ function getInputs() {
const args = getInput("args"); const args = getInput("args");
const projectBaseDir = getInput("projectBaseDir"); const projectBaseDir = getInput("projectBaseDir");
const scannerBinariesUrl = getInput("scannerBinariesUrl"); const scannerBinariesUrl = getInput("scannerBinariesUrl");
const scannerBinariesAuthHeader = getInput("scannerBinariesAuthHeader") || undefined;
if (scannerBinariesAuthHeader) {
setSecret(scannerBinariesAuthHeader);
}
const scannerVersion = getInput("scannerVersion"); const scannerVersion = getInput("scannerVersion");
const skipSignatureVerification = getBooleanInput("skipSignatureVerification"); const skipSignatureVerification = getBooleanInput("skipSignatureVerification");
return { args, projectBaseDir, scannerBinariesUrl, scannerVersion, skipSignatureVerification }; return { args, projectBaseDir, scannerBinariesUrl, scannerBinariesAuthHeader, scannerVersion, skipSignatureVerification };
} }
/** /**
@@ -4528,16 +4554,25 @@ function runSanityChecks(inputs) {
async function run() { async function run() {
try { try {
const { args, projectBaseDir, scannerVersion, scannerBinariesUrl, skipSignatureVerification } = const { args, projectBaseDir, scannerVersion, scannerBinariesUrl, scannerBinariesAuthHeader, skipSignatureVerification } =
getInputs(); getInputs();
const runnerEnv = getEnvVariables(); const runnerEnv = getEnvVariables();
const { sonarToken } = runnerEnv; const { sonarToken, sonarcloudUrl } = runnerEnv;
if (sonarcloudUrl) {
warning(
"The SONARCLOUD_URL environment variable is deprecated and will be removed in a future version. " +
"Regular users should not set it; use SONAR_REGION=us for the US region. " +
"For advanced needs, pass -Dsonar.scanner.sonarcloudUrl and -Dsonar.scanner.apiBaseUrl via the args input."
);
}
runSanityChecks({ projectBaseDir, scannerVersion, sonarToken }); runSanityChecks({ projectBaseDir, scannerVersion, sonarToken });
const scannerDir = await installSonarScanner({ const scannerDir = await installSonarScanner({
scannerVersion, scannerVersion,
scannerBinariesUrl, scannerBinariesUrl,
scannerBinariesAuthHeader,
skipSignatureVerification, skipSignatureVerification,
}); });
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
[tools]
node = "24"
+153
View File
@@ -0,0 +1,153 @@
/*
* sonarqube-scan-action
* Copyright (C) 2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import assert from "node:assert/strict";
import { describe, it, mock } from "node:test";
function mockDependencies(t, { getInputFn, setSecretFn }) {
t.mock.module("@actions/core", {
namedExports: {
getInput: getInputFn,
getBooleanInput: mock.fn(() => false),
setSecret: setSecretFn,
setFailed: mock.fn(),
info: mock.fn(),
warning: mock.fn(),
},
});
t.mock.module("../install-sonar-scanner.js", {
namedExports: { installSonarScanner: mock.fn(async () => "/scanner") },
});
t.mock.module("../run-sonar-scanner.js", {
namedExports: { runSonarScanner: mock.fn(async () => {}) },
});
t.mock.module("../sanity-checks.js", {
namedExports: {
validateScannerVersion: mock.fn(),
checkSonarToken: mock.fn(),
checkMavenProject: mock.fn(),
checkGradleProject: mock.fn(),
},
});
}
describe("SONARCLOUD_URL deprecation", () => {
it("should warn when SONARCLOUD_URL is set", async (t) => {
const warningFn = mock.fn();
const getInputFn = mock.fn(() => "");
t.mock.module("@actions/core", {
namedExports: {
getInput: getInputFn,
getBooleanInput: mock.fn(() => false),
setSecret: mock.fn(),
setFailed: mock.fn(),
info: mock.fn(),
warning: warningFn,
},
});
t.mock.module("../install-sonar-scanner.js", {
namedExports: { installSonarScanner: mock.fn(async () => "/scanner") },
});
t.mock.module("../run-sonar-scanner.js", {
namedExports: { runSonarScanner: mock.fn(async () => {}) },
});
t.mock.module("../sanity-checks.js", {
namedExports: {
validateScannerVersion: mock.fn(),
checkSonarToken: mock.fn(),
checkMavenProject: mock.fn(),
checkGradleProject: mock.fn(),
},
});
process.env.SONARCLOUD_URL = "mirror.sonarcloud.io";
t.after(() => delete process.env.SONARCLOUD_URL);
await import("../index.js?test=deprecation-warning");
assert.equal(warningFn.mock.calls.length, 1);
assert.match(
warningFn.mock.calls[0].arguments[0],
/SONARCLOUD_URL.*deprecated/
);
});
it("should not warn when SONARCLOUD_URL is not set", async (t) => {
const warningFn = mock.fn();
const getInputFn = mock.fn(() => "");
t.mock.module("@actions/core", {
namedExports: {
getInput: getInputFn,
getBooleanInput: mock.fn(() => false),
setSecret: mock.fn(),
setFailed: mock.fn(),
info: mock.fn(),
warning: warningFn,
},
});
t.mock.module("../install-sonar-scanner.js", {
namedExports: { installSonarScanner: mock.fn(async () => "/scanner") },
});
t.mock.module("../run-sonar-scanner.js", {
namedExports: { runSonarScanner: mock.fn(async () => {}) },
});
t.mock.module("../sanity-checks.js", {
namedExports: {
validateScannerVersion: mock.fn(),
checkSonarToken: mock.fn(),
checkMavenProject: mock.fn(),
checkGradleProject: mock.fn(),
},
});
delete process.env.SONARCLOUD_URL;
await import("../index.js?test=no-deprecation-warning");
assert.equal(warningFn.mock.calls.length, 0);
});
});
describe("getInputs", () => {
it("should mask scannerBinariesAuthHeader using setSecret when provided", async (t) => {
const setSecretFn = mock.fn();
const getInputFn = mock.fn((name) => name === "scannerBinariesAuthHeader" ? "Bearer mytoken" : "");
mockDependencies(t, { getInputFn, setSecretFn });
await import("../index.js?test=set-secret");
assert.equal(setSecretFn.mock.calls.length, 1);
assert.equal(setSecretFn.mock.calls[0].arguments[0], "Bearer mytoken");
});
it("should not call setSecret when scannerBinariesAuthHeader is not provided", async (t) => {
const setSecretFn = mock.fn();
const getInputFn = mock.fn(() => "");
mockDependencies(t, { getInputFn, setSecretFn });
await import("../index.js?test=no-set-secret");
assert.equal(setSecretFn.mock.calls.length, 0);
});
});
@@ -0,0 +1,253 @@
/*
* sonarqube-scan-action
* Copyright (C) 2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import assert from "node:assert/strict";
import { describe, it, mock } from "node:test";
const SCANNER_VERSION = "6.2.0.4584";
const SCANNER_SEMVER_VERSION = "6.2.0-build.4584";
const BINARIES_URL = "https://my.artifactory.example.com/sonar-scanner-cli";
const BINARY_DOWNLOAD_URL = `${BINARIES_URL}/sonar-scanner-cli-${SCANNER_VERSION}-linux-x64.zip`;
function mockUtils(t) {
t.mock.module("../utils.js", {
namedExports: {
getPlatformFlavor: mock.fn(() => "linux-x64"),
getScannerDownloadURL: mock.fn(() => BINARY_DOWNLOAD_URL),
scannerDirName: mock.fn(() => `sonar-scanner-${SCANNER_VERSION}-linux-x64`),
toSemVer: mock.fn(() => SCANNER_SEMVER_VERSION),
},
});
}
describe("installSonarScanner", () => {
it("should forward scannerBinariesAuthHeader to both binary and signature downloads", async (t) => {
const downloadCalls = [];
const downloadToolFn = mock.fn(async (url, dest, auth) => {
downloadCalls.push({ url, auth });
return `/tmp/downloaded-${downloadCalls.length}`;
});
mockUtils(t);
t.mock.module("@actions/tool-cache", {
namedExports: {
find: mock.fn(() => null),
downloadTool: downloadToolFn,
extractZip: mock.fn(async () => "/tmp/extracted"),
cacheDir: mock.fn(async () => "/tmp/cached"),
},
});
t.mock.module("@actions/core", {
namedExports: {
info: mock.fn(),
warning: mock.fn(),
addPath: mock.fn(),
},
});
t.mock.module("../gpg-verification.js", {
namedExports: {
verifySignature: mock.fn(async () => {}),
},
});
const { installSonarScanner } = await import(
`../install-sonar-scanner.js?test=auth-header`
);
await installSonarScanner({
scannerVersion: SCANNER_VERSION,
scannerBinariesUrl: BINARIES_URL,
scannerBinariesAuthHeader: "Bearer mytoken",
});
assert.equal(downloadCalls.length, 2, "Should download binary and signature");
assert.equal(downloadCalls[0].auth, "Bearer mytoken", "Binary download should use auth header");
assert.equal(downloadCalls[1].auth, "Bearer mytoken", "Signature download should use auth header");
assert.ok(downloadCalls[1].url.endsWith(".asc"), "Second download should be the signature");
});
it("should not set auth header when scannerBinariesAuthHeader is not provided", async (t) => {
const downloadCalls = [];
const downloadToolFn = mock.fn(async (url, dest, auth) => {
downloadCalls.push({ url, auth });
return `/tmp/downloaded-${downloadCalls.length}`;
});
mockUtils(t);
t.mock.module("@actions/tool-cache", {
namedExports: {
find: mock.fn(() => null),
downloadTool: downloadToolFn,
extractZip: mock.fn(async () => "/tmp/extracted"),
cacheDir: mock.fn(async () => "/tmp/cached"),
},
});
t.mock.module("@actions/core", {
namedExports: {
info: mock.fn(),
warning: mock.fn(),
addPath: mock.fn(),
},
});
t.mock.module("../gpg-verification.js", {
namedExports: {
verifySignature: mock.fn(async () => {}),
},
});
const { installSonarScanner } = await import(
`../install-sonar-scanner.js?test=no-auth-header`
);
await installSonarScanner({
scannerVersion: SCANNER_VERSION,
scannerBinariesUrl: BINARIES_URL,
});
assert.equal(downloadCalls.length, 2);
assert.equal(downloadCalls[0].auth, undefined, "Binary download should have no auth header");
assert.equal(downloadCalls[1].auth, undefined, "Signature download should have no auth header");
});
it("should skip signature download when skipSignatureVerification is true", async (t) => {
const downloadCalls = [];
const downloadToolFn = mock.fn(async (url, dest, auth) => {
downloadCalls.push({ url, auth });
return `/tmp/downloaded-${downloadCalls.length}`;
});
mockUtils(t);
t.mock.module("@actions/tool-cache", {
namedExports: {
find: mock.fn(() => null),
downloadTool: downloadToolFn,
extractZip: mock.fn(async () => "/tmp/extracted"),
cacheDir: mock.fn(async () => "/tmp/cached"),
},
});
t.mock.module("@actions/core", {
namedExports: {
info: mock.fn(),
warning: mock.fn(),
addPath: mock.fn(),
},
});
const { installSonarScanner } = await import(
`../install-sonar-scanner.js?test=skip-sig`
);
await installSonarScanner({
scannerVersion: SCANNER_VERSION,
scannerBinariesUrl: BINARIES_URL,
scannerBinariesAuthHeader: "Bearer mytoken",
skipSignatureVerification: true,
});
assert.equal(downloadCalls.length, 1, "Should only download binary, not signature");
assert.equal(downloadCalls[0].auth, "Bearer mytoken");
});
it("should use semver-compatible version for tool-cache find and cacheDir", async (t) => {
const findFn = mock.fn(() => null);
const cacheDirFn = mock.fn(async () => "/tmp/cached");
mockUtils(t);
t.mock.module("@actions/tool-cache", {
namedExports: {
find: findFn,
downloadTool: mock.fn(async () => "/tmp/downloaded"),
extractZip: mock.fn(async () => "/tmp/extracted"),
cacheDir: cacheDirFn,
},
});
t.mock.module("@actions/core", {
namedExports: {
info: mock.fn(),
warning: mock.fn(),
addPath: mock.fn(),
},
});
t.mock.module("../gpg-verification.js", {
namedExports: {
verifySignature: mock.fn(async () => {}),
},
});
const { installSonarScanner } = await import(
`../install-sonar-scanner.js?test=semver-version`
);
await installSonarScanner({
scannerVersion: SCANNER_VERSION,
scannerBinariesUrl: BINARIES_URL,
});
assert.equal(findFn.mock.calls[0].arguments[1], SCANNER_SEMVER_VERSION,
"tc.find should be called with semver-compatible version");
assert.equal(cacheDirFn.mock.calls[0].arguments[2], SCANNER_SEMVER_VERSION,
"tc.cacheDir should be called with semver-compatible version");
});
it("should use cached tool when available and skip download", async (t) => {
const downloadToolFn = mock.fn();
mockUtils(t);
t.mock.module("@actions/tool-cache", {
namedExports: {
find: mock.fn(() => "/tmp/cached-tool"),
downloadTool: downloadToolFn,
extractZip: mock.fn(),
cacheDir: mock.fn(),
},
});
t.mock.module("@actions/core", {
namedExports: {
info: mock.fn(),
warning: mock.fn(),
addPath: mock.fn(),
},
});
const { installSonarScanner } = await import(
`../install-sonar-scanner.js?test=cached`
);
await installSonarScanner({
scannerVersion: SCANNER_VERSION,
scannerBinariesUrl: BINARIES_URL,
});
assert.equal(downloadToolFn.mock.calls.length, 0, "Should not download when cached");
});
});
+20
View File
@@ -22,6 +22,7 @@ import {
getPlatformFlavor, getPlatformFlavor,
getScannerDownloadURL, getScannerDownloadURL,
scannerDirName, scannerDirName,
toSemVer,
} from "../utils.js"; } from "../utils.js";
describe("getPlatformFlavor", () => { describe("getPlatformFlavor", () => {
@@ -97,3 +98,22 @@ describe("scannerDirName", () => {
); );
}); });
}); });
describe("toSemVer", () => {
it("converts 4-part version to semver pre-release format", () => {
assert.equal(toSemVer("8.0.1.6346"), "8.0.1-build.6346");
});
it("leaves 3-part semver version unchanged", () => {
assert.equal(toSemVer("8.0.1"), "8.0.1");
});
it("leaves version with pre-release identifier unchanged", () => {
assert.equal(toSemVer("7.2.0-SNAPSHOT"), "7.2.0-SNAPSHOT");
});
it("converts different 4-part versions correctly", () => {
assert.equal(toSemVer("6.2.0.4584"), "6.2.0-build.4584");
assert.equal(toSemVer("8.1.0.6389"), "8.1.0-build.6389");
});
});
+19 -6
View File
@@ -17,14 +17,14 @@
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import * as core from "@actions/core"; import * as core from "@actions/core";
import { installSonarScanner } from "./install-sonar-scanner"; import { installSonarScanner } from "./install-sonar-scanner.js";
import { runSonarScanner } from "./run-sonar-scanner"; import { runSonarScanner } from "./run-sonar-scanner.js";
import { import {
checkGradleProject, checkGradleProject,
checkMavenProject, checkMavenProject,
checkSonarToken, checkSonarToken,
validateScannerVersion, validateScannerVersion,
} from "./sanity-checks"; } from "./sanity-checks.js";
/** /**
* Inputs are defined in action.yml * Inputs are defined in action.yml
@@ -33,10 +33,14 @@ function getInputs() {
const args = core.getInput("args"); const args = core.getInput("args");
const projectBaseDir = core.getInput("projectBaseDir"); const projectBaseDir = core.getInput("projectBaseDir");
const scannerBinariesUrl = core.getInput("scannerBinariesUrl"); const scannerBinariesUrl = core.getInput("scannerBinariesUrl");
const scannerBinariesAuthHeader = core.getInput("scannerBinariesAuthHeader") || undefined;
if (scannerBinariesAuthHeader) {
core.setSecret(scannerBinariesAuthHeader);
}
const scannerVersion = core.getInput("scannerVersion"); const scannerVersion = core.getInput("scannerVersion");
const skipSignatureVerification = core.getBooleanInput("skipSignatureVerification"); const skipSignatureVerification = core.getBooleanInput("skipSignatureVerification");
return { args, projectBaseDir, scannerBinariesUrl, scannerVersion, skipSignatureVerification }; return { args, projectBaseDir, scannerBinariesUrl, scannerBinariesAuthHeader, scannerVersion, skipSignatureVerification };
} }
/** /**
@@ -72,16 +76,25 @@ function runSanityChecks(inputs) {
async function run() { async function run() {
try { try {
const { args, projectBaseDir, scannerVersion, scannerBinariesUrl, skipSignatureVerification } = const { args, projectBaseDir, scannerVersion, scannerBinariesUrl, scannerBinariesAuthHeader, skipSignatureVerification } =
getInputs(); getInputs();
const runnerEnv = getEnvVariables(); const runnerEnv = getEnvVariables();
const { sonarToken } = runnerEnv; const { sonarToken, sonarcloudUrl } = runnerEnv;
if (sonarcloudUrl) {
core.warning(
"The SONARCLOUD_URL environment variable is deprecated and will be removed in a future version. " +
"Regular users should not set it; use SONAR_REGION=us for the US region. " +
"For advanced needs, pass -Dsonar.scanner.sonarcloudUrl and -Dsonar.scanner.apiBaseUrl via the args input."
);
}
runSanityChecks({ projectBaseDir, scannerVersion, sonarToken }); runSanityChecks({ projectBaseDir, scannerVersion, sonarToken });
const scannerDir = await installSonarScanner({ const scannerDir = await installSonarScanner({
scannerVersion, scannerVersion,
scannerBinariesUrl, scannerBinariesUrl,
scannerBinariesAuthHeader,
skipSignatureVerification, skipSignatureVerification,
}); });
+9 -6
View File
@@ -24,8 +24,9 @@ import {
getPlatformFlavor, getPlatformFlavor,
getScannerDownloadURL, getScannerDownloadURL,
scannerDirName, scannerDirName,
} from "./utils"; toSemVer,
import { verifySignature } from "./gpg-verification"; } from "./utils.js";
import { verifySignature } from "./gpg-verification.js";
const TOOLNAME = "sonar-scanner-cli"; const TOOLNAME = "sonar-scanner-cli";
@@ -35,12 +36,14 @@ const TOOLNAME = "sonar-scanner-cli";
export async function installSonarScanner({ export async function installSonarScanner({
scannerVersion, scannerVersion,
scannerBinariesUrl, scannerBinariesUrl,
scannerBinariesAuthHeader,
skipSignatureVerification = false, skipSignatureVerification = false,
}) { }) {
const flavor = getPlatformFlavor(os.platform(), os.arch()); const flavor = getPlatformFlavor(os.platform(), os.arch());
const semVerVersion = toSemVer(scannerVersion);
// Check if tool is already cached // Check if tool is already cached
let toolDir = tc.find(TOOLNAME, scannerVersion, flavor); let toolDir = tc.find(TOOLNAME, semVerVersion, flavor);
if (!toolDir) { if (!toolDir) {
core.info( core.info(
@@ -55,7 +58,7 @@ export async function installSonarScanner({
core.info(`Downloading from: ${downloadUrl}`); core.info(`Downloading from: ${downloadUrl}`);
const downloadPath = await tc.downloadTool(downloadUrl); const downloadPath = await tc.downloadTool(downloadUrl, undefined, scannerBinariesAuthHeader);
if (skipSignatureVerification) { if (skipSignatureVerification) {
core.warning("⚠ Skipping GPG signature verification (not recommended)"); core.warning("⚠ Skipping GPG signature verification (not recommended)");
@@ -65,7 +68,7 @@ export async function installSonarScanner({
let signaturePath; let signaturePath;
try { try {
signaturePath = await tc.downloadTool(signatureUrl); signaturePath = await tc.downloadTool(signatureUrl, undefined, scannerBinariesAuthHeader);
} catch (error) { } catch (error) {
throw new Error( throw new Error(
`Failed to download signature file from ${signatureUrl}: ${error.message}` `Failed to download signature file from ${signatureUrl}: ${error.message}`
@@ -83,7 +86,7 @@ export async function installSonarScanner({
scannerDirName(scannerVersion, flavor) scannerDirName(scannerVersion, flavor)
); );
toolDir = await tc.cacheDir(scannerPath, TOOLNAME, scannerVersion, flavor); toolDir = await tc.cacheDir(scannerPath, TOOLNAME, semVerVersion, flavor);
core.info(`Sonar Scanner CLI cached to: ${toolDir}`); core.info(`Sonar Scanner CLI cached to: ${toolDir}`);
} else { } else {
+13
View File
@@ -51,3 +51,16 @@ export function getScannerDownloadURL({
export const scannerDirName = (version, flavor) => export const scannerDirName = (version, flavor) =>
`sonar-scanner-${version}-${flavor}`; `sonar-scanner-${version}-${flavor}`;
/**
* Converts a 4-part version string (e.g. "8.0.1.6346") to a SemVer 2.0 compatible
* string (e.g. "8.0.1-build.6346") for use with GitHub's tool-cache library,
* which requires SemVer-compliant version strings.
*/
export function toSemVer(version) {
const parts = version.split(".");
if (parts.length === 4) {
return `${parts[0]}.${parts[1]}.${parts[2]}-build.${parts[3]}`;
}
return version;
}