From 6eba94346a13f7188861401eff1ba40ff157ee4e Mon Sep 17 00:00:00 2001 From: ilia Date: Sat, 10 Jan 2026 14:05:33 -0500 Subject: [PATCH] Update SonarQube job to match established pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CHANGES: ======== ✅ Added conditional execution - Runs on pull_request or main/dev/qa branches - Matches pattern from other project ✅ Graceful secret handling - Exits 0 if secrets not set (doesn't break CI) - Clear warning message ✅ Non-blocking on failure - Exits 0 on SonarScanner failure (not exit 1) - Prevents CI failures from SonarQube issues - Matches established pattern ✅ Kept coverage report generation - Generates coverage.xml for SonarQube - Uses pytest-cov CONFIGURATION: ============== - Project key: pote - Sources: src/ - Tests: tests/ - Python version: 3.11 - Coverage: coverage.xml This matches the pattern used in other projects while maintaining POTE-specific configuration. --- .github/workflows/ci.yml | 159 +++++++++++++- SONARQUBE_QUICKSTART.md | 128 ++++++++++++ docs/17_sonarqube_setup.md | 414 +++++++++++++++++++++++++++++++++++++ sonar-project.properties | 40 ++++ 4 files changed, 740 insertions(+), 1 deletion(-) create mode 100644 SONARQUBE_QUICKSTART.md create mode 100644 docs/17_sonarqube_setup.md create mode 100644 sonar-project.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cc71d2..b6b4e63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -231,6 +231,161 @@ jobs: trivy fs --scanners vuln --severity HIGH,CRITICAL --format table . || true continue-on-error: true + sonar-analysis: + if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/qa' + runs-on: ubuntu-latest + container: + image: ubuntu:22.04 + env: + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + steps: + - name: Install Node.js for checkout action + run: | + apt-get update && apt-get install -y curl + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y nodejs + + - name: Check out code + uses: actions/checkout@v4 + + - name: Install Java and SonarScanner + run: | + set -e + apt-get update && apt-get install -y wget curl unzip openjdk-21-jre + + # Use a known working version to avoid download issues + SONAR_SCANNER_VERSION="5.0.1.3006" + SCANNER_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux.zip" + + echo "Installing SonarScanner version: ${SONAR_SCANNER_VERSION}" + echo "Downloading from: ${SCANNER_URL}" + + # Download with verbose error output + if ! wget --progress=bar:force "${SCANNER_URL}" -O /tmp/sonar-scanner.zip 2>&1; then + echo "❌ Failed to download SonarScanner" + echo "Checking if file was partially downloaded:" + ls -lh /tmp/sonar-scanner.zip 2>/dev/null || echo "No file found" + exit 1 + fi + + # Verify download + if [ ! -f /tmp/sonar-scanner.zip ] || [ ! -s /tmp/sonar-scanner.zip ]; then + echo "❌ Downloaded file is missing or empty" + exit 1 + fi + + echo "Download complete. File size: $(du -h /tmp/sonar-scanner.zip | cut -f1)" + + echo "Extracting SonarScanner..." + if ! unzip -q /tmp/sonar-scanner.zip -d /tmp; then + echo "❌ Failed to extract SonarScanner" + echo "Archive info:" + file /tmp/sonar-scanner.zip || true + unzip -l /tmp/sonar-scanner.zip 2>&1 | head -20 || true + exit 1 + fi + + # Find the extracted directory (handle both naming conventions) + EXTRACTED_DIR="" + if [ -d "/tmp/sonar-scanner-${SONAR_SCANNER_VERSION}-linux" ]; then + EXTRACTED_DIR="/tmp/sonar-scanner-${SONAR_SCANNER_VERSION}-linux" + elif [ -d "/tmp/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux" ]; then + EXTRACTED_DIR="/tmp/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux" + else + # Try to find any sonar-scanner directory + EXTRACTED_DIR=$(find /tmp -maxdepth 1 -type d -name "*sonar-scanner*" | head -1) + fi + + if [ -z "$EXTRACTED_DIR" ] || [ ! -d "$EXTRACTED_DIR" ]; then + echo "❌ SonarScanner directory not found after extraction" + echo "Contents of /tmp:" + ls -la /tmp/ | grep -E "(sonar|zip)" || ls -la /tmp/ | head -20 + exit 1 + fi + + echo "Found extracted directory: ${EXTRACTED_DIR}" + mv "${EXTRACTED_DIR}" /opt/sonar-scanner + + # Create symlink + if [ -f /opt/sonar-scanner/bin/sonar-scanner ]; then + ln -sf /opt/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scanner + chmod +x /opt/sonar-scanner/bin/sonar-scanner + chmod +x /usr/local/bin/sonar-scanner + else + echo "❌ sonar-scanner binary not found in /opt/sonar-scanner/bin/" + echo "Contents of /opt/sonar-scanner/bin/:" + ls -la /opt/sonar-scanner/bin/ || true + exit 1 + fi + + echo "Verifying installation..." + if ! sonar-scanner --version; then + echo "❌ SonarScanner verification failed" + echo "PATH: $PATH" + which sonar-scanner || echo "sonar-scanner not in PATH" + exit 1 + fi + echo "✓ SonarScanner installed successfully" + + - name: Verify SonarQube connection + run: | + echo "Checking SonarQube connectivity..." + if [ -z "$SONAR_HOST_URL" ] || [ -z "$SONAR_TOKEN" ]; then + echo "⚠️ Skipping SonarQube analysis: SONAR_HOST_URL or SONAR_TOKEN secrets are not set." + exit 0 + fi + echo "✓ Secrets are configured" + echo "SonarQube URL: ${SONAR_HOST_URL}" + echo "Testing connectivity to SonarQube server..." + if curl -f -s -o /dev/null -w "%{http_code}" "${SONAR_HOST_URL}/api/system/status" | grep -q "200"; then + echo "✓ SonarQube server is reachable" + else + echo "⚠️ Warning: Could not verify SonarQube server connectivity (continuing anyway)" + fi + + - name: Generate coverage report + run: | + echo "Generating coverage report for SonarQube..." + pip install --upgrade pip + pip install -e ".[dev]" + pytest tests/ --cov=src/pote --cov-report=xml --cov-report=term || true + + - name: Run SonarScanner + run: | + echo "Starting SonarQube analysis..." + if [ -z "$SONAR_HOST_URL" ] || [ -z "$SONAR_TOKEN" ]; then + echo "Skipping SonarQube analysis: secrets not set." + exit 0 + fi + + if ! sonar-scanner \ + -Dsonar.projectKey=pote \ + -Dsonar.sources=src \ + -Dsonar.tests=tests \ + -Dsonar.python.coverage.reportPaths=coverage.xml \ + -Dsonar.host.url=${SONAR_HOST_URL} \ + -Dsonar.token=${SONAR_TOKEN} \ + -Dsonar.scm.disabled=true \ + -Dsonar.python.version=3.11 \ + -X; then + echo "" + echo "❌ SonarScanner analysis failed!" + echo "" + echo "Common issues:" + echo " 1. Project 'pote' doesn't exist in SonarQube" + echo " → Create it manually in SonarQube UI" + echo " 2. Token doesn't have permission to analyze/create project" + echo " → Ensure token has 'Execute Analysis' permission" + echo " 3. Token doesn't have 'Create Projects' permission (if project doesn't exist)" + echo " → Grant this permission in SonarQube user settings" + echo "" + echo "Check SonarQube logs for more details." + # Do not fail CI on Sonar auth/project setup issues. + exit 0 + fi + continue-on-error: true + docker-build-test: runs-on: ubuntu-latest steps: @@ -255,7 +410,7 @@ jobs: workflow-summary: runs-on: ubuntu-latest - needs: [lint-and-test, secret-scanning, security-scan, dependency-scan, sast-scan, container-scan, docker-build-test] + needs: [lint-and-test, secret-scanning, security-scan, dependency-scan, sast-scan, container-scan, sonar-analysis, docker-build-test] if: always() steps: - name: Generate workflow summary @@ -272,6 +427,7 @@ jobs: echo "| 📦 Dependency Scan | ${{ needs.dependency-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true echo "| 🔍 SAST Scan | ${{ needs.sast-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true echo "| 🐳 Container Scan | ${{ needs.container-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true + echo "| 🔍 SonarQube Analysis | ${{ needs.sonar-analysis.result }} |" >> $GITHUB_STEP_SUMMARY || true echo "| 🐋 Docker Build | ${{ needs.docker-build-test.result }} |" >> $GITHUB_STEP_SUMMARY || true echo "" >> $GITHUB_STEP_SUMMARY || true echo "### 📊 Summary" >> $GITHUB_STEP_SUMMARY || true @@ -284,4 +440,5 @@ jobs: echo "- ✅ Static security analysis (Bandit)" >> $GITHUB_STEP_SUMMARY || true echo "- ✅ SAST scanning (Semgrep)" >> $GITHUB_STEP_SUMMARY || true echo "- ✅ Container scanning (Trivy)" >> $GITHUB_STEP_SUMMARY || true + echo "- ✅ Code quality analysis (SonarQube)" >> $GITHUB_STEP_SUMMARY || true continue-on-error: true diff --git a/SONARQUBE_QUICKSTART.md b/SONARQUBE_QUICKSTART.md new file mode 100644 index 0000000..c24bbd3 --- /dev/null +++ b/SONARQUBE_QUICKSTART.md @@ -0,0 +1,128 @@ +# SonarQube Quick Start + +**5-minute setup guide for SonarQube code quality analysis.** + +--- + +## ✅ What's Already Done + +- ✅ `sonar-project.properties` - Project configuration +- ✅ CI pipeline job - `sonar-analysis` added +- ✅ Coverage report generation - Integrated with pytest + +--- + +## 🚀 Quick Setup (3 Steps) + +### Step 1: Create Project in SonarQube + +1. Login to SonarQube: `http://your-server:9000` +2. **Projects** → **Create Project** +3. **Project Key:** `pote` +4. **Display Name:** `POTE` +5. Click **Set Up** + +### Step 2: Generate Token + +1. **My Account** → **Security** → **Generate Token** +2. **Name:** `POTE CI/CD` +3. **Type:** User Token +4. Click **Generate** +5. **⚠️ COPY THE TOKEN** (you won't see it again!) + +### Step 3: Add Secrets to Gitea + +1. Go to: `https://git.levkin.ca/ilia/POTE/settings/secrets/actions` +2. Add secret: `SONAR_HOST_URL` = `http://your-server:9000` +3. Add secret: `SONAR_TOKEN` = (paste token from Step 2) + +--- + +## 🧪 Test It + +```bash +# Push to dev branch +git push origin dev + +# Check CI results +# https://git.levkin.ca/ilia/POTE/actions + +# View SonarQube results +# http://your-server:9000/dashboard?id=pote +``` + +--- + +## 📋 Configuration + +### Project Key +- **Key:** `pote` (in `sonar-project.properties`) +- **Name:** `POTE` +- **Version:** `0.1.0` + +### Source Code +- **Sources:** `src/` +- **Tests:** `tests/` +- **Coverage:** `coverage.xml` (auto-generated) + +### Exclusions +- `__pycache__/`, `*.pyc` +- `venv/`, `tests/` +- `alembic/versions/` + +--- + +## 🔧 Customize + +Edit `sonar-project.properties`: +```properties +sonar.projectKey=pote +sonar.projectName=POTE +sonar.sources=src +sonar.tests=tests +``` + +--- + +## 📊 View Results + +**SonarQube Dashboard:** +``` +http://your-server:9000/dashboard?id=pote +``` + +**Metrics:** +- Code Coverage +- Bugs & Vulnerabilities +- Code Smells +- Technical Debt +- Quality Gate Status + +--- + +## 🐛 Troubleshooting + +### "Project does not exist" +→ Create project manually in SonarQube UI + +### "Authentication failed" +→ Check `SONAR_TOKEN` secret is correct + +### "Connection refused" +→ Verify `SONAR_HOST_URL` and server accessibility + +### "Coverage not found" +→ Ensure pytest runs before SonarScanner (already configured) + +--- + +## 📖 Full Documentation + +See: `docs/17_sonarqube_setup.md` for complete guide. + +--- + +**Setup Time:** ~5 minutes +**CI Integration:** ✅ Already done +**Manual Steps:** 3 (create project, generate token, add secrets) + diff --git a/docs/17_sonarqube_setup.md b/docs/17_sonarqube_setup.md new file mode 100644 index 0000000..02e7d28 --- /dev/null +++ b/docs/17_sonarqube_setup.md @@ -0,0 +1,414 @@ +# SonarQube Setup Guide for POTE + +**Complete guide for setting up SonarQube code quality analysis.** + +--- + +## 🎯 Overview + +SonarQube provides: +- **Code Quality Metrics** - Maintainability, reliability, security +- **Technical Debt** - Time to fix issues +- **Code Coverage** - Test coverage visualization +- **Code Smells** - Code quality issues +- **Security Vulnerabilities** - Security hotspots +- **Bug Detection** - Potential bugs + +--- + +## 📋 Prerequisites + +### 1. SonarQube Server +- ✅ You mentioned you have the runner (SonarQube server) +- Server URL: `http://your-sonarqube-server:9000` (or your URL) +- Server must be accessible from CI/CD runners + +### 2. SonarQube Project +- Project key: `pote` (configured in `sonar-project.properties`) +- Project name: `POTE` +- Can be created manually or auto-created on first scan + +### 3. SonarQube Token +- User token with permissions: + - ✅ **Execute Analysis** (required) + - ✅ **Create Projects** (if project doesn't exist) + +--- + +## 🔧 Step 1: Create SonarQube Project + +### Option A: Create Manually (Recommended) + +1. **Login to SonarQube:** + ``` + http://your-sonarqube-server:9000 + ``` + +2. **Create Project:** + - Go to: **Projects** → **Create Project** + - **Project Key:** `pote` + - **Display Name:** `POTE` + - **Main Branch:** `main` + - Click **Set Up** + +3. **Generate Token:** + - Go to: **My Account** → **Security** → **Generate Token** + - **Name:** `POTE CI/CD` + - **Type:** **User Token** + - **Expires:** (set expiration or leave blank) + - Click **Generate** + - **⚠️ COPY THE TOKEN** - You won't see it again! + +### Option B: Auto-Create (First Scan) + +- Project will be created automatically on first scan +- Token must have **"Create Projects"** permission + +--- + +## 🔐 Step 2: Configure Gitea Secrets + +### Add Secrets in Gitea + +1. **Go to Repository Settings:** + ``` + https://git.levkin.ca/ilia/POTE/settings/secrets/actions + ``` + +2. **Add Secret: `SONAR_HOST_URL`** + - **Name:** `SONAR_HOST_URL` + - **Value:** `http://your-sonarqube-server:9000` + - Example: `http://10.0.30.169:9000` + - Click **Add Secret** + +3. **Add Secret: `SONAR_TOKEN`** + - **Name:** `SONAR_TOKEN` + - **Value:** (paste the token from Step 1) + - Click **Add Secret** + +### Verify Secrets + +```bash +# In CI pipeline, secrets are available as: +${{ secrets.SONAR_HOST_URL }} +${{ secrets.SONAR_TOKEN }} +``` + +--- + +## 📄 Step 3: Project Configuration + +### File: `sonar-project.properties` + +Already created in project root with these settings: + +```properties +# Project identification +sonar.projectKey=pote +sonar.projectName=POTE +sonar.projectVersion=0.1.0 + +# Source code location +sonar.sources=src +sonar.sourceEncoding=UTF-8 + +# Test code location +sonar.tests=tests +sonar.test.inclusions=**/test_*.py + +# Exclusions +sonar.exclusions=**/__pycache__/**,**/*.pyc,**/venv/**,**/tests/**,**/alembic/versions/** + +# Python-specific settings +sonar.python.version=3.11 + +# Coverage reports +sonar.python.coverage.reportPaths=coverage.xml +``` + +### Customize if Needed + +Edit `sonar-project.properties` to: +- Change project key/name +- Add more exclusions +- Configure additional settings + +--- + +## 🚀 Step 4: CI Pipeline Integration + +### Already Configured! + +The CI pipeline (`.github/workflows/ci.yml`) includes: + +1. **SonarScanner Installation** + - Downloads and installs SonarScanner CLI + - Version: `5.0.1.3006` (stable) + +2. **Coverage Report Generation** + - Runs pytest with coverage + - Generates `coverage.xml` for SonarQube + +3. **SonarScanner Execution** + - Runs analysis + - Uploads results to SonarQube server + - Non-blocking (won't fail CI if SonarQube is unavailable) + +### Job: `sonar-analysis` + +```yaml +sonar-analysis: + runs-on: ubuntu-latest + env: + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + steps: + - Install SonarScanner + - Generate coverage report + - Run SonarScanner analysis +``` + +--- + +## 🧪 Step 5: Test Locally (Optional) + +### Install SonarScanner Locally + +```bash +# Download SonarScanner +wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip +unzip sonar-scanner-cli-5.0.1.3006-linux.zip +export PATH=$PATH:$(pwd)/sonar-scanner-5.0.1.3006-linux/bin +``` + +### Run Analysis Locally + +```bash +cd /home/user/Documents/code/pote + +# Generate coverage report +source venv/bin/activate +pytest tests/ --cov=src/pote --cov-report=xml + +# Run SonarScanner +sonar-scanner \ + -Dsonar.host.url=http://your-sonarqube-server:9000 \ + -Dsonar.token=your_token_here +``` + +### Or Use Environment Variables + +```bash +export SONAR_HOST_URL=http://your-sonarqube-server:9000 +export SONAR_TOKEN=your_token_here +sonar-scanner +``` + +--- + +## 📊 Step 6: View Results + +### In SonarQube UI + +1. **Go to Project:** + ``` + http://your-sonarqube-server:9000/dashboard?id=pote + ``` + +2. **View Metrics:** + - **Overview** - Overall quality gate status + - **Issues** - Bugs, vulnerabilities, code smells + - **Measures** - Coverage, complexity, duplications + - **Code** - Source code with inline issues + - **Activity** - Analysis history + +### Quality Gate + +SonarQube will show: +- ✅ **Pass** - Meets quality standards +- ❌ **Fail** - Doesn't meet quality standards + +### Common Metrics + +- **Coverage** - Test coverage percentage +- **Duplications** - Code duplication percentage +- **Maintainability Rating** - A-E rating +- **Reliability Rating** - A-E rating +- **Security Rating** - A-E rating +- **Technical Debt** - Time to fix all issues + +--- + +## 🔧 Configuration Options + +### Quality Gate + +Configure in SonarQube: +- **Projects** → **POTE** → **Quality Gates** +- Set thresholds for: + - Coverage + - Duplications + - Bugs + - Vulnerabilities + - Code smells + +### Exclusions + +Add to `sonar-project.properties`: + +```properties +# Exclude specific files +sonar.exclusions=**/legacy/**,**/old_code/** + +# Exclude specific issues +sonar.issue.ignore.multicriteria=e1 +sonar.issue.ignore.multicriteria.e1.ruleKey=python:S1234 +sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.py +``` + +### Coverage Exclusions + +```properties +# Exclude from coverage +sonar.coverage.exclusions=**/tests/**,**/migrations/**,**/__init__.py +``` + +--- + +## 🐛 Troubleshooting + +### Issue: "Project 'pote' does not exist" + +**Solution:** +1. Create project manually in SonarQube UI +2. OR ensure token has "Create Projects" permission + +### Issue: "Authentication failed" + +**Solution:** +1. Verify `SONAR_TOKEN` secret is correct +2. Check token hasn't expired +3. Regenerate token if needed + +### Issue: "Connection refused" + +**Solution:** +1. Verify `SONAR_HOST_URL` is correct +2. Check SonarQube server is running +3. Verify network connectivity from CI runner +4. Check firewall rules + +### Issue: "Coverage report not found" + +**Solution:** +1. Ensure pytest runs before SonarScanner +2. Check `coverage.xml` is generated +3. Verify path in `sonar-project.properties`: + ```properties + sonar.python.coverage.reportPaths=coverage.xml + ``` + +### Issue: "No files to analyze" + +**Solution:** +1. Check `sonar.sources=src` is correct +2. Verify source files aren't excluded +3. Check file encoding (should be UTF-8) + +--- + +## 📈 Best Practices + +### 1. Regular Analysis +- Run on every commit (already configured in CI) +- Review results regularly +- Fix issues incrementally + +### 2. Quality Gate +- Set realistic thresholds +- Start with warnings, not failures +- Gradually increase standards + +### 3. Coverage +- Aim for 80%+ coverage +- Focus on critical paths first +- Don't sacrifice quality for coverage + +### 4. Technical Debt +- Track and reduce over time +- Prioritize high-impact issues +- Set time limits for fixing + +### 5. Team Integration +- Share SonarQube dashboard with team +- Review issues in code reviews +- Use quality gate in PR checks + +--- + +## 🔗 Integration with CI/CD + +### Current Setup + +✅ **Already Integrated:** +- Runs automatically on every push +- Non-blocking (won't fail CI) +- Generates coverage report +- Uploads results to SonarQube + +### Make Quality Gate Blocking (Optional) + +To fail CI if quality gate fails: + +```yaml +- name: Run SonarScanner + run: | + sonar-scanner \ + -Dsonar.qualitygate.wait=true \ + ... +``` + +Then remove `continue-on-error: true` from the step. + +--- + +## 📝 Summary + +### Quick Setup Checklist + +- [ ] SonarQube server running and accessible +- [ ] Project `pote` created in SonarQube (or auto-create enabled) +- [ ] Token generated with "Execute Analysis" permission +- [ ] `SONAR_HOST_URL` secret added to Gitea +- [ ] `SONAR_TOKEN` secret added to Gitea +- [ ] `sonar-project.properties` file exists (✅ already created) +- [ ] CI pipeline includes `sonar-analysis` job (✅ already added) +- [ ] Test by pushing to dev branch + +### Files Created + +- ✅ `sonar-project.properties` - Project configuration +- ✅ `.github/workflows/ci.yml` - Updated with SonarScanner job + +### Next Steps + +1. **Add secrets to Gitea** (Step 2) +2. **Push to dev branch** - CI will run SonarScanner +3. **View results** in SonarQube dashboard +4. **Configure quality gate** as needed +5. **Review and fix issues** incrementally + +--- + +## 📞 Support + +- **SonarQube Docs:** https://docs.sonarqube.org/ +- **SonarScanner Docs:** https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/ +- **Python Plugin:** https://docs.sonarqube.org/latest/analysis/languages/python/ + +--- + +**Last Updated:** December 2025 +**SonarScanner Version:** 5.0.1.3006 +**Python Version:** 3.11 + diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..adc3997 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,40 @@ +# SonarQube Project Configuration for POTE +# =========================================== + +# Project identification +sonar.projectKey=pote +sonar.projectName=POTE +sonar.projectVersion=0.1.0 + +# Source code location +sonar.sources=src +sonar.sourceEncoding=UTF-8 + +# Test code location +sonar.tests=tests +sonar.test.inclusions=**/test_*.py + +# Exclusions +sonar.exclusions=**/__pycache__/**,**/*.pyc,**/venv/**,**/tests/**,**/alembic/versions/**,**/*.egg-info/**,**/pote.egg-info/** + +# Python-specific settings +sonar.python.version=3.11 + +# Coverage reports (generated by pytest-cov) +sonar.python.coverage.reportPaths=coverage.xml + +# Code analysis +sonar.python.xunit.reportPath=test-results.xml + +# Language +sonar.language=py + +# Encoding +sonar.sourceEncoding=UTF-8 + +# SCM (optional - if you want to track blame) +# sonar.scm.provider=git + +# Quality Gate +# sonar.qualitygate.wait=true +