pragmatiks-cli 0.5.1__py3-none-any.whl → 0.8.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.
- pragma_cli/commands/auth.py +45 -20
- pragma_cli/commands/completions.py +50 -4
- pragma_cli/commands/{provider.py → providers.py} +76 -282
- pragma_cli/commands/resources.py +7 -1
- pragma_cli/main.py +2 -2
- {pragmatiks_cli-0.5.1.dist-info → pragmatiks_cli-0.8.1.dist-info}/METADATA +9 -7
- pragmatiks_cli-0.8.1.dist-info/RECORD +17 -0
- {pragmatiks_cli-0.5.1.dist-info → pragmatiks_cli-0.8.1.dist-info}/WHEEL +2 -2
- pragmatiks_cli-0.5.1.dist-info/RECORD +0 -17
- {pragmatiks_cli-0.5.1.dist-info → pragmatiks_cli-0.8.1.dist-info}/entry_points.txt +0 -0
pragma_cli/commands/auth.py
CHANGED
|
@@ -5,13 +5,19 @@ import webbrowser
|
|
|
5
5
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
6
6
|
from urllib.parse import parse_qs, urlparse
|
|
7
7
|
|
|
8
|
+
import httpx
|
|
8
9
|
import typer
|
|
10
|
+
from pragma_sdk import PragmaClient
|
|
9
11
|
from pragma_sdk.config import load_credentials
|
|
10
12
|
from rich import print
|
|
13
|
+
from rich.console import Console
|
|
11
14
|
|
|
12
15
|
from pragma_cli.config import CREDENTIALS_FILE, get_current_context, load_config
|
|
13
16
|
|
|
14
17
|
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
15
21
|
app = typer.Typer()
|
|
16
22
|
|
|
17
23
|
CALLBACK_PORT = int(os.getenv("PRAGMA_AUTH_CALLBACK_PORT", "8765"))
|
|
@@ -218,30 +224,49 @@ def logout(
|
|
|
218
224
|
|
|
219
225
|
@app.command()
|
|
220
226
|
def whoami():
|
|
221
|
-
"""Show current authentication status.
|
|
227
|
+
"""Show current authentication status and user information.
|
|
222
228
|
|
|
223
|
-
Displays
|
|
229
|
+
Displays the current context, authentication state, and user details
|
|
230
|
+
including email and organization name from the API.
|
|
224
231
|
"""
|
|
225
|
-
|
|
226
|
-
|
|
232
|
+
current_context_name, current_context_config = get_current_context()
|
|
233
|
+
token = load_credentials(current_context_name)
|
|
234
|
+
|
|
235
|
+
console.print()
|
|
236
|
+
console.print("[bold]Authentication Status[/bold]")
|
|
237
|
+
console.print()
|
|
238
|
+
|
|
239
|
+
if not token:
|
|
240
|
+
console.print(f" Context: [cyan]{current_context_name}[/cyan]")
|
|
241
|
+
console.print(" Status: [yellow]Not authenticated[/yellow]")
|
|
242
|
+
console.print()
|
|
243
|
+
console.print("[dim]Run 'pragma auth login' to authenticate[/dim]")
|
|
244
|
+
return
|
|
227
245
|
|
|
228
|
-
print()
|
|
229
|
-
print("[
|
|
230
|
-
print()
|
|
246
|
+
console.print(f" Context: [cyan]{current_context_name}[/cyan]")
|
|
247
|
+
console.print(" Status: [green]\u2713 Authenticated[/green]")
|
|
231
248
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
marker = "[green]*[/green]" if context_name == current_context_name else " "
|
|
249
|
+
try:
|
|
250
|
+
client = PragmaClient(base_url=current_context_config.api_url, auth_token=token)
|
|
251
|
+
user_info = client.get_me()
|
|
236
252
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
253
|
+
console.print()
|
|
254
|
+
console.print("[bold]User Information[/bold]")
|
|
255
|
+
console.print()
|
|
256
|
+
console.print(f" User ID: [cyan]{user_info.user_id}[/cyan]")
|
|
257
|
+
if user_info.email:
|
|
258
|
+
console.print(f" Email: [cyan]{user_info.email}[/cyan]")
|
|
240
259
|
else:
|
|
241
|
-
print(
|
|
242
|
-
|
|
243
|
-
print()
|
|
260
|
+
console.print(" Email: [dim]Not set[/dim]")
|
|
261
|
+
console.print(f" Organization: [cyan]{user_info.organization_name or user_info.organization_id}[/cyan]")
|
|
244
262
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
263
|
+
except httpx.HTTPStatusError as e:
|
|
264
|
+
if e.response.status_code == 401:
|
|
265
|
+
console.print()
|
|
266
|
+
console.print("[yellow]Token expired or invalid. Run 'pragma auth login' to re-authenticate.[/yellow]")
|
|
267
|
+
else:
|
|
268
|
+
console.print()
|
|
269
|
+
console.print(f"[red]Error fetching user info:[/red] {e.response.text}")
|
|
270
|
+
except httpx.RequestError as e:
|
|
271
|
+
console.print()
|
|
272
|
+
console.print(f"[red]Connection error:[/red] {e}")
|
|
@@ -1,10 +1,52 @@
|
|
|
1
|
-
"""CLI auto-completion functions for resource operations."""
|
|
1
|
+
"""CLI auto-completion functions for resource and provider operations."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
|
+
from pragma_sdk import PragmaClient
|
|
6
7
|
|
|
7
|
-
from pragma_cli import
|
|
8
|
+
from pragma_cli.config import get_current_context
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_completion_client() -> PragmaClient | None:
|
|
12
|
+
"""Get a client for shell completion context.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
PragmaClient instance or None if configuration unavailable.
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
context_name, context_config = get_current_context()
|
|
19
|
+
if context_config is None:
|
|
20
|
+
return None
|
|
21
|
+
return PragmaClient(
|
|
22
|
+
base_url=context_config.api_url,
|
|
23
|
+
context=context_name,
|
|
24
|
+
require_auth=False,
|
|
25
|
+
)
|
|
26
|
+
except Exception:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def completion_provider_ids(incomplete: str):
|
|
31
|
+
"""Complete provider identifiers based on deployed providers.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
incomplete: Partial input to complete against available providers.
|
|
35
|
+
|
|
36
|
+
Yields:
|
|
37
|
+
Provider IDs matching the incomplete input.
|
|
38
|
+
"""
|
|
39
|
+
client = _get_completion_client()
|
|
40
|
+
if client is None:
|
|
41
|
+
return
|
|
42
|
+
try:
|
|
43
|
+
providers = client.list_providers()
|
|
44
|
+
except Exception:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
for provider in providers:
|
|
48
|
+
if provider.provider_id.lower().startswith(incomplete.lower()):
|
|
49
|
+
yield provider.provider_id
|
|
8
50
|
|
|
9
51
|
|
|
10
52
|
def completion_resource_ids(incomplete: str):
|
|
@@ -16,7 +58,9 @@ def completion_resource_ids(incomplete: str):
|
|
|
16
58
|
Yields:
|
|
17
59
|
Resource identifiers matching the incomplete input.
|
|
18
60
|
"""
|
|
19
|
-
client =
|
|
61
|
+
client = _get_completion_client()
|
|
62
|
+
if client is None:
|
|
63
|
+
return
|
|
20
64
|
try:
|
|
21
65
|
resources = client.list_resources()
|
|
22
66
|
except Exception:
|
|
@@ -40,7 +84,9 @@ def completion_resource_names(ctx: typer.Context, incomplete: str):
|
|
|
40
84
|
Yields:
|
|
41
85
|
Resource names matching the incomplete input for the selected resource type.
|
|
42
86
|
"""
|
|
43
|
-
client =
|
|
87
|
+
client = _get_completion_client()
|
|
88
|
+
if client is None:
|
|
89
|
+
return
|
|
44
90
|
resource_id = ctx.params.get("resource_id")
|
|
45
91
|
if not resource_id or "/" not in resource_id:
|
|
46
92
|
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Provider management commands.
|
|
2
2
|
|
|
3
|
-
Commands for scaffolding,
|
|
3
|
+
Commands for scaffolding, building, and deploying Pragmatiks providers to the platform.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import io
|
|
@@ -17,20 +17,18 @@ import typer
|
|
|
17
17
|
from pragma_sdk import (
|
|
18
18
|
BuildResult,
|
|
19
19
|
BuildStatus,
|
|
20
|
-
Config,
|
|
21
20
|
DeploymentStatus,
|
|
22
21
|
PragmaClient,
|
|
23
22
|
ProviderDeleteResult,
|
|
24
23
|
ProviderInfo,
|
|
25
24
|
PushResult,
|
|
26
|
-
Resource,
|
|
27
25
|
)
|
|
28
|
-
from pragma_sdk.provider import discover_resources
|
|
29
26
|
from rich.console import Console
|
|
30
27
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
31
28
|
from rich.table import Table
|
|
32
29
|
|
|
33
30
|
from pragma_cli import get_client
|
|
31
|
+
from pragma_cli.commands.completions import completion_provider_ids
|
|
34
32
|
|
|
35
33
|
|
|
36
34
|
app = typer.Typer(help="Provider management commands")
|
|
@@ -129,7 +127,7 @@ def list_providers():
|
|
|
129
127
|
- Last deployed timestamp
|
|
130
128
|
|
|
131
129
|
Example:
|
|
132
|
-
pragma
|
|
130
|
+
pragma providers list
|
|
133
131
|
|
|
134
132
|
Raises:
|
|
135
133
|
typer.Exit: If authentication is missing or API call fails.
|
|
@@ -178,11 +176,7 @@ def _print_providers_table(providers: list[ProviderInfo]) -> None:
|
|
|
178
176
|
for provider in providers:
|
|
179
177
|
status = _format_deployment_status(provider.deployment_status)
|
|
180
178
|
version = provider.current_version or "[dim]never deployed[/dim]"
|
|
181
|
-
updated = (
|
|
182
|
-
provider.updated_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
183
|
-
if provider.updated_at
|
|
184
|
-
else "[dim]-[/dim]"
|
|
185
|
-
)
|
|
179
|
+
updated = provider.updated_at.strftime("%Y-%m-%d %H:%M:%S") if provider.updated_at else "[dim]-[/dim]"
|
|
186
180
|
|
|
187
181
|
table.add_row(provider.provider_id, version, status, updated)
|
|
188
182
|
|
|
@@ -248,9 +242,9 @@ def init(
|
|
|
248
242
|
- mise.toml for tool management
|
|
249
243
|
|
|
250
244
|
Example:
|
|
251
|
-
pragma
|
|
252
|
-
pragma
|
|
253
|
-
pragma
|
|
245
|
+
pragma providers init mycompany
|
|
246
|
+
pragma providers init postgres --output ./providers/postgres
|
|
247
|
+
pragma providers init mycompany --defaults --description "My provider"
|
|
254
248
|
|
|
255
249
|
Raises:
|
|
256
250
|
typer.Exit: If directory already exists or template copy fails.
|
|
@@ -303,7 +297,7 @@ def init(
|
|
|
303
297
|
typer.echo(" copier update")
|
|
304
298
|
typer.echo("")
|
|
305
299
|
typer.echo("When ready to deploy:")
|
|
306
|
-
typer.echo(" pragma
|
|
300
|
+
typer.echo(" pragma providers push")
|
|
307
301
|
|
|
308
302
|
|
|
309
303
|
@app.command()
|
|
@@ -319,8 +313,8 @@ def update(
|
|
|
319
313
|
incorporating template updates.
|
|
320
314
|
|
|
321
315
|
Example:
|
|
322
|
-
pragma
|
|
323
|
-
pragma
|
|
316
|
+
pragma providers update
|
|
317
|
+
pragma providers update ./my-provider
|
|
324
318
|
|
|
325
319
|
Raises:
|
|
326
320
|
typer.Exit: If directory is not a Copier project or update fails.
|
|
@@ -374,25 +368,25 @@ def push(
|
|
|
374
368
|
a container image.
|
|
375
369
|
|
|
376
370
|
Build only:
|
|
377
|
-
pragma
|
|
371
|
+
pragma providers push
|
|
378
372
|
-> Uploads code and waits for build
|
|
379
373
|
|
|
380
374
|
Build and deploy:
|
|
381
|
-
pragma
|
|
375
|
+
pragma providers push --deploy
|
|
382
376
|
-> Uploads code, builds, and deploys
|
|
383
377
|
|
|
384
378
|
Async build:
|
|
385
|
-
pragma
|
|
379
|
+
pragma providers push --no-wait
|
|
386
380
|
-> Uploads code and returns immediately
|
|
387
381
|
|
|
388
382
|
With logs:
|
|
389
|
-
pragma
|
|
383
|
+
pragma providers push --logs
|
|
390
384
|
-> Shows build output in real-time
|
|
391
385
|
|
|
392
386
|
Example:
|
|
393
|
-
pragma
|
|
394
|
-
pragma
|
|
395
|
-
pragma
|
|
387
|
+
pragma providers push
|
|
388
|
+
pragma providers push --deploy
|
|
389
|
+
pragma providers push --logs --deploy
|
|
396
390
|
|
|
397
391
|
Raises:
|
|
398
392
|
typer.Exit: If provider detection fails or build fails.
|
|
@@ -437,18 +431,14 @@ def push(
|
|
|
437
431
|
if not wait:
|
|
438
432
|
console.print()
|
|
439
433
|
console.print("[dim]Build running in background. Check status with:[/dim]")
|
|
440
|
-
console.print(f" pragma
|
|
434
|
+
console.print(f" pragma providers status {provider_id} --job {push_result.job_name}")
|
|
441
435
|
return
|
|
442
436
|
|
|
443
|
-
|
|
437
|
+
_wait_for_build(client, provider_id, push_result.job_name, logs)
|
|
444
438
|
|
|
445
439
|
if deploy:
|
|
446
|
-
if not build_result.image:
|
|
447
|
-
console.print("[red]Error:[/red] Build succeeded but no image was produced")
|
|
448
|
-
raise typer.Exit(1)
|
|
449
|
-
|
|
450
440
|
console.print()
|
|
451
|
-
_deploy_provider(client, provider_id,
|
|
441
|
+
_deploy_provider(client, provider_id, push_result.version)
|
|
452
442
|
except Exception as e:
|
|
453
443
|
if isinstance(e, typer.Exit):
|
|
454
444
|
raise
|
|
@@ -583,13 +573,15 @@ def _stream_build_logs(client: PragmaClient, provider_id: str, job_name: str) ->
|
|
|
583
573
|
console.print("-" * 40)
|
|
584
574
|
|
|
585
575
|
|
|
586
|
-
def _deploy_provider(
|
|
587
|
-
|
|
576
|
+
def _deploy_provider(
|
|
577
|
+
client: PragmaClient, provider_id: str, version: str | None = None
|
|
578
|
+
) -> None:
|
|
579
|
+
"""Deploy the provider to a specific version.
|
|
588
580
|
|
|
589
581
|
Args:
|
|
590
582
|
client: SDK client instance.
|
|
591
583
|
provider_id: Provider identifier.
|
|
592
|
-
|
|
584
|
+
version: Version to deploy (CalVer format). If None, deploys latest.
|
|
593
585
|
"""
|
|
594
586
|
with Progress(
|
|
595
587
|
SpinnerColumn(),
|
|
@@ -598,31 +590,34 @@ def _deploy_provider(client: PragmaClient, provider_id: str, image: str) -> None
|
|
|
598
590
|
transient=True,
|
|
599
591
|
) as progress:
|
|
600
592
|
progress.add_task("Deploying...", total=None)
|
|
601
|
-
deploy_result = client.deploy_provider(provider_id,
|
|
593
|
+
deploy_result = client.deploy_provider(provider_id, version)
|
|
602
594
|
|
|
603
595
|
console.print(f"[green]Deployment started:[/green] {deploy_result.deployment_name}")
|
|
596
|
+
console.print(f"[dim]Version:[/dim] {deploy_result.version}")
|
|
604
597
|
console.print(f"[dim]Status:[/dim] {deploy_result.status.value}")
|
|
605
598
|
|
|
606
599
|
|
|
607
600
|
@app.command()
|
|
608
601
|
def deploy(
|
|
609
|
-
|
|
610
|
-
str,
|
|
611
|
-
typer.Option("--
|
|
612
|
-
],
|
|
602
|
+
version: Annotated[
|
|
603
|
+
str | None,
|
|
604
|
+
typer.Option("--version", "-v", help="Version to deploy (e.g., 20250115.120000). Defaults to latest."),
|
|
605
|
+
] = None,
|
|
613
606
|
package: Annotated[
|
|
614
607
|
str | None,
|
|
615
608
|
typer.Option("--package", "-p", help="Provider package name (auto-detected if not specified)"),
|
|
616
609
|
] = None,
|
|
617
610
|
):
|
|
618
|
-
"""Deploy a provider
|
|
611
|
+
"""Deploy a provider to a specific version.
|
|
619
612
|
|
|
620
|
-
Deploys
|
|
621
|
-
|
|
613
|
+
Deploys the provider to Kubernetes. If no version is specified, deploys
|
|
614
|
+
the latest successful build. Use 'pragma providers builds' to see available versions.
|
|
622
615
|
|
|
623
|
-
|
|
624
|
-
pragma
|
|
625
|
-
|
|
616
|
+
Deploy latest:
|
|
617
|
+
pragma providers deploy
|
|
618
|
+
|
|
619
|
+
Deploy specific version:
|
|
620
|
+
pragma providers deploy --version 20250115.120000
|
|
626
621
|
|
|
627
622
|
Raises:
|
|
628
623
|
typer.Exit: If deployment fails.
|
|
@@ -637,7 +632,10 @@ def deploy(
|
|
|
637
632
|
provider_id = provider_name.replace("_", "-").removesuffix("-provider")
|
|
638
633
|
|
|
639
634
|
console.print(f"[bold]Deploying provider:[/bold] {provider_id}")
|
|
640
|
-
|
|
635
|
+
if version:
|
|
636
|
+
console.print(f"[dim]Version:[/dim] {version}")
|
|
637
|
+
else:
|
|
638
|
+
console.print("[dim]Version:[/dim] latest")
|
|
641
639
|
console.print()
|
|
642
640
|
|
|
643
641
|
client = get_client()
|
|
@@ -647,119 +645,12 @@ def deploy(
|
|
|
647
645
|
raise typer.Exit(1)
|
|
648
646
|
|
|
649
647
|
try:
|
|
650
|
-
_deploy_provider(client, provider_id,
|
|
648
|
+
_deploy_provider(client, provider_id, version)
|
|
651
649
|
except Exception as e:
|
|
652
650
|
console.print(f"[red]Error:[/red] {e}")
|
|
653
651
|
raise typer.Exit(1)
|
|
654
652
|
|
|
655
653
|
|
|
656
|
-
@app.command()
|
|
657
|
-
def sync(
|
|
658
|
-
package: Annotated[
|
|
659
|
-
str | None,
|
|
660
|
-
typer.Option("--package", "-p", help="Provider package name (auto-detected if not specified)"),
|
|
661
|
-
] = None,
|
|
662
|
-
dry_run: Annotated[
|
|
663
|
-
bool,
|
|
664
|
-
typer.Option("--dry-run", help="Show what would be registered without making changes"),
|
|
665
|
-
] = False,
|
|
666
|
-
):
|
|
667
|
-
"""Sync resource type definitions to the Pragmatiks platform.
|
|
668
|
-
|
|
669
|
-
Discovers resources in the provider package and registers their schemas
|
|
670
|
-
with the API. This allows users to create instances of these resource types.
|
|
671
|
-
|
|
672
|
-
The command introspects the provider code to extract:
|
|
673
|
-
- Provider and resource names from @provider.resource() decorator
|
|
674
|
-
- JSON schema from Pydantic Config classes
|
|
675
|
-
|
|
676
|
-
Example:
|
|
677
|
-
pragma provider sync
|
|
678
|
-
pragma provider sync --package postgres_provider
|
|
679
|
-
pragma provider sync --dry-run
|
|
680
|
-
|
|
681
|
-
Raises:
|
|
682
|
-
typer.Exit: If package not found or registration fails.
|
|
683
|
-
"""
|
|
684
|
-
package_name = package or detect_provider_package()
|
|
685
|
-
|
|
686
|
-
if not package_name:
|
|
687
|
-
typer.echo("Error: Could not detect provider package.", err=True)
|
|
688
|
-
typer.echo("Run from a provider directory or specify --package", err=True)
|
|
689
|
-
raise typer.Exit(1)
|
|
690
|
-
|
|
691
|
-
typer.echo(f"Discovering resources in {package_name}...")
|
|
692
|
-
|
|
693
|
-
try:
|
|
694
|
-
resources = discover_resources(package_name)
|
|
695
|
-
except ImportError as e:
|
|
696
|
-
typer.echo(f"Error importing package: {e}", err=True)
|
|
697
|
-
raise typer.Exit(1)
|
|
698
|
-
|
|
699
|
-
if not resources:
|
|
700
|
-
typer.echo("No resources found. Ensure resources are decorated with @provider.resource().")
|
|
701
|
-
raise typer.Exit(0)
|
|
702
|
-
|
|
703
|
-
typer.echo(f"Found {len(resources)} resource(s):")
|
|
704
|
-
typer.echo("")
|
|
705
|
-
|
|
706
|
-
if dry_run:
|
|
707
|
-
for (provider, resource_name), resource_class in resources.items():
|
|
708
|
-
typer.echo(f" {provider}/{resource_name} ({resource_class.__name__})")
|
|
709
|
-
typer.echo("")
|
|
710
|
-
typer.echo("Dry run - no changes made.")
|
|
711
|
-
raise typer.Exit(0)
|
|
712
|
-
|
|
713
|
-
client = get_client()
|
|
714
|
-
|
|
715
|
-
for (provider, resource_name), resource_class in resources.items():
|
|
716
|
-
try:
|
|
717
|
-
config_class = get_config_class(resource_class)
|
|
718
|
-
schema = config_class.model_json_schema()
|
|
719
|
-
except ValueError as e:
|
|
720
|
-
typer.echo(f" {provider}/{resource_name}: skipped ({e})", err=True)
|
|
721
|
-
continue
|
|
722
|
-
|
|
723
|
-
try:
|
|
724
|
-
client.register_resource(
|
|
725
|
-
provider=provider,
|
|
726
|
-
resource=resource_name,
|
|
727
|
-
schema=schema,
|
|
728
|
-
)
|
|
729
|
-
typer.echo(f" {provider}/{resource_name}: registered")
|
|
730
|
-
except Exception as e:
|
|
731
|
-
typer.echo(f" {provider}/{resource_name}: failed ({e})", err=True)
|
|
732
|
-
|
|
733
|
-
typer.echo("")
|
|
734
|
-
typer.echo("Sync complete.")
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
def get_config_class(resource_class: type[Resource]) -> type[Config]:
|
|
738
|
-
"""Extract Config subclass from Resource's config field annotation.
|
|
739
|
-
|
|
740
|
-
Args:
|
|
741
|
-
resource_class: A Resource subclass.
|
|
742
|
-
|
|
743
|
-
Returns:
|
|
744
|
-
Config subclass type from the Resource's config field.
|
|
745
|
-
|
|
746
|
-
Raises:
|
|
747
|
-
ValueError: If Resource has no config field or wrong type.
|
|
748
|
-
"""
|
|
749
|
-
annotations = resource_class.model_fields
|
|
750
|
-
config_field = annotations.get("config")
|
|
751
|
-
|
|
752
|
-
if config_field is None:
|
|
753
|
-
raise ValueError(f"Resource {resource_class.__name__} has no config field")
|
|
754
|
-
|
|
755
|
-
config_type = config_field.annotation
|
|
756
|
-
|
|
757
|
-
if not isinstance(config_type, type) or not issubclass(config_type, Config):
|
|
758
|
-
raise ValueError(f"Resource {resource_class.__name__} config field is not a Config subclass")
|
|
759
|
-
|
|
760
|
-
return config_type
|
|
761
|
-
|
|
762
|
-
|
|
763
654
|
def detect_provider_package() -> str | None:
|
|
764
655
|
"""Detect provider package name from current directory.
|
|
765
656
|
|
|
@@ -784,7 +675,10 @@ def detect_provider_package() -> str | None:
|
|
|
784
675
|
def delete(
|
|
785
676
|
provider_id: Annotated[
|
|
786
677
|
str,
|
|
787
|
-
typer.Argument(
|
|
678
|
+
typer.Argument(
|
|
679
|
+
help="Provider ID to delete (e.g., 'postgres', 'my-provider')",
|
|
680
|
+
autocompletion=completion_provider_ids,
|
|
681
|
+
),
|
|
788
682
|
],
|
|
789
683
|
cascade: Annotated[
|
|
790
684
|
bool,
|
|
@@ -801,21 +695,21 @@ def delete(
|
|
|
801
695
|
from the platform. By default, fails if the provider has any resources.
|
|
802
696
|
|
|
803
697
|
Without --cascade:
|
|
804
|
-
pragma
|
|
698
|
+
pragma providers delete my-provider
|
|
805
699
|
-> Fails if provider has resources
|
|
806
700
|
|
|
807
701
|
With --cascade:
|
|
808
|
-
pragma
|
|
702
|
+
pragma providers delete my-provider --cascade
|
|
809
703
|
-> Deletes provider and all its resources
|
|
810
704
|
|
|
811
705
|
Skip confirmation:
|
|
812
|
-
pragma
|
|
813
|
-
pragma
|
|
706
|
+
pragma providers delete my-provider --force
|
|
707
|
+
pragma providers delete my-provider --cascade --force
|
|
814
708
|
|
|
815
709
|
Example:
|
|
816
|
-
pragma
|
|
817
|
-
pragma
|
|
818
|
-
pragma
|
|
710
|
+
pragma providers delete postgres
|
|
711
|
+
pragma providers delete postgres --cascade
|
|
712
|
+
pragma providers delete postgres --cascade --force
|
|
819
713
|
|
|
820
714
|
Raises:
|
|
821
715
|
typer.Exit: If deletion fails or user cancels.
|
|
@@ -890,127 +784,27 @@ def _print_delete_result(result: ProviderDeleteResult) -> None:
|
|
|
890
784
|
console.print(table)
|
|
891
785
|
|
|
892
786
|
|
|
893
|
-
@app.command()
|
|
894
|
-
def rollback(
|
|
895
|
-
provider_id: Annotated[
|
|
896
|
-
str,
|
|
897
|
-
typer.Argument(help="Provider ID to rollback (e.g., 'postgres', 'my-provider')"),
|
|
898
|
-
],
|
|
899
|
-
version: Annotated[
|
|
900
|
-
str | None,
|
|
901
|
-
typer.Option("--version", "-v", help="Target version to rollback to (default: previous successful build)"),
|
|
902
|
-
] = None,
|
|
903
|
-
):
|
|
904
|
-
"""Rollback a provider to a previous version.
|
|
905
|
-
|
|
906
|
-
Redeploys a previously successful build. If no version is specified,
|
|
907
|
-
rolls back to the previous successful version.
|
|
908
|
-
|
|
909
|
-
Rollback to specific version:
|
|
910
|
-
pragma provider rollback my-provider --version 20250114.120000
|
|
911
|
-
|
|
912
|
-
Rollback to previous version:
|
|
913
|
-
pragma provider rollback my-provider
|
|
914
|
-
|
|
915
|
-
Example:
|
|
916
|
-
pragma provider rollback postgres --version 20250114.120000
|
|
917
|
-
pragma provider rollback postgres -v 20250114.120000
|
|
918
|
-
pragma provider rollback postgres
|
|
919
|
-
|
|
920
|
-
Raises:
|
|
921
|
-
typer.Exit: If rollback fails or no suitable version found.
|
|
922
|
-
"""
|
|
923
|
-
client = get_client()
|
|
924
|
-
|
|
925
|
-
if client._auth is None:
|
|
926
|
-
console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
|
|
927
|
-
raise typer.Exit(1)
|
|
928
|
-
|
|
929
|
-
try:
|
|
930
|
-
target_version = version
|
|
931
|
-
if target_version is None:
|
|
932
|
-
target_version = _find_previous_version(client, provider_id)
|
|
933
|
-
|
|
934
|
-
console.print(f"[bold]Rolling back provider:[/bold] {provider_id}")
|
|
935
|
-
console.print(f"[dim]Target version:[/dim] {target_version}")
|
|
936
|
-
console.print()
|
|
937
|
-
|
|
938
|
-
with Progress(
|
|
939
|
-
SpinnerColumn(),
|
|
940
|
-
TextColumn("[progress.description]{task.description}"),
|
|
941
|
-
console=console,
|
|
942
|
-
transient=True,
|
|
943
|
-
) as progress:
|
|
944
|
-
progress.add_task("Deploying previous version...", total=None)
|
|
945
|
-
result = client.rollback_provider(provider_id, target_version)
|
|
946
|
-
|
|
947
|
-
console.print(f"[green]Rollback initiated:[/green] {result.deployment_name}")
|
|
948
|
-
console.print(f"[dim]Status:[/dim] {result.status.value}")
|
|
949
|
-
|
|
950
|
-
except httpx.HTTPStatusError as e:
|
|
951
|
-
if e.response.status_code == 404:
|
|
952
|
-
detail = e.response.json().get("detail", "Build not found")
|
|
953
|
-
console.print(f"[red]Error:[/red] {detail}")
|
|
954
|
-
elif e.response.status_code == 400:
|
|
955
|
-
detail = e.response.json().get("detail", "Build not deployable")
|
|
956
|
-
console.print(f"[red]Error:[/red] {detail}")
|
|
957
|
-
else:
|
|
958
|
-
console.print(f"[red]Error:[/red] {e.response.text}")
|
|
959
|
-
raise typer.Exit(1)
|
|
960
|
-
except ValueError as e:
|
|
961
|
-
console.print(f"[red]Error:[/red] {e}")
|
|
962
|
-
raise typer.Exit(1)
|
|
963
|
-
except Exception as e:
|
|
964
|
-
console.print(f"[red]Error:[/red] {e}")
|
|
965
|
-
raise typer.Exit(1)
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
def _find_previous_version(client: PragmaClient, provider_id: str) -> str:
|
|
969
|
-
"""Find the previous successful version for rollback.
|
|
970
|
-
|
|
971
|
-
Gets the build history and finds the second-most-recent successful build,
|
|
972
|
-
which represents the version before the current deployment.
|
|
973
|
-
|
|
974
|
-
Args:
|
|
975
|
-
client: SDK client instance.
|
|
976
|
-
provider_id: Provider identifier.
|
|
977
|
-
|
|
978
|
-
Returns:
|
|
979
|
-
CalVer version string of the previous successful build.
|
|
980
|
-
|
|
981
|
-
Raises:
|
|
982
|
-
ValueError: If no previous successful build exists.
|
|
983
|
-
"""
|
|
984
|
-
builds = client.list_builds(provider_id)
|
|
985
|
-
successful_builds = [b for b in builds if b.status == BuildStatus.SUCCESS]
|
|
986
|
-
|
|
987
|
-
if len(successful_builds) < 2:
|
|
988
|
-
raise ValueError(
|
|
989
|
-
f"No previous successful build found for provider '{provider_id}'. "
|
|
990
|
-
"Specify a version explicitly with --version."
|
|
991
|
-
)
|
|
992
|
-
|
|
993
|
-
return successful_builds[1].version
|
|
994
|
-
|
|
995
|
-
|
|
996
787
|
@app.command()
|
|
997
788
|
def status(
|
|
998
789
|
provider_id: Annotated[
|
|
999
790
|
str,
|
|
1000
|
-
typer.Argument(
|
|
791
|
+
typer.Argument(
|
|
792
|
+
help="Provider ID to check status (e.g., 'postgres', 'my-provider')",
|
|
793
|
+
autocompletion=completion_provider_ids,
|
|
794
|
+
),
|
|
1001
795
|
],
|
|
1002
796
|
):
|
|
1003
797
|
"""Check the deployment status of a provider.
|
|
1004
798
|
|
|
1005
799
|
Displays:
|
|
1006
800
|
- Deployment status (pending/progressing/available/failed)
|
|
1007
|
-
-
|
|
1008
|
-
-
|
|
801
|
+
- Deployed version
|
|
802
|
+
- Health status
|
|
1009
803
|
- Last updated timestamp
|
|
1010
804
|
|
|
1011
805
|
Example:
|
|
1012
|
-
pragma
|
|
1013
|
-
pragma
|
|
806
|
+
pragma providers status postgres
|
|
807
|
+
pragma providers status my-provider
|
|
1014
808
|
|
|
1015
809
|
Raises:
|
|
1016
810
|
typer.Exit: If deployment not found or status check fails.
|
|
@@ -1018,7 +812,7 @@ def status(
|
|
|
1018
812
|
client = get_client()
|
|
1019
813
|
|
|
1020
814
|
if client._auth is None:
|
|
1021
|
-
console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
|
|
815
|
+
console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
|
|
1022
816
|
raise typer.Exit(1)
|
|
1023
817
|
|
|
1024
818
|
try:
|
|
@@ -1041,7 +835,7 @@ def _print_deployment_status(provider_id: str, result) -> None:
|
|
|
1041
835
|
|
|
1042
836
|
Args:
|
|
1043
837
|
provider_id: Provider identifier.
|
|
1044
|
-
result:
|
|
838
|
+
result: ProviderStatus from the API.
|
|
1045
839
|
"""
|
|
1046
840
|
status_colors = {
|
|
1047
841
|
"pending": "yellow",
|
|
@@ -1060,19 +854,16 @@ def _print_deployment_status(provider_id: str, result) -> None:
|
|
|
1060
854
|
table.add_column("Value")
|
|
1061
855
|
|
|
1062
856
|
table.add_row("Status", f"[{status_color}]{result.status.value}[/{status_color}]")
|
|
1063
|
-
table.add_row("Replicas", f"{result.available_replicas} available / {result.ready_replicas} ready")
|
|
1064
857
|
|
|
1065
|
-
if result.
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
858
|
+
if result.version:
|
|
859
|
+
table.add_row("Version", result.version)
|
|
860
|
+
|
|
861
|
+
healthy_display = "[green]yes[/green]" if result.healthy else "[red]no[/red]"
|
|
862
|
+
table.add_row("Healthy", healthy_display)
|
|
1069
863
|
|
|
1070
864
|
if result.updated_at:
|
|
1071
865
|
table.add_row("Updated", result.updated_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
|
|
1072
866
|
|
|
1073
|
-
if result.message:
|
|
1074
|
-
table.add_row("Message", result.message)
|
|
1075
|
-
|
|
1076
867
|
console.print(table)
|
|
1077
868
|
|
|
1078
869
|
|
|
@@ -1080,7 +871,10 @@ def _print_deployment_status(provider_id: str, result) -> None:
|
|
|
1080
871
|
def builds(
|
|
1081
872
|
provider_id: Annotated[
|
|
1082
873
|
str,
|
|
1083
|
-
typer.Argument(
|
|
874
|
+
typer.Argument(
|
|
875
|
+
help="Provider ID to list builds for (e.g., 'postgres', 'my-provider')",
|
|
876
|
+
autocompletion=completion_provider_ids,
|
|
877
|
+
),
|
|
1084
878
|
],
|
|
1085
879
|
):
|
|
1086
880
|
"""List build history for a provider.
|
|
@@ -1089,8 +883,8 @@ def builds(
|
|
|
1089
883
|
Useful for selecting versions for rollback and verifying build status.
|
|
1090
884
|
|
|
1091
885
|
Example:
|
|
1092
|
-
pragma
|
|
1093
|
-
pragma
|
|
886
|
+
pragma providers builds postgres
|
|
887
|
+
pragma providers builds my-provider
|
|
1094
888
|
|
|
1095
889
|
Raises:
|
|
1096
890
|
typer.Exit: If request fails.
|
pragma_cli/commands/resources.py
CHANGED
|
@@ -93,7 +93,13 @@ def list_resources(
|
|
|
93
93
|
):
|
|
94
94
|
"""List resources, optionally filtered by provider, resource type, or tags."""
|
|
95
95
|
client = get_client()
|
|
96
|
-
|
|
96
|
+
resources = list(client.list_resources(provider=provider, resource=resource, tags=tags))
|
|
97
|
+
|
|
98
|
+
if not resources:
|
|
99
|
+
console.print("[dim]No resources found.[/dim]")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
for res in resources:
|
|
97
103
|
print(f"{res['provider']}/{res['resource']}/{res['name']} {format_state(res['lifecycle_state'])}")
|
|
98
104
|
|
|
99
105
|
|
pragma_cli/main.py
CHANGED
|
@@ -6,7 +6,7 @@ import typer
|
|
|
6
6
|
from pragma_sdk import PragmaClient
|
|
7
7
|
|
|
8
8
|
from pragma_cli import set_client
|
|
9
|
-
from pragma_cli.commands import auth, config, ops,
|
|
9
|
+
from pragma_cli.commands import auth, config, ops, providers, resources
|
|
10
10
|
from pragma_cli.config import get_current_context
|
|
11
11
|
|
|
12
12
|
|
|
@@ -61,7 +61,7 @@ app.add_typer(resources.app, name="resources")
|
|
|
61
61
|
app.add_typer(auth.app, name="auth")
|
|
62
62
|
app.add_typer(config.app, name="config")
|
|
63
63
|
app.add_typer(ops.app, name="ops")
|
|
64
|
-
app.add_typer(
|
|
64
|
+
app.add_typer(providers.app, name="providers")
|
|
65
65
|
|
|
66
66
|
if __name__ == "__main__": # pragma: no cover
|
|
67
67
|
app()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pragmatiks-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Command-line interface for Pragmatiks
|
|
5
5
|
Requires-Dist: typer>=0.15.3
|
|
6
6
|
Requires-Dist: pragmatiks-sdk>=0.6.0
|
|
@@ -10,19 +10,21 @@ Requires-Dist: rich>=13.9.0
|
|
|
10
10
|
Requires-Python: >=3.13
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="assets/wordmark.png" alt="Pragma-OS" width="800">
|
|
15
|
+
</p>
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
# Pragma CLI
|
|
18
|
+
|
|
19
|
+
[](https://deepwiki.com/pragmatiks/pragma-cli)
|
|
16
20
|
[](https://pypi.org/project/pragmatiks-cli/)
|
|
17
21
|
[](https://www.python.org/downloads/)
|
|
18
22
|
[](https://opensource.org/licenses/MIT)
|
|
19
23
|
[](https://github.com/astral-sh/ruff)
|
|
20
24
|
|
|
21
|
-
**[Documentation](https://docs.pragmatiks.io/cli/overview)** | **[SDK](https://github.com/pragmatiks/sdk)** | **[Providers](https://github.com/pragmatiks/providers)**
|
|
22
|
-
|
|
23
|
-
Command-line interface for managing Pragmatiks resources.
|
|
25
|
+
**[Documentation](https://docs.pragmatiks.io/cli/overview)** | **[SDK](https://github.com/pragmatiks/pragma-sdk)** | **[Providers](https://github.com/pragmatiks/pragma-providers)**
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
Command-line interface for managing pragma-os resources.
|
|
26
28
|
|
|
27
29
|
## Quick Start
|
|
28
30
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pragma_cli/__init__.py,sha256=9REbOdKs9CeuOd-rxeFs17gWtou1dUdCogYU8G5Cz6c,682
|
|
2
|
+
pragma_cli/commands/__init__.py,sha256=zltFPaCZgkeTdOH1YWrUEqqBF9Dg6tokgAFcmqP4_n4,24
|
|
3
|
+
pragma_cli/commands/auth.py,sha256=HSv4mMuA-2QjYDYf69IQd-yXFANr8Uzw-oz3bAy_R0o,9311
|
|
4
|
+
pragma_cli/commands/completions.py,sha256=DwiyVANoYfDIDSNKwgb4QV_tDH2k5RjXgt3Z7TNNi-A,2872
|
|
5
|
+
pragma_cli/commands/config.py,sha256=Bow71Tg_zeprHlbn_6y8g2YsV_k9nRobA6ooYfaxtWE,2281
|
|
6
|
+
pragma_cli/commands/dead_letter.py,sha256=8Mh_QVZiwkbOA1fYkw1O9BeHgPdqepis6tSJOAY3vhA,6754
|
|
7
|
+
pragma_cli/commands/ops.py,sha256=ztx0Gx2L2mEqJQpbgDHgfOUZ4uaD132NxgKohaPOWv8,361
|
|
8
|
+
pragma_cli/commands/providers.py,sha256=5OI1Rlgk4bN8OqMVhCVIQCOo3OqAZStDuV_4YXBgWeg,29168
|
|
9
|
+
pragma_cli/commands/resources.py,sha256=_1Te1HqEayOEcpKIIJPI6ud_yk1F19Uaw-SRNQ8kCSs,7031
|
|
10
|
+
pragma_cli/config.py,sha256=kcU4tJUV1DfnXC_ydQbgQoJzjdGB1s-6-7g4cwA1nZw,2340
|
|
11
|
+
pragma_cli/helpers.py,sha256=dVxokT-sqF08oY0O35QZ64QyNC0PHZEBJYTvZ726UtI,650
|
|
12
|
+
pragma_cli/main.py,sha256=DBclLuSeymcockptWgzX5I1euqrRrHgnrTgGAsF74Xc,1973
|
|
13
|
+
pragma_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
pragmatiks_cli-0.8.1.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
|
|
15
|
+
pragmatiks_cli-0.8.1.dist-info/entry_points.txt,sha256=9xeQQlnHxq94dks6mlJ2I9LuMUKmqxuJzyKSZCb9iJM,48
|
|
16
|
+
pragmatiks_cli-0.8.1.dist-info/METADATA,sha256=qQTi7V0AAFGf_EsVWV36cQgslubzeBa0O2mcZmsAyy0,4465
|
|
17
|
+
pragmatiks_cli-0.8.1.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
pragma_cli/__init__.py,sha256=9REbOdKs9CeuOd-rxeFs17gWtou1dUdCogYU8G5Cz6c,682
|
|
2
|
-
pragma_cli/commands/__init__.py,sha256=zltFPaCZgkeTdOH1YWrUEqqBF9Dg6tokgAFcmqP4_n4,24
|
|
3
|
-
pragma_cli/commands/auth.py,sha256=6VqLTD8sZ7LRmI8RXEkSze1Ctnq5oM_1Zv0Br-HuFIA,8198
|
|
4
|
-
pragma_cli/commands/completions.py,sha256=ZCW38A1-6l_IcGC7Gj2CP5VL6PzaY4zKdcxylDuCoWM,1609
|
|
5
|
-
pragma_cli/commands/config.py,sha256=Bow71Tg_zeprHlbn_6y8g2YsV_k9nRobA6ooYfaxtWE,2281
|
|
6
|
-
pragma_cli/commands/dead_letter.py,sha256=8Mh_QVZiwkbOA1fYkw1O9BeHgPdqepis6tSJOAY3vhA,6754
|
|
7
|
-
pragma_cli/commands/ops.py,sha256=ztx0Gx2L2mEqJQpbgDHgfOUZ4uaD132NxgKohaPOWv8,361
|
|
8
|
-
pragma_cli/commands/provider.py,sha256=BX1NqGbH9XOrYFrWb92UpZhjTJA5qJ_U6cppMB1_lq0,36271
|
|
9
|
-
pragma_cli/commands/resources.py,sha256=BzwFKK5vqDo37wUPwRkulKdC4TFEJEoSXLlzsxQFD6w,6904
|
|
10
|
-
pragma_cli/config.py,sha256=kcU4tJUV1DfnXC_ydQbgQoJzjdGB1s-6-7g4cwA1nZw,2340
|
|
11
|
-
pragma_cli/helpers.py,sha256=dVxokT-sqF08oY0O35QZ64QyNC0PHZEBJYTvZ726UtI,650
|
|
12
|
-
pragma_cli/main.py,sha256=3UAKMsgc5u2v55ykWy-o_XC5W2CwyDNGfTSFhSvsLfU,1970
|
|
13
|
-
pragma_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
pragmatiks_cli-0.5.1.dist-info/WHEEL,sha256=XjEbIc5-wIORjWaafhI6vBtlxDBp7S9KiujWF1EM7Ak,79
|
|
15
|
-
pragmatiks_cli-0.5.1.dist-info/entry_points.txt,sha256=9xeQQlnHxq94dks6mlJ2I9LuMUKmqxuJzyKSZCb9iJM,48
|
|
16
|
-
pragmatiks_cli-0.5.1.dist-info/METADATA,sha256=N3AEoBl0p7bLIpxYtZtI7mltnY4xdKVHnO9iSIg4njs,4400
|
|
17
|
-
pragmatiks_cli-0.5.1.dist-info/RECORD,,
|
|
File without changes
|