Two defects an independent survey of the tooling surface found in the new helpers, fixed pre-gate: - lane-brief.sh: label filter used jq contains() (substring) — `-l security` wrongly matched label `domain/6-security`. Now exact-token match against tea's space-separated labels string. Verified live: `-l security` -> 0, `-l domain/6-security` -> the real holders. - ci-wait.sh: unknown owner silently defaulted to the `usc` Woodpecker instance (wrong credentials, wrong pipelines). Now fails hard requiring `-a <instance>`, matching lane-brief's FATAL-on-unresolved behavior. Verified: usc owner still infers and exits 0; unknown owner exits 2 with guidance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kt2D8TsnDwhtzEAPijsNmR
87 lines
3.8 KiB
Bash
Executable File
87 lines
3.8 KiB
Bash
Executable File
#!/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 <owner/repo> -n <num> [-n <num> ...] [-a <instance>] [-i <interval>] [-t <timeout>]
|
|
# 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 <owner/repo> required" >&2; exit 2; }
|
|
[[ ${#NUMS[@]} -gt 0 ]] || { echo "FATAL: at least one -n <pipeline-number> 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 ;;
|
|
*) echo "FATAL: cannot infer Woodpecker instance for owner '${REPO%%/*}' — pass -a <instance>" >&2; exit 2 ;;
|
|
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
|