diff --git a/bridge/cli/bridge.py b/bridge/cli/bridge.py index 0d7b015..e9747c4 100644 --- a/bridge/cli/bridge.py +++ b/bridge/cli/bridge.py @@ -1,6 +1,14 @@ import argparse -from bridge.cli.init import initialize_platform +from bridge.cli.db import open_database_shell +from bridge.cli.init import initialize +from bridge.cli.redis import open_redis_shell +from bridge.framework import Framework + + +def detect_framework() -> Framework: + # TODO: auto-detect framework (assuming Django) + return Framework.DJANGO def main(): @@ -8,7 +16,7 @@ def main(): parser = argparse.ArgumentParser(prog="bridge") # TODO: tie this version output to the version in pyproject.toml parser.add_argument("--version", action="version", version="%(prog)s 0.0.22") - subparsers = parser.add_subparsers(dest="command", help="sub-command help") + subparsers = parser.add_subparsers(dest="command") # Parser for 'init' command init_parser = subparsers.add_parser( @@ -19,25 +27,34 @@ def main(): help="Platform where you want to deploy this app", choices=["render", "railway", "heroku"], ) - init_parser.add_argument( - "--wsgi-path", - help="Path to your WSGI application callable (ex: myapp.wsgi:application)", - required=False, - ) - init_parser.add_argument( - "--asgi-path", - help="Path to your ASGI application callable (ex: myapp.asgi:application)", - required=False, - ) + + # Parser for db + db_parser = subparsers.add_parser("db", help="Interact with the database") + db_subparsers = db_parser.add_subparsers(dest="db_command") + db_subparsers.add_parser("shell", help="Open a database shell (psql)") + + # Parser for redis + redis_parser = subparsers.add_parser("redis", help="Interact with Redis") + redis_subparsers = redis_parser.add_subparsers(dest="redis_command") + redis_subparsers.add_parser("shell", help="Open a Redis shell (redis-cli)") # Parse the arguments args = parser.parse_args() + framework = detect_framework() + # TODO: pattern for additional config from the CLI if args.command == "init": - # Additional validation for the arguments - if args.command == "init" and args.wsgi_path and args.asgi_path: - parser.error("Both WSGI and ASGI paths cannot be provided") - initialize_platform(args.init_platform) + initialize(framework=framework, platform=args.init_platform) + elif args.command == "db": + if args.db_command == "shell": + open_database_shell() + else: + db_parser.print_help() + elif args.command == "redis": + if args.redis_command == "shell": + open_redis_shell() + else: + redis_parser.print_help() else: parser.print_help() diff --git a/bridge/cli/db/__init__.py b/bridge/cli/db/__init__.py new file mode 100644 index 0000000..0067ee0 --- /dev/null +++ b/bridge/cli/db/__init__.py @@ -0,0 +1,10 @@ +import docker + +from bridge.service.postgres import PostgresService + + +def open_database_shell(): + client = docker.from_env() + postgres_service = PostgresService(client=client) + postgres_service.start() + postgres_service.shell() diff --git a/bridge/cli/init/__init__.py b/bridge/cli/init/__init__.py index f9c35e8..11493b7 100644 --- a/bridge/cli/init/__init__.py +++ b/bridge/cli/init/__init__.py @@ -1,13 +1,14 @@ from bridge.cli.errors import ActionCancelledError from bridge.cli.init.render import build_render_init_config, initialize_render_platform from bridge.console import log_info, log_task +from bridge.framework import Framework -def initialize_platform(platform: str): +def initialize(framework: Framework, platform: str): if platform == "render": # Build config outside of log_task to avoid TUI interaction with status spinner try: - config = build_render_init_config() + config = build_render_init_config(framework=framework) except ActionCancelledError as e: log_info(str(e)) return diff --git a/bridge/cli/init/render.py b/bridge/cli/init/render.py index 0cb1415..992216a 100644 --- a/bridge/cli/init/render.py +++ b/bridge/cli/init/render.py @@ -18,7 +18,7 @@ ) from bridge.cli.init.templates.deploy_to_render_button import button_exists_in_content from bridge.console import console, log_warning -from bridge.framework.base import Framework +from bridge.framework import Framework from bridge.utils.filesystem import ( resolve_dot_bridge, resolve_project_dir, @@ -26,11 +26,6 @@ ) -def detect_framework() -> Framework: - # TODO: auto-detect framework (assuming Django) - return Framework.DJANGO - - def detect_django_settings_module(project_name: str = "") -> str: settings_path = Path(project_name) / "settings.py" if os.path.exists(settings_path): @@ -99,10 +94,9 @@ def script_dir(self) -> str: return f"bridge-{self.framework.value}-render" -def build_render_init_config() -> RenderPlatformInitConfig: +def build_render_init_config(framework: Framework) -> RenderPlatformInitConfig: # NOTE: this method may request user input directly on the CLI # to determine configuration when it cannot be auto-detected - framework = detect_framework() project_name = resolve_project_dir().name app_path = detect_application_callable(project_name=project_name) bridge_path = resolve_dot_bridge() diff --git a/bridge/cli/redis/__init__.py b/bridge/cli/redis/__init__.py new file mode 100644 index 0000000..cf7a4cc --- /dev/null +++ b/bridge/cli/redis/__init__.py @@ -0,0 +1,10 @@ +import docker + +from bridge.service.redis import RedisService + + +def open_redis_shell(): + client = docker.from_env() + postgres_service = RedisService(client=client) + postgres_service.start() + postgres_service.shell() diff --git a/bridge/framework/__init__.py b/bridge/framework/__init__.py index e69de29..5d7c9e2 100644 --- a/bridge/framework/__init__.py +++ b/bridge/framework/__init__.py @@ -0,0 +1,3 @@ +from bridge.framework.base import Framework + +__all__ = ["Framework"] diff --git a/bridge/service/postgres.py b/bridge/service/postgres.py index 71ed2da..17fbe64 100644 --- a/bridge/service/postgres.py +++ b/bridge/service/postgres.py @@ -1,3 +1,4 @@ +import os from time import sleep from typing import Optional, Union @@ -58,3 +59,25 @@ def ensure_ready(self): return except psycopg.OperationalError: sleep(0.1) + + def shell(self): + # Open a shell to the Postgres container + # NOTE: This entirely replaces the currently running process! + os.execvp( + "docker", + [ + "docker", + "exec", + "-it", + self.config.name, + "psql", + "-U", + self.config.environment.POSTGRES_USER, + "-d", + self.config.environment.POSTGRES_DB, + "-h", + self.config.environment.POSTGRES_HOST, + "-p", + self.config.environment.POSTGRES_PORT, + ], + ) diff --git a/bridge/service/redis.py b/bridge/service/redis.py index c4f5d22..d9b7e06 100644 --- a/bridge/service/redis.py +++ b/bridge/service/redis.py @@ -1,3 +1,4 @@ +import os from time import sleep from typing import Optional @@ -38,3 +39,8 @@ def ensure_ready(self): return # Redis is ready and responding except redis.ConnectionError: sleep(0.1) + + def shell(self): + # Open a shell to the Redis container + # NOTE: This entirely replaces the currently running process! + os.execvp("docker", ["docker", "exec", "-it", self.config.name, "redis-cli"])