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
119 changes: 119 additions & 0 deletions .github/workflows/coverage_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
name: coverage_tests

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

on: # yamllint disable-line rule:truthy
pull_request:
branches:
- main
- stable-*
push:
branches:
- main
- stable-*
workflow_dispatch:

jobs:
coverage:
name: Integration test coverage
runs-on: ubuntu-latest
env:
ANSIBLE_CORE_REF: "milestone"
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# ansible-test requires cwd under .../ansible_collections/{namespace}/{name}/
path: ansible_collections/cloud/aws_ops

- name: Checkout amazon.aws dependency
uses: ansible-network/github_actions/.github/actions/checkout_dependency@47822b51aee957080e811cc434443c4e3bb9569a
with:
repository: ansible-collections/amazon.aws
path: ansible_collections/amazon/aws
ref: main

- name: Checkout community.aws dependency
uses: ansible-network/github_actions/.github/actions/checkout_dependency@47822b51aee957080e811cc434443c4e3bb9569a
with:
repository: ansible-collections/community.aws
path: ansible_collections/community/aws
ref: main

- name: Checkout community.crypto dependency
uses: ansible-network/github_actions/.github/actions/checkout_dependency@47822b51aee957080e811cc434443c4e3bb9569a
with:
repository: ansible-collections/community.crypto
path: ansible_collections/community/crypto
ref: main

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install Ansible (ansible-test)
run: |
python -m pip install --upgrade pip
python -m pip install "https://github.com/ansible/ansible/archive/${ANSIBLE_CORE_REF}.tar.gz"

- name: Install integration test dependencies
run: |
python -m pip install -r ansible_collections/cloud/aws_ops/tests/integration/constraints.txt -r ansible_collections/cloud/aws_ops/tests/integration/requirements.txt

- name: Create AWS/sts session credentials
uses: ansible/ansible-test-auth@008750a5bf07124c3ab86d30d73d7319d7518f36 # v1.0.0, committed on Mar 13, 2026

- name: Run integration tests with coverage
working-directory: ansible_collections/cloud/aws_ops
run: ansible-test integration --venv --coverage --python 3.12 --requirements

- name: Combine and emit coverage XML
working-directory: ansible_collections/cloud/aws_ops
run: |
ansible-test coverage combine --venv --python 3.12 --requirements
ansible-test coverage xml --venv --python 3.12 --requirements

- name: Prepare coverage.xml for SonarCloud
env:
COLLECTION_DIR: ${{ github.workspace }}/ansible_collections/cloud/aws_ops
run: |
set -euo pipefail
xml=$(find "${COLLECTION_DIR}/tests/output/reports" -maxdepth 1 -name '*.xml' ! -name '*powershell*' | head -1)
if [[ -z "${xml}" ]]; then
echo "::error::coverage XML not found under tests/output/reports"
exit 1
fi
cp "${xml}" "${GITHUB_WORKSPACE}/coverage.xml"
sed -i "s#${GITHUB_WORKSPACE}/##g" "${GITHUB_WORKSPACE}/coverage.xml"
sed -i 's#ansible_collections/cloud/aws_ops/##g' "${GITHUB_WORKSPACE}/coverage.xml"
if ! grep -q '<coverage' "${GITHUB_WORKSPACE}/coverage.xml"; then
echo "::error::coverage.xml does not look like Cobertura XML"
exit 1
fi
test -s "${GITHUB_WORKSPACE}/coverage.xml"

- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage
path: ${{ github.workspace }}/coverage.xml

coverage_tests:
name: SonarCloud scan
permissions:
contents: read
pull-requests: read
needs:
- coverage
if: >-
${{ needs.coverage.result == 'success'
&& (github.event_name == 'push'
|| (github.event_name == 'pull_request'
&& github.event.pull_request.head.repo.full_name == github.repository)) }}
uses: ./.github/workflows/sonarcloud.yml
secrets:
ANSIBLE_COLLECTIONS_ORG_SONAR_TOKEN_CICD_BOT: ${{ secrets.ANSIBLE_COLLECTIONS_ORG_SONAR_TOKEN_CICD_BOT }}
69 changes: 48 additions & 21 deletions .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,68 @@
---
# SonarCloud analysis for cloud.aws_ops
## SonarCloud scan (reusable)
#
# Uses the same-repo + default-branch push model: GitHub does not expose org secrets to workflows
# from fork PRs (see https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions).
# This job is gated so the Sonar token is never available in untrusted fork contexts. A follow-up
# workflow triggered by workflow_run + artifacts is an alternative if the org later requires Sonar
# with coverage on fork PRs (see README.md and https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run).
# Invoked from **coverage_test** after the aggregate gate and **coverage** succeed. Uses the **caller's**
# **pull_request** / **push** event so **actions/checkout** can use **github.event.pull_request.head.sha**
# on PRs (Sonar-compliant). Not triggered by **workflow_run** + **workflow_run.head_sha** checkout.

