#!/bin/bash # silly, but good-enough bugzilla monitoring # jpokorny@redhat.com (will be pleased to know about further enhancements) # licensed under GPLv2+ (note: this file only, not the bundled helper) # usage: # 1. optionally git clone git://git.fedorahosted.org/git/python-bugzilla # (system-wide can also be used) # 2. open ~/.watchbzrc for editing # - set BUGZILLA_ROOT when using cloned python-bugzilla # - optionally set STATUS and COMPONENT as per example # (or pass such string as a 1st argument) # - optionally set BZUSER to avoid the need to login manually before # the "watch session"; you can set BZPASSWORD as well but be careful! # 3. optionally install expect package (-> security++) # # make watch work with less in order to get scrolling feature seems to be # impossible, this might be a way forward (a bit buggy when tested): # https://github.com/jyapayne/UnixWatchCommandOutput # # TODO: # - if status is always uppercased, do the uppercasing automatically? set -u umask 077 HERE="$(dirname "$(readlink -f "$0")")" BUGZILLA=$(which bugzilla 2>/dev/null) BUGZILLA_ROOT=/usr BUGZILLA_COOKIE=~/.watchbzcookies BUGZILLA_LOGOUT=1 # example defaults, modify via ~/.watchbzrc : ${WATCHBZ_DEBUG:=0} COMPONENT=acpid,mc STATUS=OPEN REFRESH_INTERVAL=600 # [s], better not to drain bugzilla's power... BZUSER= BZPASSWORD= MINE=0 QUERY_REST= BZCUSTOMFILTER= # user configuration [ -f ~/.watchbzrc ] && source ~/.watchbzrc # Fedora and color-friendly watch: rhbz#801626 WATCHCMD=watch PRECOLORIZE=cat COLORIZE=cat if watch --color -n0.1 -g date &>/dev/null; then WATCHCMD='watch --color' #PRECOLORIZE="sed -u" PRECOLORIZE="sed" PRECOLORIZE+=" 's|\( [NAPMQDVRC] \)\(.*\)\([\{]${BZUSER}[\}]\)|\x1b[1;31m\1\x1b[0m\2|'" COLORIZE="sed" COLORIZE+=" -e 's|\(\([0-9]\{0,2\}[.A-Zz]\)\+[0-9]*+\+[, ]\)|\x1b[1;32m\1\x1b[0m|g'" COLORIZE+=" -e 's|\(\([0-9]\{0,2\}[.A-Zz]\)\+[0-9]*-\+[, ]\)|\x1b[33m\1\x1b[0m|g'" COLORIZE+=" -e 's|\(\([0-9]\{0,2\}[.A-Zz]\)\+[0-9]*?\+[, ]\)|\x1b[37m\1\x1b[0m|g'" COLORIZE+=" -e 's|\(I?[, ]\)|\x1b[1;36m\1\x1b[0m|g'" COLORIZE+=" -e 's|\(!!!\|BL+\)|\x1b[1;31m\1\x1b[0m|'" fi # guess correct paths if [ -z "$BUGZILLA" ] || [ -n "${BUGZILLA_ROOT}" ]; then # BUGZILLA_ROOT (path to local repo) should rather be set in ~/.watchbzrc BUGZILLA="${BUGZILLA_ROOT}/bin/bugzilla" PYTHONPATH=${PYTHONPATH:+:${PYTHONPATH}} # force tilde expansion eval export PYTHONPATH="${BUGZILLA_ROOT}${PYTHONPATH}" fi # hardcoded for now (note the SEP - OUTPUT_FMT relationship) SEP=@@@ WIDTH=$(stty size | cut -d' ' -f2) # options/arguments CMDLINE_COMPONENT= CMDLINE_STATUS= CMDLINE_HELP= CMDLINE_QUERY_REST= while [ $# -ge 1 ]; do case "$1" in -h|--help) read -p "wanna see the script (the only help ATM)? [Yn] " \ CMDLINE_HELP [[ ${CMDLINE_HELP} =~ ^[Yy].*|^$ ]] && less "${BASH_SOURCE}"; exit;; -m|--mine) MINE=1;; -d|--debug) WATCHBZ_DEBUG=1;; --) shift; break;; -*) CMDLINE_STATUS+=",$(echo "$1" | cut -c2-)";; *) CMDLINE_COMPONENT+=",$1";; esac shift done while [ $# -ge 1 ]; do CMDLINE_QUERY_REST+=" $1"; shift; done # normalize and check die () { echo "$@"; exit 2;} test -n "${CMDLINE_COMPONENT}" && COMPONENT=${CMDLINE_COMPONENT#,} test -n "${CMDLINE_STATUS}" && STATUS=${CMDLINE_STATUS#,} test -n "${CMDLINE_QUERY_REST}" && QUERY_REST=${CMDLINE_QUERY_REST} OUTPUT_FMT_ASSIGNED_TO="\ \{%{assigned_to}\}" if test -z "${MINE}" || test "${MINE}" -eq 0; then :; else OUTPUT_FMT_ASSIGNED_TO= if test -n "${BZUSER}"; then QUERY_REST="-a \"${BZUSER}\" ${QUERY_REST}" else die "BZUSER has to be defined to use --mine" fi fi # omit component if only single one queried echo $COMPONENT | grep -q ',' if [ $? -eq 0 ]; then OUTPUT_FMT="%{component}$SEP%{bug_id}$SEP%{product}$SEP%{priority}$SEP%{status}$SEP%{flags}$SEP%{short_desc}${OUTPUT_FMT_ASSIGNED_TO}" else OUTPUT_FMT="%{bug_id}$SEP%{product}$SEP%{priority}$SEP%{status}$SEP%{flags}$SEP%{short_desc}${OUTPUT_FMT_ASSIGNED_TO}" fi do_logout () { test -n "${BZUSER}" \ && rm "${BUGZILLA_COOKIE}" \ && echo "watch-bz: Authorization cookie removed" } do_init_login () { if [ -z "$BZPASSWORD" ]; then trap 'stty echo; exit' INT trap return USR1 { timeout 60 /bin/sh -c \ "while [ ! -f \"${BUGZILLA_COOKIE}\" ]; do \ sleep 1; \ done" \ && echo " entering..." kill -USR1 -$$ } & read -s -p 'Password (optional with 1 min timeout): ' BZPASSWORD \ || return $? trap '' USR1 trap '' INT fi rm -f -- ${BUGZILLA_COOKIE}.init if which expect &>/dev/null; then expect - <<-EOF log_user 0 spawn $BUGZILLA --cookiefile=${BUGZILLA_COOKIE}.init login $BZUSER expect "Password: " send "${BZPASSWORD}\r" send_user "wait a bit..." expect eof EOF else echo "Passing password through command-line argument is DANGEROUS" local yn; read -p 'Continue? [yN]' yn [ "${yn}" -ne "y" ] && exit spawn $BUGZILLA --cookiefile=${BUGZILLA_COOKIE}.init login $BZUSER $BZPASSWORD fi if [ $? -ne 0 ]; then echo "Cannot log in" return 2 fi cp -n -- ${BUGZILLA_COOKIE}{.init,} rm -f -- ${BUGZILLA_COOKIE}.init } # exclusively using globals do_init () { # C-c handler to optionally remove cookie on exit test $BUGZILLA_LOGOUT -ne 0 && trap do_logout 0 # login when appropriate if [ -n "$BZUSER" ] && [ ! -f "${BUGZILLA_COOKIE}" ]; then do_init_login || exit $? # XXX: recheck, should not be needed fi } # exclusively using globals do_watch () { EXEC="${WATCHCMD} -t --interval $REFRESH_INTERVAL" [ "${WATCHBZ_DEBUG}" -ne 0 ] && EXEC="sh -c" TAIL="${HERE}/table-data $SEP | sort -n | ${PRECOLORIZE} | cut -c-$WIDTH | ${COLORIZE}" test -n "${BZCUSTOMFILTER}" && TAIL+="| ${BZCUSTOMFILTER}" [ "${WATCHBZ_DEBUG}" -ne 0 ] && TAIL="cat" [ "${WATCHBZ_DEBUG}" -ne 0 ] && BUGZILLA+=" --debug" [ "${WATCHBZ_DEBUG}" -ne 0 ] && echo \ "${BUGZILLA} --cookiefile=${BUGZILLA_COOKIE} query -c ${COMPONENT} \ -t \"${STATUS}\" --outputformat ${OUTPUT_FMT} ${QUERY_REST}" ${EXEC} \ "${BUGZILLA} --cookiefile=${BUGZILLA_COOKIE} query -c ${COMPONENT} \ -t \"${STATUS}\" --outputformat ${OUTPUT_FMT} ${QUERY_REST} \ | sed -u \ -e \"s|${SEP}Red Hat Enterprise Linux |${SEP}EL|\" \ -e \"s|${SEP}Fedora|${SEP} F |\" \ -e \"s|${SEP}NEW|${SEP}N|\" \ -e \"s|${SEP}ASSIGNED|${SEP}A|\" \ -e \"s|${SEP}POST|${SEP}P|\" \ -e \"s|${SEP}MODIFIED|${SEP}M|\" \ -e \"s|${SEP}ON_QA|${SEP}Q|\" \ -e \"s|${SEP}ON_DEV|${SEP}D|\" \ -e \"s|${SEP}VERIFIED|${SEP}V|\" \ -e \"s|${SEP}RELEASE_PENDING|${SEP}R|\" \ -e \"s|${SEP}CLOSED|${SEP}C|\" \ -e \"s|${SEP}urgent|${SEP}!!!|\" \ -e \"s|${SEP}high|${SEP}!!|\" \ -e \"s|${SEP}medium|${SEP} ! |\" \ -e \"s|${SEP}low|${SEP} ~ |\" \ -e \"s|${SEP}unspecified|${SEP} ? |\" \ -e \"s|qa_ack|QA|\" \ -e \"s|pm_ack|PM|\" \ -e \"s|devel_ack|DE|\" \ -e \"s|needinfo|I|\" \ -e \"s|exception|E|\" \ -e \"s|blocker|BL|\" \ -e \"s|requires_release_note|RN|\" \ -e \"s|requires_doc_text|RD|\" \ -e \"s|docs_scoped|D|g\" \ -e \":dsloop\" \ -e \"s|\([^A-Z]D-\+\),D-|\1-|g;tdsloop\" \ -e \"s|\([^A-Z]D+\+\),D+|\1+|g;tdsloop\" \ -e \"s|\([^A-Z]D?\+\),D?|\1?|g;tdsloop\" \ -e \"s|rhel-||g\" \ -e \"s|qe_test_coverage[?+-][,]\?||\" \ -e \"s|None||\" | ${TAIL}" } do_init do_watch