Files
shell_sequencer/sequencer.sh

421 lines
9.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# shellcheck disable=SC2034 # variable not used
# Exit on error. Append "|| true" if you expect an error.
set -o errexit
# Exit on error inside any functions or subshells.
set -o errtrace
# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR
set -o nounset
# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip`
set -o pipefail
# Turn on traces, useful while debugging but commented out by default
# set -o xtrace
## Globals
{
## Seq
readonly seq_name="${_sqn_alias:-${0##*/}}"
readonly seq_dir="$(cd -- "$(dirname -- "${0}")" && pwd)"
readonly seq_origin="$(cd -- "$(dirname -- "$(readlink -f -- "${0}")")" && pwd)"
readonly seq_file=$(basename -- "${0}")
readonly seq_fileName=${seq_file%%.*}
# shellcheck disable=SC2015 # && || is not if else
readonly seq_invocation="$(printf '%q' "${0}")$( (($#)) && printf ' %q' "$@" || true)"
# May be overwritten by seq
seq_configFile="${seq_fileName}.cfg"
seq_configTemplate="${0%.*}.cfg.example"
## Sequencer
readonly sqr_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly sqr_file="$(basename -- "${BASH_SOURCE[0]}")"
readonly sqr_name="${sqr_file%%.*}"
readonly sqr_origin="$(cd -- "$(dirname -- \
"$(readlink -f -- "${BASH_SOURCE[0]}")")" && pwd)"
_sqr_interactive=1
_sqr_debug=0
_sqr_dry=0
_sqr_verbose=0
# Colors
col_black= ; [ -t 1 ] && col_black='\033[0;30m'
col_darkgrey= ; [ -t 1 ] && col_darkgrey='\033[1;30m'
col_red= ; [ -t 1 ] && col_red='\033[0;31m'
col_lightred= ; [ -t 1 ] && col_lightred='\033[1;31m'
col_green= ; [ -t 1 ] && col_green='\033[0;32m'
col_lightgreen= ; [ -t 1 ] && col_lightgreen='\033[1;32m'
col_orange= ; [ -t 1 ] && col_orange='\033[0;33m'
col_yellow= ; [ -t 1 ] && col_yellow='\033[1;33m'
col_blue= ; [ -t 1 ] && col_blue='\033[0;34m'
col_lightblue= ; [ -t 1 ] && col_lightblue='\033[1;34m'
col_purple= ; [ -t 1 ] && col_purple='\033[0;35m'
col_lightpurple= ; [ -t 1 ] && col_lightpurple='\033[1;35m'
col_cyan= ; [ -t 1 ] && col_cyan='\033[0;36m'
col_lightcyan= ; [ -t 1 ] && col_lightcyan='\033[1;36m'
col_lightgray= ; [ -t 1 ] && col_lightgray='\033[0;37m'
col_white= ; [ -t 1 ] && col_white='\033[1;37m'
col_off= ;[ -t 1 ] && col_off='\033[0m' # No Color
}
## Logging
{
LOG_LEVEL="${LOG_LEVEL:-3}" # 4 = debug -> 0 = fatal (stop)
LOG_TIME="${LOG_TIME:-}" # 1 = show time stamps
# sqr::log [LOG LEVEL] [LOG_COLOR] [OPTIONS] [LOG MESSAGE]
# Construct log messages
#
# [OPTIONS]
# -a : append text (no info and timestamp)
# Uses color from last sqr::log call without -a
# -e : Output to stderr
# -- : End of options
#
sqr::log () {
sqr::debugPause
local appendText=
local arg=
local col_end="${col_off}"
local outp='/dev/stdout'
local log_level="${1:-}"
shift
local log_color="${1:-}"
shift
for arg in "${@}" ; do
case "${1:-}" in
--)
shift && break ;;
-a)
appendText=1
shift ;;
-e)
outp='/dev/stderr'
shift ;;
esac
done
[[ -z "${log_color}" ]] && col_end=""
# all remaining arguments are to be printed
local log_line=""
while IFS=$'\n' read -r log_line ; do
color red
printf '%b' "${log_color}" >${outp}
if [[ -n "${LOG_TIME}" ]] ; then
if (( appendText )) ; then
printf '%24s' "" >${outp}
else
printf '%s ' "$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >${outp}
fi
fi
if (( ! appendText )) ; then
printf " %3s " "[${log_level}]" >${outp}
printf "%s" "${log_line}" >${outp}
else
# +3 : "[] "
printf "%$((${#log_level} + 4))s%s" "" "${log_line}" >${outp}
fi
printf '%b\n' "${col_end}" >${outp}
done <<< "${@:-}"
sqr::debugContinue
}
stop () { sqr::log "stop" "${col_red}" "${@}"; exit 1; }
error () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && sqr::log "e" "${col_red}" "${@}"; true; }
warning () { [[ "${LOG_LEVEL:-0}" -ge 2 ]] && sqr::log "w" "${col_orange}" "${@}"; true; }
info () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && sqr::log "i" "" "${@}"; true; }
debug () { [[ "${LOG_LEVEL:-0}" -ge 4 ]] && sqr::log "dbug" "${col_lightpurple}" "${@}"; true; }
# internal printf same loglevel as info
# shellcheck disable=SC2059 # don't use variables in format
sqr::print () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && printf "$@"; true; }
sqr::debugPause() {
if (( _sqr_debug )) ; then set +o xtrace; else true; fi
}
sqr::debugContinue() {
if (( _sqr_debug )) ; then set -o xtrace; else true; fi
}
# color <FOREGROUND COLOR> [BACKGROUND COLOR]
color() {
[ ! -t 1 ] && return 0
[ -z "${1:-}" ] && tput sgr0 && return 0
case "${1:-}" in
black)
tput setaf 0 ;;
red)
tput setaf 1 ;;
green)
tput setaf 2 ;;
yellow)
tput setaf 3 ;;
blue)
tput setaf 4 ;;
magenta)
tput setaf 5 ;;
cyan)
tput setaf 6 ;;
white)
tput setaf 7 ;;
none)
tput sgr0
return 0 ;;
*)
tput setaf ${1:-} ;;
esac
case "${2:-}" in
black)
tput setab 0 ;;
red)
tput setab 1 ;;
green)
tput setab 2 ;;
yellow)
tput setab 3 ;;
blue)
tput setab 4 ;;
magenta)
tput setab 5 ;;
cyan)
tput setab 6 ;;
white)
tput setab 7 ;;
esac
}
}
## Traps
{
sqr::trap_exit () {
exists -f seq_trapExit && seq_trapExit
debug "Sequencer exit"
}
trap sqr::trap_exit EXIT
# requires `set -o errtrace`
sqr::error_report() {
local error_code=${?}
error "Error in ${sqr_file} in function ${1} on line ${2}"
exit ${error_code}
}
# Uncomment the following line for always providing an error backtrace
# trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR
}
# exists [-f] [--] [ELEMENT]
# [ELEMENT]
# : either a variable name or
# -f : function
exists() {
sqr::debugPause
local func=
local arg=
for arg in "$@" ; do
case "${1:-}" in
--)
shift && break ;;
-f)
func="${2:-}"
esac
done
if [[ -n "${func}" ]] ; then
declare -F "${func}" &>>/dev/null
else
[[ -n "${!1:-}" ]]
fi
sqr::debugContinue
}
# interactive
# Started with -q to use defaults for confirmations
interactive() {
(( _sqr_interactive ))
}
# quiet
# Log level smaller 3 (info) are treated as quiet request
quiet() {
[[ $LOG_LEVEL -lt 3 ]]
}
# silent
# Log level equals 0 (stop)
silent() {
[[ $LOG_LEVEL -eq 0 ]]
}
# dry-run
# Started with --dry-run
dry() {
(( _sqr_dry ))
}
# verbose
# Started with --verbose
verbose() {
(( _sqr_verbose ))
}
### interactive
# confirm [OPTIONS] [--] [QUESTION]
# Default (empty character) = no
#
# [OPTIONS]
# -f : interactive even if quiet
# -n : no input help
# -y : default = yes
# -- : end of options
confirm() {
sqr::debugPause
local arg=
local rexReply='^[Yy]$' # default no
local inputHelp='[y/N] ' # default no
local noHelp=0
local force=0
for arg in "${@}" ; do
case "${1:-}" in
--)
shift && break ;;
-f)
force=1
shift ;;
-n)
noHelp=1
shift ;;
-y)
rexReply='^[Yy]*$' # default yes
inputHelp='[Y/n] ' # default yes
shift ;;
esac
done
(( noHelp )) && inputHelp=
if interactive || (( force )) ; then
read -r -p "${1:-} ${inputHelp}" -n 1
echo ""
else
REPLY=''
fi
sqr::debugContinue
[[ $REPLY =~ ${rexReply} ]]
}
# ask [OPTION] [QUESTION] [DEFAULT]
# Will ask for input even if quiet, when [DEFAULT] is empty
#
# [OPTION]
# -e : allow empty input
# Ignored if [DEFAULT] is available
# -s : ask for secret (don't print input)
# Does not add a newline. Usage:
# pass=$(ask -s "Password"); echo
# -- : End of options
ask() {
sqr::debugPause
local hidden=
local empty=0
local arg=
for arg in "$@" ; do
case "${1}" in
--)
shift && break ;;
-e)
empty=1
shift ;;
-s)
hidden="-s"
shift ;;
esac
done
local answer=
if [[ -n "${2:-}" ]] ; then
! interactive && printf '%s\n' "${2}" && sqr::debugContinue && return 0
read ${hidden?} -r -p "${1:-"User input"} ($2) " answer
else
read ${hidden?} -r -p "${1:-"User input"} " answer
fi
if [[ -z "$answer" ]] ; then
answer="${2:-}"
fi
printf '%s\n' "${answer}"
# return if answer is empty
sqr::debugContinue
if (( ! empty )) ; then
[[ -n "${answer}" ]]
fi
}
# exe <COMMAND>
# Handle dry run and verbose output for commands without pipe and/or redirects
exe() {
dry && printf -- '--'
if dry || verbose ; then
(set -x; : "$@")
fi
if ! dry ; then
"$@"
fi
}
# exep <COMMAND AS STRING(S)>
# Handle dry run and verbose output for commands containing pipe and/or redirects
exep() {
if dry ; then
printf -- '--++ : %s\n' "$*"
elif verbose ; then
printf '++ : %s\n' "$*"
fi
if ! dry ; then
bash -c "$*"
fi
}
sqr::main() {
# options check
for arg in "$@" ; do
case "$1" in
--debug)
_sqr_debug="1"
shift ;;
--dry-run|-d)
_sqr_dry=1
shift ;;
--quiet|-q)
_sqr_interactive=0
shift ;;
--silent|-qq)
_sqr_interactive=0
LOG_LEVEL=0
shift ;;
--verbose|-v)
_sqr_verbose=1
shift ;;
esac
done
# debug mode
if [[ "${_sqr_debug:-}" = "1" ]]; then
set -o xtrace
PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
# Enable error backtracing
trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR
fi
sqr::print 'Running...\n'
confirm -y 'Continue?'
seq_config 2>/dev/null || true
step_1 "$@"
}
sqr::main "$@"