Skip to content

Commit c7bef3a

Browse files
committed
Merge branch 'v1.3-ossivalis'
2 parents bc306b1 + 8a1ee6b commit c7bef3a

File tree

4 files changed

+77
-50
lines changed

4 files changed

+77
-50
lines changed

.github/workflows/cleanup_pypi.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ jobs:
3434
- uses: actions/checkout@v4
3535
with:
3636
fetch-depth: 0
37-
38-
- if: ${{ vars.PYPI_HOST == '' }}
39-
run: |
40-
echo "Error: PYPI_HOST is not set in CI environment '${{ inputs.environment }}'"
41-
exit 1
4237
- if: ${{ vars.PYPI_CLEANUP_USERNAME == '' }}
4338
run: |
4439
echo "Error: PYPI_CLEANUP_USERNAME is not set in CI environment '${{ inputs.environment }}'"
@@ -56,8 +51,20 @@ jobs:
5651
- name: Run Cleanup
5752
run: |
5853
set -x
54+
pypi_index_flag=${{ inputs.environment == 'production.pypi' && '--prod' || '--test' }}
5955
uv sync --only-group pypi --no-install-project
6056
uv run --no-sync -s scripts/pypi_cleanup.py ${{ inputs.dry-run && '--dry' || '' }} \
61-
--index-hostname "${{ vars.PYPI_HOST }}" \
57+
${pypi_index_flag} \
6258
--username "${{ vars.PYPI_CLEANUP_USERNAME }}" \
63-
--max-nightlies ${{ vars.PYPI_MAX_NIGHTLIES }}
59+
--max-nightlies ${{ vars.PYPI_MAX_NIGHTLIES }} > cleanup_output 2>&1
60+
61+
- name: PyPI Cleanup Summary
62+
run : |
63+
echo "## PyPI Cleanup Summary" >> $GITHUB_STEP_SUMMARY
64+
echo "* Dry run: ${{ inputs.dry-run }}" >> $GITHUB_STEP_SUMMARY
65+
echo "* PyPI Host: ${{ vars.PYPI_HOST }}" >> $GITHUB_STEP_SUMMARY
66+
echo "* CI Environment: ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
67+
echo "* Output:" >> $GITHUB_STEP_SUMMARY
68+
echo '```' >> $GITHUB_STEP_SUMMARY
69+
cat cleanup_output >> $GITHUB_STEP_SUMMARY
70+
echo '```' >> $GITHUB_STEP_SUMMARY

