veris-cli 2.26.0__tar.gz → 2.27.1__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.26.0 → veris_cli-2.27.1}/PKG-INFO +29 -14
- {veris_cli-2.26.0 → veris_cli-2.27.1}/README.md +27 -13
- {veris_cli-2.26.0 → veris_cli-2.27.1}/pyproject.toml +3 -1
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/api.py +141 -9
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/build_context.py +31 -0
- veris_cli-2.27.1/src/veris_cli/commands/env.py +2426 -0
- veris_cli-2.27.1/src/veris_cli/stacks/__init__.py +125 -0
- veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/__init__.py +143 -0
- veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/fetch.py +158 -0
- veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/repo_shape.py +62 -0
- veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/scripts/snapshot.sh +64 -0
- veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/snapshot.py +118 -0
- veris_cli-2.26.0/src/veris_cli/commands/env.py +0 -1299
- {veris_cli-2.26.0 → veris_cli-2.27.1}/.gitignore +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/__init__.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/cli.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/__init__.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/_helpers.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/auth.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/evaluations.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/profile.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/reports.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/run.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/scenarios.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/commands/simulations.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/config.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/output.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/prompts.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/run_output.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/scripts/__init__.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/scripts/docker_build.sh +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/scripts/docker_push.sh +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/searchable_checkbox.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/src/veris_cli/templates.py +0 -0
- {veris_cli-2.26.0 → veris_cli-2.27.1}/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.27.1
|
|
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
|
|
@@ -8,6 +8,7 @@ Author: Veris
|
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
9
|
Requires-Dist: click>=8.1.7
|
|
10
10
|
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: jinja2>=3.1.0
|
|
11
12
|
Requires-Dist: pathspec>=0.12.0
|
|
12
13
|
Requires-Dist: pydantic>=2.6
|
|
13
14
|
Requires-Dist: pyyaml>=6.0.1
|
|
@@ -57,34 +58,48 @@ veris login YOUR_API_KEY
|
|
|
57
58
|
|
|
58
59
|
This saves your credentials to `~/.veris/config.yaml`.
|
|
59
60
|
|
|
60
|
-
### 2.
|
|
61
|
+
### 2. Submit Your Agent (Managed Onboarding)
|
|
61
62
|
|
|
62
63
|
```bash
|
|
63
|
-
|
|
64
|
+
cd ~/my-agent
|
|
65
|
+
veris env submit
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
This
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
This is the **only command you need** to onboard a new agent. It packages
|
|
69
|
+
your repo, uploads it to Veris, and lets the platform generate
|
|
70
|
+
`Dockerfile.sandbox` and `veris.yaml` for you. You'll receive an email
|
|
71
|
+
when your environment is `ready` — typically the same business day.
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
No prior `veris env create` is required. If there is no env yet, `env
|
|
74
|
+
submit` creates one (using `--name`, `--target`, or the current directory
|
|
75
|
+
name).
|
|
72
76
|
|
|
73
|
-
### 3.
|
|
74
|
-
|
|
75
|
-
Edit `.veris/Dockerfile.sandbox` and `.veris/veris.yaml` to match your agent. Set secrets:
|
|
77
|
+
### 3. Set Any Secrets
|
|
76
78
|
|
|
77
79
|
```bash
|
|
78
80
|
veris env vars set OPENAI_API_KEY=sk-your-key --secret
|
|
79
81
|
```
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
You can set secrets at any point — before or after the env reaches `ready`.
|
|
84
|
+
|
|
85
|
+
### 4. Iterate After Release
|
|
86
|
+
|
|
87
|
+
Once your env is `ready`, pull the generated config and iterate locally:
|
|
82
88
|
|
|
83
89
|
```bash
|
|
84
|
-
veris env
|
|
90
|
+
veris env config pull # Pull generated .veris/Dockerfile.sandbox + veris.yaml
|
|
91
|
+
veris env push # Build and push a new image tag
|
|
85
92
|
```
|
|
86
93
|
|
|
87
|
-
|
|
94
|
+
Re-running `veris env push` after each code change is fast. Only re-run
|
|
95
|
+
`veris env submit` when something structural changes (new agent entry
|
|
96
|
+
point, new service dependency, etc.).
|
|
97
|
+
|
|
98
|
+
> **Stack flows** — agents with non-trivial integration requirements
|
|
99
|
+
> (e.g. nemoclaw) use `veris env create --stack <name>`. Run `veris env
|
|
100
|
+
> create` without `--stack` to see the picker, which lets you choose a
|
|
101
|
+
> registered stack or route into `veris env submit` (the default for
|
|
102
|
+
> plain agents).
|
|
88
103
|
|
|
89
104
|
### 5. Generate Scenarios
|
|
90
105
|
|
|
@@ -33,34 +33,48 @@ veris login YOUR_API_KEY
|
|
|
33
33
|
|
|
34
34
|
This saves your credentials to `~/.veris/config.yaml`.
|
|
35
35
|
|
|
36
|
-
### 2.
|
|
36
|
+
### 2. Submit Your Agent (Managed Onboarding)
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
|
|
39
|
+
cd ~/my-agent
|
|
40
|
+
veris env submit
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
This
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
This is the **only command you need** to onboard a new agent. It packages
|
|
44
|
+
your repo, uploads it to Veris, and lets the platform generate
|
|
45
|
+
`Dockerfile.sandbox` and `veris.yaml` for you. You'll receive an email
|
|
46
|
+
when your environment is `ready` — typically the same business day.
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
No prior `veris env create` is required. If there is no env yet, `env
|
|
49
|
+
submit` creates one (using `--name`, `--target`, or the current directory
|
|
50
|
+
name).
|
|
48
51
|
|
|
49
|
-
### 3.
|
|
50
|
-
|
|
51
|
-
Edit `.veris/Dockerfile.sandbox` and `.veris/veris.yaml` to match your agent. Set secrets:
|
|
52
|
+
### 3. Set Any Secrets
|
|
52
53
|
|
|
53
54
|
```bash
|
|
54
55
|
veris env vars set OPENAI_API_KEY=sk-your-key --secret
|
|
55
56
|
```
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
You can set secrets at any point — before or after the env reaches `ready`.
|
|
59
|
+
|
|
60
|
+
### 4. Iterate After Release
|
|
61
|
+
|
|
62
|
+
Once your env is `ready`, pull the generated config and iterate locally:
|
|
58
63
|
|
|
59
64
|
```bash
|
|
60
|
-
veris env
|
|
65
|
+
veris env config pull # Pull generated .veris/Dockerfile.sandbox + veris.yaml
|
|
66
|
+
veris env push # Build and push a new image tag
|
|
61
67
|
```
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
Re-running `veris env push` after each code change is fast. Only re-run
|
|
70
|
+
`veris env submit` when something structural changes (new agent entry
|
|
71
|
+
point, new service dependency, etc.).
|
|
72
|
+
|
|
73
|
+
> **Stack flows** — agents with non-trivial integration requirements
|
|
74
|
+
> (e.g. nemoclaw) use `veris env create --stack <name>`. Run `veris env
|
|
75
|
+
> create` without `--stack` to see the picker, which lets you choose a
|
|
76
|
+
> registered stack or route into `veris env submit` (the default for
|
|
77
|
+
> plain agents).
|
|
64
78
|
|
|
65
79
|
### 5. Generate Scenarios
|
|
66
80
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "veris-cli"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.27.1"
|
|
4
4
|
description = "CLI to connect local agents to the Veris backend"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -16,6 +16,7 @@ dependencies = [
|
|
|
16
16
|
"pyyaml>=6.0.1",
|
|
17
17
|
"pydantic>=2.6",
|
|
18
18
|
"tenacity>=8.2.0",
|
|
19
|
+
"jinja2>=3.1.0",
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
[project.scripts]
|
|
@@ -57,6 +58,7 @@ asyncio_mode = "auto"
|
|
|
57
58
|
include = [
|
|
58
59
|
"src/veris_cli/**",
|
|
59
60
|
"src/veris_cli/scripts/*.sh",
|
|
61
|
+
"src/veris_cli/stacks/nemoclaw/scripts/*.sh",
|
|
60
62
|
"README.md",
|
|
61
63
|
"pyproject.toml",
|
|
62
64
|
]
|
|
@@ -17,13 +17,29 @@ from veris_cli.config import Config
|
|
|
17
17
|
class APIError(Exception):
|
|
18
18
|
"""Raised when the backend returns an error response with details."""
|
|
19
19
|
|
|
20
|
-
def __init__(
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
status_code: int,
|
|
23
|
+
detail: str,
|
|
24
|
+
url: str,
|
|
25
|
+
retry_after: float | None = None,
|
|
26
|
+
):
|
|
21
27
|
self.status_code = status_code
|
|
22
28
|
self.detail = detail
|
|
23
29
|
self.url = url
|
|
30
|
+
self.retry_after = retry_after
|
|
24
31
|
super().__init__(f"[{status_code}] {detail} ({url})")
|
|
25
32
|
|
|
26
33
|
|
|
34
|
+
def _parse_retry_after(value: object) -> float | None:
|
|
35
|
+
if not isinstance(value, str):
|
|
36
|
+
return None
|
|
37
|
+
try:
|
|
38
|
+
return float(value)
|
|
39
|
+
except ValueError:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
27
43
|
def _raise_for_status(response: httpx.Response) -> None:
|
|
28
44
|
"""Like response.raise_for_status() but includes the response body."""
|
|
29
45
|
if response.is_success:
|
|
@@ -33,23 +49,36 @@ def _raise_for_status(response: httpx.Response) -> None:
|
|
|
33
49
|
detail = body.get("detail", response.text)
|
|
34
50
|
except Exception:
|
|
35
51
|
detail = response.text or response.reason_phrase
|
|
36
|
-
raise APIError(
|
|
52
|
+
raise APIError(
|
|
53
|
+
response.status_code,
|
|
54
|
+
detail,
|
|
55
|
+
str(response.url),
|
|
56
|
+
retry_after=_parse_retry_after(response.headers.get("Retry-After")),
|
|
57
|
+
)
|
|
37
58
|
|
|
38
59
|
|
|
39
|
-
_RETRYABLE_STATUSES = frozenset({502, 503, 504})
|
|
60
|
+
_RETRYABLE_STATUSES = frozenset({429, 502, 503, 504})
|
|
61
|
+
_RETRY_AFTER_CAP_SECONDS = 60.0
|
|
62
|
+
_exp_wait = wait_exponential(multiplier=1, min=1, max=10)
|
|
40
63
|
|
|
41
64
|
|
|
42
65
|
def _is_transient_api_error(exc: BaseException) -> bool:
|
|
43
66
|
return isinstance(exc, APIError) and exc.status_code in _RETRYABLE_STATUSES
|
|
44
67
|
|
|
45
68
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
def _wait_with_retry_after(retry_state) -> float:
|
|
70
|
+
exc = retry_state.outcome.exception() if retry_state.outcome else None
|
|
71
|
+
if isinstance(exc, APIError) and exc.retry_after is not None:
|
|
72
|
+
return min(exc.retry_after, _RETRY_AFTER_CAP_SECONDS)
|
|
73
|
+
return _exp_wait(retry_state)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Retries transient failures on idempotent reads. Mutating calls
|
|
77
|
+
# (POST/PUT/DELETE) are left alone to avoid duplicate side effects.
|
|
49
78
|
_retry_transient = retry(
|
|
50
79
|
reraise=True,
|
|
51
80
|
stop=stop_after_attempt(5),
|
|
52
|
-
wait=
|
|
81
|
+
wait=_wait_with_retry_after,
|
|
53
82
|
retry=(
|
|
54
83
|
retry_if_exception_type(httpx.TransportError) | retry_if_exception(_is_transient_api_error)
|
|
55
84
|
),
|
|
@@ -79,13 +108,35 @@ class VerisAPI:
|
|
|
79
108
|
return {"Authorization": f"Bearer {self.api_key}"}
|
|
80
109
|
|
|
81
110
|
# Environments
|
|
82
|
-
def create_environment(
|
|
83
|
-
|
|
111
|
+
def create_environment(
|
|
112
|
+
self,
|
|
113
|
+
name: str,
|
|
114
|
+
description: Optional[str] = None,
|
|
115
|
+
stack: Optional[str] = None,
|
|
116
|
+
skip_managed_onboarding: bool = False,
|
|
117
|
+
) -> dict[str, Any]:
|
|
118
|
+
"""Create a new environment (one-time setup).
|
|
119
|
+
|
|
120
|
+
`stack` records which `veris env create --stack <name>` scaffold
|
|
121
|
+
flow produced this env (e.g. "nemoclaw"). Backend stores it on
|
|
122
|
+
the environment row for analytics + per-stack base-image hints.
|
|
123
|
+
|
|
124
|
+
`skip_managed_onboarding=True` opts this env out of managed
|
|
125
|
+
onboarding — backend stamps it directly into the released state
|
|
126
|
+
(status='ready', onboard_status='completed') and the build gate
|
|
127
|
+
doesn't apply. Used by `veris env create --self-serve`. Default
|
|
128
|
+
False keeps SDK / scripted callers on the managed-onboarding
|
|
129
|
+
path so they stay protected.
|
|
130
|
+
"""
|
|
84
131
|
payload: dict[str, Any] = {"name": name, "description": description}
|
|
85
132
|
if self.organization_id:
|
|
86
133
|
payload["organization_id"] = self.organization_id
|
|
87
134
|
else:
|
|
88
135
|
payload["personal"] = True
|
|
136
|
+
if stack is not None:
|
|
137
|
+
payload["stack"] = stack
|
|
138
|
+
if skip_managed_onboarding:
|
|
139
|
+
payload["skip_managed_onboarding"] = True
|
|
89
140
|
with httpx.Client(
|
|
90
141
|
base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
|
|
91
142
|
) as client:
|
|
@@ -93,6 +144,87 @@ class VerisAPI:
|
|
|
93
144
|
_raise_for_status(response)
|
|
94
145
|
return response.json()
|
|
95
146
|
|
|
147
|
+
# Recipes — stack scaffold templates
|
|
148
|
+
@_retry_transient
|
|
149
|
+
def get_recipe_bundle(
|
|
150
|
+
self,
|
|
151
|
+
stack: str,
|
|
152
|
+
environment_name: Optional[str] = None,
|
|
153
|
+
agent_name: Optional[str] = None,
|
|
154
|
+
provider_key: Optional[str] = None,
|
|
155
|
+
provider_base_url: Optional[str] = None,
|
|
156
|
+
) -> dict[str, Any]:
|
|
157
|
+
"""Fetch the full recipe bundle for `stack` (file contents + metadata).
|
|
158
|
+
|
|
159
|
+
Backend renders Jinja templates server-side with the supplied query
|
|
160
|
+
params. sandbox-base digest is resolved later by `snapshot.sh` and
|
|
161
|
+
patched into the rendered Dockerfile directly — not picked here.
|
|
162
|
+
|
|
163
|
+
`provider_key` selects which LLM provider's apiKey + baseUrl gets
|
|
164
|
+
patched at agent boot (via veris-agent-entrypoint.sh reading the
|
|
165
|
+
rendered NEMOCLAW_PROVIDER_* env vars). `provider_base_url` is the
|
|
166
|
+
public hostname Veris's DNS aliases route to llm-proxy; omit to use
|
|
167
|
+
the manifest's default for `provider_key`.
|
|
168
|
+
|
|
169
|
+
Used by `veris env create --stack <name>` to scaffold `.veris/`.
|
|
170
|
+
"""
|
|
171
|
+
params: dict[str, str] = {}
|
|
172
|
+
if environment_name:
|
|
173
|
+
params["environment_name"] = environment_name
|
|
174
|
+
if agent_name:
|
|
175
|
+
params["agent_name"] = agent_name
|
|
176
|
+
if provider_key:
|
|
177
|
+
params["provider_key"] = provider_key
|
|
178
|
+
if provider_base_url:
|
|
179
|
+
params["provider_base_url"] = provider_base_url
|
|
180
|
+
with httpx.Client(
|
|
181
|
+
base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
|
|
182
|
+
) as client:
|
|
183
|
+
response = client.get(f"/v1/recipes/{stack}", params=params)
|
|
184
|
+
_raise_for_status(response)
|
|
185
|
+
return response.json()
|
|
186
|
+
|
|
187
|
+
@_retry_transient
|
|
188
|
+
def get_recipe_metadata(self, stack: str) -> dict[str, Any]:
|
|
189
|
+
"""Fetch just the recipe metadata for `stack`.
|
|
190
|
+
|
|
191
|
+
Used by `veris env push` to surface an upgrade advisory when the
|
|
192
|
+
customer's pinned template version drifts behind the current one.
|
|
193
|
+
"""
|
|
194
|
+
with httpx.Client(
|
|
195
|
+
base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
|
|
196
|
+
) as client:
|
|
197
|
+
response = client.get(f"/v1/recipes/{stack}/latest")
|
|
198
|
+
_raise_for_status(response)
|
|
199
|
+
return response.json()
|
|
200
|
+
|
|
201
|
+
@_retry_transient
|
|
202
|
+
def get_recipe_providers(self, stack: str) -> dict[str, Any]:
|
|
203
|
+
"""Fetch the supported LLM providers for `stack`.
|
|
204
|
+
|
|
205
|
+
Used by `veris env create --stack <name>` to show choices in the
|
|
206
|
+
provider prompt. Customer can also pass `--provider custom
|
|
207
|
+
--provider-base-url <url>` to bypass the list.
|
|
208
|
+
"""
|
|
209
|
+
with httpx.Client(
|
|
210
|
+
base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
|
|
211
|
+
) as client:
|
|
212
|
+
response = client.get(f"/v1/recipes/{stack}/providers")
|
|
213
|
+
_raise_for_status(response)
|
|
214
|
+
return response.json()
|
|
215
|
+
|
|
216
|
+
def get_llm_providers(self) -> dict[str, Any]:
|
|
217
|
+
"""Fetch the canonical list of LLM providers Veris's llm-proxy
|
|
218
|
+
intercepts. Stack-agnostic — used by ``veris env submit`` and the
|
|
219
|
+
stack-less ``veris env create`` to populate the API-key picker.
|
|
220
|
+
"""
|
|
221
|
+
with httpx.Client(
|
|
222
|
+
base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
|
|
223
|
+
) as client:
|
|
224
|
+
response = client.get("/v1/llm-providers")
|
|
225
|
+
_raise_for_status(response)
|
|
226
|
+
return response.json()
|
|
227
|
+
|
|
96
228
|
@_retry_transient
|
|
97
229
|
def list_environments(
|
|
98
230
|
self, status: Optional[str] = None, limit: int = 20, offset: int = 0
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
Packages the project root into a .tar.gz, respecting .dockerignore patterns.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import io
|
|
6
7
|
import os
|
|
7
8
|
import tarfile
|
|
8
9
|
import tempfile
|
|
10
|
+
import time
|
|
9
11
|
from pathlib import Path
|
|
10
12
|
|
|
11
13
|
import pathspec
|
|
@@ -32,6 +34,7 @@ def create_build_context(
|
|
|
32
34
|
output_path: Path | None = None,
|
|
33
35
|
dockerfile: Path | str | None = None,
|
|
34
36
|
require_dockerfile: bool = True,
|
|
37
|
+
file_overrides: dict[str, str] | None = None,
|
|
35
38
|
) -> tuple[Path, int]:
|
|
36
39
|
"""Create a tar.gz build context from the project root.
|
|
37
40
|
|
|
@@ -46,6 +49,13 @@ def create_build_context(
|
|
|
46
49
|
to `.veris/Dockerfile.sandbox` when None.
|
|
47
50
|
require_dockerfile: If False, skip Dockerfile validation and inclusion.
|
|
48
51
|
Used by ``env submit`` where no Dockerfile exists yet.
|
|
52
|
+
file_overrides: Optional arcname → content map. For each entry the
|
|
53
|
+
given content is written into the tarball at the arcname,
|
|
54
|
+
replacing any on-disk file at the same path and bypassing
|
|
55
|
+
`.dockerignore`. The customer's working tree is never modified.
|
|
56
|
+
Used by ``env submit`` to inject the per-target single-target
|
|
57
|
+
hint into ``.veris/veris.yaml`` without rewriting the on-disk
|
|
58
|
+
(possibly multi-target) file.
|
|
49
59
|
|
|
50
60
|
Returns:
|
|
51
61
|
Tuple of (tarball_path, size_bytes).
|
|
@@ -54,6 +64,7 @@ def create_build_context(
|
|
|
54
64
|
ValueError: If tarball exceeds the size cap or the Dockerfile is missing.
|
|
55
65
|
"""
|
|
56
66
|
spec = _load_dockerignore(project_root)
|
|
67
|
+
overrides = file_overrides or {}
|
|
57
68
|
|
|
58
69
|
if output_path is None:
|
|
59
70
|
fd, tmp = tempfile.mkstemp(suffix=".tar.gz", prefix="veris-build-")
|
|
@@ -97,6 +108,12 @@ def create_build_context(
|
|
|
97
108
|
if rel_path == ".":
|
|
98
109
|
rel_path = f
|
|
99
110
|
|
|
111
|
+
# Overrides win and bypass dockerignore — defer to the
|
|
112
|
+
# post-walk loop so they're written exactly once with the
|
|
113
|
+
# override content.
|
|
114
|
+
if rel_path in overrides:
|
|
115
|
+
continue
|
|
116
|
+
|
|
100
117
|
# Skip ignored files (but always include the selected Dockerfile)
|
|
101
118
|
if spec.match_file(rel_path) and rel_path != dockerfile_rel:
|
|
102
119
|
continue
|
|
@@ -112,6 +129,20 @@ def create_build_context(
|
|
|
112
129
|
tar.add(str(dockerfile_path), arcname=dockerfile_rel)
|
|
113
130
|
file_count += 1
|
|
114
131
|
|
|
132
|
+
# Write overrides last so they always land regardless of ignore rules.
|
|
133
|
+
# We build a TarInfo by hand instead of going through ``tar.add`` —
|
|
134
|
+
# the override content lives only in memory, not on disk.
|
|
135
|
+
now = int(time.time())
|
|
136
|
+
for arcname, content in overrides.items():
|
|
137
|
+
data = content.encode("utf-8")
|
|
138
|
+
info = tarfile.TarInfo(name=arcname)
|
|
139
|
+
info.size = len(data)
|
|
140
|
+
info.mode = 0o644
|
|
141
|
+
info.mtime = now
|
|
142
|
+
info.type = tarfile.REGTYPE
|
|
143
|
+
tar.addfile(info, io.BytesIO(data))
|
|
144
|
+
file_count += 1
|
|
145
|
+
|
|
115
146
|
size = output_path.stat().st_size
|
|
116
147
|
if size > MAX_TARBALL_SIZE:
|
|
117
148
|
output_path.unlink()
|