---
name: SonarCloud

on:
push:
branches:
- main
- stable-*
pull_request:
branches:
- main
- stable-*
workflow_dispatch:
workflow_call:
secrets:
ANSIBLE_COLLECTIONS_ORG_SONAR_TOKEN_CICD_BOT:
required: true

permissions:
contents: read
pull-requests: read

jobs:
sonarqube:
name: SonarCloud Scan
scan:
name: SonarCloud scan
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
steps:
- name: Checkout
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
show-progress: false

- name: Download coverage artifacts
uses: actions/download-artifact@v4
with:
name: coverage
path: .

- name: Set coverage report paths
run: |
coverage_files=$(find . -name "coverage*.xml" -type f 2>/dev/null | tr '\n' ',' | sed 's/,$//')
echo "Found coverage files: ${coverage_files:-none}"
echo "COVERAGE_PATHS=${coverage_files}" >> "$GITHUB_ENV"

- name: Prepare SonarCloud args
env:
COMMIT_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }}
PR_HEAD_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || '' }}
PR_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || '' }}
run: |
SONAR_ARGS="-Dsonar.scm.revision=\"${COMMIT_SHA}\""
if [[ "${EVENT_NAME}" == "pull_request" ]]; then
SONAR_ARGS="${SONAR_ARGS} -Dsonar.pullrequest.key=${PR_NUMBER}"
SONAR_ARGS="${SONAR_ARGS} -Dsonar.pullrequest.branch=${PR_HEAD_REF}"
SONAR_ARGS="${SONAR_ARGS} -Dsonar.pullrequest.base=${PR_BASE_REF}"
fi
if [[ -n "${COVERAGE_PATHS:-}" ]]; then
SONAR_ARGS="${SONAR_ARGS} -Dsonar.python.coverage.reportPaths=${COVERAGE_PATHS}"
fi
echo "SONAR_ARGS=${SONAR_ARGS}" >> "$GITHUB_ENV"

- name: SonarCloud Scan
# Same pinned version as ansible-collections/amazon.aws sonarcloud.yml
uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9
env:
SONAR_TOKEN: ${{ secrets.ANSIBLE_COLLECTIONS_ORG_SONAR_TOKEN_CICD_BOT }}
with:
args: ${{ env.SONAR_ARGS }}
9 changes: 9 additions & 0 deletions CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,12 @@ Integration tests run on real AWS infrastructure and require the "safe to test"
- Integration targets are automatically split across 2 parallel jobs
- Split is determined by `ansible_test_splitter` action based on changed files
- Each job runs the subset of tests relevant to the PR's changes

### SonarCloud and coverage

| Workflow | Description |
| -------- | ----------- |
| [Integration](.github/workflows/integration.yml) | Integration tests only (no coverage) |
| [SonarCloud](.github/workflows/sonarcloud.yml) | Separate integration run with `--coverage`, **`coverage`** job, then **`finalize`** Sonar scan |

