331 lines
9.0 KiB
Python
331 lines
9.0 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Annotated
|
|
|
|
import typer
|
|
from rich.console import Console
|
|
|
|
from fc.commands.utils import get_doctype, validate_server_name
|
|
from fc.printer import Print, print_full_plan_details, print_plan_details, show_usage
|
|
|
|
if TYPE_CHECKING:
|
|
from fc.authentication.session import CloudSession
|
|
|
|
server = typer.Typer(help="Server Commands")
|
|
console = Console()
|
|
|
|
|
|
@server.command(help="Show live usage for a server")
|
|
def usage(
|
|
ctx: typer.Context,
|
|
name: Annotated[str, typer.Argument(help="Server name")],
|
|
):
|
|
session: CloudSession = ctx.obj
|
|
|
|
try:
|
|
usage_data = session.post(
|
|
"press.api.server.usage",
|
|
json={"name": name},
|
|
message=f"[bold green]Fetching usage for {name}...",
|
|
)
|
|
|
|
if not isinstance(usage_data, dict) or not usage_data:
|
|
Print.info(console, "No usage data returned.")
|
|
return
|
|
|
|
vcpu = usage_data.get("vcpu")
|
|
disk_gb = usage_data.get("disk") # GB
|
|
mem_mb = usage_data.get("memory") # MB
|
|
free_mem_bytes = usage_data.get("free_memory") # bytes (avg 10m)
|
|
|
|
show_usage(
|
|
vcpu=vcpu,
|
|
mem_mb=mem_mb,
|
|
disk_gb=disk_gb,
|
|
free_mem_bytes=free_mem_bytes,
|
|
console=console,
|
|
)
|
|
|
|
except Exception as e:
|
|
Print.error(console, e)
|
|
|
|
|
|
@server.command(help="Show details about a specific plan for a server")
|
|
def show_plan(
|
|
ctx: typer.Context,
|
|
name: Annotated[str, typer.Argument(help="Server name")],
|
|
plan: Annotated[str, typer.Option("--plan", help="Plan name")],
|
|
):
|
|
session: CloudSession = ctx.obj
|
|
|
|
try:
|
|
doctype = get_doctype(name)
|
|
payload = {"name": doctype, "cluster": "Mumbai", "platform": "arm64"}
|
|
|
|
plans = session.post(
|
|
"press.api.server.plans", json=payload, message="[bold green]Fetching available server plans..."
|
|
)
|
|
|
|
selected_plan = next((p for p in plans if p.get("name") == plan), None)
|
|
if not selected_plan:
|
|
Print.error(console, f"Plan '{plan}' not found for server '{name}'")
|
|
return
|
|
|
|
print_plan_details(selected_plan, console)
|
|
|
|
except Exception as e:
|
|
Print.error(console, e)
|
|
|
|
|
|
@server.command(help="Shows the current plan for a server")
|
|
def server_plan(
|
|
ctx: typer.Context,
|
|
name: Annotated[str, typer.Argument(help="Server name")],
|
|
):
|
|
session: CloudSession = ctx.obj
|
|
|
|
try:
|
|
doctype = get_doctype(name)
|
|
payload = {
|
|
"doctype": doctype,
|
|
"name": name,
|
|
"fields": ["current_plan"],
|
|
"debug": 0,
|
|
}
|
|
response = session.post(
|
|
"press.api.client.get", json=payload, message="[bold green]Getting server details..."
|
|
)
|
|
if not response or "current_plan" not in response:
|
|
Print.error(console, f"{doctype} '{name}' or its current plan not found.")
|
|
return
|
|
plan = response["current_plan"]
|
|
print_full_plan_details(plan, console)
|
|
except Exception as e:
|
|
Print.error(console, e)
|
|
|
|
|
|
@server.command(help="Increase storage for a server")
|
|
def increase_storage(
|
|
ctx: typer.Context,
|
|
name: Annotated[str, typer.Argument(help="Server name")],
|
|
increment: Annotated[int, typer.Option("--increment", help="Increment size in GB")],
|
|
force: Annotated[
|
|
bool,
|
|
typer.Option("--force", "-f", is_flag=True, help="Skip confirmation"),
|
|
] = False,
|
|
):
|
|
session: CloudSession = ctx.obj
|
|
is_valid, err = validate_server_name(name)
|
|
if not is_valid:
|
|
Print.error(console, err)
|
|
return
|
|
|
|
try:
|
|
doctype = get_doctype(name)
|
|
|
|
if not _should_proceed(
|
|
f"Increase storage for server '{name}' by {increment} GB? This action may be irreversible.",
|
|
force,
|
|
):
|
|
Print.info(console, "Operation cancelled.")
|
|
return
|
|
|
|
payload = {
|
|
"dt": doctype,
|
|
"dn": name,
|
|
"method": "increase_disk_size_for_server",
|
|
"args": {"server": name, "increment": increment},
|
|
}
|
|
|
|
response = session.post(
|
|
"press.api.client.run_pg_method",
|
|
json=payload,
|
|
message=f"[bold green]Increasing storage for {name} by {increment}GB...",
|
|
)
|
|
|
|
if response and response.get("success") is False:
|
|
Print.error(console, f"Failed to increase storage: {response.get('message', 'Unknown error')}")
|
|
return
|
|
|
|
Print.success(
|
|
console,
|
|
f"Storage increased for server {name} by {increment} GB",
|
|
)
|
|
|
|
except Exception as e:
|
|
Print.error(console, e)
|
|
|
|
|
|
@server.command(help="Show current plan and choose available server plans")
|
|
def choose_plan(
|
|
ctx: typer.Context,
|
|
name: Annotated[str, typer.Argument(help="Server name")],
|
|
plan: Annotated[str, typer.Option("--plan", help="Plan name")],
|
|
force: Annotated[
|
|
bool,
|
|
typer.Option("--force", "-f", is_flag=True, help="Skip confirmation"),
|
|
] = False,
|
|
):
|
|
session: CloudSession = ctx.obj
|
|
|
|
try:
|
|
doctype = get_doctype(name)
|
|
payload = {"name": doctype, "cluster": "Mumbai", "platform": "arm64"}
|
|
plans = session.post(
|
|
"press.api.server.plans", json=payload, message="[bold green]Fetching available server plans..."
|
|
)
|
|
|
|
selected_plan = next((p for p in plans if p.get("name") == plan), None)
|
|
if not selected_plan:
|
|
Print.error(console, f"Plan '{plan}' not found for server '{name}'")
|
|
return
|
|
|
|
current_plan_name = _get_current_plan_name(session, doctype, name)
|
|
|
|
if current_plan_name and current_plan_name == selected_plan.get("name"):
|
|
Print.info(
|
|
console,
|
|
f"Plan '{current_plan_name}' is already active for server '{name}'. Choose a different plan to change.",
|
|
)
|
|
return
|
|
|
|
if not _should_proceed(
|
|
f"Change plan for server '{name}' to '{selected_plan.get('name')}'?",
|
|
force,
|
|
):
|
|
Print.info(console, "Operation cancelled.")
|
|
return
|
|
|
|
change_payload = {
|
|
"dt": doctype,
|
|
"dn": name,
|
|
"method": "change_plan",
|
|
"args": {"plan": selected_plan.get("name")},
|
|
}
|
|
|
|
response = session.post(
|
|
"press.api.client.run_pg_method",
|
|
json=change_payload,
|
|
message=f"[bold green]Changing plan for {name} to {selected_plan.get('name')}...",
|
|
)
|
|
|
|
if response and response.get("success") is False:
|
|
Print.error(console, f"Failed to change plan: {response.get('message', 'Unknown error')}")
|
|
return
|
|
|
|
previous_plan = current_plan_name or "(unknown)"
|
|
Print.success(
|
|
console,
|
|
f"Plan changed for server '{name}': {previous_plan} -> {selected_plan.get('name')}",
|
|
)
|
|
print_plan_details(selected_plan, console)
|
|
|
|
except Exception as e:
|
|
Print.error(console, e)
|
|
|
|
|
|
@server.command(help="Create a new server")
|
|
def create_server(
|
|
ctx: typer.Context,
|
|
title: Annotated[str, typer.Argument(help="Server title")],
|
|
cluster: Annotated[str, typer.Option("--cluster", help="Cluster name")] = "",
|
|
app_plan: Annotated[str, typer.Option("--app-plan", help="App server plan name")] = "",
|
|
db_plan: Annotated[str, typer.Option("--db-plan", help="Database server plan name")] = "",
|
|
auto_increase_storage: Annotated[
|
|
bool, typer.Option("--auto-increase-storage", is_flag=True, help="Auto increase storage")
|
|
] = False,
|
|
):
|
|
session: CloudSession = ctx.obj
|
|
|
|
try:
|
|
server_payload = {
|
|
"cluster": cluster,
|
|
"title": title,
|
|
"app_plan": app_plan,
|
|
"db_plan": db_plan,
|
|
"auto_increase_storage": auto_increase_storage,
|
|
}
|
|
|
|
response = session.post(
|
|
"press.api.server.new",
|
|
json={"server": server_payload},
|
|
message=f"[bold green]Creating server '{title}' in cluster '{cluster}'...",
|
|
)
|
|
|
|
if not response or not response.get("server"):
|
|
Print.error(
|
|
console,
|
|
f"Failed to create server: {response.get('message', 'Unknown error') if response else 'No response from backend.'}",
|
|
)
|
|
return
|
|
|
|
Print.success(console, f"Successfully created server: {response['server']}")
|
|
if response.get("job"):
|
|
Print.info(console, f"Server creation job started: {response['job']}")
|
|
|
|
except Exception as e:
|
|
Print.error(console, e)
|
|
|
|
|
|
@server.command(help="Delete a server (archive)")
|
|
def delete_server(
|
|
ctx: typer.Context,
|
|
name: Annotated[str, typer.Argument(help="Server name to delete")],
|
|
force: Annotated[
|
|
bool,
|
|
typer.Option("--force", "-f", is_flag=True, help="Skip confirmation"),
|
|
] = False,
|
|
):
|
|
session: CloudSession = ctx.obj
|
|
|
|
try:
|
|
if not _should_proceed(
|
|
f"Are you sure you want to archive server '{name}'? This action may be irreversible.",
|
|
force,
|
|
):
|
|
Print.info(console, "Operation cancelled.")
|
|
return
|
|
|
|
response = session.post(
|
|
"press.api.server.archive",
|
|
json={"name": name},
|
|
message=f"[bold red]Archiving server '{name}'...",
|
|
)
|
|
|
|
if response and response.get("exc_type"):
|
|
Print.error(console, f"Failed to delete server: {response.get('exception', 'Unknown error')}")
|
|
return
|
|
|
|
Print.success(console, f"Successfully deleted (archived) server: {name}")
|
|
|
|
except Exception as e:
|
|
Print.error(console, e)
|
|
|
|
|
|
def _should_proceed(message: str, confirm_token: str | None) -> bool:
|
|
if isinstance(confirm_token, bool) and confirm_token:
|
|
return True
|
|
if isinstance(confirm_token, str) and confirm_token.lower() in {"force", "yes", "y"}:
|
|
return True
|
|
return typer.confirm(message, default=False)
|
|
|
|
|
|
def _get_current_plan_name(session: "CloudSession", doctype: str, name: str) -> str | None:
|
|
resp = session.post(
|
|
"press.api.client.get",
|
|
json={
|
|
"doctype": doctype,
|
|
"name": name,
|
|
"fields": ["current_plan"],
|
|
"debug": 0,
|
|
},
|
|
message="[bold green]Checking current server plan...",
|
|
)
|
|
if isinstance(resp, dict) and resp.get("current_plan"):
|
|
cp = resp["current_plan"]
|
|
if isinstance(cp, dict):
|
|
return cp.get("name")
|
|
if isinstance(cp, str):
|
|
return cp
|
|
return None
|