SonarCloud #217
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SonarCloud Analysis Workflow for awx | |
| # | |
| # This workflow runs SonarCloud analysis triggered by CI workflow completion. | |
| # It is split into two separate jobs for clarity and maintainability: | |
| # | |
| # FLOW: CI completes → workflow_run triggers this workflow → appropriate job runs | |
| # | |
| # JOB 1: sonar-pr-analysis (for PRs) | |
| # - Triggered by: workflow_run (CI on pull_request) | |
| # - Steps: Download coverage → Get PR info → Get changed files → Run SonarCloud PR analysis | |
| # - Scans: All changed files in the PR (Python, YAML, JSON, etc.) | |
| # - Quality gate: Focuses on new/changed code in PR only | |
| # | |
| # JOB 2: sonar-branch-analysis (for long-lived branches) | |
| # - Triggered by: workflow_run (CI on push to devel) | |
| # - Steps: Download coverage → Run SonarCloud branch analysis | |
| # - Scans: Full codebase | |
| # - Quality gate: Focuses on overall project health | |
| # | |
| # This ensures coverage data is always available from CI before analysis runs. | |
| # | |
| # What files are scanned: | |
| # - All files in the repository that SonarCloud can analyze | |
| # - Excludes: tests, scripts, dev environments, external collections (see sonar-project.properties) | |
| # With much help from: | |
| # https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/30 | |
| # https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/32 | |
| name: SonarCloud | |
| on: | |
| workflow_run: # This is triggered by CI being completed. | |
| workflows: | |
| - CI | |
| types: | |
| - completed | |
| permissions: read-all | |
| jobs: | |
| sonar-pr-analysis: | |
| name: SonarCloud PR Analysis | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'pull_request' && | |
| github.repository == 'ansible/awx' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Download all individual coverage artifacts from CI workflow | |
| - name: Download coverage artifacts | |
| uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| workflow: CI | |
| run_id: ${{ github.event.workflow_run.id }} | |
| pattern: api-test-artifacts | |
| # Extract PR metadata from workflow_run event | |
| - name: Set PR metadata and prepare files for analysis | |
| env: | |
| COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} | |
| REPO_NAME: ${{ github.event.repository.full_name }} | |
| HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Find all downloaded coverage XML files | |
| coverage_files=$(find . -name "coverage.xml" -type f | tr '\n' ',' | sed 's/,$//') | |
| echo "Found coverage files: $coverage_files" | |
| echo "COVERAGE_PATHS=$coverage_files" >> $GITHUB_ENV | |
| # Extract PR number from first coverage.xml file found | |
| first_coverage=$(find . -name "coverage.xml" -type f | head -1) | |
| if [ -f "$first_coverage" ]; then | |
| PR_NUMBER=$(grep -m 1 '<!-- PR' "$first_coverage" | awk '{print $3}' || echo "") | |
| else | |
| PR_NUMBER="" | |
| fi | |
| echo "🔍 SonarCloud Analysis Decision Summary" | |
| echo "========================================" | |
| echo "├── CI Event: ✅ Pull Request" | |
| echo "├── PR Number from coverage.xml: #${PR_NUMBER:-<not found>}" | |
| if [ -z "$PR_NUMBER" ]; then | |
| echo "##[error]❌ FATAL: PR number not found in coverage.xml" | |
| echo "##[error]This job requires a PR number to run PR analysis." | |
| echo "##[error]The ci workflow should have injected the PR number into coverage.xml." | |
| exit 1 | |
| fi | |
| # Get PR metadata from GitHub API | |
| PR_DATA=$(gh api "repos/$REPO_NAME/pulls/$PR_NUMBER") | |
| PR_BASE=$(echo "$PR_DATA" | jq -r '.base.ref') | |
| PR_HEAD=$(echo "$PR_DATA" | jq -r '.head.ref') | |
| # Print summary | |
| echo "🔍 SonarCloud Analysis Decision Summary" | |
| echo "========================================" | |
| echo "├── CI Event: ✅ Pull Request" | |
| echo "├── PR Number: #$PR_NUMBER" | |
| echo "├── Base Branch: $PR_BASE" | |
| echo "├── Head Branch: $PR_HEAD" | |
| echo "├── Repo: $REPO_NAME" | |
| # Export to GitHub env for later steps | |
| echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV | |
| echo "PR_BASE=$PR_BASE" >> $GITHUB_ENV | |
| echo "PR_HEAD=$PR_HEAD" >> $GITHUB_ENV | |
| echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_ENV | |
| echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV | |
| # Get all changed files from PR (with error handling) | |
| files="" | |
| if [ -n "$PR_NUMBER" ]; then | |
| if gh api repos/$REPO_NAME/pulls/$PR_NUMBER/files --jq '.[].filename' > /tmp/pr_files.txt 2>/tmp/pr_error.txt; then | |
| files=$(cat /tmp/pr_files.txt) | |
| else | |
| echo "├── Changed Files: ⚠️ Could not fetch (likely test repo or PR not found)" | |
| if [ -f coverage.xml ] && [ -s coverage.xml ]; then | |
| echo "├── Coverage Data: ✅ Available" | |
| else | |
| echo "├── Coverage Data: ⚠️ Not available" | |
| fi | |
| echo "└── Result: ✅ Running SonarCloud analysis (full scan)" | |
| # No files = no inclusions filter = full scan | |
| exit 0 | |
| fi | |
| else | |
| echo "├── PR Number: ⚠️ Not available" | |
| if [ -f coverage.xml ] && [ -s coverage.xml ]; then | |
| echo "├── Coverage Data: ✅ Available" | |
| else | |
| echo "├── Coverage Data: ⚠️ Not available" | |
| fi | |
| echo "└── Result: ✅ Running SonarCloud analysis (full scan)" | |
| exit 0 | |
| fi | |
| # Get file extensions and count for summary | |
| extensions=$(echo "$files" | sed 's/.*\.//' | sort | uniq | tr '\n' ',' | sed 's/,$//') | |
| file_count=$(echo "$files" | wc -l) | |
| echo "├── Changed Files: $file_count file(s) (.${extensions})" | |
| # Check if coverage.xml exists and has content | |
| if [ -f coverage.xml ] && [ -s coverage.xml ]; then | |
| echo "├── Coverage Data: ✅ Available" | |
| else | |
| echo "├── Coverage Data: ⚠️ Not available (analysis will proceed without coverage)" | |
| fi | |
| # Prepare file list for Sonar | |
| echo "All changed files in PR:" | |
| echo "$files" | |
| # Filter out files that are excluded by .coveragerc to avoid coverage conflicts | |
| # This prevents SonarCloud from analyzing files that have no coverage data | |
| if [ -n "$files" ]; then | |
| # Filter out files matching .coveragerc omit patterns | |
| filtered_files=$(echo "$files" | grep -v "settings/.*_defaults\.py$" | grep -v "settings/defaults\.py$" | grep -v "main/migrations/") | |
| # Show which files were filtered out for transparency | |
| excluded_files=$(echo "$files" | grep -E "(settings/.*_defaults\.py$|settings/defaults\.py$|main/migrations/)" || true) | |
| if [ -n "$excluded_files" ]; then | |
| echo "├── Filtered out (coverage-excluded): $(echo "$excluded_files" | wc -l) file(s)" | |
| echo "$excluded_files" | sed 's/^/│ - /' | |
| fi | |
| if [ -n "$filtered_files" ]; then | |
| inclusions=$(echo "$filtered_files" | tr '\n' ',' | sed 's/,$//') | |
| echo "SONAR_INCLUSIONS=$inclusions" >> $GITHUB_ENV | |
| echo "└── Result: ✅ Will scan these files (excluding coverage-omitted files): $inclusions" | |
| else | |
| echo "└── Result: ✅ All changed files are excluded by coverage config, running full SonarCloud analysis" | |
| # Don't set SONAR_INCLUSIONS, let it scan everything per sonar-project.properties | |
| fi | |
| else | |
| echo "└── Result: ✅ Running SonarCloud analysis" | |
| fi | |
| - name: Add base branch | |
| if: env.PR_NUMBER != '' | |
| run: | | |
| gh pr checkout ${{ env.PR_NUMBER }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: SonarCloud Scan | |
| uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SONAR_TOKEN: ${{ secrets.CICD_ORG_SONAR_TOKEN_CICD_BOT }} | |
| with: | |
| args: > | |
| -Dsonar.scm.revision=${{ env.COMMIT_SHA }} | |
| -Dsonar.pullrequest.key=${{ env.PR_NUMBER }} | |
| -Dsonar.pullrequest.branch=${{ env.PR_HEAD }} | |
| -Dsonar.pullrequest.base=${{ env.PR_BASE }} | |
| -Dsonar.python.coverage.reportPaths=${{ env.COVERAGE_PATHS }} | |
| ${{ env.SONAR_INCLUSIONS && format('-Dsonar.inclusions={0}', env.SONAR_INCLUSIONS) || '' }} | |
| sonar-branch-analysis: | |
| name: SonarCloud Branch Analysis | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_run' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'push' && | |
| github.repository == 'ansible/awx' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Download all individual coverage artifacts from CI workflow (optional for branch pushes) | |
| - name: Download coverage artifacts | |
| continue-on-error: true | |
| uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| workflow: CI | |
| run_id: ${{ github.event.workflow_run.id }} | |
| pattern: api-test-artifacts | |
| - name: Print SonarCloud Analysis Summary | |
| env: | |
| BRANCH_NAME: ${{ github.event.workflow_run.head_branch }} | |
| run: | | |
| # Find all downloaded coverage XML files | |
| coverage_files=$(find . -name "coverage.xml" -type f | tr '\n' ',' | sed 's/,$//') | |
| echo "Found coverage files: $coverage_files" | |
| echo "COVERAGE_PATHS=$coverage_files" >> $GITHUB_ENV | |
| echo "🔍 SonarCloud Analysis Summary" | |
| echo "==============================" | |
| echo "├── CI Event: ✅ Push (via workflow_run)" | |
| echo "├── Branch: $BRANCH_NAME" | |
| echo "├── Coverage Files: ${coverage_files:-none}" | |
| echo "├── Python Changes: ➖ N/A (Full codebase scan)" | |
| echo "└── Result: ✅ Proceed - \"Running SonarCloud analysis\"" | |
| - name: SonarCloud Scan | |
| uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SONAR_TOKEN: ${{ secrets.CICD_ORG_SONAR_TOKEN_CICD_BOT }} | |
| with: | |
| args: > | |
| -Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }} | |
| -Dsonar.branch.name=${{ github.event.workflow_run.head_branch }} | |
| ${{ env.COVERAGE_PATHS && format('-Dsonar.python.coverage.reportPaths={0}', env.COVERAGE_PATHS) || '' }} |