Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ for family, grp in itertools.groupby(collected.checks.items(), key=lambda x: x[1
- [`GH102`](https://learn.scientific-python.org/development/guides/gha-basic#GH102): Auto-cancel on repeated PRs
- [`GH103`](https://learn.scientific-python.org/development/guides/gha-basic#GH103): At least one workflow with manual dispatch trigger
- [`GH104`](https://learn.scientific-python.org/development/guides/gha-wheels#GH104): Use unique names for upload-artifact
- [`GH105`](https://learn.scientific-python.org/development/guides/gha-basic#GH105): Use Trusted Publishing instead of token-based publishing on PyPI
- [`GH200`](https://learn.scientific-python.org/development/guides/gha-basic#GH200): Maintained by Dependabot
- [`GH210`](https://learn.scientific-python.org/development/guides/gha-basic#GH210): Maintains the GitHub action versions with Dependabot
- [`GH211`](https://learn.scientific-python.org/development/guides/gha-basic#GH211): Do not pin core actions as major versions
Expand Down
3 changes: 2 additions & 1 deletion docs/pages/guides/gha_basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ like the official actions and most other actions, but instead have `release/vX`
branches that you can use.

- [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish):
Publish Python packages to PyPI. Supports trusted publisher deployment.
Publish Python packages to PyPI. Prefer Trusted Publishing over token-based
uploads.
- [re-actors/alls-green](https://github.com/re-actors/alls-green): Tooling to
check to see if all jobs passed (supports allowed failures, too).

Expand Down
20 changes: 13 additions & 7 deletions docs/pages/guides/gha_pure.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,10 @@ later in the upload action for the release job, as well).
> The artifact it produces is named `Packages`, so that's what you need to use
> later to publish. This will be used instead of the manual steps below.

And then, you need a release job:
And then, you need a release job. Trusted Publishing is more secure and
recommended {% rr GH105 %}:

{% tabs %} {% tab oidc Trusted Publishing %}
{% tabs %} {% tab oidc Trusted Publishing (recommended) %}

{% raw %}

Expand Down Expand Up @@ -194,10 +195,10 @@ publish:

{% endraw %}

When you make a GitHub release in the web UI, we publish to PyPI. You'll need to
go to PyPI, generate a token for your user, and put it into `pypi_password` on
your repo's secrets page. Once you have a project, you should delete your
user-scoped token and generate a new project-scoped token.
If you cannot use Trusted Publishing, this publishes to PyPI with a token.
You'll need to go to PyPI, generate a token for your user, and put it into
`pypi_password` on your repo's secrets page. Once you have a project, you should
delete your user-scoped token and generate a new project-scoped token.

{% endtab %} {% endtabs %}

Expand All @@ -208,7 +209,7 @@ This can be used on almost any package with a standard
exactly how to build your package, hence all packages build exactly via the same
interface:

{% tabbodies %} {% tab oidc Trusted Publishing %}
{% tabbodies %} {% tab oidc Trusted Publishing (recommended) %}

{% raw %}

Expand Down Expand Up @@ -306,6 +307,11 @@ jobs:

{% endraw %}

If you cannot use Trusted Publishing, this publishes to PyPI with a token.
You'll need to go to PyPI, generate a token for your user, and put it into
`pypi_password` on your repo's secrets page. Once you have a project, you should
delete your user-scoped token and generate a new project-scoped token.

{% endtab %} {% endtabbodies %}

{% enddetails %}
Expand Down
12 changes: 7 additions & 5 deletions docs/pages/guides/gha_wheels.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ You can skip specifying the `build[uv]` build-frontend option and pre-installing

## Publishing

{% tabs %} {% tab oidc Trusted Publishing %}
Trusted Publishing is more secure and recommended {% rr GH105 %}:

{% tabs %} {% tab oidc Trusted Publishing (recommended) %}

{% raw %}

Expand Down Expand Up @@ -232,10 +234,10 @@ upload_all:

{% endraw %}

When you make a GitHub release in the web UI, we publish to PyPI. You'll need to
go to PyPI, generate a token for your user, and put it into `pypi_password` on
your repo's secrets page. Once you have a project, you should delete your
user-scoped token and generate a new project-scoped token.
If you cannot use Trusted Publishing, this publishes to PyPI with a token.
You'll need to go to PyPI, generate a token for your user, and put it into
`pypi_password` on your repo's secrets page. Once you have a project, you should
delete your user-scoped token and generate a new project-scoped token.

{% endtab %} {% endtabs %}

Expand Down
25 changes: 25 additions & 0 deletions src/sp_repo_review/checks/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,31 @@ def check(workflows: dict[str, Any]) -> str:
return ""


class GH105(GitHub):
"Use Trusted Publishing instead of token-based publishing on PyPI"

requires = {"GH100"}
url = mk_url("gha-basic")

@staticmethod
def check(workflows: dict[str, Any]) -> str:
errors = []
for wname, workflow in workflows.items():
for jname, job in workflow.get("jobs", {}).items():
for step in job.get("steps", []):
uses = step.get("uses", "")
publish_with = step.get("with", {})
if (
uses.startswith("pypa/gh-action-pypi-publish")
and "password" in publish_with
):
errors.append(
f"* Token-based publishing detected in `{wname}.yml:{jname}`. Trusted Publishing is recommended."
)
continue
return "\n".join(errors)


class GH200(GitHub):
"Maintained by Dependabot"

Expand Down
32 changes: 32 additions & 0 deletions tests/test_github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import yaml
from repo_review.testing import compute_check


def test_gh105_trusted_publishing() -> None:
workflows = yaml.safe_load(
"""
cd:
jobs:
publish:
steps:
- uses: pypa/gh-action-pypi-publish@release/v1
"""
)
assert compute_check("GH105", workflows=workflows).result


def test_gh105_token_based_upload() -> None:
workflows = yaml.safe_load(
"""
cd:
jobs:
publish:
steps:
- uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.pypi_password }}
"""
)
res = compute_check("GH105", workflows=workflows)
assert not res.result
assert "Token-based publishing" in res.err_msg
Loading