remdb 0.3.118__py3-none-any.whl → 0.3.146__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.
Potentially problematic release.
This version of remdb might be problematic. Click here for more details.
- rem/agentic/agents/sse_simulator.py +2 -0
- rem/agentic/context.py +23 -3
- rem/agentic/mcp/tool_wrapper.py +126 -15
- rem/agentic/otel/setup.py +1 -0
- rem/agentic/providers/phoenix.py +371 -108
- rem/agentic/providers/pydantic_ai.py +122 -43
- rem/agentic/schema.py +4 -1
- rem/api/mcp_router/tools.py +13 -2
- rem/api/routers/chat/completions.py +250 -4
- rem/api/routers/chat/models.py +81 -7
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +17 -1
- rem/api/routers/chat/streaming.py +35 -1
- rem/api/routers/feedback.py +134 -14
- rem/auth/middleware.py +66 -1
- rem/cli/commands/cluster.py +590 -82
- rem/cli/commands/configure.py +3 -4
- rem/cli/commands/experiments.py +468 -76
- rem/cli/commands/session.py +336 -0
- rem/cli/dreaming.py +2 -2
- rem/cli/main.py +2 -0
- rem/config.py +8 -1
- rem/models/core/experiment.py +58 -14
- rem/models/entities/ontology.py +1 -1
- rem/models/entities/ontology_config.py +1 -1
- rem/schemas/agents/examples/contract-analyzer.yaml +1 -1
- rem/schemas/agents/examples/contract-extractor.yaml +1 -1
- rem/schemas/agents/examples/cv-parser.yaml +1 -1
- rem/services/phoenix/client.py +59 -18
- rem/services/postgres/pydantic_to_sqlalchemy.py +9 -12
- rem/services/session/compression.py +7 -0
- rem/settings.py +260 -17
- rem/sql/migrations/002_install_models.sql +91 -91
- rem/sql/migrations/004_cache_system.sql +1 -1
- rem/utils/README.md +45 -0
- rem/utils/files.py +157 -1
- rem/utils/schema_loader.py +94 -3
- rem/utils/vision.py +1 -1
- rem/workers/__init__.py +2 -1
- rem/workers/db_listener.py +579 -0
- {remdb-0.3.118.dist-info → remdb-0.3.146.dist-info}/METADATA +161 -147
- {remdb-0.3.118.dist-info → remdb-0.3.146.dist-info}/RECORD +44 -41
- {remdb-0.3.118.dist-info → remdb-0.3.146.dist-info}/WHEEL +0 -0
- {remdb-0.3.118.dist-info → remdb-0.3.146.dist-info}/entry_points.txt +0 -0
rem/cli/commands/cluster.py
CHANGED
|
@@ -324,19 +324,34 @@ def setup_ssm(config: Path | None, dry_run: bool, force: bool):
|
|
|
324
324
|
"""
|
|
325
325
|
Create required SSM parameters in AWS.
|
|
326
326
|
|
|
327
|
+
Reads API keys from environment variables if set:
|
|
328
|
+
- ANTHROPIC_API_KEY
|
|
329
|
+
- OPENAI_API_KEY
|
|
330
|
+
- GOOGLE_CLIENT_ID (optional)
|
|
331
|
+
- GOOGLE_CLIENT_SECRET (optional)
|
|
332
|
+
|
|
327
333
|
Creates the following parameters under the configured SSM prefix:
|
|
328
|
-
- /postgres/username (String)
|
|
334
|
+
- /postgres/username (String: remuser)
|
|
329
335
|
- /postgres/password (SecureString, auto-generated)
|
|
330
|
-
- /llm/anthropic-api-key (SecureString, placeholder)
|
|
331
|
-
- /llm/openai-api-key (SecureString, placeholder)
|
|
332
|
-
|
|
333
|
-
|
|
336
|
+
- /llm/anthropic-api-key (SecureString, from env or placeholder)
|
|
337
|
+
- /llm/openai-api-key (SecureString, from env or placeholder)
|
|
338
|
+
- /auth/session-secret (SecureString, auto-generated)
|
|
339
|
+
- /auth/google-client-id (String, from env or placeholder)
|
|
340
|
+
- /auth/google-client-secret (SecureString, from env or placeholder)
|
|
334
341
|
- /phoenix/api-key (SecureString, auto-generated)
|
|
335
342
|
- /phoenix/secret (SecureString, auto-generated)
|
|
343
|
+
- /phoenix/admin-secret (SecureString, auto-generated)
|
|
336
344
|
|
|
337
345
|
Examples:
|
|
346
|
+
# With environment variables set
|
|
347
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
348
|
+
export OPENAI_API_KEY=sk-proj-...
|
|
338
349
|
rem cluster setup-ssm
|
|
350
|
+
|
|
351
|
+
# Using config file
|
|
339
352
|
rem cluster setup-ssm --config my-cluster.yaml
|
|
353
|
+
|
|
354
|
+
# Preview without creating
|
|
340
355
|
rem cluster setup-ssm --dry-run
|
|
341
356
|
"""
|
|
342
357
|
import secrets
|
|
@@ -345,6 +360,12 @@ def setup_ssm(config: Path | None, dry_run: bool, force: bool):
|
|
|
345
360
|
prefix = cfg.get("aws", {}).get("ssmPrefix", "/rem")
|
|
346
361
|
region = cfg.get("aws", {}).get("region", "us-east-1")
|
|
347
362
|
|
|
363
|
+
# Read API keys from environment
|
|
364
|
+
anthropic_key = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
365
|
+
openai_key = os.environ.get("OPENAI_API_KEY", "")
|
|
366
|
+
google_client_id = os.environ.get("GOOGLE_CLIENT_ID", "placeholder")
|
|
367
|
+
google_client_secret = os.environ.get("GOOGLE_CLIENT_SECRET", "placeholder")
|
|
368
|
+
|
|
348
369
|
click.echo()
|
|
349
370
|
click.echo("SSM Parameter Setup")
|
|
350
371
|
click.echo("=" * 60)
|
|
@@ -352,19 +373,36 @@ def setup_ssm(config: Path | None, dry_run: bool, force: bool):
|
|
|
352
373
|
click.echo(f"Region: {region}")
|
|
353
374
|
click.echo()
|
|
354
375
|
|
|
376
|
+
# Show env var status
|
|
377
|
+
click.echo("Environment variables:")
|
|
378
|
+
click.echo(f" ANTHROPIC_API_KEY: {'✓ set' if anthropic_key else '✗ not set (will use placeholder)'}")
|
|
379
|
+
click.echo(f" OPENAI_API_KEY: {'✓ set' if openai_key else '✗ not set (will use placeholder)'}")
|
|
380
|
+
click.echo(f" GOOGLE_CLIENT_ID: {'✓ set' if google_client_id != 'placeholder' else '⚠ not set (OAuth disabled)'}")
|
|
381
|
+
click.echo(f" GOOGLE_CLIENT_SECRET: {'✓ set' if google_client_secret != 'placeholder' else '⚠ not set (OAuth disabled)'}")
|
|
382
|
+
click.echo()
|
|
383
|
+
|
|
355
384
|
# Define parameters to create
|
|
356
385
|
parameters = [
|
|
357
|
-
#
|
|
358
|
-
(f"{prefix}/postgres/username", "remuser", "String", "PostgreSQL username"),
|
|
386
|
+
# PostgreSQL - username MUST be remuser to match CNPG cluster owner spec
|
|
387
|
+
(f"{prefix}/postgres/username", "remuser", "String", "PostgreSQL username (must match CNPG owner)"),
|
|
359
388
|
(f"{prefix}/postgres/password", secrets.token_urlsafe(24), "SecureString", "PostgreSQL password"),
|
|
360
|
-
# LLM keys -
|
|
361
|
-
(f"{prefix}/llm/anthropic-api-key", "REPLACE_WITH_YOUR_KEY", "SecureString", "Anthropic API key"),
|
|
362
|
-
(f"{prefix}/llm/openai-api-key", "REPLACE_WITH_YOUR_KEY", "SecureString", "OpenAI API key"),
|
|
389
|
+
# LLM keys - from env or placeholder
|
|
390
|
+
(f"{prefix}/llm/anthropic-api-key", anthropic_key or "REPLACE_WITH_YOUR_KEY", "SecureString", "Anthropic API key"),
|
|
391
|
+
(f"{prefix}/llm/openai-api-key", openai_key or "REPLACE_WITH_YOUR_KEY", "SecureString", "OpenAI API key"),
|
|
392
|
+
# Auth secrets
|
|
393
|
+
(f"{prefix}/auth/session-secret", secrets.token_urlsafe(32), "SecureString", "Session signing secret"),
|
|
394
|
+
(f"{prefix}/auth/google-client-id", google_client_id, "String", "Google OAuth client ID"),
|
|
395
|
+
(f"{prefix}/auth/google-client-secret", google_client_secret, "SecureString", "Google OAuth client secret"),
|
|
363
396
|
# Phoenix - auto-generated
|
|
364
|
-
(f"{prefix}/phoenix/api-key", secrets.
|
|
365
|
-
(f"{prefix}/phoenix/secret", secrets.
|
|
397
|
+
(f"{prefix}/phoenix/api-key", secrets.token_urlsafe(24), "SecureString", "Phoenix API key"),
|
|
398
|
+
(f"{prefix}/phoenix/secret", secrets.token_urlsafe(32), "SecureString", "Phoenix session secret"),
|
|
399
|
+
(f"{prefix}/phoenix/admin-secret", secrets.token_urlsafe(32), "SecureString", "Phoenix admin secret"),
|
|
366
400
|
]
|
|
367
401
|
|
|
402
|
+
created = 0
|
|
403
|
+
skipped = 0
|
|
404
|
+
failed = 0
|
|
405
|
+
|
|
368
406
|
for name, value, param_type, description in parameters:
|
|
369
407
|
# Check if exists
|
|
370
408
|
check_cmd = ["aws", "ssm", "get-parameter", "--name", name, "--region", region]
|
|
@@ -375,43 +413,62 @@ def setup_ssm(config: Path | None, dry_run: bool, force: bool):
|
|
|
375
413
|
|
|
376
414
|
if exists and not force:
|
|
377
415
|
click.echo(f" ⏭ {name} (exists, skipping)")
|
|
416
|
+
skipped += 1
|
|
378
417
|
continue
|
|
379
418
|
|
|
380
419
|
# Create/update parameter
|
|
381
420
|
put_cmd = [
|
|
382
421
|
"aws", "ssm", "put-parameter",
|
|
383
422
|
"--name", name,
|
|
384
|
-
"--value", value
|
|
423
|
+
"--value", value,
|
|
385
424
|
"--type", param_type,
|
|
386
425
|
"--region", region,
|
|
387
|
-
"--overwrite" if force else "",
|
|
388
426
|
"--description", description,
|
|
389
427
|
]
|
|
390
|
-
|
|
391
|
-
|
|
428
|
+
if force:
|
|
429
|
+
put_cmd.append("--overwrite")
|
|
392
430
|
|
|
393
431
|
if dry_run:
|
|
394
432
|
display_value = "***" if param_type == "SecureString" else value
|
|
395
|
-
|
|
433
|
+
if "REPLACE" in value or value == "placeholder":
|
|
434
|
+
click.secho(f" Would create: {name} = {display_value} (PLACEHOLDER)", fg="yellow")
|
|
435
|
+
else:
|
|
436
|
+
click.echo(f" Would create: {name} = {display_value}")
|
|
396
437
|
else:
|
|
397
438
|
try:
|
|
398
439
|
subprocess.run(put_cmd, check=True, capture_output=True)
|
|
399
|
-
|
|
440
|
+
if "REPLACE" in value or value == "placeholder":
|
|
441
|
+
click.secho(f" ⚠ {name} (placeholder - update later)", fg="yellow")
|
|
442
|
+
else:
|
|
443
|
+
click.secho(f" ✓ {name}", fg="green")
|
|
444
|
+
created += 1
|
|
400
445
|
except subprocess.CalledProcessError as e:
|
|
401
446
|
if "ParameterAlreadyExists" in str(e.stderr):
|
|
402
447
|
click.echo(f" ⏭ {name} (exists)")
|
|
448
|
+
skipped += 1
|
|
403
449
|
else:
|
|
404
450
|
click.secho(f" ✗ {name}: {e.stderr.decode()}", fg="red")
|
|
451
|
+
failed += 1
|
|
405
452
|
|
|
406
453
|
click.echo()
|
|
407
454
|
if dry_run:
|
|
408
455
|
click.secho("Dry run - no parameters created", fg="yellow")
|
|
409
456
|
else:
|
|
410
|
-
click.secho("✓ SSM
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
457
|
+
click.secho(f"✓ SSM setup complete: {created} created, {skipped} skipped, {failed} failed", fg="green")
|
|
458
|
+
|
|
459
|
+
# Show update instructions if placeholders were used
|
|
460
|
+
if not anthropic_key or not openai_key:
|
|
461
|
+
click.echo()
|
|
462
|
+
click.secho("IMPORTANT: Update placeholder API keys:", fg="yellow")
|
|
463
|
+
if not anthropic_key:
|
|
464
|
+
click.echo(f" aws ssm put-parameter --name {prefix}/llm/anthropic-api-key --value 'sk-ant-...' --type SecureString --overwrite --region {region}")
|
|
465
|
+
if not openai_key:
|
|
466
|
+
click.echo(f" aws ssm put-parameter --name {prefix}/llm/openai-api-key --value 'sk-proj-...' --type SecureString --overwrite --region {region}")
|
|
467
|
+
click.echo()
|
|
468
|
+
click.echo("Or set environment variables and re-run with --force:")
|
|
469
|
+
click.echo(" export ANTHROPIC_API_KEY=sk-ant-...")
|
|
470
|
+
click.echo(" export OPENAI_API_KEY=sk-proj-...")
|
|
471
|
+
click.echo(" rem cluster setup-ssm --force")
|
|
415
472
|
|
|
416
473
|
|
|
417
474
|
def _generate_sql_configmap(project_name: str, namespace: str, output_dir: Path) -> None:
|
|
@@ -477,20 +534,31 @@ def _generate_sql_configmap(project_name: str, namespace: str, output_dir: Path)
|
|
|
477
534
|
type=click.Path(exists=True, path_type=Path),
|
|
478
535
|
help="Path to cluster config file",
|
|
479
536
|
)
|
|
480
|
-
|
|
537
|
+
@click.option(
|
|
538
|
+
"--pre-argocd",
|
|
539
|
+
is_flag=True,
|
|
540
|
+
help="Only check prerequisites needed before ArgoCD deployment",
|
|
541
|
+
)
|
|
542
|
+
def validate(config: Path | None, pre_argocd: bool):
|
|
481
543
|
"""
|
|
482
544
|
Validate deployment prerequisites.
|
|
483
545
|
|
|
484
546
|
Checks:
|
|
485
|
-
1. kubectl
|
|
486
|
-
2.
|
|
487
|
-
3.
|
|
488
|
-
4.
|
|
489
|
-
5.
|
|
490
|
-
6.
|
|
547
|
+
1. Required tools (kubectl, aws, openssl)
|
|
548
|
+
2. AWS credentials
|
|
549
|
+
3. Kubernetes connectivity
|
|
550
|
+
4. ArgoCD installation
|
|
551
|
+
5. Environment variables (for setup-ssm)
|
|
552
|
+
6. SSM parameters
|
|
553
|
+
7. Platform operators (ESO, CNPG, KEDA) - skipped with --pre-argocd
|
|
554
|
+
8. ClusterSecretStores - skipped with --pre-argocd
|
|
555
|
+
|
|
556
|
+
Use --pre-argocd to validate only prerequisites needed before
|
|
557
|
+
running 'rem cluster apply' for the first time.
|
|
491
558
|
|
|
492
559
|
Examples:
|
|
493
|
-
rem cluster validate
|
|
560
|
+
rem cluster validate # Full validation
|
|
561
|
+
rem cluster validate --pre-argocd # Pre-deployment checks only
|
|
494
562
|
rem cluster validate --config my-cluster.yaml
|
|
495
563
|
"""
|
|
496
564
|
cfg = load_cluster_config(config)
|
|
@@ -505,13 +573,51 @@ def validate(config: Path | None):
|
|
|
505
573
|
click.echo(f"Project: {project_name}")
|
|
506
574
|
click.echo(f"Namespace: {namespace}")
|
|
507
575
|
click.echo(f"Region: {region}")
|
|
576
|
+
if pre_argocd:
|
|
577
|
+
click.echo(f"Mode: Pre-ArgoCD (checking prerequisites only)")
|
|
508
578
|
click.echo()
|
|
509
579
|
|
|
510
580
|
errors = []
|
|
511
581
|
warnings = []
|
|
512
582
|
|
|
513
|
-
# 1. Check
|
|
514
|
-
click.echo("1.
|
|
583
|
+
# 1. Check required tools
|
|
584
|
+
click.echo("1. Required tools")
|
|
585
|
+
tools = [
|
|
586
|
+
("kubectl", ["kubectl", "version", "--client", "-o", "json"]),
|
|
587
|
+
("aws", ["aws", "--version"]),
|
|
588
|
+
("openssl", ["openssl", "version"]),
|
|
589
|
+
]
|
|
590
|
+
|
|
591
|
+
for tool, cmd in tools:
|
|
592
|
+
if shutil.which(tool):
|
|
593
|
+
click.secho(f" ✓ {tool} installed", fg="green")
|
|
594
|
+
else:
|
|
595
|
+
errors.append(f"{tool} not installed")
|
|
596
|
+
click.secho(f" ✗ {tool} not installed", fg="red")
|
|
597
|
+
|
|
598
|
+
# 2. Check AWS credentials
|
|
599
|
+
click.echo()
|
|
600
|
+
click.echo("2. AWS credentials")
|
|
601
|
+
try:
|
|
602
|
+
result = subprocess.run(
|
|
603
|
+
["aws", "sts", "get-caller-identity", "--region", region],
|
|
604
|
+
capture_output=True,
|
|
605
|
+
timeout=10,
|
|
606
|
+
)
|
|
607
|
+
if result.returncode == 0:
|
|
608
|
+
import json
|
|
609
|
+
identity = json.loads(result.stdout.decode())
|
|
610
|
+
click.secho(f" ✓ AWS credentials valid (account: {identity.get('Account', 'unknown')})", fg="green")
|
|
611
|
+
else:
|
|
612
|
+
errors.append("AWS credentials not configured")
|
|
613
|
+
click.secho(" ✗ AWS credentials not configured", fg="red")
|
|
614
|
+
except Exception as e:
|
|
615
|
+
errors.append(f"AWS CLI error: {e}")
|
|
616
|
+
click.secho(f" ✗ AWS CLI error: {e}", fg="red")
|
|
617
|
+
|
|
618
|
+
# 3. Check kubectl connectivity
|
|
619
|
+
click.echo()
|
|
620
|
+
click.echo("3. Kubernetes connectivity")
|
|
515
621
|
try:
|
|
516
622
|
result = subprocess.run(
|
|
517
623
|
["kubectl", "cluster-info"],
|
|
@@ -519,7 +625,13 @@ def validate(config: Path | None):
|
|
|
519
625
|
timeout=10,
|
|
520
626
|
)
|
|
521
627
|
if result.returncode == 0:
|
|
522
|
-
|
|
628
|
+
# Get context name
|
|
629
|
+
ctx_result = subprocess.run(
|
|
630
|
+
["kubectl", "config", "current-context"],
|
|
631
|
+
capture_output=True,
|
|
632
|
+
)
|
|
633
|
+
context = ctx_result.stdout.decode().strip() if ctx_result.returncode == 0 else "unknown"
|
|
634
|
+
click.secho(f" ✓ kubectl connected (context: {context})", fg="green")
|
|
523
635
|
else:
|
|
524
636
|
errors.append("kubectl not connected to cluster")
|
|
525
637
|
click.secho(" ✗ kubectl not connected", fg="red")
|
|
@@ -527,53 +639,65 @@ def validate(config: Path | None):
|
|
|
527
639
|
errors.append(f"kubectl error: {e}")
|
|
528
640
|
click.secho(f" ✗ kubectl error: {e}", fg="red")
|
|
529
641
|
|
|
530
|
-
#
|
|
642
|
+
# 4. Check ArgoCD installation
|
|
531
643
|
click.echo()
|
|
532
|
-
click.echo("
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
644
|
+
click.echo("4. ArgoCD installation")
|
|
645
|
+
try:
|
|
646
|
+
# Check namespace
|
|
647
|
+
result = subprocess.run(
|
|
648
|
+
["kubectl", "get", "namespace", "argocd"],
|
|
649
|
+
capture_output=True,
|
|
650
|
+
)
|
|
651
|
+
if result.returncode == 0:
|
|
652
|
+
click.secho(" ✓ ArgoCD namespace exists", fg="green")
|
|
538
653
|
|
|
539
|
-
|
|
540
|
-
try:
|
|
654
|
+
# Check server deployment
|
|
541
655
|
result = subprocess.run(
|
|
542
|
-
["kubectl", "get", "deployment",
|
|
656
|
+
["kubectl", "get", "deployment", "argocd-server", "-n", "argocd", "-o", "jsonpath={.status.readyReplicas}"],
|
|
543
657
|
capture_output=True,
|
|
544
658
|
)
|
|
545
|
-
if result.returncode == 0:
|
|
546
|
-
|
|
659
|
+
if result.returncode == 0 and result.stdout.decode().strip():
|
|
660
|
+
replicas = result.stdout.decode().strip()
|
|
661
|
+
click.secho(f" ✓ ArgoCD server running ({replicas} replica(s))", fg="green")
|
|
547
662
|
else:
|
|
548
|
-
warnings.append(
|
|
549
|
-
click.secho(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
click.secho(
|
|
663
|
+
warnings.append("ArgoCD server not ready")
|
|
664
|
+
click.secho(" ⚠ ArgoCD server not ready", fg="yellow")
|
|
665
|
+
else:
|
|
666
|
+
errors.append("ArgoCD not installed")
|
|
667
|
+
click.secho(" ✗ ArgoCD namespace not found", fg="red")
|
|
668
|
+
click.echo(" Install with:")
|
|
669
|
+
click.echo(" kubectl create namespace argocd")
|
|
670
|
+
click.echo(" kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml")
|
|
671
|
+
except Exception as e:
|
|
672
|
+
errors.append(f"Could not check ArgoCD: {e}")
|
|
673
|
+
click.secho(f" ✗ Could not check ArgoCD: {e}", fg="red")
|
|
553
674
|
|
|
554
|
-
#
|
|
675
|
+
# 5. Check environment variables
|
|
555
676
|
click.echo()
|
|
556
|
-
click.echo("
|
|
557
|
-
|
|
677
|
+
click.echo("5. Environment variables (for setup-ssm)")
|
|
678
|
+
env_vars = [
|
|
679
|
+
("ANTHROPIC_API_KEY", True),
|
|
680
|
+
("OPENAI_API_KEY", True),
|
|
681
|
+
("GITHUB_PAT", True),
|
|
682
|
+
("GITHUB_USERNAME", True),
|
|
683
|
+
("GITHUB_REPO_URL", True),
|
|
684
|
+
("GOOGLE_CLIENT_ID", False),
|
|
685
|
+
("GOOGLE_CLIENT_SECRET", False),
|
|
686
|
+
]
|
|
558
687
|
|
|
559
|
-
for
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
)
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
warnings.append(f"ClusterSecretStore {store} not found")
|
|
569
|
-
click.secho(f" ⚠ {store} not found", fg="yellow")
|
|
570
|
-
except Exception:
|
|
571
|
-
warnings.append(f"Could not check ClusterSecretStore {store}")
|
|
572
|
-
click.secho(f" ⚠ Could not check {store}", fg="yellow")
|
|
688
|
+
for var, required in env_vars:
|
|
689
|
+
value = os.environ.get(var, "")
|
|
690
|
+
if value:
|
|
691
|
+
click.secho(f" ✓ {var} is set", fg="green")
|
|
692
|
+
elif required:
|
|
693
|
+
warnings.append(f"Environment variable not set: {var}")
|
|
694
|
+
click.secho(f" ⚠ {var} not set (required for setup-ssm)", fg="yellow")
|
|
695
|
+
else:
|
|
696
|
+
click.echo(f" - {var} not set (optional)")
|
|
573
697
|
|
|
574
|
-
#
|
|
698
|
+
# 6. Check SSM parameters
|
|
575
699
|
click.echo()
|
|
576
|
-
click.echo("
|
|
700
|
+
click.echo("6. SSM parameters")
|
|
577
701
|
required_params = [
|
|
578
702
|
f"{ssm_prefix}/postgres/username",
|
|
579
703
|
f"{ssm_prefix}/postgres/password",
|
|
@@ -583,6 +707,7 @@ def validate(config: Path | None):
|
|
|
583
707
|
f"{ssm_prefix}/llm/openai-api-key",
|
|
584
708
|
]
|
|
585
709
|
|
|
710
|
+
ssm_ok = True
|
|
586
711
|
for param in required_params:
|
|
587
712
|
try:
|
|
588
713
|
result = subprocess.run(
|
|
@@ -592,11 +717,16 @@ def validate(config: Path | None):
|
|
|
592
717
|
if result.returncode == 0:
|
|
593
718
|
click.secho(f" ✓ {param}", fg="green")
|
|
594
719
|
else:
|
|
595
|
-
|
|
596
|
-
|
|
720
|
+
if pre_argocd:
|
|
721
|
+
click.echo(f" - {param} (will be created by setup-ssm)")
|
|
722
|
+
else:
|
|
723
|
+
errors.append(f"Required SSM parameter missing: {param}")
|
|
724
|
+
click.secho(f" ✗ {param} (required)", fg="red")
|
|
725
|
+
ssm_ok = False
|
|
597
726
|
except Exception as e:
|
|
598
727
|
errors.append(f"Could not check SSM: {e}")
|
|
599
728
|
click.secho(f" ✗ AWS CLI error: {e}", fg="red")
|
|
729
|
+
ssm_ok = False
|
|
600
730
|
break
|
|
601
731
|
|
|
602
732
|
for param in optional_params:
|
|
@@ -614,11 +744,64 @@ def validate(config: Path | None):
|
|
|
614
744
|
else:
|
|
615
745
|
click.secho(f" ✓ {param}", fg="green")
|
|
616
746
|
else:
|
|
617
|
-
|
|
618
|
-
|
|
747
|
+
if pre_argocd:
|
|
748
|
+
click.echo(f" - {param} (will be created by setup-ssm)")
|
|
749
|
+
else:
|
|
750
|
+
warnings.append(f"Optional SSM parameter missing: {param}")
|
|
751
|
+
click.secho(f" ⚠ {param} (optional)", fg="yellow")
|
|
619
752
|
except Exception:
|
|
620
753
|
pass # Already reported AWS CLI issues
|
|
621
754
|
|
|
755
|
+
if not ssm_ok and pre_argocd:
|
|
756
|
+
click.echo(" Run 'rem cluster setup-ssm' to create parameters")
|
|
757
|
+
|
|
758
|
+
# Skip platform operator checks if --pre-argocd
|
|
759
|
+
if not pre_argocd:
|
|
760
|
+
# 7. Check platform operators
|
|
761
|
+
click.echo()
|
|
762
|
+
click.echo("7. Platform operators")
|
|
763
|
+
operators = [
|
|
764
|
+
("external-secrets-system", "external-secrets", "External Secrets Operator"),
|
|
765
|
+
("cnpg-system", "cnpg-controller-manager", "CloudNativePG"),
|
|
766
|
+
("keda", "keda-operator", "KEDA"),
|
|
767
|
+
("cert-manager", "cert-manager", "cert-manager"),
|
|
768
|
+
]
|
|
769
|
+
|
|
770
|
+
for ns, deployment, name in operators:
|
|
771
|
+
try:
|
|
772
|
+
result = subprocess.run(
|
|
773
|
+
["kubectl", "get", "deployment", deployment, "-n", ns],
|
|
774
|
+
capture_output=True,
|
|
775
|
+
)
|
|
776
|
+
if result.returncode == 0:
|
|
777
|
+
click.secho(f" ✓ {name}", fg="green")
|
|
778
|
+
else:
|
|
779
|
+
warnings.append(f"{name} not found in {ns}")
|
|
780
|
+
click.secho(f" ⚠ {name} not found", fg="yellow")
|
|
781
|
+
except Exception:
|
|
782
|
+
warnings.append(f"Could not check {name}")
|
|
783
|
+
click.secho(f" ⚠ Could not check {name}", fg="yellow")
|
|
784
|
+
|
|
785
|
+
# 8. Check ClusterSecretStores
|
|
786
|
+
click.echo()
|
|
787
|
+
click.echo("8. ClusterSecretStores")
|
|
788
|
+
stores = ["aws-parameter-store", "kubernetes-secrets"]
|
|
789
|
+
|
|
790
|
+
for store in stores:
|
|
791
|
+
try:
|
|
792
|
+
result = subprocess.run(
|
|
793
|
+
["kubectl", "get", "clustersecretstore", store],
|
|
794
|
+
capture_output=True,
|
|
795
|
+
)
|
|
796
|
+
if result.returncode == 0:
|
|
797
|
+
click.secho(f" ✓ {store}", fg="green")
|
|
798
|
+
else:
|
|
799
|
+
warnings.append(f"ClusterSecretStore {store} not found")
|
|
800
|
+
click.secho(f" ⚠ {store} not found", fg="yellow")
|
|
801
|
+
except Exception:
|
|
802
|
+
warnings.append(f"Could not check ClusterSecretStore {store}")
|
|
803
|
+
click.secho(f" ⚠ Could not check {store}", fg="yellow")
|
|
804
|
+
|
|
622
805
|
# Summary
|
|
623
806
|
click.echo()
|
|
624
807
|
click.echo("=" * 60)
|
|
@@ -630,14 +813,21 @@ def validate(config: Path | None):
|
|
|
630
813
|
raise click.Abort()
|
|
631
814
|
elif warnings:
|
|
632
815
|
click.secho(f"⚠ Validation passed with {len(warnings)} warning(s)", fg="yellow")
|
|
633
|
-
for warning in warnings:
|
|
816
|
+
for warning in warnings[:5]:
|
|
634
817
|
click.echo(f" - {warning}")
|
|
818
|
+
if len(warnings) > 5:
|
|
819
|
+
click.echo(f" ... and {len(warnings) - 5} more")
|
|
635
820
|
else:
|
|
636
821
|
click.secho("✓ All checks passed", fg="green")
|
|
637
822
|
|
|
638
823
|
click.echo()
|
|
639
|
-
|
|
640
|
-
|
|
824
|
+
if pre_argocd:
|
|
825
|
+
click.echo("Next steps:")
|
|
826
|
+
click.echo(" 1. rem cluster setup-ssm # Create SSM parameters")
|
|
827
|
+
click.echo(" 2. rem cluster apply # Deploy ArgoCD apps")
|
|
828
|
+
else:
|
|
829
|
+
click.echo("Ready to deploy:")
|
|
830
|
+
click.echo(" rem cluster apply")
|
|
641
831
|
|
|
642
832
|
|
|
643
833
|
@click.command()
|
|
@@ -738,7 +928,324 @@ def generate(config: Path | None, output_dir: Path | None):
|
|
|
738
928
|
click.echo("Next steps:")
|
|
739
929
|
click.echo(" 1. Review generated manifests")
|
|
740
930
|
click.echo(" 2. Commit changes to git")
|
|
741
|
-
click.echo(" 3. Deploy:
|
|
931
|
+
click.echo(" 3. Deploy: rem cluster apply")
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
@click.command()
|
|
935
|
+
@click.option(
|
|
936
|
+
"--config",
|
|
937
|
+
"-c",
|
|
938
|
+
type=click.Path(exists=True, path_type=Path),
|
|
939
|
+
help="Path to cluster config file",
|
|
940
|
+
)
|
|
941
|
+
@click.option(
|
|
942
|
+
"--dry-run",
|
|
943
|
+
is_flag=True,
|
|
944
|
+
help="Show what would be deployed without executing",
|
|
945
|
+
)
|
|
946
|
+
@click.option(
|
|
947
|
+
"--skip-platform",
|
|
948
|
+
is_flag=True,
|
|
949
|
+
help="Skip deploying platform-apps (only deploy rem-stack)",
|
|
950
|
+
)
|
|
951
|
+
def apply(config: Path | None, dry_run: bool, skip_platform: bool):
|
|
952
|
+
"""
|
|
953
|
+
Deploy ArgoCD applications to the cluster.
|
|
954
|
+
|
|
955
|
+
This command:
|
|
956
|
+
1. Creates ArgoCD repository secret (for private repo access)
|
|
957
|
+
2. Creates the application namespace
|
|
958
|
+
3. Deploys platform-apps (app-of-apps for operators)
|
|
959
|
+
4. Deploys rem-stack application
|
|
960
|
+
|
|
961
|
+
Required environment variables:
|
|
962
|
+
- GITHUB_REPO_URL: Git repository URL
|
|
963
|
+
- GITHUB_PAT: GitHub Personal Access Token
|
|
964
|
+
- GITHUB_USERNAME: GitHub username
|
|
965
|
+
|
|
966
|
+
Examples:
|
|
967
|
+
# Full deployment
|
|
968
|
+
rem cluster apply
|
|
969
|
+
|
|
970
|
+
# Preview what would be deployed
|
|
971
|
+
rem cluster apply --dry-run
|
|
972
|
+
|
|
973
|
+
# Only deploy rem-stack (platform already exists)
|
|
974
|
+
rem cluster apply --skip-platform
|
|
975
|
+
"""
|
|
976
|
+
cfg = load_cluster_config(config)
|
|
977
|
+
project_name = cfg.get("project", {}).get("name", "rem")
|
|
978
|
+
namespace = cfg.get("project", {}).get("namespace", project_name)
|
|
979
|
+
git_repo = cfg.get("git", {}).get("repoURL", "")
|
|
980
|
+
|
|
981
|
+
# Get credentials from environment, with fallback to gh CLI
|
|
982
|
+
github_repo_url = os.environ.get("GITHUB_REPO_URL", git_repo)
|
|
983
|
+
github_pat = os.environ.get("GITHUB_PAT", "")
|
|
984
|
+
github_username = os.environ.get("GITHUB_USERNAME", "")
|
|
985
|
+
|
|
986
|
+
# Auto-detect from gh CLI if not set
|
|
987
|
+
if not github_pat or not github_username:
|
|
988
|
+
try:
|
|
989
|
+
# Try to get from gh CLI
|
|
990
|
+
gh_user = subprocess.run(
|
|
991
|
+
["gh", "api", "user", "--jq", ".login"],
|
|
992
|
+
capture_output=True, text=True, timeout=10
|
|
993
|
+
)
|
|
994
|
+
gh_token = subprocess.run(
|
|
995
|
+
["gh", "auth", "token"],
|
|
996
|
+
capture_output=True, text=True, timeout=10
|
|
997
|
+
)
|
|
998
|
+
if gh_user.returncode == 0 and gh_token.returncode == 0:
|
|
999
|
+
if not github_username:
|
|
1000
|
+
github_username = gh_user.stdout.strip()
|
|
1001
|
+
if not github_pat:
|
|
1002
|
+
github_pat = gh_token.stdout.strip()
|
|
1003
|
+
click.secho(" ℹ Using credentials from gh CLI", fg="cyan")
|
|
1004
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
1005
|
+
pass # gh CLI not available
|
|
1006
|
+
|
|
1007
|
+
# Info about token type
|
|
1008
|
+
if github_pat:
|
|
1009
|
+
if github_pat.startswith("gho_"):
|
|
1010
|
+
click.secho(" ℹ Using OAuth token from gh CLI", fg="cyan")
|
|
1011
|
+
elif github_pat.startswith("ghp_"):
|
|
1012
|
+
click.secho(" ℹ Using Personal Access Token", fg="cyan")
|
|
1013
|
+
elif github_pat.startswith("github_pat_"):
|
|
1014
|
+
click.secho(" ℹ Using Fine-grained Personal Access Token", fg="cyan")
|
|
1015
|
+
|
|
1016
|
+
# Auto-detect git remote if repo URL not set
|
|
1017
|
+
if not github_repo_url:
|
|
1018
|
+
try:
|
|
1019
|
+
result = subprocess.run(
|
|
1020
|
+
["git", "remote", "get-url", "origin"],
|
|
1021
|
+
capture_output=True, text=True, timeout=5
|
|
1022
|
+
)
|
|
1023
|
+
if result.returncode == 0:
|
|
1024
|
+
github_repo_url = result.stdout.strip()
|
|
1025
|
+
click.secho(f" ℹ Using repo URL from git remote: {github_repo_url}", fg="cyan")
|
|
1026
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
1027
|
+
pass
|
|
1028
|
+
|
|
1029
|
+
click.echo()
|
|
1030
|
+
click.echo("ArgoCD Application Deployment")
|
|
1031
|
+
click.echo("=" * 60)
|
|
1032
|
+
|
|
1033
|
+
# Pre-validation
|
|
1034
|
+
click.echo("Pre-flight checks:")
|
|
1035
|
+
errors = 0
|
|
1036
|
+
|
|
1037
|
+
# Check kubectl
|
|
1038
|
+
result = subprocess.run(["which", "kubectl"], capture_output=True)
|
|
1039
|
+
if result.returncode != 0:
|
|
1040
|
+
click.secho(" ✗ kubectl not found", fg="red")
|
|
1041
|
+
errors += 1
|
|
1042
|
+
else:
|
|
1043
|
+
click.secho(" ✓ kubectl available", fg="green")
|
|
1044
|
+
|
|
1045
|
+
# Check cluster access
|
|
1046
|
+
result = subprocess.run(
|
|
1047
|
+
["kubectl", "cluster-info"],
|
|
1048
|
+
capture_output=True,
|
|
1049
|
+
timeout=10,
|
|
1050
|
+
)
|
|
1051
|
+
if result.returncode != 0:
|
|
1052
|
+
click.secho(" ✗ Cannot connect to Kubernetes cluster", fg="red")
|
|
1053
|
+
click.echo(" Run: aws eks update-kubeconfig --name <cluster> --profile rem")
|
|
1054
|
+
errors += 1
|
|
1055
|
+
else:
|
|
1056
|
+
click.secho(" ✓ Kubernetes cluster accessible", fg="green")
|
|
1057
|
+
|
|
1058
|
+
# Check argocd namespace exists
|
|
1059
|
+
result = subprocess.run(
|
|
1060
|
+
["kubectl", "get", "namespace", "argocd"],
|
|
1061
|
+
capture_output=True,
|
|
1062
|
+
)
|
|
1063
|
+
if result.returncode != 0:
|
|
1064
|
+
click.secho(" ✗ argocd namespace not found", fg="red")
|
|
1065
|
+
click.echo(" ArgoCD should be installed by CDK (ENABLE_ARGOCD=true)")
|
|
1066
|
+
errors += 1
|
|
1067
|
+
else:
|
|
1068
|
+
click.secho(" ✓ argocd namespace exists", fg="green")
|
|
1069
|
+
|
|
1070
|
+
if errors > 0:
|
|
1071
|
+
click.echo()
|
|
1072
|
+
click.secho(f"Pre-flight failed with {errors} error(s)", fg="red")
|
|
1073
|
+
raise click.Abort()
|
|
1074
|
+
|
|
1075
|
+
click.echo()
|
|
1076
|
+
click.echo(f"Project: {project_name}")
|
|
1077
|
+
click.echo(f"Namespace: {namespace}")
|
|
1078
|
+
click.echo(f"Repository: {github_repo_url}")
|
|
1079
|
+
if dry_run:
|
|
1080
|
+
click.secho("Mode: DRY RUN (no changes will be made)", fg="yellow")
|
|
1081
|
+
click.echo()
|
|
1082
|
+
|
|
1083
|
+
# Validate required values
|
|
1084
|
+
if not github_repo_url:
|
|
1085
|
+
click.secho("✗ GITHUB_REPO_URL not set", fg="red")
|
|
1086
|
+
click.echo(" Set via environment variable or cluster-config.yaml")
|
|
1087
|
+
raise click.Abort()
|
|
1088
|
+
|
|
1089
|
+
if not github_pat or not github_username:
|
|
1090
|
+
click.secho("⚠ GITHUB_PAT or GITHUB_USERNAME not set", fg="yellow")
|
|
1091
|
+
click.echo(" Private repos will not be accessible without credentials")
|
|
1092
|
+
if not click.confirm("Continue without repo credentials?"):
|
|
1093
|
+
raise click.Abort()
|
|
1094
|
+
|
|
1095
|
+
manifests_dir = get_manifests_dir()
|
|
1096
|
+
|
|
1097
|
+
# Step 1: Create ArgoCD repository secret
|
|
1098
|
+
click.echo("1. ArgoCD repository secret")
|
|
1099
|
+
if github_pat and github_username:
|
|
1100
|
+
# Check if secret exists
|
|
1101
|
+
result = subprocess.run(
|
|
1102
|
+
["kubectl", "get", "secret", "repo-reminiscent", "-n", "argocd"],
|
|
1103
|
+
capture_output=True,
|
|
1104
|
+
)
|
|
1105
|
+
secret_exists = result.returncode == 0
|
|
1106
|
+
|
|
1107
|
+
if secret_exists:
|
|
1108
|
+
click.echo(" ⏭ Secret 'repo-reminiscent' exists (skipping)")
|
|
1109
|
+
else:
|
|
1110
|
+
if dry_run:
|
|
1111
|
+
click.echo(" Would create: secret/repo-reminiscent in argocd namespace")
|
|
1112
|
+
else:
|
|
1113
|
+
# Create the secret
|
|
1114
|
+
create_cmd = [
|
|
1115
|
+
"kubectl", "create", "secret", "generic", "repo-reminiscent",
|
|
1116
|
+
"--namespace", "argocd",
|
|
1117
|
+
f"--from-literal=url={github_repo_url}",
|
|
1118
|
+
f"--from-literal=username={github_username}",
|
|
1119
|
+
f"--from-literal=password={github_pat}",
|
|
1120
|
+
"--from-literal=type=git",
|
|
1121
|
+
"--dry-run=client", "-o", "yaml",
|
|
1122
|
+
]
|
|
1123
|
+
# Pipe to kubectl apply
|
|
1124
|
+
create_result = subprocess.run(create_cmd, capture_output=True)
|
|
1125
|
+
if create_result.returncode == 0:
|
|
1126
|
+
apply_result = subprocess.run(
|
|
1127
|
+
["kubectl", "apply", "-f", "-"],
|
|
1128
|
+
input=create_result.stdout,
|
|
1129
|
+
capture_output=True,
|
|
1130
|
+
)
|
|
1131
|
+
if apply_result.returncode == 0:
|
|
1132
|
+
# Label it as ArgoCD repo secret
|
|
1133
|
+
subprocess.run([
|
|
1134
|
+
"kubectl", "label", "secret", "repo-reminiscent",
|
|
1135
|
+
"-n", "argocd",
|
|
1136
|
+
"argocd.argoproj.io/secret-type=repository",
|
|
1137
|
+
"--overwrite",
|
|
1138
|
+
], capture_output=True)
|
|
1139
|
+
click.secho(" ✓ Created secret 'repo-reminiscent'", fg="green")
|
|
1140
|
+
else:
|
|
1141
|
+
click.secho(f" ✗ Failed to create secret: {apply_result.stderr.decode()}", fg="red")
|
|
1142
|
+
raise click.Abort()
|
|
1143
|
+
else:
|
|
1144
|
+
click.echo(" ⏭ Skipping (no credentials provided)")
|
|
1145
|
+
|
|
1146
|
+
# Step 2: Create namespace
|
|
1147
|
+
click.echo()
|
|
1148
|
+
click.echo("2. Application namespace")
|
|
1149
|
+
result = subprocess.run(
|
|
1150
|
+
["kubectl", "get", "namespace", namespace],
|
|
1151
|
+
capture_output=True,
|
|
1152
|
+
)
|
|
1153
|
+
if result.returncode == 0:
|
|
1154
|
+
click.echo(f" ⏭ Namespace '{namespace}' exists")
|
|
1155
|
+
else:
|
|
1156
|
+
if dry_run:
|
|
1157
|
+
click.echo(f" Would create: namespace/{namespace}")
|
|
1158
|
+
else:
|
|
1159
|
+
result = subprocess.run(
|
|
1160
|
+
["kubectl", "create", "namespace", namespace],
|
|
1161
|
+
capture_output=True,
|
|
1162
|
+
)
|
|
1163
|
+
if result.returncode == 0:
|
|
1164
|
+
click.secho(f" ✓ Created namespace '{namespace}'", fg="green")
|
|
1165
|
+
else:
|
|
1166
|
+
click.secho(f" ✗ Failed to create namespace: {result.stderr.decode()}", fg="red")
|
|
1167
|
+
raise click.Abort()
|
|
1168
|
+
|
|
1169
|
+
# Step 3: Deploy platform-apps (app-of-apps)
|
|
1170
|
+
if not skip_platform:
|
|
1171
|
+
click.echo()
|
|
1172
|
+
click.echo("3. Platform apps (app-of-apps)")
|
|
1173
|
+
platform_app = manifests_dir / "platform" / "argocd" / "app-of-apps.yaml"
|
|
1174
|
+
|
|
1175
|
+
if not platform_app.exists():
|
|
1176
|
+
click.secho(f" ✗ Not found: {platform_app}", fg="red")
|
|
1177
|
+
raise click.Abort()
|
|
1178
|
+
|
|
1179
|
+
if dry_run:
|
|
1180
|
+
click.echo(f" Would apply: {platform_app}")
|
|
1181
|
+
else:
|
|
1182
|
+
result = subprocess.run(
|
|
1183
|
+
["kubectl", "apply", "-f", str(platform_app)],
|
|
1184
|
+
capture_output=True,
|
|
1185
|
+
)
|
|
1186
|
+
if result.returncode == 0:
|
|
1187
|
+
click.secho(" ✓ Applied platform-apps", fg="green")
|
|
1188
|
+
else:
|
|
1189
|
+
click.secho(f" ✗ Failed: {result.stderr.decode()}", fg="red")
|
|
1190
|
+
raise click.Abort()
|
|
1191
|
+
|
|
1192
|
+
# Wait for critical platform apps
|
|
1193
|
+
if not dry_run:
|
|
1194
|
+
click.echo()
|
|
1195
|
+
click.echo(" Waiting for cert-manager...")
|
|
1196
|
+
for _ in range(30): # 5 minutes max
|
|
1197
|
+
result = subprocess.run(
|
|
1198
|
+
["kubectl", "get", "application", "cert-manager", "-n", "argocd",
|
|
1199
|
+
"-o", "jsonpath={.status.health.status}"],
|
|
1200
|
+
capture_output=True,
|
|
1201
|
+
)
|
|
1202
|
+
status = result.stdout.decode().strip()
|
|
1203
|
+
if status == "Healthy":
|
|
1204
|
+
click.secho(" ✓ cert-manager is healthy", fg="green")
|
|
1205
|
+
break
|
|
1206
|
+
click.echo(f" ... cert-manager status: {status or 'Unknown'}")
|
|
1207
|
+
import time
|
|
1208
|
+
time.sleep(10)
|
|
1209
|
+
else:
|
|
1210
|
+
click.secho(" ⚠ cert-manager not healthy yet (continuing anyway)", fg="yellow")
|
|
1211
|
+
|
|
1212
|
+
# Step 4: Deploy rem-stack
|
|
1213
|
+
click.echo()
|
|
1214
|
+
click.echo("4. REM stack application" if not skip_platform else "3. REM stack application")
|
|
1215
|
+
rem_stack_app = manifests_dir / "application" / "rem-stack" / "argocd-staging.yaml"
|
|
1216
|
+
|
|
1217
|
+
if not rem_stack_app.exists():
|
|
1218
|
+
click.secho(f" ✗ Not found: {rem_stack_app}", fg="red")
|
|
1219
|
+
raise click.Abort()
|
|
1220
|
+
|
|
1221
|
+
if dry_run:
|
|
1222
|
+
click.echo(f" Would apply: {rem_stack_app}")
|
|
1223
|
+
else:
|
|
1224
|
+
result = subprocess.run(
|
|
1225
|
+
["kubectl", "apply", "-f", str(rem_stack_app)],
|
|
1226
|
+
capture_output=True,
|
|
1227
|
+
)
|
|
1228
|
+
if result.returncode == 0:
|
|
1229
|
+
click.secho(" ✓ Applied rem-stack-staging", fg="green")
|
|
1230
|
+
else:
|
|
1231
|
+
click.secho(f" ✗ Failed: {result.stderr.decode()}", fg="red")
|
|
1232
|
+
raise click.Abort()
|
|
1233
|
+
|
|
1234
|
+
# Summary
|
|
1235
|
+
click.echo()
|
|
1236
|
+
click.echo("=" * 60)
|
|
1237
|
+
if dry_run:
|
|
1238
|
+
click.secho("Dry run complete - no changes made", fg="yellow")
|
|
1239
|
+
else:
|
|
1240
|
+
click.secho("✓ Deployment initiated", fg="green")
|
|
1241
|
+
click.echo()
|
|
1242
|
+
click.echo("Monitor progress:")
|
|
1243
|
+
click.echo(" kubectl get applications -n argocd")
|
|
1244
|
+
click.echo(" watch kubectl get pods -n " + namespace)
|
|
1245
|
+
click.echo()
|
|
1246
|
+
click.echo("ArgoCD UI:")
|
|
1247
|
+
click.echo(" kubectl port-forward svc/argocd-server -n argocd 8080:443")
|
|
1248
|
+
click.echo(" # Get password: kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d")
|
|
742
1249
|
|
|
743
1250
|
|
|
744
1251
|
# =============================================================================
|
|
@@ -1034,8 +1541,8 @@ def env_check(env_file: Path | None, environment: str, strict: bool):
|
|
|
1034
1541
|
@click.option(
|
|
1035
1542
|
"--namespace",
|
|
1036
1543
|
"-n",
|
|
1037
|
-
default="
|
|
1038
|
-
help="Kubernetes namespace (default:
|
|
1544
|
+
default="rem",
|
|
1545
|
+
help="Kubernetes namespace (default: rem)",
|
|
1039
1546
|
)
|
|
1040
1547
|
@click.option(
|
|
1041
1548
|
"--exclude-secrets",
|
|
@@ -1182,8 +1689,8 @@ def env_generate(
|
|
|
1182
1689
|
@click.option(
|
|
1183
1690
|
"--namespace",
|
|
1184
1691
|
"-n",
|
|
1185
|
-
default="
|
|
1186
|
-
help="Kubernetes namespace (default:
|
|
1692
|
+
default="rem",
|
|
1693
|
+
help="Kubernetes namespace (default: rem)",
|
|
1187
1694
|
)
|
|
1188
1695
|
def env_diff(env_file: Path | None, configmap: str, namespace: str):
|
|
1189
1696
|
"""
|
|
@@ -1297,4 +1804,5 @@ def register_commands(cluster_group):
|
|
|
1297
1804
|
cluster_group.add_command(setup_ssm)
|
|
1298
1805
|
cluster_group.add_command(validate)
|
|
1299
1806
|
cluster_group.add_command(generate)
|
|
1807
|
+
cluster_group.add_command(apply)
|
|
1300
1808
|
cluster_group.add_command(env)
|