...

Text file src/github.com/letsencrypt/boulder/sa/migrations.sh

Documentation: github.com/letsencrypt/boulder/sa

     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