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,529 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Profile management CRUD operations for Vantage CLI."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from loguru import logger
|
|
10
|
+
from rich import print_json
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from typing_extensions import Annotated
|
|
15
|
+
|
|
16
|
+
from vantage_cli.command_base import JsonOption, get_effective_json_output
|
|
17
|
+
from vantage_cli.config import (
|
|
18
|
+
Settings,
|
|
19
|
+
dump_settings,
|
|
20
|
+
get_active_profile,
|
|
21
|
+
init_user_filesystem,
|
|
22
|
+
set_active_profile,
|
|
23
|
+
)
|
|
24
|
+
from vantage_cli.constants import USER_CONFIG_FILE, USER_TOKEN_CACHE_DIR
|
|
25
|
+
from vantage_cli.exceptions import Abort
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_profile(
|
|
29
|
+
ctx: typer.Context,
|
|
30
|
+
profile_name: Annotated[str, typer.Argument(help="Name of the profile to create")],
|
|
31
|
+
api_base_url: Annotated[
|
|
32
|
+
str, typer.Option("--api-url", help="API base URL")
|
|
33
|
+
] = "https://apis.vantagecompute.ai",
|
|
34
|
+
tunnel_api_url: Annotated[
|
|
35
|
+
str, typer.Option("--tunnel-url", help="Tunnel API URL")
|
|
36
|
+
] = "https://tunnel.vantagecompute.ai",
|
|
37
|
+
oidc_base_url: Annotated[
|
|
38
|
+
str, typer.Option("--oidc-url", help="OIDC base URL")
|
|
39
|
+
] = "https://auth.vantagecompute.ai",
|
|
40
|
+
oidc_client_id: Annotated[str, typer.Option("--client-id", help="OIDC client ID")] = "default",
|
|
41
|
+
oidc_max_poll_time: Annotated[
|
|
42
|
+
int, typer.Option("--max-poll-time", help="OIDC max poll time in seconds")
|
|
43
|
+
] = 300,
|
|
44
|
+
force: Annotated[
|
|
45
|
+
bool, typer.Option("--force", "-f", help="Overwrite existing profile")
|
|
46
|
+
] = False,
|
|
47
|
+
activate: Annotated[
|
|
48
|
+
bool, typer.Option("--activate", help="Activate this profile after creation")
|
|
49
|
+
] = False,
|
|
50
|
+
json_output: JsonOption = False,
|
|
51
|
+
):
|
|
52
|
+
"""Create a new Vantage CLI profile."""
|
|
53
|
+
# Get the effective JSON output preference
|
|
54
|
+
effective_json = get_effective_json_output(ctx, json_output)
|
|
55
|
+
|
|
56
|
+
# Check if profile already exists
|
|
57
|
+
existing_profiles = _get_all_profiles()
|
|
58
|
+
|
|
59
|
+
if profile_name in existing_profiles and not force:
|
|
60
|
+
message = f"Profile '{profile_name}' already exists. Use --force to overwrite."
|
|
61
|
+
if effective_json:
|
|
62
|
+
result = {"success": False, "profile_name": profile_name, "message": message}
|
|
63
|
+
print_json(data=result)
|
|
64
|
+
return
|
|
65
|
+
else:
|
|
66
|
+
raise Abort(
|
|
67
|
+
message,
|
|
68
|
+
subject="Profile Exists",
|
|
69
|
+
log_message=f"Profile '{profile_name}' already exists",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Create the settings
|
|
73
|
+
try:
|
|
74
|
+
settings = Settings(
|
|
75
|
+
api_base_url=api_base_url,
|
|
76
|
+
oidc_base_url=oidc_base_url,
|
|
77
|
+
tunnel_api_url=tunnel_api_url,
|
|
78
|
+
oidc_client_id=oidc_client_id,
|
|
79
|
+
oidc_max_poll_time=oidc_max_poll_time,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Initialize filesystem for this profile
|
|
83
|
+
init_user_filesystem(profile_name)
|
|
84
|
+
|
|
85
|
+
# Save the settings
|
|
86
|
+
dump_settings(profile_name, settings)
|
|
87
|
+
|
|
88
|
+
# Set as active profile if requested
|
|
89
|
+
if activate:
|
|
90
|
+
set_active_profile(profile_name)
|
|
91
|
+
logger.info(f"Set '{profile_name}' as active profile")
|
|
92
|
+
|
|
93
|
+
logger.info(f"Created profile '{profile_name}'")
|
|
94
|
+
|
|
95
|
+
if effective_json:
|
|
96
|
+
result = {
|
|
97
|
+
"success": True,
|
|
98
|
+
"profile_name": profile_name,
|
|
99
|
+
"settings": settings.model_dump(),
|
|
100
|
+
"is_active": activate,
|
|
101
|
+
"message": f"Profile '{profile_name}' created successfully",
|
|
102
|
+
}
|
|
103
|
+
if activate:
|
|
104
|
+
result["message"] += " and set as active"
|
|
105
|
+
print_json(data=result)
|
|
106
|
+
else:
|
|
107
|
+
console = Console()
|
|
108
|
+
console.print()
|
|
109
|
+
message = f"✅ Profile '[bold cyan]{profile_name}[/bold cyan]' created successfully!"
|
|
110
|
+
if activate:
|
|
111
|
+
message += "\n🎯 Set as active profile!"
|
|
112
|
+
console.print(
|
|
113
|
+
Panel(message, title="[green]Profile Created[/green]", border_style="green")
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Show profile details
|
|
117
|
+
_render_profile_details(profile_name, settings)
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"Failed to create profile '{profile_name}': {str(e)}")
|
|
121
|
+
if effective_json:
|
|
122
|
+
result = {
|
|
123
|
+
"success": False,
|
|
124
|
+
"profile_name": profile_name,
|
|
125
|
+
"message": f"Failed to create profile: {str(e)}",
|
|
126
|
+
}
|
|
127
|
+
print_json(data=result)
|
|
128
|
+
else:
|
|
129
|
+
raise Abort(
|
|
130
|
+
f"Failed to create profile '{profile_name}': {str(e)}",
|
|
131
|
+
subject="Profile Creation Failed",
|
|
132
|
+
log_message=f"Profile creation error: {str(e)}",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def delete_profile(
|
|
137
|
+
ctx: typer.Context,
|
|
138
|
+
profile_name: Annotated[str, typer.Argument(help="Name of the profile to delete")],
|
|
139
|
+
force: Annotated[bool, typer.Option("--force", "-f", help="Skip confirmation prompt")] = False,
|
|
140
|
+
json_output: JsonOption = False,
|
|
141
|
+
):
|
|
142
|
+
"""Delete a Vantage CLI profile."""
|
|
143
|
+
# Get the effective JSON output preference
|
|
144
|
+
effective_json = get_effective_json_output(ctx, json_output)
|
|
145
|
+
|
|
146
|
+
# Check if profile exists
|
|
147
|
+
existing_profiles = _get_all_profiles()
|
|
148
|
+
|
|
149
|
+
if profile_name not in existing_profiles:
|
|
150
|
+
message = f"Profile '{profile_name}' does not exist."
|
|
151
|
+
if effective_json:
|
|
152
|
+
result = {"success": False, "profile_name": profile_name, "message": message}
|
|
153
|
+
print_json(data=result)
|
|
154
|
+
return
|
|
155
|
+
else:
|
|
156
|
+
raise Abort(
|
|
157
|
+
message,
|
|
158
|
+
subject="Profile Not Found",
|
|
159
|
+
log_message=f"Profile '{profile_name}' not found for deletion",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Don't allow deletion of 'default' profile without force
|
|
163
|
+
if profile_name == "default" and not force:
|
|
164
|
+
message = "Cannot delete 'default' profile without --force flag."
|
|
165
|
+
if effective_json:
|
|
166
|
+
result = {"success": False, "profile_name": profile_name, "message": message}
|
|
167
|
+
print_json(data=result)
|
|
168
|
+
return
|
|
169
|
+
else:
|
|
170
|
+
raise Abort(
|
|
171
|
+
message,
|
|
172
|
+
subject="Cannot Delete Default Profile",
|
|
173
|
+
log_message="Attempted to delete default profile without force",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Confirmation prompt unless force is used
|
|
177
|
+
if not force and not effective_json:
|
|
178
|
+
from rich.prompt import Confirm
|
|
179
|
+
|
|
180
|
+
console = Console()
|
|
181
|
+
console.print(
|
|
182
|
+
f"\n[yellow]⚠️ You are about to delete profile '[bold red]{profile_name}[/bold red]'[/yellow]"
|
|
183
|
+
)
|
|
184
|
+
console.print(
|
|
185
|
+
"[yellow]This will remove all settings and cached tokens for this profile![/yellow]"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if not Confirm.ask("Are you sure you want to proceed?"):
|
|
189
|
+
console.print("[dim]Deletion cancelled.[/dim]")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
# Remove from config file
|
|
194
|
+
if USER_CONFIG_FILE.exists():
|
|
195
|
+
all_profiles = json.loads(USER_CONFIG_FILE.read_text())
|
|
196
|
+
if profile_name in all_profiles:
|
|
197
|
+
del all_profiles[profile_name]
|
|
198
|
+
USER_CONFIG_FILE.write_text(json.dumps(all_profiles, indent=2))
|
|
199
|
+
|
|
200
|
+
# Clear token cache for this profile
|
|
201
|
+
_clear_profile_token_cache(profile_name)
|
|
202
|
+
|
|
203
|
+
# Remove profile token cache directory if it exists
|
|
204
|
+
profile_cache_dir = USER_TOKEN_CACHE_DIR / profile_name
|
|
205
|
+
if profile_cache_dir.exists():
|
|
206
|
+
import shutil
|
|
207
|
+
|
|
208
|
+
shutil.rmtree(profile_cache_dir)
|
|
209
|
+
|
|
210
|
+
logger.info(f"Deleted profile '{profile_name}'")
|
|
211
|
+
|
|
212
|
+
if effective_json:
|
|
213
|
+
result = {
|
|
214
|
+
"success": True,
|
|
215
|
+
"profile_name": profile_name,
|
|
216
|
+
"message": f"Profile '{profile_name}' deleted successfully",
|
|
217
|
+
}
|
|
218
|
+
print_json(data=result)
|
|
219
|
+
else:
|
|
220
|
+
console = Console()
|
|
221
|
+
console.print()
|
|
222
|
+
console.print(
|
|
223
|
+
Panel(
|
|
224
|
+
f"✅ Profile '[bold cyan]{profile_name}[/bold cyan]' deleted successfully!",
|
|
225
|
+
title="[green]Profile Deleted[/green]",
|
|
226
|
+
border_style="green",
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
console.print()
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"Failed to delete profile '{profile_name}': {str(e)}")
|
|
233
|
+
if effective_json:
|
|
234
|
+
result = {
|
|
235
|
+
"success": False,
|
|
236
|
+
"profile_name": profile_name,
|
|
237
|
+
"message": f"Failed to delete profile: {str(e)}",
|
|
238
|
+
}
|
|
239
|
+
print_json(data=result)
|
|
240
|
+
else:
|
|
241
|
+
raise Abort(
|
|
242
|
+
f"Failed to delete profile '{profile_name}': {str(e)}",
|
|
243
|
+
subject="Profile Deletion Failed",
|
|
244
|
+
log_message=f"Profile deletion error: {str(e)}",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_profile(
|
|
249
|
+
ctx: typer.Context,
|
|
250
|
+
profile_name: Annotated[str, typer.Argument(help="Name of the profile to get details for")],
|
|
251
|
+
json_output: JsonOption = False,
|
|
252
|
+
):
|
|
253
|
+
"""Get details of a specific Vantage CLI profile."""
|
|
254
|
+
# Get the effective JSON output preference
|
|
255
|
+
effective_json = get_effective_json_output(ctx, json_output)
|
|
256
|
+
|
|
257
|
+
# Check if profile exists
|
|
258
|
+
existing_profiles = _get_all_profiles()
|
|
259
|
+
|
|
260
|
+
if profile_name not in existing_profiles:
|
|
261
|
+
message = f"Profile '{profile_name}' does not exist."
|
|
262
|
+
if effective_json:
|
|
263
|
+
result = {"success": False, "profile_name": profile_name, "message": message}
|
|
264
|
+
print_json(data=result)
|
|
265
|
+
return
|
|
266
|
+
else:
|
|
267
|
+
raise Abort(
|
|
268
|
+
message,
|
|
269
|
+
subject="Profile Not Found",
|
|
270
|
+
log_message=f"Profile '{profile_name}' not found",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
# Load the profile settings
|
|
275
|
+
profile_data = existing_profiles[profile_name]
|
|
276
|
+
settings = Settings(**profile_data)
|
|
277
|
+
|
|
278
|
+
if effective_json:
|
|
279
|
+
result = {
|
|
280
|
+
"success": True,
|
|
281
|
+
"profile_name": profile_name,
|
|
282
|
+
"settings": settings.model_dump(),
|
|
283
|
+
}
|
|
284
|
+
print_json(data=result)
|
|
285
|
+
else:
|
|
286
|
+
_render_profile_details(profile_name, settings)
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.error(f"Failed to get profile '{profile_name}': {str(e)}")
|
|
290
|
+
if effective_json:
|
|
291
|
+
result = {
|
|
292
|
+
"success": False,
|
|
293
|
+
"profile_name": profile_name,
|
|
294
|
+
"message": f"Failed to get profile: {str(e)}",
|
|
295
|
+
}
|
|
296
|
+
print_json(data=result)
|
|
297
|
+
else:
|
|
298
|
+
raise Abort(
|
|
299
|
+
f"Failed to get profile '{profile_name}': {str(e)}",
|
|
300
|
+
subject="Profile Retrieval Failed",
|
|
301
|
+
log_message=f"Profile retrieval error: {str(e)}",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def list_profiles(ctx: typer.Context, json_output: JsonOption = False):
|
|
306
|
+
"""List all Vantage CLI profiles."""
|
|
307
|
+
# Get the effective JSON output preference
|
|
308
|
+
effective_json = get_effective_json_output(ctx, json_output)
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
# Get active profile from file system
|
|
312
|
+
active_profile = get_active_profile()
|
|
313
|
+
|
|
314
|
+
# Get all profiles
|
|
315
|
+
all_profiles = _get_all_profiles()
|
|
316
|
+
|
|
317
|
+
if not all_profiles:
|
|
318
|
+
if effective_json:
|
|
319
|
+
result = {"profiles": [], "total": 0, "current_profile": active_profile}
|
|
320
|
+
print_json(data=result)
|
|
321
|
+
else:
|
|
322
|
+
console = Console()
|
|
323
|
+
console.print()
|
|
324
|
+
console.print(Panel("No profiles found.", title="[yellow]No Profiles"))
|
|
325
|
+
console.print()
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
if effective_json:
|
|
329
|
+
# Prepare profiles with additional metadata
|
|
330
|
+
profiles_list = []
|
|
331
|
+
for name, settings_data in all_profiles.items():
|
|
332
|
+
profile_info = {
|
|
333
|
+
"name": name,
|
|
334
|
+
"settings": settings_data,
|
|
335
|
+
"is_current": name == active_profile,
|
|
336
|
+
}
|
|
337
|
+
profiles_list.append(profile_info)
|
|
338
|
+
|
|
339
|
+
result = {
|
|
340
|
+
"profiles": profiles_list,
|
|
341
|
+
"total": len(profiles_list),
|
|
342
|
+
"current_profile": active_profile,
|
|
343
|
+
}
|
|
344
|
+
print_json(data=result)
|
|
345
|
+
else:
|
|
346
|
+
_render_profiles_table(all_profiles, active_profile)
|
|
347
|
+
|
|
348
|
+
except Exception as e:
|
|
349
|
+
logger.error(f"Failed to list profiles: {str(e)}")
|
|
350
|
+
if effective_json:
|
|
351
|
+
result = {"success": False, "message": f"Failed to list profiles: {str(e)}"}
|
|
352
|
+
print_json(data=result)
|
|
353
|
+
else:
|
|
354
|
+
raise Abort(
|
|
355
|
+
f"Failed to list profiles: {str(e)}",
|
|
356
|
+
subject="Profile Listing Failed",
|
|
357
|
+
log_message=f"Profile listing error: {str(e)}",
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def use_profile(
|
|
362
|
+
ctx: typer.Context,
|
|
363
|
+
profile_name: Annotated[str, typer.Argument(help="Name of the profile to activate")],
|
|
364
|
+
json_output: JsonOption = False,
|
|
365
|
+
):
|
|
366
|
+
"""Activate a profile for use in the current session."""
|
|
367
|
+
# Get the effective JSON output preference
|
|
368
|
+
effective_json = get_effective_json_output(ctx, json_output)
|
|
369
|
+
|
|
370
|
+
# Check if profile exists
|
|
371
|
+
existing_profiles = _get_all_profiles()
|
|
372
|
+
|
|
373
|
+
if profile_name not in existing_profiles:
|
|
374
|
+
message = f"Profile '{profile_name}' does not exist."
|
|
375
|
+
if effective_json:
|
|
376
|
+
result = {
|
|
377
|
+
"success": False,
|
|
378
|
+
"profile_name": profile_name,
|
|
379
|
+
"message": message,
|
|
380
|
+
"available_profiles": list(existing_profiles.keys()),
|
|
381
|
+
}
|
|
382
|
+
print_json(data=result)
|
|
383
|
+
return
|
|
384
|
+
else:
|
|
385
|
+
console = Console()
|
|
386
|
+
console.print()
|
|
387
|
+
console.print(
|
|
388
|
+
Panel(
|
|
389
|
+
f"❌ Profile '[bold red]{profile_name}[/bold red]' does not exist.\n\n"
|
|
390
|
+
f"Available profiles: {', '.join(existing_profiles.keys()) if existing_profiles else 'None'}",
|
|
391
|
+
title="[red]Profile Not Found[/red]",
|
|
392
|
+
border_style="red",
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
raise typer.Exit(1)
|
|
396
|
+
|
|
397
|
+
# Set as active profile
|
|
398
|
+
try:
|
|
399
|
+
set_active_profile(profile_name)
|
|
400
|
+
logger.info(f"Set '{profile_name}' as active profile")
|
|
401
|
+
|
|
402
|
+
if effective_json:
|
|
403
|
+
result = {
|
|
404
|
+
"success": True,
|
|
405
|
+
"profile_name": profile_name,
|
|
406
|
+
"message": f"Profile '{profile_name}' is now active",
|
|
407
|
+
"note": "This profile will be used for all future commands until changed",
|
|
408
|
+
}
|
|
409
|
+
print_json(data=result)
|
|
410
|
+
else:
|
|
411
|
+
console = Console()
|
|
412
|
+
console.print()
|
|
413
|
+
console.print(
|
|
414
|
+
Panel(
|
|
415
|
+
f"🎯 Profile '[bold cyan]{profile_name}[/bold cyan]' is now active!\n\n"
|
|
416
|
+
f"This profile will be used for all future commands until changed.",
|
|
417
|
+
title="[green]Profile Activated[/green]",
|
|
418
|
+
border_style="green",
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
except Exception as e:
|
|
423
|
+
logger.error(f"Failed to set '{profile_name}' as active: {str(e)}")
|
|
424
|
+
if effective_json:
|
|
425
|
+
result = {
|
|
426
|
+
"success": False,
|
|
427
|
+
"profile_name": profile_name,
|
|
428
|
+
"message": f"Failed to activate profile: {str(e)}",
|
|
429
|
+
}
|
|
430
|
+
print_json(data=result)
|
|
431
|
+
else:
|
|
432
|
+
raise Abort(
|
|
433
|
+
f"Failed to activate profile '{profile_name}': {str(e)}",
|
|
434
|
+
subject="Profile Activation Failed",
|
|
435
|
+
log_message=f"Profile activation error: {str(e)}",
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _get_all_profiles() -> Dict[str, Any]:
|
|
440
|
+
"""Get all profiles from the config file."""
|
|
441
|
+
if not USER_CONFIG_FILE.exists():
|
|
442
|
+
return {}
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
return json.loads(USER_CONFIG_FILE.read_text())
|
|
446
|
+
except (json.JSONDecodeError, FileNotFoundError):
|
|
447
|
+
return {}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _render_profiles_table(profiles: Dict[str, Any], current_profile: str) -> None:
|
|
451
|
+
"""Render a table of all profiles."""
|
|
452
|
+
console = Console()
|
|
453
|
+
|
|
454
|
+
# Create the table
|
|
455
|
+
table = Table(
|
|
456
|
+
title="Vantage CLI Profiles",
|
|
457
|
+
caption=f"Items: {len(profiles)} • Current profile: [bold]{current_profile}[/bold]",
|
|
458
|
+
show_header=True,
|
|
459
|
+
header_style="bold white",
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Add columns
|
|
463
|
+
table.add_column("Name", style="bold cyan")
|
|
464
|
+
table.add_column("Current", style="green", justify="center")
|
|
465
|
+
table.add_column("API Base URL", style="blue")
|
|
466
|
+
table.add_column("OIDC Base URL", style="yellow")
|
|
467
|
+
table.add_column("Tunnel API URL", style="yellow")
|
|
468
|
+
table.add_column("Client ID", style="white")
|
|
469
|
+
|
|
470
|
+
# Add rows
|
|
471
|
+
for name, settings_data in profiles.items():
|
|
472
|
+
current_marker = "✓" if name == current_profile else ""
|
|
473
|
+
|
|
474
|
+
# Handle both old and new settings format
|
|
475
|
+
api_url = settings_data.get("api_base_url", "N/A")
|
|
476
|
+
oidc_url = settings_data.get("oidc_base_url", "N/A")
|
|
477
|
+
tunnel_url = settings_data.get("tunnel_api_url", "N/A")
|
|
478
|
+
client_id = settings_data.get("oidc_client_id", "N/A")
|
|
479
|
+
|
|
480
|
+
table.add_row(
|
|
481
|
+
name, current_marker, str(api_url), str(oidc_url), str(tunnel_url), str(client_id)
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
console.print()
|
|
485
|
+
console.print(table)
|
|
486
|
+
console.print()
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _render_profile_details(profile_name: str, settings: Settings) -> None:
|
|
490
|
+
"""Render detailed information for a single profile."""
|
|
491
|
+
console = Console()
|
|
492
|
+
|
|
493
|
+
# Create a table matching the whoami command style
|
|
494
|
+
table = Table(
|
|
495
|
+
title=f"Profile Details: {profile_name}", show_header=True, header_style="bold white"
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
table.add_column("Property", style="bold cyan")
|
|
499
|
+
table.add_column("Value", style="white")
|
|
500
|
+
|
|
501
|
+
# Add profile information
|
|
502
|
+
table.add_row("Profile Name", profile_name)
|
|
503
|
+
table.add_row("API Base URL", settings.api_base_url)
|
|
504
|
+
table.add_row("OIDC Base URL", settings.oidc_base_url)
|
|
505
|
+
table.add_row("Tunnel Base URL", settings.tunnel_api_url)
|
|
506
|
+
table.add_row("OIDC Domain", settings.oidc_domain)
|
|
507
|
+
table.add_row("OIDC Client ID", settings.oidc_client_id)
|
|
508
|
+
table.add_row("OIDC Max Poll Time", f"{settings.oidc_max_poll_time} seconds")
|
|
509
|
+
table.add_row("Supported Clouds", ", ".join(settings.supported_clouds))
|
|
510
|
+
|
|
511
|
+
console.print()
|
|
512
|
+
console.print(table)
|
|
513
|
+
console.print()
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _clear_profile_token_cache(profile: str) -> None:
|
|
517
|
+
"""Clear token cache for a specific profile."""
|
|
518
|
+
token_dir = USER_TOKEN_CACHE_DIR / profile
|
|
519
|
+
if token_dir.exists():
|
|
520
|
+
access_token_path = token_dir / "access.token"
|
|
521
|
+
refresh_token_path = token_dir / "refresh.token"
|
|
522
|
+
|
|
523
|
+
logger.debug(f"Removing access token at {access_token_path}")
|
|
524
|
+
if access_token_path.exists():
|
|
525
|
+
access_token_path.unlink()
|
|
526
|
+
|
|
527
|
+
logger.debug(f"Removing refresh token at {refresh_token_path}")
|
|
528
|
+
if refresh_token_path.exists():
|
|
529
|
+
refresh_token_path.unlink()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Render helpers for profile command output formatting."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def render_profile_operation_result(
|
|
13
|
+
operation: str,
|
|
14
|
+
profile_name: str,
|
|
15
|
+
success: bool = True,
|
|
16
|
+
details: Optional[Dict[str, Any]] = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Render the result of a profile operation.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
operation: The operation performed (create, delete, update)
|
|
22
|
+
profile_name: Name of the profile
|
|
23
|
+
success: Whether the operation was successful
|
|
24
|
+
details: Additional details about the operation
|
|
25
|
+
"""
|
|
26
|
+
console = Console()
|
|
27
|
+
console.print()
|
|
28
|
+
|
|
29
|
+
status_icon = "✅" if success else "❌"
|
|
30
|
+
status_color = "green" if success else "red"
|
|
31
|
+
action_text = f"{operation.title()}d" if success else f"{operation.title()} Failed"
|
|
32
|
+
|
|
33
|
+
console.print(
|
|
34
|
+
Panel(
|
|
35
|
+
f"{status_icon} Profile '[bold cyan]{profile_name}[/bold cyan]' {operation} {'successful' if success else 'failed'}!",
|
|
36
|
+
title=f"[{status_color}]{action_text}[/{status_color}]",
|
|
37
|
+
border_style=status_color,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if details:
|
|
42
|
+
# Show additional details if provided
|
|
43
|
+
details_table = Table(title="Profile Details", show_header=False, box=None, padding=(0, 1))
|
|
44
|
+
details_table.add_column("Field", style="bold blue", width=20)
|
|
45
|
+
details_table.add_column("Value", style="white")
|
|
46
|
+
|
|
47
|
+
for key, value in details.items():
|
|
48
|
+
if value is not None:
|
|
49
|
+
display_key = key.replace("_", " ").title()
|
|
50
|
+
details_table.add_row(display_key, str(value))
|
|
51
|
+
|
|
52
|
+
console.print()
|
|
53
|
+
console.print(details_table)
|
|
54
|
+
|
|
55
|
+
console.print()
|