diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7413966..5ab2cef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -116,6 +116,38 @@ jobs: - name: Scan for secrets run: gitleaks detect --source . --redact --verbose + # Coverage upload to Codecov via GitHub OIDC (tokenless -- no CODECOV_TOKEN). This makes + # the template repo its own Codecov project AND is the canary that validates the OIDC + # path the template now ships to generated public repos. Deliberately NOT in the + # ci-passed roll-up: a Codecov outage (the thing under test) must not block merges. + coverage: + name: Coverage (Codecov via OIDC) + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: "pyproject.toml" + + - name: Set up Python + run: uv python install 3.12 + + - name: Run tests with coverage + run: uv run --group tests pytest tests/ -m "not slow and not integration" -n auto --cov=tests --cov-report=xml --cov-fail-under=0 + + - name: Upload coverage to Codecov (tokenless via OIDC) + uses: codecov/codecov-action@v7 + with: + use_oidc: true + files: coverage.xml + fail_ci_if_error: true + # Single required status check. The branch ruleset requires this one job, not the # individual jobs above: test-fast is a matrix (its check-run names vary by OS/Python and # differ between draft and ready PRs) and test-full is skipped on draft PRs, so neither diff --git a/template/.github/{% if include_actions %}workflows{% endif %}/nightly.yml.jinja b/template/.github/{% if include_actions %}workflows{% endif %}/nightly.yml.jinja index a8ed950..00c5d0e 100644 --- a/template/.github/{% if include_actions %}workflows{% endif %}/nightly.yml.jinja +++ b/template/.github/{% if include_actions %}workflows{% endif %}/nightly.yml.jinja @@ -19,7 +19,11 @@ jobs: {%- for v in versions if v >= min_python_version and v <= max_python_version -%} "{{ v }}"{{ ", " if not loop.last else "" }} {%- endfor -%}] -{% if include_codecov %} env: +{% if include_codecov and repo_visibility == 'public' %} # Codecov upload authenticates via GitHub OIDC (tokenless) -- no stored CODECOV_TOKEN. + permissions: + contents: read + id-token: write +{% elif include_codecov %} env: CODECOV_TOKEN: {% raw %}${{ secrets.CODECOV_TOKEN }}{% endraw %} {% endif %} steps: - uses: actions/checkout@v7 @@ -47,10 +51,11 @@ jobs: {% if include_codecov %} - name: Upload coverage reports uses: codecov/codecov-action@v7 - if: {% raw %}${{ matrix.python-version == '3.12' && env.CODECOV_TOKEN != '' }}{% endraw %} + if: {% raw %}${{ matrix.python-version == '3.12'{% endraw %}{% if repo_visibility != 'public' %}{% raw %} && env.CODECOV_TOKEN != ''{% endraw %}{% endif %}{% raw %} }}{% endraw %} with: - token: {% raw %}${{ env.CODECOV_TOKEN }}{% endraw %} -{% endif %} +{% if repo_visibility == 'public' %} use_oidc: true +{% else %} token: {% raw %}${{ env.CODECOV_TOKEN }}{% endraw %} +{% endif %}{% endif %} create-issue-on-failure: needs: test if: {% raw %}${{ failure() }}{% endraw %} diff --git a/template/.github/{% if include_actions %}workflows{% endif %}/tests.yml.jinja b/template/.github/{% if include_actions %}workflows{% endif %}/tests.yml.jinja index d1b053a..9718635 100644 --- a/template/.github/{% if include_actions %}workflows{% endif %}/tests.yml.jinja +++ b/template/.github/{% if include_actions %}workflows{% endif %}/tests.yml.jinja @@ -107,7 +107,11 @@ jobs: {%- for v in versions if v >= min_python_version and v <= max_python_version -%} "{{ v }}"{{ ", " if not loop.last else "" }} {%- endfor -%}] -{% if include_codecov %} env: +{% if include_codecov and repo_visibility == 'public' %} # Codecov upload authenticates via GitHub OIDC (tokenless) -- no stored CODECOV_TOKEN. + permissions: + contents: read + id-token: write +{% elif include_codecov %} env: CODECOV_TOKEN: {% raw %}${{ secrets.CODECOV_TOKEN }}{% endraw %} {% endif %} steps: @@ -140,18 +144,20 @@ jobs: run: nox -s test --python {% raw %}${{ matrix.python-version }}{% endraw %} {% if include_codecov %} - name: Upload test results to Codecov - if: {% raw %}${{ !cancelled() && matrix.python-version == '{% endraw %}{{ min_python_version }}{% raw %}' && env.CODECOV_TOKEN != '' }}{% endraw %} + if: {% raw %}${{ !cancelled() && matrix.python-version == '{% endraw %}{{ min_python_version }}{% raw %}'{% endraw %}{% if repo_visibility != 'public' %}{% raw %} && env.CODECOV_TOKEN != ''{% endraw %}{% endif %}{% raw %} }}{% endraw %} uses: codecov/test-results-action@v1 with: - token: {% raw %}${{ env.CODECOV_TOKEN }}{% endraw %} - file: {% raw %}'${{ github.workspace }}/junit.${{ matrix.python-version }}.xml'{% endraw %} +{% if repo_visibility == 'public' %} use_oidc: true +{% else %} token: {% raw %}${{ env.CODECOV_TOKEN }}{% endraw %} +{% endif %} file: {% raw %}'${{ github.workspace }}/junit.${{ matrix.python-version }}.xml'{% endraw %} - name: Upload coverage report to Codecov uses: codecov/codecov-action@v7 - if: {% raw %}${{ matrix.python-version == '{% endraw %}{{ min_python_version }}{% raw %}' && env.CODECOV_TOKEN != '' }}{% endraw %} + if: {% raw %}${{ matrix.python-version == '{% endraw %}{{ min_python_version }}{% raw %}'{% endraw %}{% if repo_visibility != 'public' %}{% raw %} && env.CODECOV_TOKEN != ''{% endraw %}{% endif %}{% raw %} }}{% endraw %} with: - token: {% raw %}${{ env.CODECOV_TOKEN }}{% endraw %} - files: {% raw %}'${{ github.workspace }}/coverage.${{ matrix.python-version }}.xml'{% endraw %} +{% if repo_visibility == 'public' %} use_oidc: true +{% else %} token: {% raw %}${{ env.CODECOV_TOKEN }}{% endraw %} +{% endif %} files: {% raw %}'${{ github.workspace }}/coverage.${{ matrix.python-version }}.xml'{% endraw %} env_vars: OS,PYTHON fail_ci_if_error: true {% endif %}