Skip to content

Commit 22ae4d8

Browse files
authored
Replace release.py with OIDC publishing (#8483)
We are now in the PyPI OIDC publishing beta
1 parent 5e3061c commit 22ae4d8

File tree

3 files changed

+69
-118
lines changed

3 files changed

+69
-118
lines changed

.github/workflows/pypi-publish.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
run_id:
7+
description: The run of wheel-builder to use for finding artifacts.
8+
required: true
9+
environment:
10+
description: Which PyPI environment to upload to
11+
required: true
12+
type: choice
13+
options: ["pypi", "testpypi"]
14+
# Disabled until this has been validated with `workflow_dispatch` + Test PyPI.
15+
# workflow_run:
16+
# workflows: ["wheel-builder.yml"]
17+
# types: [completed]
18+
19+
jobs:
20+
publish:
21+
runs-on: ubuntu-latest
22+
# We're not actually verifying that the triggering push event was for a
23+
# tag, because github doesn't expose enough information to do so.
24+
# wheel-builder.yml currently only has push events for tags.
25+
if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.event == 'push' && github.event.workflow_run.conclusion == 'success')
26+
permissions:
27+
id-token: "write"
28+
steps:
29+
- uses: dawidd6/action-download-artifact@5e780fc7bbd0cac69fc73271ed86edf5dcb72d67
30+
with:
31+
path: dist/
32+
run_id: ${{ github.event.inputs.run_id || github.event.workflow_run.event.id }}
33+
- run: pip install -c ci-constraints-requirements.txt twine requests
34+
35+
- run: |
36+
echo "OIDC_AUDIENCE=pypi" >> GITHUB_ENV
37+
echo "PYPI_DOMAIN=pypi.org" >> GITHUB_ENV
38+
echo "TWINE_REPO=pypi" >> GITHUB_ENV
39+
if: github.event_name == 'workflow_run' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'pypi')
40+
- run: |
41+
echo "OIDC_AUDIENCE=testpypi" >> GITHUB_ENV
42+
echo "PYPI_DOMAIN=test.pypi.org" >> GITHUB_ENV
43+
echo "TWINE_REPO=testpypi" >> GITHUB_ENV
44+
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'testpypi'
45+
46+
- run: |
47+
import os
48+
49+
import requests
50+
51+
response = requests.get(
52+
os.environ["ACTIONS_ID_TOKEN_REQUEST_URL"],
53+
params={"audience": os.environ["OIDC_AUDIENCE"]},
54+
headers={"Authorization": f"bearer {os.environ['ACTIONS_ID_TOKEN_REQUEST_TOKEN']}"}
55+
)
56+
response.raise_for_status()
57+
token = response.json()["value"]
58+
59+
response = requests.post(f"https://{os.environ['PYPI_DOMAIN']}/_/oidc/github/mint-token", json={"token": token})
60+
response.raise_for_status()
61+
pypi_token = response.json()["token"]
62+
63+
with open(os.environ["GITHUB_ENV"], "a") as f:
64+
f.write("TWINE_PASSWORD={pypi_token}\n")
65+
shell: python
66+
67+
- run: "twine upload --repository $TWINE_REPO dist/*"

.github/workflows/wheel-builder.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
inputs:
77
version:
88
description: The version to build
9+
# Do not add any non-tag push events without updating pypi-publish.yml. If
10+
# you do, it'll upload wheels to PyPI.
911
push:
1012
tags:
1113
- '*.*'

release.py

Lines changed: 0 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2,144 +2,26 @@
22
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
# for complete details.
44

5-
import getpass
6-
import io
7-
import os
85
import subprocess
9-
import time
10-
import typing
11-
import zipfile
126

137
import click
14-
import requests
158

169

1710
def run(*args: str) -> None:
1811
print(f"[running] {list(args)}")
1912
subprocess.check_call(list(args))
2013

2114

