1- # SonarCloud PR Workflow for django-ansible-base
1+ # SonarCloud Analysis Workflow for django-ansible-base
22#
3- # This workflow runs on every pull request and push to the repository.
3+ # This workflow runs SonarCloud analysis triggered by CI workflow completion.
4+ # It is split into two separate jobs for clarity and maintainability:
45#
5- # Steps overview:
6- # 1. Download coverage data from CI workflow
7- # 2. Check for backport labels (skip if found)
8- # 3. Check for Python file changes (skip if none)
9- # 4. Extract PR info and set environment
10- # 5. Run SonarCloud analysis with quality gate
6+ # FLOW: CI completes → workflow_run triggers this workflow → appropriate job runs
117#
12- # What files are scanned:
13- # - All Python files (.py) in the repository
14- # - Excludes: tests, scripts, dev environments, external collections
15- # - Quality gate focuses on new/changed code in PR only
8+ # JOB 1: sonar-pr-analysis (for PRs)
9+ # - Triggered by: workflow_run (CI on pull_request)
10+ # - Steps: Download coverage → Get PR info → Run SonarCloud PR analysis
11+ # - Scans: All files in the PR
12+ # - Quality gate: Focuses on new/changed code in PR only
13+ #
14+ # JOB 2: sonar-branch-analysis (for long-lived branches)
15+ # - Triggered by: workflow_run (CI on push to devel/stable-*)
16+ # - Steps: Download coverage → Run SonarCloud branch analysis
17+ # - Scans: Full codebase
18+ # - Quality gate: Focuses on overall project health
19+ #
20+ # This ensures coverage data is always available from CI before analysis runs.
1621
1722
1823# With much help from:
1924# https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/30
2025# https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/32
2126name : SonarCloud
2227on :
23- workflow_run :
28+ workflow_run : # This is triggered by CI being completed.
2429 workflows :
2530 - CI
2631 types :
2732 - completed
28- workflow_dispatch :
29- inputs :
30- pr_number :
31- description : ' PR number (for push events, use 0)'
32- required : true
33- type : string
34- event_type :
35- description : ' Event type (push or pull_request)'
36- required : true
37- type : string
38- commit_sha :
39- description : ' Commit SHA'
40- required : true
41- type : string
42- repository :
43- description : ' Repository name'
44- required : true
45- type : string
46- ci_run_id :
47- description : ' CI workflow run ID (for downloading coverage artifact)'
48- required : false
49- type : string
5033permissions : read-all
5134jobs :
52- sonar :
53- name : Upload to SonarCloud
35+ sonar-pr-analysis :
36+ name : SonarCloud PR Analysis
5437 runs-on : ubuntu-latest
5538 if : |
56- (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request') ||
57- (github.event_name == 'workflow_dispatch')
39+ github.event.workflow_run.conclusion == 'success' &&
40+ github.event.workflow_run.event == 'pull_request' &&
41+ startsWith(github.repository, 'ansible') && endsWith(github.repository, 'django-ansible-base')
5842 steps :
59- - uses : actions/checkout@v3
43+ - uses : actions/checkout@v4
44+ with :
45+ show-progress : false
6046
61- # Download coverage artifact (for both workflow_run and workflow_dispatch)
47+ # Download coverage artifact from CI workflow
6248 - name : Download coverage artifact
63- if : |
64- github.event_name == 'workflow_run' ||
65- (github.event_name == 'workflow_dispatch' && inputs.ci_run_id != '')
6649 uses : dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615
6750 with :
6851 github_token : ${{ secrets.GITHUB_TOKEN }}
6952 workflow : CI
70- run_id : ${{ github.event_name == 'workflow_run' && github. event.workflow_run.id || inputs.ci_run_id }}
53+ run_id : ${{ github.event.workflow_run.id }}
7154 name : coverage
7255
73- # Set PR number based on trigger type
74- - name : Set PR number and event type
75- run : |
76- if [ "${{ github.event_name }}" = "workflow_run" ]; then
77- # Extract from coverage.xml for workflow_run
78- echo "PR_NUMBER=$(grep -m 1 '<!-- PR' coverage.xml | awk '{print $3}')" >> $GITHUB_ENV
79- echo "EVENT_TYPE=pull_request" >> $GITHUB_ENV
80- echo "COMMIT_SHA=${{ github.event.workflow_run.head_sha }}" >> $GITHUB_ENV
81- echo "REPO_NAME=${{ github.event.repository.full_name }}" >> $GITHUB_ENV
82- else
83- # Use inputs for workflow_dispatch
84- echo "PR_NUMBER=${{ inputs.pr_number }}" >> $GITHUB_ENV
85- echo "EVENT_TYPE=${{ inputs.event_type }}" >> $GITHUB_ENV
86- echo "COMMIT_SHA=${{ inputs.commit_sha }}" >> $GITHUB_ENV
87- echo "REPO_NAME=${{ inputs.repository }}" >> $GITHUB_ENV
88- fi
89-
90- - name : Get PR info
91- if : env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0'
92- 93- id : pr_info
94- with :
95- route : GET /repos/{repo}/pulls/{number}
96- repo : ${{ env.REPO_NAME }}
97- number : ${{ env.PR_NUMBER }}
56+ # Extract PR metadata from workflow_run event
57+ - name : Set PR metadata and prepare for analysis
9858 env :
59+ COMMIT_SHA : ${{ github.event.workflow_run.head_sha }}
60+ REPO_NAME : ${{ github.event.repository.full_name }}
61+ HEAD_BRANCH : ${{ github.event.workflow_run.head_branch }}
9962 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
100-
101- - name : Print SonarCloud Analysis Decision Summary
102- id : print_summary
10363 run : |
10464 echo "🔍 SonarCloud Analysis Decision Summary"
10565 echo "========================================"
106-
107- # Check CI Event type
108- if [ "${{ env.EVENT_TYPE }}" = "pull_request" ]; then
109- echo "├── CI Event: ✅ Pull Request"
110-
111- # Check backport labels (only for PRs)
112- if [ "${{ env.PR_NUMBER }}" != "0" ]; then
113- echo "├── Backport Label: ➖ N/A (Skip check for workflow_dispatch)"
114-
115- # Check Python changes for PRs
116- files=$(gh api repos/${{ env.REPO_NAME }}/pulls/${{ env.PR_NUMBER }}/files --jq '.[].filename')
117-
118- # Get file extensions for summary
119- extensions=$(echo "$files" | sed 's/.*\.//' | sort | uniq | tr '\n' ',' | sed 's/,$//')
120-
121- # Check if any Python files were changed
122- python_files=$(echo "$files" | grep '\.py$' || true)
123- if [ -z "$python_files" ]; then
124- echo "├── Python Changes: ❌ None (.$extensions only)"
125- echo "└── Result: ⏭️ Skip - \"No Python code changes detected\""
126- exit 0
127- else
128- python_count=$(echo "$python_files" | wc -l)
129- echo "├── Python Changes: ✅ Found ($python_count files)"
130- echo "└── Result: ✅ Proceed - \"Running SonarCloud analysis\""
131- fi
132- else
133- echo "├── Backport Label: ➖ N/A (Not a PR)"
134- echo "└── Result: ✅ Proceed - \"Running SonarCloud analysis\""
135- fi
66+ echo "├── CI Event: ✅ Pull Request"
67+ echo "├── Repo: $REPO_NAME"
68+
69+ # Extract PR number from coverage.xml
70+ if [ -f coverage.xml ]; then
71+ PR_NUMBER=$(grep -m 1 '<!-- PR' coverage.xml | awk '{print $3}' || echo "")
13672 else
137- # For push events, always proceed
138- echo "├── CI Event: ✅ Push"
139- echo "├── Backport Label: ➖ N/A (Push event)"
140- echo "├── Python Changes: ➖ N/A (Full codebase scan)"
141- echo "└── Result: ✅ Proceed - \"Running SonarCloud analysis\""
73+ PR_NUMBER=""
14274 fi
143- env :
144- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
14575
146- - name : Check if analysis should proceed
147- id : check_proceed
148- run : |
149- # This step only runs if we got past the summary step
150- # which means all conditions are met for analysis
151- echo "proceed=true" >> $GITHUB_OUTPUT
152- echo "✅ All conditions met - proceeding with SonarCloud analysis"
153-
154- - name : Set PR info into env
155- if : steps.check_proceed.outputs.proceed == 'true' && env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0' && steps.pr_info.outputs.data != ''
156- run : |
157- echo "PR_BASE=${{ fromJson(steps.pr_info.outputs.data).base.ref }}" >> $GITHUB_ENV
158- echo "PR_HEAD=${{ fromJson(steps.pr_info.outputs.data).head.ref }}" >> $GITHUB_ENV
76+ echo "├── PR Number from coverage.xml: #${PR_NUMBER:-<not found>}"
15977
160- - name : Get changed Python files for Sonar
161- if : steps.check_proceed.outputs.proceed == 'true' && env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0'
162- run : |
163- files=$(gh api repos/${{ env.REPO_NAME }}/pulls/${{ env.PR_NUMBER }}/files --jq '.[].filename')
164- echo "All changed files in PR:"
165- echo "$files"
166- python_files=$(echo "$files" | grep '\.py$' || true)
167- if [ -n "$python_files" ]; then
168- echo "Changed Python files:"
169- echo "$python_files"
170- # Convert to comma-separated list for sonar.inclusions
171- inclusions=$(echo "$python_files" | tr '\n' ',' | sed 's/,$//')
172- echo "SONAR_INCLUSIONS=$inclusions" >> $GITHUB_ENV
173- echo "Will scan these Python files: $inclusions"
78+ if [ -z "$PR_NUMBER" ]; then
79+ echo "##[error]❌ FATAL: PR number not found in coverage.xml"
80+ echo "##[error]This job requires a PR number to run PR analysis."
81+ echo "##[error]The CI workflow should have injected the PR number into coverage.xml."
82+ exit 1
17483 fi
175- env :
176- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
17784
178- - name : Add base branch (for PRs)
179- if : steps.check_proceed.outputs.proceed == 'true' && env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0'
85+ # Get PR metadata from GitHub API
86+ PR_DATA=$(gh api "repos/$REPO_NAME/pulls/$PR_NUMBER")
87+ PR_BASE=$(echo "$PR_DATA" | jq -r '.base.ref')
88+ PR_HEAD=$(echo "$PR_DATA" | jq -r '.head.ref')
89+
90+ # Print summary
91+ echo "├── Base Branch: $PR_BASE"
92+ echo "├── Head Branch: $PR_HEAD"
93+ echo "├── Coverage Data: ✅ Available"
94+ echo "└── Result: ✅ Running SonarCloud PR analysis"
95+
96+ # Export to GitHub env for later steps
97+ echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
98+ echo "PR_BASE=$PR_BASE" >> $GITHUB_ENV
99+ echo "PR_HEAD=$PR_HEAD" >> $GITHUB_ENV
100+ echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_ENV
101+ echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
102+
103+ - name : Add base branch
104+ if : env.PR_NUMBER != ''
180105 run : |
181106 gh pr checkout ${{ env.PR_NUMBER }}
182107 env :
183108 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
184109
185- - name : SonarCloud Scan (Pull Request)
186- if : steps.check_proceed.outputs.proceed == 'true' && env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0'
187- uses : SonarSource/sonarqube-scan-action@v5
110+ - name : SonarCloud Scan
111+ uses : SonarSource/sonarcloud-github-action@e44258b109568baa0df60ed515909fc6c72cba92
188112 env :
189113 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
190- SONAR_TOKEN : ${{ secrets[format('{0}', vars.SONAR_TOKEN_SECRET_NAME)] }}
114+ SONAR_TOKEN : ${{ secrets.SONAR_TOKEN }}
191115 with :
192116 args : >
193117 -Dsonar.scm.revision=${{ env.COMMIT_SHA }}
194118 -Dsonar.pullrequest.key=${{ env.PR_NUMBER }}
195119 -Dsonar.pullrequest.branch=${{ env.PR_HEAD }}
196120 -Dsonar.pullrequest.base=${{ env.PR_BASE }}
197- ${{ env.SONAR_INCLUSIONS && format('-Dsonar.inclusions={0}', env.SONAR_INCLUSIONS) || '' }}
198121
199- - name : SonarCloud Scan (Push)
200- if : steps.check_proceed.outputs.proceed == 'true' && env.EVENT_TYPE == 'push'
201- uses : SonarSource/sonarqube-scan-action@v5
122+ sonar-branch-analysis :
123+ name : SonarCloud Branch Analysis
124+ runs-on : ubuntu-latest
125+ if : |
126+ github.event_name == 'workflow_run' &&
127+ github.event.workflow_run.conclusion == 'success' &&
128+ github.event.workflow_run.event == 'push' &&
129+ startsWith(github.repository, 'ansible') && endsWith(github.repository, 'django-ansible-base')
130+ steps :
131+ - uses : actions/checkout@v4
132+ with :
133+ show-progress : false
134+
135+ # Download coverage artifact from CI workflow (optional for branch pushes)
136+ - name : Download coverage artifact
137+ continue-on-error : true
138+ uses : dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615
139+ with :
140+ github_token : ${{ secrets.GITHUB_TOKEN }}
141+ workflow : CI
142+ run_id : ${{ github.event.workflow_run.id }}
143+ name : coverage
144+
145+ - name : Print SonarCloud Analysis Summary
146+ env :
147+ BRANCH_NAME : ${{ github.event.workflow_run.head_branch }}
148+ REPO_NAME : ${{ github.event.repository.full_name }}
149+ run : |
150+ echo "🔍 SonarCloud Analysis Summary"
151+ echo "=============================="
152+ echo "├── CI Event: ✅ Push (via workflow_run)"
153+ echo "├── Repo: $REPO_NAME"
154+ echo "├── Branch: $BRANCH_NAME"
155+
156+ if [ -f coverage.xml ]; then
157+ echo "├── Coverage Data: ✅ Available"
158+ else
159+ echo "├── Coverage Data: ⚠️ Not available (analysis will proceed without coverage)"
160+ fi
161+
162+ echo "└── Result: ✅ Running SonarCloud branch analysis"
163+
164+ - name : SonarCloud Scan
165+ uses : SonarSource/sonarcloud-github-action@e44258b109568baa0df60ed515909fc6c72cba92
202166 env :
203167 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
204- SONAR_TOKEN : ${{ secrets[format('{0}', vars.SONAR_TOKEN_SECRET_NAME)] }}
168+ SONAR_TOKEN : ${{ secrets.SONAR_TOKEN }}
205169 with :
206170 args : >
207- -Dsonar.scm.revision=${{ env.COMMIT_SHA }}
171+ -Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }}
172+ -Dsonar.branch.name=${{ github.event.workflow_run.head_branch }}
0 commit comments