Skip to content

Commit

Permalink
Preserve manage_db.sh, but do not hide alembic
Browse files Browse the repository at this point in the history
- Preserves usage of legacy script while not hiding the Alembic script.
    (see manage_db.sh and run_alembic.sh)
- Adds documentation (see top of script files)
- Includes misc. relevant refactorings
- Includes unit tests for testing legacy script handling
  • Loading branch information
jdavcs committed Mar 9, 2022
1 parent c0fc555 commit 0977b7a
Show file tree
Hide file tree
Showing 11 changed files with 271 additions and 52 deletions.
2 changes: 1 addition & 1 deletion lib/galaxy/model/migrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ def _get_upgrade_message(
msg = f'Your {model} database has version {db_version}, but this code expects '
msg += f'version {code_version}. '
msg += 'This database can be upgraded automatically if database_auto_migrate is set. '
msg += 'To upgrade manually, run `migrate_db.sh` (see instructions in that file). '
msg += 'To upgrade manually, run `run_alembic.sh` (see instructions in that file). '
msg += 'Please remember to backup your database before migrating.'
return msg

Expand Down
75 changes: 75 additions & 0 deletions lib/galaxy/model/migrations/scripts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import re
from typing import (
List,
NamedTuple,
Expand Down Expand Up @@ -60,3 +61,77 @@ def get_configuration(argv: List[str], cwd: str) -> Tuple[DatabaseConfig, Databa
tsi_config = DatabaseConfig(url, template, encoding)

return (gxy_config, tsi_config, is_auto_migrate)


def add_db_urls_to_command_arguments(argv: List[str], cwd: str) -> None:
gxy_config, tsi_config, _ = get_configuration(argv, cwd)
_insert_x_argument(argv, 'tsi_url', tsi_config.url)
_insert_x_argument(argv, 'gxy_url', gxy_config.url)


def _insert_x_argument(argv, key: str, value: str) -> None:
# `_insert_x_argument('mykey', 'myval')` transforms `foo -a 1` into `foo -x mykey=myval -a 42`
argv.insert(1, f'{key}={value}')
argv.insert(1, '-x')


class LegacyScripts:

LEGACY_CONFIG_FILE_ARG_NAMES = ['-c', '--config', '--config-file']
ALEMBIC_CONFIG_FILE_ARG = '--alembic-config' # alembic config file, set in the calling script

def pop_database_argument(self, argv: List[str]) -> str:
"""
If last argument is a valid database name, pop and return it; otherwise return default.
"""
arg = argv[-1]
if arg in ['galaxy', 'install']:
return argv.pop()
return 'galaxy'

def rename_config_argument(self, argv: List[str]) -> None:
"""
Rename the optional config argument: we can't use '-c' because that option is used by Alembic.
"""
for arg in self.LEGACY_CONFIG_FILE_ARG_NAMES:
if arg in argv:
self._rename_arg(argv, arg, CONFIG_FILE_ARG)
return

def rename_alembic_config_argument(self, argv: List[str]) -> None:
"""
Rename argument name: `--alembic-config` to `-c`. There should be no `-c` argument present.
"""
if '-c' in argv:
raise Exception('Cannot rename alembic config argument: `-c` argument present.')
self._rename_arg(argv, self.ALEMBIC_CONFIG_FILE_ARG, '-c')

def convert_version_argument(self, argv: List[str], database: str) -> None:
"""
Convert legacy version argument to current spec required by Alembic.
"""
if '--version' in argv:
# Just remove it: the following argument should be the version/revision identifier.
pos = argv.index('--version')
argv.pop(pos)
else:
# If we find --version=foo, extract foo and replace arg with foo (which is the revision identifier)
p = re.compile(r'--version=([0-9A-Fa-f]+)')
for i, arg in enumerate(argv):
m = p.match(arg)
if m:
argv[i] = m.group(1)
return
# No version argumen found: construct branch@head argument for an upgrade operation.
# Raise exception otherwise.
if 'upgrade' not in argv:
raise Exception('If no `--version` argument supplied, `upgrade` argument is requried')

if database == 'galaxy':
argv.append('gxy@head')
elif database == 'install':
argv.append('tsi@head')

def _rename_arg(self, argv, old_name, new_name) -> None:
pos = argv.index(old_name)
argv[pos] = new_name
2 changes: 1 addition & 1 deletion lib/galaxy/model/orm/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def get_config(argv, use_argparse=True, cwd=None):

def manage_db():
# This is a duplicate implementation of scripts/migrate_db.py.
# See migrate_db.sh for usage.
# See run_alembic.sh for usage.
def _insert_x_argument(key, value):
sys.argv.insert(1, f'{key}={value}')
sys.argv.insert(1, '-x')
Expand Down
4 changes: 2 additions & 2 deletions lib/tool_shed/webapp/model/migrate/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def create_or_verify_database(url, engine_options=None):
1) Empty database --> initialize with latest version and return
2) Database older than migration support --> fail and require manual update
3) Database at state where migrate support introduced --> add version control information but make no changes (might still require manual update)
4) Database versioned but out of date --> fail with informative message, user must run "sh migrate_toolshed_db.sh upgrade"
4) Database versioned but out of date --> fail with informative message, user must run "sh manage_db.sh upgrade"
"""
engine_options = engine_options or {}
Expand Down Expand Up @@ -81,7 +81,7 @@ def create_or_verify_database(url, engine_options=None):
migrate_repository.versions.latest,
)
exception_msg += "Back up your database and then migrate the schema by running the following from your Galaxy installation directory:"
exception_msg += "\n\nsh migrate_toolshed_db.sh upgrade tool_shed\n"
exception_msg += "\n\nsh manage_db.sh upgrade tool_shed\n"
raise Exception(exception_msg)
else:
log.info("At database version %d" % db_schema.version)
Expand Down
49 changes: 49 additions & 0 deletions manage_db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/sh

#######
# The purpose of this script is to preserve the legacy interface provided by
# manage_db.sh, which was a thin wrapper around SQLAlchemy Migrate. Unless you
# need to use the legacy interface, to manage migrations of the "galaxy" and
# "install" databases, you are encouraged to use run_alembic.sh directly and
# take advantage of Alembic's command line options.
#
# Use this script to upgrade or downgrade your database.
# Database options: galaxy (default), install, tool_shed
# To pass a galaxy config file, you may use `-c|--config|--config-file your-config-file`

# To upgrade or downgrade to some version X:
# sh manage_db.sh [upgrade|downgrade] --version=X [tool_shed|install|galaxy]
#
# You may also skip the version argument when upgrading, in which case the database
# will be upgraded to the latest version.
#
# Example 1: upgrade "galaxy" database to version "abc123" using default config:
# sh manage_db.sh upgrade --version=xyz567
#
# Example 2: downgrade "install" database to version "xyz789" passing config file "mygalaxy.yml":
# sh manage_db.sh downgrade --version=abc123 -c mygalaxy.yml install
#
# Example 3: upgrade "galaxy" database to latest version using defualt config:
# sh manage_db.sh upgrade
#
# (Note: Tool Shed migrations use the legacy migrations system, so we check the
# last argument (the database) to invoke the appropriate script. Therefore, if
# you don't specify the database (galaxy is used by default) and pass a config
# file, your config file should not be named `tool_shed`.)
#######

ALEMBIC_CONFIG='lib/galaxy/model/migrations/alembic.ini'

cd `dirname $0`

. ./scripts/common_startup_functions.sh

setup_python

for i; do :; done
if [ "$i" == "tool_shed" ]; then
python ./scripts/migrate_toolshed_db.py $@ tool_shed
else
find lib/galaxy/model/migrations/alembic -name '*.pyc' -delete
python ./scripts/manage_db_adapter.py --alembic-config "$ALEMBIC_CONFIG" $@
fi
18 changes: 0 additions & 18 deletions migrate_toolshed_db.sh

This file was deleted.

28 changes: 14 additions & 14 deletions migrate_db.sh → run_alembic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#######
# Use this script to manage Galaxy and Tool Shed Install migrations.
# (Use migrate_toolshed_db.sh to manage Tool Shed migrations.)
# (Use the legacy manage_db.sh script to manage Tool Shed migrations.)
#
# NOTE: If your database is empty OR is not under Alembic version control,
# use create_db.sh instead.
Expand All @@ -12,31 +12,31 @@
# Use these identifiers: `gxy` for galaxy, and `tsi` for tool_shed_install.
#
# To create a revision for galaxy:
# ./migrate_db.sh revision --head=gxy@head -m "your description"
# ./run_alembic.sh revision --head=gxy@head -m "your description"
#
# To create a revision for tool_shed_install:
# ./migrate_db.sh revision --head=tsi@head -m "your description"
# ./run_alembic.sh revision --head=tsi@head -m "your description"
#
# To upgrade:
# ./migrate_db.sh upgrade gxy@head # upgrade gxy to head revision
# ./migrate_db.sh upgrade gxy@+1 # upgrade gxy to 1 revision above current
# ./migrate_db.sh upgrade [revision identifier] # upgrade gxy to a specific revision
# ./migrate_db.sh upgrade [revision identifier]+1 # upgrade gxy to 1 revision above specific revision
# ./migrate_db.sh upgrade heads # upgrade gxy and tsi to head revisions
# ./run_alembic.sh upgrade gxy@head # upgrade gxy to head revision
# ./run_alembic.sh upgrade gxy@+1 # upgrade gxy to 1 revision above current
# ./run_alembic.sh upgrade [revision identifier] # upgrade gxy to a specific revision
# ./run_alembic.sh upgrade [revision identifier]+1 # upgrade gxy to 1 revision above specific revision
# ./run_alembic.sh upgrade heads # upgrade gxy and tsi to head revisions
#
# To downgrade:
# ./migrate_db.sh downgrade gxy@base # downgrade gxy to base (empty db with empty alembic table)
# ./migrate_db.sh downgrade gxy@-1 # downgrade gxy to 1 revision below current
# ./migrate_db.sh downgrade [revision identifier] # downgrade gxy to a specific revision
# ./migrate_db.sh downgrade [revision identifier]-1 # downgrade gxy to 1 revision below specific revision
# ./run_alembic.sh downgrade gxy@base # downgrade gxy to base (empty db with empty alembic table)
# ./run_alembic.sh downgrade gxy@-1 # downgrade gxy to 1 revision below current
# ./run_alembic.sh downgrade [revision identifier] # downgrade gxy to a specific revision
# ./run_alembic.sh downgrade [revision identifier]-1 # downgrade gxy to 1 revision below specific revision
#
# To pass a galaxy config file, use `--galaxy-config`
#
# You may also override the galaxy database url and/or the
# tool shed install database url, as well as the database_template
# and database_encoding configuration options with env vars:
# GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION=my-db-url ./migrate_db.sh ...
# GALAXY_INSTALL_CONFIG_OVERRIDE_DATABASE_CONNECTION=my-other-db-url ./migrate_db.sh ...
# GALAXY_CONFIG_OVERRIDE_DATABASE_CONNECTION=my-db-url ./run_alembic.sh ...
# GALAXY_INSTALL_CONFIG_OVERRIDE_DATABASE_CONNECTION=my-other-db-url ./run_alembic.sh ...
#
# For more options, see Alembic's documentation at https://alembic.sqlalchemy.org
#######
Expand Down
43 changes: 43 additions & 0 deletions scripts/manage_db_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
This script is intended to be invoked by the legacy manage_db.sh script.
It translates the arguments supplied to manage_db.sh into the format used
by migrate_db.py.
INPUT: | OUTPUT:
----------------------------------------------------------
upgrade --version=foo | upgrade foo
upgrade --version foo | upgrade foo
upgrade | upgrade gxy@head
upgrade install | upgrade tsi@head
upgrade --version=bar install | upgrade bar
upgrade -c path-to-galaxy.yml | upgrade --galaxy-config path-to-galaxy.yml gxy@head
The converted sys.argv will include `-c path-to-alembic.ini`.
The optional `-c` argument name is renamed to `--galaxy-config`.
"""

