From 5f3f792f6f4ba67a2dd49e9c69b28567540c2c48 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 23 Mar 2018 09:41:05 -0400 Subject: [PATCH] Unification of Galaxy configuration parsing for scripts/* --- lib/galaxy/config.py | 47 +++-- lib/galaxy/model/orm/scripts.py | 62 ++++--- lib/galaxy/util/script.py | 29 ++- .../admin_cleanup_datasets.py | 104 +++++------ scripts/cleanup_datasets/cleanup_datasets.py | 96 +++++----- scripts/cleanup_datasets/pgcleanup.py | 110 +++++------- scripts/cleanup_datasets/populate_uuid.py | 28 +-- scripts/communication/communication_server.py | 25 ++- scripts/db_shell.py | 2 +- scripts/galaxy-main | 2 +- scripts/grt/export.py | 27 +-- scripts/grt/upload.py | 2 +- scripts/helper.py | 62 +++---- scripts/manage_db.py | 3 +- scripts/runtime_stats.py | 24 +-- scripts/secret_decoder_ring.py | 39 ++-- scripts/set_dataset_sizes.py | 30 +--- scripts/set_user_disk_usage.py | 59 +++--- test/integration/test_scripts.py | 168 ++++++++++++++++++ 19 files changed, 537 insertions(+), 382 deletions(-) create mode 100644 test/integration/test_scripts.py diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index ed4ebc1c7c1..7af7edf6b44 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -861,6 +861,31 @@ def get_database_engine_options(kwargs, model_prefix=''): return rval +def get_database_url(config): + if config.database_connection: + db_url = config.database_connection + else: + db_url = "sqlite:///%s?isolation_level=IMMEDIATE" % config.database + return db_url + + +def init_models_from_config(config, map_install_models=False, object_store=None, trace_logger=None): + db_url = get_database_url(config) + from galaxy.model import mapping + model = mapping.init( + config.file_path, + db_url, + config.database_engine_options, + map_install_models=map_install_models, + database_query_profiling_proxy=config.database_query_profiling_proxy, + object_store=object_store, + trace_logger=trace_logger, + use_pbkdf2=config.get_bool('use_pbkdf2', True), + slow_query_log_threshold=config.slow_query_log_threshold + ) + return model + + def configure_logging(config): """Allow some basic logging configuration to be read from ini file. @@ -1004,10 +1029,7 @@ class ConfiguresGalaxyMixin: """ Preconditions: object_store must be set on self. """ - if self.config.database_connection: - db_url = self.config.database_connection - else: - db_url = "sqlite:///%s?isolation_level=IMMEDIATE" % self.config.database + db_url = get_database_url(self.config) install_db_url = self.config.install_database_connection # TODO: Consider more aggressive check here that this is not the same # database file under the hood. @@ -1036,17 +1058,12 @@ class ConfiguresGalaxyMixin: install_database_options = self.config.install_database_engine_options verify_tools(self, install_db_url, config_file, install_database_options) - from galaxy.model import mapping - self.model = mapping.init(self.config.file_path, - db_url, - self.config.database_engine_options, - map_install_models=combined_install_database, - database_query_profiling_proxy=self.config.database_query_profiling_proxy, - object_store=self.object_store, - trace_logger=getattr(self, "trace_logger", None), - use_pbkdf2=self.config.get_bool('use_pbkdf2', True), - slow_query_log_threshold=self.config.slow_query_log_threshold) - + self.model = init_models_from_config( + self.config, + map_install_models=combined_install_database, + object_store=self.object_store, + trace_logger=getattr(self, "trace_logger", None) + ) if combined_install_database: log.info("Install database targetting Galaxy's database configuration.") self.install_model = self.model diff --git a/lib/galaxy/model/orm/scripts.py b/lib/galaxy/model/orm/scripts.py index fc7b4742c97..59a77e14a0f 100644 --- a/lib/galaxy/model/orm/scripts.py +++ b/lib/galaxy/model/orm/scripts.py @@ -1,11 +1,13 @@ """ Code to support database helper scripts (create_db.py, manage_db.py, etc...). """ +import argparse import logging import os.path from galaxy.util.path import get_ext from galaxy.util.properties import find_config_file, load_app_properties +from galaxy.util.script import populate_config_args log = logging.getLogger(__name__) @@ -45,17 +47,36 @@ DATABASE = { } -def read_config_file_arg(argv, config_names, cwd=None): - if '-c' in argv: - pos = argv.index('-c') - argv.pop(pos) - return argv.pop(pos) - if cwd: - cwd = [cwd, os.path.join(cwd, 'config')] - return find_config_file(config_names, dirs=cwd) +def _read_model_arguments(argv, use_argparse=False): + if use_argparse: + parser = argparse.ArgumentParser() + parser.add_argument('database', metavar='DATABASE', type=str, + default="galaxy", + nargs='?', + help='database to target (galaxy, tool_shed, install)') + populate_config_args(parser) + args = parser.parse_args(argv[1:] if argv else []) + return args.config_file, args.config_section, args.database + else: + config_file = None + for arg in ["-c", "--config", "--config-file"]: + if arg in argv: + pos = argv.index(arg) + argv.pop(pos) + config_file = argv.pop(pos) + config_section = None + if "--config-section" in argv: + pos = argv.index("--config-section") + argv.pop(pos) + config_section = argv.pop(pos) + if argv and (argv[-1] in DATABASE): + database = argv.pop() # database name tool_shed, galaxy, or install. + else: + database = 'galaxy' + return config_file, config_section, database -def get_config(argv, cwd=None): +def get_config(argv, use_argparse=True, cwd=None): """ Read sys.argv and parse out repository of migrations and database url. @@ -84,23 +105,24 @@ def get_config(argv, cwd=None): 'lib/galaxy/model/migrate' >>> rmtree(config_dir) """ - if argv and (argv[-1] in DATABASE): - database = argv.pop() # database name tool_shed, galaxy, or install. - else: - database = 'galaxy' + config_file, config_section, database = _read_model_arguments(argv, use_argparse=use_argparse) database_defaults = DATABASE[database] + if config_file is None: + config_names = database_defaults.get('config_names', DEFAULT_CONFIG_NAMES) + if cwd: + cwd = [cwd, os.path.join(cwd, 'config')] + config_file = find_config_file(config_names, dirs=cwd) - config_names = database_defaults.get('config_names', DEFAULT_CONFIG_NAMES) - config_file = read_config_file_arg(argv, config_names, cwd=cwd) repo = database_defaults['repo'] config_prefix = database_defaults.get('config_prefix', DEFAULT_CONFIG_PREFIX) config_override = database_defaults.get('config_override', 'GALAXY_CONFIG_') default_sqlite_file = database_defaults['default_sqlite_file'] - if not config_file or get_ext(config_file, ignore='sample') == 'yaml': - config_section = database_defaults.get('config_section', None) - else: - # An .ini file - just let load_app_properties find app:main. - config_section = None + if config_section is None: + if not config_file or get_ext(config_file, ignore='sample') == 'yaml': + config_section = database_defaults.get('config_section', None) + else: + # An .ini file - just let load_app_properties find app:main. + config_section = None properties = load_app_properties(config_file=config_file, config_prefix=config_override, config_section=config_section) if ("%sdatabase_connection" % config_prefix) in properties: diff --git a/lib/galaxy/util/script.py b/lib/galaxy/util/script.py index b227cff8344..c2d58b8eefc 100644 --- a/lib/galaxy/util/script.py +++ b/lib/galaxy/util/script.py @@ -26,19 +26,35 @@ def main(argv=None): if argv is None: argv = sys.argv[1:] args = _arg_parser().parse_args(argv) - kwargs = _app_properties(args) + kwargs = app_properties_from_args(args) action = args.action action_func = ACTIONS[action] action_func(args, kwargs) -def _app_properties(args): - # FIXME: you can use galaxy.util.path.extensions for this - config_file = args.config_file or find_config_file(args.app) +def app_properties_from_args(args, legacy_config_override=None): + config_file = config_file_from_args(args, legacy_config_override=legacy_config_override) app_properties = load_app_properties(config_file=config_file, config_section=args.config_section) return app_properties +def config_file_from_args(args, legacy_config_override=None): + # FIXME: you can use galaxy.util.path.extensions for this + config_file = legacy_config_override or args.config_file or find_config_file(getattr(args, "app", "galaxy")) + return config_file + + +def populate_config_args(parser): + # config and config-file respected because we have used different arguments at different + # time for scripts. + parser.add_argument("-c", "--config-file", "--config", + default=os.environ.get('GALAXY_CONFIG_FILE', None), + help="Galaxy config file (defaults to config/galaxy.ini or config/galaxy.yml)") + parser.add_argument("--config-section", + default=os.environ.get('GALAXY_CONFIG_SECTION', None), + help="app section in config file (defaults to 'galaxy' for YAML/JSON, 'main' (w/ 'app:' prepended) for INI") + + def _arg_parser(): parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument('action', metavar='ACTION', type=str, @@ -46,10 +62,7 @@ def _arg_parser(): default=DEFAULT_ACTION, nargs='?' if DEFAULT_ACTION is not None else None, help='action to perform') - parser.add_argument("-c", "--config-file", - default=os.environ.get('GALAXY_CONFIG_FILE', None)) - parser.add_argument("--config-section", - default=os.environ.get('GALAXY_CONFIG_SECTION', None)) + populate_config_args(parser) parser.add_argument("--app", default=os.environ.get('GALAXY_APP', 'galaxy')) for argument in ARGUMENTS: diff --git a/scripts/cleanup_datasets/admin_cleanup_datasets.py b/scripts/cleanup_datasets/admin_cleanup_datasets.py index ef3bddfcedd..5ab0dd4397b 100755 --- a/scripts/cleanup_datasets/admin_cleanup_datasets.py +++ b/scripts/cleanup_datasets/admin_cleanup_datasets.py @@ -38,6 +38,7 @@ Author: Lance Parsons (lparsons@princeton.edu) """ from __future__ import print_function +import argparse import logging import os import shutil @@ -45,12 +46,10 @@ import sys import time from collections import defaultdict from datetime import datetime, timedelta -from optparse import OptionParser from time import strftime import sqlalchemy as sa from mako.template import Template -from six.moves import configparser from sqlalchemy import and_, false sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'lib'))) @@ -58,6 +57,7 @@ sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pa import galaxy.config import galaxy.model.mapping import galaxy.util +from galaxy.util.script import app_properties_from_args, populate_config_args from cleanup_datasets import CleanupDatasetsApplication # noqa: I100 @@ -74,59 +74,59 @@ def main(): contains the specified text will be marked as deleted in user's history and the user will be notified by email using the specified template file. """ - usage = "usage: %prog [options] galaxy.ini" - parser = OptionParser(usage=usage) - parser.add_option("-d", "--days", dest="days", action="store", - type="int", help="number of days (60)", default=60) - parser.add_option("--tool_id", default=None, - help="Text to match against tool_id" - "Default: match all") - parser.add_option("--template", default=None, - help="Mako Template file to use as email " - "Variables are 'cutoff' for the cutoff in days, " - "'email' for users email and " - "'datasets' which is a list of tuples " - "containing 'dataset' and 'history' names. " - "Default: admin_cleanup_deletion_template.txt") - parser.add_option("-i", "--info_only", action="store_true", - dest="info_only", help="info about the requested action", - default=False) - parser.add_option("-e", "--email_only", action="store_true", - dest="email_only", help="Send emails only, don't delete", - default=False) - parser.add_option("--smtp", default=None, - help="SMTP Server to use to send email. " - "Default: [read from galaxy ini file]") - parser.add_option("--fromaddr", default=None, - help="From address to use to send email. " - "Default: [read from galaxy ini file]") - (options, args) = parser.parse_args() - if len(args) != 1: - parser.print_help() - sys.exit() - ini_file = args[0] + parser = argparse.ArgumentParser() + parser.add_argument('legacy_config', metavar='CONFIG', type=str, + default=None, + nargs='?', + help='config file (legacy, use --config instead)') + parser.add_argument("-d", "--days", dest="days", action="store", + type=int, help="number of days (60)", default=60) + parser.add_argument("--tool_id", default=None, + help="Text to match against tool_id" + "Default: match all") + parser.add_argument("--template", default=None, + help="Mako Template file to use as email " + "Variables are 'cutoff' for the cutoff in days, " + "'email' for users email and " + "'datasets' which is a list of tuples " + "containing 'dataset' and 'history' names. " + "Default: admin_cleanup_deletion_template.txt") + parser.add_argument("-i", "--info_only", action="store_true", + dest="info_only", help="info about the requested action", + default=False) + parser.add_argument("-e", "--email_only", action="store_true", + dest="email_only", help="Send emails only, don't delete", + default=False) + parser.add_argument("--smtp", default=None, + help="SMTP Server to use to send email. " + "Default: [read from galaxy ini file]") + parser.add_argument("--fromaddr", default=None, + help="From address to use to send email. " + "Default: [read from galaxy ini file]") + populate_config_args(parser) - config_parser = configparser.ConfigParser({'here': os.getcwd()}) - config_parser.read(ini_file) - config_dict = {} - for key, value in config_parser.items("app:main"): - config_dict[key] = value + args = parser.parse_args() + config_override = None + if args.legacy_config: + config_override = args.legacy_config - if options.smtp is not None: - config_dict['smtp_server'] = options.smtp - if config_dict.get('smtp_server') is None: + app_properties = app_properties_from_args(args, legacy_config_override=config_override) + + if args.smtp is not None: + app_properties['smtp_server'] = args.smtp + if app_properties.get('smtp_server') is None: parser.error("SMTP Server must be specified as an option (--smtp) " "or in the config file (smtp_server)") - if options.fromaddr is not None: - config_dict['email_from'] = options.fromaddr - if config_dict.get('email_from') is None: + if args.fromaddr is not None: + app_properties['email_from'] = args.fromaddr + if app_properties.get('email_from') is None: parser.error("From address must be specified as an option " "(--fromaddr) or in the config file " "(email_from)") scriptdir = os.path.dirname(os.path.abspath(__file__)) - template_file = options.template + template_file = args.template if template_file is None: default_template = os.path.join(scriptdir, 'admin_cleanup_deletion_template.txt') @@ -145,24 +145,24 @@ def main(): elif not os.path.exists(template_file): parser.error("Specified template file (%s) not found." % template_file) - config = galaxy.config.Configuration(**config_dict) + config = galaxy.config.Configuration(**app_properties) app = CleanupDatasetsApplication(config) - cutoff_time = datetime.utcnow() - timedelta(days=options.days) + cutoff_time = datetime.utcnow() - timedelta(days=args.days) now = strftime("%Y-%m-%d %H:%M:%S") print("##########################################") - print("\n# %s - Handling stuff older than %i days" % (now, options.days)) + print("\n# %s - Handling stuff older than %i days" % (now, args.days)) - if options.info_only: + if args.info_only: print("# Displaying info only ( --info_only )\n") - elif options.email_only: + elif args.email_only: print("# Sending emails only, not deleting ( --email_only )\n") administrative_delete_datasets( - app, cutoff_time, options.days, tool_id=options.tool_id, + app, cutoff_time, args.days, tool_id=args.tool_id, template_file=template_file, config=config, - email_only=options.email_only, info_only=options.info_only) + email_only=args.email_only, info_only=args.info_only) app.shutdown() sys.exit(0) diff --git a/scripts/cleanup_datasets/cleanup_datasets.py b/scripts/cleanup_datasets/cleanup_datasets.py index ee79b842396..28f4cb53458 100755 --- a/scripts/cleanup_datasets/cleanup_datasets.py +++ b/scripts/cleanup_datasets/cleanup_datasets.py @@ -1,28 +1,27 @@ #!/usr/bin/env python from __future__ import print_function +import argparse import logging import os import shutil import sys import time from datetime import datetime, timedelta -from optparse import OptionParser from time import strftime import sqlalchemy as sa -from six.moves import configparser from sqlalchemy import and_, false, null, true from sqlalchemy.orm import eagerload sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'lib'))) import galaxy.config -import galaxy.model.mapping from galaxy.datatypes.registry import Registry from galaxy.exceptions import ObjectNotFound from galaxy.objectstore import build_object_store_from_config from galaxy.util import unicodify +from galaxy.util.script import app_properties_from_args, populate_config_args log = logging.getLogger() log.setLevel(logging.INFO) @@ -69,68 +68,65 @@ def main(): Another way of stating it is: LibraryDatasetDatasetAssociation objects map LibraryDataset objects to Dataset objects, and Dataset objects may be mapped to History objects via HistoryDatasetAssociation objects. """ - usage = "usage: %prog [options] galaxy.ini" - parser = OptionParser(usage=usage) - parser.add_option("-d", "--days", dest="days", action="store", type="int", help="number of days (60)", default=60) - parser.add_option("-r", "--remove_from_disk", action="store_true", dest="remove_from_disk", help="remove datasets from disk when purged", default=False) - parser.add_option("-i", "--info_only", action="store_true", dest="info_only", help="info about the requested action", default=False) - parser.add_option("-f", "--force_retry", action="store_true", dest="force_retry", help="performs the requested actions, but ignores whether it might have been done before. Useful when -r wasn't used, but should have been", default=False) - parser.add_option("-1", "--delete_userless_histories", action="store_true", dest="delete_userless_histories", default=False, help="delete userless histories and datasets") - parser.add_option("-2", "--purge_histories", action="store_true", dest="purge_histories", default=False, help="purge deleted histories") - parser.add_option("-3", "--purge_datasets", action="store_true", dest="purge_datasets", default=False, help="purge deleted datasets") - parser.add_option("-4", "--purge_libraries", action="store_true", dest="purge_libraries", default=False, help="purge deleted libraries") - parser.add_option("-5", "--purge_folders", action="store_true", dest="purge_folders", default=False, help="purge deleted library folders") - parser.add_option("-6", "--delete_datasets", action="store_true", dest="delete_datasets", default=False, help="mark deletable datasets as deleted and purge associated dataset instances") + parser = argparse.ArgumentParser() + parser.add_argument('legacy_config', metavar='CONFIG', type=str, + default=None, + nargs='?', + help='config file (legacy, use --config instead)') + parser.add_argument("-d", "--days", dest="days", action="store", type=int, help="number of days (60)", default=60) + parser.add_argument("-r", "--remove_from_disk", action="store_true", dest="remove_from_disk", help="remove datasets from disk when purged", default=False) + parser.add_argument("-i", "--info_only", action="store_true", dest="info_only", help="info about the requested action", default=False) + parser.add_argument("-f", "--force_retry", action="store_true", dest="force_retry", help="performs the requested actions, but ignores whether it might have been done before. Useful when -r wasn't used, but should have been", default=False) + parser.add_argument("-1", "--delete_userless_histories", action="store_true", dest="delete_userless_histories", default=False, help="delete userless histories and datasets") + parser.add_argument("-2", "--purge_histories", action="store_true", dest="purge_histories", default=False, help="purge deleted histories") + parser.add_argument("-3", "--purge_datasets", action="store_true", dest="purge_datasets", default=False, help="purge deleted datasets") + parser.add_argument("-4", "--purge_libraries", action="store_true", dest="purge_libraries", default=False, help="purge deleted libraries") + parser.add_argument("-5", "--purge_folders", action="store_true", dest="purge_folders", default=False, help="purge deleted library folders") + parser.add_argument("-6", "--delete_datasets", action="store_true", dest="delete_datasets", default=False, help="mark deletable datasets as deleted and purge associated dataset instances") + populate_config_args(parser) - (options, args) = parser.parse_args() - if len(args) != 1: - parser.print_help() - sys.exit() - ini_file = args[0] + args = parser.parse_args() + config_override = None + if args.legacy_config: + config_override = args.legacy_config - if not (options.purge_folders ^ options.delete_userless_histories ^ - options.purge_libraries ^ options.purge_histories ^ - options.purge_datasets ^ options.delete_datasets): + if not (args.purge_folders ^ args.delete_userless_histories ^ + args.purge_libraries ^ args.purge_histories ^ + args.purge_datasets ^ args.delete_datasets): parser.print_help() sys.exit(0) - if options.remove_from_disk and options.info_only: + if args.remove_from_disk and args.info_only: parser.error("remove_from_disk and info_only are mutually exclusive") - config_parser = configparser.ConfigParser({'here': os.getcwd()}) - config_parser.read(ini_file) - config_dict = {} - for key, value in config_parser.items("app:main"): - config_dict[key] = value - - config = galaxy.config.Configuration(**config_dict) - + app_properties = app_properties_from_args(args, legacy_config_override=config_override) + config = galaxy.config.Configuration(**app_properties) app = CleanupDatasetsApplication(config) - cutoff_time = datetime.utcnow() - timedelta(days=options.days) + cutoff_time = datetime.utcnow() - timedelta(days=args.days) now = strftime("%Y-%m-%d %H:%M:%S") print("##########################################") - print("\n# %s - Handling stuff older than %i days" % (now, options.days)) + print("\n# %s - Handling stuff older than %i days" % (now, args.days)) - if options.info_only: + if args.info_only: print("# Displaying info only ( --info_only )\n") - elif options.remove_from_disk: + elif args.remove_from_disk: print("Datasets will be removed from disk.\n") else: print("Datasets will NOT be removed from disk.\n") - if options.delete_userless_histories: - delete_userless_histories(app, cutoff_time, info_only=options.info_only, force_retry=options.force_retry) - elif options.purge_histories: - purge_histories(app, cutoff_time, options.remove_from_disk, info_only=options.info_only, force_retry=options.force_retry) - elif options.purge_datasets: - purge_datasets(app, cutoff_time, options.remove_from_disk, info_only=options.info_only, force_retry=options.force_retry) - elif options.purge_libraries: - purge_libraries(app, cutoff_time, options.remove_from_disk, info_only=options.info_only, force_retry=options.force_retry) - elif options.purge_folders: - purge_folders(app, cutoff_time, options.remove_from_disk, info_only=options.info_only, force_retry=options.force_retry) - elif options.delete_datasets: - delete_datasets(app, cutoff_time, options.remove_from_disk, info_only=options.info_only, force_retry=options.force_retry) + if args.delete_userless_histories: + delete_userless_histories(app, cutoff_time, info_only=args.info_only, force_retry=args.force_retry) + elif args.purge_histories: + purge_histories(app, cutoff_time, args.remove_from_disk, info_only=args.info_only, force_retry=args.force_retry) + elif args.purge_datasets: + purge_datasets(app, cutoff_time, args.remove_from_disk, info_only=args.info_only, force_retry=args.force_retry) + elif args.purge_libraries: + purge_libraries(app, cutoff_time, args.remove_from_disk, info_only=args.info_only, force_retry=args.force_retry) + elif args.purge_folders: + purge_folders(app, cutoff_time, args.remove_from_disk, info_only=args.info_only, force_retry=args.force_retry) + elif args.delete_datasets: + delete_datasets(app, cutoff_time, args.remove_from_disk, info_only=args.info_only, force_retry=args.force_retry) app.shutdown() sys.exit(0) @@ -522,11 +518,9 @@ def _purge_folder(folder, app, remove_from_disk, info_only=False): class CleanupDatasetsApplication(object): """Encapsulates the state of a Universe application""" def __init__(self, config): - if config.database_connection is False: - config.database_connection = "sqlite:///%s?isolation_level=IMMEDIATE" % config.database self.object_store = build_object_store_from_config(config) # Setup the database engine and ORM - self.model = galaxy.model.mapping.init(config.file_path, config.database_connection, engine_options={}, create_tables=False, object_store=self.object_store) + self.model = galaxy.config.init_models_from_config(config, object_store=self.object_store) registry = Registry() registry.load_datatypes() galaxy.model.set_datatypes_registry(registry) diff --git a/scripts/cleanup_datasets/pgcleanup.py b/scripts/cleanup_datasets/pgcleanup.py index cdbda675e21..8666f3b51ea 100755 --- a/scripts/cleanup_datasets/pgcleanup.py +++ b/scripts/cleanup_datasets/pgcleanup.py @@ -6,16 +6,15 @@ pgcleanup.py - A script for cleaning up datasets in Galaxy efficiently, by """ from __future__ import print_function +import argparse import datetime import inspect import logging import os import shutil import sys -from optparse import OptionParser import psycopg2 -from six.moves.configparser import ConfigParser from sqlalchemy.engine.url import make_url galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) @@ -25,6 +24,7 @@ import galaxy.config from galaxy.exceptions import ObjectNotFound from galaxy.objectstore import build_object_store_from_config from galaxy.util.bunch import Bunch +from galaxy.util.script import app_properties_from_args, populate_config_args log = logging.getLogger() @@ -39,7 +39,6 @@ class Dataset(Bunch): class Cleanup(object): def __init__(self): - self.options = None self.args = None self.config = None self.conn = None @@ -61,49 +60,38 @@ class Cleanup(object): self.action_names.append(name) def __parse_args(self): - default_config = os.path.abspath(os.path.join(galaxy_root, 'config', 'galaxy.ini')) + parser = argparse.ArgumentParser() + populate_config_args(parser) + parser.add_argument('-d', '--debug', action='store_true', dest='debug', help='Enable debug logging', default=False) + parser.add_argument('--dry-run', action='store_true', dest='dry_run', help="Dry run (rollback all transactions)", default=False) + parser.add_argument('--force-retry', action='store_true', dest='force_retry', help="Retry file removals (on applicable actions)", default=False) + parser.add_argument('-o', '--older-than', type=int, dest='days', help='Only perform action(s) on objects that have not been updated since the specified number of days', default=14) + parser.add_argument('-U', '--no-update-time', action='store_false', dest='update_time', help="Don't set update_time on updated objects", default=True) + parser.add_argument('-s', '--sequence', dest='sequence', help='Comma-separated sequence of actions, chosen from: %s' % self.action_names, default='') + parser.add_argument('-w', '--work-mem', dest='work_mem', help='Set PostgreSQL work_mem for this connection', default=None) + parser.add_argument('-l', '--log-dir', dest='log_dir', help='Log file directory', default=os.path.join(galaxy_root, 'scripts', 'cleanup_datasets')) + self.args = parser.parse_args() - parser = OptionParser() - parser.add_option('-c', '--config', dest='config', help='Path to Galaxy config file (config/galaxy.ini)', default=default_config) - parser.add_option('-d', '--debug', action='store_true', dest='debug', help='Enable debug logging', default=False) - parser.add_option('--dry-run', action='store_true', dest='dry_run', help="Dry run (rollback all transactions)", default=False) - parser.add_option('--force-retry', action='store_true', dest='force_retry', help="Retry file removals (on applicable actions)", default=False) - parser.add_option('-o', '--older-than', type='int', dest='days', help='Only perform action(s) on objects that have not been updated since the specified number of days', default=14) - parser.add_option('-U', '--no-update-time', action='store_false', dest='update_time', help="Don't set update_time on updated objects", default=True) - parser.add_option('-s', '--sequence', dest='sequence', help='Comma-separated sequence of actions, chosen from: %s' % self.action_names, default='') - parser.add_option('-w', '--work-mem', dest='work_mem', help='Set PostgreSQL work_mem for this connection', default=None) - parser.add_option('-l', '--log-dir', dest='log_dir', help='Log file directory', default=os.path.join(galaxy_root, 'scripts', 'cleanup_datasets')) - (self.options, self.args) = parser.parse_args() + self.args.sequence = [x.strip() for x in self.args.sequence.split(',')] - self.options.sequence = [x.strip() for x in self.options.sequence.split(',')] - - if self.options.sequence == ['']: + if self.args.sequence == ['']: print("Error: At least one action must be specified in the action sequence\n") parser.print_help() sys.exit(0) def __setup_logging(self): format = "%(funcName)s %(levelname)s %(asctime)s %(message)s" - if self.options.debug: + if self.args.debug: logging.basicConfig(level=logging.DEBUG, format=format) else: logging.basicConfig(level=logging.INFO, format=format) def __load_config(self): - log.info('Reading config from %s' % self.options.config) - config_parser = ConfigParser(dict(here=os.getcwd(), - database_connection='sqlite:///database/universe.sqlite?isolation_level=IMMEDIATE')) - config_parser.read(self.options.config) - - config_dict = {} - for key, value in config_parser.items('app:main'): - config_dict[key] = value - config_dict['root_dir'] = galaxy_root - - self.config = galaxy.config.Configuration(**config_dict) + app_properties = app_properties_from_args(self.args) + self.config = galaxy.config.Configuration(**app_properties) def __connect_db(self): - url = make_url(self.config.database_connection) + url = make_url(galaxy.config.get_database_url(self.config)) log.info('Connecting to database with URL: %s' % url) args = url.translate_connect_args(username='user') @@ -118,9 +106,9 @@ class Cleanup(object): def _open_logfile(self): action_name = inspect.stack()[1][3] - logname = os.path.join(self.options.log_dir, action_name + '.log') + logname = os.path.join(self.args.log_dir, action_name + '.log') - if self.options.dry_run: + if self.args.dry_run: log.debug('--dry-run specified, logging changes to stdout instead of log file: %s' % logname) self.logs[action_name] = sys.stdout else: @@ -145,7 +133,7 @@ class Cleanup(object): self.logs[action_name].write(message.ljust(72, '=')) self.logs[action_name].write('\n') - if self.options.dry_run: + if self.args.dry_run: log.debug('--dry-run specified, changes were logged to stdout insted of log file') else: log.debug('Closing log file: %s' % self.logs[action_name].name) @@ -155,14 +143,14 @@ class Cleanup(object): def _run(self): ok = True - for name in self.options.sequence: + for name in self.args.sequence: if name not in self.action_names: log.error('Unknown action in sequence: %s' % name) ok = False if not ok: log.critical('Exiting due to previous error(s)') sys.exit(1) - for name in self.options.sequence: + for name in self.args.sequence: log.info('Calling %s' % name) self.__getattribute__(name)() log.info('Finished %s' % name) @@ -188,7 +176,7 @@ class Cleanup(object): cur = self.conn.cursor() - if self.options.dry_run: + if self.args.dry_run: sql = "SELECT MAX(id) FROM cleanup_event;" cur.execute(sql) max_id = cur.fetchone()[0] @@ -217,9 +205,9 @@ class Cleanup(object): cur = self.conn.cursor() - if self.options.work_mem is not None: - log.info('Setting work_mem to %s' % self.options.work_mem) - cur.execute('SET work_mem TO %s', (self.options.work_mem,)) + if self.args.work_mem is not None: + log.info('Setting work_mem to %s' % self.args.work_mem) + cur.execute('SET work_mem TO %s', (self.args.work_mem,)) log.info('Executing SQL') cur.execute(sql, args) @@ -228,7 +216,7 @@ class Cleanup(object): return cur def _flush(self): - if self.options.dry_run: + if self.args.dry_run: self.conn.rollback() log.info("--dry-run specified, all changes rolled back") else: @@ -245,7 +233,7 @@ class Cleanup(object): log.error('Unable to get MetadataFile %s filename: %s' % (id, e)) return - if not self.options.dry_run: + if not self.args.dry_run: try: os.unlink(filename) except Exception as e: @@ -341,7 +329,7 @@ class Cleanup(object): """ Mark deleted all "anonymous" Histories (not owned by a registered user) that are older than the specified number of days. """ - log.info('Marking deleted all userless Histories older than %i days' % self.options.days) + log.info('Marking deleted all userless Histories older than %i days' % self.args.days) event_id = self._create_event() @@ -364,12 +352,12 @@ class Cleanup(object): """ update_time_sql = '' - if self.options.update_time: + if self.args.update_time: update_time_sql = """, update_time = NOW()""" sql = sql % (update_time_sql, '%s', '%s') - args = (self.options.days, event_id) + args = (self.args.days, event_id) cur = self._update(sql, args) self._flush() @@ -385,7 +373,7 @@ class Cleanup(object): Mark deleted all ImplicitlyConvertedDatasetAssociations whose hda_parent_id is purged in this step. Mark purged all HistoryDatasetAssociations for which an ImplicitlyConvertedDatasetAssociation with matching hda_id is deleted in this step. """ - log.info('Marking purged all deleted HistoryDatasetAssociations older than %i days' % self.options.days) + log.info('Marking purged all deleted HistoryDatasetAssociations older than %i days' % self.args.days) event_id = self._create_event() @@ -457,16 +445,16 @@ class Cleanup(object): AND NOT purged""" update_time_sql = "" - if self.options.force_retry: + if self.args.force_retry: force_retry_sql = "" else: # only update time if not doing force retry (otherwise a lot of things would have their update times reset that were actually purged a long time ago) - if self.options.update_time: + if self.args.update_time: update_time_sql = """, update_time = NOW()""" sql = sql % (update_time_sql, force_retry_sql, '%s', update_time_sql, update_time_sql, update_time_sql, '%s', '%s', '%s', '%s') - args = (self.options.days, event_id, event_id, event_id, event_id) + args = (self.args.days, event_id, event_id, event_id, event_id) cur = self._update(sql, args) self._flush() @@ -573,15 +561,15 @@ class Cleanup(object): AND NOT purged""" update_time_sql = "" - if self.options.force_retry: + if self.args.force_retry: force_retry_sql = "" else: - if self.options.update_time: + if self.args.update_time: update_time_sql += """, update_time = NOW()""" sql = sql % (update_time_sql, force_retry_sql, '%s', update_time_sql, update_time_sql, update_time_sql, update_time_sql, '%s', '%s', '%s', '%s', '%s') - args = (self.options.days, event_id, event_id, event_id, event_id, event_id) + args = (self.args.days, event_id, event_id, event_id, event_id, event_id) cur = self._update(sql, args) self._flush() @@ -627,12 +615,12 @@ class Cleanup(object): """ update_time_sql = "" - if self.options.update_time: + if self.args.update_time: update_time_sql += """, update_time = NOW()""" sql = sql % (update_time_sql, '%s', '%s') - args = (self.options.days, event_id) + args = (self.args.days, event_id) cur = self._update(sql, args) self._flush() @@ -676,12 +664,12 @@ class Cleanup(object): """ update_time_sql = "" - if self.options.update_time: + if self.args.update_time: update_time_sql += """, update_time = NOW()""" sql = sql % (update_time_sql, '%s', '%s', '%s') - args = (self.options.days, self.options.days, event_id) + args = (self.args.days, self.args.days, event_id) cur = self._update(sql, args) self._flush() @@ -721,15 +709,15 @@ class Cleanup(object): AND NOT purged""" update_time_sql = "" - if self.options.force_retry: + if self.args.force_retry: force_retry_sql = "" else: - if self.options.update_time: + if self.args.update_time: update_time_sql = """, update_time = NOW()""" sql = sql % (update_time_sql, force_retry_sql, '%s', '%s') - args = (self.options.days, event_id) + args = (self.args.days, event_id) cur = self._update(sql, args) self._flush() @@ -752,7 +740,7 @@ class Cleanup(object): # don't check for existence of the dataset, it should exist self._log('Removing from disk: %s' % filename) - if not self.options.dry_run: + if not self.args.dry_run: try: os.unlink(filename) except Exception as e: @@ -761,7 +749,7 @@ class Cleanup(object): # extra_files_dir is optional so it's checked first if extra_files_dir is not None and os.path.exists(extra_files_dir): self._log('Removing from disk: %s' % extra_files_dir) - if not self.options.dry_run: + if not self.args.dry_run: try: shutil.rmtree(extra_files_dir) except Exception as e: diff --git a/scripts/cleanup_datasets/populate_uuid.py b/scripts/cleanup_datasets/populate_uuid.py index fe9c391509c..a3fa6654c05 100755 --- a/scripts/cleanup_datasets/populate_uuid.py +++ b/scripts/cleanup_datasets/populate_uuid.py @@ -8,33 +8,32 @@ script fixes datasets that were generated before the change. """ from __future__ import print_function +import argparse +import os import sys import uuid -from galaxy.model import mapping -from galaxy.model.orm.scripts import get_config +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'lib'))) -assert sys.version_info[:2] >= (2, 6) +import galaxy.config +from galaxy.util.script import app_properties_from_args, populate_config_args - -def usage(prog): - print("usage: %s galaxy.ini" % prog) - print(""" +DESCRIPTION = """ Populates blank uuid fields in datasets with randomly generated values. Going forward, these ids will be generated for all new datasets. This script fixes datasets that were generated before the change. - """) +""" def main(): - if len(sys.argv) != 2 or sys.argv == "-h" or sys.argv == "--help": - usage(sys.argv[0]) - sys.exit() - ini_file = sys.argv.pop(1) - config = get_config(ini_file) + parser = argparse.ArgumentParser(DESCRIPTION) + populate_config_args(parser) + args = parser.parse_args() - model = mapping.init(ini_file, config['db_url'], create_tables=False) + app_properties = app_properties_from_args(args) + config = galaxy.config.Configuration(**app_properties) + model = galaxy.config.init_models_from_config(config) for row in model.context.query(model.Dataset): if row.uuid is None: @@ -47,6 +46,7 @@ def main(): row.uuid = uuid.uuid4() print("Setting Workflow:", row.id, " UUID to ", row.uuid) model.context.flush() + print("Complete") if __name__ == "__main__": diff --git a/scripts/communication/communication_server.py b/scripts/communication/communication_server.py index a1cc76799b7..1909bfcb447 100755 --- a/scripts/communication/communication_server.py +++ b/scripts/communication/communication_server.py @@ -49,22 +49,26 @@ from flask_socketio import ( sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'lib'))) -from galaxy.model import mapping -from galaxy.model.orm.scripts import get_config -from galaxy.util.properties import load_app_properties +import galaxy.config from galaxy.util.sanitize_html import sanitize_html +from galaxy.util.script import app_properties_from_args, populate_config_args from galaxy.web.security import SecurityHelper logging.basicConfig() log = logging.getLogger(__name__) -# Get config file and load up SA session -config = get_config(sys.argv) -model = mapping.init('/tmp/', config['db_url']) -sa_session = model.context.current +parser = argparse.ArgumentParser(description='Real-time communication server for Galaxy.') +parser.add_argument('--port', type=int, default="7070", help='Port number on which the server should run.') +parser.add_argument('--host', default='localhost', help='Hostname of the communication server.') +populate_config_args(parser) +args = parser.parse_args() # With the config file we can load the full app properties -app_properties = load_app_properties(ini_file=config['config_file']) +app_properties = app_properties_from_args(args) + +config = galaxy.config.Configuration(**app_properties) +model = galaxy.config.init_models_from_config(config) +sa_session = model.context.current # We need the ID secret for configuring the security helper to decrypt # galaxysession cookies. @@ -217,9 +221,4 @@ def leave(message): if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Real-time communication server for Galaxy.') - parser.add_argument('--port', type=int, default="7070", help='Port number on which the server should run.') - parser.add_argument('--host', default='localhost', help='Hostname of the communication server.') - - args = parser.parse_args() socketio.run(app, host=args.host, port=args.port) diff --git a/scripts/db_shell.py b/scripts/db_shell.py index 7fac545d943..2ec27eb2db4 100644 --- a/scripts/db_shell.py +++ b/scripts/db_shell.py @@ -32,7 +32,7 @@ sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pa from galaxy.datatypes.registry import Registry from galaxy.model import * # noqa -from galaxy.model import set_datatypes_registry # More expclicit than `*` import +from galaxy.model import set_datatypes_registry # More explicit than `*` import from galaxy.model.mapping import init from galaxy.model.orm.scripts import get_config diff --git a/scripts/galaxy-main b/scripts/galaxy-main index 76d7623017e..04c59f3b688 100755 --- a/scripts/galaxy-main +++ b/scripts/galaxy-main @@ -184,7 +184,7 @@ class GalaxyConfigBuilder(object): def populate_options(cls, arg_parser): arg_parser.add_argument("-c", "--config-file", default=None, help="Galaxy config file (defaults to config/galaxy.ini)") arg_parser.add_argument("--ini-path", default=None, help="DEPRECATED: use -c/--config-file") - arg_parser.add_argument("--app", default=None, help="app section in ini file (defaults to 'galaxy' for YAML/JSON, 'main' (w/ 'app:' prepended) for INI") + arg_parser.add_argument("--app", default=None, help="app section in config file (defaults to 'galaxy' for YAML/JSON, 'main' (w/ 'app:' prepended) for INI") arg_parser.add_argument("-d", "--daemonize", default=False, help="Daemonzie process", action="store_true") arg_parser.add_argument("--daemon-log-file", default=None, help="log file for daemon script ") arg_parser.add_argument("--log-file", default=None, help="Galaxy log file (overrides log configuration in config_file if set)") diff --git a/scripts/grt/export.py b/scripts/grt/export.py index 46ebcd70fb7..3048ac7d15c 100644 --- a/scripts/grt/export.py +++ b/scripts/grt/export.py @@ -18,41 +18,31 @@ import yaml sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'lib'))) import galaxy import galaxy.config -from galaxy.model import mapping from galaxy.objectstore import build_object_store_from_config from galaxy.util import hash_util -from galaxy.util.properties import load_app_properties +from galaxy.util.script import app_properties_from_args, config_file_from_args, populate_config_args sample_config = os.path.abspath(os.path.join(os.path.dirname(__file__), 'grt.yml.sample')) default_config = os.path.abspath(os.path.join(os.path.dirname(__file__), 'grt.yml')) -def _init(config, need_app=False): - if config.startswith('/'): - config_file = os.path.abspath(config) - else: - config_file = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, config)) - - properties = load_app_properties(ini_file=config_file) +def _init(args, need_app=False): + properties = app_properties_from_args(args) config = galaxy.config.Configuration(**properties) object_store = build_object_store_from_config(config) if not config.database_connection: logging.warning("The database connection is empty. If you are using the default value, please uncomment that in your galaxy.ini") if need_app: + config_file = config_file_from_args(args) app = galaxy.app.UniverseApplication(global_conf={'__file__': config_file, 'here': os.getcwd()}) else: app = None + model = galaxy.config.init_models_from_config(config, object_store=object_store) return ( - mapping.init( - config.file_path, - config.database_connection, - create_tables=False, - object_store=object_store - ), + model, object_store, - config.database_connection.split(':')[0], config, app ) @@ -167,7 +157,7 @@ def main(argv): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('-r', '--report-directory', help='Directory to store reports in', default=os.path.abspath(os.path.join('.', 'reports'))) - parser.add_argument('-c', '--config', help='Path to GRT config file', + parser.add_argument('-g', '--grt-config', help='Path to GRT config file', default=default_config) parser.add_argument("-l", "--loglevel", choices=['debug', 'info', 'warning', 'error', 'critical'], help="Set the logging level", default='warning') @@ -175,6 +165,7 @@ def main(argv): help="Batch size for sql queries") parser.add_argument("-m", "--max-records", type=int, default=0, help="Maximum number of records to include in a single report. This option should ONLY be used when reporting historical data. Setting this may require running GRT multiple times to capture all historical logs.") + populate_config_args(parser) args = parser.parse_args() logging.getLogger().setLevel(getattr(logging, args.loglevel.upper())) @@ -209,7 +200,7 @@ def main(argv): last_job_sent = -1 annotate('galaxy_init', 'Loading Galaxy...') - model, object_store, engine, gxconfig, app = _init(config['galaxy_config'], need_app=config['grt']['share_toolbox']) + model, object_store, gxconfig, app = _init(args, need_app=config['grt']['share_toolbox']) # Galaxy overrides our logging level. logging.getLogger().setLevel(getattr(logging, args.loglevel.upper())) sa_session = model.context.current diff --git a/scripts/grt/upload.py b/scripts/grt/upload.py index 8ec619fc5b7..f847cfcd1b1 100644 --- a/scripts/grt/upload.py +++ b/scripts/grt/upload.py @@ -20,7 +20,7 @@ def main(argv): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('-r', '--report-directory', help='Directory in which reports are stored', default=os.path.abspath(os.path.join('.', 'reports'))) - parser.add_argument('-c', '--config', help='Path to GRT config file', + parser.add_argument('-g', '--grt-config', help='Path to GRT config file', default=default_config) parser.add_argument("-l", "--loglevel", choices=['debug', 'info', 'warning', 'error', 'critical'], help="Set the logging level", default='warning') diff --git a/scripts/helper.py b/scripts/helper.py index af7150f7320..1bf36fb9e4e 100644 --- a/scripts/helper.py +++ b/scripts/helper.py @@ -6,61 +6,47 @@ returns the disk path of a dataset. """ from __future__ import print_function +import argparse import os import sys -from optparse import OptionParser - -from six.moves.configparser import ConfigParser sys.path.insert(1, os.path.join(os.path.dirname(__file__), os.pardir, 'lib')) -from galaxy.model import mapping +import galaxy.config +from galaxy.util.script import app_properties_from_args, populate_config_args from galaxy.web import security -default_config = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'config/galaxy.ini')) +parser = argparse.ArgumentParser() +populate_config_args(parser) +parser.add_argument('-e', '--encode-id', dest='encode_id', help='Encode an ID') +parser.add_argument('-d', '--decode-id', dest='decode_id', help='Decode an ID') +parser.add_argument('--hda', dest='hda_id', help='Display HistoryDatasetAssociation info') +parser.add_argument('--ldda', dest='ldda_id', help='Display LibraryDatasetDatasetAssociation info') +args = parser.parse_args() -parser = OptionParser() -parser.add_option('-c', '--config', dest='config', help='Path to Galaxy config file (config/galaxy.ini)', default=default_config) -parser.add_option('-e', '--encode-id', dest='encode_id', help='Encode an ID') -parser.add_option('-d', '--decode-id', dest='decode_id', help='Decode an ID') -parser.add_option('--hda', dest='hda_id', help='Display HistoryDatasetAssociation info') -parser.add_option('--ldda', dest='ldda_id', help='Display LibraryDatasetDatasetAssociation info') -(options, args) = parser.parse_args() +app_properties = app_properties_from_args(args) +config = galaxy.config.Configuration(**app_properties) +helper = security.SecurityHelper(id_secret=app_properties.get('id_secret')) +model = galaxy.config.init_models_from_config(config) -try: - assert options.encode_id or options.decode_id or options.hda_id or options.ldda_id -except Exception: - parser.print_help() - sys.exit(1) +if args.encode_id: + print('Encoded "%s": %s' % (args.encode_id, helper.encode_id(args.encode_id))) -options.config = os.path.abspath(options.config) +if args.decode_id: + print('Decoded "%s": %s' % (args.decode_id, helper.decode_id(args.decode_id))) -config = ConfigParser(dict(file_path='database/files', - id_secret='USING THE DEFAULT IS NOT SECURE!', - database_connection='sqlite:///database/universe.sqlite?isolation_level=IMMEDIATE')) -config.read(options.config) - -helper = security.SecurityHelper(id_secret=config.get('app:main', 'id_secret')) -model = mapping.init(config.get('app:main', 'file_path'), config.get('app:main', 'database_connection'), create_tables=False) - -if options.encode_id: - print('Encoded "%s": %s' % (options.encode_id, helper.encode_id(options.encode_id))) - -if options.decode_id: - print('Decoded "%s": %s' % (options.decode_id, helper.decode_id(options.decode_id))) - -if options.hda_id: +if args.hda_id: try: - hda_id = int(options.hda_id) + hda_id = int(args.hda_id) except Exception: - hda_id = int(helper.decode_id(options.hda_id)) + hda_id = int(helper.decode_id(args.hda_id)) hda = model.context.current.query(model.HistoryDatasetAssociation).get(hda_id) print('HDA "%s" is Dataset "%s" at: %s' % (hda.id, hda.dataset.id, hda.file_name)) -if options.ldda_id: +if args.ldda_id: try: - ldda_id = int(options.ldda_id) + ldda_id = int(args.ldda_id) except Exception: - ldda_id = int(helper.decode_id(options.ldda_id)) + ldda_id = int(helper.decode_id(args.ldda_id)) ldda = model.context.current.query(model.HistoryDatasetAssociation).get(ldda_id) print('LDDA "%s" is Dataset "%s" at: %s' % (ldda.id, ldda.dataset.id, ldda.file_name)) diff --git a/scripts/manage_db.py b/scripts/manage_db.py index 65c519fcfb9..c697f6263d8 100644 --- a/scripts/manage_db.py +++ b/scripts/manage_db.py @@ -12,7 +12,8 @@ from galaxy.model.orm.scripts import get_config def invoke_migrate_main(): - config = get_config(sys.argv) + # Migrate has its own args, so cannot use argparse + config = get_config(sys.argv, use_argparse=False) db_url = config['db_url'] repo = config['repo'] diff --git a/scripts/runtime_stats.py b/scripts/runtime_stats.py index d5dc75ce4b3..37e17468057 100755 --- a/scripts/runtime_stats.py +++ b/scripts/runtime_stats.py @@ -32,14 +32,20 @@ Examples from __future__ import print_function import argparse +import os import re import sys import numpy import psycopg2 -from six.moves import configparser from sqlalchemy.engine import url +galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) +sys.path.insert(1, os.path.join(galaxy_root, 'lib')) + +import galaxy.config +from galaxy.util.script import app_properties_from_args, populate_config_args + DATA_SOURCES = ('metrics', 'history') METRICS_SQL = """ @@ -78,7 +84,7 @@ def parse_arguments(): help='Use SQL `LIKE` operator to find ' 'a shed-installed tool using the tool\'s ' '"short" id') - parser.add_argument('-c', '--config', help='Galaxy config file') + populate_config_args(parser) parser.add_argument('-d', '--debug', action='store_true', default=False, @@ -110,14 +116,12 @@ def parse_arguments(): print('ERROR: Data source `%s` unknown, valid source are: %s' % (args.source, ', '.join(DATA_SOURCES))) - if args.config: - cp = configparser.ConfigParser() - cp.readfp(open(args.config)) - uri = cp.get('app:main', 'database_connection') - names = {'database': 'dbname', 'username': 'user'} - args.connect_args = url.make_url(uri).translate_connect_args(**names) - else: - args.connect_args = {} + app_properties = app_properties_from_args(args) + config = galaxy.config.Configuration(**app_properties) + uri = args.config.get_database_url(config) + + names = {'database': 'dbname', 'username': 'user'} + args.connect_args = url.make_url(uri).translate_connect_args(**names) if args.debug: print('Got options:') diff --git a/scripts/secret_decoder_ring.py b/scripts/secret_decoder_ring.py index ff809a39222..7b8c6a66223 100644 --- a/scripts/secret_decoder_ring.py +++ b/scripts/secret_decoder_ring.py @@ -2,32 +2,36 @@ """ Script to encode/decode the IDs that galaxy exposes to users and admins. """ +import argparse import logging import os import sys sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'lib'))) -from galaxy.model import mapping -from galaxy.model.orm.scripts import get_config -from galaxy.util.properties import load_app_properties +from galaxy.util.script import app_properties_from_args, populate_config_args from galaxy.web.security import SecurityHelper logging.basicConfig() log = logging.getLogger(__name__) -# Get config file and load up SA session -config = get_config(sys.argv) -model = mapping.init('/tmp/', config['db_url']) -sa_session = model.context.current +parser = argparse.ArgumentParser() +parser.add_argument('action', metavar='ACTION', type=str, + default=None, + help='decode|encode') +parser.add_argument('value', metavar='VALUE', type=str, + default=None, + help='value to encode or decode') +populate_config_args(parser) +args = parser.parse_args() -# With the config file we can load the full app properties -app_properties = load_app_properties(ini_file=config['config_file']) +app_properties = app_properties_from_args(args) +helper = SecurityHelper(id_secret=app_properties.get('id_secret')) # We need the ID secret for configuring the security helper to decrypt # galaxysession cookies. if "id_secret" not in app_properties: - log.warning('No ID_SECRET specified. Please set the "id_secret" in your galaxy.ini.') + log.warning('No ID_SECRET specified. Please set the "id_secret" in your galaxy.yml.') id_secret = app_properties.get('id_secret', 'dangerous_default') @@ -35,17 +39,10 @@ security_helper = SecurityHelper(id_secret=id_secret) # And get access to the models # Login manager to manage current_user functionality -if len(sys.argv) != 3: - sys.stdout.write("python %s (encode|decode) value\n" % sys.argv[0]) - sys.exit(1) - -action = sys.argv[1] -value = sys.argv[2] - -if action == 'decode': - sys.stdout.write(security_helper.decode_guid(value.lstrip('F'))) -elif action == 'encode': - sys.stdout.write(security_helper.encode_guid(value)) +if args.action == 'decode': + sys.stdout.write(security_helper.decode_guid(args.value.lstrip('F'))) +elif args.action == 'encode': + sys.stdout.write(security_helper.encode_guid(args.value)) else: sys.stdout.write("Unknown argument") sys.stdout.write('\n') diff --git a/scripts/set_dataset_sizes.py b/scripts/set_dataset_sizes.py index 0209ace6546..86b6b24c357 100644 --- a/scripts/set_dataset_sizes.py +++ b/scripts/set_dataset_sizes.py @@ -1,40 +1,28 @@ #!/usr/bin/env python from __future__ import print_function +import argparse import os import sys -from optparse import OptionParser - -from six.moves.configparser import ConfigParser sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'lib'))) import galaxy.config -from galaxy.model import mapping from galaxy.objectstore import build_object_store_from_config -default_config = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'config/galaxy.ini')) +from galaxy.util.script import app_properties_from_args, populate_config_args -parser = OptionParser() -parser.add_option('-c', '--config', dest='config', help='Path to Galaxy config file (config/galaxy.ini)', default=default_config) -(options, args) = parser.parse_args() +parser = argparse.ArgumentParser() +populate_config_args(parser) +args = parser.parse_args() def init(): - options.config = os.path.abspath(options.config) - - config_parser = ConfigParser(dict(here=os.getcwd(), - database_connection='sqlite:///database/universe.sqlite?isolation_level=IMMEDIATE')) - config_parser.read(options.config) - - config_dict = {} - for key, value in config_parser.items("app:main"): - config_dict[key] = value - - config = galaxy.config.Configuration(**config_dict) + app_properties = app_properties_from_args(args) + config = galaxy.config.Configuration(**app_properties) object_store = build_object_store_from_config(config) - return (mapping.init(config.file_path, config.database_connection, create_tables=False, object_store=object_store), - object_store) + model = galaxy.config.init_models_from_config(config, object_store=object_store) + return model, object_store if __name__ == '__main__': diff --git a/scripts/set_user_disk_usage.py b/scripts/set_user_disk_usage.py index 426e813259e..79ab26f1b8c 100755 --- a/scripts/set_user_disk_usage.py +++ b/scripts/set_user_disk_usage.py @@ -1,11 +1,9 @@ #!/usr/bin/env python from __future__ import print_function +import argparse import os import sys -from optparse import OptionParser - -from six.moves.configparser import ConfigParser sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'lib'))) @@ -13,41 +11,30 @@ import galaxy.config from galaxy.model.util import pgcalc from galaxy.objectstore import build_object_store_from_config from galaxy.util import nice_size - +from galaxy.util.script import app_properties_from_args, populate_config_args default_config = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'config/galaxy.ini')) -parser = OptionParser() -parser.add_option('-c', '--config', dest='config', help='Path to Galaxy config file (config/galaxy.ini)', default=default_config) -parser.add_option('-u', '--username', dest='username', help='Username of user to update', default='all') -parser.add_option('-e', '--email', dest='email', help='Email address of user to update', default='all') -parser.add_option('--dry-run', dest='dryrun', help='Dry run (show changes but do not save to database)', action='store_true', default=False) -(options, args) = parser.parse_args() +parser = argparse.ArgumentParser() +parser.add_argument('-u', '--username', dest='username', help='Username of user to update', default='all') +parser.add_argument('-e', '--email', dest='email', help='Email address of user to update', default='all') +parser.add_argument('--dry-run', dest='dryrun', help='Dry run (show changes but do not save to database)', action='store_true', default=False) +populate_config_args(parser) +args = parser.parse_args() def init(): - options.config = os.path.abspath(options.config) - if options.username == 'all': - options.username = None - if options.email == 'all': - options.email = None - config_parser = ConfigParser(dict(here=os.getcwd(), - database_connection='sqlite:///database/universe.sqlite?isolation_level=IMMEDIATE')) - config_parser.read(options.config) + if args.username == 'all': + args.username = None + if args.email == 'all': + args.email = None - config_dict = {} - for key, value in config_parser.items("app:main"): - config_dict[key] = value - - config = galaxy.config.Configuration(**config_dict) + app_properties = app_properties_from_args(args) + config = galaxy.config.Configuration(**app_properties) object_store = build_object_store_from_config(config) - - from galaxy.model import mapping - - return (mapping.init(config.file_path, config.database_connection, create_tables=False, object_store=object_store), - object_store, - config.database_connection.split(':')[0]) + engine = galaxy.config.get_database_url(config).split(":")[0] + return galaxy.config.init_models_from_config(config, object_store=object_store), object_store, engine def quotacheck(sa_session, users, engine): @@ -62,7 +49,7 @@ def quotacheck(sa_session, users, engine): print('usage changed while calculating, trying again...') return quotacheck(sa_session, user, engine) else: - new = pgcalc(sa_session, user.id, dryrun=options.dryrun) + new = pgcalc(sa_session, user.id, dryrun=args.dryrun) # yes, still a small race condition between here and the flush print('old usage:', nice_size(current), 'change:', end=' ') if new in (current, None): @@ -72,7 +59,7 @@ def quotacheck(sa_session, users, engine): print('+%s' % (nice_size(new - current))) else: print('-%s' % (nice_size(current - new))) - if not options.dryrun and engine not in ('postgres', 'postgresql'): + if not args.dryrun and engine not in ('postgres', 'postgresql'): user.set_disk_usage(new) sa_session.add(user) sa_session.flush() @@ -83,7 +70,7 @@ if __name__ == '__main__': model, object_store, engine = init() sa_session = model.context.current - if not options.username and not options.email: + if not args.username and not args.email: user_count = sa_session.query(model.User).count() print('Processing %i users...' % user_count) for i, user in enumerate(sa_session.query(model.User).enable_eagerloads(False).yield_per(1000)): @@ -92,10 +79,10 @@ if __name__ == '__main__': print('100% complete') object_store.shutdown() sys.exit(0) - elif options.username: - user = sa_session.query(model.User).enable_eagerloads(False).filter_by(username=options.username).first() - elif options.email: - user = sa_session.query(model.User).enable_eagerloads(False).filter_by(email=options.email).first() + elif args.username: + user = sa_session.query(model.User).enable_eagerloads(False).filter_by(username=args.username).first() + elif args.email: + user = sa_session.query(model.User).enable_eagerloads(False).filter_by(email=args.email).first() if not user: print('User not found') sys.exit(1) diff --git a/test/integration/test_scripts.py b/test/integration/test_scripts.py new file mode 100644 index 00000000000..c4ad3443c82 --- /dev/null +++ b/test/integration/test_scripts.py @@ -0,0 +1,168 @@ +"""Integration tests for various scripts in scripts/. +""" + +import json +import os +import subprocess +import tempfile +import unittest + +import yaml +from base import integration_util +from base.populators import DatasetPopulator + +from galaxy.util import galaxy_directory + + +def skip_unless_module(module): + available = True + try: + __import__(module) + except ImportError: + available = False + if available: + return lambda func: func + template = "Module %s could not be loaded, dependent test skipped." + return unittest.skip(template % module) + + +class ScriptsIntegrationTestCase(integration_util.IntegrationTestCase): + + def setUp(self): + super(ScriptsIntegrationTestCase, self).setUp() + self.dataset_populator = DatasetPopulator(self.galaxy_interactor) + self.config_dir = tempfile.mkdtemp() + + @classmethod + def handle_galaxy_config_kwds(cls, config): + cls._raw_config = config + + def test_helper(self): + history_id = self.dataset_populator.new_history() + dataset = self.dataset_populator.new_dataset(history_id, wait=True) + dataset_id = dataset["id"] + config_file = self.write_config_file() + output = self._scripts_check_output("helper.py", ["-c", config_file, "--decode-id", dataset_id]) + assert "Decoded " in output + + def test_cleanup(self): + history_id = self.dataset_populator.new_history() + delete_response = self.dataset_populator._delete("histories/%s" % history_id) + assert delete_response.status_code == 200 + assert delete_response.json()["purged"] is False + config_file = self.write_config_file() + output = self._scripts_check_output("cleanup_datasets/cleanup_datasets.py", ["-c", config_file, "--days", "0", "--purge_histories"]) + print(output) + history_response = self.dataset_populator._get("histories/%s" % history_id) + assert history_response.status_code == 200 + assert history_response.json()["purged"] is True, history_response.json() + + def test_pgcleanup(self): + self._skip_if_not_postgres() + + history_id = self.dataset_populator.new_history() + delete_response = self.dataset_populator._delete("histories/%s" % history_id) + assert delete_response.status_code == 200 + assert delete_response.json()["purged"] is False + config_file = self.write_config_file() + output = self._scripts_check_output("cleanup_datasets/pgcleanup.py", ["-c", config_file, "--older-than", "0", "--sequence", "purge_deleted_histories"]) + print(output) + history_response = self.dataset_populator._get("histories/%s" % history_id) + assert history_response.status_code == 200 + assert history_response.json()["purged"] is True, history_response.json() + + def test_set_user_disk_usage(self): + history_id = self.dataset_populator.new_history() + self.dataset_populator.new_dataset(history_id, wait=True) + config_file = self.write_config_file() + output = self._scripts_check_output("set_user_disk_usage.py", ["-c", config_file]) + # verify the script runs to completion without crashing + assert "100% complete" in output, output + + def test_set_dataset_sizes(self): + # TODO: change the size of the dataset and verify this works. + history_id = self.dataset_populator.new_history() + self.dataset_populator.new_dataset(history_id, wait=True) + config_file = self.write_config_file() + output = self._scripts_check_output("set_dataset_sizes.py", ["-c", config_file]) + # verify the script runs to completion without crashing + assert "Completed 100%" in output, output + + def test_populate_uuid(self): + history_id = self.dataset_populator.new_history() + self.dataset_populator.new_dataset(history_id, wait=True) + config_file = self.write_config_file() + output = self._scripts_check_output("cleanup_datasets/populate_uuid.py", ["-c", config_file]) + assert "Complete" in output + + def test_grt_export(self): + self._scripts_check_argparse_help("grt/export.py") + + history_id = self.dataset_populator.new_history() + self.dataset_populator.new_dataset(history_id, wait=True) + config_file = self.write_config_file() + grt_config_file = os.path.join(self.config_dir, "grt.yml") + with open(grt_config_file, "w") as f: + yaml.dump({"grt": {"shared_toolbox": True}, "sanitization": {"tools": []}, "tool_params": {}}, f) + self._scripts_check_output("grt/export.py", ["-c", config_file, "-g", grt_config_file, "-r", self.config_dir]) + report_files = os.listdir(self.config_dir) + json_files = [j for j in report_files if j.endswith(".json")] + assert len(json_files) == 1, "Expected one json report file in [%s]" % json_files + json_file = os.path.join(self.config_dir, json_files[0]) + with open(json_file, "r") as f: + export = json.load(f) + assert export["version"] == 1 + + def test_admin_cleanup_datasets(self): + self._scripts_check_argparse_help("cleanup_datasets/admin_cleanup_datasets.py") + + @skip_unless_module("flask_socketio") + def test_communication_server(self): + self._scripts_check_argparse_help("communication/communication_server.py") + + def test_secret_decoder_ring(self): + self._scripts_check_argparse_help("secret_decoder_ring.py") + + config_file = self.write_config_file() + output = self._scripts_check_output("secret_decoder_ring.py", ["-c", config_file, "encode", "1"]) + encoded_id = output.strip() + + output = self._scripts_check_output("secret_decoder_ring.py", ["-c", config_file, "decode", encoded_id]) + assert output.strip() == "1" + + def test_database_scripts(self): + self._scripts_check_argparse_help("create_db.py") + self._scripts_check_argparse_help("manage_db.py") + # TODO: test creating a smaller database - e.g. tool install database based on fresh + # config file. + + def test_runtime_stats(self): + self._skip_if_not_postgres() + self._scripts_check_argparse_help("runtime_stats.py") + + def _skip_if_not_postgres(self): + if not self._app.config.database_connection.startswith("post"): + raise unittest.SkipTest("Test only valid for postgres") + + def _scripts_check_argparse_help(self, script): + # Test imports and argparse repsonse to --help with 0 exit code. + output = self._scripts_check_output(script, ["--help"]) + # Test -h, --help in printed output message. + assert "-h, --help" in output + + def _scripts_check_output(self, script, args): + cwd = galaxy_directory() + cmd = ["python", os.path.join(cwd, "scripts", script)] + args + clean_env = { + "PATH": os.environ.get("PATH", None), + } # Don't let testing environment variables interfere with config. + return subprocess.check_output(cmd, cwd=cwd, env=clean_env) + + def write_config_file(self): + config_dir = self.config_dir + path = os.path.join(config_dir, "galaxy.yml") + self._test_driver.temp_directories.extend([config_dir]) + with open(path, "w") as f: + yaml.dump({"galaxy": self._raw_config}, f) + + return path