249 lines
6.4 KiB
Bash
Executable File
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"
|