-
Notifications
You must be signed in to change notification settings - Fork 7
AAP-81136 - Add workflow for SonarCloud reporting #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: devel
Are you sure you want to change the base?
Changes from all commits
c2dc2c2
9686eb5
f15ac64
51dc554
afce158
de25621
1df2b59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This piece of automation doesn't seem unique enough to be attached to the project — I'd maybe look into having a composite action or a reusable workflow in a shared repository rather than here.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I agree that it should be, the ask to implement sonar has been quick and delivery wants to be quick. I do think its something that we could look into but I think that would need to be at an org level. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| --- | ||
| # SonarCloud Analysis Workflow for awx_plugins.interfaces | ||
| # | ||
| # 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 → 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another approach could be having "modules" of form
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have an example of what this would look like? Would they be a |
||
| workflows: | ||
| - "🧪" | ||
| types: | ||
| - completed | ||
| permissions: | ||
|
jessicamack marked this conversation as resolved.
|
||
| contents: read | ||
| actions: read | ||
| pull-requests: read | ||
| jobs: | ||
| sonar-pr-analysis: | ||
| name: SonarCloud PR Analysis | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| if: | | ||
| github.event.workflow_run.conclusion == 'success' && | ||
| github.event.workflow_run.event == 'pull_request' && | ||
| github.repository == 'ansible/awx_plugins.interfaces' | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| # Download all individual coverage artifacts from CI | ||
| - name: Download coverage artifacts | ||
| uses: >- | ||
| dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 | ||
|
jessicamack marked this conversation as resolved.
|
||
| with: | ||
| github_token: ${{ secrets.GITHUB_TOKEN }} | ||
| workflow: CI | ||
| run_id: ${{ github.event.workflow_run.id }} | ||
| pattern: api-test-artifacts | ||
| if_no_artifact_found: ignore | ||
| continue-on-error: true | ||
|
|
||
| # 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 workflow_run event | ||
| PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}" | ||
|
|
||
| if [ -z "$PR_NUMBER" ]; then | ||
| echo "##[error]❌ FATAL: PR number not found in workflow_run event" | ||
| echo "##[error]This job requires a PR number to run PR analysis." | ||
| 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" | ||
| echo "PR_BASE=$PR_BASE" | ||
| echo "PR_HEAD=$PR_HEAD" | ||
| echo "COMMIT_SHA=$COMMIT_SHA" | ||
| echo "REPO_NAME=$REPO_NAME" | ||
| } >> "$GITHUB_ENV" | ||
|
|
||
| # Get all changed files from PR (with error handling) | ||
| # PR_NUMBER is guaranteed non-empty here (fatal exit) | ||
| files="" | ||
| if gh api "repos/$REPO_NAME/pulls/$PR_NUMBER/files" \ | ||
| --paginate --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" | ||
| if [ -n "$coverage_files" ]; then | ||
| echo "├── Coverage Data: ✅ Available" | ||
| else | ||
| echo "├── Coverage Data: ⚠️ Not available" | ||
| fi | ||
| echo "└── Result: ✅ Running SonarCloud (full scan)" | ||
| # No files = no inclusions filter = full scan | ||
| exit 0 | ||
| fi | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| # 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" | ||
| fi | ||
|
|
||
| # Prepare file list for Sonar | ||
| echo "All changed files in PR:" | ||
| echo "$files" | ||
|
|
||
| # Set changed files as inclusions for SonarCloud PR analysis | ||
| # This focuses the scan on modified files only, improving performance | ||
| # Note: sonar-project.properties contains the project-wide exclusions | ||
| if [ -n "$files" ]; then | ||
| inclusions=$(echo "$files" | tr '\n' ',' | sed 's/,$//') | ||
| echo "SONAR_INCLUSIONS=$inclusions" >> "$GITHUB_ENV" | ||
| echo "└── Result: ✅ Will scan $file_count changed file(s)" | ||
| else | ||
| echo "└── Result: ✅ Running SonarCloud (full scan)" | ||
| fi | ||
|
|
||
| - name: Prepare repository for PR analysis | ||
| if: env.PR_NUMBER != '' | ||
| run: | | ||
| # Validate PR_NUMBER is numeric to prevent command injection | ||
| if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | ||
| echo "##[error]Invalid PR number: $PR_NUMBER" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Fetch the PR branch for SonarCloud PR analysis | ||
| git fetch origin "$PR_BASE" | ||
|
|
||
| # Checkout the exact commit that CI tested | ||
| # This ensures SonarCloud analyzes CI-validated code | ||
| git checkout "$COMMIT_SHA" | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: SonarCloud Scan | ||
| uses: >- | ||
| SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 | ||
| 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 }} | ||
| ${{ env.COVERAGE_PATHS && | ||
| format('-Dsonar.python.coverage.reportPaths={0}', | ||
| env.COVERAGE_PATHS) || '' }} | ||
| ${{ env.SONAR_INCLUSIONS && | ||
| format('-Dsonar.inclusions={0}', | ||
| env.SONAR_INCLUSIONS) || '' }} | ||
|
|
||
| sonar-branch-analysis: | ||
| name: SonarCloud Branch Analysis | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| if: | | ||
| github.event_name == 'workflow_run' && | ||
| github.event.workflow_run.conclusion == 'success' && | ||
| github.event.workflow_run.event == 'push' && | ||
| github.repository == 'ansible/awx_plugins.interfaces' | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.workflow_run.head_sha }} | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| # Download all coverage artifacts from CI (optional) | ||
| - 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 scan)" | ||
| echo "└── Result: ✅ Proceed - Running SonarCloud" | ||
|
|
||
| - name: SonarCloud Scan | ||
| uses: >- | ||
| SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 | ||
| 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) || '' }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| # SonarCloud project configuration for awx_plugins.interfaces | ||
| # Complete documentation: https://docs.sonarqube.org/latest/analysis/analysis-parameters/ | ||
|
|
||
| # ============================================================================= | ||
| # PROJECT IDENTIFICATION (REQUIRED) | ||
| # ============================================================================= | ||
|
|
||
| # The unique project identifier. This is mandatory. | ||
| # Do not duplicate or reuse! | ||
| # Available characters: [a-zA-Z0-9_:\.\-] | ||
| # Must have least one non-digit. | ||
| sonar.projectKey=ansible_awx_plugins.interfaces | ||
| sonar.organization=ansible | ||
|
|
||
| # Project metadata | ||
| sonar.projectName=awx_plugins.interfaces | ||
|
|
||
| # ============================================================================= | ||
| # SOURCE AND TEST CONFIGURATION | ||
| # ============================================================================= | ||
|
|
||
| # Source directories to analyze | ||
| sonar.sources=. | ||
|
|
||
| # Test directories | ||
| sonar.tests=tests | ||
|
|
||
| # Test file patterns | ||
| sonar.test.inclusions=\ | ||
| **/*_test.py,\ | ||
| **/tests/**/*.py | ||
|
|
||
| # Set branch-specific new code definition | ||
| # | ||
| # This is important to always check against the main branch for new PRs, | ||
| # otherwise the PR may fail during backporting, since the old version of the code | ||
| # may not respect the minimum requirements for the existing Quality Gate. | ||
| sonar.newCode.referenceBranch=devel | ||
|
|
||
| # ============================================================================= | ||
| # LANGUAGE CONFIGURATION | ||
| # ============================================================================= | ||
|
|
||
| # Python versions supported by the project | ||
| #sonar.python.version=3.9,3.10,3.11 | ||
|
|
||
| # File encoding | ||
| sonar.sourceEncoding=UTF-8 | ||
|
|
||
| # ============================================================================= | ||
| # REPORTS AND COVERAGE | ||
| # ============================================================================= | ||
|
|
||
| # Test and coverage reports (paths relative to project root) | ||
| sonar.python.coverage.reportPaths=reports/coverage.xml,awxkit/coverage.xml | ||
| sonar.python.xunit.reportPath=reports/junit.xml | ||
|
|
||
| # External tool reports (add these paths when tools are configured) | ||
| # sonar.python.pylint.reportPaths=reports/pylint-report.txt | ||
| # sonar.python.bandit.reportPaths=reports/bandit-report.json | ||
| # sonar.python.mypy.reportPath=reports/mypy-report.txt | ||
| # sonar.python.flake8.reportPaths=reports/flake8-report.txt | ||
| # sonar.python.xunit.reportPath=reports/junit.xml | ||
|
|
||
| # ============================================================================= | ||
| # EXCLUSIONS - FILES AND DIRECTORIES TO IGNORE | ||
| # ============================================================================= | ||
|
|
||
| # General exclusions - files and directories to ignore from analysis | ||
| sonar.exclusions=\ | ||
| **/tests/**,\ | ||
| **/__pycache__/**,\ | ||
| **/*.pyc,\ | ||
| **/*.pyo,\ | ||
| **/*.pyd,\ | ||
| **/build/**,\ | ||
| **/dist/**,\ | ||
| **/*.egg-info/**,\ | ||
| **/download-json.py,\ | ||
| docs/docsite/conf.py | ||
|
|
||
| # ============================================================================= | ||
| # COVERAGE EXCLUSIONS | ||
| # ============================================================================= | ||
|
|
||
| # Files to exclude from coverage calculations | ||
| sonar.coverage.exclusions=\ | ||
| **/tests/**,\ | ||
| **/.tox/**,\ | ||
| **/test_*.py,\ | ||
| **/*_test.py | ||
|
|
||
| # ============================================================================= | ||
| # DUPLICATION EXCLUSIONS | ||
| # ============================================================================= | ||
|
|
||
| # Ignore code duplication in migrations and tests | ||
| sonar.cpd.exclusions=\ | ||
| **/tests/** | ||
|
|
||
| # ============================================================================= | ||
| # ISSUE IGNORE RULES | ||
| # ============================================================================= | ||
|
|
||
| # Ignore specific rules for certain file patterns | ||
| sonar.issue.ignore.multicriteria=e1 | ||
| # Ignore "should be a variable" in migrations | ||
| sonar.issue.ignore.multicriteria.e1.ruleKey=python:S1192 | ||
|
|
||
| # ============================================================================= | ||
| # GITHUB INTEGRATION | ||
| # ============================================================================= | ||
|
|
||
| # The following properties are automatically handled by GitHub Actions: | ||
| # sonar.pullrequest.key - handled automatically | ||
| # sonar.pullrequest.branch - handled automatically | ||
| # sonar.pullrequest.base - handled automatically | ||
|
|
||
| # ============================================================================= | ||
| # DEBUGGING | ||
| # ============================================================================= | ||
|
|
||
| # These are aggressive settings to ensure maximum detection | ||
| # do not use in production | ||
|
|
||
| # sonar.verbose=true | ||
| # sonar.log.level=DEBUG | ||
| # sonar.scm.exclusions.disabled=true | ||
| # sonar.java.skipUnchanged=false | ||
| # sonar.scm.forceReloadAll=true | ||
| # sonar.filesize.limit=100 | ||
| # sonar.qualitygate.wait=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(personal request, preexisting convention)
Could you use kebab-case style name for this file?