github.com/letsencrypt/boulder@v0.20251208.0/sa/migrations.sh (about)

     1  #!/usr/bin/env bash
     2  
     3  set -eu
     4  
     5  if type realpath >/dev/null 2>&1 ; then
     6    cd "$(realpath -- $(dirname -- "$0"))"
     7  fi
     8  
     9  # posix compliant escape sequence
    10  esc=$'\033'"["
    11  res="${esc}0m"
    12  
    13  #
    14  # Defaults
    15  #
    16  DB_NEXT_PATH="db-next"
    17  DB_PATH="db"
    18  OUTCOME="ERROR"
    19  PROMOTE=()
    20  RUN=()
    21  DB=""
    22  
    23  #
    24  # Print Functions
    25  #
    26  function 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  
    35  function print_usage_exit() {
    36    echo "${USAGE}"
    37    exit 0
    38  }
    39  
    40  # newline + bold magenta
    41  function print_heading() {
    42    echo
    43    echo -e "${esc}0;34;1m${1}${res}"
    44  }
    45  
    46  # bold cyan
    47  function 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
    55  function print_unlinking() {
    56    echo -e "unlinking: ${esc}0;33;1m${1}${res}"
    57  }
    58  
    59  # bold magenta
    60  function 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  
    67  function 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  
    74  function 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  
    83  function exit_msg() {
    84    # complain to STDERR and exit with error
    85    echo "${*}" >&2
    86    exit 2
    87  }
    88  
    89  #
    90  # Utility Functions
    91  #
    92  function 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  
   106  function 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  #
   123  USAGE="$(cat -- <<-EOM
   124  
   125  Usage:
   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  
   139  EOM
   140  )"
   141  
   142  while 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
   158  done
   159  shift $((OPTIND-1)) # remove parsed opts and args from $@ list
   160  
   161  # On EXIT, trap and print outcome
   162  trap "print_outcome" EXIT
   163  
   164  [ -z "${DB}" ] && exit_msg "You must specify a database with flag -b \"foo\" or --db=\"foo\""
   165  
   166  STEP="list_next"
   167  if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
   168    print_heading "Next Migrations"
   169    migrations=($(get_promotable_migrations "${DB}"))
   170    print_migrations "${migrations[@]}"
   171  fi
   172  
   173  STEP="list_current"
   174  if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
   175    print_heading "Current Migrations"
   176    migrations=($(get_demotable_migrations "${DB}"))
   177    print_migrations "${migrations[@]}"
   178  fi
   179  
   180  STEP="promote"
   181  if [[ "${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
   213  fi
   214  
   215  STEP="demote"
   216  if [[ "${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
   246  fi
   247  
   248  OUTCOME="OK"