Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/scripts/publish_hastegeo_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,19 @@ def validate_wheel(
f"Wheel filename {wheel_path.name!r} does not match "
f"{expected_name!r}"
)
if not wheel_path.is_file() or not zipfile.is_zipfile(wheel_path):
if not wheel_path.is_file():
raise ValueError(
f"Expected wheel not found: {wheel_path}. The downloaded "
"artifact does not contain a file matching the re-resolved "
f"version {expected_version!r}. This is unexpected: either "
"the build artifact was not uploaded correctly, or the "
"version re-resolved here no longer matches what the build "
"produced (e.g. a wheel was published for this target "
"version by another run after this one was built); re-run "
"the build workflow to obtain a wheel matching the "
"currently available version."
)
if not zipfile.is_zipfile(wheel_path):
raise ValueError(f"Not a valid wheel ZIP file: {wheel_path}")

with zipfile.ZipFile(wheel_path) as archive:
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/hastegeo-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ permissions:
actions: read
contents: read

# Serialize entire workflow runs (not just the publish-rc job) so that
# version resolution in "prepare" always reflects the outcome of every
# earlier run. Without this, two concurrent PR builds can both resolve
# the same "next" RC number (e.g. rc1) before either one publishes,
# and the second run's build artifact permanently mismatches the RC
# number re-resolved by its "prepare" job on any retry.
concurrency:
group: hastegeo-publish
cancel-in-progress: false

jobs:
prepare:
if: >-
Expand Down
11 changes: 11 additions & 0 deletions hastelib/tests/build/test_release_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ def test_validate_wheel_rejects_stable_version_on_rc_channel(self):
with self.assertRaisesRegex(ValueError, "requires an rcN"):
publish_hastegeo_wheel.validate_wheel(wheel, "1.0.26", "rc")

def test_validate_wheel_reports_missing_file_clearly(self):
with tempfile.TemporaryDirectory() as temp_dir:
missing = Path(temp_dir) / "hastegeo-1.0.26rc2-py3-none-any.whl"

with self.assertRaisesRegex(
ValueError, "Expected wheel not found"
):
publish_hastegeo_wheel.validate_wheel(
missing, "1.0.26rc2", "rc"
)

@patch.object(
publish_hastegeo_wheel,
"list_release_assets",
Expand Down
22 changes: 22 additions & 0 deletions hastelib/tests/build/test_release_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,28 @@ def test_rc_and_stable_are_both_automatic_but_kill_switched(self):
self.assertIn("contents: write", workflow)
self.assertNotIn("--clobber", publisher)

def test_publish_workflow_runs_are_serialized(self):
"""Concurrent runs must not race for the same "next" RC number.

"prepare" re-resolves the version independently of the triggering
build. If two builds for different commits both find the release
with no RC yet for their target version, they both compute the
same next number, and whichever publishes second permanently fails
because its already-uploaded build artifact can never match a
re-resolved, higher RC number. A workflow-level concurrency group
(not just the publish-rc job's) prevents this by ensuring only one
run's "prepare" step is ever resolving a version against the
release state at a time.
"""
workflow = (
REPO_ROOT / ".github/workflows/hastegeo-publish.yml"
).read_text(encoding="utf-8")
pre_jobs = workflow.split("\njobs:", 1)[0]

self.assertIn("concurrency:", pre_jobs)
self.assertIn("group: hastegeo-publish", pre_jobs)
self.assertIn("cancel-in-progress: false", pre_jobs)

def test_pr_workflow_does_not_build_images_twice(self):
workflow = (
REPO_ROOT / ".github/workflows/hastegeo-build.yml"
Expand Down