name: SonarQube Analysis on: push: pull_request: types: [opened, synchronize, reopened] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: sonarqube: name: Build, Test & Analyse runs-on: ubuntu-latest timeout-minutes: 15 env: SONAR_PROJECT_KEY: security-scan-test steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Make Maven wrapper executable run: chmod +x mvnw - name: Cache Maven packages uses: actions/cache@v4 with: path: ~/.m2/repository key: maven-${{ runner.os }}-${{ hashFiles('**/pom.xml') }} restore-keys: maven-${{ runner.os }}- - name: Cache SonarQube analysis data uses: actions/cache@v4 with: path: ~/.sonar/cache key: sonar-${{ runner.os }} restore-keys: sonar-${{ runner.os }} # SONAR_TOKEN replaced by SONAR_ADMIN_TOKEN — the admin token is used # only for provisioning; the scan uses an ephemeral PROJECT_ANALYSIS_TOKEN # generated in the bootstrap step below. - name: Validate required secrets env: SONAR_ADMIN_TOKEN: SONAR_HOST_URL: run: | [[ -n "$SONAR_ADMIN_TOKEN" ]] || { echo "::error::SONAR_ADMIN_TOKEN is not set"; exit 1; } [[ -n "$SONAR_HOST_URL" ]] || { echo "::error::SONAR_HOST_URL is not set"; exit 1; } [[ -n "$SONAR_PROJECT_KEY" ]] || { echo "::error::SONAR_PROJECT_KEY is not set"; exit 1; } - name: Bootstrap SonarQube project and generate scan token id: sonar-bootstrap env: SONAR_ADMIN_TOKEN: SONAR_HOST_URL: run: | # ---- 1. Create the project if it doesn't already exist ---- PROJECT_COUNT=$(curl -sf \ -u "${SONAR_ADMIN_TOKEN}:" \ "${SONAR_HOST_URL}/api/projects/search?projects=${SONAR_PROJECT_KEY}" \ | jq '.paging.total') if [[ "$PROJECT_COUNT" == "0" ]]; then echo "Project '${SONAR_PROJECT_KEY}' not found — creating it..." curl -sf -X POST \ -u "${SONAR_ADMIN_TOKEN}:" \ "${SONAR_HOST_URL}/api/projects/create" \ --data-urlencode "name=${SONAR_PROJECT_KEY}" \ --data-urlencode "project=${SONAR_PROJECT_KEY}" \ --data-urlencode "mainBranch=main" \ --data-urlencode "visibility=private" echo "✅ Project created." else echo "✅ Project '${SONAR_PROJECT_KEY}' already exists — skipping creation." fi # ---- 2. Revoke any leftover token from a previous failed run ---- TOKEN_NAME="gitea-scan-${SONAR_PROJECT_KEY}" curl -sf -X POST \ -u "${SONAR_ADMIN_TOKEN}:" \ "${SONAR_HOST_URL}/api/user_tokens/revoke" \ --data-urlencode "name=${TOKEN_NAME}" \ > /dev/null 2>&1 || true # ---- 3. Generate a fresh PROJECT_ANALYSIS_TOKEN for this run ---- SCAN_TOKEN=$(curl -sf -X POST \ -u "${SONAR_ADMIN_TOKEN}:" \ "${SONAR_HOST_URL}/api/user_tokens/generate" \ --data-urlencode "name=${TOKEN_NAME}" \ --data-urlencode "type=PROJECT_ANALYSIS_TOKEN" \ --data-urlencode "projectKey=${SONAR_PROJECT_KEY}" \ | jq -r '.token') [[ -n "$SCAN_TOKEN" ]] || { echo "::error::Failed to generate scan token"; exit 1; } echo "✅ Scan token generated for project '${SONAR_PROJECT_KEY}'" echo "::add-mask::${SCAN_TOKEN}" echo "SCAN_TOKEN=${SCAN_TOKEN}" >> "$GITHUB_ENV" - name: Build and test run: | ./mvnw -B verify \ -Dtest='!PostgresIntegrationTests,!MySqlIntegrationTests' - name: SonarQube analysis env: SCAN_TOKEN: SONAR_HOST_URL: run: | ./mvnw -B org.sonarsource.scanner.maven:sonar-maven-plugin:4.0.0.4121:sonar \ -Dsonar.projectKey="${SONAR_PROJECT_KEY}" \ -Dsonar.host.url="${SONAR_HOST_URL}" \ -Dsonar.token="${SCAN_TOKEN}" \ -Dsonar.java.source=17 \ -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml - name: Quality Gate check env: SCAN_TOKEN: SONAR_HOST_URL: run: | echo "Waiting for SonarQube to process the analysis..." for i in $(seq 1 24); do RESPONSE=$(curl -sf -u "${SCAN_TOKEN}:" \ "${SONAR_HOST_URL}/api/qualitygates/project_status?projectKey=${SONAR_PROJECT_KEY}" || true) STATUS=$(echo "$RESPONSE" | jq -r '.projectStatus.status' 2>/dev/null || echo "NONE") if [[ "$STATUS" =~ ^(OK|ERROR|WARN)$ ]]; then break; fi echo " Status: ${STATUS:-pending} — retrying in 5s..." sleep 5 done echo "Quality Gate status: $STATUS" [[ "$STATUS" != "ERROR" ]] || { echo "::error::Quality Gate FAILED"; exit 1; } - name: Revoke scan token if: always() env: SONAR_ADMIN_TOKEN: SONAR_HOST_URL: run: | curl -sf -X POST \ -u "${SONAR_ADMIN_TOKEN}:" \ "${SONAR_HOST_URL}/api/user_tokens/revoke" \ --data-urlencode "name=gitea-scan-${SONAR_PROJECT_KEY}" \ && echo "✅ Scan token revoked" \ || echo "⚠️ Token revocation failed (may have already been removed)"