diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index 7435f08..58cf35f 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -555,7 +555,7 @@ check-all.sh [--update | --prepare | --verify | --publish ] [-- ### 4.2 Step-by-step 1. Load `.aurtomator.conf` when it exists. In CI, `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL` and `GPG_KEY_ID` come from the workflow environment. -2. Clone `ssh://aur@aur.archlinux.org/.git` into a temporary directory, with up to 3 attempts. A clone without a PKGBUILD stops the update. +2. Wait 2 seconds, then clone `ssh://aur@aur.archlinux.org/.git` into a temporary directory, with up to 3 attempts 5 seconds apart. A clone without a PKGBUILD stops the update. 3. When the clone lands on a detached `HEAD`, reset `master` to `origin/master`, so the push is a fast-forward. 4. Warn when the PKGBUILD has no `# Maintainer:` line. 5. Audit the PKGBUILD. Split packages, `pkgver()` with the wrong strategy and mixed checksums block the update. Patches only warn. See [SUPPORTED-PACKAGES.md](SUPPORTED-PACKAGES.md). @@ -565,9 +565,11 @@ check-all.sh [--update | --prepare | --verify | --publish ] [-- 9. Regenerate `.SRCINFO` with `makepkg --printsrcinfo`. 10. With `--dry-run`, print the PKGBUILD diff and stop. 11. Commit `PKGBUILD` and `.SRCINFO` as ` release from `, authored by `GIT_AUTHOR_NAME` and `GIT_AUTHOR_EMAIL` (default `aurtomator`). With `GPG_KEY_ID` set, the commit is amended with a signature. When signing fails, the commit is pushed unsigned. -12. Push, with up to 3 attempts. +12. Push, with up to 3 attempts 5 seconds apart. 13. Write `current` and `last_updated` to the package YAML. +`AURTOMATOR_CLONE_DELAY` and `AURTOMATOR_RETRY_DELAY` change the 2 and the 5 seconds. The tests set both to 0. + ### 4.3 Downgrade behavior aurtomator follows upstream when it publishes a version older than the tracked one, and does **not** block it. Upstream is the source of truth: a packager that refuses a re-published older release only drifts out of sync. A downgrade is logged and shown, and it does not count as a failure. diff --git a/scripts/lib.sh b/scripts/lib.sh index 165cfc0..218ffa5 100644 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -275,12 +275,13 @@ validate_source_urls() { # AUR GIT # ============================================================================= -# Clone an AUR repo, with 3 attempts +# Clone an AUR repo, with 3 attempts. AURTOMATOR_CLONE_DELAY and +# AURTOMATOR_RETRY_DELAY set the pauses, in seconds. # Args: $1=url, $2=destination, $3...=options for git clone aur_clone() { - local attempt + local attempt delay="${AURTOMATOR_RETRY_DELAY:-5}" # Brief delay to reduce AUR SSH rate-limit pressure from GitHub Actions IP pool - sleep 2 + sleep "${AURTOMATOR_CLONE_DELAY:-2}" for attempt in 1 2 3; do if git clone "${@:3}" "$1" "$2" 2>&1; then return 0 @@ -289,9 +290,9 @@ aur_clone() { log_err "Failed to clone AUR repo $1 after 3 attempts" return 1 fi - log_warn "Clone attempt $attempt failed, retrying in 5s..." + log_warn "Clone attempt $attempt failed, retrying in ${delay}s..." rm -rf "$2" - sleep 5 + sleep "$delay" done } @@ -340,10 +341,11 @@ aur_export() { } # Commit what the index holds, sign when GPG_KEY_ID is set, push with 3 -# attempts. The caller stages PKGBUILD and .SRCINFO. +# attempts, AURTOMATOR_RETRY_DELAY seconds apart. The caller stages +# PKGBUILD and .SRCINFO. # Args: $1=clone directory, $2=version, $3=strategy (for the commit message) aur_commit_push() { - local dir="$1" attempt + local dir="$1" attempt delay="${AURTOMATOR_RETRY_DELAY:-5}" log_info "Pushing to AUR" git -C "$dir" config user.name "${GIT_AUTHOR_NAME:-aurtomator}" git -C "$dir" config user.email "${GIT_AUTHOR_EMAIL:-aurtomator@users.noreply.github.com}" @@ -369,8 +371,8 @@ aur_commit_push() { log_err "git push to AUR failed after 3 attempts" return 1 fi - log_warn "Push attempt $attempt failed, retrying in 5s..." - sleep 5 + log_warn "Push attempt $attempt failed, retrying in ${delay}s..." + sleep "$delay" done } diff --git a/tests/gpg-flow.bats b/tests/gpg-flow.bats index ab4c43d..8c74db3 100644 --- a/tests/gpg-flow.bats +++ b/tests/gpg-flow.bats @@ -6,11 +6,10 @@ load helpers/setup.sh -setup() { - setup_test_env - - # Isolated GPG keyring for all tests - export GNUPGHOME="$TEST_TMPDIR/gnupg" +# The keys are generated once per file, and every test gets a copy of the +# keyring +setup_file() { + export GNUPGHOME="$BATS_FILE_TMPDIR/gnupg" mkdir -m 0700 "$GNUPGHOME" echo "allow-loopback-pinentry" >"$GNUPGHOME/gpg-agent.conf" @@ -49,6 +48,17 @@ GPGEOF withpass_fpr=$(gpg --list-secret-keys --keyid-format long 2>/dev/null | grep -B2 "Test Withpass" | grep "^sec" | head -1 | awk '{print $2}' | cut -d'/' -f2) WITHPASS_MASTER_ID="$withpass_fpr" WITHPASS_SUBKEY_ID=$(gpg --list-secret-keys --keyid-format long 2>/dev/null | grep -A8 "Test Withpass" | grep "^ssb" | head -1 | awk '{print $2}' | cut -d'/' -f2) + export NOPASS_SUBKEY_ID NOPASS_MASTER_ID WITHPASS_MASTER_ID WITHPASS_SUBKEY_ID + + gpgconf --kill gpg-agent +} + +setup() { + setup_test_env + + # Isolated GPG keyring for each test + export GNUPGHOME="$TEST_TMPDIR/gnupg" + cp -a "$BATS_FILE_TMPDIR/gnupg" "$GNUPGHOME" # Git repo for commit signing tests AUR_DIR="$TEST_TMPDIR/aur-test-pkg" diff --git a/tests/helpers/setup.sh b/tests/helpers/setup.sh index 0c73bf0..c25054b 100644 --- a/tests/helpers/setup.sh +++ b/tests/helpers/setup.sh @@ -29,6 +29,9 @@ setup_test_env() { # Override AURTOMATOR_DIR so all scripts use our temp workspace export AURTOMATOR_DIR="$TEST_TMPDIR" + + # No pause before an AUR clone or between attempts + export AURTOMATOR_CLONE_DELAY=0 AURTOMATOR_RETRY_DELAY=0 } teardown_test_env() { diff --git a/tests/publish.bats b/tests/publish.bats index 85bf516..5eccfb0 100644 --- a/tests/publish.bats +++ b/tests/publish.bats @@ -1238,6 +1238,51 @@ make_export_clone() { [ ! -e "$clone/PKGBUILD" ] } +# === aur_clone and aur_commit_push === + +# Helper: a sleep on PATH that logs its argument to $TEST_TMPDIR/sleeps, and +# a git whose clone always fails +mock_sleep_and_clone() { + printf '#!/usr/bin/env bash\necho "$1" >>"%s/sleeps"\n' "$TEST_TMPDIR" >"$MOCK_DIR/sleep" + printf '#!/usr/bin/env bash\n[[ "$1" == clone ]] && exit 128\nexec /usr/bin/git "$@"\n' >"$MOCK_DIR/git" + chmod +x "$MOCK_DIR/sleep" "$MOCK_DIR/git" +} + +# Helper: a repo without a remote, with a staged PKGBUILD, at $TEST_TMPDIR/clone +make_unpushable_repo() { + git init -q -b master "$TEST_TMPDIR/clone" + echo "pkgver=1.0.0" >"$TEST_TMPDIR/clone/PKGBUILD" + git -C "$TEST_TMPDIR/clone" add PKGBUILD +} + +@test "aur_clone: waits the pauses the environment sets" { + mock_sleep_and_clone + AURTOMATOR_CLONE_DELAY=3 AURTOMATOR_RETRY_DELAY=7 run aur_clone https://example.invalid/demo.git "$TEST_TMPDIR/clone" + [ "$status" -eq 1 ] + [ "$(cat "$TEST_TMPDIR/sleeps")" = $'3\n7\n7' ] + [[ "$output" == *"Clone attempt 2 failed, retrying in 7s"* ]] +} + +@test "aur_commit_push: waits the pause the environment sets" { + mock_sleep_and_clone + make_unpushable_repo + AURTOMATOR_RETRY_DELAY=7 run aur_commit_push "$TEST_TMPDIR/clone" 1.0.0 test + [ "$status" -eq 1 ] + [ "$(cat "$TEST_TMPDIR/sleeps")" = $'7\n7' ] + [[ "$output" == *"Push attempt 2 failed, retrying in 7s"* ]] +} + +@test "aur_clone and aur_commit_push: wait 2 s before a clone and 5 s between attempts by default" { + mock_sleep_and_clone + make_unpushable_repo + unset AURTOMATOR_CLONE_DELAY AURTOMATOR_RETRY_DELAY + run aur_clone https://example.invalid/demo.git "$TEST_TMPDIR/other" + [ "$status" -eq 1 ] + run aur_commit_push "$TEST_TMPDIR/clone" 1.0.0 test + [ "$status" -eq 1 ] + [ "$(cat "$TEST_TMPDIR/sleeps")" = $'2\n5\n5\n5\n5' ] +} + # === check_prepared_pkgbuild === @test "check_prepared_pkgbuild: accepts new pkgver, pkgrel and checksums" {