Skip to content

Commit f423491

Browse files
authored
Update Python patch upgrade automation for PBS releases (#22159)
* Update Python patch upgrade automation for PBS releases * follow redirects * Reset PYTHON_VERSION_FULL constant
1 parent b48c1e0 commit f423491

File tree

3 files changed

+99
-23
lines changed

3 files changed

+99
-23
lines changed

ddev/src/ddev/cli/meta/scripts/upgrade_python.py

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@
1818

1919
# Python.org URLs
2020
PYTHON_FTP_URL = "https://www.python.org/ftp/python/"
21-
PYTHON_MACOS_PKG_URL_TEMPLATE = "https://www.python.org/ftp/python/{version}/python-{version}-macos11.pkg"
2221
PYTHON_SBOM_LINUX_URL_TEMPLATE = "https://www.python.org/ftp/python/{version}/Python-{version}.tgz.spdx.json"
2322
PYTHON_SBOM_WINDOWS_URL_TEMPLATE = "https://www.python.org/ftp/python/{version}/python-{version}-amd64.exe.spdx.json"
2423

24+
# Python Build Standalone (PBS) - used for macOS
25+
# https://github.com/astral-sh/python-build-standalone
26+
PBS_LATEST_RELEASE_URL = "https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest"
27+
PBS_SHA256SUMS_URL_TEMPLATE = (
28+
"https://github.com/astral-sh/python-build-standalone/releases/download/{release}/SHA256SUMS"
29+
)
30+
2531
# Regex patterns for Dockerfile updates
2632
# Linux: ENV PYTHON3_VERSION=3.13.7 (no quotes, matches version at end of line)
2733
LINUX_VERSION_PATTERN = re.compile(r'(ENV PYTHON3_VERSION=)(\d+\.\d+\.\d+)$', re.MULTILINE)
@@ -248,28 +254,44 @@ def apply_substitution(pattern: re.Pattern, replace_func, error_msg: str, _docke
248254

249255

250256
def upgrade_macos_python_version(app: Application, new_version: str, tracker: ValidationTracker):
251-
macos_python_file = app.repo.path / '.github' / 'workflows' / 'resolve-build-deps.yaml'
257+
"""
258+
Update macOS Python version in the workflow file.
252259
253-
macos_content = read_file_safely(macos_python_file, 'macOS workflow', tracker)
254-
if macos_content is None:
260+
The workflow uses Python Build Standalone (PBS) from Astral. Updates PYTHON_PATCH,
261+
PBS_RELEASE, and the SHA256 hashes for both macOS architectures.
262+
"""
263+
workflow_file = app.repo.path / '.github' / 'workflows' / 'resolve-build-deps.yaml'
264+
content = read_file_safely(workflow_file, 'macOS workflow', tracker)
265+
if content is None:
255266
return
256267

257-
target_line = next((line for line in macos_content.splitlines() if 'PYTHON3_DOWNLOAD_URL' in line), None)
268+
new_patch = new_version.split('.')[-1]
258269

259-
if target_line is None:
260-
tracker.error(('macOS workflow',), message='Could not find PYTHON3_DOWNLOAD_URL')
270+
# Get the latest PBS release and SHA256 hashes
271+
pbs_info = get_pbs_release_info(app, new_version)
272+
if pbs_info is None:
273+
tracker.error(
274+
('macOS workflow',),
275+
message=f'Could not find PBS release with Python {new_version}. '
276+
'A new PBS release may not be available yet.',
277+
)
261278
return
262279

263-
new_url = PYTHON_MACOS_PKG_URL_TEMPLATE.format(version=new_version)
264-
indent = target_line[: target_line.index('PYTHON3_DOWNLOAD_URL')]
265-
new_line = f'{indent}PYTHON3_DOWNLOAD_URL: "{new_url}"'
280+
# Define replacements: (pattern, new_value)
281+
replacements = [
282+
(r'^(\s*PYTHON_PATCH:\s*)\d+\s*$', rf'\g<1>{new_patch}'),
283+
(r'^(\s*PBS_RELEASE:\s*)\d+\s*$', rf"\g<1>{pbs_info['release']}"),
284+
(r'^(\s*PBS_SHA256__aarch64:\s*)[0-9a-f]+\s*$', rf"\g<1>{pbs_info['aarch64']}"),
285+
(r'^(\s*PBS_SHA256__x86_64:\s*)[0-9a-f]+\s*$', rf"\g<1>{pbs_info['x86_64']}"),
286+
]
266287

267-
if target_line == new_line:
268-
app.display_info(f"Python version in macOS workflow is already at {new_version}")
269-
return
288+
for pattern, replacement in replacements:
289+
content, count = re.subn(pattern, replacement, content, count=1, flags=re.MULTILINE)
290+
if count == 0:
291+
tracker.error(('macOS workflow',), message=f'Could not find pattern: {pattern}')
292+
return
270293

271-
updated_content = macos_content.replace(target_line, new_line, 1)
272-
write_file_safely(macos_python_file, updated_content, 'macOS workflow', tracker)
294+
write_file_safely(workflow_file, content, 'macOS workflow', tracker)
273295

274296

275297
def upgrade_python_version_full_constant(app: Application, new_version: str, tracker: ValidationTracker):
@@ -336,6 +358,46 @@ def get_latest_python_version(app: Application, major_minor: str) -> str | None:
336358
return str(versions[-1])
337359

338360

361+
def get_pbs_release_info(app: Application, python_version: str) -> dict[str, str] | None:
362+
"""
363+
Get the latest PBS release info with SHA256 hashes for the specified Python version.
364+
365+
Returns dict with 'release', 'aarch64', 'x86_64' keys, or None if not found.
366+
"""
367+
try:
368+
# Get latest PBS release tag
369+
response = httpx.get(PBS_LATEST_RELEASE_URL, timeout=30)
370+
response.raise_for_status()
371+
release = orjson.loads(response.text).get('tag_name')
372+
if not release:
373+
return None
374+
375+
# Fetch SHA256SUMS file and extract hashes for our target files
376+
sha_response = httpx.get(PBS_SHA256SUMS_URL_TEMPLATE.format(release=release), timeout=30, follow_redirects=True)
377+
if sha_response.status_code != 200:
378+
return None
379+
380+
hashes = {'release': release}
381+
for arch in ('aarch64', 'x86_64'):
382+
filename = f'cpython-{python_version}+{release}-{arch}-apple-darwin-install_only_stripped.tar.gz'
383+
for line in sha_response.text.splitlines():
384+
if filename in line:
385+
sha_hash = line.split()[0]
386+
if validate_sha256(sha_hash):
387+
hashes[arch] = sha_hash
388+
break
389+
390+
# Verify we found both architectures
391+
if 'aarch64' not in hashes or 'x86_64' not in hashes:
392+
return None
393+
394+
return hashes
395+
396+
except httpx.RequestException as e:
397+
app.display_warning(f"Error fetching PBS release info: {e}")
398+
return None
399+
400+
339401
def get_python_sha256_hashes(app: Application, version: str) -> dict[str, str]:
340402
"""
341403
Fetch SHA256 hashes for Python release artifacts using SBOM files.

ddev/tests/cli/meta/scripts/conftest.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ def fake_repo(tmp_path_factory, config_file, local_repo, ddev, mocker):
192192
)
193193

194194
# Create fake macOS workflow file for Python upgrade tests
195+
# Uses Python Build Standalone (PBS) format
195196
write_file(
196197
repo_path / '.github' / 'workflows',
197198
'resolve-build-deps.yaml',
@@ -201,12 +202,14 @@ def fake_repo(tmp_path_factory, config_file, local_repo, ddev, mocker):
201202
build-macos:
202203
runs-on: macos-latest
203204
steps:
204-
- name: Install Python
205+
- name: Set up Python
205206
env:
206-
PYTHON3_DOWNLOAD_URL: "https://www.python.org/ftp/python/3.13.7/python-3.13.7-macos11.pkg"
207-
run: |-
208-
curl "$PYTHON3_DOWNLOAD_URL" -o python3.pkg
209-
sudo installer -pkg python3.pkg -target /
207+
PYTHON_PATCH: 7
208+
PBS_RELEASE: 20251202
209+
PBS_SHA256__aarch64: 799a3b76240496e4472dd60ed0cd5197e04637bea7fa16af68caeb989fadcb3a
210+
PBS_SHA256__x86_64: 705b39dd74490c3e9b4beb1c4f40bf802b50ba40fe085bdca635506a944d5e74
211+
run: |
212+
curl -fsSL -o pbs.tgz "https://github.com/astral-sh/python-build-standalone/releases/download/$PBS_RELEASE/cpython-$PYTHON_VERSION.$PYTHON_PATCH+$PBS_RELEASE-aarch64-apple-darwin-install_only_stripped.tar.gz"
210213
""",
211214
)
212215

ddev/tests/cli/meta/scripts/test_upgrade_python.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ def test_update_python_version_success(fake_repo, ddev, mocker):
1818
'windows_amd64_sha256': '200ddff856bbff949d2cc1be42e8807c07538abd6b6966d5113a094cf628c5c5',
1919
},
2020
)
21+
mocker.patch(
22+
'ddev.cli.meta.scripts.upgrade_python.get_pbs_release_info',
23+
return_value={
24+
'release': '20251210',
25+
'aarch64': 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2',
26+
'x86_64': 'f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5',
27+
},
28+
)
2129

