Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI Command to Delete Lightning App #15783

Merged
merged 26 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6e584df
initial work on deleting apps
rlizzo Nov 21, 2022
de5c038
after PR review
rlizzo Nov 22, 2022
f9de268
delete CLI working
rlizzo Nov 23, 2022
19a19e5
restructred to make tests easier
rlizzo Nov 23, 2022
04d51ab
revert manifest changes
rlizzo Nov 23, 2022
b7ce10e
Merge branch 'master' into rick/delete-app-cli-command
rlizzo Nov 29, 2022
536614e
added changelog, fix mypy issue
rlizzo Nov 30, 2022
46e4b48
updates
rlizzo Nov 30, 2022
a1812af
Merge branch 'master' into rick/delete-app-cli-command
rlizzo Nov 30, 2022
6fab77d
Update src/lightning_app/cli/cmd_apps.py
rlizzo Dec 5, 2022
e52c584
Update src/lightning_app/cli/lightning_cli_delete.py
rlizzo Dec 5, 2022
947e349
Update src/lightning_app/cli/lightning_cli_delete.py
rlizzo Dec 5, 2022
9a347c2
Update src/lightning_app/cli/lightning_cli_delete.py
rlizzo Dec 5, 2022
37e2ed2
Update src/lightning_app/cli/lightning_cli_delete.py
rlizzo Dec 5, 2022
5cb5914
Merge branch 'master' into rick/delete-app-cli-command
rlizzo Dec 5, 2022
6990f43
import typing
rlizzo Dec 5, 2022
e126cbd
adding tests
rlizzo Dec 5, 2022
902bdc0
finished adding tests
rlizzo Dec 5, 2022
d46eb09
addressed code review comments
rlizzo Dec 5, 2022
f6cc36f
fix mypy error
rlizzo Dec 5, 2022
7173b8c
Merge branch 'master' into rick/delete-app-cli-command
rlizzo Dec 5, 2022
b95de60
make mypy happy
rlizzo Dec 5, 2022
74e47a5
make mypy happy
rlizzo Dec 5, 2022
b6c78bf
make mypy happy
rlizzo Dec 5, 2022
b310905
make mypy happy
rlizzo Dec 5, 2022
93ea43e
fix windows cli
rlizzo Dec 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/lightning_app/cli/cmd_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ def list(self, cluster_id: str = None, limit: int = 100) -> None:
console = Console()
console.print(_AppList(self.list_apps(cluster_id=cluster_id, limit=limit)).as_table())

def delete(self, app_id: str) -> None:
rlizzo marked this conversation as resolved.
Show resolved Hide resolved
project = _get_project(self.api_client)
self.api_client.lightningapp_instance_service_delete_lightningapp_instance(
project_id=project.project_id,
id=app_id,
)
return
rlizzo marked this conversation as resolved.
Show resolved Hide resolved


class _AppList(Formatable):
def __init__(self, apps: List[Externalv1LightningappInstance]) -> None:
Expand Down
4 changes: 4 additions & 0 deletions src/lightning_app/cli/cmd_clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ def create(
)
)

def list_clusters(self) -> List[Externalv1Cluster]:
resp = self.api_client.cluster_service_list_clusters(phase_not_in=[V1ClusterState.DELETED])
return resp.clusters

def get_clusters(self) -> ClusterList:
resp = self.api_client.cluster_service_list_clusters(phase_not_in=[V1ClusterState.DELETED])
return ClusterList(resp.clusters)
Expand Down
165 changes: 165 additions & 0 deletions src/lightning_app/cli/lightning_cli_delete.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import click
import inquirer
from inquirer.themes import GreenPassion
from lightning_cloud.openapi import V1ClusterType
from rich.console import Console

from lightning_app.cli.cmd_apps import _AppManager
from lightning_app.cli.cmd_clusters import AWSClusterManager
from lightning_app.cli.cmd_ssh_keys import _SSHKeyManager

Expand Down Expand Up @@ -52,6 +57,166 @@ def delete_cluster(cluster: str, force: bool = False, wait: bool = False) -> Non
cluster_manager.delete(cluster_id=cluster, force=force, wait=wait)


def _cli_delete_app_find_cluster(app_name: str, cluster_id: str) -> str:
rlizzo marked this conversation as resolved.
Show resolved Hide resolved
rlizzo marked this conversation as resolved.
Show resolved Hide resolved
console = Console()
cluster_manager = AWSClusterManager()

default_cluster, valid_clusters = None, []
for cluster in cluster_manager.list_clusters():
valid_clusters.append(cluster.id)
rlizzo marked this conversation as resolved.
Show resolved Hide resolved
if cluster.spec.cluster_type == V1ClusterType.GLOBAL and default_cluster is None:
default_cluster = cluster.id
rlizzo marked this conversation as resolved.
Show resolved Hide resolved

# when no cluster-id is passed in, use the default (Lightning Cloud) cluster
if cluster_id is None:
cluster_id = default_cluster

