pypi-publish/README.md

395 lines
16 KiB
Markdown
Raw Normal View History

[![SWUbanner]][SWUdocs]
2022-12-06 16:59:26 -05:00
[![🧪 GitHub Actions CI/CD workflow tests badge]][GHA workflow runs list]
[![pre-commit.ci status badge]][pre-commit.ci results page]
2019-03-27 15:58:36 -04:00
# PyPI publish GitHub Action
2020-06-03 11:53:04 -04:00
This action allows you to upload your [Python distribution packages]
in the `dist/` directory to PyPI.
2019-09-27 07:37:19 -04:00
This text suggests a minimalistic usage overview. For more detailed
walkthrough check out the [PyPA guide].
2019-03-27 15:58:36 -04:00
If you have any feedback regarding specific action versions, please leave
comments in the corresponding [per-release announcement discussions].
> [!TIP]
> A limited number of usage scenarios is supported, including the
> [PyPA guide] example. See the [non-goals] for more detail.
2019-03-27 15:58:36 -04:00
## 🌇 `master` branch sunset ❗
The `master` branch version has been sunset. Please, change the GitHub
Action version you use from `master` to `release/v1` or use an exact
tag, or opt-in to [use a full Git commit SHA] and Dependabot.
2019-03-27 15:58:36 -04:00
## Usage
### Trusted publishing
2024-02-24 05:46:07 -05:00
> [!NOTE]
> Trusted publishing cannot be used from within a reusable workflow at this
> time. It is recommended to instead create a non-reusable workflow that contains a
> job calling your reusable workflow, and then do the trusted publishing step from
> a separate job within that non-reusable workflow. Alternatively, you can still
> use a username/token inside the reusable workflow.
> [!NOTE]
> Trusted publishing is sometimes referred to by its
> underlying technology -- OpenID Connect, or OIDC for short.
> If you see references to "OIDC publishing" in the context of PyPI,
> this is what they're referring to.
This example jumps right into the current best practice. If you want to
use API tokens directly or a less secure username and password, check out
[how to specify username and password].
This action supports PyPI's [trusted publishing]
implementation, which allows authentication to PyPI without a manually
configured API token or username/password combination. To perform
[trusted publishing] with this action, your project's
publisher must already be [configured on PyPI].
To enter the trusted publishing flow, configure this action's job with the
`id-token: write` permission and **without** an explicit username or password:
```yaml
# .github/workflows/ci-cd.yml
jobs:
pypi-publish:
name: Upload release to PyPI
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/<your-pypi-project-name>
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
# retrieve your distributions here
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
```
> [!NOTE]
> Pro tip: instead of using branch pointers, like `unstable/v1`, pin versions of
> Actions that you use to tagged versions or sha1 commit identifiers.
> This will make your workflows more secure and better reproducible, saving you
> from sudden and unpleasant surprises.
Other indices that support trusted publishing can also be used, like TestPyPI:
```yaml
- name: Publish package distributions to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
```
_(don't forget to update the environment name to `testpypi` or similar!)_
> [!NOTE]
> Pro tip: only set the `id-token: write` permission in the job that does
> publishing, not globally. Also, try to separate building from publishing
> — this makes sure that any scripts maliciously injected into the build
> or test environment won't be able to elevate privileges while flying under
> the radar.
A common use case is to upload packages only on a tagged commit, to do so add a
filter to the job:
```yml
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
```
### Generating and uploading attestations
> [!IMPORTANT]
> Support for generating and uploading [digital attestations] is currently
> experimental and limited only to Trusted Publishing flows using PyPI or TestPyPI.
> Support for this feature is not yet stable; the settings and behavior described
> below may change without prior notice.
> [!NOTE]
> Generating and uploading digital attestations currently requires
> authentication with a [trusted publisher].
Generating signed [digital attestations] for all the distribution files
and uploading them all together is now on by default for all projects
using Trusted Publishing. To disable it, set `attestations` as follows:
```yml
with:
attestations: false
```
The attestation objects are created using [Sigstore] for each
distribution package, signing them with the identity provided
by the GitHub's OIDC token associated with the current workflow. This means
both the trusted publishing authentication and the attestations are tied to the
same identity.
2019-09-12 08:06:55 -04:00
## Non-goals
This GitHub Action [has nothing to do with _building package
distributions_]. Users are responsible for preparing dists for upload
by putting them into the `dist/` folder prior to running this Action.
They are typically expected to do this in a _separate GitHub Actions
CI/CD job_ running before the one where they call this action and having
restricted privileges.
2019-09-12 08:06:55 -04:00
> [!IMPORTANT]
> Since this GitHub Action is docker-based, it can only
> be used from within GNU/Linux based jobs in GitHub Actions CI/CD
> workflows. This is by design and is unlikely to change due to a number
> of considerations we rely on.
>
> This should not stop one from publishing platform-specific
> distribution packages, though. It is strongly advised to separate jobs
> for building the OS-specific wheels from the publish job. This allows
> one to (1) test exactly the same artifacts that are about to be
> uploaded to PyPI, (2) prevent parallel unsynchronized jobs from
> publishing only part of the dists asynchronously (in case when part of
> the jobs fail and others succeed ending up with an incomplete release
> on PyPI) and (3) make an atomic upload to PyPI (when part of the dists
> appear on PyPI, installers like pip will use that version for the
> dependency resolution but this may cause some environments to use
2021-02-19 14:28:01 -05:00
> sdists while the wheel for their runtime is not yet available).
>
> To implement this sort of orchestration, please use
> `actions/upload-artifact` and `actions/download-artifact` actions for
> sharing the built dists across stages and jobs. Then, use the `needs`
> setting to order the build, test and publish stages.
The expected environment for running `pypi-publish` is the
GitHub-provided Ubuntu VM. We are running a smoke-test against
`ubuntu-latest` in CI but any currently available numbered versions
should do. We'll consider them supported for as long as GitHub itself
supports them.
Running the action in a job that has a `container:` set is not
supported. It might work for you but you're on your own when it breaks.
If you feel the need to use it, it's likely that you're not following
the recommendation of invoking the build automation in a separate job,
which is considered a security issue (especially, when using [Trusted
Publishing][trusted publisher] that may cause privilege escalation and
would enable the attackers to impersonate the GitHub-backed identity of
the repository through transitive build dependency poisoning). The
solution is to have one job (or multiple, in case of projects with
C-extensions) for building the distribution packages, followed by
another that publishes them.
Self-hosted runners are best effort, provided no other unsupported
things influence them. We are unable to test this in CI and they may
break. This is often the case when using custom runtimes and not the
official GitHub-provided VMs. In general, if you follow the
recommendation of building in a separate job, you shouldn't need to run
this action within a self-hosted runner — it should be possible to
build your dists in a self-hosted runner, save them as a GitHub Actions
artifact in that job, and then invoke the publishing job that would run
within GitHub-provided runners, downloading the artifact with the dists
and publishing them. Such separation is the _recommended_/**supported**
way of handling this scenario.
Our understandng is that Trusted publishing is expected to work on
self-hosted runners. It is backed by OIDC. If it doesn't work, you
should probably ask GitHub if you missed something. We wouldn't be able
to assist here.
Trusted Publishing cannot be tested in CI at the moment, sadly. It is
supported and bugs should be reported but it may take time to sort out
as it often requires cross-project collaboration to debug (sometimes,
problems occur due to changes in PyPI and not in the action).
The only case that is explicitly unsupported at the moment is [Trusted
Publishing][trusted publisher] in reusable workflows. This requires
support on the PyPI side and is being worked on. Please, do not report
bugs related to this case. The current recommendation is to put
everything else you want into a reusable workflow but keep the job
calling `pypi-publish` in a top-level one.
Invoking `pypi-publish` from composite actions is unsupported. It is not
tested. GitHub Runners have limitations and bugs in this case. But more
importantly, this is usually an indication of using it insecurely. When
using [Trusted Publishing][trusted publisher], it is imperative to keep
build machinery invocation in a separate job with restrictive priviliges
as [Trusted Publishing][trusted publisher] itself requires elevated
permissions to make use of OIDC. Our observation is that the users
sometimes create in-project composite actions that invoke building and
publishing in the same job. As such, we don't seek to support such a
dangerous configuration in the first place. The solution is pretty much
the same as with the previous problem — use a separate job with
dedicated and scoped privileges just for publishing; and invoke that
in-project composite action from a different job.
And finally, invoking `pypi-publish` more than once in the same job is
not considered supported. It may work in a limited number of scenarios
but please, don't do this. If you want to publish to several indexes,
build the dists in one job and add several publishing jobs, one per
upload.
2019-09-12 08:06:55 -04:00
2019-09-15 03:16:54 -04:00
## Advanced release management
For best results, figure out what kind of workflow fits your
project's specific needs.
2019-09-16 07:01:16 -04:00
For example, you could implement a parallel job that
2019-09-19 04:04:14 -04:00
pushes every commit to TestPyPI or your own index server,
2019-09-15 03:16:54 -04:00
like `devpi`. For this, you'd need to (1) specify a custom
`repository-url` value and (2) generate a unique version
2019-09-15 03:16:54 -04:00
number for each upload so that they'd not create a conflict.
2019-09-16 07:01:16 -04:00
The latter is possible if you use `setuptools_scm` package but
2019-09-15 03:16:54 -04:00
you could also invent your own solution based on the distance
to the latest tagged commit.
You'll need to create another token for a separate host and then [save it as a
GitHub repo secret][Creating & using secrets] under an environment used in
your job. Though, passing a password would disable the secretless [trusted
publishing] so it's better to configure it instead, when publishing to TestPyPI
and not something custom.
2019-09-15 09:24:35 -04:00
2019-09-15 03:16:54 -04:00
The action invocation in this case would look like:
```yml
2019-09-19 04:04:14 -04:00
- name: Publish package to TestPyPI
2021-02-19 14:05:09 -05:00
uses: pypa/gh-action-pypi-publish@release/v1
2019-09-15 03:16:54 -04:00
with:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository-url: https://test.pypi.org/legacy/
2019-09-15 03:16:54 -04:00
```
### Customizing target package dists directory
2019-12-05 18:25:02 -05:00
You can change the default target directory of `dist/`
to any directory of your liking. The action invocation
would now look like:
```yml
- name: Publish package to PyPI
2021-02-19 14:05:09 -05:00
uses: pypa/gh-action-pypi-publish@release/v1
2019-12-05 18:25:02 -05:00
with:
packages-dir: custom-dir/
2019-12-05 18:25:02 -05:00
```
2019-09-15 03:16:54 -04:00
2020-06-03 11:04:52 -04:00
### Disabling metadata verification
2020-06-03 11:04:52 -04:00
It is recommended that you run `twine check` just after producing your files,
but this also runs `twine check` before upload. You can also disable the twine
check with:
```yml
with:
verify-metadata: false
```
### Tolerating release package file duplicates
Sometimes, when you publish releases from multiple places, your workflow
may hit race conditions. For example, when publishing from multiple CIs
2020-07-09 03:45:41 -04:00
or even having workflows with the same steps triggered within GitHub
Actions CI/CD for different events concerning the same high-level act.
To facilitate this use-case, you may use `skip-existing` (disabled by
default) setting as follows:
```yml
with:
skip-existing: true
```
> [!NOTE]
> Pro tip: try to avoid enabling this setting where possible. If you
> have steps for publishing to both PyPI and TestPyPI, consider only using
> it for the latter, having the former fail loudly on duplicates.
2020-09-15 00:31:21 -04:00
### For Debugging
Sometimes, `twine upload` can fail and to debug use the `verbose` setting as follows:
```yml
with:
verbose: true
```
2022-01-07 23:41:13 -05:00
### Showing hash values of files to be uploaded
You may want to verify whether the files on PyPI were automatically uploaded by CI script.
It will show SHA256, MD5, BLAKE2-256 values of files to be uploaded.
```yml
with:
print-hash: true
2022-01-07 23:41:13 -05:00
```
### Specifying a different username
The default username value is `__token__`. If you publish to a custom
registry that does not provide API tokens, like `devpi`, you may need to
specify a custom username and password pair. This is how it's done.
```yml
with:
user: guido
password: ${{ secrets.DEVPI_PASSWORD }}
```
The secret used in `${{ secrets.DEVPI_PASSWORD }}` needs to be created on the
environment page under the settings of your project on GitHub.
See [Creating & using secrets].
In the past, when publishing to PyPI, the most secure way of the access scoping
for automatic publishing was to use the [API tokens][PyPI API token] feature of
PyPI. One would make it project-scoped and save as an environment-bound secret
in their GitHub repository settings, naming it `${{ secrets.PYPI_API_TOKEN }}`,
for example. See [Creating & using secrets]. While still secure,
[trusted publishing] is now encouraged over API tokens as a best practice
on supported platforms (like GitHub).
2019-03-27 15:58:36 -04:00
## License
2019-03-27 15:58:36 -04:00
The Dockerfile and associated scripts and documentation in this project
are released under the [BSD 3-clause license](LICENSE.md).
2022-12-06 16:59:26 -05:00
[🧪 GitHub Actions CI/CD workflow tests badge]:
https://github.com/pypa/gh-action-pypi-publish/actions/workflows/build-and-push-docker-image.yml/badge.svg?branch=unstable%2Fv1&event=push
2022-12-06 16:59:26 -05:00
[GHA workflow runs list]:
https://github.com/pypa/gh-action-pypi-publish/actions/workflows/self-smoke-test-action.yml?query=branch%3Aunstable%2Fv1
[pre-commit.ci results page]:
https://results.pre-commit.ci/latest/github/pypa/gh-action-pypi-publish/unstable/v1
2022-12-06 16:59:26 -05:00
[pre-commit.ci status badge]:
https://results.pre-commit.ci/badge/github/pypa/gh-action-pypi-publish/unstable/v1.svg
2022-12-06 16:59:26 -05:00
[use a full Git commit SHA]:
https://julienrenaux.fr/2019/12/20/github-actions-security-risk/
[per-release announcement discussions]:
https://github.com/pypa/gh-action-pypi-publish/discussions/categories/announcements
[non-goals]: #Non-goals
2019-09-24 17:04:57 -04:00
[Creating & using secrets]:
https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets
2019-09-12 08:06:55 -04:00
[has nothing to do with _building package distributions_]:
https://github.com/pypa/gh-action-pypi-publish/issues/11#issuecomment-530480449
2019-09-27 07:37:19 -04:00
[PyPA guide]:
https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
2020-06-03 11:53:04 -04:00
[PyPI API token]: https://pypi.org/help/#apitoken
[Python distribution packages]:
https://packaging.python.org/glossary/#term-Distribution-Package
[SWUbanner]:
https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct-single.svg
[SWUdocs]:
https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md
[warehouse#12965]: https://github.com/pypi/warehouse/issues/12965
[trusted publishing]: https://docs.pypi.org/trusted-publishers/
[configured on PyPI]: https://docs.pypi.org/trusted-publishers/adding-a-publisher/
[how to specify username and password]: #specifying-a-different-username
[digital attestations]: https://peps.python.org/pep-0740/
[Sigstore]: https://www.sigstore.dev/
[trusted publisher]: #trusted-publishing