...
1#!/usr/bin/env bash
2
3set -eu
4
5if type realpath >/dev/null 2>&1 ; then
6 cd "$(realpath -- $(dirname -- "$0"))"
7fi
8
9# posix compliant escape sequence
10esc=$'\033'"["
11res="${esc}0m"
12
13#
14# Defaults
15#
16DB_NEXT_PATH="db-next"
17DB_PATH="db"
18OUTCOME="ERROR"
19PROMOTE=()
20RUN=()
21DB=""
22
23#
24# Print Functions
25#
26function print_outcome() {
27 if [ "${OUTCOME}" == OK ]
28 then
29 echo -e "${esc}0;32;1m${OUTCOME}${res}"
30 else
31 echo -e "${esc}0;31;1m${OUTCOME}${res}"
32 fi
33}
34
35function print_usage_exit() {
36 echo "${USAGE}"
37 exit 0
38}
39
40# newline + bold magenta
41function print_heading() {
42 echo
43 echo -e "${esc}0;34;1m${1}${res}"
44}
45
46# bold cyan
47function print_moving() {
48 local src=${1}
49 local dest=${2}
50 echo -e "moving: ${esc}0;36;1m${src}${res}"
51 echo -e "to: ${esc}0;32;1m${dest}${res}"
52}
53
54# bold yellow
55function print_unlinking() {
56 echo -e "unlinking: ${esc}0;33;1m${1}${res}"
57}
58
59# bold magenta
60function print_linking () {
61 local from=${1}
62 local to=${2}
63 echo -e "linking: ${esc}0;35;1m${from} ->${res}"
64 echo -e "to: ${esc}0;39;1m${to}${res}"
65}
66
67function check_arg() {
68 if [ -z "${OPTARG}" ]
69 then
70 exit_msg "No arg for --${OPT} option, use: -h for help">&2
71 fi
72}
73
74function print_migrations() {
75 iter=1
76 for file in "${migrations[@]}"
77 do
78 echo "${iter}) $(basename -- ${file})"
79 iter=$(expr "${iter}" + 1)
80 done
81}
82
83function exit_msg() {
84 # complain to STDERR and exit with error
85 echo "${*}" >&2
86 exit 2
87}
88
89#
90# Utility Functions
91#
92function get_promotable_migrations() {
93 local migrations=()
94 local migpath="${DB_NEXT_PATH}/${1}"
95 for file in "${migpath}"/*.sql; do
96 [[ -f "${file}" && ! -L "${file}" ]] || continue
97 migrations+=("${file}")
98 done
99 if [[ "${migrations[@]}" ]]; then
100 echo "${migrations[@]}"
101 else
102 exit_msg "There are no promotable migrations at path: "\"${migpath}\"""
103 fi
104}
105
106function get_demotable_migrations() {
107 local migrations=()
108 local migpath="${DB_NEXT_PATH}/${1}"
109 for file in "${migpath}"/*.sql; do
110 [[ -L "${file}" ]] || continue
111 migrations+=("${file}")
112 done
113 if [[ "${migrations[@]}" ]]; then
114 echo "${migrations[@]}"
115 else
116 exit_msg "There are no demotable migrations at path: "\"${migpath}\"""
117 fi
118}
119
120#
121# CLI Parser
122#
123USAGE="$(cat -- <<-EOM
124
125Usage:
126
127 Boulder DB Migrations CLI
128
129 Helper for listing, promoting, and demoting migration files
130
131 ./$(basename "${0}") [OPTION]...
132 -b --db Name of the database, this is required (e.g. boulder_sa or incidents_sa)
133 -n, --list-next Lists migration files present in sa/db-next/<db>
134 -c, --list-current Lists migration files promoted from sa/db-next/<db> to sa/db/<db>
135 -p, --promote Select and promote a migration from sa/db-next/<db> to sa/db/<db>
136 -d, --demote Select and demote a migration from sa/db/<db> to sa/db-next/<db>
137 -h, --help Shows this help message
138
139EOM
140)"
141
142while getopts nchpd-:b:-: OPT; do
143 if [ "$OPT" = - ]; then # long option: reformulate OPT and OPTARG
144 OPT="${OPTARG%%=*}" # extract long option name
145 OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
146 OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
147 fi
148 case "${OPT}" in
149 b | db ) check_arg; DB="${OPTARG}" ;;
150 n | list-next ) RUN+=("list_next") ;;
151 c | list-current ) RUN+=("list_current") ;;
152 p | promote ) RUN+=("promote") ;;
153 d | demote ) RUN+=("demote") ;;
154 h | help ) print_usage_exit ;;
155 ??* ) exit_msg "Illegal option --${OPT}" ;; # bad long option
156 ? ) exit 2 ;; # bad short option (error reported via getopts)
157 esac
158done
159shift $((OPTIND-1)) # remove parsed opts and args from $@ list
160
161# On EXIT, trap and print outcome
162trap "print_outcome" EXIT
163
164[ -z "${DB}" ] && exit_msg "You must specify a database with flag -b \"foo\" or --db=\"foo\""
165
166STEP="list_next"
167if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
168 print_heading "Next Migrations"
169 migrations=($(get_promotable_migrations "${DB}"))
170 print_migrations "${migrations[@]}"
171fi
172
173STEP="list_current"
174if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
175 print_heading "Current Migrations"
176 migrations=($(get_demotable_migrations "${DB}"))
177 print_migrations "${migrations[@]}"
178fi
179
180STEP="promote"
181if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
182 print_heading "Promote Migration"
183 migrations=($(get_promotable_migrations "${DB}"))
184 declare -a mig_index=()
185 declare -A mig_file=()
186 for i in "${!migrations[@]}"; do
187 mig_index["$i"]="${migrations[$i]%% *}"
188 mig_file["${mig_index[$i]}"]="${migrations[$i]#* }"
189 done
190
191 promote=""
192 PS3='Which migration would you like to promote? (q to cancel): '
193
194 select opt in "${mig_index[@]}"; do
195 case "${opt}" in
196 "") echo "Invalid option or cancelled, exiting..." ; break ;;
197 *) mig_file_path="${mig_file[$opt]}" ; break ;;
198 esac
199 done
200 if [[ "${mig_file_path}" ]]
201 then
202 print_heading "Promoting Migration"
203 promote_mig_name="$(basename -- "${mig_file_path}")"
204 promoted_mig_file_path="${DB_PATH}/${DB}/${promote_mig_name}"
205 symlink_relpath="$(realpath --relative-to=${DB_NEXT_PATH}/${DB} ${promoted_mig_file_path})"
206
207 print_moving "${mig_file_path}" "${promoted_mig_file_path}"
208 mv "${mig_file_path}" "${promoted_mig_file_path}"
209
210 print_linking "${mig_file_path}" "${symlink_relpath}"
211 ln -s "${symlink_relpath}" "${DB_NEXT_PATH}/${DB}"
212 fi
213fi
214
215STEP="demote"
216if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
217 print_heading "Demote Migration"
218 migrations=($(get_demotable_migrations "${DB}"))
219 declare -a mig_index=()
220 declare -A mig_file=()
221 for i in "${!migrations[@]}"; do
222 mig_index["$i"]="${migrations[$i]%% *}"
223 mig_file["${mig_index[$i]}"]="${migrations[$i]#* }"
224 done
225
226 demote_mig=""
227 PS3='Which migration would you like to demote? (q to cancel): '
228
229 select opt in "${mig_index[@]}"; do
230 case "${opt}" in
231 "") echo "Invalid option or cancelled, exiting..." ; break ;;
232 *) mig_link_path="${mig_file[$opt]}" ; break ;;
233 esac
234 done
235 if [[ "${mig_link_path}" ]]
236 then
237 print_heading "Demoting Migration"
238 demote_mig_name="$(basename -- "${mig_link_path}")"
239 demote_mig_from="${DB_PATH}/${DB}/${demote_mig_name}"
240
241 print_unlinking "${mig_link_path}"
242 rm "${mig_link_path}"
243 print_moving "${demote_mig_from}" "${mig_link_path}"
244 mv "${demote_mig_from}" "${mig_link_path}"
245 fi
246fi
247
248OUTCOME="OK"
View as plain text