pragmatiks-cli 0.5.1__py3-none-any.whl → 0.12.5__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 +86 -32
- pragma_cli/commands/completions.py +75 -4
- pragma_cli/commands/config.py +8 -1
- pragma_cli/commands/{provider.py → providers.py} +130 -332
- pragma_cli/commands/resources.py +344 -57
- pragma_cli/config.py +19 -0
- pragma_cli/helpers.py +40 -1
- pragma_cli/main.py +30 -4
- {pragmatiks_cli-0.5.1.dist-info → pragmatiks_cli-0.12.5.dist-info}/METADATA +9 -7
- pragmatiks_cli-0.12.5.dist-info/RECORD +17 -0
- {pragmatiks_cli-0.5.1.dist-info → pragmatiks_cli-0.12.5.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.12.5.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|
@@ -15,22 +15,21 @@ import copier
|
|
|
15
15
|
import httpx
|
|
16
16
|
import typer
|
|
17
17
|
from pragma_sdk import (
|
|
18
|
-
|
|
18
|
+
BuildInfo,
|
|
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, completion_provider_versions
|
|
32
|
+
from pragma_cli.helpers import OutputFormat, output_data
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
app = typer.Typer(help="Provider management commands")
|
|
@@ -119,7 +118,9 @@ def get_template_source() -> str:
|
|
|
119
118
|
|
|
120
119
|
|
|
121
120
|
@app.command("list")
|
|
122
|
-
def list_providers(
|
|
121
|
+
def list_providers(
|
|
122
|
+
output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
|
|
123
|
+
):
|
|
123
124
|
"""List all deployed providers.
|
|
124
125
|
|
|
125
126
|
Shows providers with their deployment status. Displays:
|
|
@@ -128,8 +129,9 @@ def list_providers():
|
|
|
128
129
|
- Status (running/stopped)
|
|
129
130
|
- Last deployed timestamp
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
pragma
|
|
132
|
+
Examples:
|
|
133
|
+
pragma providers list
|
|
134
|
+
pragma providers list -o json
|
|
133
135
|
|
|
134
136
|
Raises:
|
|
135
137
|
typer.Exit: If authentication is missing or API call fails.
|
|
@@ -160,7 +162,12 @@ def list_providers():
|
|
|
160
162
|
console.print("[dim]No providers found.[/dim]")
|
|
161
163
|
return
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
if output == OutputFormat.TABLE:
|
|
166
|
+
_print_providers_table(providers)
|
|
167
|
+
else:
|
|
168
|
+
# Convert Pydantic models to dicts for JSON/YAML output
|
|
169
|
+
data = [p.model_dump(mode="json") for p in providers]
|
|
170
|
+
output_data(data, output)
|
|
164
171
|
|
|
165
172
|
|
|
166
173
|
def _print_providers_table(providers: list[ProviderInfo]) -> None:
|
|
@@ -178,11 +185,7 @@ def _print_providers_table(providers: list[ProviderInfo]) -> None:
|
|
|
178
185
|
for provider in providers:
|
|
179
186
|
status = _format_deployment_status(provider.deployment_status)
|
|
180
187
|
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
|
-
)
|
|
188
|
+
updated = provider.updated_at.strftime("%Y-%m-%d %H:%M:%S") if provider.updated_at else "[dim]-[/dim]"
|
|
186
189
|
|
|
187
190
|
table.add_row(provider.provider_id, version, status, updated)
|
|
188
191
|
|
|
@@ -248,9 +251,9 @@ def init(
|
|
|
248
251
|
- mise.toml for tool management
|
|
249
252
|
|
|
250
253
|
Example:
|
|
251
|
-
pragma
|
|
252
|
-
pragma
|
|
253
|
-
pragma
|
|
254
|
+
pragma providers init mycompany
|
|
255
|
+
pragma providers init postgres --output ./providers/postgres
|
|
256
|
+
pragma providers init mycompany --defaults --description "My provider"
|
|
254
257
|
|
|
255
258
|
Raises:
|
|
256
259
|
typer.Exit: If directory already exists or template copy fails.
|
|
@@ -303,7 +306,7 @@ def init(
|
|
|
303
306
|
typer.echo(" copier update")
|
|
304
307
|
typer.echo("")
|
|
305
308
|
typer.echo("When ready to deploy:")
|
|
306
|
-
typer.echo(" pragma
|
|
309
|
+
typer.echo(" pragma providers push")
|
|
307
310
|
|
|
308
311
|
|
|
309
312
|
@app.command()
|
|
@@ -319,8 +322,8 @@ def update(
|
|
|
319
322
|
incorporating template updates.
|
|
320
323
|
|
|
321
324
|
Example:
|
|
322
|
-
pragma
|
|
323
|
-
pragma
|
|
325
|
+
pragma providers update
|
|
326
|
+
pragma providers update ./my-provider
|
|
324
327
|
|
|
325
328
|
Raises:
|
|
326
329
|
typer.Exit: If directory is not a Copier project or update fails.
|
|
@@ -374,25 +377,25 @@ def push(
|
|
|
374
377
|
a container image.
|
|
375
378
|
|
|
376
379
|
Build only:
|
|
377
|
-
pragma
|
|
380
|
+
pragma providers push
|
|
378
381
|
-> Uploads code and waits for build
|
|
379
382
|
|
|
380
383
|
Build and deploy:
|
|
381
|
-
pragma
|
|
384
|
+
pragma providers push --deploy
|
|
382
385
|
-> Uploads code, builds, and deploys
|
|
383
386
|
|
|
384
387
|
Async build:
|
|
385
|
-
pragma
|
|
388
|
+
pragma providers push --no-wait
|
|
386
389
|
-> Uploads code and returns immediately
|
|
387
390
|
|
|
388
391
|
With logs:
|
|
389
|
-
pragma
|
|
392
|
+
pragma providers push --logs
|
|
390
393
|
-> Shows build output in real-time
|
|
391
394
|
|
|
392
395
|
Example:
|
|
393
|
-
pragma
|
|
394
|
-
pragma
|
|
395
|
-
pragma
|
|
396
|
+
pragma providers push
|
|
397
|
+
pragma providers push --deploy
|
|
398
|
+
pragma providers push --logs --deploy
|
|
396
399
|
|
|
397
400
|
Raises:
|
|
398
401
|
typer.Exit: If provider detection fails or build fails.
|
|
@@ -428,7 +431,7 @@ def push(
|
|
|
428
431
|
client = get_client()
|
|
429
432
|
|
|
430
433
|
if client._auth is None:
|
|
431
|
-
console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
|
|
434
|
+
console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
|
|
432
435
|
raise typer.Exit(1)
|
|
433
436
|
|
|
434
437
|
try:
|
|
@@ -437,18 +440,14 @@ def push(
|
|
|
437
440
|
if not wait:
|
|
438
441
|
console.print()
|
|
439
442
|
console.print("[dim]Build running in background. Check status with:[/dim]")
|
|
440
|
-
console.print(f" pragma
|
|
443
|
+
console.print(f" pragma providers builds {provider_id} {push_result.version}")
|
|
441
444
|
return
|
|
442
445
|
|
|
443
|
-
|
|
446
|
+
_wait_for_build(client, provider_id, push_result.version, logs)
|
|
444
447
|
|
|
445
448
|
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
449
|
console.print()
|
|
451
|
-
_deploy_provider(client, provider_id,
|
|
450
|
+
_deploy_provider(client, provider_id, push_result.version)
|
|
452
451
|
except Exception as e:
|
|
453
452
|
if isinstance(e, typer.Exit):
|
|
454
453
|
raise
|
|
@@ -465,7 +464,7 @@ def _upload_code(client: PragmaClient, provider_id: str, tarball: bytes) -> Push
|
|
|
465
464
|
tarball: Gzipped tarball bytes of provider source.
|
|
466
465
|
|
|
467
466
|
Returns:
|
|
468
|
-
PushResult with build
|
|
467
|
+
PushResult with build details including version.
|
|
469
468
|
"""
|
|
470
469
|
with Progress(
|
|
471
470
|
SpinnerColumn(),
|
|
@@ -476,42 +475,42 @@ def _upload_code(client: PragmaClient, provider_id: str, tarball: bytes) -> Push
|
|
|
476
475
|
progress.add_task("Uploading code...", total=None)
|
|
477
476
|
push_result = client.push_provider(provider_id, tarball)
|
|
478
477
|
|
|
479
|
-
console.print(f"[green]Build started:[/green] {push_result.
|
|
478
|
+
console.print(f"[green]Build started:[/green] {push_result.version}")
|
|
480
479
|
return push_result
|
|
481
480
|
|
|
482
481
|
|
|
483
482
|
def _wait_for_build(
|
|
484
483
|
client: PragmaClient,
|
|
485
484
|
provider_id: str,
|
|
486
|
-
|
|
485
|
+
version: str,
|
|
487
486
|
logs: bool,
|
|
488
|
-
) ->
|
|
487
|
+
) -> BuildInfo:
|
|
489
488
|
"""Wait for build to complete, optionally streaming logs.
|
|
490
489
|
|
|
491
490
|
Args:
|
|
492
491
|
client: SDK client instance.
|
|
493
492
|
provider_id: Provider identifier.
|
|
494
|
-
|
|
493
|
+
version: CalVer version string.
|
|
495
494
|
logs: Whether to stream build logs.
|
|
496
495
|
|
|
497
496
|
Returns:
|
|
498
|
-
Final
|
|
497
|
+
Final BuildInfo.
|
|
499
498
|
|
|
500
499
|
Raises:
|
|
501
500
|
typer.Exit: On build failure or timeout.
|
|
502
501
|
"""
|
|
503
502
|
if logs:
|
|
504
|
-
_stream_build_logs(client, provider_id,
|
|
503
|
+
_stream_build_logs(client, provider_id, version)
|
|
505
504
|
else:
|
|
506
|
-
build_result = _poll_build_status(client, provider_id,
|
|
505
|
+
build_result = _poll_build_status(client, provider_id, version)
|
|
507
506
|
|
|
508
507
|
if build_result.status == BuildStatus.FAILED:
|
|
509
508
|
console.print(f"[red]Build failed:[/red] {build_result.error_message}")
|
|
510
509
|
raise typer.Exit(1)
|
|
511
510
|
|
|
512
|
-
console.print(f"[green]Build successful:[/green] {build_result.
|
|
511
|
+
console.print(f"[green]Build successful:[/green] {build_result.version}")
|
|
513
512
|
|
|
514
|
-
final_build = client.get_build_status(provider_id,
|
|
513
|
+
final_build = client.get_build_status(provider_id, version)
|
|
515
514
|
|
|
516
515
|
if final_build.status != BuildStatus.SUCCESS:
|
|
517
516
|
console.print(f"[red]Build failed:[/red] {final_build.error_message}")
|
|
@@ -520,16 +519,16 @@ def _wait_for_build(
|
|
|
520
519
|
return final_build
|
|
521
520
|
|
|
522
521
|
|
|
523
|
-
def _poll_build_status(client: PragmaClient, provider_id: str,
|
|
522
|
+
def _poll_build_status(client: PragmaClient, provider_id: str, version: str) -> BuildInfo:
|
|
524
523
|
"""Poll build status until completion or timeout.
|
|
525
524
|
|
|
526
525
|
Args:
|
|
527
526
|
client: SDK client instance.
|
|
528
527
|
provider_id: Provider identifier.
|
|
529
|
-
|
|
528
|
+
version: CalVer version string.
|
|
530
529
|
|
|
531
530
|
Returns:
|
|
532
|
-
Final
|
|
531
|
+
Final BuildInfo.
|
|
533
532
|
|
|
534
533
|
Raises:
|
|
535
534
|
typer.Exit: If build times out.
|
|
@@ -545,7 +544,7 @@ def _poll_build_status(client: PragmaClient, provider_id: str, job_name: str) ->
|
|
|
545
544
|
task = progress.add_task("Building...", total=None)
|
|
546
545
|
|
|
547
546
|
while True:
|
|
548
|
-
build_result = client.get_build_status(provider_id,
|
|
547
|
+
build_result = client.get_build_status(provider_id, version)
|
|
549
548
|
|
|
550
549
|
if build_result.status in (BuildStatus.SUCCESS, BuildStatus.FAILED):
|
|
551
550
|
return build_result
|
|
@@ -559,37 +558,37 @@ def _poll_build_status(client: PragmaClient, provider_id: str, job_name: str) ->
|
|
|
559
558
|
time.sleep(BUILD_POLL_INTERVAL)
|
|
560
559
|
|
|
561
560
|
|
|
562
|
-
def _stream_build_logs(client: PragmaClient, provider_id: str,
|
|
561
|
+
def _stream_build_logs(client: PragmaClient, provider_id: str, version: str) -> None:
|
|
563
562
|
"""Stream build logs to console.
|
|
564
563
|
|
|
565
564
|
Args:
|
|
566
565
|
client: SDK client instance.
|
|
567
566
|
provider_id: Provider identifier.
|
|
568
|
-
|
|
567
|
+
version: CalVer version string.
|
|
569
568
|
"""
|
|
570
569
|
console.print()
|
|
571
570
|
console.print("[bold]Build logs:[/bold]")
|
|
572
571
|
console.print("-" * 40)
|
|
573
572
|
|
|
574
573
|
try:
|
|
575
|
-
with client.stream_build_logs(provider_id,
|
|
574
|
+
with client.stream_build_logs(provider_id, version) as response:
|
|
576
575
|
for line in response.iter_lines():
|
|
577
576
|
console.print(line)
|
|
578
577
|
except httpx.HTTPError as e:
|
|
579
578
|
console.print(f"[yellow]Warning:[/yellow] Could not stream logs: {e}")
|
|
580
579
|
console.print("[dim]Falling back to polling...[/dim]")
|
|
581
|
-
_poll_build_status(client, provider_id,
|
|
580
|
+
_poll_build_status(client, provider_id, version)
|
|
582
581
|
|
|
583
582
|
console.print("-" * 40)
|
|
584
583
|
|
|
585
584
|
|
|
586
|
-
def _deploy_provider(client: PragmaClient, provider_id: str,
|
|
587
|
-
"""Deploy the provider.
|
|
585
|
+
def _deploy_provider(client: PragmaClient, provider_id: str, version: str | None = None) -> None:
|
|
586
|
+
"""Deploy the provider to a specific version.
|
|
588
587
|
|
|
589
588
|
Args:
|
|
590
589
|
client: SDK client instance.
|
|
591
590
|
provider_id: Provider identifier.
|
|
592
|
-
|
|
591
|
+
version: Version to deploy (CalVer format). If None, deploys latest.
|
|
593
592
|
"""
|
|
594
593
|
with Progress(
|
|
595
594
|
SpinnerColumn(),
|
|
@@ -598,46 +597,50 @@ def _deploy_provider(client: PragmaClient, provider_id: str, image: str) -> None
|
|
|
598
597
|
transient=True,
|
|
599
598
|
) as progress:
|
|
600
599
|
progress.add_task("Deploying...", total=None)
|
|
601
|
-
deploy_result = client.deploy_provider(provider_id,
|
|
600
|
+
deploy_result = client.deploy_provider(provider_id, version)
|
|
602
601
|
|
|
603
|
-
console.print(f"[green]Deployment started:[/green] {
|
|
602
|
+
console.print(f"[green]Deployment started:[/green] {provider_id}")
|
|
603
|
+
if deploy_result.image:
|
|
604
|
+
console.print(f"[dim]Image:[/dim] {deploy_result.image}")
|
|
604
605
|
console.print(f"[dim]Status:[/dim] {deploy_result.status.value}")
|
|
605
606
|
|
|
606
607
|
|
|
607
608
|
@app.command()
|
|
608
609
|
def deploy(
|
|
609
|
-
|
|
610
|
+
provider_id: Annotated[
|
|
610
611
|
str,
|
|
611
|
-
typer.
|
|
612
|
+
typer.Argument(
|
|
613
|
+
help="Provider ID to deploy (e.g., 'postgres', 'my-provider')",
|
|
614
|
+
autocompletion=completion_provider_ids,
|
|
615
|
+
),
|
|
612
616
|
],
|
|
613
|
-
|
|
617
|
+
version: Annotated[
|
|
614
618
|
str | None,
|
|
615
|
-
typer.
|
|
619
|
+
typer.Argument(
|
|
620
|
+
help="Version to deploy (e.g., 20250115.120000). Defaults to latest.",
|
|
621
|
+
autocompletion=completion_provider_versions,
|
|
622
|
+
),
|
|
616
623
|
] = None,
|
|
617
624
|
):
|
|
618
|
-
"""Deploy a provider
|
|
625
|
+
"""Deploy a provider to a specific version.
|
|
619
626
|
|
|
620
|
-
Deploys
|
|
621
|
-
|
|
627
|
+
Deploys the provider to Kubernetes. If no version is specified, deploys
|
|
628
|
+
the latest successful build. Use 'pragma providers builds' to see available versions.
|
|
622
629
|
|
|
623
|
-
|
|
624
|
-
pragma
|
|
625
|
-
|
|
630
|
+
Deploy latest:
|
|
631
|
+
pragma providers deploy postgres
|
|
632
|
+
|
|
633
|
+
Deploy specific version:
|
|
634
|
+
pragma providers deploy postgres 20250115.120000
|
|
626
635
|
|
|
627
636
|
Raises:
|
|
628
637
|
typer.Exit: If deployment fails.
|
|
629
638
|
"""
|
|
630
|
-
provider_name = package or detect_provider_package()
|
|
631
|
-
|
|
632
|
-
if not provider_name:
|
|
633
|
-
console.print("[red]Error:[/red] Could not detect provider package.")
|
|
634
|
-
console.print("Run from a provider directory or specify --package")
|
|
635
|
-
raise typer.Exit(1)
|
|
636
|
-
|
|
637
|
-
provider_id = provider_name.replace("_", "-").removesuffix("-provider")
|
|
638
|
-
|
|
639
639
|
console.print(f"[bold]Deploying provider:[/bold] {provider_id}")
|
|
640
|
-
|
|
640
|
+
if version:
|
|
641
|
+
console.print(f"[dim]Version:[/dim] {version}")
|
|
642
|
+
else:
|
|
643
|
+
console.print("[dim]Version:[/dim] latest")
|
|
641
644
|
console.print()
|
|
642
645
|
|
|
643
646
|
client = get_client()
|
|
@@ -647,119 +650,12 @@ def deploy(
|
|
|
647
650
|
raise typer.Exit(1)
|
|
648
651
|
|
|
649
652
|
try:
|
|
650
|
-
_deploy_provider(client, provider_id,
|
|
653
|
+
_deploy_provider(client, provider_id, version)
|
|
651
654
|
except Exception as e:
|
|
652
655
|
console.print(f"[red]Error:[/red] {e}")
|
|
653
656
|
raise typer.Exit(1)
|
|
654
657
|
|
|
655
658
|
|
|
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
659
|
def detect_provider_package() -> str | None:
|
|
764
660
|
"""Detect provider package name from current directory.
|
|
765
661
|
|
|
@@ -784,7 +680,10 @@ def detect_provider_package() -> str | None:
|
|
|
784
680
|
def delete(
|
|
785
681
|
provider_id: Annotated[
|
|
786
682
|
str,
|
|
787
|
-
typer.Argument(
|
|
683
|
+
typer.Argument(
|
|
684
|
+
help="Provider ID to delete (e.g., 'postgres', 'my-provider')",
|
|
685
|
+
autocompletion=completion_provider_ids,
|
|
686
|
+
),
|
|
788
687
|
],
|
|
789
688
|
cascade: Annotated[
|
|
790
689
|
bool,
|
|
@@ -801,21 +700,21 @@ def delete(
|
|
|
801
700
|
from the platform. By default, fails if the provider has any resources.
|
|
802
701
|
|
|
803
702
|
Without --cascade:
|
|
804
|
-
pragma
|
|
703
|
+
pragma providers delete my-provider
|
|
805
704
|
-> Fails if provider has resources
|
|
806
705
|
|
|
807
706
|
With --cascade:
|
|
808
|
-
pragma
|
|
707
|
+
pragma providers delete my-provider --cascade
|
|
809
708
|
-> Deletes provider and all its resources
|
|
810
709
|
|
|
811
710
|
Skip confirmation:
|
|
812
|
-
pragma
|
|
813
|
-
pragma
|
|
711
|
+
pragma providers delete my-provider --force
|
|
712
|
+
pragma providers delete my-provider --cascade --force
|
|
814
713
|
|
|
815
714
|
Example:
|
|
816
|
-
pragma
|
|
817
|
-
pragma
|
|
818
|
-
pragma
|
|
715
|
+
pragma providers delete postgres
|
|
716
|
+
pragma providers delete postgres --cascade
|
|
717
|
+
pragma providers delete postgres --cascade --force
|
|
819
718
|
|
|
820
719
|
Raises:
|
|
821
720
|
typer.Exit: If deletion fails or user cancels.
|
|
@@ -823,7 +722,7 @@ def delete(
|
|
|
823
722
|
client = get_client()
|
|
824
723
|
|
|
825
724
|
if client._auth is None:
|
|
826
|
-
console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
|
|
725
|
+
console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
|
|
827
726
|
raise typer.Exit(1)
|
|
828
727
|
|
|
829
728
|
console.print(f"[bold]Provider:[/bold] {provider_id}")
|
|
@@ -871,146 +770,37 @@ def _print_delete_result(result: ProviderDeleteResult) -> None:
|
|
|
871
770
|
"""
|
|
872
771
|
console.print()
|
|
873
772
|
console.print(f"[green]Provider deleted:[/green] {result.provider_id}")
|
|
874
|
-
console.print()
|
|
875
773
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
table.add_column("Deleted", justify="right")
|
|
879
|
-
|
|
880
|
-
table.add_row("Builds Cancelled", str(result.builds_cancelled))
|
|
881
|
-
table.add_row("Source Archives", str(result.source_archives_deleted))
|
|
882
|
-
table.add_row("Deployment", "Yes" if result.deployment_deleted else "No (not found)")
|
|
883
|
-
table.add_row("Resources", str(result.resources_deleted))
|
|
884
|
-
table.add_row("Resource Definitions", str(result.resource_definitions_deleted))
|
|
885
|
-
table.add_row("Outbox Events", str(result.outbox_events_deleted))
|
|
886
|
-
table.add_row("Dead Letter Events", str(result.dead_letter_events_deleted))
|
|
887
|
-
table.add_row("NATS Messages Purged", str(result.messages_purged))
|
|
888
|
-
table.add_row("NATS Consumer", "Deleted" if result.consumer_deleted else "Not found")
|
|
774
|
+
if result.deployment_deleted:
|
|
775
|
+
console.print("[dim]Deployment stopped[/dim]")
|
|
889
776
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
|
777
|
+
if result.resources_deleted > 0:
|
|
778
|
+
console.print(f"[dim]Resources deleted: {result.resources_deleted}[/dim]")
|
|
994
779
|
|
|
995
780
|
|
|
996
781
|
@app.command()
|
|
997
782
|
def status(
|
|
998
783
|
provider_id: Annotated[
|
|
999
784
|
str,
|
|
1000
|
-
typer.Argument(
|
|
785
|
+
typer.Argument(
|
|
786
|
+
help="Provider ID to check status (e.g., 'postgres', 'my-provider')",
|
|
787
|
+
autocompletion=completion_provider_ids,
|
|
788
|
+
),
|
|
1001
789
|
],
|
|
790
|
+
output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
|
|
1002
791
|
):
|
|
1003
792
|
"""Check the deployment status of a provider.
|
|
1004
793
|
|
|
1005
794
|
Displays:
|
|
1006
795
|
- Deployment status (pending/progressing/available/failed)
|
|
1007
|
-
-
|
|
1008
|
-
-
|
|
796
|
+
- Deployed version
|
|
797
|
+
- Health status
|
|
1009
798
|
- Last updated timestamp
|
|
1010
799
|
|
|
1011
|
-
|
|
1012
|
-
pragma
|
|
1013
|
-
pragma
|
|
800
|
+
Examples:
|
|
801
|
+
pragma providers status postgres
|
|
802
|
+
pragma providers status my-provider
|
|
803
|
+
pragma providers status postgres -o json
|
|
1014
804
|
|
|
1015
805
|
Raises:
|
|
1016
806
|
typer.Exit: If deployment not found or status check fails.
|
|
@@ -1018,7 +808,7 @@ def status(
|
|
|
1018
808
|
client = get_client()
|
|
1019
809
|
|
|
1020
810
|
if client._auth is None:
|
|
1021
|
-
console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
|
|
811
|
+
console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
|
|
1022
812
|
raise typer.Exit(1)
|
|
1023
813
|
|
|
1024
814
|
try:
|
|
@@ -1033,7 +823,13 @@ def status(
|
|
|
1033
823
|
console.print(f"[red]Error:[/red] {e}")
|
|
1034
824
|
raise typer.Exit(1)
|
|
1035
825
|
|
|
1036
|
-
|
|
826
|
+
if output == OutputFormat.TABLE:
|
|
827
|
+
_print_deployment_status(provider_id, result)
|
|
828
|
+
else:
|
|
829
|
+
# Convert Pydantic model to dict for JSON/YAML output
|
|
830
|
+
data = result.model_dump(mode="json")
|
|
831
|
+
data["provider_id"] = provider_id # Include provider_id in output
|
|
832
|
+
output_data(data, output)
|
|
1037
833
|
|
|
1038
834
|
|
|
1039
835
|
def _print_deployment_status(provider_id: str, result) -> None:
|
|
@@ -1060,19 +856,18 @@ def _print_deployment_status(provider_id: str, result) -> None:
|
|
|
1060
856
|
table.add_column("Value")
|
|
1061
857
|
|
|
1062
858
|
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
859
|
|
|
1065
860
|
if result.image:
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
861
|
+
table.add_row("Image", result.image)
|
|
862
|
+
|
|
863
|
+
# Healthy is determined by having ready replicas
|
|
864
|
+
healthy = result.ready_replicas > 0
|
|
865
|
+
healthy_display = "[green]yes[/green]" if healthy else "[red]no[/red]"
|
|
866
|
+
table.add_row("Healthy", healthy_display)
|
|
1069
867
|
|
|
1070
868
|
if result.updated_at:
|
|
1071
869
|
table.add_row("Updated", result.updated_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
|
|
1072
870
|
|
|
1073
|
-
if result.message:
|
|
1074
|
-
table.add_row("Message", result.message)
|
|
1075
|
-
|
|
1076
871
|
console.print(table)
|
|
1077
872
|
|
|
1078
873
|
|
|
@@ -1080,7 +875,10 @@ def _print_deployment_status(provider_id: str, result) -> None:
|
|
|
1080
875
|
def builds(
|
|
1081
876
|
provider_id: Annotated[
|
|
1082
877
|
str,
|
|
1083
|
-
typer.Argument(
|
|
878
|
+
typer.Argument(
|
|
879
|
+
help="Provider ID to list builds for (e.g., 'postgres', 'my-provider')",
|
|
880
|
+
autocompletion=completion_provider_ids,
|
|
881
|
+
),
|
|
1084
882
|
],
|
|
1085
883
|
):
|
|
1086
884
|
"""List build history for a provider.
|
|
@@ -1089,8 +887,8 @@ def builds(
|
|
|
1089
887
|
Useful for selecting versions for rollback and verifying build status.
|
|
1090
888
|
|
|
1091
889
|
Example:
|
|
1092
|
-
pragma
|
|
1093
|
-
pragma
|
|
890
|
+
pragma providers builds postgres
|
|
891
|
+
pragma providers builds my-provider
|
|
1094
892
|
|
|
1095
893
|
Raises:
|
|
1096
894
|
typer.Exit: If request fails.
|
|
@@ -1098,7 +896,7 @@ def builds(
|
|
|
1098
896
|
client = get_client()
|
|
1099
897
|
|
|
1100
898
|
if client._auth is None:
|
|
1101
|
-
console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
|
|
899
|
+
console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
|
|
1102
900
|
raise typer.Exit(1)
|
|
1103
901
|
|
|
1104
902
|
try:
|