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
vantage_cli/main.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Main typer app for vantage-cli."""
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from jose import jwt
|
|
11
|
+
from loguru import logger
|
|
12
|
+
from rich import print_json
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from typing_extensions import Annotated
|
|
17
|
+
|
|
18
|
+
from vantage_cli import AsyncTyper
|
|
19
|
+
from vantage_cli.apps import apps_app
|
|
20
|
+
from vantage_cli.auth import extract_persona, fetch_auth_tokens, is_token_expired
|
|
21
|
+
from vantage_cli.cache import clear_token_cache, load_tokens_from_cache, with_cache
|
|
22
|
+
from vantage_cli.client import attach_client
|
|
23
|
+
from vantage_cli.commands.clouds import clouds_app
|
|
24
|
+
from vantage_cli.commands.clusters import cluster_app
|
|
25
|
+
from vantage_cli.commands.profile import profile_app
|
|
26
|
+
from vantage_cli.config import (
|
|
27
|
+
attach_settings,
|
|
28
|
+
clear_settings,
|
|
29
|
+
ensure_default_profile_exists,
|
|
30
|
+
get_active_profile,
|
|
31
|
+
)
|
|
32
|
+
from vantage_cli.exceptions import handle_abort
|
|
33
|
+
from vantage_cli.schemas import CliContext, Persona, TokenSet
|
|
34
|
+
|
|
35
|
+
app = AsyncTyper(
|
|
36
|
+
name="Vantage CLI",
|
|
37
|
+
add_completion=True,
|
|
38
|
+
context_settings={
|
|
39
|
+
"allow_extra_args": True,
|
|
40
|
+
"allow_interspersed_args": True,
|
|
41
|
+
"max_content_width": 120,
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
app.add_typer(clouds_app, name="clouds")
|
|
46
|
+
app.add_typer(cluster_app, name="clusters")
|
|
47
|
+
app.add_typer(profile_app, name="profile")
|
|
48
|
+
app.add_typer(apps_app, name="apps")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def setup_logging(verbose: bool = False):
|
|
52
|
+
"""Configure logging based on verbosity level."""
|
|
53
|
+
logger.remove()
|
|
54
|
+
|
|
55
|
+
if verbose:
|
|
56
|
+
logger.add(sys.stdout, level="DEBUG")
|
|
57
|
+
|
|
58
|
+
logger.debug("Logging initialized")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.callback()
|
|
62
|
+
def main(
|
|
63
|
+
ctx: typer.Context,
|
|
64
|
+
verbose: Annotated[
|
|
65
|
+
bool, typer.Option("--verbose", "-v", help="Enable verbose output")
|
|
66
|
+
] = False,
|
|
67
|
+
profile: Annotated[
|
|
68
|
+
Optional[str], typer.Option("--profile", "-p", help="Specify the profile to use")
|
|
69
|
+
] = None,
|
|
70
|
+
json: Annotated[bool, typer.Option("--json", "-j", help="Output in JSON format")] = False,
|
|
71
|
+
):
|
|
72
|
+
"""Handle global options for the application."""
|
|
73
|
+
if ctx.invoked_subcommand is None:
|
|
74
|
+
console = Console()
|
|
75
|
+
console.print("No command provided. Please check [bold magenta]usage[/bold magenta]")
|
|
76
|
+
raise typer.Exit()
|
|
77
|
+
|
|
78
|
+
ensure_default_profile_exists()
|
|
79
|
+
|
|
80
|
+
# Use explicit profile if provided, otherwise get the active profile
|
|
81
|
+
active_profile = profile if profile is not None else get_active_profile()
|
|
82
|
+
|
|
83
|
+
setup_logging(verbose=verbose)
|
|
84
|
+
|
|
85
|
+
cli_ctx = CliContext(profile=active_profile, verbose=verbose, json_output=json)
|
|
86
|
+
ctx.obj = cli_ctx
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _check_existing_login(profile: str) -> Optional[str]:
|
|
90
|
+
"""Check if user is already logged in with a valid token.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Email of logged in user if valid token exists, None otherwise
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
# Try to load tokens from cache
|
|
97
|
+
token_set = load_tokens_from_cache(profile)
|
|
98
|
+
|
|
99
|
+
# Check if access token is valid (not expired)
|
|
100
|
+
if token_set.access_token and not is_token_expired(token_set.access_token):
|
|
101
|
+
# Extract email from token for display
|
|
102
|
+
try:
|
|
103
|
+
persona = extract_persona(profile, token_set)
|
|
104
|
+
return persona.identity_data.email
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.debug(f"Could not extract persona from existing token: {e}")
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
# Token cache doesn't exist or other error - user not logged in
|
|
111
|
+
logger.debug(f"No valid existing login found: {e}")
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@app.command()
|
|
118
|
+
@handle_abort
|
|
119
|
+
@with_cache
|
|
120
|
+
@attach_settings
|
|
121
|
+
@attach_client
|
|
122
|
+
async def login(ctx: typer.Context):
|
|
123
|
+
"""Authenticate against the Vantage CLI by obtaining an authentication token."""
|
|
124
|
+
# Check if user is already logged in with a valid token
|
|
125
|
+
existing_email = _check_existing_login(ctx.obj.profile)
|
|
126
|
+
if existing_email:
|
|
127
|
+
console = Console()
|
|
128
|
+
console.print()
|
|
129
|
+
console.print(
|
|
130
|
+
Panel(
|
|
131
|
+
f"Profile: [bold]{ctx.obj.profile}[/bold]\n\n"
|
|
132
|
+
f"✅ Valid token already exists for user: [bold cyan]{existing_email}[/bold cyan]\n\n"
|
|
133
|
+
f"If you want to generate a new token, please run '[bold magenta]vantage logout[/bold magenta]' first.",
|
|
134
|
+
title="[green]Already Authenticated[/green]",
|
|
135
|
+
border_style="green",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
console.print()
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
token_set: TokenSet = await fetch_auth_tokens(ctx.obj)
|
|
142
|
+
persona: Persona = extract_persona(ctx.obj.profile, token_set)
|
|
143
|
+
console = Console()
|
|
144
|
+
console.print()
|
|
145
|
+
console.print(
|
|
146
|
+
Panel(
|
|
147
|
+
f"Profile: [bold]{ctx.obj.profile}[/bold]\n\n"
|
|
148
|
+
f"✅ Successful authentication: [bold cyan]{persona.identity_data.email}[/bold cyan]\n\n"
|
|
149
|
+
"You can now use the CLI to interact with Vantage Compute platform.",
|
|
150
|
+
title="[green]Successful Authentication[/green]",
|
|
151
|
+
border_style="green",
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
console.print()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.command()
|
|
158
|
+
@handle_abort
|
|
159
|
+
@with_cache
|
|
160
|
+
async def logout(ctx: typer.Context):
|
|
161
|
+
"""Log out of the vantage-cli and clear saved user credentials."""
|
|
162
|
+
existing_email = _check_existing_login(ctx.obj.profile)
|
|
163
|
+
if existing_email:
|
|
164
|
+
console = Console()
|
|
165
|
+
console.print()
|
|
166
|
+
console.print(
|
|
167
|
+
Panel(
|
|
168
|
+
f"Profile: [bold]{ctx.obj.profile}[/bold]\n\n"
|
|
169
|
+
f"✅ [bold]User:[/bold] {existing_email}\n\n"
|
|
170
|
+
f"Please run '[bold magenta]vantage login[/bold magenta]' to log back in.",
|
|
171
|
+
title="[green]Successfully Signed Out[/green]",
|
|
172
|
+
border_style="green",
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
console.print()
|
|
176
|
+
clear_token_cache(ctx.obj.profile)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@app.command()
|
|
180
|
+
@with_cache
|
|
181
|
+
@attach_settings
|
|
182
|
+
async def whoami(ctx: typer.Context):
|
|
183
|
+
"""Display information about the currently authenticated user."""
|
|
184
|
+
# Get the JSON output preference from context
|
|
185
|
+
json_output = getattr(ctx.obj, "json_output", False)
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
# Extract persona from cached tokens
|
|
189
|
+
persona: Persona = extract_persona(ctx.obj.profile)
|
|
190
|
+
|
|
191
|
+
token_info = {}
|
|
192
|
+
try:
|
|
193
|
+
token_data = jwt.decode(
|
|
194
|
+
persona.token_set.access_token,
|
|
195
|
+
"", # Empty key is acceptable when verify_signature is False
|
|
196
|
+
options={
|
|
197
|
+
"verify_signature": False,
|
|
198
|
+
"verify_aud": False,
|
|
199
|
+
"verify_exp": False, # Don't verify expiration for display
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Extract additional fields if available
|
|
204
|
+
if "exp" in token_data:
|
|
205
|
+
exp_timestamp = token_data["exp"]
|
|
206
|
+
exp_datetime = datetime.datetime.fromtimestamp(exp_timestamp)
|
|
207
|
+
token_info["token_expires_at"] = exp_datetime.isoformat()
|
|
208
|
+
token_info["token_expired"] = exp_datetime < datetime.datetime.now()
|
|
209
|
+
|
|
210
|
+
if "iat" in token_data:
|
|
211
|
+
iat_timestamp = token_data["iat"]
|
|
212
|
+
iat_datetime = datetime.datetime.fromtimestamp(iat_timestamp)
|
|
213
|
+
token_info["token_issued_at"] = iat_datetime.isoformat()
|
|
214
|
+
|
|
215
|
+
if "sub" in token_data:
|
|
216
|
+
token_info["user_id"] = token_data["sub"]
|
|
217
|
+
|
|
218
|
+
if "name" in token_data:
|
|
219
|
+
token_info["name"] = token_data["name"]
|
|
220
|
+
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.debug(f"Could not decode token for additional info: {e}")
|
|
223
|
+
|
|
224
|
+
# Prepare user information
|
|
225
|
+
user_info = {
|
|
226
|
+
"email": persona.identity_data.email,
|
|
227
|
+
"client_id": persona.identity_data.client_id,
|
|
228
|
+
"profile": ctx.obj.profile,
|
|
229
|
+
"logged_in": True,
|
|
230
|
+
**token_info,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if json_output:
|
|
234
|
+
print_json(data=user_info)
|
|
235
|
+
else:
|
|
236
|
+
console = Console()
|
|
237
|
+
console.print()
|
|
238
|
+
|
|
239
|
+
# Create a table for user information
|
|
240
|
+
table = Table(
|
|
241
|
+
title="Current User Information", show_header=True, header_style="bold white"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
table.add_column("Property", style="bold cyan")
|
|
245
|
+
table.add_column("Value", style="white")
|
|
246
|
+
|
|
247
|
+
table.add_row("Email", user_info["email"] or "Not available")
|
|
248
|
+
table.add_row("Client ID", user_info["client_id"])
|
|
249
|
+
table.add_row("Profile", user_info["profile"])
|
|
250
|
+
|
|
251
|
+
if "name" in user_info:
|
|
252
|
+
table.add_row("Name", user_info["name"])
|
|
253
|
+
|
|
254
|
+
if "user_id" in user_info:
|
|
255
|
+
table.add_row("User ID", user_info["user_id"])
|
|
256
|
+
|
|
257
|
+
if "token_issued_at" in user_info:
|
|
258
|
+
table.add_row("Token Issued", user_info["token_issued_at"])
|
|
259
|
+
|
|
260
|
+
if "token_expires_at" in user_info:
|
|
261
|
+
exp_status = "❌ Expired" if user_info.get("token_expired", False) else "✅ Valid"
|
|
262
|
+
table.add_row("Token Expires", f"{user_info['token_expires_at']} ({exp_status})")
|
|
263
|
+
|
|
264
|
+
table.add_row("Status", "✅ Logged in")
|
|
265
|
+
|
|
266
|
+
console.print(table)
|
|
267
|
+
console.print()
|
|
268
|
+
|
|
269
|
+
except Exception as e:
|
|
270
|
+
logger.error(f"Failed to get user information: {str(e)}")
|
|
271
|
+
|
|
272
|
+
error_info = {
|
|
273
|
+
"logged_in": False,
|
|
274
|
+
"error": "Not authenticated or token expired",
|
|
275
|
+
"profile": ctx.obj.profile,
|
|
276
|
+
"message": "Please run 'vantage login' to authenticate",
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if json_output:
|
|
280
|
+
print_json(data=error_info)
|
|
281
|
+
else:
|
|
282
|
+
console = Console()
|
|
283
|
+
console.print()
|
|
284
|
+
console.print(
|
|
285
|
+
Panel(
|
|
286
|
+
"❌ Not authenticated or token expired\n\n"
|
|
287
|
+
f"Current profile: [bold]{ctx.obj.profile}[/bold]\n"
|
|
288
|
+
f"Please run [bold]vantage login[/bold] to authenticate",
|
|
289
|
+
title="[red]Authentication Required[/red]",
|
|
290
|
+
border_style="red",
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
console.print()
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@app.command()
|
|
297
|
+
async def clear_config():
|
|
298
|
+
"""Clear all user tokens and config."""
|
|
299
|
+
clear_settings()
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if __name__ == "__main__":
|
|
303
|
+
app()
|
vantage_cli/render.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# © 2025 Vantage Compute, Inc. All rights reserved.
|
|
2
|
+
# Confidential and proprietary. Unauthorized use prohibited.
|
|
3
|
+
"""Rendering utilities for CLI output."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
import snick
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StyleMapper:
|
|
13
|
+
"""Provide a mapper that can set Rich styles for rendered output of data tables.
|
|
14
|
+
|
|
15
|
+
Similar to jobbergate-cli's StyleMapper, this class provides a way to define
|
|
16
|
+
styles that should be applied to columns of tables.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, **colors: str):
|
|
20
|
+
"""Initialize the StyleMapper with color mappings."""
|
|
21
|
+
self.colors = colors
|
|
22
|
+
|
|
23
|
+
def map_style(self, column: str) -> Dict[str, Any]:
|
|
24
|
+
"""Map a column name to the style that should be used to render it."""
|
|
25
|
+
color = self.colors.get(column, "white")
|
|
26
|
+
return {
|
|
27
|
+
"style": color,
|
|
28
|
+
"header_style": f"bold {color}",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def render_quick_start_guide() -> None:
|
|
33
|
+
"""Render quick start guide for the CLI."""
|
|
34
|
+
"""Render a quick start guide panel similar to jobbergate-cli's demo."""
|
|
35
|
+
message = snick.dedent(
|
|
36
|
+
"""
|
|
37
|
+
• To view cluster details, use the command: vantage clusters get
|
|
38
|
+
|
|
39
|
+
• To create a new cluster, use the command: vantage clusters create --help
|
|
40
|
+
|
|
41
|
+
• For more information on any command run it with the --help option.
|
|
42
|
+
|
|
43
|
+
• To check all the available commands, refer to: vantage --help
|
|
44
|
+
"""
|
|
45
|
+
).strip()
|
|
46
|
+
|
|
47
|
+
console = Console()
|
|
48
|
+
panel = Panel(
|
|
49
|
+
message,
|
|
50
|
+
title="[bold magenta]Quick Start Guide for Vantage-CLI[/bold magenta]",
|
|
51
|
+
border_style="blue",
|
|
52
|
+
expand=False,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
console.print()
|
|
56
|
+
console.print(panel)
|
vantage_cli/schemas.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Data models and schemas for the Vantage CLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from vantage_cli.config import Settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TokenSet(BaseModel):
|
|
12
|
+
"""OAuth token set containing access and refresh tokens."""
|
|
13
|
+
|
|
14
|
+
access_token: str
|
|
15
|
+
refresh_token: Optional[str] = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IdentityData(BaseModel):
|
|
19
|
+
"""User identity information extracted from tokens."""
|
|
20
|
+
|
|
21
|
+
client_id: str
|
|
22
|
+
email: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Persona(BaseModel):
|
|
26
|
+
"""User persona combining token set and identity data."""
|
|
27
|
+
|
|
28
|
+
token_set: TokenSet
|
|
29
|
+
identity_data: IdentityData
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DeviceCodeData(BaseModel):
|
|
33
|
+
"""OAuth device code flow data."""
|
|
34
|
+
|
|
35
|
+
device_code: str
|
|
36
|
+
verification_uri_complete: str
|
|
37
|
+
interval: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CliContext(BaseModel, arbitrary_types_allowed=True):
|
|
41
|
+
"""CLI context for command execution."""
|
|
42
|
+
|
|
43
|
+
profile: str = "default"
|
|
44
|
+
verbose: bool = False
|
|
45
|
+
json_output: bool = False
|
|
46
|
+
persona: Optional[Persona] = None
|
|
47
|
+
client: Optional[httpx.AsyncClient] = None
|
|
48
|
+
settings: Optional[Settings] = None
|
vantage_cli/time_loop.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Time-based loop utilities for the Vantage CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
import pendulum
|
|
8
|
+
from rich.progress import Progress
|
|
9
|
+
|
|
10
|
+
from vantage_cli.exceptions import VantageCliError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Tick:
|
|
15
|
+
"""Represents a single tick in a time loop with timing information."""
|
|
16
|
+
|
|
17
|
+
counter: int
|
|
18
|
+
elapsed: pendulum.Duration
|
|
19
|
+
total_elapsed: pendulum.Duration
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TimeLoop:
|
|
23
|
+
"""Time-based loop with progress tracking and duration management."""
|
|
24
|
+
|
|
25
|
+
advent: pendulum.DateTime | None
|
|
26
|
+
moment: pendulum.DateTime | None
|
|
27
|
+
last_moment: pendulum.DateTime | None
|
|
28
|
+
counter: int
|
|
29
|
+
progress: Progress | None
|
|
30
|
+
duration: pendulum.Duration
|
|
31
|
+
message: str
|
|
32
|
+
color: str
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
duration: pendulum.Duration | int,
|
|
37
|
+
message: str = "Processing",
|
|
38
|
+
color: str = "green",
|
|
39
|
+
):
|
|
40
|
+
self.advent = None
|
|
41
|
+
self.moment = None
|
|
42
|
+
self.last_moment = None
|
|
43
|
+
self.counter = 0
|
|
44
|
+
self.progress = None
|
|
45
|
+
if isinstance(duration, int):
|
|
46
|
+
VantageCliError.require_condition(
|
|
47
|
+
duration > 0,
|
|
48
|
+
"The duration must be a positive integer",
|
|
49
|
+
)
|
|
50
|
+
self.duration = pendulum.duration(seconds=duration)
|
|
51
|
+
else:
|
|
52
|
+
self.duration = duration
|
|
53
|
+
self.message = message
|
|
54
|
+
self.color = color
|
|
55
|
+
|
|
56
|
+
def __del__(self):
|
|
57
|
+
"""Explicitly clear the progress meter if the time-loop is destroyed."""
|
|
58
|
+
self.clear()
|
|
59
|
+
|
|
60
|
+
def __iter__(self) -> "TimeLoop":
|
|
61
|
+
"""Start the iterator.
|
|
62
|
+
|
|
63
|
+
Creates and starts the progress meter
|
|
64
|
+
"""
|
|
65
|
+
now = pendulum.now()
|
|
66
|
+
self.advent = now
|
|
67
|
+
self.last_moment = now
|
|
68
|
+
self.moment = now
|
|
69
|
+
self.counter = 0
|
|
70
|
+
self.progress = Progress()
|
|
71
|
+
self.progress.add_task(
|
|
72
|
+
f"[{self.color}]{self.message}...",
|
|
73
|
+
total=self.duration.total_seconds(),
|
|
74
|
+
)
|
|
75
|
+
self.progress.start()
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
def __next__(self) -> Tick:
|
|
79
|
+
"""Iterate the time loop and return a tick.
|
|
80
|
+
|
|
81
|
+
If the duration is complete, clear the progress meter and stop iteration.
|
|
82
|
+
"""
|
|
83
|
+
progress: Progress = VantageCliError.enforce_defined(
|
|
84
|
+
self.progress,
|
|
85
|
+
"Progress bar has not been initialized...this should not happen",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self.counter += 1
|
|
89
|
+
self.last_moment = self.moment
|
|
90
|
+
self.moment = pendulum.now()
|
|
91
|
+
if self.moment is not None and self.last_moment is not None:
|
|
92
|
+
elapsed: pendulum.Duration = self.moment - self.last_moment
|
|
93
|
+
else:
|
|
94
|
+
elapsed = pendulum.duration(seconds=0)
|
|
95
|
+
if self.moment is not None and self.advent is not None:
|
|
96
|
+
total_elapsed: pendulum.Duration = self.moment - self.advent
|
|
97
|
+
else:
|
|
98
|
+
total_elapsed = pendulum.duration(seconds=0)
|
|
99
|
+
|
|
100
|
+
for task_id in progress.task_ids:
|
|
101
|
+
progress.advance(task_id, elapsed.total_seconds())
|
|
102
|
+
|
|
103
|
+
if progress.finished:
|
|
104
|
+
self.clear()
|
|
105
|
+
raise StopIteration
|
|
106
|
+
|
|
107
|
+
return Tick(
|
|
108
|
+
counter=self.counter,
|
|
109
|
+
elapsed=elapsed,
|
|
110
|
+
total_elapsed=total_elapsed,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def clear(self):
|
|
114
|
+
"""Clear the time-loop.
|
|
115
|
+
|
|
116
|
+
Stops the progress meter (if set) and reset moments, counter, progress meter.
|
|
117
|
+
"""
|
|
118
|
+
if self.progress is not None:
|
|
119
|
+
self.progress.stop()
|
|
120
|
+
self.counter = 0
|
|
121
|
+
self.progress = None
|
|
122
|
+
now = pendulum.now()
|
|
123
|
+
self.moment = now
|
|
124
|
+
self.last_moment = now
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vantage-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: The Vantage Compute CLI.
|
|
5
|
+
Author-email: jamesbeedy <james@vantagecompute.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
9
|
+
Requires-Dist: anyio>=3.6.0
|
|
10
|
+
Requires-Dist: gql>=4.0.0
|
|
11
|
+
Requires-Dist: httpx>=0.28.1
|
|
12
|
+
Requires-Dist: juju~=3.6
|
|
13
|
+
Requires-Dist: loguru>=0.7.3
|
|
14
|
+
Requires-Dist: pendulum>=3.1.0
|
|
15
|
+
Requires-Dist: py-buzz>=7.3
|
|
16
|
+
Requires-Dist: pydantic>=2.11
|
|
17
|
+
Requires-Dist: python-jose[cryptography]>=3.5.0
|
|
18
|
+
Requires-Dist: requests-toolbelt>=0.10.0
|
|
19
|
+
Requires-Dist: rich>=14.1.0
|
|
20
|
+
Requires-Dist: ruamel-yaml
|
|
21
|
+
Requires-Dist: snick>=2.1
|
|
22
|
+
Requires-Dist: typer>=0.17.4
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: black; extra == 'dev'
|
|
25
|
+
Requires-Dist: codespell; extra == 'dev'
|
|
26
|
+
Requires-Dist: coverage[toml]~=7.8; extra == 'dev'
|
|
27
|
+
Requires-Dist: pyright; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
vantage_cli/__init__.py,sha256=sQo96C0Qwkyufu_5N8rpUiG7b6KAG4Xl0USrgEJ1rj4,4676
|
|
2
|
+
vantage_cli/auth.py,sha256=zi1xnNYnxXkcVCY4tCatcLE1q3qukX3HOFbrZoGv8Ag,15430
|
|
3
|
+
vantage_cli/cache.py,sha256=N8goumBtDLcQge2RmJHxyauiCL1aK_5eCXlWtL9OqpQ,5394
|
|
4
|
+
vantage_cli/client.py,sha256=07vp-1GGS2aV3kRQHl5deIMWNdgkjGz7rohGtxeyCiI,2689
|
|
5
|
+
vantage_cli/command_base.py,sha256=_Ln81IHLxXUsJZtWJBx6q10JXrLjhLgNfA0Vs-3-Tfg,2466
|
|
6
|
+
vantage_cli/config.py,sha256=UouRz1DcmawAuTqxkMyiPJk_ioRZ1NPC0fNwugCp5X8,5913
|
|
7
|
+
vantage_cli/constants.py,sha256=7dwKUX4aOG0smpqdS2JjtxiqHo1kFGf59PG1Sxezx-4,1627
|
|
8
|
+
vantage_cli/exceptions.py,sha256=3g21lMxR7mU-L32c911rGIUZPwqQkYvkyUlHedd2fcE,3355
|
|
9
|
+
vantage_cli/format.py,sha256=0H09Nio9wl890cq3dpNbmihqPf6A5_nItPqFFc_5Xp4,1057
|
|
10
|
+
vantage_cli/gql_client.py,sha256=bBt7keOC4isRJz_U59KgoCHsvzWgv9KIToMI-RSc3j4,23972
|
|
11
|
+
vantage_cli/main.py,sha256=SmFXF882depC0Qht2szJeVORu0b6Veb0iVLTXLkfbjk,10215
|
|
12
|
+
vantage_cli/render.py,sha256=3S_2vvDo-EijdEGlh95ddFNkSOg8r7PG1czyXUSeRow,1723
|
|
13
|
+
vantage_cli/schemas.py,sha256=sbpicXKBGpJW4BdEv6lYUZb-dA77QU5VZboWK7lb6A8,1079
|
|
14
|
+
vantage_cli/time_loop.py,sha256=pGb1Q3XI3sEGl-Ha0dsvQ_rTc3TpRje6e5WLqr2WXLw,3569
|
|
15
|
+
vantage_cli/apps/__init__.py,sha256=Xn3XwDeXMnUjaMecYcIJimFWWpi_2yNOoMQoJ0MmmhI,733
|
|
16
|
+
vantage_cli/apps/common.py,sha256=Ul_S2rzMAVGS5yNL2DT8F_euwjRQt13awZLi8qPUxas,2108
|
|
17
|
+
vantage_cli/apps/templates.py,sha256=2dXZqrGFzfV2ZTAlNncObRJkyuO6U95gsqlDBDc-UbI,7457
|
|
18
|
+
vantage_cli/apps/juju_localhost/__init__.py,sha256=HpKfUMSAMugjw9kGegoJMZ8Il-5LBL9c_-i4BiwU7xc,431
|
|
19
|
+
vantage_cli/apps/juju_localhost/app.py,sha256=C2KS7hOOWRAiRZ1ussI2AurkkouOuX_ArRV0SK2ltjk,10297
|
|
20
|
+
vantage_cli/apps/juju_localhost/bundle_yaml.py,sha256=bMyJ3cVDDPOKxiu5FhotF9gh_CywABVW-W2Y2S8waRI,4850
|
|
21
|
+
vantage_cli/apps/microk8s/README.md,sha256=Ud4wWx35FqeESHI7mWTduTM4b5RmCAbrNwAfg6jBgIo,2244
|
|
22
|
+
vantage_cli/apps/microk8s/__init__.py,sha256=uwEWzxy-vzVRmYgqlpo7s2AxtHQl3XY7ZPRvkvkK10Y,194
|
|
23
|
+
vantage_cli/apps/microk8s/app.py,sha256=AaLYpcZ0DuvcI_F56-slvujZJvPJ9hJfnHtrxuMJRYU,9535
|
|
24
|
+
vantage_cli/apps/multipass_singlenode/__init__.py,sha256=Z03IriK9S9cRYNTNDWkR25SgUYhthMTIIb4I1kdHpSU,312
|
|
25
|
+
vantage_cli/apps/multipass_singlenode/app.py,sha256=KDGhfC24rH5Mz1D6ZuZbVF0wq3nXR2pcd7xsrq36IxQ,6712
|
|
26
|
+
vantage_cli/commands/__init__.py,sha256=yZI-TkoUYDiVop1cs0_WHyb7eWeuw5C2rBJHSNo-uPM,35
|
|
27
|
+
vantage_cli/commands/clouds/__init__.py,sha256=Xf0K3BcWDJ4x3D5FLfcb0QCrg7tYXG2Bk0LfXKenpqA,597
|
|
28
|
+
vantage_cli/commands/clouds/add.py,sha256=5t-Xes2BFzoLuwEZ7-Zcb4YtkzriH2aceeGeSe61nq0,2597
|
|
29
|
+
vantage_cli/commands/clouds/delete.py,sha256=GyYfRoXD03lUQKABWe1KovOiCdVoBPFC-8y6ca_SNtg,2023
|
|
30
|
+
vantage_cli/commands/clouds/render.py,sha256=ibAlets-2CJXvqjIPI8c65qrWCeOuZa9pYg-WZjzEHc,4526
|
|
31
|
+
vantage_cli/commands/clouds/update.py,sha256=F3ksM0bSJjc7rqdGplN98AdRjclM4VddisvnRnyVzW8,3094
|
|
32
|
+
vantage_cli/commands/clusters/__init__.py,sha256=VPvUVKq9jTvbQEf-L06jWqX8wc4_qYb8Pe2azLCT8Pc,857
|
|
33
|
+
vantage_cli/commands/clusters/create.py,sha256=PhtxEivP4pzzrOKtPWWBW6uDCffqZ84ychpBJcj57-c,9503
|
|
34
|
+
vantage_cli/commands/clusters/delete.py,sha256=c41jbC4uLj1Ynr6iltUtqz-TNFJrTOQzcgJ6a61O-BY,3564
|
|
35
|
+
vantage_cli/commands/clusters/get.py,sha256=s_KRkHFAEqxZIcH88D48-C-eUKnCxsIFSB4ALrBboc4,1128
|
|
36
|
+
vantage_cli/commands/clusters/list.py,sha256=k3oLpzHog011nJzCbePd38xPwvyl5DwjmXPD8rnpFRs,2649
|
|
37
|
+
vantage_cli/commands/clusters/render.py,sha256=UVeTalX7CkkDgedwrcH8xIO-Dmm324-8DyT1kp_sGdA,7282
|
|
38
|
+
vantage_cli/commands/clusters/schema.py,sha256=nbH4byz3Zn_8YNIfmzjnOc3viqlxIUb37ALsEMqDf-I,712
|
|
39
|
+
vantage_cli/commands/clusters/utils.py,sha256=bBZD7epv_VVy8oOQkaPYm0ADes9_JQJduzipDyJzlmQ,9194
|
|
40
|
+
vantage_cli/commands/profile/__init__.py,sha256=064lGji7Qu84v2wrVzuhlBQVeWvPWEgbo7Ola30I4mg,938
|
|
41
|
+
vantage_cli/commands/profile/crud.py,sha256=IJCiV_JA9xTNNW5kdtAim4G6Od2ywAYFzyjYRgVfALk,18876
|
|
42
|
+
vantage_cli/commands/profile/render.py,sha256=RWldJrdkvvhgXUfvXf872nFgAReOLmZ2wYtIqWop8-w,1852
|
|
43
|
+
vantage_cli-0.1.1.dist-info/METADATA,sha256=FfMNZJ4oPdcXqcL11AixW6aIB_lOfP-XhuMbzDH7eQA,955
|
|
44
|
+
vantage_cli-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
45
|
+
vantage_cli-0.1.1.dist-info/entry_points.txt,sha256=TCUnwMb93FDfzPeFna1pxXxeE7Yi-F3JOLHW-oXo6jg,49
|
|
46
|
+
vantage_cli-0.1.1.dist-info/RECORD,,
|