Skip to content

Commit bfe383c

Browse files
committed
feat: add automated sonarqube analysis as a reusable workflow
0 parents  commit bfe383c

File tree

7 files changed

+791
-0
lines changed

7 files changed

+791
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: Code Quality Analysis
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
sonarqube-analysis:
10+
name: SonarQube Analysis
11+
uses: iic2154-uc-cl/org-reusable-workflows/.github/workflows/sonarqube-analysis.yml@main
12+
with:
13+
sonarqube_url: "https://sonarqube.ing.puc.cl"
14+
secrets:
15+
SONARQUBE_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
name: SonarQube Analysis with Auto Project Creation
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
sonarqube_url:
7+
description: "SonarQube server URL"
8+
required: true
9+
type: string
10+
sonarqube_project_key:
11+
description: "SonarQube project key (optional, will use repository name if not provided)"
12+
required: false
13+
type: string
14+
default: ""
15+
java_version:
16+
description: "Java version for SonarQube scanner (auto-detected if not specified)"
17+
required: false
18+
type: string
19+
default: "17"
20+
node_version:
21+
description: "Node.js version (auto-detected if not specified)"
22+
required: false
23+
type: string
24+
default: "18"
25+
python_version:
26+
description: "Python version (auto-detected if not specified)"
27+
required: false
28+
type: string
29+
default: "3.11"
30+
secrets:
31+
SONARQUBE_TOKEN:
32+
description: "SonarQube authentication token"
33+
required: true
34+
35+
jobs:
36+
sonarqube-analysis:
37+
name: SonarQube Analysis
38+
runs-on: ubuntu-latest
39+
40+
steps:
41+
- name: Checkout code
42+
uses: actions/checkout@v4
43+
with:
44+
fetch-depth: 0 # Shallow clones should be disabled for better analysis
45+
46+
- name: Setup Java (if needed)
47+
uses: actions/setup-java@v4
48+
with:
49+
java-version: ${{ inputs.java_version }}
50+
distribution: "temurin"
51+
if: hashFiles('**/*.java') != '' || hashFiles('pom.xml') != '' || hashFiles('build.gradle') != '' || hashFiles('build.gradle.kts') != ''
52+
53+
- name: Setup Java 17 for SonarQube Scanner
54+
uses: actions/setup-java@v4
55+
with:
56+
java-version: "17"
57+
distribution: "temurin"
58+
59+
- name: Setup Node.js (if needed)
60+
uses: actions/setup-node@v4
61+
with:
62+
node-version: ${{ inputs.node_version }}
63+
if: hashFiles('**/*.js') != '' || hashFiles('**/*.ts') != '' || hashFiles('**/*.jsx') != '' || hashFiles('**/*.tsx') != '' || hashFiles('package.json') != ''
64+
65+
- name: Setup Python (if needed)
66+
uses: actions/setup-python@v4
67+
with:
68+
python-version: ${{ inputs.python_version }}
69+
if: hashFiles('**/*.py') != '' || hashFiles('requirements.txt') != '' || hashFiles('pyproject.toml') != '' || hashFiles('setup.py') != ''
70+
71+
- name: Parse repository information
72+
id: parse_repo
73+
run: |
74+
REPO_NAME="${{ github.repository }}"
75+
REPO_NAME_ONLY="${REPO_NAME#*/}"
76+
echo "Repository name: $REPO_NAME_ONLY"
77+
78+
# Extract components using regex
79+
# Format: [year]-[semester]-S[section]-Grupo[team]-[additional_name]
80+
if [[ $REPO_NAME_ONLY =~ ^([0-9]{4})-([12])-S([0-9]+)-Grupo([0-9]+)-(.+)$ ]]; then
81+
YEAR="${BASH_REMATCH[1]}"
82+
SEMESTER="${BASH_REMATCH[2]}"
83+
SECTION="${BASH_REMATCH[3]}"
84+
GROUP="${BASH_REMATCH[4]}"
85+
ADDITIONAL_NAME="${BASH_REMATCH[5]}"
86+
87+
echo "Parsed components:"
88+
echo "Year: $YEAR"
89+
echo "Semester: $SEMESTER"
90+
echo "Section: $SECTION"
91+
echo "Group: $GROUP"
92+
echo "Additional Name: $ADDITIONAL_NAME"
93+
94+
# Generate tags
95+
TAG1="${YEAR}-${SEMESTER}"
96+
TAG2="${YEAR}-${SEMESTER}-S${SECTION}"
97+
TAG3="${YEAR}-${SEMESTER}-S${SECTION}-Grupo${GROUP}"
98+
99+
echo "Generated tags:"
100+
echo "Tag 1: $TAG1"
101+
echo "Tag 2: $TAG2"
102+
echo "Tag 3: $TAG3"
103+
104+
# Set outputs
105+
echo "year=$YEAR" >> $GITHUB_OUTPUT
106+
echo "semester=$SEMESTER" >> $GITHUB_OUTPUT
107+
echo "section=$SECTION" >> $GITHUB_OUTPUT
108+
echo "group=$GROUP" >> $GITHUB_OUTPUT
109+
echo "additional_name=$ADDITIONAL_NAME" >> $GITHUB_OUTPUT
110+
echo "tag1=$TAG1" >> $GITHUB_OUTPUT
111+
echo "tag2=$TAG2" >> $GITHUB_OUTPUT
112+
echo "tag3=$TAG3" >> $GITHUB_OUTPUT
113+
echo "valid_format=true" >> $GITHUB_OUTPUT
114+
echo "project_key=${REPO_NAME_ONLY}" >> $GITHUB_OUTPUT
115+
echo "project_name=${REPO_NAME_ONLY}" >> $GITHUB_OUTPUT
116+
else
117+
echo "Repository name does not match expected format: [year]-[semester]-S[section]-Grupo[group]-[name]"
118+
echo "valid_format=false" >> $GITHUB_OUTPUT
119+
echo "project_key=${REPO_NAME_ONLY}" >> $GITHUB_OUTPUT
120+
echo "project_name=${REPO_NAME_ONLY}" >> $GITHUB_OUTPUT
121+
echo "tag1=" >> $GITHUB_OUTPUT
122+
echo "tag2=" >> $GITHUB_OUTPUT
123+
echo "tag3=" >> $GITHUB_OUTPUT
124+
fi
125+
126+
- name: Check if SonarQube project exists
127+
id: check_project
128+
run: |
129+
PROJECT_KEY="${{ inputs.sonarqube_project_key || steps.parse_repo.outputs.project_key }}"
130+
131+
echo "Checking if project '$PROJECT_KEY' exists in SonarQube..."
132+
133+
# Check if project exists using SonarQube Web API
134+
RESPONSE=$(curl -s -w "%{http_code}" -u "${{ secrets.SONARQUBE_TOKEN }}:" \
135+
"${{ inputs.sonarqube_url }}/api/projects/search?projects=$PROJECT_KEY" \
136+
-o response.json)
137+
138+
HTTP_CODE=${RESPONSE: -3}
139+
echo "HTTP response code: $HTTP_CODE"
140+
141+
if [ "$HTTP_CODE" = "200" ]; then
142+
PROJECT_COUNT=$(jq -r '.components | length' response.json)
143+
echo "Projects found: $PROJECT_COUNT"
144+
145+
if [ "$PROJECT_COUNT" -gt 0 ]; then
146+
echo "project_exists=true" >> $GITHUB_OUTPUT
147+
echo "Project already exists"
148+
else
149+
echo "project_exists=false" >> $GITHUB_OUTPUT
150+
echo "Project does not exist"
151+
fi
152+
else
153+
echo "project_exists=false" >> $GITHUB_OUTPUT
154+
echo "Error checking project or project does not exist"
155+
cat response.json || true
156+
fi
157+
158+
echo "project_key=$PROJECT_KEY" >> $GITHUB_OUTPUT
159+
rm -f response.json
160+
161+
- name: Create SonarQube project
162+
if: steps.check_project.outputs.project_exists == 'false'
163+
run: |
164+
PROJECT_KEY="${{ steps.check_project.outputs.project_key }}"
165+
PROJECT_NAME="${{ steps.parse_repo.outputs.project_name }}"
166+
167+
echo "Creating SonarQube project: $PROJECT_KEY"
168+
169+
# Create project using SonarQube Web API
170+
curl -X POST \
171+
-u "${{ secrets.SONARQUBE_TOKEN }}:" \
172+
"${{ inputs.sonarqube_url }}/api/projects/create" \
173+
-d "project=$PROJECT_KEY" \
174+
-d "name=$PROJECT_NAME"
175+
176+
echo "Project created successfully"
177+
178+
- name: Set project tags
179+
if: steps.parse_repo.outputs.valid_format == 'true'
180+
run: |
181+
PROJECT_KEY="${{ steps.check_project.outputs.project_key }}"
182+
183+
echo "Setting tags for project: $PROJECT_KEY"
184+
185+
# Prepare tags array
186+
TAGS="${{ steps.parse_repo.outputs.tag1 }},${{ steps.parse_repo.outputs.tag2 }},${{ steps.parse_repo.outputs.tag3 }}"
187+
188+
echo "Tags to set: $TAGS"
189+
190+
# Set tags using SonarQube Web API
191+
curl -X POST \
192+
-u "${{ secrets.SONARQUBE_TOKEN }}:" \
193+
"${{ inputs.sonarqube_url }}/api/project_tags/set" \
194+
-d "project=$PROJECT_KEY" \
195+
-d "tags=$TAGS"
196+
197+
echo "Tags set successfully"
198+
199+
- name: Cache SonarQube packages
200+
uses: actions/cache@v3
201+
with:
202+
path: ~/.sonar/cache
203+
key: ${{ runner.os }}-sonar
204+
restore-keys: ${{ runner.os }}-sonar
205+
206+
- name: Install SonarQube Scanner
207+
run: |
208+
wget -q https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip
209+
unzip -q sonar-scanner-cli-5.0.1.3006-linux.zip
210+
sudo mv sonar-scanner-5.0.1.3006-linux /opt/sonar-scanner
211+
sudo ln -s /opt/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scanner
212+
213+
- name: Run SonarQube Scanner
214+
env:
215+
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
216+
SONAR_HOST_URL: ${{ inputs.sonarqube_url }}
217+
run: |
218+
sonar-scanner \
219+
-Dsonar.projectKey="${{ steps.check_project.outputs.project_key }}" \
220+
-Dsonar.projectName="${{ steps.parse_repo.outputs.project_name }}" \
221+
-Dsonar.sources=. \
222+
-Dsonar.host.url="${{ inputs.sonarqube_url }}" \
223+
-Dsonar.login="${{ secrets.SONARQUBE_TOKEN }}" \
224+
-Dsonar.projectVersion="${{ github.sha }}" \
225+
-Dsonar.exclusions="**/node_modules/**,**/target/**,**/*.class,**/*.jar,**/.git/**,**/__pycache__/**,**/*.pyc,**/venv/**,**/.venv/**,**/env/**,**/.env/**,**/build/**,**/dist/**,**/*.egg-info/**,**/coverage/**,**/migrations/**,**/.pytest_cache/**,**/htmlcov/**" \
226+
-Dsonar.test.inclusions="**/test/**,**/tests/**,**/*_test.py,**/*_test.java,**/*_test.js,**/*_test.ts,**/spec/**,**/*.spec.js,**/*.spec.ts,**/*.test.js,**/*.test.ts" \
227+
-Dsonar.coverage.exclusions="**/test/**,**/tests/**,**/*_test.*,**/spec/**,**/*.spec.*,**/*.test.*,**/migrations/**,**/manage.py,**/settings.py,**/urls.py,**/wsgi.py,**/asgi.py" \
228+
-Dsonar.python.coverage.reportPaths="coverage.xml,**/coverage.xml" \
229+
-Dsonar.javascript.lcov.reportPaths="coverage/lcov.info,**/coverage/lcov.info" \
230+
-Dsonar.java.coveragePlugin="jacoco" \
231+
-Dsonar.coverage.jacoco.xmlReportPaths="target/site/jacoco/jacoco.xml,**/jacoco.xml" \
232+
-Dsonar.scm.provider="git"
233+
234+
- name: Check Quality Gate Status (Informational Only)
235+
continue-on-error: true
236+
run: |
237+
echo "Waiting for Quality Gate results..."
238+
sleep 30
239+
240+
# Get project status (for information only - won't fail workflow)
241+
QUALITY_GATE_STATUS=$(curl -s -u "${{ secrets.SONARQUBE_TOKEN }}:" \
242+
"${{ inputs.sonarqube_url }}/api/qualitygates/project_status?projectKey=${{ steps.check_project.outputs.project_key }}" \
243+
| jq -r '.projectStatus.status' 2>/dev/null || echo "UNKNOWN")
244+
245+
echo ""
246+
echo "📊 Quality Gate Status: $QUALITY_GATE_STATUS"
247+
echo ""
248+
249+
case "$QUALITY_GATE_STATUS" in
250+
"OK")
251+
echo "✅ Quality Gate: Passed - Great code quality!"
252+
;;
253+
"ERROR")
254+
echo "⚠️ Quality Gate: Failed - Check SonarQube for improvement suggestions"
255+
echo "📝 This is informational only - workflow continues normally"
256+
;;
257+
"WARN")
258+
echo "⚠️ Quality Gate: Warning - Some issues detected"
259+
echo "📝 This is informational only - workflow continues normally"
260+
;;
261+
*)
262+
echo "📊 Quality Gate: Status pending or unknown"
263+
echo "📝 Analysis may still be processing"
264+
;;
265+
esac
266+
267+
echo ""
268+
echo "🔗 View detailed results at: ${{ inputs.sonarqube_url }}/dashboard?id=${{ steps.check_project.outputs.project_key }}"
269+
270+
- name: Display analysis summary
271+
run: |
272+
echo "✅ SonarQube analysis completed successfully!"
273+
echo ""
274+
echo "📊 Analysis Summary:"
275+
echo "Repository: ${{ github.repository }}"
276+
echo "Project Key: ${{ steps.check_project.outputs.project_key }}"
277+
echo "SonarQube URL: ${{ inputs.sonarqube_url }}"
278+
echo ""
279+
echo "📝 Note: This analysis is for information and improvement guidance only."
280+
echo "🚀 Code quality issues do not block merges - they help you improve!"
281+
echo ""
282+
if [ "${{ steps.parse_repo.outputs.valid_format }}" = "true" ]; then
283+
echo "🏷️ Applied Tags:"
284+
echo "• ${{ steps.parse_repo.outputs.tag1 }} (Year-Semester)"
285+
echo "• ${{ steps.parse_repo.outputs.tag2 }} (Year-Semester-Section)"
286+
echo "• ${{ steps.parse_repo.outputs.tag3 }} (Year-Semester-Section-Group)"
287+
else
288+
echo "⚠️ Repository name doesn't match expected format - no tags applied"
289+
echo "Expected format: [year]-[semester]-S[section]-Grupo[group]-[name]"
290+
fi
291+
echo ""
292+
echo "🔍 View detailed analysis: ${{ inputs.sonarqube_url }}/dashboard?id=${{ steps.check_project.outputs.project_key }}"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
repos.json

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Gonzalo Fernández - Pontificia Universidad Católica de Chile
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)