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
38 changes: 38 additions & 0 deletions scripts/release-signature-reminder.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,44 @@ list_reminders() {
}
ISSUES="$(list_reminders)"

# Stale pass: reminders for releases that are no longer the latest. At most 20
# per run; only titles matching the pattern exactly are ever touched.
# The jq program is kept in a variable (a quoted here-document) so it reads as the
# jq program it is; it only ever receives the issue list as JSON input.
STALE_FILTER="$(cat <<'JQ'
[.[] | select(.title
| test("^Sign release v[0-9]+\\.[0-9]+\\.[0-9]+ \\(minisign\\)$"))
| {number, tag: (.title | capture("^Sign release (?<t>[^ ]+) \\(minisign\\)$").t)}
| select(.tag != $latest)]
| sort_by(.number) | .[0:20][]
| "\(.number) \(.tag)"
JQ
)"
STALE="$(printf '%s' "$ISSUES" | jq -r --arg latest "$TAG" "$STALE_FILTER")"
if [ -n "$STALE" ]; then
while read -r SNUM STAG; do
printf '%s' "$STAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' || continue
ERR="$(mktemp)"
if OLD="$(gh api "repos/$REPO/releases/tags/$STAG" 2>"$ERR" </dev/null)"; then
if printf '%s' "$OLD" | jq -e '[.assets[]?.name] | index("checksums-sha256.txt.minisig") != null' >/dev/null; then
MSG="Signature found on the release, closing."
else
MSG="Superseded: $TAG is now the latest release, and \`uteke upgrade\` only installs the latest release, so this release no longer needs signing. Closing."
fi
elif grep -q 'HTTP 404' "$ERR"; then
MSG="Superseded: $TAG is now the latest release, and \`uteke upgrade\` only installs the latest release, so this release no longer needs signing. Closing."
else
echo "Could not look up $STAG for reminder #$SNUM; leaving it open."
rm -f "$ERR"; continue
fi
rm -f "$ERR"
echo "Reminder #$SNUM is for $STAG, not the latest ($TAG); closing."
mutate issue close "$SNUM" --repo "$REPO" --comment "$MSG"
done <<STALE_EOF
$STALE
STALE_EOF
fi

has_asset() {
printf '%s' "$REL" | jq -e --arg n "$1" '[.assets[]?.name] | index($n) != null' >/dev/null
}
Expand Down
53 changes: 53 additions & 0 deletions scripts/tests/release-signature-reminder.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ case "$1" in
api)
case "$2" in
*/releases/latest) printf '%s' "$STUB_RELEASE" ;;
*/releases/tags/*)
f="$STUB_TAGDIR/${2##*/}.json"
if [ -n "${STUB_API_ERR-}" ]; then echo "$STUB_API_ERR" >&2; exit 1; fi
if [ -f "$f" ]; then cat "$f"; else echo "gh: Not Found (HTTP 404)" >&2; exit 1; fi ;;
esac ;;
issue)
case "$2" in
Expand Down Expand Up @@ -158,15 +162,56 @@ t "unknown flag before --dry-run -> exit 2" rc_is 2
run "$(rel v1.2.3 "$A_BUNDLE")" '[]' --dry-run --dry-run
t "repeated --dry-run is harmless" no_mutation

# --- stale pass: reminders for superseded releases ---
rm -f "$T/tags/"*
echo "{\"assets\":$A_SIGNED}" > "$T/tags/v1.0.0.json"
echo "{\"assets\":$A_BUNDLE}" > "$T/tags/v1.1.0.json"
LATEST="$(rel v1.2.3 "$A_SIGNED")"

run "$LATEST" "[$(oldissue 3 v1.0.0)]"
t "old reminder, signed old release -> exit 0" rc_is 0
grep -q '^CLOSE issue close 3 .*Signature found on the release, closing' "$T/log"; check "old signed -> Signature found comment" $?
grep -q '^CLOSE issue close 3 ' "$T/log"; check "old signed -> closed" $?

run "$LATEST" "[$(oldissue 4 v1.1.0)]"
grep -q '^CLOSE issue close 4 .*Superseded: v1.2.3 is now the latest release' "$T/log"; check "old unsigned -> superseded comment" $?
grep -q '^CLOSE issue close 4 ' "$T/log"; check "old unsigned -> closed" $?

