Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions .github/workflows/sonarcloud_pr.yml

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach could be having "modules" of form reusable-*.yml plugged into ci-cd.yml.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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 job that uses the reusable-*yml like https://github.com/ansible/awx_plugins.interfaces/blob/devel/.github/workflows/ci-cd.yml#L336? Do we want them stored under /reusable or in the potential new shared repo for these workflows?

workflows:
- "🧪"
types:
- completed
permissions:
Comment thread
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
Comment thread
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
Comment thread
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) || '' }}
132 changes: 132 additions & 0 deletions sonar-project.properties
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
Loading