#!/usr/bin/env bash # Run cutover-one for each line in a list file OR a registry batch. # # Usage: # ./cutover-list.sh --batch wave1-lm # ./cutover-list.sh batches/foo.txt # PARALLELISM=2 ./cutover-list.sh --batch wave4-lp-3 # ./cutover-list.sh --batch wave1-lm --force # also re-cutover status=done|cutover # ./cutover-list.sh --batch wave4-lp-3 --mem-req 256Mi # MM memory request after cutover # # Without --force: ONLY registry status=verified (batch export + per-row check). # With --force: also queue status=done|cutover (re-cutover). # --mem-req: optional k8s memory request for MM (256Mi, 250M, 1Gi); default from config.env. # Prefer PARALLELISM=1 for production cutovers unless you know you want overlap. set -euo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "${DIR}/lib.sh" LIST="" BATCH="" FORCE=0 MEM_REQ="" # locked unless --force (then export may include done/cutover) STATUS="verified" while [[ $# -gt 0 ]]; do case "$1" in --batch) BATCH="${2:-}"; shift 2 ;; --status) # only meaningful with --force; without force we always use verified STATUS="${2:-}" shift 2 ;; --mem-req|--memory-request) MEM_REQ="$(normalize_mem_req "${2:-}")" shift 2 ;; --force) FORCE=1; shift ;; -h|--help) sed -n '2,16p' "$0" exit 0 ;; *) LIST="$1" shift ;; esac done if [[ -n "$MEM_REQ" ]]; then export CUTOVER_MEMORY_REQUEST="$MEM_REQ" fi if [[ "$FORCE" != "1" ]]; then STATUS="verified" else # re-cutover already finished installs too if [[ "$STATUS" == "verified" ]]; then STATUS="verified,done,cutover" fi fi registry_status() { local installation="$1" st st="$(registry_get "$installation" status 2>/dev/null || true)" printf '%s' "$st" | tr -d '[:space:]' } # Return 0 if this install may be cut over under current FORCE/STATUS rules. cutover_allowed() { local installation="$1" st allowed st="$(registry_status "$installation")" if [[ "$FORCE" == "1" ]]; then case "$st" in verified|done|cutover) return 0 ;; *) # honor custom --status list when --force IFS=',' read -r -a allowed <<<"$STATUS" for a in "${allowed[@]}"; do [[ "$st" == "$a" ]] && return 0 done return 1 ;; esac fi [[ "$st" == "verified" ]] } WORK="$(mktemp)" NORM="$(mktemp)" trap 'rm -f "$WORK" "$NORM"' EXIT if [[ -n "$BATCH" ]]; then # --include-done: verified/done/cutover are in DONE_LIKE and would be skipped otherwise "${DIR}/registry-export-batch.sh" "$BATCH" --status "$STATUS" --include-done -o "$WORK" log "=== cutover-list batch=${BATCH} status=${STATUS} parallelism=${PARALLELISM} force=${FORCE} mem-req=${CUTOVER_MEMORY_REQUEST:-default} ===" elif [[ -n "$LIST" ]]; then [[ -f "$LIST" ]] || die "list file not found: $LIST" while IFS= read -r line || [[ -n "$line" ]]; do [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue echo "$line" >>"${WORK}.raw" done <"$LIST" mv "${WORK}.raw" "$WORK" log "=== cutover-list ${LIST} parallelism=${PARALLELISM} force=${FORCE} mem-req=${CUTOVER_MEMORY_REQUEST:-default} (filter status=${STATUS}) ===" else die "usage: $0 --batch | [--force] [--mem-req 256Mi]" fi skipped=0 queued_preview=0 while IFS= read -r line || [[ -n "$line" ]]; do [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue # shellcheck disable=SC2086 set -- $line ctx="${1:-}"; installation="${2:-}" [[ -n "$ctx" && -n "$installation" ]] || continue shift 2 2>/dev/null || true if ! cutover_allowed "$installation"; then st="$(registry_status "$installation")" if [[ "$FORCE" == "1" ]]; then log "SKIP ${installation} (status=${st:-empty}; --force allows verified|done|cutover)" else log "SKIP ${installation} (status=${st:-empty}; need verified, or pass --force)" fi skipped=$((skipped + 1)) continue fi printf '%s\t%s' "$ctx" "$installation" >>"$NORM" for a in "$@"; do printf '\t%s' "$a" >>"$NORM" done printf '\n' >>"$NORM" queued_preview=$((queued_preview + 1)) done <"$WORK" total="$(wc -l <"$NORM" | tr -d ' ')" log "Queued ${total} installations for cutover (skipped=${skipped})" [[ "$total" -gt 0 ]] || { log "nothing to cutover"; exit 0; } run_one() { local ctx="$1" installation="$2" st shift 2 # re-check: parallel workers / stale export if ! cutover_allowed "$installation"; then st="$(registry_status "$installation")" log "SKIP ${installation} (status=${st:-empty} at run time)" return 0 fi local extra=() if [[ -n "${MEM_REQ:-}" ]]; then extra+=(--mem-req "$MEM_REQ") fi "${DIR}/cutover-one.sh" "$ctx" "$installation" "${extra[@]+"${extra[@]}"}" "$@" \ && LOOPCTL_YES=1 "${DIR}/bind.sh" "$ctx" "$installation" \ || log "FAILED cutover/bind $installation (continuing)" } if [[ "${PARALLELISM}" -le 1 ]]; then while IFS=$'\t' read -r ctx installation a3 a4 || [[ -n "${ctx:-}" ]]; do [[ -z "${ctx:-}" ]] && continue args=() [[ -n "${a3:-}" ]] && args+=("$a3") [[ -n "${a4:-}" ]] && args+=("$a4") run_one "$ctx" "$installation" "${args[@]+"${args[@]}"}" done <"$NORM" else while IFS=$'\t' read -r ctx installation a3 a4 || [[ -n "${ctx:-}" ]]; do [[ -z "${ctx:-}" ]] && continue while [[ "$(jobs -pr | wc -l | tr -d ' ')" -ge "${PARALLELISM}" ]]; do sleep 5 done args=() [[ -n "${a3:-}" ]] && args+=("$a3") [[ -n "${a4:-}" ]] && args+=("$a4") run_one "$ctx" "$installation" "${args[@]+"${args[@]}"}" & done <"$NORM" wait || true fi log "=== cutover-list finished ===" python3 "${DIR}/registry_lib.py" --registry "${REGISTRY_PATH}" summary || true