import os
import sys

import alembic.config

sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'lib')))

from galaxy.model.migrations.scripts import (
add_db_urls_to_command_arguments,
LegacyScripts,
)


def run():
ls = LegacyScripts()
target_database = ls.pop_database_argument(sys.argv)
ls.rename_config_argument(sys.argv)
ls.rename_alembic_config_argument(sys.argv)
ls.convert_version_argument(sys.argv, target_database)
add_db_urls_to_command_arguments(sys.argv, os.getcwd())
alembic.config.main()


if __name__ == '__main__':
run()
19 changes: 4 additions & 15 deletions scripts/migrate_db.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""
This script retrieves relevant configuration values and invokes
the Alembic console runner.
It is wrapped by migrate_db.sh (see that file for usage).
It is wrapped by run_alembic.sh (see that file for usage) and by
manage_db.sh (for legacy usage).
"""
import logging
import os.path
Expand All @@ -12,14 +13,14 @@
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'lib')))

from galaxy.model.migrations import GXY, TSI
from galaxy.model.migrations.scripts import get_configuration
from galaxy.model.migrations.scripts import add_db_urls_to_command_arguments

logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)


def invoke_alembic():
_add_db_urls_to_command_arguments()
add_db_urls_to_command_arguments(sys.argv, os.getcwd())

# Accept 'heads' as the target revision argument to enable upgrading both gxy and tsi in one command.
# This is consistent with Alembic's CLI, which allows `upgrade heads`. However, this would not work for
Expand All @@ -35,17 +36,5 @@ def invoke_alembic():
alembic.config.main()


def _add_db_urls_to_command_arguments():
gxy_config, tsi_config, _ = get_configuration(sys.argv, os.getcwd())
_insert_x_argument('tsi_url', tsi_config.url)
_insert_x_argument('gxy_url', gxy_config.url)


def _insert_x_argument(key, value):
# `_insert_x_argument('mykey', 'myval')` transforms `foo -a 1` into `foo -x mykey=myval -a 42`
sys.argv.insert(1, f'{key}={value}')
sys.argv.insert(1, '-x')


if __name__ == '__main__':
invoke_alembic()
2 changes: 1 addition & 1 deletion scripts/migrate_toolshed_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
This script parses the Tool Shed config file for database connection
and then delegates to sqlalchemy_migrate shell main function in
migrate.versioning.shell.
It is wrapped by migrate_toolshed_db.sh (see that file for usage).
It is wrapped by manage_db.sh (see that file for usage).
"""
import logging
import os.path
Expand Down
Loading

0 comments on commit 0977b7a

Please sign in to comment.