Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
- name: Check the release waits for crates.io to index each crate
run: bash tests/release/crates-index-poll.test.sh

- name: Check GitHub release creation is safe to re-run
run: bash tests/release/release-create-rerun.test.sh

- name: Test internal dependency pin coverage
run: bash tests/release/release-version-pins.test.sh

Expand Down Expand Up @@ -310,7 +313,7 @@
patchelf

- name: Set up Rust
uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f # stable (branch)

Check warning on line 316 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / docs-and-hygiene

316:79 [comments] too few spaces before comment: expected 2
with:
toolchain: stable
components: rustfmt, clippy
Expand Down Expand Up @@ -376,7 +379,7 @@
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Rust
uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f # stable (branch)

Check warning on line 382 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / docs-and-hygiene

382:79 [comments] too few spaces before comment: expected 2
with:
toolchain: stable

Expand Down
15 changes: 11 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,17 @@ jobs:
VERSION: ${{ github.ref_name }}
REPO: ${{ github.repository }}
run: |
gh release create "$VERSION" \
--repo "$REPO" \
--title "$VERSION" \
--generate-notes
if release_output="$(gh release view "$VERSION" --repo "$REPO" 2>&1)"; then
echo "Release $VERSION already exists; skipping creation"
elif [[ "$release_output" == *'release not found'* ]]; then
gh release create "$VERSION" \
--repo "$REPO" \
--title "$VERSION" \
--generate-notes
else
printf '::error::Failed to check release %s: %s\n' "$VERSION" "$release_output" >&2
exit 1
fi

- name: Upload release assets
# `dist/sysknife-*-linux-x86_64` matches BOTH `sysknife-v…-x86_64`
Expand Down
58 changes: 58 additions & 0 deletions tests/release/release-create-rerun.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"

python3 - <<'PY'
import re
import sys
from pathlib import Path

import yaml

workflow = yaml.safe_load(Path(".github/workflows/release.yml").read_text())
steps = workflow["jobs"]["release"]["steps"]
scripts = [step.get("run", "") for step in steps]
creates = [script for script in scripts if 'gh release create "$VERSION"' in script]
if len(creates) != 1:
sys.exit("FAIL: expected exactly one release creation script")

# Match the control flow, not merely the presence of view and create somewhere
# in the workflow. Creation must be confined to the explicit not-found branch.
guard = re.fullmatch(
r'if release_output="\$\(gh release view "\$VERSION" --repo "\$REPO" 2>&1\)"; then\n'
r'(?P<exists>.*?)'
r'^elif \[\[ "\$release_output" == \*\'release not found\'\* \]\]; then\n'
r'(?P<missing>.*?)'
r'^else\n(?P<error>.*?)^fi\s*',
creates[0],
re.MULTILINE | re.DOTALL,
)
if guard is None:
sys.exit("FAIL: gh release create is not guarded by the release-existence check")

create = re.fullmatch(
r'\s*gh release create "\$VERSION" \\\n'
r'\s*--repo "\$REPO" \\\n'
r'\s*--title "\$VERSION" \\\n'
r'\s*--generate-notes\s*',
guard["missing"],
)
if create is None or 'gh release create' in guard["exists"] + guard["error"]:
sys.exit("FAIL: release creation must occur only for a missing release")
if not re.fullmatch(r'\s*echo [^\n]+\n', guard["exists"]):
sys.exit("FAIL: an existing release must continue without creation or exit")
if not re.fullmatch(
r"\s*printf '::error::[^\n]+\"\$release_output\" >&2\n\s*exit 1\n",
guard["error"],
):
sys.exit("FAIL: unexpected lookup errors must be reported and stop the release")

create_index = scripts.index(creates[0])
if not any('gh release upload "$VERSION"' in script and '--clobber' in script
for script in scripts[create_index + 1:]):
sys.exit("FAIL: guarded creation must be followed by clobber-enabled upload")

print("release creation re-run contract passed.")
PY
Loading