if cluster_id not in valid_clusters:
console.print(f'[b][yellow]You don\'t have access to cluster "{cluster_id}"[/yellow][/b]')
if len(valid_clusters) == 1:
rlizzo marked this conversation as resolved.
Show resolved Hide resolved
# if there are no BYOC clusters, then confirm that
# the user wants to fall back to the Lightning Cloud.
try:
ask = [
inquirer.Confirm(
"confirm",
message=f'Did you mean to specify the default Lightning Cloud cluster "{default_cluster}"?',
default=True,
),
]
if inquirer.prompt(ask, theme=GreenPassion(), raise_keyboard_interrupt=True)["confirm"] is False:
console.print("[b][red]Aborted![/b][/red]")
raise InterruptedError
except KeyboardInterrupt:
console.print("[b][red]Cancelled by user![/b][/red]")
raise InterruptedError
cluster_id = default_cluster
else:
# When there are BYOC clusters, have them select which cluster to use from all available.
try:
ask = [
inquirer.List(
"cluster",
message=f'Please select which cluster app "{app_name}" should be deleted from',
choices=valid_clusters,
default=default_cluster,
),
]
cluster_id = inquirer.prompt(ask, theme=GreenPassion(), raise_keyboard_interrupt=True)["cluster"]
except KeyboardInterrupt:
console.print("[b][red]Cancelled by user![/b][/red]")
raise InterruptedError

return cluster_id


def _cli_delete_app_find_selected_instance_id(app_name: str, cluster_id: str) -> str:
rlizzo marked this conversation as resolved.
Show resolved Hide resolved
console = Console()
app_manager = _AppManager()

all_app_names_and_ids = {}
selected_app_instance_id = None

for app in app_manager.list_apps(cluster_id=cluster_id):
all_app_names_and_ids[app.name] = app.id
rlizzo marked this conversation as resolved.
Show resolved Hide resolved
# figure out the ID of some app_name on the cluster.
if app_name == app.name:
selected_app_instance_id = app.id
break
if app_name == app.id:
selected_app_instance_id = app.id
break
rlizzo marked this conversation as resolved.
Show resolved Hide resolved

if selected_app_instance_id is None:
# when there is no app with the given app_name on the cluster,
# ask the user which app they would like to delete.
console.print(f'[b][yellow]Cluster "{cluster_id}" does not have an app named "{app_name}"[/yellow][/b]')
try:
ask = [
inquirer.List(
"app_name",
message="Select the app name to delete",
choices=list(all_app_names_and_ids.keys()),
),
]
app_name = inquirer.prompt(ask, theme=GreenPassion(), raise_keyboard_interrupt=True)["app_name"]
selected_app_instance_id = all_app_names_and_ids[app_name]
except KeyboardInterrupt:
console.print("[b][red]Cancelled by user![/b][/red]")
raise InterruptedError

return selected_app_instance_id


def _cli_delete_app_user_confirmation_prompt(app_name: str, cluster_id: str):
console = Console()

# when the --yes / -y flags were not passed, do a final
# confirmation that the user wants to delete the app.
try:
ask = [
inquirer.Confirm(
"confirm",
message=f'Are you sure you want to delete app "{app_name}" on cluster "{cluster_id}"?',
default=False,
),
]
if inquirer.prompt(ask, theme=GreenPassion(), raise_keyboard_interrupt=True)["confirm"] is False:
console.print("[b][red]Aborted![/b][/red]")
raise InterruptedError
except KeyboardInterrupt:
console.print("[b][red]Cancelled by user![/b][/red]")
raise InterruptedError


@delete.command("app")
@click.argument("app-name", type=str)
@click.option(
"--cluster-id",
type=str,
default=None,
help="Delete the Lighting App from a specific Lightning AI BYOC compute cluster",
)
@click.option(
"skip_user_confirm_prompt",
"--yes",
"-y",
is_flag=True,
default=False,
help="Do not prompt for confirmation.",
)
def delete_app(app_name: str, cluster_id: str, skip_user_confirm_prompt: bool) -> None:
"""Delete a Lightning app.

Deleting an app also deletes all app websites, works, artifacts, and logs. This permanently removes any record of
the app as well as all any of its associated resources and data. This does not affect any resources and data
associated with other Lightning apps on your account.
"""
console = Console()

try:
cluster_id = _cli_delete_app_find_cluster(app_name=app_name, cluster_id=cluster_id)
selected_app_instance_id = _cli_delete_app_find_selected_instance_id(app_name=app_name, cluster_id=cluster_id)
rlizzo marked this conversation as resolved.
Show resolved Hide resolved
if not skip_user_confirm_prompt:
_cli_delete_app_user_confirmation_prompt(app_name=app_name, cluster_id=cluster_id)
except InterruptedError:
return

try:
# Delete the app!
app_manager = _AppManager()
app_manager.delete(app_id=selected_app_instance_id)
except Exception as e:
console.print(
f'[b][red]An issue occurred while deleting app "{app_name}. If the issue persists, please '
"reach out to us at [link=mailto:[email protected]][email protected][/link][/b][/red]."
)
raise click.ClickException(str(e))

console.print(f'[b][green]App "{app_name}" has been successfully deleted from cluster "{cluster_id}"![/green][/b]')
return


@delete.command("ssh-key")
@click.argument("key_id")
def remove_ssh_key(key_id: str) -> None:
Expand Down
Empty file.