Skip to content

Commit 8e337fc

Browse files
Merge pull request #9 from joergschultzelutter/trusted-workflows
Trusted Publisher migration
2 parents 215958e + 5e49f0a commit 8e337fc

File tree

2 files changed

+97
-23
lines changed

2 files changed

+97
-23
lines changed

README.md

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,20 @@
22

33
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![CodeQL](https://github.com/joergschultzelutter/pypi-publish-workflow/actions/workflows/codeql.yml/badge.svg)](https://github.com/joergschultzelutter/pypi-publish-workflow/actions/workflows/codeql.yml)
44

5-
This is a __Github Actions__ workflow for automatic publications to PyPi. Version data from a python file is extracted and then used by the PyPi setup process which will publish the package to PyPi Test and Prod.
5+
This is a __Github Actions__ workflow for automatic publications to PyPi. Version data from a python file is extracted and then used by the PyPi setup process which will publish the package to PyPi Test and Prod, following PyPi's [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) model.
66

77
The workflow will only be triggered for the publication of new repo releases / prereleases for the 'master' repo branch.
88

9-
## Installation instructions
10-
11-
### Setup Github Secrets
12-
13-
- Create token secrets for both [PyPi Test](https://test.pypi.org/) and [PyPi Prod](https://www.pypi.org/) (``Account Settings`` > ``API Tokens`` > ``Add API token``).
14-
- In your Github project, goto ``Settings`` > ``Secrets and Variables`` > ``Actions``
15-
- Create two `Secrets` keys (`New repository secret`) named ``TEST_PYPI_API_TOKEN`` and ``PROD_PYPI_API_TOKEN`` and assign the previously created token secrets to these keys
16-
17-
### Overview on config files
9+
## Overview on config files
1810

1911
This repo contains three files that you may need to amend and copy to your Github repository:
2012

2113
- ``setup.py``: this is a regular Python ``setup.py`` file; amend the file content with your package information and then save the file in your repo's root directory
2214
- ``publish-to-pypi.yml``: Edit this file, amend the configuration settings (see next chapter) and then save the file in your repo's Github Actions directory (``.github/workflows``). You may also need to activate the new workflow once you have installed it - see [documentation on Github](https://docs.github.com/en/actions).
2315

24-
### `publish-to-pypi.yml` configuration
16+
## Configuration file instructions
17+
18+
### `publish-to-pypi.yml`
2519

2620
Open the file. You will notice a section which looks like this:
2721

@@ -98,20 +92,42 @@ Necessary steps for a manual usage:
9892
- open `setup.py` and assign a version number to the `GITHUB_PROGRAM_VERSION` variable
9993
- `pip install git+https://github.com/my-repository-name@my-branch#egg=my-package-name`
10094

95+
## Installation instructions
96+
97+
The workflow uses PyPi's [Trusted Publisher](https://docs.pypi.org/trusted-publishers/) model. For a new project on PyPi, follow [these instructions](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/) for setting up a trusted publisher. For an existing project which you may want to migrate from a secret-based workflow to the new trusted workflows, use [these instructions](https://docs.pypi.org/trusted-publishers/adding-a-publisher/).
98+
99+
### Step 1: set up a Github environment
100+
101+
- In your GitHub project, go to **Settings > Environments** and create a new environment called `pypi`.
102+
- Configure `Required reviewers` or other settings, if necessary. You do NOT need to configure any secrets here.
103+
104+
### Step 2: Deploy the workflow and the setup file
105+
106+
- You need to configure the files prior to deployment, see previous chapter **Configuration file instructions**
107+
- `setup.py` goes into your repository's root directory
108+
- `publish-to-pypi.yml` goes into your repository's `.github/workflows` directory (or add as a new GitHub action)
109+
110+
### Step 3: Trusted Publisher Setup
111+
112+
- Log on to your PyPi Test & Prod accounts
113+
- Follow the instructions on how to set up a [Trusted Publisher](https://docs.pypi.org/trusted-publishers/) on both Test and Prod environments:
114+
- Set the `Workflow Name` to `publish-to-pypi.yml`
115+
- Set the `Environment` to `pypi`
116+
101117
## Running the Github Action
102118

103119
This Github action will do the following __whenever a new release/pre-release is published for the 'master' branch__:
104120

105121
- Read the Python file and extract the version information, based on the given Regex. Abort job if no match was found.
106122
- Check if the Github ``ref_type`` has the value ``tag``. This is only the case when you drafted a new release. Otherwise, this value is likely set to ``master``. Abort job in case of a mismatch.
107123
- Check if the Github ``ref_name`` is equal to the extracted version from you Python file. Abort job in case of a mismatch. This will prevent issues where there is a mismatch between your Github release version (tag) and the one in the Python file.
108-
- Build the PyPi package. Deploy it to PyPi Test and (if successful AND not a pre-release) PyPi Prod.
124+
- Build the PyPi package. Deploy it to PyPi Test and (if successful AND not a pre-release) PyPi Prod. Note: This is done as a separate workflow step, see [this issue](https://github.com/pypa/gh-action-pypi-publish/issues/319) for technical details.
109125

110126
This job will be triggered for releases AND prereleases in 'created' state (read: you tag a (pre)release in Github). Releases will be pushed to both PyPi Test and Prod whereas prereleases will only be pushed to PyPi Test.
111127

112128
## Test your work flow
113129

114-
In case you want to become acquainted with this work flow: The safest way to test the work flow is to create both Github secret entries ``TEST_PYPI_API_TOKEN`` and ``PROD_PYPI_API_TOKEN`` but assign an invalid token to them. When you run the workflow for a new 'master' branch prerelease, the job will try to push it to PyPi Test and will fail because of the invalid token.
130+
- Publish your package as a prerelease. This should deploy your code only to PyPi Test.
115131

116132
## Workflow
117133
A basic workflow diagram of this Github Action can be found [here](docs/workflow.jpg)

publish-to-pypi.yml

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ jobs:
8484
# to next Github Actions job in line.
8585
get-python-version-info:
8686
runs-on: ubuntu-latest
87+
environment: pypi
8788
permissions: write-all
8889

8990
# Output which is passed to the PyPi publication job
@@ -150,12 +151,21 @@ jobs:
150151
#
151152
# BEGIN of Job 2
152153
#
153-
# This section will create the PyPi package and first deploy it to PyPi test.
154-
# If successful, it will also try to issue a PyPi Prod deployment afterwards
154+
# This section will create the PyPi package and deploy it to PyPi test.
155+
# in case a pre-release was selected
155156
#
156-
deploy-to-pypi:
157+
# NOTE: When using PyPi's 'Trusted Publishing", one cannot use the same
158+
# OIDC token for both Test and Prod - otherwise, error
159+
# "Attestation generation failure: The following distributions already have publish attestations"
160+
# will be thrown. See https://github.com/pypa/gh-action-pypi-publish/issues/319 for details
161+
# This is the only reason for Test and Prod not sharing the same work flow
162+
#
163+
deploy-to-pypi-test:
157164
runs-on: ubuntu-latest
165+
environment: pypi
158166
needs: get-python-version-info
167+
permissions:
168+
id-token: write
159169

160170
steps:
161171

@@ -187,17 +197,65 @@ jobs:
187197
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags')
188198
uses: pypa/gh-action-pypi-publish@release/v1
189199
with:
190-
user: __token__
191-
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
200+
#user: __token__
201+
#password: ${{ secrets.TEST_PYPI_API_TOKEN }}
192202
repository-url: https://test.pypi.org/legacy/
193203

204+
#
205+
# END of Job 2
206+
#
207+
#
208+
# BEGIN of Job 3
209+
#
210+
# This section will create the PyPi package and fdeploy it to PyPi Prod.
211+
#
212+
# NOTE: When using PyPi's 'Trusted Publishing", one cannot use the same
213+
# OIDC token for both Test and Prod - otherwise, error
214+
# "Attestation generation failure: The following distributions already have publish attestations"
215+
# will be thrown. See https://github.com/pypa/gh-action-pypi-publish/issues/319 for details
216+
# This is the only reason for Test and Prod not sharing the same work flow
217+
#
218+
deploy-to-pypi-prod:
219+
runs-on: ubuntu-latest
220+
environment: pypi
221+
needs: [get-python-version-info, deploy-to-pypi-test]
222+
permissions:
223+
id-token: write
224+
225+
steps:
226+
227+
- uses: actions/checkout@v5
228+
# Set up Python environment
229+
- name: Set up Python
230+
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') && !github.event.release.prerelease
231+
uses: actions/setup-python@v6
232+
with:
233+
python-version: '${{ env.PYTHON_VERSION }}'
234+
235+
# Install all dependencies
236+
- name: Install dependencies
237+
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') && !github.event.release.prerelease
238+
run: |
239+
python -m pip install --upgrade pip
240+
pip install build
241+
242+
# Export the program version; content will be picked up by the setup.py script
243+
- name: Export program version
244+
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') && !github.event.release.prerelease
245+
run: echo GITHUB_PROGRAM_VERSION='${{ needs.get-python-version-info.outputs.my-program-version }}' >> $GITHUB_ENV
246+
247+
# Build the package. The export MUST be part of THIS step
248+
# Otherwise, the Python setup job will not see this information
249+
- name: Build package
250+
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') && !github.event.release.prerelease
251+
run: python -m build
252+
194253
# Publish everything to Prod PyPi but only if it is not a prerelease
195254
- name: Publish package to Prod PyPi
196255
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') && !github.event.release.prerelease
197256
uses: pypa/gh-action-pypi-publish@release/v1
198-
with:
199-
user: __token__
200-
password: ${{ secrets.PROD_PYPI_API_TOKEN }}
201-
#
202-
# END of Job 2
257+
#with:
258+
#user: __token__
259+
#password: ${{ secrets.PROD_PYPI_API_TOKEN }}
203260
#
261+
# END of Job 3

0 commit comments

Comments
 (0)