Skip to content
98 changes: 98 additions & 0 deletions sonarqube-python/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: 'Sonarqube Python Scanner'
description: 'Quality Gate Sonarqube for Python projects'

inputs:
sonar-verbose:
required: false
type: boolean
default: false
sonar-sources:
required: false
type: string
default: "src"
sonar-sources-tests:
required: false
type: string
default: "tests"
sonar-test-inclusions:
required: false
type: string
default: "**/test_*.py,**/*_test.py,**/__pycache__/**,**/__init__.py"
sonar-report-paths:
required: false
type: string
default: "coverage.xml"
sonar-source-encoding:
required: false
type: string
default: "UTF-8"
sonar-exclusions:
required: false
type: string
default: "**/__pycache__/**,**/*.pyc,**/migrations/**,**/fixtures/**,**/.venv/**,**/.git/**,**/.vscode/**,**/*.html,**/htmlcov/**,coverage.xml"
sonar-lang-version:
required: false
type: string
default: "3.11"
sonar-host-url:
required: true
type: string
sonar-api-token:
required: true
type: string
sonar-token:
required: true
type: string
sonar-project-key:
required: true
type: string

runs:
using: composite
steps:
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@v5.2.0
continue-on-error: true
with:
args: >
-Dsonar.projectKey=${{ inputs.sonar-project-key }}
-Dsonar.sources=${{ inputs.sonar-sources }}
-Dsonar.tests=${{ inputs.sonar-sources-tests }}
-Dsonar.test.inclusions=${{inputs.sonar-test-inclusions }}
-Dsonar.exclusions=${{ inputs.sonar-exclusions }}
-Dsonar.python.version=${{ inputs.sonar-lang-version }}
-Dsonar.python.coverage.reportPaths=${{ inputs.sonar-report-paths }}
-Dsonar.verbose=${{ inputs.sonar-verbose }}
-Dsonar.sourceEncoding=${{ inputs.sonar-source-encoding }}
env:
SONAR_TOKEN: ${{ inputs.sonar-token }}
SONAR_HOST_URL: ${{ inputs.sonar-host-url }}

- name: Check SonarQube Quality Gate Status
id: sonarqube-status
shell: bash
continue-on-error: true
run: ${{ github.action_path }}/sonarqube-status.sh
env:
SONAR_PROJECT_KEY: ${{ inputs.sonar-project-key }}
SONAR_TOKEN: ${{ inputs.sonar-api-token }}
SONAR_HOST_URL: ${{ inputs.sonar-host-url }}

- name: Get Coverage
shell: bash
id: coverage-calc
if: steps.sonarqube-status.outputs.status == 'OK'
continue-on-error: true
run: ${{ github.action_path }}/coverage-calc.sh

- name: Update Quality Gates Sonarqube
shell: bash
id: quality-gates-sonarqube
if: steps.sonarqube-status.outputs.status == 'OK'
continue-on-error: true
run: ${{ github.action_path }}/quality-gates-sonarqube.sh
env:
SONAR_PROJECT_KEY: ${{ inputs.sonar-project-key }}
SONAR_TOKEN: ${{ inputs.sonar-api-token }}
SONAR_HOST_URL: ${{ inputs.sonar-host-url }}
LEGACY_COV_NUM: ${{ steps.coverage-calc.outputs.coverage }}
7 changes: 7 additions & 0 deletions sonarqube-python/coverage-calc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

COVERAGE=$(python -c "import xml.etree.ElementTree as ET; \
print(float(ET.parse('coverage.xml').getroot().attrib['line-rate']) * 100)")

echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "legacy_coverage=$COVERAGE" >> $GITHUB_OUTPUT
40 changes: 40 additions & 0 deletions sonarqube-python/quality-gates-sonarqube.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash

PROJECT_RESPONSE=$(curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST_URL/api/qualitygates/get_by_project?project=$SONAR_PROJECT_KEY")

QUALITY_GATE_NAME=$(echo "$PROJECT_RESPONSE" | jq -r '.qualityGate.name')
QUALITY_GATE_NAME=$(echo "$QUALITY_GATE_NAME" | jq -sRr @uri)

echo "Quality Gate Name: $QUALITY_GATE_NAME"

GATE_RESPONSE=$(curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST_URL/api/qualitygates/show?name=$QUALITY_GATE_NAME")

NEW_COV_CONDITION_ID=$(echo "$GATE_RESPONSE" | jq -r '.conditions[] | select(.metric=="coverage") | .id')
CURRENT_NEW_COV=$(echo "$GATE_RESPONSE" | jq -r '.conditions[] | select(.metric=="coverage") | .error')

if [ -n "$NEW_COV_CONDITION_ID" ]; then
CURRENT_NEW_COV_NUM=$(echo "$CURRENT_NEW_COV" | sed 's/[^0-9.]*//g')

if (( $(echo "$LEGACY_COV_NUM > $CURRENT_NEW_COV_NUM" | bc -l) )); then
if (( $(echo "$LEGACY_COV_NUM > 80" | bc -l) )); then
echo "Legacy coverage ($LEGACY_COV_NUM%) > 80%, updating new_coverage threshold to 80%"
curl -u "$SONAR_TOKEN:" -X POST \
"$SONAR_HOST_URL/api/qualitygates/update_condition" \
-d "id=$NEW_COV_CONDITION_ID&metric=new_coverage&op=LT&error=80"
else
echo "Updating new_coverage threshold from $CURRENT_NEW_COV to $LEGACY_COV_NUM%"
curl -u "$SONAR_TOKEN:" -X POST \
"$SONAR_HOST_URL/api/qualitygates/update_condition" \
-d "id=$NEW_COV_CONDITION_ID&metric=new_coverage&op=LT&error=$LEGACY_COV_NUM"
fi
else
echo "Legacy coverage ($LEGACY_COV_NUM%) not greater than current new_coverage ($CURRENT_NEW_COV), keeping existing configuration"
fi
else
echo "Criando nova condição para new_coverage"
curl -u "$SONAR_TOKEN:" -X POST \
"$SONAR_HOST_URL/api/qualitygates/create_condition" \
-d "gateName=$QUALITY_GATE_NAME&metric=new_coverage&op=LT&error=$LEGACY_COV_NUM"
fi
22 changes: 22 additions & 0 deletions sonarqube-python/sonarqube-status.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

sleep 10
SONAR_STATUS=$(curl -s -u "$SONAR_TOKEN:" \
"$SONAR_HOST_URL/api/qualitygates/project_status?projectKey=$SONAR_PROJECT_KEY")

# Extract overall status
STATUS=$(echo "$SONAR_STATUS" | jq -r '.projectStatus.status')

# Process failing conditions
echo "SonarQube Quality Gate Status: $STATUS"
if [ "$STATUS" != "OK" ]; then
echo "=== Failing Conditions ==="
echo "$SONAR_STATUS" | jq -r '.projectStatus.conditions[] | select(.status != "OK") | "Metric: \(.metricKey) | Threshold: \(.errorThreshold) | Actual: \(.actualValue)"'

echo "status=$STATUS" >> $GITHUB_OUTPUT
echo "::error::Quality Gate failed with status: $STATUS"
exit 1
fi

echo "status=$STATUS" >> $GITHUB_OUTPUT
echo "Quality Gate passed successfully!"