run "$LATEST" "[$(oldissue 7 v1.2.3 "")]"
t "latest-tag reminder (signed latest) handled only by main logic: one close" test "$(nlog '^CLOSE ')" = 1
grep -q 'Signature found' "$T/log"; check "latest-tag reminder gets the normal close, not superseded" $?
run "$(rel v1.2.3 "$A_BUNDLE")" "[$(oldissue 7 v1.2.3 "" app/github-actions 2)]"
t "latest-tag unsigned young reminder untouched by stale pass" no_mutation

run "$LATEST" "[$(oldissue 5 v1.0.0 'Sign release v1.0.0 (minisign) please'),$(oldissue 6 v1.0.0 'Sign release v1.0.0-rc.1 (minisign)'),$(oldissue 9 x 'Unrelated issue')]"
t "non-matching titles never touched" no_mutation

run "$LATEST" "[$(oldissue 3 v1.0.0),$(oldissue 4 v1.1.0)]"
t "two stale issues -> two closes" test "$(nlog '^CLOSE ')" = 2
t "two stale issues -> comments ride on the closes" test "$(nlog '^COMMENT ')" = 0

run "$LATEST" "[$(oldissue 8 v0.9.0)]"
grep -q '^CLOSE issue close 8 .*Superseded' "$T/log"; check "404 on old tag -> superseded" $?
grep -q '^CLOSE issue close 8 ' "$T/log"; check "404 on old tag -> closed" $?

run "$LATEST" "[$(oldissue 3 v1.0.0)]" --dry-run
t "--dry-run stale pass -> no mutating call" no_mutation

# --- trust boundary: issue authors ---
BUN="$(rel v1.2.3 "$A_BUNDLE")"
run "$BUN" "[$(oldissue 20 v1.2.3 "" stranger 2)]"
t "stranger look-alike ignored, genuine issue still created" test "$(nlog '^CREATE ')" = 1
t "stranger look-alike never commented or closed" test "$(nlog '^COMMENT \|^CLOSE ')" = 0
run "$LATEST" "[$(oldissue 21 v1.2.3 "" stranger 2),$(oldissue 22 v1.0.0 "" stranger)]"
t "stranger look-alikes (latest and stale) never touched when signed" no_mutation
run "$BUN" "[$(oldissue 23 v1.2.3 "" 'github-actions[bot]' 13)]"
grep -q '^COMMENT issue comment 23 ' "$T/log"; check "github-actions[bot] issue is acted on" $?
run "$BUN" "[$(oldissue 24 v1.2.3 "" ajianaz 13)]"
grep -q '^COMMENT issue comment 24 ' "$T/log"; check "RELEASE_SIGNER issue is acted on" $?
run "$LATEST" "[$(oldissue 25 v1.0.0 "" ajianaz)]"
grep -q '^CLOSE issue close 25 ' "$T/log"; check "signer-authored stale issue closed" $?
run "$LATEST" "[$(oldissue 26 v1.0.0 "" stranger)]"
t "stranger stale look-alike not closed" no_mutation

# --- review hardening ---
# listing never uses --search (stub fails the run if it does)
Expand All @@ -190,6 +235,14 @@ t "REMINDER_AFTER_HOURS=08 accepted, 10h old -> re-pings" test "$RC" = 0 -a "$(n
OUT="$(env PATH="$T/bin:$PATH" REMINDER_AFTER_HOURS=abc GH_REPO=o/r bash "$SCRIPT" 2>&1)"; RC=$?
t "non-numeric hours -> exit 2" rc_is 2

# 404 parsing
STUB_API_ERR="gh: Internal error mentions 404 (HTTP 502)" run "$LATEST" "[$(oldissue 3 v1.0.0)]"
t "5xx whose text contains 404 -> issue left open" no_mutation
STUB_API_ERR="gh: Server Error (HTTP 500)" run "$LATEST" "[$(oldissue 3 v1.0.0)]"
t "HTTP 500 on old tag -> issue left open" no_mutation
STUB_API_ERR="gh: API rate limit exceeded (HTTP 403)" run "$LATEST" "[$(oldissue 3 v1.0.0)]"
t "rate limit on old tag -> issue left open" no_mutation

# assign failure: re-list before retrying
STUB_FAIL_ASSIGN=1 STUB_ISSUES2="[$(oldissue 30 v1.2.3 "" app/github-actions 1)]" run "$BUN" '[]'
t "assign fails but issue now exists -> no second create" test "$(nlog '^CREATE ')" = 1
Expand Down
Loading