sonar.yaml 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. name: Sonar
  2. # Privileged trigger: workflow_run is needed to report Sonar results for fork PRs. No PR code is executed; sonar.host.url is pinned on the CLI.
  3. on: # zizmor: ignore[dangerous-triggers]
  4. workflow_run:
  5. workflows: ["Build/Test"]
  6. types: [completed]
  7. permissions: {}
  8. jobs:
  9. sonar:
  10. name: Sonar
  11. runs-on: ubuntu-latest
  12. if: github.event.workflow_run.conclusion == 'success'
  13. permissions:
  14. statuses: write
  15. contents: read
  16. actions: read
  17. steps:
  18. - name: Set Quality Gate status (pending)
  19. if: always()
  20. uses: guibranco/github-status-action-v2@77639353504055053524efa7a3719aaf0b731ce9 # v1.2.4
  21. with:
  22. authToken: ${{ secrets.GITHUB_TOKEN }}
  23. state: 'pending'
  24. context: 'Quality Gate'
  25. description: 'Quality Gate check in progress...'
  26. sha: ${{ github.event.workflow_run.head_sha }}
  27. target_url: 'https://sonarcloud.io/dashboard?id=opencost_opencost'
  28. - name: Checkout upstream repository
  29. uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
  30. with:
  31. repository: opencost/opencost
  32. ref: develop
  33. fetch-depth: 0
  34. persist-credentials: false
  35. - name: Fetch PR branch from fork
  36. if: github.event.workflow_run.head_branch != 'develop' || github.event.workflow_run.head_repository.full_name != 'opencost/opencost'
  37. env:
  38. HEAD_REPO_FULL_NAME: ${{ github.event.workflow_run.head_repository.full_name }}
  39. HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
  40. HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
  41. run: |
  42. git remote add fork "https://github.com/$HEAD_REPO_FULL_NAME.git"
  43. if [ "$HEAD_BRANCH" = "develop" ]; then
  44. # For fork's develop branch, fetch and checkout by SHA to avoid refspec conflict
  45. git fetch fork "$HEAD_BRANCH"
  46. git checkout "$HEAD_SHA"
  47. else
  48. # For feature branches, fetch and checkout normally
  49. git fetch fork "$HEAD_BRANCH:$HEAD_BRANCH"
  50. git checkout "$HEAD_BRANCH"
  51. fi
  52. - name: Checkout develop branch at specific SHA
  53. if: github.event.workflow_run.head_branch == 'develop' && github.event.workflow_run.head_repository.full_name == 'opencost/opencost'
  54. env:
  55. HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
  56. run: |
  57. git checkout "$HEAD_SHA"
  58. - name: Download coverage artifacts
  59. uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
  60. with:
  61. name: code-coverage
  62. run-id: ${{ github.event.workflow_run.id }}
  63. github-token: ${{ github.token }}
  64. path: pr-artifact
  65. - name: Validate Coverage Vars
  66. id: validate-vars
  67. if: github.event.workflow_run.head_branch != 'develop'
  68. shell: bash
  69. run: |
  70. # check the PR number
  71. pr_content=$(cat pr-artifact/pr_num.txt | tr -d '\n' | tr -d ' ')
  72. # Check if the content matches a single number
  73. if [[ "$pr_content" =~ ^[0-9]+$ ]]; then
  74. echo "The file 'pr_num.txt' contains a single number: $pr_content"
  75. else
  76. echo "The file 'pr_num.txt' does not contain a single number."
  77. exit 1
  78. fi
  79. base_content=$(cat pr-artifact/base.txt | tr -d '\n' | tr -d ' ')
  80. if git check-ref-format --allow-onelevel "$base_content"; then
  81. echo "The file 'base.txt' contains a valid git ref: $base_content"
  82. else
  83. echo "The file 'base.txt' does not contain a valid git ref: $base_content"
  84. exit 1
  85. fi
  86. head_content=$(cat pr-artifact/head.txt | tr -d '\n' | tr -d ' ')
  87. if git check-ref-format --allow-onelevel "$head_content"; then
  88. echo "The file 'head.txt' contains a valid git ref: $head_content"
  89. else
  90. echo "The file 'head.txt' does not contain a valid git ref: $head_content"
  91. exit 1
  92. fi
  93. - name: set vars
  94. id: set-vars
  95. run: |
  96. echo "SONAR_PR_NUM=$(cat pr-artifact/pr_num.txt | tr -d '\n' | tr -d ' ')" >> $GITHUB_OUTPUT
  97. echo "SONAR_BASE=$(cat pr-artifact/base.txt | tr -d '\n' | tr -d ' ')" >> $GITHUB_OUTPUT
  98. echo "SONAR_HEAD=$(cat pr-artifact/head.txt | tr -d '\n' | tr -d ' ')" >> $GITHUB_OUTPUT
  99. # move coverage file to root where sonar properties file is expecting it
  100. cp pr-artifact/coverage.out coverage.out
  101. # on develop branch, only run a baseline scan
  102. # The checked-out tree may come from a fork PR. sonar.host.url is set on the
  103. # command line (which overrides sonar-project.properties) so a PR cannot
  104. # redirect SONAR_TOKEN to another server.
  105. - name: SonarCloud Scan (Baseline)
  106. uses: SonarSource/sonarqube-scan-action@ba9859eae8dd6bd29e412f25ddbbef3d032000f4 # v8.2.2
  107. if: github.event.workflow_run.head_branch == 'develop'
  108. env:
  109. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  110. SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
  111. with:
  112. args: >
  113. -Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }}
  114. -Dsonar.host.url=https://sonarcloud.io
  115. -Dsonar.projectKey=opencost_opencost
  116. -Dsonar.organization=opencost
  117. -Dsonar.branch.name=develop
  118. - name: SonarCloud Scan (PR)
  119. uses: SonarSource/sonarqube-scan-action@ba9859eae8dd6bd29e412f25ddbbef3d032000f4 # v8.2.2
  120. if: github.event.workflow_run.head_branch != 'develop'
  121. env:
  122. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  123. SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
  124. with:
  125. args: >
  126. -Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }}
  127. -Dsonar.pullrequest.key=${{ steps.set-vars.outputs.SONAR_PR_NUM }}
  128. -Dsonar.pullrequest.branch=${{ steps.set-vars.outputs.SONAR_HEAD }}
  129. -Dsonar.pullrequest.base=${{ steps.set-vars.outputs.SONAR_BASE }}
  130. -Dsonar.host.url=https://sonarcloud.io
  131. -Dsonar.projectKey=opencost_opencost
  132. -Dsonar.organization=opencost
  133. - name: SonarQube Quality Gate check
  134. id: sonarqube-quality-gate-check
  135. continue-on-error: true
  136. uses: sonarsource/sonarqube-quality-gate-action@7a5fffe8e523c40e0c740b6bc2712ab503e52efa # v1.2.1
  137. # fail step after specific time.
  138. timeout-minutes: 5
  139. env:
  140. SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
  141. SONAR_HOST_URL: "https://sonarcloud.io"
  142. - name: Set Quality Gate status (failed - PR)
  143. id: fail-quality-gate-pr
  144. if: steps.sonarqube-quality-gate-check.outputs.quality-gate-status != 'PASSED' && steps.set-vars.outputs.SONAR_PR_NUM != ''
  145. uses: guibranco/github-status-action-v2@77639353504055053524efa7a3719aaf0b731ce9 # v1.2.4
  146. with:
  147. authToken: ${{ secrets.GITHUB_TOKEN }}
  148. state: 'failure'
  149. context: 'Quality Gate'
  150. description: 'Quality Gate failed. Check the SonarCloud Dashboard for PR #${{ steps.set-vars.outputs.SONAR_PR_NUM }}'
  151. sha: ${{ github.event.workflow_run.head_sha }}
  152. target_url: 'https://sonarcloud.io/dashboard?id=opencost_opencost&pullRequest=${{ steps.set-vars.outputs.SONAR_PR_NUM }}'
  153. - name: Set Quality Gate status (failed - develop)
  154. id: fail-quality-gate-develop
  155. if: steps.sonarqube-quality-gate-check.outputs.quality-gate-status != 'PASSED' && steps.set-vars.outputs.SONAR_PR_NUM == ''
  156. uses: guibranco/github-status-action-v2@77639353504055053524efa7a3719aaf0b731ce9 # v1.2.4
  157. with:
  158. authToken: ${{ secrets.GITHUB_TOKEN }}
  159. state: 'failure'
  160. context: 'Quality Gate'
  161. description: 'Quality Gate failed. Check the SonarCloud Dashboard for more details.'
  162. sha: ${{ github.event.workflow_run.head_sha }}
  163. target_url: 'https://sonarcloud.io/dashboard?id=opencost_opencost'
  164. - name: Set Quality Gate status (passed - PR)
  165. id: pass-quality-gate-pr
  166. if: steps.sonarqube-quality-gate-check.outputs.quality-gate-status == 'PASSED' && steps.set-vars.outputs.SONAR_PR_NUM != ''
  167. uses: guibranco/github-status-action-v2@77639353504055053524efa7a3719aaf0b731ce9 # v1.2.4
  168. with:
  169. authToken: ${{ secrets.GITHUB_TOKEN }}
  170. state: 'success'
  171. context: 'Quality Gate'
  172. description: 'Quality Gate passed. Check the SonarCloud Dashboard for PR #${{ steps.set-vars.outputs.SONAR_PR_NUM }}'
  173. sha: ${{ github.event.workflow_run.head_sha }}
  174. target_url: 'https://sonarcloud.io/dashboard?id=opencost_opencost&pullRequest=${{ steps.set-vars.outputs.SONAR_PR_NUM }}'
  175. - name: Set Quality Gate status (passed - develop)
  176. id: pass-quality-gate-develop
  177. if: steps.sonarqube-quality-gate-check.outputs.quality-gate-status == 'PASSED' && steps.set-vars.outputs.SONAR_PR_NUM == ''
  178. uses: guibranco/github-status-action-v2@77639353504055053524efa7a3719aaf0b731ce9 # v1.2.4
  179. with:
  180. authToken: ${{ secrets.GITHUB_TOKEN }}
  181. state: 'success'
  182. context: 'Quality Gate'
  183. description: 'Quality Gate passed. Check the SonarCloud Dashboard for more details.'
  184. sha: ${{ github.event.workflow_run.head_sha }}
  185. target_url: 'https://sonarcloud.io/dashboard?id=opencost_opencost'