vantage-cli 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vantage_cli/__init__.py +131 -0
- vantage_cli/apps/__init__.py +22 -0
- vantage_cli/apps/common.py +78 -0
- vantage_cli/apps/juju_localhost/__init__.py +17 -0
- vantage_cli/apps/juju_localhost/app.py +255 -0
- vantage_cli/apps/juju_localhost/bundle_yaml.py +143 -0
- vantage_cli/apps/microk8s/README.md +47 -0
- vantage_cli/apps/microk8s/__init__.py +3 -0
- vantage_cli/apps/microk8s/app.py +301 -0
- vantage_cli/apps/multipass_singlenode/__init__.py +12 -0
- vantage_cli/apps/multipass_singlenode/app.py +173 -0
- vantage_cli/apps/templates.py +178 -0
- vantage_cli/auth.py +429 -0
- vantage_cli/cache.py +143 -0
- vantage_cli/client.py +84 -0
- vantage_cli/command_base.py +63 -0
- vantage_cli/commands/__init__.py +1 -0
- vantage_cli/commands/clouds/__init__.py +20 -0
- vantage_cli/commands/clouds/add.py +81 -0
- vantage_cli/commands/clouds/delete.py +61 -0
- vantage_cli/commands/clouds/render.py +146 -0
- vantage_cli/commands/clouds/update.py +97 -0
- vantage_cli/commands/clusters/__init__.py +27 -0
- vantage_cli/commands/clusters/create.py +270 -0
- vantage_cli/commands/clusters/delete.py +101 -0
- vantage_cli/commands/clusters/get.py +30 -0
- vantage_cli/commands/clusters/list.py +84 -0
- vantage_cli/commands/clusters/render.py +233 -0
- vantage_cli/commands/clusters/schema.py +31 -0
- vantage_cli/commands/clusters/utils.py +248 -0
- vantage_cli/commands/profile/__init__.py +30 -0
- vantage_cli/commands/profile/crud.py +529 -0
- vantage_cli/commands/profile/render.py +55 -0
- vantage_cli/config.py +161 -0
- vantage_cli/constants.py +40 -0
- vantage_cli/exceptions.py +127 -0
- vantage_cli/format.py +39 -0
- vantage_cli/gql_client.py +655 -0
- vantage_cli/main.py +303 -0
- vantage_cli/render.py +56 -0
- vantage_cli/schemas.py +48 -0
- vantage_cli/time_loop.py +124 -0
- vantage_cli-0.1.1.dist-info/METADATA +30 -0
- vantage_cli-0.1.1.dist-info/RECORD +46 -0
- vantage_cli-0.1.1.dist-info/WHEEL +4 -0
- vantage_cli-0.1.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Update cloud command."""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def update_command(
|
|
16
|
+
ctx: typer.Context,
|
|
17
|
+
cloud_name: Annotated[str, typer.Argument(help="Name of the cloud to update")],
|
|
18
|
+
provider: Annotated[
|
|
19
|
+
Optional[str], typer.Option("--provider", "-p", help="Update cloud provider")
|
|
20
|
+
] = None,
|
|
21
|
+
region: Annotated[
|
|
22
|
+
Optional[str], typer.Option("--region", "-r", help="Update default region")
|
|
23
|
+
] = None,
|
|
24
|
+
config_file: Annotated[
|
|
25
|
+
Optional[Path],
|
|
26
|
+
typer.Option(
|
|
27
|
+
"--config-file",
|
|
28
|
+
help="Path to updated configuration file",
|
|
29
|
+
exists=True,
|
|
30
|
+
file_okay=True,
|
|
31
|
+
dir_okay=False,
|
|
32
|
+
readable=True,
|
|
33
|
+
),
|
|
34
|
+
] = None,
|
|
35
|
+
credentials_file: Annotated[
|
|
36
|
+
Optional[Path],
|
|
37
|
+
typer.Option(
|
|
38
|
+
"--credentials-file",
|
|
39
|
+
help="Path to updated credentials file",
|
|
40
|
+
exists=True,
|
|
41
|
+
file_okay=True,
|
|
42
|
+
dir_okay=False,
|
|
43
|
+
readable=True,
|
|
44
|
+
),
|
|
45
|
+
] = None,
|
|
46
|
+
description: Annotated[
|
|
47
|
+
Optional[str], typer.Option("--description", help="Update cloud description")
|
|
48
|
+
] = None,
|
|
49
|
+
):
|
|
50
|
+
"""Update an existing cloud configuration."""
|
|
51
|
+
verbose = ctx.obj.get("verbose", False)
|
|
52
|
+
settings = ctx.obj.get("settings")
|
|
53
|
+
|
|
54
|
+
logger.info(f"Updating cloud configuration: {cloud_name}")
|
|
55
|
+
|
|
56
|
+
if verbose:
|
|
57
|
+
logger.debug(f"Provider: {provider}")
|
|
58
|
+
logger.debug(f"Region: {region}")
|
|
59
|
+
logger.debug(f"Config file: {config_file}")
|
|
60
|
+
logger.debug(f"Credentials file: {credentials_file}")
|
|
61
|
+
logger.debug(f"Description: {description}")
|
|
62
|
+
logger.debug(f"Settings: {settings}")
|
|
63
|
+
|
|
64
|
+
# TODO: Check if cloud exists
|
|
65
|
+
|
|
66
|
+
# Check if any updates were provided
|
|
67
|
+
updates = []
|
|
68
|
+
if provider:
|
|
69
|
+
updates.append(f"provider: {provider}")
|
|
70
|
+
if region:
|
|
71
|
+
updates.append(f"region: {region}")
|
|
72
|
+
if config_file:
|
|
73
|
+
updates.append(f"config file: {config_file}")
|
|
74
|
+
if credentials_file:
|
|
75
|
+
updates.append(f"credentials file: {credentials_file}")
|
|
76
|
+
if description:
|
|
77
|
+
updates.append(f"description: {description}")
|
|
78
|
+
|
|
79
|
+
if not updates:
|
|
80
|
+
typer.echo(f"No updates specified for cloud '{cloud_name}'")
|
|
81
|
+
typer.echo("Use --help to see available update options")
|
|
82
|
+
raise typer.Exit(1)
|
|
83
|
+
|
|
84
|
+
typer.echo(f"Updating cloud '{cloud_name}' with:")
|
|
85
|
+
for update in updates:
|
|
86
|
+
typer.echo(f" - {update}")
|
|
87
|
+
|
|
88
|
+
# TODO: Implement actual cloud configuration update logic
|
|
89
|
+
# This would typically:
|
|
90
|
+
# 1. Load existing cloud configuration
|
|
91
|
+
# 2. Update specified fields
|
|
92
|
+
# 3. Validate the updated configuration
|
|
93
|
+
# 4. Save the updated configuration
|
|
94
|
+
# 5. Update any related files or settings
|
|
95
|
+
|
|
96
|
+
logger.info(f"Cloud {cloud_name} updated successfully")
|
|
97
|
+
typer.echo(f"✅ Cloud '{cloud_name}' updated successfully")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Cluster management commands for Vantage CLI."""
|
|
4
|
+
|
|
5
|
+
from vantage_cli import AsyncTyper
|
|
6
|
+
|
|
7
|
+
from .create import create_cluster
|
|
8
|
+
from .delete import delete_cluster
|
|
9
|
+
from .get import get_cluster
|
|
10
|
+
from .list import list_clusters
|
|
11
|
+
|
|
12
|
+
# Create the cluster command group
|
|
13
|
+
cluster_app = AsyncTyper(
|
|
14
|
+
name="clusters",
|
|
15
|
+
help="Manage Vantage compute clusters for high-performance computing workloads.",
|
|
16
|
+
context_settings={
|
|
17
|
+
"allow_extra_args": True,
|
|
18
|
+
"allow_interspersed_args": True,
|
|
19
|
+
"ignore_unknown_options": True,
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Register subcommands directly
|
|
24
|
+
cluster_app.command("create")(create_cluster)
|
|
25
|
+
cluster_app.command("delete")(delete_cluster)
|
|
26
|
+
cluster_app.command("get")(get_cluster)
|
|
27
|
+
cluster_app.command("list")(list_clusters)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Create cluster command for Vantage CLI."""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import typer
|
|
10
|
+
from loguru import logger
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from typing_extensions import Annotated
|
|
13
|
+
|
|
14
|
+
from vantage_cli.config import attach_settings
|
|
15
|
+
from vantage_cli.exceptions import Abort
|
|
16
|
+
from vantage_cli.gql_client import create_async_graphql_client
|
|
17
|
+
|
|
18
|
+
from .render import render_cluster_details
|
|
19
|
+
from .utils import get_app_choices, get_available_apps, get_cloud_choices
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@attach_settings
|
|
25
|
+
async def create_cluster(
|
|
26
|
+
ctx: typer.Context,
|
|
27
|
+
cluster_name: Annotated[str, typer.Argument(help="Name of the cluster to create")],
|
|
28
|
+
cloud: Annotated[
|
|
29
|
+
str,
|
|
30
|
+
typer.Option(
|
|
31
|
+
"--cloud",
|
|
32
|
+
"-c",
|
|
33
|
+
help="Cloud to use for deployment.",
|
|
34
|
+
case_sensitive=False,
|
|
35
|
+
click_type=click.Choice(get_cloud_choices(), case_sensitive=False),
|
|
36
|
+
),
|
|
37
|
+
],
|
|
38
|
+
config_file: Annotated[
|
|
39
|
+
Optional[Path],
|
|
40
|
+
typer.Option(
|
|
41
|
+
"--config-file",
|
|
42
|
+
help="Path to configuration file for cluster creation.",
|
|
43
|
+
exists=True,
|
|
44
|
+
file_okay=True,
|
|
45
|
+
dir_okay=False,
|
|
46
|
+
readable=True,
|
|
47
|
+
),
|
|
48
|
+
] = None,
|
|
49
|
+
deploy: Annotated[
|
|
50
|
+
Optional[str],
|
|
51
|
+
typer.Option(
|
|
52
|
+
"--deploy",
|
|
53
|
+
help="Deploy an application after cluster creation.",
|
|
54
|
+
case_sensitive=False,
|
|
55
|
+
click_type=click.Choice(get_app_choices(), case_sensitive=False),
|
|
56
|
+
),
|
|
57
|
+
] = None,
|
|
58
|
+
):
|
|
59
|
+
"""Create a new Vantage cluster."""
|
|
60
|
+
# Ensure we have settings configured
|
|
61
|
+
if not ctx.obj or not ctx.obj.settings:
|
|
62
|
+
raise Abort(
|
|
63
|
+
"No settings configured. Please run 'vantage config set' first.",
|
|
64
|
+
subject="Configuration Required",
|
|
65
|
+
log_message="Settings not configured",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Validate cloud provider
|
|
69
|
+
supported_clouds = ctx.obj.settings.supported_clouds
|
|
70
|
+
if cloud not in supported_clouds:
|
|
71
|
+
raise Abort(
|
|
72
|
+
f"Unsupported cloud provider: {cloud}. Supported providers: {', '.join(supported_clouds)}",
|
|
73
|
+
subject="Invalid Cloud Provider",
|
|
74
|
+
log_message=f"Invalid cloud provider: {cloud}",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# GraphQL mutation for creating a cluster
|
|
78
|
+
create_mutation = """
|
|
79
|
+
mutation createCluster($createClusterInput: CreateClusterInput!) {
|
|
80
|
+
createCluster(createClusterInput: $createClusterInput) {
|
|
81
|
+
... on Cluster {
|
|
82
|
+
name
|
|
83
|
+
status
|
|
84
|
+
clientId
|
|
85
|
+
description
|
|
86
|
+
ownerEmail
|
|
87
|
+
provider
|
|
88
|
+
cloudAccountId
|
|
89
|
+
creationParameters
|
|
90
|
+
}
|
|
91
|
+
... on ClusterNameInUse {
|
|
92
|
+
message
|
|
93
|
+
}
|
|
94
|
+
... on InvalidInput {
|
|
95
|
+
message
|
|
96
|
+
}
|
|
97
|
+
... on ClusterCouldNotBeDeployed {
|
|
98
|
+
message
|
|
99
|
+
}
|
|
100
|
+
... on UnexpectedBehavior {
|
|
101
|
+
message
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
# Map cloud provider to GraphQL enum values
|
|
108
|
+
provider_mapping = {
|
|
109
|
+
"localhost": "on_prem",
|
|
110
|
+
"aws": "aws",
|
|
111
|
+
"gcp": "on_prem", # TODO: Add GCP support to backend
|
|
112
|
+
"azure": "on_prem", # TODO: Add Azure support to backend
|
|
113
|
+
"on-premises": "on_prem",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Build the input variables
|
|
117
|
+
cluster_input: dict[str, Any] = {
|
|
118
|
+
"name": cluster_name,
|
|
119
|
+
"description": f"Cluster {cluster_name} created via CLI",
|
|
120
|
+
"provider": provider_mapping.get(cloud, "on_prem"),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Add provider-specific attributes if needed
|
|
124
|
+
if cloud == "aws":
|
|
125
|
+
# For AWS, we need providerAttributes (camelCase for GraphQL)
|
|
126
|
+
# This is a simplified example - in practice you'd need to collect more details
|
|
127
|
+
cluster_input["providerAttributes"] = {
|
|
128
|
+
"aws": {
|
|
129
|
+
"headNodeInstanceType": "t3.medium", # camelCase for GraphQL
|
|
130
|
+
"keyPair": "default", # This should be configurable
|
|
131
|
+
"cloudAccountId": 1, # This should come from user's cloud account
|
|
132
|
+
"regionName": "us_west_2",
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# If config file is provided, read and parse it
|
|
137
|
+
if config_file:
|
|
138
|
+
try:
|
|
139
|
+
import json
|
|
140
|
+
|
|
141
|
+
config_data = json.loads(config_file.read_text())
|
|
142
|
+
# Merge config file data with input
|
|
143
|
+
cluster_input.update(config_data)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
raise Abort(
|
|
146
|
+
f"Failed to read configuration file: {e}",
|
|
147
|
+
subject="Configuration File Error",
|
|
148
|
+
log_message=f"Config file error: {e}",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
variables = {"createClusterInput": cluster_input}
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
# Create async GraphQL client
|
|
155
|
+
logger.debug(f"CTX OBJ: {ctx.obj}")
|
|
156
|
+
graphql_client = create_async_graphql_client(ctx.obj.settings, ctx.obj.profile)
|
|
157
|
+
|
|
158
|
+
# Execute the mutation
|
|
159
|
+
logger.debug(f"Creating cluster: {cluster_name}")
|
|
160
|
+
console.print(f"[bold blue]Creating cluster '{cluster_name}' on {cloud}...[/bold blue]")
|
|
161
|
+
|
|
162
|
+
response_data = await graphql_client.execute_async(create_mutation, variables)
|
|
163
|
+
|
|
164
|
+
# Handle the response
|
|
165
|
+
create_result = response_data.get("createCluster")
|
|
166
|
+
|
|
167
|
+
if not create_result:
|
|
168
|
+
raise Abort(
|
|
169
|
+
"No response from server",
|
|
170
|
+
subject="Server Error",
|
|
171
|
+
log_message="Empty response from createCluster mutation",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Check for errors in the response
|
|
175
|
+
if "message" in create_result:
|
|
176
|
+
# This is an error response
|
|
177
|
+
error_message = create_result["message"]
|
|
178
|
+
console.print(f"[bold red]Failed to create cluster: {error_message}[/bold red]")
|
|
179
|
+
raise Abort(
|
|
180
|
+
f"Cluster creation failed: {error_message}",
|
|
181
|
+
subject="Cluster Creation Failed",
|
|
182
|
+
log_message=f"GraphQL error: {error_message}",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Success case - cluster was created
|
|
186
|
+
if "name" in create_result:
|
|
187
|
+
console.print(
|
|
188
|
+
f"[bold green]✓ Cluster '{create_result['name']}' created successfully![/bold green]"
|
|
189
|
+
)
|
|
190
|
+
console.print()
|
|
191
|
+
|
|
192
|
+
# Display detailed cluster information
|
|
193
|
+
render_cluster_details(create_result, json_output=False)
|
|
194
|
+
|
|
195
|
+
# Deploy application if --deploy option was provided
|
|
196
|
+
if deploy:
|
|
197
|
+
await deploy_app_to_cluster(ctx, create_result, deploy)
|
|
198
|
+
|
|
199
|
+
else:
|
|
200
|
+
console.print(
|
|
201
|
+
"[bold yellow]Cluster creation initiated but status unclear[/bold yellow]"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
except Abort:
|
|
205
|
+
# Re-raise Abort exceptions as they contain user-friendly messages
|
|
206
|
+
raise
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"Unexpected error creating cluster: {e}")
|
|
209
|
+
raise Abort(
|
|
210
|
+
"An unexpected error occurred while creating the cluster.",
|
|
211
|
+
subject="Unexpected Error",
|
|
212
|
+
log_message=f"Unexpected error: {e}",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
async def deploy_app_to_cluster(ctx: typer.Context, cluster_data: dict, app_name: str):
|
|
217
|
+
"""Deploy an application to the newly created cluster."""
|
|
218
|
+
try:
|
|
219
|
+
# Get available apps
|
|
220
|
+
available_apps = get_available_apps()
|
|
221
|
+
|
|
222
|
+
if app_name not in available_apps:
|
|
223
|
+
console.print(f"[bold red]✗ App '{app_name}' not found[/bold red]")
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
console.print(
|
|
227
|
+
f"[bold blue]Deploying app '{app_name}' to cluster '{cluster_data['name']}'...[/bold blue]"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Get the app info
|
|
231
|
+
app_info = available_apps[app_name]
|
|
232
|
+
|
|
233
|
+
# Check if this is a function-based app or class-based app
|
|
234
|
+
if "deploy_function" in app_info:
|
|
235
|
+
# Function-based app
|
|
236
|
+
deploy_function = app_info["deploy_function"]
|
|
237
|
+
await deploy_function(ctx, cluster_data)
|
|
238
|
+
console.print(f"[bold green]✓ App '{app_name}' deployed successfully![/bold green]")
|
|
239
|
+
elif "instance" in app_info:
|
|
240
|
+
# Class-based app
|
|
241
|
+
app_instance = app_info["instance"]
|
|
242
|
+
|
|
243
|
+
# Check if the app has a deploy method
|
|
244
|
+
if hasattr(app_instance, "deploy"):
|
|
245
|
+
# Call the app's deploy method
|
|
246
|
+
await app_instance.deploy(ctx)
|
|
247
|
+
console.print(
|
|
248
|
+
f"[bold green]✓ App '{app_name}' deployed successfully![/bold green]"
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
console.print(
|
|
252
|
+
f"[bold yellow]! App '{app_name}' does not support automatic deployment[/bold yellow]"
|
|
253
|
+
)
|
|
254
|
+
console.print(
|
|
255
|
+
"[dim]You can manually deploy this app using the appropriate commands.[/dim]"
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
console.print(
|
|
259
|
+
f"[bold yellow]! App '{app_name}' does not support automatic deployment[/bold yellow]"
|
|
260
|
+
)
|
|
261
|
+
console.print(
|
|
262
|
+
"[dim]You can manually deploy this app using the appropriate commands.[/dim]"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.error(f"Failed to deploy app '{app_name}': {e}")
|
|
267
|
+
console.print(f"[bold red]✗ Failed to deploy app '{app_name}': {e}[/bold red]")
|
|
268
|
+
console.print(
|
|
269
|
+
"[dim]The cluster was created successfully, but app deployment failed.[/dim]"
|
|
270
|
+
)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Delete cluster command for Vantage CLI."""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from typing_extensions import Annotated
|
|
8
|
+
|
|
9
|
+
from vantage_cli.command_base import JsonOption, get_effective_json_output
|
|
10
|
+
from vantage_cli.config import attach_settings
|
|
11
|
+
from vantage_cli.exceptions import Abort
|
|
12
|
+
from vantage_cli.gql_client import create_async_graphql_client
|
|
13
|
+
|
|
14
|
+
from .render import render_cluster_deletion_result
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@attach_settings
|
|
18
|
+
async def delete_cluster(
|
|
19
|
+
ctx: typer.Context,
|
|
20
|
+
cluster_name: Annotated[str, typer.Argument(help="Name of the cluster to delete")],
|
|
21
|
+
force: Annotated[bool, typer.Option("--force", "-f", help="Skip confirmation prompt")] = False,
|
|
22
|
+
json_output: JsonOption = False,
|
|
23
|
+
):
|
|
24
|
+
"""Delete a Vantage cluster."""
|
|
25
|
+
# Get effective JSON output setting
|
|
26
|
+
effective_json = get_effective_json_output(ctx, json_output)
|
|
27
|
+
|
|
28
|
+
# Confirmation prompt unless force is used
|
|
29
|
+
if not force and not effective_json:
|
|
30
|
+
from rich.console import Console
|
|
31
|
+
from rich.prompt import Confirm
|
|
32
|
+
|
|
33
|
+
console = Console()
|
|
34
|
+
console.print(
|
|
35
|
+
f"\n[yellow]⚠️ You are about to delete cluster '[bold red]{cluster_name}[/bold red]'[/yellow]"
|
|
36
|
+
)
|
|
37
|
+
console.print("[yellow]This action cannot be undone![/yellow]")
|
|
38
|
+
|
|
39
|
+
if not Confirm.ask("Are you sure you want to proceed?"):
|
|
40
|
+
console.print("[dim]Deletion cancelled.[/dim]")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# GraphQL mutation for deleting a cluster
|
|
44
|
+
delete_mutation = """
|
|
45
|
+
mutation deleteCluster($clusterName: String!) {
|
|
46
|
+
deleteCluster(clusterName: $clusterName) {
|
|
47
|
+
... on ClusterDeleted {
|
|
48
|
+
message
|
|
49
|
+
}
|
|
50
|
+
... on ClusterNotFound {
|
|
51
|
+
message
|
|
52
|
+
}
|
|
53
|
+
... on InvalidProviderInput {
|
|
54
|
+
message
|
|
55
|
+
}
|
|
56
|
+
... on UnexpectedBehavior {
|
|
57
|
+
message
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# Create async GraphQL client
|
|
65
|
+
profile = getattr(ctx.obj, "profile", "default")
|
|
66
|
+
graphql_client = create_async_graphql_client(ctx.obj.settings, profile)
|
|
67
|
+
|
|
68
|
+
# Prepare deletion variables
|
|
69
|
+
delete_variables = {"clusterName": cluster_name}
|
|
70
|
+
|
|
71
|
+
# Execute the deletion mutation
|
|
72
|
+
logger.debug(f"Deleting cluster: {cluster_name}")
|
|
73
|
+
delete_response = await graphql_client.execute_async(delete_mutation, delete_variables)
|
|
74
|
+
|
|
75
|
+
# Extract deletion result
|
|
76
|
+
deletion_data = delete_response.get("deleteCluster", {})
|
|
77
|
+
|
|
78
|
+
# Log the response for debugging
|
|
79
|
+
logger.debug(f"Delete response: {deletion_data}")
|
|
80
|
+
|
|
81
|
+
# Determine success - if we get any response it likely succeeded
|
|
82
|
+
# The GraphQL union types make it tricky to detect success vs failure
|
|
83
|
+
success = bool(deletion_data) # If we got a response without error, consider it success
|
|
84
|
+
|
|
85
|
+
# Render deletion result
|
|
86
|
+
render_cluster_deletion_result(
|
|
87
|
+
cluster_name=cluster_name,
|
|
88
|
+
success=success,
|
|
89
|
+
json_output=effective_json,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
except Abort:
|
|
93
|
+
# Re-raise Abort exceptions as they contain user-friendly messages
|
|
94
|
+
raise
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Unexpected error deleting cluster '{cluster_name}': {e}")
|
|
97
|
+
raise Abort(
|
|
98
|
+
f"An unexpected error occurred while deleting cluster '{cluster_name}'.",
|
|
99
|
+
subject="Unexpected Error",
|
|
100
|
+
log_message=f"Unexpected error: {e}",
|
|
101
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Get cluster command for Vantage CLI."""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing_extensions import Annotated
|
|
7
|
+
|
|
8
|
+
from vantage_cli.command_base import JsonOption, get_effective_json_output
|
|
9
|
+
from vantage_cli.commands.clusters.utils import get_cluster_by_name
|
|
10
|
+
from vantage_cli.config import attach_settings
|
|
11
|
+
from vantage_cli.exceptions import Abort
|
|
12
|
+
|
|
13
|
+
from .render import render_cluster_details
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@attach_settings
|
|
17
|
+
async def get_cluster(
|
|
18
|
+
ctx: typer.Context,
|
|
19
|
+
cluster_name: Annotated[str, typer.Argument(help="Name of the cluster to get details for")],
|
|
20
|
+
json_output: JsonOption = False,
|
|
21
|
+
):
|
|
22
|
+
"""Get details of a specific Vantage cluster."""
|
|
23
|
+
cluster = await get_cluster_by_name(ctx=ctx, cluster_name=cluster_name)
|
|
24
|
+
if not cluster:
|
|
25
|
+
raise Abort(
|
|
26
|
+
f"No cluster found with name '{cluster_name}'.",
|
|
27
|
+
subject="Cluster Not Found",
|
|
28
|
+
log_message=f"Cluster '{cluster_name}' not found",
|
|
29
|
+
)
|
|
30
|
+
render_cluster_details(cluster, json_output=get_effective_json_output(ctx, json_output))
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""List clusters command."""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from vantage_cli.command_base import JsonOption, get_effective_json_output
|
|
9
|
+
from vantage_cli.config import attach_settings
|
|
10
|
+
from vantage_cli.exceptions import Abort
|
|
11
|
+
from vantage_cli.gql_client import create_async_graphql_client
|
|
12
|
+
from vantage_cli.render import render_quick_start_guide
|
|
13
|
+
|
|
14
|
+
from .render import render_clusters_table
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@attach_settings
|
|
18
|
+
async def list_clusters(
|
|
19
|
+
ctx: typer.Context,
|
|
20
|
+
json_output: JsonOption = False,
|
|
21
|
+
):
|
|
22
|
+
"""List all Vantage clusters."""
|
|
23
|
+
# GraphQL query to fetch clusters
|
|
24
|
+
query = """
|
|
25
|
+
query getClusters($first: Int!) {
|
|
26
|
+
clusters(first: $first) {
|
|
27
|
+
edges {
|
|
28
|
+
node {
|
|
29
|
+
name
|
|
30
|
+
status
|
|
31
|
+
clientId
|
|
32
|
+
description
|
|
33
|
+
ownerEmail
|
|
34
|
+
provider
|
|
35
|
+
cloudAccountId
|
|
36
|
+
creationParameters
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
total
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
variables = {"first": 100} # Fetch up to 100 clusters
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Create async GraphQL client
|
|
48
|
+
profile = getattr(ctx.obj, "profile", "default")
|
|
49
|
+
graphql_client = create_async_graphql_client(ctx.obj.settings, profile)
|
|
50
|
+
|
|
51
|
+
# Execute the query
|
|
52
|
+
logger.debug("Executing clusters query")
|
|
53
|
+
response_data = await graphql_client.execute_async(query, variables)
|
|
54
|
+
|
|
55
|
+
# Extract cluster data
|
|
56
|
+
clusters_data = response_data.get("clusters", {})
|
|
57
|
+
clusters = [edge["node"] for edge in clusters_data.get("edges", [])]
|
|
58
|
+
total_count = clusters_data.get("total", 0)
|
|
59
|
+
|
|
60
|
+
# Get effective JSON output setting
|
|
61
|
+
effective_json = get_effective_json_output(ctx, json_output)
|
|
62
|
+
|
|
63
|
+
# Render results using Rich table
|
|
64
|
+
render_clusters_table(
|
|
65
|
+
clusters,
|
|
66
|
+
title="Clusters List",
|
|
67
|
+
total_count=total_count,
|
|
68
|
+
json_output=effective_json,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Show quick start guide after listing clusters (only if not JSON output)
|
|
72
|
+
if clusters and not effective_json:
|
|
73
|
+
render_quick_start_guide()
|
|
74
|
+
|
|
75
|
+
except Abort:
|
|
76
|
+
# Re-raise Abort exceptions as they contain user-friendly messages
|
|
77
|
+
raise
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Unexpected error listing clusters: {e}")
|
|
80
|
+
raise Abort(
|
|
81
|
+
"An unexpected error occurred while listing clusters.",
|
|
82
|
+
subject="Unexpected Error",
|
|
83
|
+
log_message=f"Unexpected error: {e}",
|
|
84
|
+
)
|