1
+ # SonarCloud PR Workflow for django-ansible-base
2
+ #
3
+ # This workflow runs on every pull request and push to the repository.
4
+ #
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
11
+ #
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
16
+
17
+
1
18
# With much help from:
2
19
# https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/30
3
20
# https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/32
8
25
- CI
9
26
types :
10
27
- 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
50
+ permissions : read-all
11
51
jobs :
12
52
sonar :
13
53
name : Upload to SonarCloud
14
54
runs-on : ubuntu-latest
15
- if : github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
55
+ 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')
16
58
steps :
17
- - uses : actions/checkout@v4
18
- with :
19
- show-progress : false
59
+ - uses : actions/checkout@v3
20
60
21
- # Always use a full sha here to protect tokens, it's a third-party action
61
+ # Download coverage artifact (for both workflow_run and workflow_dispatch)
22
62
- name : Download coverage artifact
63
+ if : |
64
+ github.event_name == 'workflow_run' ||
65
+ (github.event_name == 'workflow_dispatch' && inputs.ci_run_id != '')
23
66
uses : dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615
24
67
with :
25
68
github_token : ${{ secrets.GITHUB_TOKEN }}
26
69
workflow : CI
27
- run_id : ${{ github.event.workflow_run.id }}
70
+ run_id : ${{ github.event_name == 'workflow_run' && github. event.workflow_run.id || inputs.ci_run_id }}
28
71
name : coverage
29
72
30
- - name : Extract PR number from coverage.xml
73
+ # Set PR number based on trigger type
74
+ - name : Set PR number and event type
31
75
run : |
32
- echo "PR_NUMBER=$(grep -m 1 '<!-- PR' coverage.xml | awk '{print $3}')" >> $GITHUB_ENV
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
33
89
34
90
- name : Get PR info
91
+ if : env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0'
35
92
36
93
id : pr_info
37
94
with :
38
95
route : GET /repos/{repo}/pulls/{number}
39
- repo : ${{ github.event.repository.full_name }}
96
+ repo : ${{ env.REPO_NAME }}
40
97
number : ${{ env.PR_NUMBER }}
41
98
env :
42
99
GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
43
100
101
+ - name : Print SonarCloud Analysis Decision Summary
102
+ id : print_summary
103
+ run : |
104
+ echo "🔍 SonarCloud Analysis Decision Summary"
105
+ 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
136
+ 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\""
142
+ fi
143
+ env :
144
+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
145
+
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
+
44
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 != ''
45
156
run : |
46
157
echo "PR_BASE=${{ fromJson(steps.pr_info.outputs.data).base.ref }}" >> $GITHUB_ENV
47
158
echo "PR_HEAD=${{ fromJson(steps.pr_info.outputs.data).head.ref }}" >> $GITHUB_ENV
48
159
49
- - name : Add base branch
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'
50
162
run : |
51
- gh pr checkout ${{ env.PR_NUMBER }}
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"
174
+ fi
52
175
env :
53
176
GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
54
177
55
- - name : ' Extract and export repo owner/name '
56
- shell : bash
178
+ - name : Add base branch (for PRs)
179
+ if : steps.check_proceed.outputs.proceed == 'true' && env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0'
57
180
run : |
58
- # 1. Read the full slug (owner/repo)
59
- REPO_SLUG="${GITHUB_REPOSITORY}"
60
- # 2. Split into owner and repo
61
- IFS="/" read -r REPO_OWNER REPO_NAME <<< "$REPO_SLUG"
62
- # 3. Export to the workflow environment
63
- echo "REPO_OWNER=$REPO_OWNER" >> $GITHUB_ENV
64
- echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
181
+ gh pr checkout ${{ env.PR_NUMBER }}
182
+ env :
183
+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
65
184
66
- - name : SonarQube Scan
185
+ - name : SonarCloud Scan (Pull Request)
186
+ if : steps.check_proceed.outputs.proceed == 'true' && env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0'
67
187
uses : SonarSource/sonarqube-scan-action@v5
68
188
env :
69
189
GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
70
- SONAR_TOKEN : ${{ secrets[format('{0}', vars.SONAR_TOKEN_SECRET_NAME)] }}
190
+ SONAR_TOKEN : ${{ secrets[format('{0}', vars.SONAR_TOKEN_SECRET_NAME)] }}
71
191
with :
72
192
args : >
73
- -Dsonar.projectKey=${{ env.REPO_OWNER }}_${{ env.REPO_NAME }}
74
- -Dsonar.organization=${{ env.REPO_OWNER }}
75
- -Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }}
193
+ -Dsonar.scm.revision=${{ env.COMMIT_SHA }}
76
194
-Dsonar.pullrequest.key=${{ env.PR_NUMBER }}
77
195
-Dsonar.pullrequest.branch=${{ env.PR_HEAD }}
78
196
-Dsonar.pullrequest.base=${{ env.PR_BASE }}
197
+ ${{ env.SONAR_INCLUSIONS && format('-Dsonar.inclusions={0}', env.SONAR_INCLUSIONS) || '' }}
198
+
199
+ - name : SonarCloud Scan (Push)
200
+ if : steps.check_proceed.outputs.proceed == 'true' && env.EVENT_TYPE == 'push'
201
+ uses : SonarSource/sonarqube-scan-action@v5
202
+ env :
203
+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
204
+ SONAR_TOKEN : ${{ secrets[format('{0}', vars.SONAR_TOKEN_SECRET_NAME)] }}
205
+ with :
206
+ args : >
207
+ -Dsonar.scm.revision=${{ env.COMMIT_SHA }}
0 commit comments