#!/usr/bin/env bash # ci-wait.sh — block until one or more Woodpecker pipelines reach terminal state. # # Problem it solves: orchestrators hand-author a `while true; curl .../repos/1/pipelines/$n # ...; sleep` loop for every CI wait. Those loops HARDCODE Woodpecker repo id 1 (only # correct for whichever repo happens to be id 1), re-implement URL building with raw # curl, and tend to get armed as tight <300s ScheduleWakeup polls (each poll = a full # wake+reload+recheck cycle). This encapsulates the loop once, on top of the existing # `pipeline-status.sh` wrapper (which resolves repo->id correctly and is instance-aware), # so a CI wait becomes a one-liner. # # Intended use: as the COMMAND of a Monitor / event-driven re-invoke (primary), paired # with a single long (>=1500s) timed fallback — NOT as a tight standalone poll. # # Usage: # ci-wait.sh -r -n [-n ...] [-a ] [-i ] [-t ] # ci-wait.sh -r usc/uconnect -n 3917 -n 3918 # wait for both, infer instance # ci-wait.sh -r usc/uconnect -n 3922 -a usc -i 30 -t 2400 # # Instance is inferred from the owner (usc->usc, mosaicstack/mosaic->mosaic) unless -a given. # Exit: 0 = all pipelines terminal AND all 'success'; 1 = >=1 terminal non-success; # 2 = usage/precondition error; 3 = timeout before all terminal. set -euo pipefail # Resolve pipeline-status.sh as a sibling, matching how the woodpecker tools source # _lib.sh — works under the installed runtime AND an in-repo checkout, no MOSAIC_HOME dep. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PS="$SCRIPT_DIR/pipeline-status.sh" REPO="" INSTANCE="" INTERVAL=30 TIMEOUT=3600 NUMS=() while getopts "r:n:a:i:t:h" opt; do case "$opt" in r) REPO="$OPTARG" ;; n) NUMS+=("$OPTARG") ;; a) INSTANCE="$OPTARG" ;; i) INTERVAL="$OPTARG" ;; t) TIMEOUT="$OPTARG" ;; h) grep '^#' "$0" | sed 's/^# \?//'; exit 0 ;; *) echo "see -h" >&2; exit 2 ;; esac done [[ -n "$REPO" ]] || { echo "FATAL: -r required" >&2; exit 2; } [[ ${#NUMS[@]} -gt 0 ]] || { echo "FATAL: at least one -n required" >&2; exit 2; } [[ -x "$PS" ]] || { echo "FATAL: pipeline-status.sh not found/executable at $PS" >&2; exit 2; } # Infer Woodpecker instance from owner unless overridden (matches the git-wrapper convention). if [[ -z "$INSTANCE" ]]; then case "${REPO%%/*}" in usc|USC) INSTANCE=usc ;; mosaicstack|mosaic) INSTANCE=mosaic ;; *) INSTANCE=usc ;; esac fi command -v jq >/dev/null || { echo "FATAL: jq not found" >&2; exit 2; } TERMINAL_RE='^(success|failure|error|killed|declined|blocked)$' declare -A STATE=() # num -> terminal status, once reached start=$(date +%s 2>/dev/null || echo 0) echo "ci-wait: $REPO pipelines [${NUMS[*]}] (instance=$INSTANCE, every ${INTERVAL}s, timeout ${TIMEOUT}s)" while true; do for n in "${NUMS[@]}"; do [[ -n "${STATE[$n]:-}" ]] && continue s=$("$PS" -r "$REPO" -n "$n" -a "$INSTANCE" -f json 2>/dev/null | jq -r '.status // empty' 2>/dev/null || true) if [[ "$s" =~ $TERMINAL_RE ]]; then STATE[$n]="$s" echo " pipeline $n TERMINAL: $s" fi done # all terminal? if [[ ${#STATE[@]} -eq ${#NUMS[@]} ]]; then bad=0 for n in "${NUMS[@]}"; do [[ "${STATE[$n]}" == "success" ]] || bad=1; done if [[ $bad -eq 0 ]]; then echo "ci-wait: ALL SUCCESS"; exit 0; fi echo "ci-wait: all terminal, NOT all success — $(for n in "${NUMS[@]}"; do printf '%s=%s ' "$n" "${STATE[$n]}"; done)" exit 1 fi now=$(date +%s 2>/dev/null || echo 0) if [[ "$start" != 0 && $((now - start)) -ge $TIMEOUT ]]; then echo "ci-wait: TIMEOUT after ${TIMEOUT}s — pending: $(for n in "${NUMS[@]}"; do [[ -z "${STATE[$n]:-}" ]] && printf '%s ' "$n"; done)" exit 3 fi sleep "$INTERVAL" done