import shlex
import subprocess
import webbrowser
from typing import Optional
import typer
from rich.console import Console
import runhouse.rns.login
from runhouse import __version__, cluster, configs
# create an explicit Typer application
app = typer.Typer(add_completion=False)
state = {"verbose": False}
# For printing with typer
console = Console()
[docs]@app.command()
def login(
token: Optional[str] = typer.Argument(None, help="Your Runhouse API token"),
yes: Optional[bool] = typer.Option(
False, "--yes", "-y", help="Sets any confirmations to 'yes' automatically."
),
):
"""Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between
local environment and Runhouse / Vault.
"""
valid_token: str = (
runhouse.rns.login.login(
token=token,
download_config=True,
upload_config=True,
download_secrets=True,
upload_secrets=True,
)
if yes
else runhouse.rns.login.login(token=token, interactive=True, ret_token=True)
)
if valid_token:
webbrowser.open(f"{configs.get('dashboard_url')}/dashboard?token={valid_token}")
raise typer.Exit()
else:
raise typer.Exit(code=1)
[docs]@app.command()
def logout():
"""Logout of Runhouse. Provides options to delete locally configured secrets and local Runhouse configs"""
runhouse.rns.login.logout(interactive=True)
raise typer.Exit()
[docs]@app.command()
def notebook(
cluster_name: str, up: bool = typer.Option(False, help="Start the cluster")
):
"""Open a Jupyter notebook on a cluster."""
c = cluster(name=cluster_name)
if up:
c.up_if_not()
if not c.is_up():
console.print(
f"Cluster {cluster_name} is not up. Please run `runhouse notebook {cluster_name} --up`."
)
raise typer.Exit(1)
c.notebook()
[docs]@app.command()
def ssh(cluster_name: str, up: bool = typer.Option(False, help="Start the cluster")):
"""SSH into a cluster created elsewhere (so `ssh cluster` doesn't work out of the box) or not yet up."""
c = cluster(name=cluster_name)
if up:
c.up_if_not()
if not c.is_up():
console.print(
f"Cluster {cluster_name} is not up. Please run `runhouse ssh {cluster_name} --up`."
)
raise typer.Exit(1)
c.ssh()
def load_cluster(cluster_name: str):
"""Load a cluster from RNS into the local environment, e.g. to be able to ssh."""
c = cluster(name=cluster_name)
if not c.address:
c._update_from_sky_status(dryrun=True)
def _start_server(restart, restart_ray, screen, create_logfile=True):
from runhouse.resources.hardware.cluster import Cluster
cmds = Cluster._start_server_cmds(
restart=restart,
restart_ray=restart_ray,
screen=screen,
create_logfile=create_logfile,
)
try:
# We do these one by one so it's more obvious where the error is if there is one
for cmd in cmds:
console.print(f"Executing `{cmd}`")
result = subprocess.run(shlex.split(cmd), text=True)
# We don't want to raise an error if the server kill fails, as it may simply not be running
if result.returncode != 0 and "pkill" not in cmd:
console.print(f"Error while executing `{cmd}`")
raise typer.Exit(1)
except FileNotFoundError:
console.print(
"python3 command was not found. Make sure you have python3 installed."
)
raise typer.Exit(1)
@app.command()
def start(
restart_ray: bool = typer.Option(False, help="Restart the Ray runtime"),
screen: bool = typer.Option(False, help="Start the server in a screen"),
):
"""Start the HTTP server on the cluster."""
_start_server(
restart=False, restart_ray=restart_ray, screen=screen, create_logfile=True
)
@app.command()
def restart(
name: str = typer.Option(None, help="A *saved* remote cluster object to restart."),
restart_ray: bool = typer.Option(True, help="Restart the Ray runtime"),
screen: bool = typer.Option(
True,
help="Start the server in a screen. Only relevant when restarting locally.",
),
resync_rh: bool = typer.Option(
False,
help="Resync the Runhouse package. Only relevant when restarting remotely.",
),
):
"""Restart the HTTP server on the cluster."""
if name:
c = cluster(name=name)
c.restart_server(resync_rh=resync_rh, restart_ray=restart_ray)
return
_start_server(
restart=True,
restart_ray=restart_ray,
screen=screen,
create_logfile=True,
)
@app.callback()
def main(verbose: bool = False):
"""
Runhouse CLI
"""
if verbose:
name = "runhouse"
console.print(f"{name}=={__version__}", style="bold green")
state["verbose"] = True