diff --git a/bin/bentoctl b/bin/bentoctl new file mode 100755 index 00000000..2749dd6b --- /dev/null +++ b/bin/bentoctl @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# bentoctl - Global wrapper to run bentoctl from anywhere +# +# This script allows you to invoke bentoctl from any directory. +# It changes to the BENTO_HOME directory and runs the actual bentoctl.bash script. + +# Determine the Bento installation directory +# If BENTO_HOME is set, use it; otherwise, use the script's parent directory +if [[ -z "${BENTO_HOME}" ]]; then + # Get the directory where this script is located (resolving symlinks) + SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")" + BENTO_HOME="$(dirname "$(dirname "$SCRIPT_PATH")")" +fi + +export BENTO_HOME + +# Validate that the directory exists +if [[ ! -d "${BENTO_HOME}" ]]; then + echo "Error: BENTO_HOME directory not found: ${BENTO_HOME}" >&2 + echo "Set BENTO_HOME environment variable to your bento installation path." >&2 + exit 1 +fi + +# Validate that bentoctl.bash exists +if [[ ! -f "${BENTO_HOME}/bentoctl.bash" ]]; then + echo "Error: bentoctl.bash not found in ${BENTO_HOME}" >&2 + exit 1 +fi + +# Change to the Bento directory and execute the script +cd "${BENTO_HOME}" || exit 1 + +# Activate virtual environment if it exists +if [[ -f "${BENTO_HOME}/env/bin/activate" ]]; then + source "${BENTO_HOME}/env/bin/activate" +fi + +# Execute the actual bentoctl script with all arguments +exec ./bentoctl.bash "$@" diff --git a/completions/_bentoctl b/completions/_bentoctl new file mode 100644 index 00000000..e243eb00 --- /dev/null +++ b/completions/_bentoctl @@ -0,0 +1,176 @@ +#compdef bentoctl + +# Zsh completion script for bentoctl +# Place this file in a directory in your $fpath (e.g., ~/.zsh/completions/) +# Or source it directly in your .zshrc + +_bentoctl() { + local curcontext="$curcontext" state line + typeset -A opt_args + + local -a commands + commands=( + # Initialization commands + 'init-dirs:Initialize directories for BentoV2 structure' + 'init-auth:Configure authentication with Keycloak' + 'init-certs:Initialize SSL certificates for gateway domains' + 'init-docker:Initialize Docker configuration and networks' + 'init-web:Initialize web app (public or private) files' + 'init-all:Initialize certs, directories, Docker networks, and web portals' + 'init-config:Initialize configuration files for specific services' + 'init-cbioportal:Initialize cBioPortal if enabled' + + # Database commands + 'pg-dump:Dump contents of all Postgres databases to a directory' + 'pg-load:Load contents of all Postgres databases from a directory' + + # Utility commands + 'convert-pheno:Convert a Phenopacket V1 JSON document to V2' + 'conv:Convert a Phenopacket V1 JSON document to V2' + + # Service commands + 'run:Run Bento services' + 'start:Run Bento services' + 'up:Run Bento services' + 'stop:Stop Bento services' + 'down:Stop Bento services' + 'restart:Restart Bento services' + 'clean:Clean services' + 'work-on:Work on a service in local development mode' + 'dev:Work on a service in local development mode' + 'develop:Work on a service in local development mode' + 'local:Work on a service in local development mode' + 'prebuilt:Switch a service back to prebuilt mode' + 'pre-built:Switch a service back to prebuilt mode' + 'prod:Switch a service back to prebuilt mode' + 'mode:See service production/development mode' + 'state:See service production/development mode' + 'status:See service production/development mode' + 'pull:Pull the image for a specific service' + 'shell:Run a shell inside a running service container' + 'sh:Run a shell inside a running service container' + 'run-as-shell:Run a shell inside a stopped service container' + 'logs:Check logs for a service' + 'compose-config:Generate Compose config YAML' + ) + + # Common services for service-based commands + local -a all_services + all_services=( + 'all:All services' + 'aggregation:Federation/aggregation service' + 'auth:Authentication service' + 'auth-db:Authentication database' + 'authz:Authorization service' + 'authz-db:Authorization database' + 'beacon:Beacon service' + 'cbioportal:cBioPortal service' + 'drs:Data Repository Service' + 'drop-box:Drop box service' + 'event-relay:Event relay service' + 'gateway:Gateway/proxy service' + 'gohan:Gohan services (api + elasticsearch)' + 'gohan-api:Gohan API service' + 'gohan-elasticsearch:Gohan Elasticsearch' + 'katsu:Metadata service' + 'katsu-db:Metadata database' + 'notification:Notification service' + 'public:Public frontend' + 'redis:Redis cache' + 'reference:Reference service' + 'reference-db:Reference database' + 'service-registry:Service registry' + 'web:Web frontend' + 'wes:Workflow Execution Service' + ) + + # Services that can be worked on (have repositories) + local -a workable_services + workable_services=( + 'aggregation:Federation/aggregation service' + 'authz:Authorization service' + 'beacon:Beacon service' + 'drs:Data Repository Service' + 'drop-box:Drop box service' + 'event-relay:Event relay service' + 'gateway:Gateway service' + 'gohan-api:Gohan API service' + 'katsu:Metadata service' + 'notification:Notification service' + 'public:Public frontend' + 'reference:Reference service' + 'service-registry:Service registry' + 'web:Web frontend' + 'wes:Workflow Execution Service' + ) + + _arguments -C \ + '(-d --debug)'{-d,--debug}'[Enable remote Python debugging]' \ + '1: :->command' \ + '*:: :->args' + + case $state in + command) + _describe -t commands 'bentoctl command' commands + ;; + args) + case $line[1] in + run|start|up|stop|down|restart|clean|logs|pull|mode|state|status) + _arguments \ + '1: :->service' \ + '(-p --pull)'{-p,--pull}'[Pull the service image first]' \ + '(-f --follow)'{-f,--follow}'[Follow logs]' + if [[ $state == service ]]; then + _describe -t services 'service' all_services + fi + ;; + work-on|dev|develop|local) + _arguments '1: :->service' + if [[ $state == service ]]; then + _describe -t services 'service' workable_services + fi + ;; + prebuilt|pre-built|prod) + _arguments '1: :->service' + if [[ $state == service ]]; then + _describe -t services 'service' all_services + fi + ;; + shell|sh|run-as-shell) + _arguments \ + '1: :->service' \ + '(-s --shell)'{-s,--shell}'[Shell to use]: :(/bin/bash /bin/sh)' + if [[ $state == service ]]; then + _describe -t services 'service' all_services + fi + ;; + init-web) + _arguments \ + '1: :(public private)' \ + '(-f --force)'{-f,--force}'[Overwrite existing files]' + ;; + init-config) + _arguments \ + '1: :(katsu beacon beacon-network)' \ + '(-f --force)'{-f,--force}'[Overwrite existing config]' + ;; + init-certs) + _arguments '(-f --force)'{-f,--force}'[Remove and recreate certificates]' + ;; + pg-dump|pg-load) + _arguments '1:directory:_directories' + ;; + convert-pheno|conv) + _arguments \ + '1:source file:_files -g "*.json"' \ + '2:target file:_files -g "*.json"' + ;; + compose-config) + _arguments '--services[List services seen by Compose]' + ;; + esac + ;; + esac +} + +_bentoctl "$@" diff --git a/completions/bentoctl.bash b/completions/bentoctl.bash new file mode 100644 index 00000000..b794f708 --- /dev/null +++ b/completions/bentoctl.bash @@ -0,0 +1,72 @@ +# Bash completion script for bentoctl +# Place this file in /etc/bash_completion.d/ or ~/.local/share/bash-completion/completions/ + +_bentoctl_completions() { + local cur prev words cword + _init_completion || return + + local commands="init-dirs init-auth init-certs init-docker init-web init-all init-config init-cbioportal pg-dump pg-load convert-pheno conv run start up stop down restart clean work-on dev develop local prebuilt pre-built prod mode state status pull shell sh run-as-shell logs compose-config" + + # All services (for most commands) + local all_services="all aggregation auth auth-db authz authz-db beacon cbioportal drs drop-box event-relay gateway gohan gohan-api gohan-elasticsearch katsu katsu-db notification public redis reference reference-db service-registry web wes" + + # Services that can be worked on (have repositories) + local workable_services="aggregation authz beacon drs drop-box event-relay gateway gohan-api katsu notification public reference service-registry web wes" + + if [[ ${cword} -eq 1 ]]; then + COMPREPLY=($(compgen -W "${commands}" -- "${cur}")) + return + fi + + case "${prev}" in + run|start|up|stop|down|restart|clean|logs|pull|mode|state|status) + COMPREPLY=($(compgen -W "${all_services}" -- "${cur}")) + ;; + work-on|dev|develop|local) + COMPREPLY=($(compgen -W "${workable_services}" -- "${cur}")) + ;; + prebuilt|pre-built|prod) + COMPREPLY=($(compgen -W "${all_services}" -- "${cur}")) + ;; + shell|sh|run-as-shell) + COMPREPLY=($(compgen -W "${all_services}" -- "${cur}")) + ;; + init-web) + COMPREPLY=($(compgen -W "public private" -- "${cur}")) + ;; + init-config) + COMPREPLY=($(compgen -W "katsu beacon beacon-network" -- "${cur}")) + ;; + pg-dump|pg-load) + _filedir -d + ;; + convert-pheno|conv) + _filedir + ;; + -s|--shell) + COMPREPLY=($(compgen -W "/bin/bash /bin/sh" -- "${cur}")) + ;; + *) + # Handle flags + case "${words[1]}" in + run|start|up|restart) + COMPREPLY=($(compgen -W "-p --pull" -- "${cur}")) + ;; + logs) + COMPREPLY=($(compgen -W "-f --follow" -- "${cur}")) + ;; + init-certs|init-web|init-config) + COMPREPLY=($(compgen -W "-f --force" -- "${cur}")) + ;; + shell|sh|run-as-shell) + COMPREPLY=($(compgen -W "-s --shell" -- "${cur}")) + ;; + compose-config) + COMPREPLY=($(compgen -W "--services" -- "${cur}")) + ;; + esac + ;; + esac +} + +complete -F _bentoctl_completions bentoctl diff --git a/docs/global-install.md b/docs/global-install.md new file mode 100644 index 00000000..e2898e25 --- /dev/null +++ b/docs/global-install.md @@ -0,0 +1,150 @@ +# Global Installation of bentoctl + +This guide explains how to install `bentoctl` so it can be run from any directory with shell autocompletions. + +## Installation Steps + +### 1. Install Python Dependencies + +```bash +cd /path/to/bento +source env/bin/activate +pip install -r requirements.txt +``` + +### 2. Create Global Symlink + +Choose one of the following locations: + +**User-local (recommended, no sudo required):** + +```bash +mkdir -p ~/.local/bin +ln -sf /path/to/bento/bin/bentoctl ~/.local/bin/bentoctl +``` + +**System-wide:** + +```bash +sudo ln -sf /path/to/bento/bin/bentoctl /usr/local/bin/bentoctl +``` + +### 3. Ensure PATH is Set + +Add to your shell configuration file (`~/.zshrc` or `~/.bashrc`): + +```bash +export PATH="$HOME/.local/bin:$PATH" +``` + +### 4. Set Up Shell Completions + +#### Zsh (with argcomplete - recommended) + +Add to `~/.zshrc`: + +```zsh +# bentoctl completions +autoload -U bashcompinit && bashcompinit +eval "$(register-python-argcomplete bentoctl)" +``` + +#### Zsh (with static completions - alternative) + +Add to `~/.zshrc`: + +```zsh +# Add completions directory to fpath +fpath=(/path/to/bento/completions $fpath) +autoload -Uz compinit && compinit +``` + +#### Bash (with argcomplete - recommended) + +Add to `~/.bashrc`: + +```bash +# bentoctl completions +eval "$(register-python-argcomplete bentoctl)" +``` + +#### Bash (with static completions - alternative) + +Copy the completion script to the bash-completion directory: + +```bash +# System-wide +sudo cp /path/to/bento/completions/bentoctl.bash /etc/bash_completion.d/bentoctl + +# Or user-local +mkdir -p ~/.local/share/bash-completion/completions +cp /path/to/bento/completions/bentoctl.bash ~/.local/share/bash-completion/completions/bentoctl +``` + +### 5. Reload Shell + +```bash +source ~/.zshrc # or ~/.bashrc +``` + +## Usage + +Once installed, you can run `bentoctl` from any directory: + +```bash +# Run from anywhere +bentoctl run all +bentoctl logs katsu -f +bentoctl mode + +# Tab completion works for commands and services +bentoctl # Shows all commands +bentoctl run # Shows all services +bentoctl logs - # Shows flags +``` + +## Multiple Installations + +If you have multiple Bento installations (e.g., dev and staging), you can set `BENTO_HOME` to switch between them: + +```bash +# Use a specific installation +BENTO_HOME=/path/to/staging bentoctl run all + +# Or create aliases +alias bentoctl-dev='BENTO_HOME=/path/to/dev bentoctl' +alias bentoctl-staging='BENTO_HOME=/path/to/staging bentoctl' +``` + +## Troubleshooting + +### Command not found + +Ensure `~/.local/bin` is in your PATH: + +```bash +echo $PATH | grep -q ".local/bin" && echo "OK" || echo "Add ~/.local/bin to PATH" +``` + +### Completions not working + +1. Verify argcomplete is installed: + + ```bash + pip show argcomplete + ``` + +2. For zsh, ensure bashcompinit is loaded before the eval command. + +3. Try regenerating completions: + ```bash + source ~/.zshrc + ``` + +### Wrong Bento directory + +The wrapper script auto-detects `BENTO_HOME` from the symlink location. To override: + +```bash +export BENTO_HOME=/path/to/your/bento +``` diff --git a/py_bentoctl/entry.py b/py_bentoctl/entry.py index a465f6f8..e0823618 100644 --- a/py_bentoctl/entry.py +++ b/py_bentoctl/entry.py @@ -3,19 +3,98 @@ import subprocess import sys +import argcomplete + from abc import ABC, abstractmethod from pathlib import Path - -from .auth_helper import init_auth -from . import config as c -from . import db_helpers as dh -from . import feature_helpers as fh -from . import services as s -from . import other_helpers as oh -from . import utils as u - from typing import Optional, Tuple, Type +# Lazy imports - these modules are heavy and slow down tab completion +# They will be imported on first use in the functions that need them +_config = None +_db_helpers = None +_feature_helpers = None +_services = None +_other_helpers = None +_utils = None +_auth_helper = None + + +def _get_config(): + global _config + if _config is None: + from . import config as c + _config = c + return _config + + +def _get_db_helpers(): + global _db_helpers + if _db_helpers is None: + from . import db_helpers as dh + _db_helpers = dh + return _db_helpers + + +def _get_feature_helpers(): + global _feature_helpers + if _feature_helpers is None: + from . import feature_helpers as fh + _feature_helpers = fh + return _feature_helpers + + +def _get_services(): + global _services + if _services is None: + from . import services as s + _services = s + return _services + + +def _get_other_helpers(): + global _other_helpers + if _other_helpers is None: + from . import other_helpers as oh + _other_helpers = oh + return _other_helpers + + +def _get_utils(): + global _utils + if _utils is None: + from . import utils as u + _utils = u + return _utils + + +def _get_auth_helper(): + global _auth_helper + if _auth_helper is None: + from .auth_helper import init_auth + _auth_helper = init_auth + return _auth_helper + + +# Static service lists for fast completions +# (these don't require loading config) +# These are used during tab completion to avoid loading the heavy config module +STATIC_ALL_SERVICES = ( + "all", "aggregation", "auth", "auth-db", "authz", "authz-db", "beacon", + "cbioportal", "drs", "drop-box", "event-relay", "gateway", "gohan", + "gohan-api", "gohan-elasticsearch", "katsu", "katsu-db", "notification", + "public", "redis", "reference", "reference-db", "service-registry", + "web", "wes" +) + +STATIC_WORKABLE_SERVICES = ( + "aggregation", "authz", "beacon", "drs", "drop-box", "event-relay", + "gateway", "gohan-api", "katsu", "notification", "public", "reference", + "service-registry", "web", "wes" +) + +STATIC_MULTI_SERVICE_PREFIXES = ("gohan",) + class SubCommand(ABC): @@ -33,7 +112,7 @@ class InitAuth(SubCommand): @staticmethod def exec(args): - init_auth(docker_client=u.get_docker_client()) + _get_auth_helper()(docker_client=_get_utils().get_docker_client()) class Run(SubCommand): @@ -41,17 +120,22 @@ class Run(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, nargs="?", default=c.SERVICE_LITERAL_ALL, choices=c.DOCKER_COMPOSE_SERVICES_PLUS_ALL, + "service", type=str, nargs="?", default="all", + choices=STATIC_ALL_SERVICES, help="Service to run, or 'all' to run everything.") - sp.add_argument("--pull", "-p", action="store_true", help="Try to pull the corresponding service image first.") + sp.add_argument( + "--pull", "-p", action="store_true", + help="Try to pull the corresponding service image first.") @staticmethod def exec(args): + c = _get_config() + s = _get_services() if args.pull: s.pull_service(args.service) if c.BENTO_FEATURE_CBIOPORTAL.enabled: - fh.init_cbioportal() + _get_feature_helpers().init_cbioportal() s.run_service(args.service) @@ -61,12 +145,13 @@ class Stop(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, nargs="?", default=c.SERVICE_LITERAL_ALL, choices=c.DOCKER_COMPOSE_SERVICES_PLUS_ALL, + "service", type=str, nargs="?", default="all", + choices=STATIC_ALL_SERVICES, help="Service to stop, or 'all' to stop everything.") @staticmethod def exec(args): - s.stop_service(args.service) + _get_services().stop_service(args.service) class Restart(SubCommand): @@ -74,7 +159,8 @@ class Restart(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, nargs="?", default=c.SERVICE_LITERAL_ALL, choices=c.DOCKER_COMPOSE_SERVICES_PLUS_ALL, + "service", type=str, nargs="?", default="all", + choices=STATIC_ALL_SERVICES, help="Service to restart, or 'all' to restart everything.") sp.add_argument( "--pull", "-p", action="store_true", @@ -82,6 +168,7 @@ def add_args(sp): @staticmethod def exec(args): + s = _get_services() if args.pull: s.pull_service(args.service) s.restart_service(args.service) @@ -92,23 +179,26 @@ class Clean(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, nargs="?", default=c.SERVICE_LITERAL_ALL, choices=c.DOCKER_COMPOSE_SERVICES_PLUS_ALL, + "service", type=str, nargs="?", default="all", + choices=STATIC_ALL_SERVICES, help="Service to clean, or 'all' to clean everything.") @staticmethod def exec(args): - s.clean_service(args.service) + _get_services().clean_service(args.service) class WorkOn(SubCommand): @staticmethod def add_args(sp): - sp.add_argument("service", type=str, choices=tuple(c.BENTO_SERVICES_DATA.keys()), help="Service to work on.") + sp.add_argument( + "service", type=str, choices=STATIC_WORKABLE_SERVICES, + help="Service to work on.") @staticmethod def exec(args): - s.work_on_service(args.service) + _get_services().work_on_service(args.service) class Prebuilt(SubCommand): @@ -116,12 +206,12 @@ class Prebuilt(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, choices=c.BENTO_SERVICES_COMPOSE_IDS_PLUS_ALL, - help="Service to switch to pre-built mode (will use an entirely pre-built image with code).") + "service", type=str, choices=STATIC_ALL_SERVICES, + help="Service to switch to pre-built mode.") @staticmethod def exec(args): - s.prod_service(args.service) + _get_services().prod_service(args.service) class Mode(SubCommand): @@ -129,13 +219,13 @@ class Mode(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, nargs="?", default=c.SERVICE_LITERAL_ALL, - choices=c.BENTO_SERVICES_COMPOSE_IDS_PLUS_ALL, + "service", type=str, nargs="?", default="all", + choices=STATIC_ALL_SERVICES, help="Displays the current mode of the service(s).") @staticmethod def exec(args): - s.mode_service(args.service) + _get_services().mode_service(args.service) class Pull(SubCommand): @@ -143,20 +233,25 @@ class Pull(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, nargs="?", default=c.SERVICE_LITERAL_ALL, - choices=(*c.DOCKER_COMPOSE_SERVICES, *c.MULTI_SERVICE_PREFIXES, c.SERVICE_LITERAL_ALL), + "service", type=str, nargs="?", default="all", + choices=STATIC_ALL_SERVICES, help="Service to pull image for.") @staticmethod def exec(args): - s.pull_service(args.service) + _get_services().pull_service(args.service) class Shell(SubCommand): @staticmethod def add_args(sp): - sp.add_argument("service", type=str, choices=c.DOCKER_COMPOSE_SERVICES, help="Service to enter a shell for.") + # Exclude "all" from shell choices - can only shell into one service + shell_services = tuple( + s for s in STATIC_ALL_SERVICES if s != "all") + sp.add_argument( + "service", type=str, choices=shell_services, + help="Service to enter a shell for.") sp.add_argument( "--shell", "-s", default="/bin/bash", type=str, choices=("/bin/bash", "/bin/sh"), @@ -164,14 +259,19 @@ def add_args(sp): @staticmethod def exec(args): - s.enter_shell_for_service(args.service, args.shell) + _get_services().enter_shell_for_service(args.service, args.shell) class RunShell(SubCommand): @staticmethod def add_args(sp): - sp.add_argument("service", type=str, choices=c.DOCKER_COMPOSE_SERVICES, help="Service to run a shell for.") + # Exclude "all" from shell choices - can only shell into one service + shell_services = tuple( + s for s in STATIC_ALL_SERVICES if s != "all") + sp.add_argument( + "service", type=str, choices=shell_services, + help="Service to run a shell for.") sp.add_argument( "--shell", "-s", default="/bin/bash", type=str, choices=("/bin/bash", "/bin/sh"), @@ -179,7 +279,7 @@ def add_args(sp): @staticmethod def exec(args): - s.run_as_shell_for_service(args.service, args.shell) + _get_services().run_as_shell_for_service(args.service, args.shell) class Logs(SubCommand): @@ -187,26 +287,29 @@ class Logs(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, nargs="?", default=c.SERVICE_LITERAL_ALL, choices=c.DOCKER_COMPOSE_SERVICES_PLUS_ALL, + "service", type=str, nargs="?", default="all", + choices=STATIC_ALL_SERVICES, help="Service to check logs of.") sp.add_argument( "--follow", "-f", action="store_true", - help="Whether to follow the logs (keep them open and show new entries).") + help="Follow logs (keep open and show new entries).") @staticmethod def exec(args): - s.logs_service(args.service, args.follow) + _get_services().logs_service(args.service, args.follow) class ComposeConfig(SubCommand): @staticmethod def add_args(sp): - sp.add_argument("--services", action="store_true", help="List services seen by Compose.") + sp.add_argument( + "--services", action="store_true", + help="List services seen by Compose.") @staticmethod def exec(args): - s.compose_config(args.services) + _get_services().compose_config(args.services) # Other helpers @@ -215,7 +318,7 @@ class InitDirs(SubCommand): @staticmethod def exec(args): - oh.init_dirs() + _get_other_helpers().init_dirs() class InitCerts(SubCommand): @@ -224,18 +327,19 @@ class InitCerts(SubCommand): def add_args(sp): sp.add_argument( "--force", "-f", action="store_true", - help="Removes all previously created certs and keys before creating new ones.") + help="Remove existing certs and keys before creating new ones.") @staticmethod def exec(args): - oh.init_self_signed_certs(args.force) + _get_other_helpers().init_self_signed_certs(args.force) class InitDocker(SubCommand): @staticmethod def exec(args): - oh.init_docker(client=u.get_docker_client()) + client = _get_utils().get_docker_client() + _get_other_helpers().init_docker(client=client) class InitWeb(SubCommand): @@ -243,21 +347,22 @@ class InitWeb(SubCommand): @staticmethod def add_args(sp): sp.add_argument( - "service", type=str, choices=["public", "private"], help="Prepares the web applications for deployment.") + "service", type=str, choices=["public", "private"], + help="Prepares the web applications for deployment.") sp.add_argument( "--force", "-f", action="store_true", help="Overwrites any existing branding/translation/etc.") @staticmethod def exec(args): - oh.init_web(args.service, args.force) + _get_other_helpers().init_web(args.service, args.force) class InitCBioPortal(SubCommand): @staticmethod def exec(args): - fh.init_cbioportal() + _get_feature_helpers().init_cbioportal() class InitAll(SubCommand): @@ -268,13 +373,14 @@ def add_args(sp): @staticmethod def exec(args): + oh = _get_other_helpers() oh.init_self_signed_certs(force=False) oh.init_dirs() - oh.init_docker(client=u.get_docker_client()) + oh.init_docker(client=_get_utils().get_docker_client()) oh.init_web("private", force=False) oh.init_web("public", force=False) - if c.BENTO_FEATURE_CBIOPORTAL.enabled: - fh.init_cbioportal() + if _get_config().BENTO_FEATURE_CBIOPORTAL.enabled: + _get_feature_helpers().init_cbioportal() class InitConfig(SubCommand): @@ -293,45 +399,52 @@ def add_args(sp): @staticmethod def exec(args): - oh.init_config(args.service, args.force) + _get_other_helpers().init_config(args.service, args.force) class PgDump(SubCommand): @staticmethod def add_args(sp): - sp.add_argument("dir", type=Path, help="Path to a new directory for the database dump files.") + sp.add_argument( + "dir", type=Path, + help="Path to a new directory for the database dump files.") @staticmethod def exec(args): - dh.pg_dump(args.dir) + _get_db_helpers().pg_dump(args.dir) class PgLoad(SubCommand): @staticmethod def add_args(sp): - sp.add_argument("dir", type=Path, help="Path to a directory to load the database dump files from.") + sp.add_argument( + "dir", type=Path, + help="Path to a directory to load database dump files from.") @staticmethod def exec(args): - dh.pg_load(args.dir) + _get_db_helpers().pg_load(args.dir) class ConvertPhenopacket(SubCommand): @staticmethod def add_args(sp): - sp.add_argument("source", type=str, help="Source Phenopackets V1 JSON document to convert") - sp.add_argument("target", nargs="?", default=None, type=str, - help="Optional target file path where to place the converted Phenopackets") + sp.add_argument( + "source", type=str, + help="Source Phenopackets V1 JSON document to convert") + sp.add_argument( + "target", nargs="?", default=None, type=str, + help="Target file path for converted Phenopackets") @staticmethod def exec(args): # import asyncio # TODO: re-convert to async # asyncio.run(oh.convert_phenopacket_file(args.source, args.target)) - oh.convert_phenopacket_file(args.source, args.target) + _get_other_helpers().convert_phenopacket_file(args.source, args.target) def main(args: Optional[list[str]] = None) -> int: @@ -349,59 +462,85 @@ def main(args: Optional[list[str]] = None) -> int: print("break on this line") parser = argparse.ArgumentParser( - description="Tools for managing a Bento deployment (development or production).", + description="Tools for managing a Bento deployment.", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--debug", "-d", action="store_true") subparsers = parser.add_subparsers() - def _add_subparser(arg: str, help_text: str, subcommand: Type[SubCommand], aliases: Tuple[str, ...] = ()): - subparser = subparsers.add_parser(arg, help=help_text, aliases=aliases) + def _add_subparser( + arg: str, + help_text: str, + subcommand: Type[SubCommand], + aliases: Tuple[str, ...] = () + ): + subparser = subparsers.add_parser( + arg, help=help_text, aliases=aliases) subparser.set_defaults(func=subcommand.exec) subcommand.add_args(subparser) # Generic initialization helpers - _add_subparser("init-dirs", "Initialize directories for BentoV2 structure.", InitDirs) - _add_subparser("init-auth", "Configure authentication for BentoV2 with a local Keycloak instance.", InitAuth) - _add_subparser("init-certs", "Initialize ssl certificates for BentoV2 gateway domains.", InitCerts) - _add_subparser("init-docker", "Initialize Docker configuration & networks.", InitDocker) - _add_subparser("init-web", "Init web app (public or private) files", InitWeb) + _add_subparser( + "init-dirs", "Initialize directories for BentoV2.", InitDirs) + _add_subparser( + "init-auth", "Configure authentication with Keycloak.", InitAuth) + _add_subparser( + "init-certs", "Initialize SSL certificates for gateway.", InitCerts) + _add_subparser( + "init-docker", "Initialize Docker config & networks.", InitDocker) + _add_subparser( + "init-web", "Init web app (public or private) files", InitWeb) _add_subparser( "init-all", - "Initialize certs, directories, Docker networks, secrets, and web portals. DOES NOT initialize Keycloak.", + "Initialize certs, dirs, Docker, web. NOT Keycloak.", InitAll) - _add_subparser("init-config", "Initialize configuration files for specific services.", InitConfig) + _add_subparser( + "init-config", "Initialize config files for services.", InitConfig) # Feature-specific initialization commands - _add_subparser("init-cbioportal", "Initialize cBioPortal if enabled", InitCBioPortal) + _add_subparser( + "init-cbioportal", "Initialize cBioPortal if enabled", InitCBioPortal) # Database commands # - Postgres: - _add_subparser("pg-dump", "Dump contents of all Postgres database containers to a directory.", PgDump) - _add_subparser("pg-load", "Load contents of all Postgres database containers from a directory.", PgLoad) + _add_subparser( + "pg-dump", "Dump Postgres databases to directory.", PgDump) + _add_subparser( + "pg-load", "Load Postgres databases from directory.", PgLoad) # Other commands - _add_subparser("convert-pheno", - "Convert a Phenopacket V1 JSON document to V2", ConvertPhenopacket, aliases=("conv",)) + _add_subparser( + "convert-pheno", "Convert Phenopacket V1 to V2", + ConvertPhenopacket, aliases=("conv",)) # Service commands - _add_subparser("run", "Run Bento services.", Run, aliases=("start", "up")) - _add_subparser("stop", "Stop Bento services.", Stop, aliases=("down",)) + _add_subparser( + "run", "Run Bento services.", Run, aliases=("start", "up")) + _add_subparser( + "stop", "Stop Bento services.", Stop, aliases=("down",)) _add_subparser("restart", "Restart Bento services.", Restart) _add_subparser("clean", "Clean services.", Clean) _add_subparser( - "work-on", "Work on a specific service in a local development mode.", WorkOn, + "work-on", "Work on service in local dev mode.", WorkOn, aliases=("dev", "develop", "local")) - _add_subparser("prebuilt", "Switch a service back to prebuilt mode.", Prebuilt, aliases=("pre-built", "prod")) _add_subparser( - "mode", "See if a service (or which services) are in production/development mode.", Mode, + "prebuilt", "Switch service to prebuilt mode.", Prebuilt, + aliases=("pre-built", "prod")) + _add_subparser( + "mode", "See service mode (local/prebuilt).", Mode, aliases=("state", "status")) - _add_subparser("pull", "Pull the image for a specific service.", Pull) - _add_subparser("shell", "Run a shell inside an already-running service container.", Shell, aliases=("sh",)) - _add_subparser("run-as-shell", "Run a shell inside a stopped service container.", RunShell) + _add_subparser( + "pull", "Pull the image for a service.", Pull) + _add_subparser( + "shell", "Shell into running service container.", Shell, + aliases=("sh",)) + _add_subparser( + "run-as-shell", "Shell into stopped service container.", RunShell) _add_subparser("logs", "Check logs for a service.", Logs) - _add_subparser("compose-config", "Generate Compose config YAML.", ComposeConfig) + _add_subparser( + "compose-config", "Generate Compose config YAML.", ComposeConfig) + argcomplete.autocomplete(parser) p_args = parser.parse_args(args) if not getattr(p_args, "func", None): diff --git a/requirements.txt b/requirements.txt index 802dde28..c06a09f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ tqdm==4.67.1 typing_extensions==4.15.0 urllib3==2.5.0 websocket-client==1.9.0 +argcomplete==3.5.3