Skip to content
quarry Artifact [209ee35bd7]
Login

Artifact [209ee35bd7]

Artifact 209ee35bd7444566773c88f1d552ca8d5471c6b91105716925f57a6a9f5e695d:


#!/bin/sh
# Watch deploys for the failures that end with nobody told. Run every
# five minutes by quarry-deploy-watch.timer (docs/running.md); each
# finding alerts once, through quarry-alert.sh.
#
#   1. a deploy has held the lock for over ten minutes
#   2. a deploy ended — its lock is free — while its state says it was
#      mid-way (installing, restarting, rolling back) and the journal has
#      no result line for it: killed, or its terminal went away
#   3. a deploy run by hand wrote a FAILED line: a unit's failure is
#      alerted by its own OnFailure=, a terminal's only by this
set -eu
LOCK=${QUARRY_DEPLOY_LOCK:-/run/quarry-deploy.lock}
STATE=${QUARRY_DEPLOY_STATE:-/var/lib/quarry-deploy/state}
WATCH=${QUARRY_DEPLOY_WATCH:-/var/lib/quarry-deploy-watch}
ALERT=${QUARRY_ALERT:-/srv/quarry/quarry-alert.sh}
LIMIT=${QUARRY_DEPLOY_LIMIT:-600}
mkdir -p "$WATCH"
now=$(date +%s)

# once KEY MESSAGE: alert unless KEY already was. An alert that could
# not be sent is tried again on the next run (no mark), and never stops
# this one: the other checks still run.
once() {
  mark="$WATCH/sent.$(printf '%s' "$1" | tr -c 'A-Za-z0-9._-' '_')"
  [ -f "$mark" ] && return 0
  if "$ALERT" --key "$1" --message "$2"; then
    touch "$mark"
  else
    logger -t quarry-deploy-watch "alert not sent: $1" || true
  fi
}

held=
if [ -f "$LOCK" ] && ! flock -n "$LOCK" true; then held=1; fi

# 1. Too long. The holder's id is "<start epoch>-<pid>".
if [ -n "$held" ]; then
  id=$(sed -n 's/.*id \([0-9]*-[0-9]*\)).*/\1/p' "$LOCK")
  start=${id%%-*}
  if [ -n "$start" ] && [ $((now - start)) -gt "$LIMIT" ]; then
    once "deploy-long:$id" "a deploy on $(hostname) has run $(( (now - start) / 60 )) min: $(cat "$LOCK")"
  fi
fi

# 2. Ended without a result.
if [ -z "$held" ] && [ -f "$STATE" ]; then
  read -r id stage tip <"$STATE" || true
  case "$stage" in
    ok | failed | "") ;;
    *)
      if ! journalctl -t quarry-deploy -o cat --since "@${id%%-*}" 2>/dev/null | grep -qE "^(OK|FAILED) .*id=$id( |$)"; then
        what="ended during $stage"
        [ "$stage" = rollback ] && what="began a rollback"
        once "deploy-lost:$id" "a deploy of $tip on $(hostname) $what and recorded no result; check which binary is serving (journalctl -t quarry-deploy, /srv/quarry/bin)"
      fi ;;
  esac
fi

# 3. FAILED lines from deploys run by hand, since the last look. The
# cursor file makes each line read once; the first run looks back one
# interval rather than at all of history.
cur="$WATCH/cursor"
if [ -s "$cur" ]; then
  lines=$(journalctl -t quarry-deploy -o cat --cursor-file="$cur" 2>/dev/null || true)
else
  lines=$(journalctl -t quarry-deploy -o cat --since=-6min --cursor-file="$cur" 2>/dev/null || true)
fi
printf '%s\n' "$lines" | grep '^FAILED .* via=manual:' | while IFS= read -r line; do
  id=$(printf '%s' "$line" | sed -n 's/.* id=\([0-9]*-[0-9]*\) .*/\1/p')
  once "deploy-fail:${id:-$now}" "deploy on $(hostname): $line"
done