This collection does not use an **`all_green`** gate. Coverage for Sonar is **not** produced by the Integration workflow. Details: [SONARCLOUD.md](SONARCLOUD.md).
23 changes: 10 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=redhat-cop_cloud.aws_ops&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=redhat-cop_cloud.aws_ops)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=redhat-cop_cloud.aws_ops&metric=coverage)](https://sonarcloud.io/summary/new_code?id=redhat-cop_cloud.aws_ops)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=redhat-cop_cloud.aws_ops&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=redhat-cop_cloud.aws_ops)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=redhat-cop_cloud.aws_ops&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=redhat-cop_cloud.aws_ops)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=redhat-cop_cloud.aws_ops&metric=bugs)](https://sonarcloud.io/summary/new_code?id=redhat-cop_cloud.aws_ops)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=redhat-cop_cloud.aws_ops&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=redhat-cop_cloud.aws_ops)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=redhat-cop_cloud.aws_ops&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=redhat-cop_cloud.aws_ops)

# cloud.aws_ops Validated Content Collection

This repository hosts the `cloud.aws_ops` Ansible Collection.

## SonarCloud (code quality)

Static analysis runs on [SonarCloud](https://sonarcloud.io) using `sonar-project.properties` and
`.github/workflows/sonarcloud.yml`. **Note:** Coverage in SonarCloud is not wired yet.

The SonarCloud project key must match `sonar.projectKey` (`ansible-collections_cloud.aws_ops`). Adding
or renaming the project is coordinated via Ansible Collections maintainers.

GitHub does not expose organization secrets to workflows for pull requests opened from forks. The
Sonar job therefore only runs on pushes to this repository's branches and on pull requests where the
head branch is on `redhat-cop/cloud.aws_ops` (not from forks). That matches GitHub's
documented behavior for [secrets in Actions](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions).
Static analysis and **integration-test coverage** are reported on [SonarCloud](https://sonarcloud.io/project/overview?id=redhat-cop_cloud.aws_ops). Coverage is collected in [.github/workflows/sonarcloud.yml](.github/workflows/sonarcloud.yml), separate from [.github/workflows/integration.yml](.github/workflows/integration.yml). See [SONARCLOUD.md](SONARCLOUD.md).

If the project later needs Sonar with coverage on **fork** PRs, maintainers typically add a separate
trusted job after a workflow that uploads coverage artifacts, using GitHub's `workflow_run` event.
See [workflow_run (GitHub Docs)](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run).
The SonarCloud project key must match `sonar.projectKey` in [sonar-project.properties](sonar-project.properties) (`redhat-cop_cloud.aws_ops`).

## Description

Expand Down
30 changes: 30 additions & 0 deletions SONARCLOUD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# SonarCloud

Dashboard:

[SonarCloud project overview](https://sonarcloud.io/project/overview?id=redhat-cop_cloud.aws_ops)

## CI integration

Sonar analysis and **coverage collection** live in **[.github/workflows/sonarcloud.yml](.github/workflows/sonarcloud.yml)** only. They are **not** part of **[.github/workflows/integration.yml](.github/workflows/integration.yml)** or an **`all_green`** aggregator.

| Workflow | Role |
| -------- | ---- |
| [integration.yml](.github/workflows/integration.yml) | Standard integration tests (`ansible_test_integration`, no coverage) |
| [sonarcloud.yml](.github/workflows/sonarcloud.yml) | Same PR gates and targets, but `ansible-test integration --coverage`; **`coverage`** job emits `coverage.xml`; **`finalize`** runs the Sonar scanner |

Both workflows use **`pull_request_target`**, the **safe to test** label, and the same splitter/AWS setup. A labeled PR therefore runs integration tests twice (once per workflow); only the SonarCloud workflow produces coverage for Sonar.

### SonarCloud workflow jobs

- **`coverage-test`** (matrix) — integration tests with `--coverage`; uploads **`coverage-raw-*`** artifacts
- **`coverage`** — `ansible-test coverage combine` / `coverage xml`, path rewrite, upload artifact **`coverage`**
- **`finalize`** — downloads **`coverage`**, sets **`sonar.python.coverage.reportPaths`**, runs **`SonarSource/sonarqube-scan-action`** (same-repo PRs only, when org secret is set)

Scanner configuration: [sonar-project.properties](sonar-project.properties) (`sonar.projectKey=redhat-cop_cloud.aws_ops`, `sonar.tests=tests/integration`).

**`finalize`** uses org secret **`ANSIBLE_COLLECTIONS_ORG_SONAR_TOKEN_CICD_BOT`** and is gated so the token is not used for fork-head PRs. See GitHub [secrets in Actions](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions).

## Branch protection (repository settings)

If **SonarCloud** / **finalize** should block merges, add the check under **Settings** > **Branches** > **Required status checks**. That is not configured in YAML.
4 changes: 2 additions & 2 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# SonarCloud project configuration for cloud.aws_ops
# Complete documentation: https://docs.sonarqube.org/latest/analysis/analysis-parameters/

sonar.projectKey=ansible-collections_cloud.aws_ops
sonar.organization=ansible-collections
sonar.projectKey=redhat-cop_cloud.aws_ops
sonar.organization=redhat-cop
sonar.sources=.
sonar.projectName=cloud.aws_ops
sonar.python.coverage.reportPaths=coverage.xml
Expand Down
Loading