2230
result = ddev('meta', 'scripts', 'upgrade-python-version')
2331

@@ -46,11 +54,14 @@ def test_update_python_version_success(fake_repo, ddev, mocker):
4654
assert '-Hash \'200ddff856bbff949d2cc1be42e8807c07538abd6b6966d5113a094cf628c5c5\'' in contents
4755
assert 'ENV PYTHON_VERSION="3.13.7"' not in contents
4856

49-
# Verify macOS workflow was updated
57+
# Verify macOS workflow was updated with PBS format
5058
workflow_file = fake_repo.path / '.github' / 'workflows' / 'resolve-build-deps.yaml'
5159
contents = workflow_file.read_text()
52-
assert 'python-3.13.9-macos11.pkg' in contents
53-
assert 'python-3.13.7-macos11.pkg' not in contents
60+
assert 'PYTHON_PATCH: 9' in contents
61+
assert 'PYTHON_PATCH: 7' not in contents
62+
assert 'PBS_RELEASE: 20251210' in contents
63+
assert 'PBS_SHA256__aarch64: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2' in contents
64+
assert 'PBS_SHA256__x86_64: f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5' in contents
5465

5566

5667
def test_update_python_version_already_latest(fake_repo, ddev, mocker):

0 commit comments

Comments
 (0)