22-
def wait_for_build_complete_github_actions(
23-
session: requests.Session, token: str, run_url: str
24-
) -> None:
25-
while True:
26-
response = session.get(
27-
run_url,
28-
headers={
29-
"Content-Type": "application/json",
30-
"Authorization": f"token {token}",
31-
},
32-
)
33-
response.raise_for_status()
34-
if response.json()["conclusion"] is not None:
35-
break
36-
time.sleep(3)
37-
38-
39-
def download_artifacts_github_actions(
40-
session: requests.Session, token: str, run_url: str
41-
) -> typing.List[str]:
42-
response = session.get(
43-
run_url,
44-
headers={
45-
"Content-Type": "application/json",
46-
"Authorization": f"token {token}",
47-
},
48-
)
49-
response.raise_for_status()
50-
51-
response = session.get(
52-
response.json()["artifacts_url"],
53-
headers={
54-
"Content-Type": "application/json",
55-
"Authorization": f"token {token}",
56-
},
57-
)
58-
response.raise_for_status()
59-
paths = []
60-
for artifact in response.json()["artifacts"]:
61-
response = session.get(
62-
artifact["archive_download_url"],
63-
headers={
64-
"Content-Type": "application/json",
65-
"Authorization": f"token {token}",
66-
},
67-
)
68-
with zipfile.ZipFile(io.BytesIO(response.content)) as z:
69-
for name in z.namelist():
70-
if not name.endswith(".whl") and not name.endswith(".tar.gz"):
71-
continue
72-
p = z.open(name)
73-
out_path = os.path.join(
74-
os.path.dirname(__file__),
75-
"dist",
76-
os.path.basename(name),
77-
)
78-
with open(out_path, "wb") as f:
79-
f.write(p.read())
80-
paths.append(out_path)
81-
return paths
82-
83-
84-
def fetch_github_actions_artifacts(
85-
token: str, version: str
86-
) -> typing.List[str]:
87-
session = requests.Session()
88-
89-
workflow_runs = []
90-
91-
# There is a race condition where no workflow run has triggered after
92-
# pushing the tag, so loop until we get the run.
93-
while True:
94-
response = session.get(
95-
(
96-
f"https://api.github.com/repos/pyca/cryptography/actions"
97-
f"/workflows/wheel-builder.yml/runs?event=push&"
98-
f"branch={version}"
99-
),
100-
headers={
101-
"Content-Type": "application/json",
102-
"Authorization": f"token {token}",
103-
},
104-
)
105-
response.raise_for_status()
106-
workflow_runs = response.json()["workflow_runs"]
107-
if len(workflow_runs) > 0:
108-
break
109-
time.sleep(3)
110-
111-
run_url: str = workflow_runs[0]["url"]
112-
wait_for_build_complete_github_actions(session, token, run_url)
113-
return download_artifacts_github_actions(session, token, run_url)
114-
115-
11615
@click.command()
11716
@click.argument("version")
11817
def release(version: str) -> None:
11918
"""
12019
``version`` should be a string like '0.4' or '1.0'.
12120
"""
122-
print(
123-
f"Create a new GH PAT with only actions permissions at: "
124-
f"https://github.com/settings/tokens/new?"
125-
f"description={version}&scopes=repo"
126-
)
127-
github_token = getpass.getpass("Github person access token: ")
128-
12921
# Tag and push the tag (this will trigger the wheel builder in Actions)
13022
run("git", "tag", "-s", version, "-m", f"{version} release")
13123
run("git", "push", "--tags")
13224

133-
os.makedirs(os.path.join(os.path.dirname(__file__), "dist"), exist_ok=True)
134-
135-
# Wait for Actions to complete and download the wheels
136-
github_actions_artifact_paths = fetch_github_actions_artifacts(
137-
github_token, version
138-
)
139-
140-
# Upload wheels and sdist
141-
run("twine", "upload", *github_actions_artifact_paths)
142-
14325

14426
if __name__ == "__main__":
14527
release()

0 commit comments

Comments
 (0)