boulder/sa/migrations.sh

249 lines
6.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -eu
if type realpath >/dev/null 2>&1 ; then
cd "$(realpath -- $(dirname -- "$0"))"
fi
# posix compliant escape sequence
esc=$'\033'"["
res="${esc}0m"
#
# Defaults
#
DB_NEXT_PATH="db-next"
DB_PATH="db"
OUTCOME="ERROR"
PROMOTE=()
RUN=()
DB=""
#
# Print Functions
#
function print_outcome() {
if [ "${OUTCOME}" == OK ]
then
echo -e "${esc}0;32;1m${OUTCOME}${res}"
else
echo -e "${esc}0;31;1m${OUTCOME}${res}"
fi
}
function print_usage_exit() {
echo "${USAGE}"
exit 0
}
# newline + bold magenta
function print_heading() {
echo
echo -e "${esc}0;34;1m${1}${res}"
}
# bold cyan
function print_moving() {
local src=${1}
local dest=${2}
echo -e "moving: ${esc}0;36;1m${src}${res}"
echo -e "to: ${esc}0;32;1m${dest}${res}"
}
# bold yellow
function print_unlinking() {
echo -e "unlinking: ${esc}0;33;1m${1}${res}"
}
# bold magenta
function print_linking () {
local from=${1}
local to=${2}
echo -e "linking: ${esc}0;35;1m${from} ->${res}"
echo -e "to: ${esc}0;39;1m${to}${res}"
}
function check_arg() {
if [ -z "${OPTARG}" ]
then
exit_msg "No arg for --${OPT} option, use: -h for help">&2
fi
}
function print_migrations() {
iter=1
for file in "${migrations[@]}"
do
echo "${iter}) $(basename -- ${file})"
iter=$(expr "${iter}" + 1)
done
}
function exit_msg() {
# complain to STDERR and exit with error
echo "${*}" >&2
exit 2
}
#
# Utility Functions
#
function get_promotable_migrations() {
local migrations=()
local migpath="${DB_NEXT_PATH}/${1}"
for file in "${migpath}"/*.sql; do
[[ -f "${file}" && ! -L "${file}" ]] || continue
migrations+=("${file}")
done
if [[ "${migrations[@]}" ]]; then
echo "${migrations[@]}"
else
exit_msg "There are no promotable migrations at path: "\"${migpath}\"""
fi
}
function get_demotable_migrations() {
local migrations=()
local migpath="${DB_NEXT_PATH}/${1}"
for file in "${migpath}"/*.sql; do
[[ -L "${file}" ]] || continue
migrations+=("${file}")
done
if [[ "${migrations[@]}" ]]; then
echo "${migrations[@]}"
else
exit_msg "There are no demotable migrations at path: "\"${migpath}\"""
fi
}
#
# CLI Parser
#
USAGE="$(cat -- <<-EOM
Usage:
Boulder DB Migrations CLI
Helper for listing, promoting, and demoting migration files
./$(basename "${0}") [OPTION]...
-b --db Name of the database, this is required (e.g. boulder_sa or incidents_sa)
-n, --list-next Lists migration files present in sa/db-next/<db>
-c, --list-current Lists migration files promoted from sa/db-next/<db> to sa/db/<db>
-p, --promote Select and promote a migration from sa/db-next/<db> to sa/db/<db>
-d, --demote Select and demote a migration from sa/db/<db> to sa/db-next/<db>
-h, --help Shows this help message
EOM
)"
while getopts nchpd-:b:-: OPT; do
if [ "$OPT" = - ]; then # long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
fi
case "${OPT}" in
b | db ) check_arg; DB="${OPTARG}" ;;
n | list-next ) RUN+=("list_next") ;;
c | list-current ) RUN+=("list_current") ;;
p | promote ) RUN+=("promote") ;;
d | demote ) RUN+=("demote") ;;
h | help ) print_usage_exit ;;
??* ) exit_msg "Illegal option --${OPT}" ;; # bad long option
? ) exit 2 ;; # bad short option (error reported via getopts)
esac
done
shift $((OPTIND-1)) # remove parsed opts and args from $@ list
# On EXIT, trap and print outcome
trap "print_outcome" EXIT
[ -z "${DB}" ] && exit_msg "You must specify a database with flag -b \"foo\" or --db=\"foo\""
STEP="list_next"
if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
print_heading "Next Migrations"
migrations=($(get_promotable_migrations "${DB}"))
print_migrations "${migrations[@]}"
fi
STEP="list_current"
if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
print_heading "Current Migrations"
migrations=($(get_demotable_migrations "${DB}"))
print_migrations "${migrations[@]}"
fi
STEP="promote"
if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
print_heading "Promote Migration"
migrations=($(get_promotable_migrations "${DB}"))
declare -a mig_index=()
declare -A mig_file=()
for i in "${!migrations[@]}"; do
mig_index["$i"]="${migrations[$i]%% *}"
mig_file["${mig_index[$i]}"]="${migrations[$i]#* }"
done
promote=""
PS3='Which migration would you like to promote? (q to cancel): '
select opt in "${mig_index[@]}"; do
case "${opt}" in
"") echo "Invalid option or cancelled, exiting..." ; break ;;
*) mig_file_path="${mig_file[$opt]}" ; break ;;
esac
done
if [[ "${mig_file_path}" ]]
then
print_heading "Promoting Migration"
promote_mig_name="$(basename -- "${mig_file_path}")"
promoted_mig_file_path="${DB_PATH}/${DB}/${promote_mig_name}"
symlink_relpath="$(realpath --relative-to=${DB_NEXT_PATH}/${DB} ${promoted_mig_file_path})"
print_moving "${mig_file_path}" "${promoted_mig_file_path}"
mv "${mig_file_path}" "${promoted_mig_file_path}"
print_linking "${mig_file_path}" "${symlink_relpath}"
ln -s "${symlink_relpath}" "${DB_NEXT_PATH}/${DB}"
fi
fi
STEP="demote"
if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
print_heading "Demote Migration"
migrations=($(get_demotable_migrations "${DB}"))
declare -a mig_index=()
declare -A mig_file=()
for i in "${!migrations[@]}"; do
mig_index["$i"]="${migrations[$i]%% *}"
mig_file["${mig_index[$i]}"]="${migrations[$i]#* }"
done
demote_mig=""
PS3='Which migration would you like to demote? (q to cancel): '
select opt in "${mig_index[@]}"; do
case "${opt}" in
"") echo "Invalid option or cancelled, exiting..." ; break ;;
*) mig_link_path="${mig_file[$opt]}" ; break ;;
esac
done
if [[ "${mig_link_path}" ]]
then
print_heading "Demoting Migration"
demote_mig_name="$(basename -- "${mig_link_path}")"
demote_mig_from="${DB_PATH}/${DB}/${demote_mig_name}"
print_unlinking "${mig_link_path}"
rm "${mig_link_path}"
print_moving "${demote_mig_from}" "${mig_link_path}"
mv "${demote_mig_from}" "${mig_link_path}"
fi
fi
OUTCOME="OK"