veris-cli 2.23.0__tar.gz → 2.25.0__tar.gz
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.
- {veris_cli-2.23.0 → veris_cli-2.25.0}/PKG-INFO +1 -1
- {veris_cli-2.23.0 → veris_cli-2.25.0}/pyproject.toml +1 -1
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/api.py +28 -1
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/build_context.py +17 -12
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/env.py +260 -11
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/simulations.py +7 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/config.py +10 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/.gitignore +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/README.md +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/__init__.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/cli.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/__init__.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/_helpers.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/auth.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/evaluations.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/profile.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/reports.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/run.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/commands/scenarios.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/output.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/prompts.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/run_output.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/scripts/__init__.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/scripts/docker_build.sh +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/scripts/docker_push.sh +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/searchable_checkbox.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/templates.py +0 -0
- {veris_cli-2.23.0 → veris_cli-2.25.0}/src/veris_cli/veris_yaml.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: veris-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.25.0
|
|
4
4
|
Summary: CLI to connect local agents to the Veris backend
|
|
5
5
|
Project-URL: Homepage, https://github.com/veris-ai/veris-cli
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/veris-ai/veris-cli/issues
|
|
@@ -212,12 +212,13 @@ class VerisAPI:
|
|
|
212
212
|
scenario_set_id: str,
|
|
213
213
|
environment_id: str,
|
|
214
214
|
config: Optional[dict] = None,
|
|
215
|
+
auto_evaluate: bool = False,
|
|
215
216
|
) -> dict[str, Any]:
|
|
216
217
|
"""Create a new run."""
|
|
217
218
|
payload: dict[str, Any] = {
|
|
218
219
|
"scenario_set_id": scenario_set_id,
|
|
219
220
|
"environment_id": environment_id,
|
|
220
|
-
"auto_evaluate":
|
|
221
|
+
"auto_evaluate": auto_evaluate,
|
|
221
222
|
}
|
|
222
223
|
if config:
|
|
223
224
|
payload["config"] = config
|
|
@@ -431,6 +432,32 @@ class VerisAPI:
|
|
|
431
432
|
_raise_for_status(response)
|
|
432
433
|
return response.content
|
|
433
434
|
|
|
435
|
+
# Managed Setup
|
|
436
|
+
def create_submit(self, environment_id: str) -> dict[str, Any]:
|
|
437
|
+
"""Get a signed upload URL for submitting agent source code."""
|
|
438
|
+
with httpx.Client(
|
|
439
|
+
base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
|
|
440
|
+
) as client:
|
|
441
|
+
response = client.post(f"/v1/environments/{environment_id}/submit")
|
|
442
|
+
_raise_for_status(response)
|
|
443
|
+
return response.json()
|
|
444
|
+
|
|
445
|
+
def notify_submit_uploaded(self, environment_id: str) -> dict[str, Any]:
|
|
446
|
+
"""Confirm that the submit tarball has been uploaded."""
|
|
447
|
+
with httpx.Client(base_url=self.base_url, headers=self._headers(), timeout=30) as client:
|
|
448
|
+
response = client.post(f"/v1/environments/{environment_id}/submit/uploaded")
|
|
449
|
+
_raise_for_status(response)
|
|
450
|
+
return response.json()
|
|
451
|
+
|
|
452
|
+
def get_managed_config(self, environment_id: str) -> dict[str, Any]:
|
|
453
|
+
"""Get managed config files (signed download URLs) for an environment."""
|
|
454
|
+
with httpx.Client(
|
|
455
|
+
base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
|
|
456
|
+
) as client:
|
|
457
|
+
response = client.get(f"/v1/environments/{environment_id}/managed-config")
|
|
458
|
+
_raise_for_status(response)
|
|
459
|
+
return response.json()
|
|
460
|
+
|
|
434
461
|
# Sandbox metadata
|
|
435
462
|
def list_services(self) -> dict[str, Any]:
|
|
436
463
|
"""List available sandbox mock services."""
|
|
@@ -31,6 +31,7 @@ def create_build_context(
|
|
|
31
31
|
project_root: Path,
|
|
32
32
|
output_path: Path | None = None,
|
|
33
33
|
dockerfile: Path | str | None = None,
|
|
34
|
+
require_dockerfile: bool = True,
|
|
34
35
|
) -> tuple[Path, int]:
|
|
35
36
|
"""Create a tar.gz build context from the project root.
|
|
36
37
|
|
|
@@ -43,6 +44,8 @@ def create_build_context(
|
|
|
43
44
|
output_path: Where to write the tarball. If None, uses a tempfile.
|
|
44
45
|
dockerfile: Dockerfile path (absolute or relative to project_root). Defaults
|
|
45
46
|
to `.veris/Dockerfile.sandbox` when None.
|
|
47
|
+
require_dockerfile: If False, skip Dockerfile validation and inclusion.
|
|
48
|
+
Used by ``env submit`` where no Dockerfile exists yet.
|
|
46
49
|
|
|
47
50
|
Returns:
|
|
48
51
|
Tuple of (tarball_path, size_bytes).
|
|
@@ -57,18 +60,20 @@ def create_build_context(
|
|
|
57
60
|
os.close(fd)
|
|
58
61
|
output_path = Path(tmp)
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
dockerfile_rel: str | None = None
|
|
64
|
+
if require_dockerfile:
|
|
65
|
+
if dockerfile is None:
|
|
66
|
+
dockerfile_path = project_root / ".veris" / "Dockerfile.sandbox"
|
|
67
|
+
dockerfile_label = ".veris/Dockerfile.sandbox"
|
|
68
|
+
else:
|
|
69
|
+
df = Path(dockerfile)
|
|
70
|
+
dockerfile_path = df if df.is_absolute() else project_root / df
|
|
71
|
+
dockerfile_label = str(dockerfile)
|
|
72
|
+
if not dockerfile_path.exists():
|
|
73
|
+
raise ValueError(f"{dockerfile_label} not found")
|
|
74
|
+
dockerfile_rel = str(dockerfile_path.relative_to(project_root))
|
|
69
75
|
|
|
70
76
|
file_count = 0
|
|
71
|
-
dockerfile_rel = str(dockerfile_path.relative_to(project_root))
|
|
72
77
|
dockerfile_added = False
|
|
73
78
|
|
|
74
79
|
with tarfile.open(output_path, "w:gz") as tar:
|
|
@@ -99,11 +104,11 @@ def create_build_context(
|
|
|
99
104
|
full_path = Path(root) / f
|
|
100
105
|
tar.add(str(full_path), arcname=rel_path)
|
|
101
106
|
file_count += 1
|
|
102
|
-
if rel_path == dockerfile_rel:
|
|
107
|
+
if dockerfile_rel and rel_path == dockerfile_rel:
|
|
103
108
|
dockerfile_added = True
|
|
104
109
|
|
|
105
110
|
# If the Dockerfile wasn't added (e.g. its dir was ignored), add it explicitly
|
|
106
|
-
if not dockerfile_added:
|
|
111
|
+
if require_dockerfile and not dockerfile_added and dockerfile_rel:
|
|
107
112
|
tar.add(str(dockerfile_path), arcname=dockerfile_rel)
|
|
108
113
|
file_count += 1
|
|
109
114
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
import time
|
|
5
|
+
from datetime import UTC, datetime
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
import click
|
|
@@ -364,8 +365,9 @@ def env_create(ctx, name: str | None, agent_name: str | None):
|
|
|
364
365
|
if unconfigured:
|
|
365
366
|
import questionary
|
|
366
367
|
|
|
368
|
+
_NEW_TARGET = "__new__"
|
|
367
369
|
choices = [questionary.Choice(title=t, value=t) for t in unconfigured] + [
|
|
368
|
-
questionary.Choice(title="Create a new target", value=
|
|
370
|
+
questionary.Choice(title="Create a new target", value=_NEW_TARGET),
|
|
369
371
|
]
|
|
370
372
|
selected = questionary.select(
|
|
371
373
|
"Existing targets found in veris.yaml. Set up environment for:",
|
|
@@ -373,7 +375,7 @@ def env_create(ctx, name: str | None, agent_name: str | None):
|
|
|
373
375
|
).ask()
|
|
374
376
|
if selected is None:
|
|
375
377
|
return
|
|
376
|
-
if selected:
|
|
378
|
+
if selected and selected != _NEW_TARGET:
|
|
377
379
|
name = selected
|
|
378
380
|
# Pull the agent name from the target block
|
|
379
381
|
t_obj = parsed.get_target(selected)
|
|
@@ -444,9 +446,12 @@ def env_create(ctx, name: str | None, agent_name: str | None):
|
|
|
444
446
|
output.print_success(f"Environment created: {env_id}")
|
|
445
447
|
output.print_info(f"Environment ID saved to .veris/config.yaml under target '{env_name}'")
|
|
446
448
|
output.print_info("\nNext steps:")
|
|
447
|
-
output.print_info("
|
|
448
|
-
output.print_info("
|
|
449
|
-
output.print_info("
|
|
449
|
+
output.print_info(" Option A — Self-serve:")
|
|
450
|
+
output.print_info(" 1. Edit .veris/Dockerfile.sandbox and .veris/veris.yaml")
|
|
451
|
+
output.print_info(" 2. Run 'veris env push' to build and push your agent")
|
|
452
|
+
output.print_info(" Option B — Let Veris set it up:")
|
|
453
|
+
output.print_info(" 1. Run 'veris env submit' to upload your code")
|
|
454
|
+
output.print_info(" 2. We'll email you when your environment is ready")
|
|
450
455
|
|
|
451
456
|
except ValueError as e:
|
|
452
457
|
output.print_error(str(e))
|
|
@@ -645,6 +650,83 @@ def _resolve_or_create_cross_profile(
|
|
|
645
650
|
return None
|
|
646
651
|
|
|
647
652
|
|
|
653
|
+
@env.command(name="submit")
|
|
654
|
+
@click.option("--env-id", default=None, help="Environment ID (overrides config)")
|
|
655
|
+
@target_option
|
|
656
|
+
@click.pass_context
|
|
657
|
+
def env_submit(ctx, env_id: str | None, target: str | None):
|
|
658
|
+
"""Submit your repo for managed environment setup.
|
|
659
|
+
|
|
660
|
+
Packages your project into a tarball and uploads it to Veris. The team
|
|
661
|
+
will generate your Dockerfile.sandbox and veris.yaml. You'll receive an
|
|
662
|
+
email when the setup is complete.
|
|
663
|
+
|
|
664
|
+
\b
|
|
665
|
+
After receiving the email:
|
|
666
|
+
veris env config pull # Pull generated config files
|
|
667
|
+
veris env push # Build and push your agent
|
|
668
|
+
"""
|
|
669
|
+
profile = get_profile(ctx)
|
|
670
|
+
resolved_target = resolve_target(target, profile)
|
|
671
|
+
|
|
672
|
+
# Prompt for target selection if multiple targets and none resolved
|
|
673
|
+
if not resolved_target and not env_id and sys.stdin.isatty():
|
|
674
|
+
veris_yaml_path = Path.cwd() / ".veris" / "veris.yaml"
|
|
675
|
+
parsed = _try_load_veris_yaml(veris_yaml_path)
|
|
676
|
+
if parsed and len(parsed.target_names) > 1:
|
|
677
|
+
import questionary
|
|
678
|
+
|
|
679
|
+
resolved_target = questionary.select(
|
|
680
|
+
"Multiple targets found. Submit which one?",
|
|
681
|
+
choices=parsed.target_names,
|
|
682
|
+
).ask()
|
|
683
|
+
if not resolved_target:
|
|
684
|
+
return
|
|
685
|
+
|
|
686
|
+
env_id = resolve_env_id(profile, env_id, resolved_target)
|
|
687
|
+
|
|
688
|
+
tarball_path = None
|
|
689
|
+
try:
|
|
690
|
+
from veris_cli.build_context import create_build_context
|
|
691
|
+
|
|
692
|
+
output.print_info("Packaging project...")
|
|
693
|
+
tarball_path, size = create_build_context(Path.cwd(), require_dockerfile=False)
|
|
694
|
+
size_mb = size / 1024 / 1024
|
|
695
|
+
output.print_success(f"Package size: {size_mb:.1f} MB")
|
|
696
|
+
|
|
697
|
+
api = VerisAPI(profile=profile)
|
|
698
|
+
submit_result = api.create_submit(env_id)
|
|
699
|
+
upload_url = submit_result["upload_url"]
|
|
700
|
+
|
|
701
|
+
output.print_info("Uploading...")
|
|
702
|
+
with open(tarball_path, "rb") as f:
|
|
703
|
+
upload_resp = httpx.put(
|
|
704
|
+
upload_url,
|
|
705
|
+
content=f,
|
|
706
|
+
headers={"Content-Type": "application/gzip"},
|
|
707
|
+
timeout=300,
|
|
708
|
+
)
|
|
709
|
+
if upload_resp.status_code not in (200, 201):
|
|
710
|
+
output.print_error(f"Upload failed: {upload_resp.status_code}")
|
|
711
|
+
sys.exit(1)
|
|
712
|
+
output.print_success("Upload complete")
|
|
713
|
+
|
|
714
|
+
api.notify_submit_uploaded(env_id)
|
|
715
|
+
output.print_success("Submitted for managed setup!")
|
|
716
|
+
output.print_info("You'll receive an email when your environment is ready.")
|
|
717
|
+
output.print_info("Then run 'veris env push' to build and deploy.")
|
|
718
|
+
|
|
719
|
+
except ValueError as e:
|
|
720
|
+
output.print_error(str(e))
|
|
721
|
+
sys.exit(1)
|
|
722
|
+
except Exception as e:
|
|
723
|
+
output.print_error(f"Submit failed: {e}")
|
|
724
|
+
sys.exit(1)
|
|
725
|
+
finally:
|
|
726
|
+
if tarball_path:
|
|
727
|
+
tarball_path.unlink(missing_ok=True)
|
|
728
|
+
|
|
729
|
+
|
|
648
730
|
@env.command(name="push")
|
|
649
731
|
@click.option("--tag", default="latest", help="Image tag (default: latest)")
|
|
650
732
|
@click.option("--env-id", default=None, help="Environment ID (overrides config)")
|
|
@@ -674,6 +756,16 @@ def env_push(ctx, tag: str, env_id: str | None, target: str | None):
|
|
|
674
756
|
parsed = _load_veris_yaml(veris_yaml_path)
|
|
675
757
|
resolved_target = resolve_target(target, profile, parsed_veris_yaml=parsed)
|
|
676
758
|
|
|
759
|
+
if not resolved_target and not env_id and sys.stdin.isatty() and len(parsed.target_names) > 1:
|
|
760
|
+
import questionary
|
|
761
|
+
|
|
762
|
+
resolved_target = questionary.select(
|
|
763
|
+
"Multiple targets found. Push which one?",
|
|
764
|
+
choices=parsed.target_names,
|
|
765
|
+
).ask()
|
|
766
|
+
if not resolved_target:
|
|
767
|
+
return
|
|
768
|
+
|
|
677
769
|
try:
|
|
678
770
|
target_obj = parsed.get_target(resolved_target)
|
|
679
771
|
except KeyError as e:
|
|
@@ -682,9 +774,6 @@ def env_push(ctx, tag: str, env_id: str | None, target: str | None):
|
|
|
682
774
|
|
|
683
775
|
dockerfile_rel = target_obj.resolved_dockerfile()
|
|
684
776
|
dockerfile_path = Path.cwd() / dockerfile_rel
|
|
685
|
-
if not dockerfile_path.exists():
|
|
686
|
-
output.print_error(f"{dockerfile_rel} not found. Run 'veris env create' first.")
|
|
687
|
-
sys.exit(1)
|
|
688
777
|
|
|
689
778
|
if not env_id and not target:
|
|
690
779
|
env_id = _migrate_legacy_config(profile, resolved_target, parsed)
|
|
@@ -693,9 +782,59 @@ def env_push(ctx, tag: str, env_id: str | None, target: str | None):
|
|
|
693
782
|
if not env_id:
|
|
694
783
|
env_id = resolve_env_id(profile, env_id, resolved_target)
|
|
695
784
|
|
|
785
|
+
api = VerisAPI(profile=profile)
|
|
786
|
+
|
|
787
|
+
if not dockerfile_path.exists():
|
|
788
|
+
output.print_error(
|
|
789
|
+
f"{dockerfile_rel} not found. Run 'veris env create' or 'veris env config pull' first."
|
|
790
|
+
)
|
|
791
|
+
sys.exit(1)
|
|
792
|
+
|
|
793
|
+
# Config sync guard: check if remote config was updated since last sync.
|
|
794
|
+
# Prevents accidentally overwriting team-generated config with local scaffold.
|
|
795
|
+
try:
|
|
796
|
+
env_info = api.get_environment(env_id)
|
|
797
|
+
except Exception:
|
|
798
|
+
env_info = {}
|
|
799
|
+
|
|
800
|
+
remote_config_at = env_info.get("config_updated_at")
|
|
801
|
+
pc = ProjectConfig(profile=profile, target=resolved_target)
|
|
802
|
+
local_synced_at = pc.get_config_synced_at()
|
|
803
|
+
|
|
804
|
+
if remote_config_at and (not local_synced_at or remote_config_at > local_synced_at):
|
|
805
|
+
if sys.stdin.isatty():
|
|
806
|
+
import questionary
|
|
807
|
+
|
|
808
|
+
action = questionary.select(
|
|
809
|
+
"Remote config was updated since your last sync.",
|
|
810
|
+
choices=[
|
|
811
|
+
questionary.Choice(title="Pull remote config (overwrites local)", value="pull"),
|
|
812
|
+
questionary.Choice(title="Push local config (overwrites remote)", value="push"),
|
|
813
|
+
questionary.Choice(title="Cancel", value="cancel"),
|
|
814
|
+
],
|
|
815
|
+
).ask()
|
|
816
|
+
if action == "cancel" or action is None:
|
|
817
|
+
return
|
|
818
|
+
if action == "pull":
|
|
819
|
+
ctx.invoke(config_pull, env_id=env_id, target=resolved_target)
|
|
820
|
+
# Reload target config after pull (resolved_target stays the same)
|
|
821
|
+
parsed = _load_veris_yaml(veris_yaml_path)
|
|
822
|
+
target_obj = parsed.get_target(resolved_target)
|
|
823
|
+
dockerfile_rel = target_obj.resolved_dockerfile()
|
|
824
|
+
dockerfile_path = Path.cwd() / dockerfile_rel
|
|
825
|
+
else:
|
|
826
|
+
output.print_error(
|
|
827
|
+
"Remote config was updated since your last sync. Run 'veris env config pull' first."
|
|
828
|
+
)
|
|
829
|
+
sys.exit(1)
|
|
830
|
+
|
|
696
831
|
try:
|
|
697
|
-
api = VerisAPI(profile=profile)
|
|
698
832
|
api.upload_environment_config(env_id, target_obj.to_upload_dict())
|
|
833
|
+
# Re-fetch to get the server-assigned config_updated_at
|
|
834
|
+
updated_env = api.get_environment(env_id)
|
|
835
|
+
pc.set_config_synced_at(
|
|
836
|
+
updated_env.get("config_updated_at") or datetime.now(UTC).isoformat()
|
|
837
|
+
)
|
|
699
838
|
output.print_success("Config validated and uploaded")
|
|
700
839
|
except Exception as e:
|
|
701
840
|
detail = getattr(e, "detail", str(e))
|
|
@@ -704,7 +843,6 @@ def env_push(ctx, tag: str, env_id: str | None, target: str | None):
|
|
|
704
843
|
|
|
705
844
|
# On-prem clusters route through the customer's external registry instead of Cloud Build.
|
|
706
845
|
try:
|
|
707
|
-
env_info = api.get_environment(env_id)
|
|
708
846
|
if env_info.get("image_registry_url"):
|
|
709
847
|
tag_result = api.create_image_tag(env_id, tag)
|
|
710
848
|
if tag_result.get("pull_credentials"):
|
|
@@ -871,9 +1009,48 @@ def config_push(ctx, file_path: str | None, env_id: str, target: str | None):
|
|
|
871
1009
|
output.print_error(str(e))
|
|
872
1010
|
sys.exit(1)
|
|
873
1011
|
|
|
1012
|
+
api = VerisAPI(profile=profile)
|
|
1013
|
+
pc = ProjectConfig(profile=profile, target=resolved_target)
|
|
1014
|
+
|
|
1015
|
+
# Config sync guard
|
|
1016
|
+
try:
|
|
1017
|
+
env_info = api.get_environment(env_id)
|
|
1018
|
+
except Exception:
|
|
1019
|
+
env_info = {}
|
|
1020
|
+
|
|
1021
|
+
remote_config_at = env_info.get("config_updated_at")
|
|
1022
|
+
local_synced_at = pc.get_config_synced_at()
|
|
1023
|
+
|
|
1024
|
+
if remote_config_at and (not local_synced_at or remote_config_at > local_synced_at):
|
|
1025
|
+
if sys.stdin.isatty():
|
|
1026
|
+
import questionary
|
|
1027
|
+
|
|
1028
|
+
action = questionary.select(
|
|
1029
|
+
"Remote config was updated since your last sync.",
|
|
1030
|
+
choices=[
|
|
1031
|
+
questionary.Choice(title="Pull remote config (overwrites local)", value="pull"),
|
|
1032
|
+
questionary.Choice(title="Push local config (overwrites remote)", value="push"),
|
|
1033
|
+
questionary.Choice(title="Cancel", value="cancel"),
|
|
1034
|
+
],
|
|
1035
|
+
).ask()
|
|
1036
|
+
if action == "cancel" or action is None:
|
|
1037
|
+
return
|
|
1038
|
+
if action == "pull":
|
|
1039
|
+
# Delegate to config pull logic
|
|
1040
|
+
output.print_info("Pulling remote config...")
|
|
1041
|
+
ctx.invoke(config_pull, env_id=env_id, target=resolved_target)
|
|
1042
|
+
return
|
|
1043
|
+
else:
|
|
1044
|
+
output.print_error(
|
|
1045
|
+
"Remote config was updated since your last sync. Run 'veris env config pull' first."
|
|
1046
|
+
)
|
|
1047
|
+
sys.exit(1)
|
|
1048
|
+
|
|
874
1049
|
try:
|
|
875
|
-
api = VerisAPI(profile=profile)
|
|
876
1050
|
api.upload_environment_config(env_id, target_obj.to_upload_dict())
|
|
1051
|
+
env_info = api.get_environment(env_id)
|
|
1052
|
+
config_ts = env_info.get("config_updated_at") or datetime.now(UTC).isoformat()
|
|
1053
|
+
pc.set_config_synced_at(config_ts)
|
|
877
1054
|
output.print_success(f"Config uploaded for environment {env_id}")
|
|
878
1055
|
except Exception as e:
|
|
879
1056
|
detail = getattr(e, "detail", str(e))
|
|
@@ -881,6 +1058,78 @@ def config_push(ctx, file_path: str | None, env_id: str, target: str | None):
|
|
|
881
1058
|
sys.exit(1)
|
|
882
1059
|
|
|
883
1060
|
|
|
1061
|
+
@config.command(name="pull")
|
|
1062
|
+
@click.option("--env-id", default=None, help="Environment ID (uses project config if omitted)")
|
|
1063
|
+
@target_option
|
|
1064
|
+
@click.pass_context
|
|
1065
|
+
def config_pull(ctx, env_id: str | None, target: str | None):
|
|
1066
|
+
"""Pull config files from Veris to local .veris/ directory.
|
|
1067
|
+
|
|
1068
|
+
Downloads the Dockerfile.sandbox and veris.yaml from the backend.
|
|
1069
|
+
Use after 'veris env submit' (managed setup) or to sync with
|
|
1070
|
+
remote config changes.
|
|
1071
|
+
|
|
1072
|
+
Example: veris env config pull
|
|
1073
|
+
"""
|
|
1074
|
+
profile = get_profile(ctx)
|
|
1075
|
+
resolved_target = resolve_target(target, profile)
|
|
1076
|
+
env_id = resolve_env_id(profile, env_id, resolved_target)
|
|
1077
|
+
|
|
1078
|
+
try:
|
|
1079
|
+
api = VerisAPI(profile=profile)
|
|
1080
|
+
result = api.get_managed_config(env_id)
|
|
1081
|
+
|
|
1082
|
+
status = result.get("status", "created")
|
|
1083
|
+
if status != "ready":
|
|
1084
|
+
output.print_info(f"Environment status: {status}")
|
|
1085
|
+
if status == "pending":
|
|
1086
|
+
output.print_info(
|
|
1087
|
+
"Your environment is being set up. You'll receive an email when it's ready."
|
|
1088
|
+
)
|
|
1089
|
+
else:
|
|
1090
|
+
output.print_info("No remote config available. Run 'veris env submit' first.")
|
|
1091
|
+
return
|
|
1092
|
+
|
|
1093
|
+
veris_dir = Path.cwd() / ".veris"
|
|
1094
|
+
veris_dir.mkdir(parents=True, exist_ok=True)
|
|
1095
|
+
|
|
1096
|
+
dockerfile_url = result.get("dockerfile_url")
|
|
1097
|
+
if dockerfile_url:
|
|
1098
|
+
resp = httpx.get(dockerfile_url, timeout=60)
|
|
1099
|
+
if resp.status_code == 200:
|
|
1100
|
+
(veris_dir / "Dockerfile.sandbox").write_text(resp.text)
|
|
1101
|
+
output.print_success("Downloaded .veris/Dockerfile.sandbox")
|
|
1102
|
+
else:
|
|
1103
|
+
output.print_warning("Failed to download Dockerfile.sandbox")
|
|
1104
|
+
|
|
1105
|
+
veris_yaml_url = result.get("veris_yaml_url")
|
|
1106
|
+
if veris_yaml_url and resolved_target:
|
|
1107
|
+
resp = httpx.get(veris_yaml_url, timeout=60)
|
|
1108
|
+
if resp.status_code == 200:
|
|
1109
|
+
flat_config = yaml.safe_load(resp.text)
|
|
1110
|
+
# Merge into existing veris.yaml to preserve other targets
|
|
1111
|
+
yaml_path = veris_dir / "veris.yaml"
|
|
1112
|
+
if yaml_path.exists():
|
|
1113
|
+
existing = yaml.safe_load(yaml_path.read_text()) or {}
|
|
1114
|
+
else:
|
|
1115
|
+
existing = {"version": "1.0"}
|
|
1116
|
+
existing.setdefault("version", "1.0")
|
|
1117
|
+
existing[resolved_target] = flat_config
|
|
1118
|
+
yaml_path.write_text(yaml.dump(existing, default_flow_style=False, sort_keys=False))
|
|
1119
|
+
output.print_success("Downloaded .veris/veris.yaml")
|
|
1120
|
+
else:
|
|
1121
|
+
output.print_warning("Failed to download veris.yaml")
|
|
1122
|
+
|
|
1123
|
+
# Save sync timestamp
|
|
1124
|
+
env_detail = api.get_environment(env_id)
|
|
1125
|
+
config_ts = env_detail.get("config_updated_at") or datetime.now(UTC).isoformat()
|
|
1126
|
+
ProjectConfig(profile=profile, target=resolved_target).set_config_synced_at(config_ts)
|
|
1127
|
+
|
|
1128
|
+
except Exception as e:
|
|
1129
|
+
output.print_error(f"Failed to pull config: {e}")
|
|
1130
|
+
sys.exit(1)
|
|
1131
|
+
|
|
1132
|
+
|
|
884
1133
|
@env.group()
|
|
885
1134
|
def vars():
|
|
886
1135
|
"""Environment variable management."""
|
|
@@ -30,6 +30,11 @@ def simulations():
|
|
|
30
30
|
help="Simulation timeout in seconds (60-3600)",
|
|
31
31
|
)
|
|
32
32
|
@click.option("--image-tag", default=None, help="Image tag to use (default: latest)")
|
|
33
|
+
@click.option(
|
|
34
|
+
"--auto-evaluate/--no-auto-evaluate",
|
|
35
|
+
default=True,
|
|
36
|
+
help="Auto-trigger evaluation on completion (default: enabled)",
|
|
37
|
+
)
|
|
33
38
|
@click.pass_context
|
|
34
39
|
def simulations_create(
|
|
35
40
|
ctx,
|
|
@@ -37,6 +42,7 @@ def simulations_create(
|
|
|
37
42
|
env_id: str,
|
|
38
43
|
simulation_timeout: int,
|
|
39
44
|
image_tag: str,
|
|
45
|
+
auto_evaluate: bool,
|
|
40
46
|
):
|
|
41
47
|
"""Create a new simulation run."""
|
|
42
48
|
profile = get_profile(ctx)
|
|
@@ -71,6 +77,7 @@ def simulations_create(
|
|
|
71
77
|
scenario_set_id=scenario_set_id,
|
|
72
78
|
environment_id=env_id,
|
|
73
79
|
config=config or None,
|
|
80
|
+
auto_evaluate=auto_evaluate,
|
|
74
81
|
)
|
|
75
82
|
|
|
76
83
|
run_id = result.get("id")
|
|
@@ -312,6 +312,16 @@ class ProjectConfig:
|
|
|
312
312
|
raw.pop("active_target", None)
|
|
313
313
|
self.save(raw)
|
|
314
314
|
|
|
315
|
+
def get_config_synced_at(self) -> Optional[str]:
|
|
316
|
+
"""Get the last config sync timestamp (ISO format)."""
|
|
317
|
+
return self._load_scope().get("config_synced_at")
|
|
318
|
+
|
|
319
|
+
def set_config_synced_at(self, timestamp: str) -> None:
|
|
320
|
+
"""Save the config sync timestamp (ISO format)."""
|
|
321
|
+
scope = self._load_scope()
|
|
322
|
+
scope["config_synced_at"] = timestamp
|
|
323
|
+
self._save_scope(scope)
|
|
324
|
+
|
|
315
325
|
def get_ci_config(self) -> dict:
|
|
316
326
|
"""Get CI config block from project config (profile-level, not per-target)."""
|
|
317
327
|
return self._load_profile().get("ci", {})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|