forked from Shifty/pyserveX
175 lines
4.8 KiB
Python
175 lines
4.8 KiB
Python
"""
|
|
pyserve up - Start all services
|
|
"""
|
|
|
|
import asyncio
|
|
import signal
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import List, Optional
|
|
|
|
import click
|
|
|
|
|
|
@click.command("up")
|
|
@click.argument("services", nargs=-1)
|
|
@click.option(
|
|
"-d",
|
|
"--detach",
|
|
is_flag=True,
|
|
help="Run in background (detached mode)",
|
|
)
|
|
@click.option(
|
|
"--build",
|
|
is_flag=True,
|
|
help="Build/reload applications before starting",
|
|
)
|
|
@click.option(
|
|
"--force-recreate",
|
|
is_flag=True,
|
|
help="Recreate services even if configuration hasn't changed",
|
|
)
|
|
@click.option(
|
|
"--scale",
|
|
"scales",
|
|
multiple=True,
|
|
help="Scale SERVICE to NUM workers (e.g., --scale api=4)",
|
|
)
|
|
@click.option(
|
|
"--timeout",
|
|
"timeout",
|
|
default=60,
|
|
type=int,
|
|
help="Timeout in seconds for service startup",
|
|
)
|
|
@click.option(
|
|
"--wait",
|
|
is_flag=True,
|
|
help="Wait for services to be healthy before returning",
|
|
)
|
|
@click.option(
|
|
"--remove-orphans",
|
|
is_flag=True,
|
|
help="Remove services not defined in configuration",
|
|
)
|
|
@click.pass_obj
|
|
def up_cmd(
|
|
ctx,
|
|
services: tuple,
|
|
detach: bool,
|
|
build: bool,
|
|
force_recreate: bool,
|
|
scales: tuple,
|
|
timeout: int,
|
|
wait: bool,
|
|
remove_orphans: bool,
|
|
):
|
|
"""
|
|
Start services defined in configuration.
|
|
|
|
If no services are specified, all services will be started.
|
|
|
|
\b
|
|
Examples:
|
|
pyserve up # Start all services
|
|
pyserve up -d # Start in background
|
|
pyserve up api admin # Start specific services
|
|
pyserve up --scale api=4 # Scale api to 4 workers
|
|
pyserve up --wait # Wait for healthy status
|
|
"""
|
|
from ..output import console, print_error, print_info, print_success, print_warning
|
|
from ..state import StateManager
|
|
from .._runner import ServiceRunner
|
|
|
|
config_path = Path(ctx.config_file)
|
|
|
|
if not config_path.exists():
|
|
print_error(f"Configuration file not found: {config_path}")
|
|
print_info("Run 'pyserve init' to create a configuration file")
|
|
raise click.Abort()
|
|
|
|
scale_map = {}
|
|
for scale in scales:
|
|
try:
|
|
service, num = scale.split("=")
|
|
scale_map[service] = int(num)
|
|
except ValueError:
|
|
print_error(f"Invalid scale format: {scale}. Use SERVICE=NUM")
|
|
raise click.Abort()
|
|
|
|
try:
|
|
from ...config import Config
|
|
|
|
config = Config.from_yaml(str(config_path))
|
|
except Exception as e:
|
|
print_error(f"Failed to load configuration: {e}")
|
|
raise click.Abort()
|
|
|
|
state_manager = StateManager(Path(".pyserve"), ctx.project)
|
|
|
|
if state_manager.is_daemon_running():
|
|
daemon_pid = state_manager.get_daemon_pid()
|
|
print_warning(f"PyServe daemon is already running (PID: {daemon_pid})")
|
|
if not click.confirm("Do you want to restart it?"):
|
|
raise click.Abort()
|
|
try:
|
|
import os
|
|
from typing import cast
|
|
# FIXME: Please fix the cast usage here
|
|
os.kill(cast(int, daemon_pid), signal.SIGTERM)
|
|
time.sleep(2)
|
|
except ProcessLookupError:
|
|
pass
|
|
state_manager.clear_daemon_pid()
|
|
|
|
runner = ServiceRunner(config, state_manager)
|
|
|
|
service_list = list(services) if services else None
|
|
|
|
if detach:
|
|
console.print("[bold]Starting PyServe in background...[/bold]")
|
|
|
|
try:
|
|
pid = runner.start_daemon(
|
|
service_list,
|
|
scale_map=scale_map,
|
|
force_recreate=force_recreate,
|
|
)
|
|
state_manager.set_daemon_pid(pid)
|
|
print_success(f"PyServe started in background (PID: {pid})")
|
|
print_info("Use 'pyserve ps' to see service status")
|
|
print_info("Use 'pyserve logs -f' to follow logs")
|
|
print_info("Use 'pyserve down' to stop")
|
|
except Exception as e:
|
|
print_error(f"Failed to start daemon: {e}")
|
|
raise click.Abort()
|
|
else:
|
|
console.print("[bold]Starting PyServe...[/bold]")
|
|
|
|
def signal_handler(signum, frame):
|
|
console.print("\n[yellow]Received shutdown signal...[/yellow]")
|
|
runner.stop()
|
|
sys.exit(0)
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
try:
|
|
asyncio.run(
|
|
runner.start(
|
|
service_list,
|
|
scale_map=scale_map,
|
|
force_recreate=force_recreate,
|
|
wait_healthy=wait,
|
|
timeout=timeout,
|
|
)
|
|
)
|
|
except KeyboardInterrupt:
|
|
console.print("\n[yellow]Shutting down...[/yellow]")
|
|
except Exception as e:
|
|
print_error(f"Failed to start services: {e}")
|
|
if ctx.debug:
|
|
raise
|
|
raise click.Abort()
|