Skip to content

Commit

Permalink
Run db-next migrations with config-next configuration (#5320)
Browse files Browse the repository at this point in the history
Docker container should load the appropriate schema (`sa/_db` or
`sa/_db-next`) for the given configuration.

- Add `docker-compose.next.yml` docker-compose overrides
- Detect when to apply `sa/_db-next/migrations`
- Detect mismatch between `goose dbversion` and the latest migration
- Symlink `promoted` schema back to `sa/_db-next/migrations`
- Add tooling to consistently promote/demote schema migrations

Fixes #5300
  • Loading branch information
beautifulentropy authored Mar 11, 2021
1 parent ceffe18 commit fc53482
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 40 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/boulder-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ jobs:
- "docker-compose run --use-aliases boulder ./test.sh --integration"
# Config changes that have landed in main but not yet been applied to
# production can be made in boulder-config-next.json.
- "docker-compose run --use-aliases boulder ./test.sh --integration --config-next"
# Database migrations in `sa/_db-next/migrations` are only performed
# when `docker-compose` is called using `-f docker-compose.yml -f
# docker-compose.next.yml`
- "docker-compose -f docker-compose.yml -f docker-compose.next.yml run --use-aliases boulder ./test.sh --integration"
- "docker-compose run --use-aliases boulder ./test.sh --unit --enable-race-detection"
- "docker-compose run --use-aliases boulder ./test.sh --unit --enable-race-detection --config-next"
- "docker-compose -f docker-compose.yml -f docker-compose.next.yml run --use-aliases boulder ./test.sh --unit --enable-race-detection"
- "docker-compose run --use-aliases boulder ./test.sh --start-py"
# gomod-vendor runs with a separate network access definition
# because it needs to fetch packages from GitHub et. al., which
Expand Down
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ env:
- TESTFLAGS="--lints --integration --generate --rpm"
# Config changes that have landed in main but not yet been applied to
# production can be made in boulder-config-next.json.
- TESTFLAGS="--integration --config-next"
- TESTFLAGS="--integration" OVERRIDES="-f docker-compose.yml -f docker-compose.next.yml"
- TESTFLAGS="--unit --enable-race-detection"
- TESTFLAGS="--unit --enable-race-detection --config-next"
- TESTFLAGS="--unit --enable-race-detection" OVERRIDES="-f docker-compose.yml -f docker-compose.next.yml"
- TESTFLAGS="--start-py"
# gomod-vendor runs with a separate container because it needs to fetch
# packages from GitHub et. al., which is incompatible with the DNS server
Expand All @@ -51,7 +51,7 @@ before_script:

script:
- >-
docker-compose run --use-aliases
docker-compose ${OVERRIDES} run --use-aliases
-e TRAVIS_BRANCH
-e TRAVIS_JOB_ID
-e TRAVIS_PULL_REQUEST
Expand Down
16 changes: 16 additions & 0 deletions docker-compose.next.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3'
services:
boulder:
environment:
FAKE_DNS: 10.77.77.77
BOULDER_CONFIG_DIR: test/config-next
GOFLAGS: -mod=vendor
# This is required so Python doesn't throw an error when printing
# non-ASCII to stdout.
PYTHONIOENCODING: utf-8
# These are variables you can set to affect what tests get run or
# how they are run. Including them here with no value means they are
# passed through from the environment.
RUN: ""
INT_FILTER: ""
RACE: ""
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ services:
environment:
GO111MODULE: "on"
GOFLAGS: "-mod=vendor"
BOULDER_CONFIG_DIR: test/config
networks:
- bluenet
volumes:
Expand Down
10 changes: 0 additions & 10 deletions sa/_db-next/dbconf.yml

This file was deleted.

1 change: 1 addition & 0 deletions sa/_db-next/dbconf.yml
1 change: 1 addition & 0 deletions sa/_db-next/migrations/20210223140000_CombinedSchema.sql
235 changes: 235 additions & 0 deletions sa/migrations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#!/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/migrations"
DB_PATH="_db/migrations"
OUTCOME="ERROR"
PROMOTE=()
RUN=()

#
# 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 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=()
for file in "${DB_NEXT_PATH}"/*.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: "\"${DB_NEXT_PATH}\"""
fi
}

function get_demotable_migrations() {
local migrations=()
for file in "${DB_NEXT_PATH}"/*.sql; do
[[ -L "${file}" ]] || continue
migrations+=("${file}")
done
if [[ "${migrations[@]}" ]]; then
echo "${migrations[@]}"
else
exit_msg "There are no demotable migrations at path: "\"${DB_NEXT_PATH}\"""
fi
}

#
# CLI Parser
#
USAGE="$(cat -- <<-EOM
Usage:
Boulder DB Migrations CLI
Helper for listing, promoting, and demoting Boulder schema files
./$(basename "${0}") [OPTION]...
-l, --list-next Lists schema files present in sa/_db-next
-c, --list-current Lists schema files promoted from sa/_db-next to sa/_db
-p, --promote Select and promote a schema from sa/_db-next to sa/_db
-d, --demote Select and demote a schema from sa/_db to sa/_db-next
-h, --help Shows this help message
EOM
)"

while getopts nchpd-: 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
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

STEP="list_next"
if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
print_heading "Next Schemas"
migrations=($(get_promotable_migrations))
print_migrations "${migrations[@]}"
fi

STEP="list_current"
if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
print_heading "Current Schemas"
migrations=($(get_demotable_migrations))
print_migrations "${migrations[@]}"
fi

STEP="promote"
if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
print_heading "Promote Schema"
migrations=($(get_promotable_migrations))
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 schema 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 Schema"
promote_mig_name="$(basename -- "${mig_file_path}")"
promoted_mig_file_path="${DB_PATH}/${promote_mig_name}"
symlink_relpath="$(realpath --relative-to=${DB_NEXT_PATH} ${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}"
fi
fi

STEP="demote"
if [[ "${RUN[@]}" =~ "${STEP}" ]] ; then
print_heading "Demote Schema"
migrations=($(get_demotable_migrations))
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 schema 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 Schema"
demote_mig_name="$(basename -- "${mig_link_path}")"
demote_mig_from="${DB_PATH}/${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"
1 change: 0 additions & 1 deletion test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ fi
# Defaults
#
export RACE="false"
export BOULDER_CONFIG_DIR="test/config"
STAGE="starting"
STATUS="FAILURE"
RUN=()
Expand Down
Loading

0 comments on commit fc53482

Please sign in to comment.