.github/workflows/on_external_dispatch.yml

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ jobs:
8080
upload_s3:
8181
name: Upload Artifacts to the S3 Staging Bucket
8282
runs-on: ubuntu-latest
83-
needs: externally_triggered_build
83+
needs: [commit_submodule, externally_triggered_build]
8484
outputs:
8585
version: ${{ steps.s3_upload.outputs.version }}
8686
if: ${{ github.repository_owner == 'duckdb' && inputs.publish-packages }}
@@ -102,15 +102,24 @@ jobs:
102102
- name: Upload Artifacts
103103
id: s3_upload
104104
run: |
105-
version=$(basename artifacts/*.tar.gz | sed 's/duckdb-\(.*\).tar.gz/\1/g')
106-
aws s3 cp artifacts s3://duckdb-staging/${{ github.repository }}/${version}/ --recursive
105+
sha=${{ needs.commit_submodule.outputs.sha-after-commit }}
106+
aws s3 cp artifacts s3://duckdb-staging/${{ github.repository }}/${sha:0:10}/ --recursive
107107
echo "version=${version}" >> $GITHUB_OUTPUT
108108
109+
- name: S3 Upload Summary
110+
run : |
111+
sha=${{ needs.commit_submodule.outputs.sha-after-commit }}
112+
version=$(basename artifacts/*.tar.gz | sed 's/duckdb-\(.*\).tar.gz/\1/g')
113+
echo "## S3 Upload Summary" >> $GITHUB_STEP_SUMMARY
114+
echo "* Version: ${version}" >> $GITHUB_STEP_SUMMARY
115+
echo "* SHA: ${sha:0:10}" >> $GITHUB_STEP_SUMMARY
116+
echo "* S3 URL: s3://duckdb-staging/${{ github.repository }}/${sha:0:10}/" >> $GITHUB_STEP_SUMMARY
117+
109118
publish_to_pypi:
110119
name: Upload Artifacts to PyPI
111-
needs: upload_s3
112-
if: ${{ force-version == '' }}
120+
needs: [ commit_submodule, upload_s3 ]
121+
if: ${{ inputs.force-version == '' }}
113122
uses: ./.github/workflows/upload_to_pypi.yml
114123
with:
115-
version: ${{ needs.upload_s3.outputs.version }}
116-
environment: pypi.production
124+
sha: ${{ needs.commit_submodule.outputs.sha-after-commit }}
125+
environment: pypi.production

.github/workflows/upload_to_pypi.yml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ on:
66
description: CI environment to run in (test.pypi or production.pypi)
77
type: string
88
required: true
9-
version:
10-
description: The version to upload (must be present in the S3 staging bucket)
9+
sha:
10+
description: The SHA of the commit that the packages were built from
1111
type: string
1212
required: true
1313
workflow_dispatch:
@@ -20,13 +20,13 @@ on:
2020
options:
2121
- test.pypi
2222
- production.pypi
23-
version:
24-
description: The version to upload (must be present in the S3 staging bucket)
23+
sha:
24+
description: The SHA of the commit that the packages were built from
2525
type: string
2626
required: true
2727

2828
concurrency:
29-
group: ${{ inputs.version }}
29+
group: ${{ inputs.sha }}
3030
cancel-in-progress: true
3131

3232
jobs:
@@ -53,7 +53,7 @@ jobs:
5353

5454
- name: Download Artifacts From S3
5555
env:
56-
S3_URL: 's3://duckdb-staging/${{ github.repository }}/${{ inputs.version }}/'
56+
S3_URL: 's3://duckdb-staging/${{ github.repository }}/${{ inputs.sha }}/'
5757
AWS_ACCESS_KEY_ID: ${{ secrets.S3_DUCKDB_STAGING_ID }}
5858
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_DUCKDB_STAGING_KEY }}
5959
run: |
@@ -66,6 +66,14 @@ jobs:
6666
repository-url: 'https://${{ vars.PYPI_HOST }}/legacy/'
6767
packages-dir: packages
6868

69+
- name: PyPI Upload Summary
70+
run : |
71+
version=$(basename packages/*.tar.gz | sed 's/duckdb-\(.*\).tar.gz/\1/g')
72+
echo "## PyPI Upload Summary" >> $GITHUB_STEP_SUMMARY
73+
echo "* Version: ${version}" >> $GITHUB_STEP_SUMMARY
74+
echo "* PyPI Host: ${{ vars.PYPI_HOST }}" >> $GITHUB_STEP_SUMMARY
75+
echo "* CI Environment: ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
76+
6977
cleanup_nightlies:
7078
name: Remove Nightlies from PyPI
7179
needs: publish-pypi

scripts/pypi_cleanup.py

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414
import requests
1515
from requests.exceptions import RequestException
1616

17-
import argparse
18-
import re
19-
import os
17+
_PYPI_URL_PROD = 'https://pypi.org/'
18+
_PYPI_URL_TEST = 'https://test.pypi.org/'
2019

2120
def valid_hostname(hostname):
2221
"""Validate hostname format"""
@@ -41,7 +40,9 @@ def non_empty_string(value):
4140
epilog="Environment variables required (unless --dry): PYPI_CLEANUP_PASSWORD, PYPI_CLEANUP_OTP"
4241
)
4342
parser.add_argument("--dry", action="store_true", help="Show what would be deleted but don't actually do it")
44-
parser.add_argument("-i", "--index-hostname", type=valid_hostname, required=True, help="Index hostname (required)")
43+
host_group = parser.add_mutually_exclusive_group(required=True)
44+
host_group.add_argument("--prod", action="store_true", help="Use production PyPI (pypi.org)")
45+
host_group.add_argument("--test", action="store_true", help="Use test PyPI (test.pypi.org)")
4546
parser.add_argument("-m", "--max-nightlies", type=int, default=2, help="Max number of nightlies of unreleased versions (default=2)")
4647
parser.add_argument("-u", "--username", type=non_empty_string, help="Username (required unless --dry)")
4748
args = parser.parse_args()
@@ -62,21 +63,23 @@ def non_empty_string(value):
6263
if not otp:
6364
parser.error("PYPI_CLEANUP_OTP environment variable is required when not in dry-run mode")
6465

65-
print(f"Dry run: {args.dry}")
66-
print(f"Max nightlies: {args.max_nightlies}")
66+
dry_run = args.dry
67+
pypi_username = args.username
68+
max_dev_releases = args.max_nightlies
69+
if args.prod:
70+
pypi_url = _PYPI_URL_PROD
71+
else:
72+
pypi_url = _PYPI_URL_TEST
73+
pypi_password = password
74+
pypi_otp = otp
75+
76+
print(f"Dry run: {dry_run}")
77+
print(f"Max nightlies: {max_dev_releases}")
6778
if not args.dry:
68-
print(f"Hostname: {args.index_hostname}")
69-
print(f"Username: {args.username}")
79+
print(f"URL: {pypi_url}")
80+
print(f"Username: {pypi_username}")
7081
print("Password and OTP loaded from environment variables")
7182

72-
# deletes old dev wheels from pypi. evil hack.
73-
actually_delete = not args.dry
74-
pypi_username = args.username or "user"
75-
max_dev_releases = args.max_nightlies
76-
host = 'https://{}/'.format(args.index_hostname)
77-
pypi_password = password or "password"
78-
pypi_otp = otp or "otp"
79-
8083
patterns = [re.compile(r".*\.dev\d+$")]
8184
###### NOTE: This code is taken from the pypi-cleanup package (https://github.com/arcivanov/pypi-cleanup/tree/master)
8285
class CsfrParser(HTMLParser):
@@ -185,8 +188,8 @@ def run(self):
185188
sorted_versions = sorted(versions, key=lambda x: int(x.split('dev')[-1]))
186189
pkg_vers.extend(sorted_versions[:-self.max_dev_releases])
187190

191+
print("Following pkg_vers can be deleted: ", pkg_vers)
188192
if not self.do_it:
189-
print("Following pkg_vers can be deleted: ", pkg_vers)
190193
return
191194

192195
if not pkg_vers:
@@ -231,9 +234,9 @@ def run(self):
231234

232235
two_factor = False
233236
with s.post(
234-
f"{self.url}/account/login/",
235-
data={"csrf_token": csrf, "username": self.username, "password": self.password},
236-
headers={"referer": f"{self.url}/account/login/"},
237+
f"{self.url}/account/login/",
238+
data={"csrf_token": csrf, "username": self.username, "password": self.password},
239+
headers={"referer": f"{self.url}/account/login/"},
237240
) as r:
238241
r.raise_for_status()
239242
if r.url == f"{self.url}/account/login/":
@@ -255,9 +258,9 @@ def run(self):
255258
for i in range(3):
256259
auth_code = pyotp.TOTP(self.otp).now()
257260
with s.post(
258-
two_factor_url,
259-
data={"csrf_token": csrf, "method": "totp", "totp_value": auth_code},
260-
headers={"referer": two_factor_url},
261+
two_factor_url,
262+
data={"csrf_token": csrf, "method": "totp", "totp_value": auth_code},
263+
headers={"referer": two_factor_url},
261264
) as r:
262265
r.raise_for_status()
263266
if r.url == two_factor_url:
@@ -286,12 +289,12 @@ def run(self):
286289
referer = r.url
287290

288291
with s.post(
289-
form_url,
290-
data={
291-
"csrf_token": csrf,
292-
"confirm_delete_version": pkg_ver,
293-
},
294-
headers={"referer": referer},
292+
form_url,
293+
data={
294+
"csrf_token": csrf,
295+
"confirm_delete_version": pkg_ver,
296+
},
297+
headers={"referer": referer},
295298
) as r:
296299
r.raise_for_status()
297300

@@ -300,4 +303,4 @@ def run(self):
300303
logging.info(f"Would be deleting {self.package} version {pkg_ver}, but not doing it!")
301304

302305

303-
PypiCleanup(host, pypi_username, 'duckdb', pypi_password, pypi_otp, patterns, actually_delete, max_dev_releases).run()
306+
PypiCleanup(pypi_url, pypi_username, 'duckdb', pypi_password, pypi_otp, patterns, not dry_run, max_dev_releases).run()

0 commit comments

Comments
 (0)