veris-cli 2.25.1__tar.gz → 2.26.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.25.1 → veris_cli-2.26.0}/PKG-INFO +3 -2
- {veris_cli-2.25.1 → veris_cli-2.26.0}/README.md +1 -1
- {veris_cli-2.25.1 → veris_cli-2.26.0}/pyproject.toml +2 -1
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/api.py +47 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/env.py +128 -81
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/scripts/docker_build.sh +11 -8
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/templates.py +4 -4
- {veris_cli-2.25.1 → veris_cli-2.26.0}/.gitignore +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/__init__.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/build_context.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/cli.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/__init__.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/_helpers.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/auth.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/evaluations.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/profile.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/reports.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/run.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/scenarios.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/commands/simulations.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/config.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/output.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/prompts.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/run_output.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/scripts/__init__.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/scripts/docker_push.sh +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.0}/src/veris_cli/searchable_checkbox.py +0 -0
- {veris_cli-2.25.1 → veris_cli-2.26.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.26.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
|
|
@@ -13,6 +13,7 @@ Requires-Dist: pydantic>=2.6
|
|
|
13
13
|
Requires-Dist: pyyaml>=6.0.1
|
|
14
14
|
Requires-Dist: questionary>=2.0.0
|
|
15
15
|
Requires-Dist: rich>=13.7.0
|
|
16
|
+
Requires-Dist: tenacity>=8.2.0
|
|
16
17
|
Provides-Extra: dev
|
|
17
18
|
Requires-Dist: pre-commit>=4.3.0; extra == 'dev'
|
|
18
19
|
Provides-Extra: test
|
|
@@ -355,7 +356,7 @@ my-cool-agent-env:
|
|
|
355
356
|
entry_point: uv run app
|
|
356
357
|
port: 8008
|
|
357
358
|
environment:
|
|
358
|
-
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/
|
|
359
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veris
|
|
359
360
|
```
|
|
360
361
|
|
|
361
362
|
When only one target is defined, all commands auto-select it.
|
|
@@ -332,7 +332,7 @@ my-cool-agent-env:
|
|
|
332
332
|
entry_point: uv run app
|
|
333
333
|
port: 8008
|
|
334
334
|
environment:
|
|
335
|
-
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/
|
|
335
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veris
|
|
336
336
|
```
|
|
337
337
|
|
|
338
338
|
When only one target is defined, all commands auto-select it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "veris-cli"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.26.0"
|
|
4
4
|
description = "CLI to connect local agents to the Veris backend"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -15,6 +15,7 @@ dependencies = [
|
|
|
15
15
|
"questionary>=2.0.0",
|
|
16
16
|
"pyyaml>=6.0.1",
|
|
17
17
|
"pydantic>=2.6",
|
|
18
|
+
"tenacity>=8.2.0",
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
[project.scripts]
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
from typing import Any, Optional
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
|
+
from tenacity import (
|
|
7
|
+
retry,
|
|
8
|
+
retry_if_exception,
|
|
9
|
+
retry_if_exception_type,
|
|
10
|
+
stop_after_attempt,
|
|
11
|
+
wait_exponential,
|
|
12
|
+
)
|
|
6
13
|
|
|
7
14
|
from veris_cli.config import Config
|
|
8
15
|
|
|
@@ -29,6 +36,26 @@ def _raise_for_status(response: httpx.Response) -> None:
|
|
|
29
36
|
raise APIError(response.status_code, detail, str(response.url))
|
|
30
37
|
|
|
31
38
|
|
|
39
|
+
_RETRYABLE_STATUSES = frozenset({502, 503, 504})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _is_transient_api_error(exc: BaseException) -> bool:
|
|
43
|
+
return isinstance(exc, APIError) and exc.status_code in _RETRYABLE_STATUSES
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Retries transient failures (network errors, 502/503/504). Only applied to
|
|
47
|
+
# idempotent reads — mutating calls (POST/PUT/DELETE) are left alone to avoid
|
|
48
|
+
# duplicate side effects on retry.
|
|
49
|
+
_retry_transient = retry(
|
|
50
|
+
reraise=True,
|
|
51
|
+
stop=stop_after_attempt(5),
|
|
52
|
+
wait=wait_exponential(multiplier=1, min=1, max=10),
|
|
53
|
+
retry=(
|
|
54
|
+
retry_if_exception_type(httpx.TransportError) | retry_if_exception(_is_transient_api_error)
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
32
59
|
class VerisAPI:
|
|
33
60
|
"""Simple HTTP client for Veris backend API."""
|
|
34
61
|
|
|
@@ -66,6 +93,7 @@ class VerisAPI:
|
|
|
66
93
|
_raise_for_status(response)
|
|
67
94
|
return response.json()
|
|
68
95
|
|
|
96
|
+
@_retry_transient
|
|
69
97
|
def list_environments(
|
|
70
98
|
self, status: Optional[str] = None, limit: int = 20, offset: int = 0
|
|
71
99
|
) -> dict[str, Any]:
|
|
@@ -81,6 +109,7 @@ class VerisAPI:
|
|
|
81
109
|
_raise_for_status(response)
|
|
82
110
|
return response.json()
|
|
83
111
|
|
|
112
|
+
@_retry_transient
|
|
84
113
|
def get_environment(self, environment_id: str) -> dict[str, Any]:
|
|
85
114
|
"""Get environment details."""
|
|
86
115
|
with httpx.Client(
|
|
@@ -124,6 +153,7 @@ class VerisAPI:
|
|
|
124
153
|
_raise_for_status(response)
|
|
125
154
|
return response.json()
|
|
126
155
|
|
|
156
|
+
@_retry_transient
|
|
127
157
|
def get_build_status(self, environment_id: str, build_id: str) -> dict[str, Any]:
|
|
128
158
|
"""Poll build status."""
|
|
129
159
|
with httpx.Client(base_url=self.base_url, headers=self._headers(), timeout=30) as client:
|
|
@@ -133,6 +163,7 @@ class VerisAPI:
|
|
|
133
163
|
_raise_for_status(response)
|
|
134
164
|
return response.json()
|
|
135
165
|
|
|
166
|
+
@_retry_transient
|
|
136
167
|
def get_build_logs(
|
|
137
168
|
self, environment_id: str, build_id: str, cursor: str | None = None
|
|
138
169
|
) -> dict[str, Any]:
|
|
@@ -169,6 +200,7 @@ class VerisAPI:
|
|
|
169
200
|
_raise_for_status(response)
|
|
170
201
|
|
|
171
202
|
# Scenario Sets
|
|
203
|
+
@_retry_transient
|
|
172
204
|
def list_scenario_sets(
|
|
173
205
|
self, environment_id: Optional[str] = None, limit: int = 100, skip: int = 0
|
|
174
206
|
) -> list[dict[str, Any]]:
|
|
@@ -189,6 +221,7 @@ class VerisAPI:
|
|
|
189
221
|
else data
|
|
190
222
|
)
|
|
191
223
|
|
|
224
|
+
@_retry_transient
|
|
192
225
|
def get_scenario_set(self, set_id: str) -> dict[str, Any]:
|
|
193
226
|
"""Get scenario set details."""
|
|
194
227
|
with httpx.Client(
|
|
@@ -232,6 +265,7 @@ class VerisAPI:
|
|
|
232
265
|
_raise_for_status(response)
|
|
233
266
|
return response.json()
|
|
234
267
|
|
|
268
|
+
@_retry_transient
|
|
235
269
|
def list_runs(
|
|
236
270
|
self,
|
|
237
271
|
status: Optional[str] = None,
|
|
@@ -253,6 +287,7 @@ class VerisAPI:
|
|
|
253
287
|
_raise_for_status(response)
|
|
254
288
|
return response.json()
|
|
255
289
|
|
|
290
|
+
@_retry_transient
|
|
256
291
|
def get_run(self, run_id: str) -> dict[str, Any]:
|
|
257
292
|
"""Get run details."""
|
|
258
293
|
with httpx.Client(
|
|
@@ -270,6 +305,7 @@ class VerisAPI:
|
|
|
270
305
|
response = client.delete(f"/v1/runs/{run_id}")
|
|
271
306
|
_raise_for_status(response)
|
|
272
307
|
|
|
308
|
+
@_retry_transient
|
|
273
309
|
def get_run_events(self, run_id: str, limit: int = 100, offset: int = 0) -> dict[str, Any]:
|
|
274
310
|
"""Get run events/logs."""
|
|
275
311
|
params = {"limit": limit, "offset": offset}
|
|
@@ -280,6 +316,7 @@ class VerisAPI:
|
|
|
280
316
|
_raise_for_status(response)
|
|
281
317
|
return response.json()
|
|
282
318
|
|
|
319
|
+
@_retry_transient
|
|
283
320
|
def list_run_simulations(self, run_id: str) -> dict[str, Any]:
|
|
284
321
|
"""List simulations for a run."""
|
|
285
322
|
with httpx.Client(base_url=self.base_url, headers=self._headers(), timeout=30) as client:
|
|
@@ -307,6 +344,7 @@ class VerisAPI:
|
|
|
307
344
|
return response.json()
|
|
308
345
|
|
|
309
346
|
# Graders
|
|
347
|
+
@_retry_transient
|
|
310
348
|
def list_graders(
|
|
311
349
|
self,
|
|
312
350
|
environment_id: str,
|
|
@@ -340,6 +378,7 @@ class VerisAPI:
|
|
|
340
378
|
_raise_for_status(response)
|
|
341
379
|
return response.json()
|
|
342
380
|
|
|
381
|
+
@_retry_transient
|
|
343
382
|
def list_evaluation_runs(self, run_id: str, limit: int = 20, offset: int = 0) -> dict[str, Any]:
|
|
344
383
|
"""List evaluation runs for a given run."""
|
|
345
384
|
params = {"limit": limit, "offset": offset}
|
|
@@ -350,6 +389,7 @@ class VerisAPI:
|
|
|
350
389
|
_raise_for_status(response)
|
|
351
390
|
return response.json()
|
|
352
391
|
|
|
392
|
+
@_retry_transient
|
|
353
393
|
def get_evaluation_run(self, run_id: str, eval_run_id: str) -> dict[str, Any]:
|
|
354
394
|
"""Get evaluation run details including per-simulation results."""
|
|
355
395
|
with httpx.Client(
|
|
@@ -360,6 +400,7 @@ class VerisAPI:
|
|
|
360
400
|
return response.json()
|
|
361
401
|
|
|
362
402
|
# Environment Variables
|
|
403
|
+
@_retry_transient
|
|
363
404
|
def list_environment_variables(self, environment_id: str) -> dict[str, Any]:
|
|
364
405
|
"""List environment variables for an environment."""
|
|
365
406
|
with httpx.Client(
|
|
@@ -404,6 +445,7 @@ class VerisAPI:
|
|
|
404
445
|
_raise_for_status(response)
|
|
405
446
|
return response.json()
|
|
406
447
|
|
|
448
|
+
@_retry_transient
|
|
407
449
|
def list_reports(self, environment_id: str, limit: int = 20, offset: int = 0) -> dict[str, Any]:
|
|
408
450
|
"""List reports for an environment."""
|
|
409
451
|
params: dict[str, Any] = {
|
|
@@ -418,6 +460,7 @@ class VerisAPI:
|
|
|
418
460
|
_raise_for_status(response)
|
|
419
461
|
return response.json()
|
|
420
462
|
|
|
463
|
+
@_retry_transient
|
|
421
464
|
def get_report(self, report_id: str) -> dict[str, Any]:
|
|
422
465
|
"""Get report details including html_content."""
|
|
423
466
|
with httpx.Client(base_url=self.base_url, headers=self._headers(), timeout=60) as client:
|
|
@@ -425,6 +468,7 @@ class VerisAPI:
|
|
|
425
468
|
_raise_for_status(response)
|
|
426
469
|
return response.json()
|
|
427
470
|
|
|
471
|
+
@_retry_transient
|
|
428
472
|
def download_report(self, report_id: str, format: str) -> bytes:
|
|
429
473
|
"""Download report in the specified format (pdf, md)."""
|
|
430
474
|
with httpx.Client(base_url=self.base_url, headers=self._headers(), timeout=120) as client:
|
|
@@ -449,6 +493,7 @@ class VerisAPI:
|
|
|
449
493
|
_raise_for_status(response)
|
|
450
494
|
return response.json()
|
|
451
495
|
|
|
496
|
+
@_retry_transient
|
|
452
497
|
def get_managed_config(self, environment_id: str) -> dict[str, Any]:
|
|
453
498
|
"""Get managed config files (signed download URLs) for an environment."""
|
|
454
499
|
with httpx.Client(
|
|
@@ -459,6 +504,7 @@ class VerisAPI:
|
|
|
459
504
|
return response.json()
|
|
460
505
|
|
|
461
506
|
# Sandbox metadata
|
|
507
|
+
@_retry_transient
|
|
462
508
|
def list_services(self) -> dict[str, Any]:
|
|
463
509
|
"""List available sandbox mock services."""
|
|
464
510
|
with httpx.Client(
|
|
@@ -468,6 +514,7 @@ class VerisAPI:
|
|
|
468
514
|
_raise_for_status(response)
|
|
469
515
|
return response.json()
|
|
470
516
|
|
|
517
|
+
@_retry_transient
|
|
471
518
|
def list_channels(self) -> dict[str, Any]:
|
|
472
519
|
"""List available channel types."""
|
|
473
520
|
with httpx.Client(
|
|
@@ -22,6 +22,63 @@ from veris_cli.config import Config, ProjectConfig
|
|
|
22
22
|
from veris_cli.veris_yaml import DEFAULT_DOCKERFILE, VerisYaml, slugify
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _check_sync_guard(
|
|
26
|
+
env_info: dict,
|
|
27
|
+
pc: ProjectConfig,
|
|
28
|
+
force: bool,
|
|
29
|
+
) -> str | None:
|
|
30
|
+
"""Check if remote managed config diverges from local and prompt user.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
"push" — user chose to overwrite remote with local config
|
|
34
|
+
"pull" — user chose to pull remote config
|
|
35
|
+
None — guard didn't fire (proceed normally)
|
|
36
|
+
|
|
37
|
+
Exits via sys.exit(1) in non-interactive mode when divergence is detected.
|
|
38
|
+
Raises SystemExit on cancel.
|
|
39
|
+
"""
|
|
40
|
+
if env_info.get("status") != "ready" or force:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
remote_config_at = env_info.get("config_updated_at")
|
|
44
|
+
local_synced_at = pc.get_config_synced_at()
|
|
45
|
+
if not remote_config_at or (local_synced_at and remote_config_at <= local_synced_at):
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
if not sys.stdin.isatty():
|
|
49
|
+
output.print_error(
|
|
50
|
+
"Remote config was updated since your last sync. "
|
|
51
|
+
"Run 'veris env config pull' first, or pass --force to overwrite."
|
|
52
|
+
)
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
|
|
55
|
+
import questionary
|
|
56
|
+
|
|
57
|
+
if not local_synced_at:
|
|
58
|
+
sync_message = "Your managed setup is complete. New config is available on the server."
|
|
59
|
+
else:
|
|
60
|
+
sync_message = "Remote config was updated since your last sync."
|
|
61
|
+
|
|
62
|
+
action = questionary.select(
|
|
63
|
+
sync_message,
|
|
64
|
+
choices=[
|
|
65
|
+
questionary.Choice(title="Pull remote config (overwrites local)", value="pull"),
|
|
66
|
+
questionary.Choice(title="Push local config (overwrites remote)", value="push"),
|
|
67
|
+
questionary.Choice(title="Cancel", value="cancel"),
|
|
68
|
+
],
|
|
69
|
+
).ask()
|
|
70
|
+
if action == "cancel" or action is None:
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
if action == "push":
|
|
73
|
+
confirm = questionary.confirm(
|
|
74
|
+
"This will overwrite the managed config generated by the Veris team. Continue?",
|
|
75
|
+
default=False,
|
|
76
|
+
).ask()
|
|
77
|
+
if not confirm:
|
|
78
|
+
sys.exit(0)
|
|
79
|
+
return action
|
|
80
|
+
|
|
81
|
+
|
|
25
82
|
def _env_push_local(
|
|
26
83
|
env_id: str,
|
|
27
84
|
tag: str,
|
|
@@ -61,6 +118,10 @@ def _env_push_local(
|
|
|
61
118
|
"-f",
|
|
62
119
|
dockerfile,
|
|
63
120
|
"--build-arg",
|
|
121
|
+
f"VERIS_BASE={base_image}",
|
|
122
|
+
# Also send the legacy name so Dockerfiles still using
|
|
123
|
+
# ARG GVISOR_BASE keep building during the rename period.
|
|
124
|
+
"--build-arg",
|
|
64
125
|
f"GVISOR_BASE={base_image}",
|
|
65
126
|
"-t",
|
|
66
127
|
image_uri,
|
|
@@ -439,6 +500,10 @@ def env_create(ctx, name: str | None, agent_name: str | None):
|
|
|
439
500
|
if target_obj is not None:
|
|
440
501
|
try:
|
|
441
502
|
api.upload_environment_config(env_id, target_obj.to_upload_dict())
|
|
503
|
+
# Record sync timestamp so `env push` sync guard stays in sync.
|
|
504
|
+
# We just uploaded from this machine — no need to fetch the
|
|
505
|
+
# server timestamp; "now" is accurate enough.
|
|
506
|
+
project_config.set_config_synced_at(datetime.now(UTC).isoformat())
|
|
442
507
|
output.print_success("Config uploaded")
|
|
443
508
|
except Exception as e:
|
|
444
509
|
output.print_warning(f"Config upload failed (non-fatal): {e}")
|
|
@@ -714,7 +779,7 @@ def env_submit(ctx, env_id: str | None, target: str | None):
|
|
|
714
779
|
api.notify_submit_uploaded(env_id)
|
|
715
780
|
output.print_success("Submitted for managed setup!")
|
|
716
781
|
output.print_info("You'll receive an email when your environment is ready.")
|
|
717
|
-
output.print_info("Then run 'veris env
|
|
782
|
+
output.print_info("Then run 'veris env config pull' followed by 'veris env push'.")
|
|
718
783
|
|
|
719
784
|
except ValueError as e:
|
|
720
785
|
output.print_error(str(e))
|
|
@@ -791,59 +856,62 @@ def env_push(ctx, tag: str, env_id: str | None, force: bool, target: str | None)
|
|
|
791
856
|
|
|
792
857
|
api = VerisAPI(profile=profile)
|
|
793
858
|
|
|
794
|
-
|
|
795
|
-
output.print_error(
|
|
796
|
-
f"{dockerfile_rel} not found. Run 'veris env create' or 'veris env config pull' first."
|
|
797
|
-
)
|
|
798
|
-
sys.exit(1)
|
|
799
|
-
|
|
800
|
-
# Config sync guard: only managed-setup envs (status=ready) have config
|
|
801
|
-
# that could be authored by someone other than this CLI user. For self-serve
|
|
802
|
-
# envs (status=created), the user IS the author — no divergence is possible
|
|
803
|
-
# and the guard would only cause spurious CI failures (local config_synced_at
|
|
804
|
-
# is always missing on fresh runners). status=pending is already blocked by
|
|
805
|
-
# the backend with its own message.
|
|
859
|
+
# Fetch env info early — needed for Dockerfile check and sync guard.
|
|
806
860
|
try:
|
|
807
861
|
env_info = api.get_environment(env_id)
|
|
808
862
|
except Exception:
|
|
809
863
|
env_info = {}
|
|
810
864
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
865
|
+
if not dockerfile_path.exists():
|
|
866
|
+
env_status = env_info.get("status", "")
|
|
867
|
+
if env_status == "pending":
|
|
868
|
+
output.print_error(
|
|
869
|
+
"Your environment is still being set up. You'll receive an email when it's ready."
|
|
870
|
+
)
|
|
871
|
+
sys.exit(1)
|
|
872
|
+
elif env_status == "ready":
|
|
873
|
+
# Config is ready on the server — offer to pull it.
|
|
817
874
|
if sys.stdin.isatty():
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if action == "cancel" or action is None:
|
|
833
|
-
return
|
|
834
|
-
if action == "pull":
|
|
835
|
-
ctx.invoke(config_pull, env_id=env_id, target=resolved_target)
|
|
836
|
-
# Reload target config after pull (resolved_target stays the same)
|
|
837
|
-
parsed = _load_veris_yaml(veris_yaml_path)
|
|
838
|
-
target_obj = parsed.get_target(resolved_target)
|
|
839
|
-
dockerfile_rel = target_obj.resolved_dockerfile()
|
|
840
|
-
dockerfile_path = Path.cwd() / dockerfile_rel
|
|
875
|
+
output.print_info(
|
|
876
|
+
"Dockerfile not found locally, but config is ready on the server."
|
|
877
|
+
)
|
|
878
|
+
pulled = ctx.invoke(config_pull, env_id=env_id, target=resolved_target)
|
|
879
|
+
if not pulled:
|
|
880
|
+
sys.exit(1)
|
|
881
|
+
# Reload after pull
|
|
882
|
+
parsed = _load_veris_yaml(veris_yaml_path)
|
|
883
|
+
target_obj = parsed.get_target(resolved_target)
|
|
884
|
+
dockerfile_rel = target_obj.resolved_dockerfile()
|
|
885
|
+
dockerfile_path = Path.cwd() / dockerfile_rel
|
|
886
|
+
if not dockerfile_path.exists():
|
|
887
|
+
output.print_error("Dockerfile still missing after config pull.")
|
|
888
|
+
sys.exit(1)
|
|
841
889
|
else:
|
|
842
890
|
output.print_error(
|
|
843
|
-
"
|
|
844
|
-
"Run 'veris env config pull' first, or pass --force to overwrite."
|
|
891
|
+
"Dockerfile not found. Run 'veris env config pull' to download it."
|
|
845
892
|
)
|
|
846
893
|
sys.exit(1)
|
|
894
|
+
else:
|
|
895
|
+
output.print_error(
|
|
896
|
+
"Dockerfile not found. Create it locally, or run 'veris env submit' "
|
|
897
|
+
"for managed setup."
|
|
898
|
+
)
|
|
899
|
+
sys.exit(1)
|
|
900
|
+
|
|
901
|
+
# Config sync guard: only managed-setup envs (status=ready) have config
|
|
902
|
+
# that could be authored by someone other than this CLI user.
|
|
903
|
+
pc = ProjectConfig(profile=profile, target=resolved_target)
|
|
904
|
+
guard_action = _check_sync_guard(env_info, pc, force)
|
|
905
|
+
if guard_action == "pull":
|
|
906
|
+
pulled = ctx.invoke(config_pull, env_id=env_id, target=resolved_target)
|
|
907
|
+
if not pulled:
|
|
908
|
+
output.print_error("Config pull did not download files. Aborting push.")
|
|
909
|
+
sys.exit(1)
|
|
910
|
+
# Reload target config after pull (resolved_target stays the same)
|
|
911
|
+
parsed = _load_veris_yaml(veris_yaml_path)
|
|
912
|
+
target_obj = parsed.get_target(resolved_target)
|
|
913
|
+
dockerfile_rel = target_obj.resolved_dockerfile()
|
|
914
|
+
dockerfile_path = Path.cwd() / dockerfile_rel
|
|
847
915
|
|
|
848
916
|
try:
|
|
849
917
|
api.upload_environment_config(env_id, target_obj.to_upload_dict())
|
|
@@ -1036,46 +1104,16 @@ def config_push(ctx, file_path: str | None, env_id: str, force: bool, target: st
|
|
|
1036
1104
|
api = VerisAPI(profile=profile)
|
|
1037
1105
|
pc = ProjectConfig(profile=profile, target=resolved_target)
|
|
1038
1106
|
|
|
1039
|
-
# Config sync guard — only meaningful for managed-setup envs (status=ready)
|
|
1040
|
-
# where someone else may have authored the remote config. See env_push for
|
|
1041
|
-
# the full rationale.
|
|
1042
1107
|
try:
|
|
1043
1108
|
env_info = api.get_environment(env_id)
|
|
1044
1109
|
except Exception:
|
|
1045
1110
|
env_info = {}
|
|
1046
1111
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
import questionary
|
|
1053
|
-
|
|
1054
|
-
action = questionary.select(
|
|
1055
|
-
"Remote config was updated since your last sync.",
|
|
1056
|
-
choices=[
|
|
1057
|
-
questionary.Choice(
|
|
1058
|
-
title="Pull remote config (overwrites local)", value="pull"
|
|
1059
|
-
),
|
|
1060
|
-
questionary.Choice(
|
|
1061
|
-
title="Push local config (overwrites remote)", value="push"
|
|
1062
|
-
),
|
|
1063
|
-
questionary.Choice(title="Cancel", value="cancel"),
|
|
1064
|
-
],
|
|
1065
|
-
).ask()
|
|
1066
|
-
if action == "cancel" or action is None:
|
|
1067
|
-
return
|
|
1068
|
-
if action == "pull":
|
|
1069
|
-
# Delegate to config pull logic
|
|
1070
|
-
output.print_info("Pulling remote config...")
|
|
1071
|
-
ctx.invoke(config_pull, env_id=env_id, target=resolved_target)
|
|
1072
|
-
return
|
|
1073
|
-
else:
|
|
1074
|
-
output.print_error(
|
|
1075
|
-
"Remote config was updated since your last sync. "
|
|
1076
|
-
"Run 'veris env config pull' first, or pass --force to overwrite."
|
|
1077
|
-
)
|
|
1078
|
-
sys.exit(1)
|
|
1112
|
+
guard_action = _check_sync_guard(env_info, pc, force)
|
|
1113
|
+
if guard_action == "pull":
|
|
1114
|
+
output.print_info("Pulling remote config...")
|
|
1115
|
+
ctx.invoke(config_pull, env_id=env_id, target=resolved_target)
|
|
1116
|
+
return
|
|
1079
1117
|
|
|
1080
1118
|
try:
|
|
1081
1119
|
api.upload_environment_config(env_id, target_obj.to_upload_dict())
|
|
@@ -1118,8 +1156,11 @@ def config_pull(ctx, env_id: str | None, target: str | None):
|
|
|
1118
1156
|
"Your environment is being set up. You'll receive an email when it's ready."
|
|
1119
1157
|
)
|
|
1120
1158
|
else:
|
|
1121
|
-
output.print_info(
|
|
1122
|
-
|
|
1159
|
+
output.print_info(
|
|
1160
|
+
"No remote config available yet. "
|
|
1161
|
+
"If using managed setup, run 'veris env submit'."
|
|
1162
|
+
)
|
|
1163
|
+
return False
|
|
1123
1164
|
|
|
1124
1165
|
veris_dir = Path.cwd() / ".veris"
|
|
1125
1166
|
veris_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -1134,6 +1175,11 @@ def config_pull(ctx, env_id: str | None, target: str | None):
|
|
|
1134
1175
|
output.print_warning("Failed to download Dockerfile.sandbox")
|
|
1135
1176
|
|
|
1136
1177
|
veris_yaml_url = result.get("veris_yaml_url")
|
|
1178
|
+
if veris_yaml_url and not resolved_target:
|
|
1179
|
+
output.print_warning(
|
|
1180
|
+
"Skipped veris.yaml download — no target resolved. "
|
|
1181
|
+
"Use --target or set an active target with 'veris env targets set <name>'."
|
|
1182
|
+
)
|
|
1137
1183
|
if veris_yaml_url and resolved_target:
|
|
1138
1184
|
resp = httpx.get(veris_yaml_url, timeout=60)
|
|
1139
1185
|
if resp.status_code == 200:
|
|
@@ -1155,6 +1201,7 @@ def config_pull(ctx, env_id: str | None, target: str | None):
|
|
|
1155
1201
|
env_detail = api.get_environment(env_id)
|
|
1156
1202
|
config_ts = env_detail.get("config_updated_at") or datetime.now(UTC).isoformat()
|
|
1157
1203
|
ProjectConfig(profile=profile, target=resolved_target).set_config_synced_at(config_ts)
|
|
1204
|
+
return True
|
|
1158
1205
|
|
|
1159
1206
|
except Exception as e:
|
|
1160
1207
|
output.print_error(f"Failed to pull config: {e}")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
set -e
|
|
3
3
|
|
|
4
4
|
# Docker build script for Veris CLI
|
|
5
|
-
# Usage: docker_build.sh <dockerfile_path> <image_uri> <registry> <username> <password> [no_cache] [
|
|
5
|
+
# Usage: docker_build.sh <dockerfile_path> <image_uri> <registry> <username> <password> [no_cache] [veris_base]
|
|
6
6
|
|
|
7
7
|
DOCKERFILE_PATH="$1"
|
|
8
8
|
IMAGE_URI="$2"
|
|
@@ -10,7 +10,7 @@ REGISTRY="$3"
|
|
|
10
10
|
USERNAME="$4"
|
|
11
11
|
PASSWORD="$5"
|
|
12
12
|
NO_CACHE="${6:-0}" # Optional, defaults to 0 (use cache)
|
|
13
|
-
|
|
13
|
+
VERIS_BASE="$7" # Optional, overrides VERIS_BASE build arg
|
|
14
14
|
|
|
15
15
|
if [ -z "$DOCKERFILE_PATH" ] || [ -z "$IMAGE_URI" ] || [ -z "$REGISTRY" ] || [ -z "$USERNAME" ] || [ -z "$PASSWORD" ]; then
|
|
16
16
|
echo "Usage: $0 <dockerfile_path> <image_uri> <registry> <username> <password> [no_cache]"
|
|
@@ -49,13 +49,13 @@ echo " Registry: $REGISTRY"
|
|
|
49
49
|
echo ""
|
|
50
50
|
echo "$PASSWORD" | docker login -u "$USERNAME" --password-stdin "$REGISTRY"
|
|
51
51
|
|
|
52
|
-
if [ -n "$
|
|
52
|
+
if [ -n "$VERIS_BASE" ] && ! grep -qE 'ARG (VERIS_BASE|GVISOR_BASE)' "$DOCKERFILE_PATH"; then
|
|
53
53
|
echo ""
|
|
54
|
-
echo "ERROR: Dockerfile is missing 'ARG
|
|
54
|
+
echo "ERROR: Dockerfile is missing 'ARG VERIS_BASE' declaration."
|
|
55
55
|
echo "The base image cannot be overridden without it. Replace your FROM line with:"
|
|
56
56
|
echo ""
|
|
57
|
-
echo " ARG
|
|
58
|
-
echo " FROM \${
|
|
57
|
+
echo " ARG VERIS_BASE"
|
|
58
|
+
echo " FROM \${VERIS_BASE}"
|
|
59
59
|
echo ""
|
|
60
60
|
exit 1
|
|
61
61
|
fi
|
|
@@ -72,8 +72,11 @@ echo ""
|
|
|
72
72
|
# FROM images via registry metadata, failing for local-only base images
|
|
73
73
|
# (e.g. veris-gvisor:latest built by build-local.sh).
|
|
74
74
|
BUILD_ARGS="--platform linux/amd64 -f $DOCKERFILE_PATH -t $IMAGE_URI"
|
|
75
|
-
if [ -n "$
|
|
76
|
-
|
|
75
|
+
if [ -n "$VERIS_BASE" ]; then
|
|
76
|
+
# Send both the new name (VERIS_BASE) and the legacy one (GVISOR_BASE)
|
|
77
|
+
# so Dockerfiles with either declaration keep building. Docker ignores
|
|
78
|
+
# --build-arg flags whose ARG isn't declared.
|
|
79
|
+
BUILD_ARGS="$BUILD_ARGS --build-arg VERIS_BASE=$VERIS_BASE --build-arg GVISOR_BASE=$VERIS_BASE"
|
|
77
80
|
fi
|
|
78
81
|
if [ "$NO_CACHE" = "1" ]; then
|
|
79
82
|
docker rmi "$IMAGE_URI" 2>/dev/null || true
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Static file templates for veris init."""
|
|
2
2
|
|
|
3
|
-
DOCKERFILE_SANDBOX = """# Extends
|
|
4
|
-
ARG
|
|
5
|
-
FROM ${
|
|
3
|
+
DOCKERFILE_SANDBOX = """# Extends the Veris base image with your agent code
|
|
4
|
+
ARG VERIS_BASE
|
|
5
|
+
FROM ${VERIS_BASE}
|
|
6
6
|
|
|
7
7
|
# ============================================================
|
|
8
8
|
# 1. AGENT CODE — Update paths to match your project structure
|
|
@@ -141,7 +141,7 @@ TARGET_BODY = """\
|
|
|
141
141
|
entry_point: uv run --no-sync uvicorn app.main:app --host 0.0.0.0 --port 8008
|
|
142
142
|
port: 8008
|
|
143
143
|
# environment:
|
|
144
|
-
# DATABASE_URL: postgresql://postgres:postgres@localhost:5432/
|
|
144
|
+
# DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veris
|
|
145
145
|
"""
|
|
146
146
|
|
|
147
147
|
|
|
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
|