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+
118# With much help from:
219# https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/30
320# https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/32
825 - CI
926 types :
1027 - 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
1151jobs :
1252 sonar :
1353 name : Upload to SonarCloud
1454 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')
1658 steps :
17- - uses : actions/checkout@v4
18- with :
19- show-progress : false
59+ - uses : actions/checkout@v3
2060
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)
2262 - name : Download coverage artifact
63+ if : |
64+ github.event_name == 'workflow_run' ||
65+ (github.event_name == 'workflow_dispatch' && inputs.ci_run_id != '')
2366 uses : dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615
2467 with :
2568 github_token : ${{ secrets.GITHUB_TOKEN }}
2669 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 }}
2871 name : coverage
2972
30- - name : Extract PR number from coverage.xml
73+ # Set PR number based on trigger type
74+ - name : Set PR number and event type
3175 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
3389
3490 - name : Get PR info
91+ if : env.EVENT_TYPE == 'pull_request' && env.PR_NUMBER != '0'
35923693 id : pr_info
3794 with :
3895 route : GET /repos/{repo}/pulls/{number}
39- repo : ${{ github.event.repository.full_name }}
96+ repo : ${{ env.REPO_NAME }}
4097 number : ${{ env.PR_NUMBER }}
4198 env :
4299 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
43100
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+
44154 - 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 != ''
45156 run : |
46157 echo "PR_BASE=${{ fromJson(steps.pr_info.outputs.data).base.ref }}" >> $GITHUB_ENV
47158 echo "PR_HEAD=${{ fromJson(steps.pr_info.outputs.data).head.ref }}" >> $GITHUB_ENV
48159
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'
50162 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
52175 env :
53176 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
54177
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'
57180 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 }}
65184
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'
67187 uses : SonarSource/sonarqube-scan-action@v5
68188 env :
69189 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)] }}
71191 with :
72192 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 }}
76194 -Dsonar.pullrequest.key=${{ env.PR_NUMBER }}
77195 -Dsonar.pullrequest.branch=${{ env.PR_HEAD }}
78196 -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