vers-sdk 0.1.1__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.
- vers_sdk/__init__.py +34 -0
- vers_sdk/client.py +529 -0
- vers_sdk/errors.py +132 -0
- vers_sdk/models.py +545 -0
- vers_sdk-0.1.1.dist-info/METADATA +10 -0
- vers_sdk-0.1.1.dist-info/RECORD +8 -0
- vers_sdk-0.1.1.dist-info/WHEEL +5 -0
- vers_sdk-0.1.1.dist-info/top_level.txt +1 -0
vers_sdk/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Generated by Sterling SDK Generator
|
|
2
|
+
# Orchestrator Control Plane API v0.1.0
|
|
3
|
+
|
|
4
|
+
from .client import VersSdkClient
|
|
5
|
+
from .errors import (
|
|
6
|
+
VersSDKError,
|
|
7
|
+
APIError,
|
|
8
|
+
APIConnectionError,
|
|
9
|
+
APIConnectionTimeoutError,
|
|
10
|
+
BadRequestError,
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
PermissionDeniedError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
ConflictError,
|
|
15
|
+
UnprocessableEntityError,
|
|
16
|
+
RateLimitError,
|
|
17
|
+
InternalServerError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"VersSdkClient",
|
|
22
|
+
"VersSDKError",
|
|
23
|
+
"APIError",
|
|
24
|
+
"APIConnectionError",
|
|
25
|
+
"APIConnectionTimeoutError",
|
|
26
|
+
"BadRequestError",
|
|
27
|
+
"AuthenticationError",
|
|
28
|
+
"PermissionDeniedError",
|
|
29
|
+
"NotFoundError",
|
|
30
|
+
"ConflictError",
|
|
31
|
+
"UnprocessableEntityError",
|
|
32
|
+
"RateLimitError",
|
|
33
|
+
"InternalServerError",
|
|
34
|
+
]
|
vers_sdk/client.py
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
# Generated by Sterling SDK Generator
|
|
2
|
+
# Orchestrator Control Plane API v0.1.0
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import calendar
|
|
6
|
+
import email.utils
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import random
|
|
10
|
+
import time as _time
|
|
11
|
+
import uuid
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Optional
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
17
|
+
from .errors import APIError, APIConnectionError
|
|
18
|
+
from .models import * # noqa: F403
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class RequestOptions:
|
|
23
|
+
"""Per-request options that override client defaults."""
|
|
24
|
+
|
|
25
|
+
#: Additional headers to send with the request.
|
|
26
|
+
#: Set a header to None to remove it.
|
|
27
|
+
headers: dict[str, str | None] | None = None
|
|
28
|
+
|
|
29
|
+
#: Request timeout in seconds. Overrides the client-level timeout.
|
|
30
|
+
timeout: float | None = None
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger("vers_sdk")
|
|
33
|
+
|
|
34
|
+
_LOG_LEVEL_MAP = {
|
|
35
|
+
"debug": logging.DEBUG,
|
|
36
|
+
"info": logging.INFO,
|
|
37
|
+
"warn": logging.WARNING,
|
|
38
|
+
"error": logging.ERROR,
|
|
39
|
+
"off": logging.CRITICAL + 1,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _configure_logger() -> None:
|
|
44
|
+
env_level = os.environ.get("VERS_LOG", "warn").lower()
|
|
45
|
+
level = _LOG_LEVEL_MAP.get(env_level, logging.WARNING)
|
|
46
|
+
logger.setLevel(level)
|
|
47
|
+
if not logger.handlers:
|
|
48
|
+
handler = logging.StreamHandler()
|
|
49
|
+
handler.setFormatter(logging.Formatter("%(asctime)s [vers_sdk %(levelname)s] %(message)s"))
|
|
50
|
+
logger.addHandler(handler)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_configure_logger()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Status codes that are retryable even though they are 4xx
|
|
57
|
+
RETRYABLE_STATUS_CODES = {408, 409, 429}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_retryable_status(status: int) -> bool:
|
|
61
|
+
return status >= 500 or status in RETRYABLE_STATUS_CODES
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _retry_delay(attempt: int) -> float:
|
|
65
|
+
base = 0.5 * (2 ** attempt)
|
|
66
|
+
jitter = random.random() * base * 0.25
|
|
67
|
+
return base + jitter
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _parse_retry_after(header_value: Optional[str]) -> Optional[float]:
|
|
71
|
+
if header_value is None:
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
delay = float(header_value)
|
|
75
|
+
return min(max(delay, 0.0), 60.0)
|
|
76
|
+
except (ValueError, OverflowError):
|
|
77
|
+
pass
|
|
78
|
+
parsed = email.utils.parsedate(header_value)
|
|
79
|
+
if parsed is not None:
|
|
80
|
+
retry_time = calendar.timegm(parsed)
|
|
81
|
+
delay = retry_time - _time.time()
|
|
82
|
+
return min(max(delay, 0.0), 60.0)
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class VersSdkClient:
|
|
87
|
+
"""Client for Orchestrator Control Plane API"""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
base_url: Optional[str] = None,
|
|
92
|
+
api_key: Optional[str] = None,
|
|
93
|
+
headers: Optional[dict[str, str | None]] = None,
|
|
94
|
+
max_retries: int = 2,
|
|
95
|
+
timeout: float = 30.0,
|
|
96
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
97
|
+
):
|
|
98
|
+
self.base_url = base_url or os.environ.get("VERS_BASE_URL", "https://api.vers.sh")
|
|
99
|
+
self.api_key = api_key or os.environ.get("VERS_API_KEY")
|
|
100
|
+
self.max_retries = max_retries
|
|
101
|
+
self.timeout = timeout
|
|
102
|
+
self._custom_headers: dict[str, str | None] = headers or {}
|
|
103
|
+
if http_client is not None:
|
|
104
|
+
self._client = http_client
|
|
105
|
+
else:
|
|
106
|
+
import platform as _platform
|
|
107
|
+
default_headers: dict[str, str] = {
|
|
108
|
+
"User-Agent": f"vers-sdk/0.1.1 python/{_platform.python_version()} {_platform.system().lower()}/{_platform.machine()}",
|
|
109
|
+
}
|
|
110
|
+
if self.api_key:
|
|
111
|
+
default_headers["Authorization"] = f"Bearer {self.api_key}"
|
|
112
|
+
merged = self._merge_headers(default_headers, self._custom_headers)
|
|
113
|
+
self._client = httpx.AsyncClient(
|
|
114
|
+
base_url=self.base_url,
|
|
115
|
+
headers=merged,
|
|
116
|
+
timeout=self.timeout,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def _merge_headers(*sources: Optional[dict[str, str | None]]) -> dict[str, str]:
|
|
121
|
+
"""Merge header dicts. Later sources override earlier ones (case-insensitive).
|
|
122
|
+
A ``None`` value removes the header."""
|
|
123
|
+
merged: dict[str, str] = {}
|
|
124
|
+
for source in sources:
|
|
125
|
+
if source is None:
|
|
126
|
+
continue
|
|
127
|
+
for key, value in source.items():
|
|
128
|
+
if value is None:
|
|
129
|
+
# Remove header (case-insensitive)
|
|
130
|
+
merged = {
|
|
131
|
+
k: v for k, v in merged.items()
|
|
132
|
+
if k.lower() != key.lower()
|
|
133
|
+
}
|
|
134
|
+
else:
|
|
135
|
+
merged[key] = value
|
|
136
|
+
return merged
|
|
137
|
+
|
|
138
|
+
def _check_response(self, response: httpx.Response) -> None:
|
|
139
|
+
if response.is_success:
|
|
140
|
+
return
|
|
141
|
+
body: Any = None
|
|
142
|
+
message: str | None = None
|
|
143
|
+
try:
|
|
144
|
+
body = response.json()
|
|
145
|
+
except Exception:
|
|
146
|
+
message = response.text
|
|
147
|
+
headers = dict(response.headers)
|
|
148
|
+
raise APIError.generate(
|
|
149
|
+
status=response.status_code,
|
|
150
|
+
body=body,
|
|
151
|
+
message=message,
|
|
152
|
+
headers=headers,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
async def _request(self, method: str, path: str, options: RequestOptions | None = None, **kwargs: Any) -> httpx.Response:
|
|
156
|
+
logger.debug("request: %s %s", method, path)
|
|
157
|
+
|
|
158
|
+
req_headers: dict[str, str] = kwargs.pop("headers", {})
|
|
159
|
+
if method.upper() in ("POST", "PUT", "PATCH", "DELETE"):
|
|
160
|
+
req_headers["X-Idempotency-Key"] = str(uuid.uuid4())
|
|
161
|
+
# Apply per-request header overrides
|
|
162
|
+
if options and options.headers:
|
|
163
|
+
for k, v in options.headers.items():
|
|
164
|
+
if v is None:
|
|
165
|
+
req_headers.pop(k, None)
|
|
166
|
+
else:
|
|
167
|
+
req_headers[k] = v
|
|
168
|
+
if req_headers:
|
|
169
|
+
kwargs["headers"] = req_headers
|
|
170
|
+
effective_timeout = options.timeout if options and options.timeout is not None else None
|
|
171
|
+
|
|
172
|
+
last_exc: Optional[Exception] = None
|
|
173
|
+
retry_after_used = False
|
|
174
|
+
for attempt in range(self.max_retries + 1):
|
|
175
|
+
if attempt > 0 and not retry_after_used:
|
|
176
|
+
logger.info("retry attempt %d/%d for %s %s", attempt, self.max_retries, method, path)
|
|
177
|
+
await asyncio.sleep(_retry_delay(attempt - 1))
|
|
178
|
+
retry_after_used = False
|
|
179
|
+
try:
|
|
180
|
+
req_kwargs = dict(kwargs)
|
|
181
|
+
if effective_timeout is not None:
|
|
182
|
+
req_kwargs["timeout"] = effective_timeout
|
|
183
|
+
response = await self._client.request(method, path, **req_kwargs)
|
|
184
|
+
# Remove headers that were set to None
|
|
185
|
+
logger.debug("response: %d for %s %s", response.status_code, method, path)
|
|
186
|
+
if _is_retryable_status(response.status_code) and attempt < self.max_retries:
|
|
187
|
+
ra_delay = _parse_retry_after(response.headers.get("retry-after"))
|
|
188
|
+
if ra_delay is not None:
|
|
189
|
+
retry_after_used = True
|
|
190
|
+
logger.info("retry attempt %d/%d for %s %s (retry-after: %.1fs)", attempt + 1, self.max_retries, method, path, ra_delay)
|
|
191
|
+
await asyncio.sleep(ra_delay)
|
|
192
|
+
last_exc = Exception(f"HTTP {response.status_code}")
|
|
193
|
+
continue
|
|
194
|
+
self._check_response(response)
|
|
195
|
+
return response
|
|
196
|
+
except APIError:
|
|
197
|
+
raise
|
|
198
|
+
except (httpx.ConnectError, httpx.TimeoutException) as exc:
|
|
199
|
+
logger.error("connection error: %s %s - %s", method, path, exc)
|
|
200
|
+
if attempt == self.max_retries:
|
|
201
|
+
raise APIConnectionError(cause=exc) from exc
|
|
202
|
+
last_exc = exc
|
|
203
|
+
raise last_exc # type: ignore[misc]
|
|
204
|
+
|
|
205
|
+
async def resize_vm_disk(self, vm_id: str, body: dict[str, Any], params: ResizeVmDiskParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
206
|
+
""""""
|
|
207
|
+
path = f"/api/v1/vm/{vm_id}/disk"
|
|
208
|
+
query: dict[str, Any] = {}
|
|
209
|
+
if params is not None:
|
|
210
|
+
if params.skip_wait_boot is not None:
|
|
211
|
+
query["skip_wait_boot"] = params.skip_wait_boot
|
|
212
|
+
return await self._request("PATCH", path, options, json=body, params=query)
|
|
213
|
+
|
|
214
|
+
async def exec_vm_stream_attach(self, vm_id: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
215
|
+
""""""
|
|
216
|
+
path = f"/api/v1/vm/{vm_id}/exec/stream/attach"
|
|
217
|
+
return await self._request("POST", path, options, json=body)
|
|
218
|
+
|
|
219
|
+
async def create_new_root_vm(self, body: dict[str, Any], params: CreateNewRootVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
220
|
+
""""""
|
|
221
|
+
path = "/api/v1/vm/new_root"
|
|
222
|
+
query: dict[str, Any] = {}
|
|
223
|
+
if params is not None:
|
|
224
|
+
if params.wait_boot is not None:
|
|
225
|
+
query["wait_boot"] = params.wait_boot
|
|
226
|
+
return await self._request("POST", path, options, json=body, params=query)
|
|
227
|
+
|
|
228
|
+
async def vm_logs(self, vm_id: str, params: VmLogsParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
229
|
+
""""""
|
|
230
|
+
path = f"/api/v1/vm/{vm_id}/logs"
|
|
231
|
+
query: dict[str, Any] = {}
|
|
232
|
+
if params is not None:
|
|
233
|
+
if params.offset is not None:
|
|
234
|
+
query["offset"] = params.offset
|
|
235
|
+
if params.max_entries is not None:
|
|
236
|
+
query["max_entries"] = params.max_entries
|
|
237
|
+
if params.stream is not None:
|
|
238
|
+
query["stream"] = params.stream
|
|
239
|
+
return await self._request("GET", path, options, params=query)
|
|
240
|
+
|
|
241
|
+
async def branch_by_ref(self, repo_name: str, tag_name: str, body: dict[str, Any] | None = None, params: BranchByRefParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
242
|
+
""""""
|
|
243
|
+
path = f"/api/v1/vm/branch/by_ref/{repo_name}/{tag_name}"
|
|
244
|
+
query: dict[str, Any] = {}
|
|
245
|
+
if params is not None:
|
|
246
|
+
if params.count is not None:
|
|
247
|
+
query["count"] = params.count
|
|
248
|
+
return await self._request("POST", path, options, json=body, params=query)
|
|
249
|
+
|
|
250
|
+
async def list_public_commits(self, options: RequestOptions | None = None) -> httpx.Response:
|
|
251
|
+
""""""
|
|
252
|
+
path = "/api/v1/commits/public"
|
|
253
|
+
return await self._request("GET", path, options)
|
|
254
|
+
|
|
255
|
+
async def list_public_repo_tags(self, org_name: str, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
256
|
+
""""""
|
|
257
|
+
path = f"/api/v1/public/repositories/{org_name}/{repo_name}/tags"
|
|
258
|
+
return await self._request("GET", path, options)
|
|
259
|
+
|
|
260
|
+
async def branch_by_tag(self, tag_name: str, body: dict[str, Any] | None = None, params: BranchByTagParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
261
|
+
""""""
|
|
262
|
+
path = f"/api/v1/vm/branch/by_tag/{tag_name}"
|
|
263
|
+
query: dict[str, Any] = {}
|
|
264
|
+
if params is not None:
|
|
265
|
+
if params.count is not None:
|
|
266
|
+
query["count"] = params.count
|
|
267
|
+
return await self._request("POST", path, options, json=body, params=query)
|
|
268
|
+
|
|
269
|
+
async def branch_vm(self, vm_or_commit_id: str, body: dict[str, Any] | None = None, params: BranchVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
270
|
+
""""""
|
|
271
|
+
path = f"/api/v1/vm/{vm_or_commit_id}/branch"
|
|
272
|
+
query: dict[str, Any] = {}
|
|
273
|
+
if params is not None:
|
|
274
|
+
if params.keep_paused is not None:
|
|
275
|
+
query["keep_paused"] = params.keep_paused
|
|
276
|
+
if params.skip_wait_boot is not None:
|
|
277
|
+
query["skip_wait_boot"] = params.skip_wait_boot
|
|
278
|
+
if params.count is not None:
|
|
279
|
+
query["count"] = params.count
|
|
280
|
+
return await self._request("POST", path, options, json=body, params=query)
|
|
281
|
+
|
|
282
|
+
async def get_repo_tag(self, repo_name: str, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
283
|
+
""""""
|
|
284
|
+
path = f"/api/v1/repositories/{repo_name}/tags/{tag_name}"
|
|
285
|
+
return await self._request("GET", path, options)
|
|
286
|
+
|
|
287
|
+
async def delete_repo_tag(self, repo_name: str, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
288
|
+
""""""
|
|
289
|
+
path = f"/api/v1/repositories/{repo_name}/tags/{tag_name}"
|
|
290
|
+
return await self._request("DELETE", path, options)
|
|
291
|
+
|
|
292
|
+
async def update_repo_tag(self, repo_name: str, tag_name: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
293
|
+
""""""
|
|
294
|
+
path = f"/api/v1/repositories/{repo_name}/tags/{tag_name}"
|
|
295
|
+
return await self._request("PATCH", path, options, json=body)
|
|
296
|
+
|
|
297
|
+
async def get_repository(self, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
298
|
+
""""""
|
|
299
|
+
path = f"/api/v1/repositories/{repo_name}"
|
|
300
|
+
return await self._request("GET", path, options)
|
|
301
|
+
|
|
302
|
+
async def delete_repository(self, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
303
|
+
""""""
|
|
304
|
+
path = f"/api/v1/repositories/{repo_name}"
|
|
305
|
+
return await self._request("DELETE", path, options)
|
|
306
|
+
|
|
307
|
+
async def list_parent_commits(self, commit_id: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
308
|
+
""""""
|
|
309
|
+
path = f"/api/v1/vm/commits/{commit_id}/parents"
|
|
310
|
+
return await self._request("GET", path, options)
|
|
311
|
+
|
|
312
|
+
async def update_vm_state(self, vm_id: str, body: dict[str, Any], params: UpdateVmStateParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
313
|
+
""""""
|
|
314
|
+
path = f"/api/v1/vm/{vm_id}/state"
|
|
315
|
+
query: dict[str, Any] = {}
|
|
316
|
+
if params is not None:
|
|
317
|
+
if params.skip_wait_boot is not None:
|
|
318
|
+
query["skip_wait_boot"] = params.skip_wait_boot
|
|
319
|
+
return await self._request("PATCH", path, options, json=body, params=query)
|
|
320
|
+
|
|
321
|
+
async def fork_repository(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
322
|
+
""""""
|
|
323
|
+
path = "/api/v1/repositories/fork"
|
|
324
|
+
return await self._request("POST", path, options, json=body)
|
|
325
|
+
|
|
326
|
+
async def list_repositories(self, options: RequestOptions | None = None) -> httpx.Response:
|
|
327
|
+
""""""
|
|
328
|
+
path = "/api/v1/repositories"
|
|
329
|
+
return await self._request("GET", path, options)
|
|
330
|
+
|
|
331
|
+
async def create_repository(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
332
|
+
""""""
|
|
333
|
+
path = "/api/v1/repositories"
|
|
334
|
+
return await self._request("POST", path, options, json=body)
|
|
335
|
+
|
|
336
|
+
async def restore_from_commit(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
337
|
+
""""""
|
|
338
|
+
path = "/api/v1/vm/from_commit"
|
|
339
|
+
return await self._request("POST", path, options, json=body)
|
|
340
|
+
|
|
341
|
+
async def get_domain(self, domain_id: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
342
|
+
""""""
|
|
343
|
+
path = f"/api/v1/domains/{domain_id}"
|
|
344
|
+
return await self._request("GET", path, options)
|
|
345
|
+
|
|
346
|
+
async def delete_domain(self, domain_id: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
347
|
+
""""""
|
|
348
|
+
path = f"/api/v1/domains/{domain_id}"
|
|
349
|
+
return await self._request("DELETE", path, options)
|
|
350
|
+
|
|
351
|
+
async def list_tags(self, options: RequestOptions | None = None) -> httpx.Response:
|
|
352
|
+
""""""
|
|
353
|
+
path = "/api/v1/commit_tags"
|
|
354
|
+
return await self._request("GET", path, options)
|
|
355
|
+
|
|
356
|
+
async def create_tag(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
357
|
+
""""""
|
|
358
|
+
path = "/api/v1/commit_tags"
|
|
359
|
+
return await self._request("POST", path, options, json=body)
|
|
360
|
+
|
|
361
|
+
async def list_public_repositories(self, options: RequestOptions | None = None) -> httpx.Response:
|
|
362
|
+
""""""
|
|
363
|
+
path = "/api/v1/public/repositories"
|
|
364
|
+
return await self._request("GET", path, options)
|
|
365
|
+
|
|
366
|
+
async def get_public_repo_tag(self, org_name: str, repo_name: str, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
367
|
+
""""""
|
|
368
|
+
path = f"/api/v1/public/repositories/{org_name}/{repo_name}/tags/{tag_name}"
|
|
369
|
+
return await self._request("GET", path, options)
|
|
370
|
+
|
|
371
|
+
async def get_vm_metadata(self, vm_id: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
372
|
+
""""""
|
|
373
|
+
path = f"/api/v1/vm/{vm_id}/metadata"
|
|
374
|
+
return await self._request("GET", path, options)
|
|
375
|
+
|
|
376
|
+
async def list_env_vars(self, options: RequestOptions | None = None) -> httpx.Response:
|
|
377
|
+
""""""
|
|
378
|
+
path = "/api/v1/env_vars"
|
|
379
|
+
return await self._request("GET", path, options)
|
|
380
|
+
|
|
381
|
+
async def set_env_vars(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
382
|
+
""""""
|
|
383
|
+
path = "/api/v1/env_vars"
|
|
384
|
+
return await self._request("PUT", path, options, json=body)
|
|
385
|
+
|
|
386
|
+
async def delete_vm(self, vm_id: str, params: DeleteVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
387
|
+
""""""
|
|
388
|
+
path = f"/api/v1/vm/{vm_id}"
|
|
389
|
+
query: dict[str, Any] = {}
|
|
390
|
+
if params is not None:
|
|
391
|
+
if params.skip_wait_boot is not None:
|
|
392
|
+
query["skip_wait_boot"] = params.skip_wait_boot
|
|
393
|
+
return await self._request("DELETE", path, options, params=query)
|
|
394
|
+
|
|
395
|
+
async def list_commits(self, options: RequestOptions | None = None) -> httpx.Response:
|
|
396
|
+
""""""
|
|
397
|
+
path = "/api/v1/commits"
|
|
398
|
+
return await self._request("GET", path, options)
|
|
399
|
+
|
|
400
|
+
async def get_tag(self, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
401
|
+
""""""
|
|
402
|
+
path = f"/api/v1/commit_tags/{tag_name}"
|
|
403
|
+
return await self._request("GET", path, options)
|
|
404
|
+
|
|
405
|
+
async def delete_tag(self, tag_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
406
|
+
""""""
|
|
407
|
+
path = f"/api/v1/commit_tags/{tag_name}"
|
|
408
|
+
return await self._request("DELETE", path, options)
|
|
409
|
+
|
|
410
|
+
async def update_tag(self, tag_name: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
411
|
+
""""""
|
|
412
|
+
path = f"/api/v1/commit_tags/{tag_name}"
|
|
413
|
+
return await self._request("PATCH", path, options, json=body)
|
|
414
|
+
|
|
415
|
+
async def branch_by_vm(self, vm_id: str, body: dict[str, Any] | None = None, params: BranchByVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
416
|
+
""""""
|
|
417
|
+
path = f"/api/v1/vm/branch/by_vm/{vm_id}"
|
|
418
|
+
query: dict[str, Any] = {}
|
|
419
|
+
if params is not None:
|
|
420
|
+
if params.keep_paused is not None:
|
|
421
|
+
query["keep_paused"] = params.keep_paused
|
|
422
|
+
if params.skip_wait_boot is not None:
|
|
423
|
+
query["skip_wait_boot"] = params.skip_wait_boot
|
|
424
|
+
if params.count is not None:
|
|
425
|
+
query["count"] = params.count
|
|
426
|
+
return await self._request("POST", path, options, json=body, params=query)
|
|
427
|
+
|
|
428
|
+
async def delete_env_var(self, key: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
429
|
+
""""""
|
|
430
|
+
path = f"/api/v1/env_vars/{key}"
|
|
431
|
+
return await self._request("DELETE", path, options)
|
|
432
|
+
|
|
433
|
+
async def exec_vm_stream(self, vm_id: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
434
|
+
""""""
|
|
435
|
+
path = f"/api/v1/vm/{vm_id}/exec/stream"
|
|
436
|
+
return await self._request("POST", path, options, json=body)
|
|
437
|
+
|
|
438
|
+
async def commit_vm(self, vm_id: str, body: dict[str, Any] | None = None, params: CommitVmParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
439
|
+
""""""
|
|
440
|
+
path = f"/api/v1/vm/{vm_id}/commit"
|
|
441
|
+
query: dict[str, Any] = {}
|
|
442
|
+
if params is not None:
|
|
443
|
+
if params.keep_paused is not None:
|
|
444
|
+
query["keep_paused"] = params.keep_paused
|
|
445
|
+
if params.skip_wait_boot is not None:
|
|
446
|
+
query["skip_wait_boot"] = params.skip_wait_boot
|
|
447
|
+
return await self._request("POST", path, options, json=body, params=query)
|
|
448
|
+
|
|
449
|
+
async def delete_commit(self, commit_id: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
450
|
+
""""""
|
|
451
|
+
path = f"/api/v1/commits/{commit_id}"
|
|
452
|
+
return await self._request("DELETE", path, options)
|
|
453
|
+
|
|
454
|
+
async def update_commit(self, commit_id: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
455
|
+
""""""
|
|
456
|
+
path = f"/api/v1/commits/{commit_id}"
|
|
457
|
+
return await self._request("PATCH", path, options, json=body)
|
|
458
|
+
|
|
459
|
+
async def list_repo_tags(self, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
460
|
+
""""""
|
|
461
|
+
path = f"/api/v1/repositories/{repo_name}/tags"
|
|
462
|
+
return await self._request("GET", path, options)
|
|
463
|
+
|
|
464
|
+
async def create_repo_tag(self, repo_name: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
465
|
+
""""""
|
|
466
|
+
path = f"/api/v1/repositories/{repo_name}/tags"
|
|
467
|
+
return await self._request("POST", path, options, json=body)
|
|
468
|
+
|
|
469
|
+
async def ssh_key(self, vm_id: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
470
|
+
""""""
|
|
471
|
+
path = f"/api/v1/vm/{vm_id}/ssh_key"
|
|
472
|
+
return await self._request("GET", path, options)
|
|
473
|
+
|
|
474
|
+
async def exec_vm(self, vm_id: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
475
|
+
""""""
|
|
476
|
+
path = f"/api/v1/vm/{vm_id}/exec"
|
|
477
|
+
return await self._request("POST", path, options, json=body)
|
|
478
|
+
|
|
479
|
+
async def set_repository_visibility(self, repo_name: str, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
480
|
+
""""""
|
|
481
|
+
path = f"/api/v1/repositories/{repo_name}/visibility"
|
|
482
|
+
return await self._request("PATCH", path, options, json=body)
|
|
483
|
+
|
|
484
|
+
async def list_vms(self, options: RequestOptions | None = None) -> httpx.Response:
|
|
485
|
+
""""""
|
|
486
|
+
path = "/api/v1/vms"
|
|
487
|
+
return await self._request("GET", path, options)
|
|
488
|
+
|
|
489
|
+
async def list_domains(self, params: ListDomainsParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
490
|
+
""""""
|
|
491
|
+
path = "/api/v1/domains"
|
|
492
|
+
query: dict[str, Any] = {}
|
|
493
|
+
if params is not None:
|
|
494
|
+
if params.vm_id is not None:
|
|
495
|
+
query["vm_id"] = params.vm_id
|
|
496
|
+
return await self._request("GET", path, options, params=query)
|
|
497
|
+
|
|
498
|
+
async def create_domain(self, body: dict[str, Any], options: RequestOptions | None = None) -> httpx.Response:
|
|
499
|
+
""""""
|
|
500
|
+
path = "/api/v1/domains"
|
|
501
|
+
return await self._request("POST", path, options, json=body)
|
|
502
|
+
|
|
503
|
+
async def vm_status(self, vm_id: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
504
|
+
""""""
|
|
505
|
+
path = f"/api/v1/vm/{vm_id}/status"
|
|
506
|
+
return await self._request("GET", path, options)
|
|
507
|
+
|
|
508
|
+
async def get_public_repository(self, org_name: str, repo_name: str, options: RequestOptions | None = None) -> httpx.Response:
|
|
509
|
+
""""""
|
|
510
|
+
path = f"/api/v1/public/repositories/{org_name}/{repo_name}"
|
|
511
|
+
return await self._request("GET", path, options)
|
|
512
|
+
|
|
513
|
+
async def branch_by_commit(self, commit_id: str, body: dict[str, Any] | None = None, params: BranchByCommitParams | None = None, options: RequestOptions | None = None) -> httpx.Response:
|
|
514
|
+
""""""
|
|
515
|
+
path = f"/api/v1/vm/branch/by_commit/{commit_id}"
|
|
516
|
+
query: dict[str, Any] = {}
|
|
517
|
+
if params is not None:
|
|
518
|
+
if params.count is not None:
|
|
519
|
+
query["count"] = params.count
|
|
520
|
+
return await self._request("POST", path, options, json=body, params=query)
|
|
521
|
+
|
|
522
|
+
async def close(self) -> None:
|
|
523
|
+
await self._client.aclose()
|
|
524
|
+
|
|
525
|
+
async def __aenter__(self) -> "VersSdkClient":
|
|
526
|
+
return self
|
|
527
|
+
|
|
528
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
529
|
+
await self.close()
|
vers_sdk/errors.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Generated by Sterling SDK Generator
|
|
2
|
+
# Orchestrator Control Plane API v0.1.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VersSDKError(Exception):
|
|
10
|
+
"""Base error class for the VersSdk SDK."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class APIError(VersSDKError):
|
|
15
|
+
"""Error returned when the API responds with a non-success status code."""
|
|
16
|
+
|
|
17
|
+
status: int | None
|
|
18
|
+
headers: dict[str, str] | None
|
|
19
|
+
body: Any
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
status: int | None,
|
|
24
|
+
body: Any = None,
|
|
25
|
+
message: str | None = None,
|
|
26
|
+
headers: dict[str, str] | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
self.status = status
|
|
29
|
+
self.headers = headers
|
|
30
|
+
self.body = body
|
|
31
|
+
super().__init__(self._make_message(status, body, message))
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _make_message(status: int | None, body: Any, message: str | None) -> str:
|
|
35
|
+
msg = None
|
|
36
|
+
if isinstance(body, dict) and "message" in body:
|
|
37
|
+
msg = body["message"] if isinstance(body["message"], str) else str(body["message"])
|
|
38
|
+
elif body is not None:
|
|
39
|
+
msg = str(body)
|
|
40
|
+
elif message is not None:
|
|
41
|
+
msg = message
|
|
42
|
+
|
|
43
|
+
if status and msg:
|
|
44
|
+
return f"{status} {msg}"
|
|
45
|
+
if status:
|
|
46
|
+
return f"{status} status code (no body)"
|
|
47
|
+
if msg:
|
|
48
|
+
return msg
|
|
49
|
+
return "(no status code or body)"
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def generate(
|
|
53
|
+
cls,
|
|
54
|
+
status: int | None,
|
|
55
|
+
body: Any = None,
|
|
56
|
+
message: str | None = None,
|
|
57
|
+
headers: dict[str, str] | None = None,
|
|
58
|
+
) -> APIError:
|
|
59
|
+
if status is None or headers is None:
|
|
60
|
+
return APIConnectionError(message=message)
|
|
61
|
+
|
|
62
|
+
error_map: dict[int, type[APIError]] = {
|
|
63
|
+
400: BadRequestError,
|
|
64
|
+
401: AuthenticationError,
|
|
65
|
+
403: PermissionDeniedError,
|
|
66
|
+
404: NotFoundError,
|
|
67
|
+
409: ConflictError,
|
|
68
|
+
422: UnprocessableEntityError,
|
|
69
|
+
429: RateLimitError,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
error_cls = error_map.get(status)
|
|
73
|
+
if error_cls is not None:
|
|
74
|
+
return error_cls(status=status, body=body, message=message, headers=headers)
|
|
75
|
+
if status >= 500:
|
|
76
|
+
return InternalServerError(status=status, body=body, message=message, headers=headers)
|
|
77
|
+
return APIError(status=status, body=body, message=message, headers=headers)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class APIConnectionError(APIError):
|
|
81
|
+
"""Error thrown when a connection to the API cannot be established."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, message: str | None = None, cause: BaseException | None = None) -> None:
|
|
84
|
+
super().__init__(status=None, body=None, message=message or "Connection error.")
|
|
85
|
+
self.__cause__ = cause
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class APIConnectionTimeoutError(APIConnectionError):
|
|
89
|
+
"""Error thrown when a request times out."""
|
|
90
|
+
|
|
91
|
+
def __init__(self, message: str | None = None) -> None:
|
|
92
|
+
super().__init__(message=message or "Request timed out.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BadRequestError(APIError):
|
|
96
|
+
"""400 Bad Request"""
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AuthenticationError(APIError):
|
|
101
|
+
"""401 Unauthorized"""
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class PermissionDeniedError(APIError):
|
|
106
|
+
"""403 Forbidden"""
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class NotFoundError(APIError):
|
|
111
|
+
"""404 Not Found"""
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ConflictError(APIError):
|
|
116
|
+
"""409 Conflict"""
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class UnprocessableEntityError(APIError):
|
|
121
|
+
"""422 Unprocessable Entity"""
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class RateLimitError(APIError):
|
|
126
|
+
"""429 Too Many Requests"""
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class InternalServerError(APIError):
|
|
131
|
+
"""5xx Internal Server Error"""
|
|
132
|
+
pass
|
vers_sdk/models.py
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
# Generated by Sterling SDK Generator
|
|
2
|
+
# Orchestrator Control Plane API v0.1.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Optional, Union
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ForkRepositoryResponse:
|
|
11
|
+
"""Response body for POST /api/v1/repositories/fork"""
|
|
12
|
+
#: The new commit in your org (snapshot of the forked VM)
|
|
13
|
+
commit_id: str
|
|
14
|
+
#: Full reference: repo_name:tag_name
|
|
15
|
+
reference: str
|
|
16
|
+
#: The new repository name in your org
|
|
17
|
+
repo_name: str
|
|
18
|
+
#: The tag name pointing to the forked commit
|
|
19
|
+
tag_name: str
|
|
20
|
+
#: The new VM that was created from the fork
|
|
21
|
+
vm_id: str
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class VmSshKeyResponse:
|
|
25
|
+
"""Response body for GET /api/vm/{vm_id}/ssh_key"""
|
|
26
|
+
#: The SSH port that will be DNAT'd to the VM's netns (and, in turn, to its TAP device)
|
|
27
|
+
ssh_port: int
|
|
28
|
+
#: Private SSH key in stringified OpenSSH format
|
|
29
|
+
ssh_private_key: str
|
|
30
|
+
|
|
31
|
+
class VmState(str, Enum):
|
|
32
|
+
"""The state of a VM"""
|
|
33
|
+
BOOTING = "booting"
|
|
34
|
+
RUNNING = "running"
|
|
35
|
+
PAUSED = "paused"
|
|
36
|
+
SLEEPING = "sleeping"
|
|
37
|
+
DEAD = "dead"
|
|
38
|
+
|
|
39
|
+
class VmUpdateStateEnum(str, Enum):
|
|
40
|
+
"""Possible options for the state requested in PATCH /api/vm/{vm_id}/state"""
|
|
41
|
+
PAUSED = "Paused"
|
|
42
|
+
RUNNING = "Running"
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class VmUpdateStateRequest:
|
|
46
|
+
"""Request body for PATCH /api/vm/{vm_id}/state"""
|
|
47
|
+
#: The requested state for the VM
|
|
48
|
+
state: VmUpdateStateEnum
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class ErrorResponse:
|
|
52
|
+
""""""
|
|
53
|
+
#: Reason of error
|
|
54
|
+
error: Optional[str] = None
|
|
55
|
+
#: Is always: false
|
|
56
|
+
success: Optional[bool] = None
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class UpdateCommitRequest:
|
|
60
|
+
"""Request body for PATCH /commits/{commit_id}"""
|
|
61
|
+
is_public: bool
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class EnvVarsResponse:
|
|
65
|
+
"""Response body for GET /env_vars and PUT /env_vars."""
|
|
66
|
+
#: All environment variables currently set for the authenticated user.
|
|
67
|
+
vars: Any
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class CreateRepositoryResponse:
|
|
71
|
+
"""Response body for POST /api/v1/repositories"""
|
|
72
|
+
#: The name of the repository
|
|
73
|
+
name: str
|
|
74
|
+
#: The ID of the newly created repository
|
|
75
|
+
repo_id: str
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class CreateRepositoryRequest:
|
|
79
|
+
"""Request body for POST /api/v1/repositories"""
|
|
80
|
+
#: Optional description of the repository
|
|
81
|
+
description: Optional[Any] = None
|
|
82
|
+
#: The name of the repository (alphanumeric, hyphens, underscores, dots, 1-64 chars)
|
|
83
|
+
name: str
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class DomainResponse:
|
|
87
|
+
"""Response type for domain operations."""
|
|
88
|
+
created_at: str
|
|
89
|
+
domain: str
|
|
90
|
+
domain_id: str
|
|
91
|
+
vm_id: str
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class PublicRepositoryInfo:
|
|
95
|
+
"""Public repository information (includes owner org name for namespacing)"""
|
|
96
|
+
#: When the repository was created
|
|
97
|
+
created_at: str
|
|
98
|
+
#: Optional description
|
|
99
|
+
description: Optional[Any] = None
|
|
100
|
+
#: Full reference: org_name/repo_name
|
|
101
|
+
full_name: str
|
|
102
|
+
#: The repository name
|
|
103
|
+
name: str
|
|
104
|
+
#: The owning organization's name (namespace)
|
|
105
|
+
org_name: str
|
|
106
|
+
#: The repository's unique identifier
|
|
107
|
+
repo_id: str
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class ListTagsResponse:
|
|
111
|
+
"""Response body for GET /api/v1/commit_tags"""
|
|
112
|
+
#: List of all tags in the user's organization
|
|
113
|
+
tags: list[TagInfo]
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class CreateRepoTagRequest:
|
|
117
|
+
"""Request body for creating a tag within a repository: POST /api/v1/repositories/{repo_name}/tags"""
|
|
118
|
+
#: The commit ID this tag should point to
|
|
119
|
+
commit_id: str
|
|
120
|
+
#: Optional description of what this tag represents
|
|
121
|
+
description: Optional[Any] = None
|
|
122
|
+
#: The tag name (e.g. 'latest', 'v1.0')
|
|
123
|
+
tag_name: str
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class ForkRepositoryRequest:
|
|
127
|
+
"""Request body for POST /api/v1/repositories/fork"""
|
|
128
|
+
#: Name for the new repository in your org (defaults to source_repo if omitted)
|
|
129
|
+
repo_name: Optional[Any] = None
|
|
130
|
+
#: The organization that owns the source public repository
|
|
131
|
+
source_org: str
|
|
132
|
+
#: The source repository name
|
|
133
|
+
source_repo: str
|
|
134
|
+
#: The tag to fork (e.g. 'latest', 'v1.0')
|
|
135
|
+
source_tag: str
|
|
136
|
+
#: Tag name in the new repo (defaults to source_tag if omitted)
|
|
137
|
+
tag_name: Optional[Any] = None
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class VmCommitEntity:
|
|
141
|
+
""""""
|
|
142
|
+
created_at: str
|
|
143
|
+
description: Optional[Any] = None
|
|
144
|
+
#: The commit that this commit's parent VM was started from, if any. Intended to optimize traversing the commit tree.
|
|
145
|
+
grandparent_commit_id: Optional[Any] = None
|
|
146
|
+
id: str
|
|
147
|
+
#: Whether this commit is publicly accessible (readable/restorable by anyone).
|
|
148
|
+
is_public: bool
|
|
149
|
+
name: str
|
|
150
|
+
#: api key id.
|
|
151
|
+
owner_id: str
|
|
152
|
+
#: The VM that this commit was created from, if any.
|
|
153
|
+
parent_vm_id: Optional[Any] = None
|
|
154
|
+
|
|
155
|
+
@dataclass
|
|
156
|
+
class VmExecLogQuery:
|
|
157
|
+
"""Query params for GET /api/vm/{vm_id}/exec/logs"""
|
|
158
|
+
#: Maximum number of entries to return (server applies caps).
|
|
159
|
+
max_entries: Optional[Any] = None
|
|
160
|
+
#: Byte offset into the log file to start reading from.
|
|
161
|
+
offset: Optional[Any] = None
|
|
162
|
+
#: Skip waiting for boot state (mirrors exec).
|
|
163
|
+
skip_wait_boot: Optional[Any] = None
|
|
164
|
+
stream: Optional[Any] = None
|
|
165
|
+
|
|
166
|
+
@dataclass
|
|
167
|
+
class TagInfo:
|
|
168
|
+
"""Tag information returned in list and get operations"""
|
|
169
|
+
#: The commit ID this tag currently points to
|
|
170
|
+
commit_id: str
|
|
171
|
+
#: When the tag was created
|
|
172
|
+
created_at: str
|
|
173
|
+
#: Optional description of what this tag represents
|
|
174
|
+
description: Optional[Any] = None
|
|
175
|
+
#: The tag's unique identifier
|
|
176
|
+
tag_id: str
|
|
177
|
+
#: The name of the tag
|
|
178
|
+
tag_name: str
|
|
179
|
+
#: When the tag was last updated (moved to different commit or description changed)
|
|
180
|
+
updated_at: str
|
|
181
|
+
|
|
182
|
+
@dataclass
|
|
183
|
+
class ListCommitsResponse:
|
|
184
|
+
""""""
|
|
185
|
+
commits: list[CommitInfo]
|
|
186
|
+
limit: int
|
|
187
|
+
offset: int
|
|
188
|
+
total: int
|
|
189
|
+
|
|
190
|
+
@dataclass
|
|
191
|
+
class ListRepoTagsResponse:
|
|
192
|
+
"""Response body for GET /api/v1/repositories/{repo_name}/tags"""
|
|
193
|
+
#: The repository name
|
|
194
|
+
repository: str
|
|
195
|
+
#: List of tags in this repository
|
|
196
|
+
tags: list[RepoTagInfo]
|
|
197
|
+
|
|
198
|
+
@dataclass
|
|
199
|
+
class VmExecLogEntry:
|
|
200
|
+
"""Individual log entry describing emitted stdout/stderr chunk."""
|
|
201
|
+
#: Base64-encoded bytes from stdout/stderr chunk.
|
|
202
|
+
data_b64: str
|
|
203
|
+
exec_id: Optional[Any] = None
|
|
204
|
+
stream: VmExecLogStream
|
|
205
|
+
timestamp: str
|
|
206
|
+
|
|
207
|
+
class VmExecLogStream(str, Enum):
|
|
208
|
+
"""Streams available for exec logging."""
|
|
209
|
+
STDOUT = "stdout"
|
|
210
|
+
STDERR = "stderr"
|
|
211
|
+
|
|
212
|
+
@dataclass
|
|
213
|
+
class VmExecResponse:
|
|
214
|
+
"""Response body for POST /api/vm/{vm_id}/exec"""
|
|
215
|
+
#: Exec identifier associated with this run.
|
|
216
|
+
exec_id: Optional[Any] = None
|
|
217
|
+
#: Exit code returned by the command.
|
|
218
|
+
exit_code: int
|
|
219
|
+
#: UTF-8 decoded stderr (lossy).
|
|
220
|
+
stderr: str
|
|
221
|
+
#: UTF-8 decoded stdout (lossy).
|
|
222
|
+
stdout: str
|
|
223
|
+
|
|
224
|
+
@dataclass
|
|
225
|
+
class VmExecStreamAttachRequest:
|
|
226
|
+
"""Request body for POST /api/vm/{vm_id}/exec/stream/attach"""
|
|
227
|
+
#: Optional cursor to resume from (exclusive). If omitted, the full retained backlog is replayed.
|
|
228
|
+
cursor: Optional[Any] = None
|
|
229
|
+
#: Identifier of the exec stream session to reattach to.
|
|
230
|
+
exec_id: str
|
|
231
|
+
#: Start streaming after the latest retained chunk (ignores cursor).
|
|
232
|
+
from_latest: Optional[Any] = None
|
|
233
|
+
|
|
234
|
+
@dataclass
|
|
235
|
+
class VmExecRequest:
|
|
236
|
+
"""Request body for POST /api/vm/{vm_id}/exec"""
|
|
237
|
+
#: Command and arguments to execute.
|
|
238
|
+
command: list[str]
|
|
239
|
+
#: Optional environment variables to set for the process.
|
|
240
|
+
env: Optional[Any] = None
|
|
241
|
+
#: Optional exec identifier for tracking.
|
|
242
|
+
exec_id: Optional[Any] = None
|
|
243
|
+
#: Optional stdin payload passed to the command.
|
|
244
|
+
stdin: Optional[Any] = None
|
|
245
|
+
#: Timeout in seconds (0 = no timeout).
|
|
246
|
+
timeout_secs: Optional[Any] = None
|
|
247
|
+
#: Optional working directory for the command.
|
|
248
|
+
working_dir: Optional[Any] = None
|
|
249
|
+
|
|
250
|
+
@dataclass
|
|
251
|
+
class NewVmsResponse:
|
|
252
|
+
""""""
|
|
253
|
+
vms: list[NewVmResponse]
|
|
254
|
+
|
|
255
|
+
@dataclass
|
|
256
|
+
class SetRepositoryVisibilityRequest:
|
|
257
|
+
"""Request body for PATCH /api/v1/repositories/{repo_name}/visibility"""
|
|
258
|
+
#: Whether the repository should be publicly visible
|
|
259
|
+
is_public: bool
|
|
260
|
+
|
|
261
|
+
@dataclass
|
|
262
|
+
class UpdateRepoTagRequest:
|
|
263
|
+
"""Request body for PATCH /api/v1/repositories/{repo_name}/tags/{tag_name}"""
|
|
264
|
+
#: Optional new commit ID to move the tag to
|
|
265
|
+
commit_id: Optional[Any] = None
|
|
266
|
+
#: Optional new description for the tag. Send `null` to clear.
|
|
267
|
+
description: Optional[Any] = None
|
|
268
|
+
|
|
269
|
+
@dataclass
|
|
270
|
+
class NewVmResponse:
|
|
271
|
+
"""Response body for new VM requests (new_root, from_commit, branch)"""
|
|
272
|
+
vm_id: str
|
|
273
|
+
|
|
274
|
+
@dataclass
|
|
275
|
+
class ListPublicRepositoriesResponse:
|
|
276
|
+
"""Response body for GET /api/v1/public/repositories"""
|
|
277
|
+
repositories: list[PublicRepositoryInfo]
|
|
278
|
+
|
|
279
|
+
@dataclass
|
|
280
|
+
class CreateTagRequest:
|
|
281
|
+
"""Request body for POST /api/v1/commit_tags"""
|
|
282
|
+
#: The commit ID this tag should point to
|
|
283
|
+
commit_id: str
|
|
284
|
+
#: Optional description of what this tag represents
|
|
285
|
+
description: Optional[Any] = None
|
|
286
|
+
#: The name of the tag (alphanumeric, hyphens, underscores, dots, 1-64 chars)
|
|
287
|
+
tag_name: str
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class FromCommitVmRequestCommitIdVariant:
|
|
291
|
+
"""Request body for POST /api/v1/vm/from_commit"""
|
|
292
|
+
commit_id: str
|
|
293
|
+
|
|
294
|
+
@dataclass
|
|
295
|
+
class FromCommitVmRequestTagNameVariant:
|
|
296
|
+
"""Request body for POST /api/v1/vm/from_commit"""
|
|
297
|
+
tag_name: str
|
|
298
|
+
|
|
299
|
+
@dataclass
|
|
300
|
+
class FromCommitVmRequestRefVariant:
|
|
301
|
+
"""Request body for POST /api/v1/vm/from_commit"""
|
|
302
|
+
ref: str
|
|
303
|
+
|
|
304
|
+
FromCommitVmRequest = Union[FromCommitVmRequestCommitIdVariant, FromCommitVmRequestTagNameVariant, FromCommitVmRequestRefVariant]
|
|
305
|
+
|
|
306
|
+
@dataclass
|
|
307
|
+
class CreateRepoTagResponse:
|
|
308
|
+
"""Response body for POST /api/v1/repositories/{repo_name}/tags"""
|
|
309
|
+
#: The commit ID this tag points to
|
|
310
|
+
commit_id: str
|
|
311
|
+
#: Full reference in image_name:tag format
|
|
312
|
+
reference: str
|
|
313
|
+
#: The ID of the newly created tag
|
|
314
|
+
tag_id: str
|
|
315
|
+
|
|
316
|
+
@dataclass
|
|
317
|
+
class CommitInfo:
|
|
318
|
+
""""""
|
|
319
|
+
commit_id: str
|
|
320
|
+
created_at: str
|
|
321
|
+
description: Optional[Any] = None
|
|
322
|
+
grandparent_commit_id: Optional[Any] = None
|
|
323
|
+
is_public: bool
|
|
324
|
+
name: str
|
|
325
|
+
owner_id: str
|
|
326
|
+
parent_vm_id: Optional[Any] = None
|
|
327
|
+
|
|
328
|
+
@dataclass
|
|
329
|
+
class CreateDomainRequest:
|
|
330
|
+
"""Request body for POST /api/v1/domains"""
|
|
331
|
+
domain: str
|
|
332
|
+
vm_id: str
|
|
333
|
+
|
|
334
|
+
@dataclass
|
|
335
|
+
class DeleteDomainResponse:
|
|
336
|
+
"""Response body for DELETE /api/v1/domains/{domain_id}"""
|
|
337
|
+
domain_id: str
|
|
338
|
+
|
|
339
|
+
@dataclass
|
|
340
|
+
class ListRepositoriesResponse:
|
|
341
|
+
"""Response body for GET /api/v1/repositories"""
|
|
342
|
+
#: List of all repositories in the user's organization
|
|
343
|
+
repositories: list[RepositoryInfo]
|
|
344
|
+
|
|
345
|
+
@dataclass
|
|
346
|
+
class RepositoryInfo:
|
|
347
|
+
"""Repository information returned in list and get operations"""
|
|
348
|
+
#: When the repository was created
|
|
349
|
+
created_at: str
|
|
350
|
+
#: Optional description
|
|
351
|
+
description: Optional[Any] = None
|
|
352
|
+
#: Whether this repository is publicly visible
|
|
353
|
+
is_public: bool
|
|
354
|
+
#: The repository name
|
|
355
|
+
name: str
|
|
356
|
+
#: The repository's unique identifier
|
|
357
|
+
repo_id: str
|
|
358
|
+
|
|
359
|
+
@dataclass
|
|
360
|
+
class UpdateTagRequest:
|
|
361
|
+
"""Request body for PATCH /api/v1/commit_tags/{tag_name} For `description`: - Field absent from JSON → don't change the description - Field present as `null` → clear the description - Field present as `'text'` → set the description to 'text'"""
|
|
362
|
+
#: Optional new commit ID to move the tag to
|
|
363
|
+
commit_id: Optional[Any] = None
|
|
364
|
+
#: Optional new description for the tag. Send `null` to clear an existing description.
|
|
365
|
+
description: Optional[Any] = None
|
|
366
|
+
|
|
367
|
+
@dataclass
|
|
368
|
+
class NewRootRequest:
|
|
369
|
+
""""""
|
|
370
|
+
vm_config: VmCreateVmConfig
|
|
371
|
+
|
|
372
|
+
@dataclass
|
|
373
|
+
class SetEnvVarsRequest:
|
|
374
|
+
"""Request body for PUT /env_vars — sets (upserts) one or more environment variables. # Lifecycle model Environment variables are written to `/etc/environment` inside a VM **once at boot time** via a vsock `WriteFile` request. They are **not** live-synced to running VMs. This is intentional: VMs are ephemeral (create → use → destroy/branch), so env var changes naturally take effect on the next VM. The `replace` flag exists so callers can atomically express 'I want exactly these variables and nothing else' without having to DELETE each stale key individually. Without it, the only way to remove a variable from future VMs is a separate `DELETE /env_vars/{key}` call per key."""
|
|
375
|
+
#: If true, delete all existing variables before writing the new set. This gives 'set exactly these vars' semantics. Default: false (upsert).
|
|
376
|
+
replace: Optional[bool] = None
|
|
377
|
+
#: Key-value pairs to set. Keys must be valid shell identifiers (`^[A-Za-z_][A-Za-z0-9_]*$`, max 256 chars). Values max 8192 chars. When `replace` is false (default): existing keys are overwritten, keys not mentioned are left untouched (upsert semantics). When `replace` is true: all existing variables are deleted first, then only the provided vars are stored (replace-all semantics).
|
|
378
|
+
vars: Any
|
|
379
|
+
|
|
380
|
+
@dataclass
|
|
381
|
+
class VM:
|
|
382
|
+
""""""
|
|
383
|
+
created_at: str
|
|
384
|
+
owner_id: str
|
|
385
|
+
state: VmState
|
|
386
|
+
vm_id: str
|
|
387
|
+
|
|
388
|
+
@dataclass
|
|
389
|
+
class VmCreateVmConfig:
|
|
390
|
+
"""Struct representing configuration options common to all VMs"""
|
|
391
|
+
#: The disk size, in MiB.
|
|
392
|
+
fs_size_mib: Optional[Any] = None
|
|
393
|
+
#: The filesystem base image name. Currently, must be 'default'
|
|
394
|
+
image_name: Optional[Any] = None
|
|
395
|
+
#: The kernel name. Currently, must be 'default.bin'
|
|
396
|
+
kernel_name: Optional[Any] = None
|
|
397
|
+
#: The RAM size, in MiB.
|
|
398
|
+
mem_size_mib: Optional[Any] = None
|
|
399
|
+
#: How many vCPUs to allocate to this VM (and its children)
|
|
400
|
+
vcpu_count: Optional[Any] = None
|
|
401
|
+
|
|
402
|
+
@dataclass
|
|
403
|
+
class VmDeleteResponse:
|
|
404
|
+
"""Response body for DELETE /api/vm/{vm_id}"""
|
|
405
|
+
vm_id: str
|
|
406
|
+
|
|
407
|
+
@dataclass
|
|
408
|
+
class CreateTagResponse:
|
|
409
|
+
"""Response body for POST /api/v1/commit_tags"""
|
|
410
|
+
#: The commit ID this tag points to
|
|
411
|
+
commit_id: str
|
|
412
|
+
#: The ID of the newly created tag
|
|
413
|
+
tag_id: str
|
|
414
|
+
#: The name of the tag
|
|
415
|
+
tag_name: str
|
|
416
|
+
|
|
417
|
+
@dataclass
|
|
418
|
+
class RepoTagInfo:
|
|
419
|
+
"""Tag information within a repository context"""
|
|
420
|
+
#: The commit ID this tag currently points to
|
|
421
|
+
commit_id: str
|
|
422
|
+
#: When the tag was created
|
|
423
|
+
created_at: str
|
|
424
|
+
#: Optional description
|
|
425
|
+
description: Optional[Any] = None
|
|
426
|
+
#: Full reference in image_name:tag format
|
|
427
|
+
reference: str
|
|
428
|
+
#: The tag's unique identifier
|
|
429
|
+
tag_id: str
|
|
430
|
+
#: The tag name
|
|
431
|
+
tag_name: str
|
|
432
|
+
#: When the tag was last updated
|
|
433
|
+
updated_at: str
|
|
434
|
+
|
|
435
|
+
@dataclass
|
|
436
|
+
class VmExecLogResponse:
|
|
437
|
+
"""Response for exec log tail requests."""
|
|
438
|
+
#: Returned log entries.
|
|
439
|
+
entries: list[VmExecLogEntry]
|
|
440
|
+
#: True when the end of file was reached.
|
|
441
|
+
eof: bool
|
|
442
|
+
#: Next byte offset to continue from.
|
|
443
|
+
next_offset: int
|
|
444
|
+
|
|
445
|
+
@dataclass
|
|
446
|
+
class VmMetadataResponse:
|
|
447
|
+
"""Response for GET /api/v1/vm/{vm_id}/metadata"""
|
|
448
|
+
created_at: str
|
|
449
|
+
deleted_at: Optional[Any] = None
|
|
450
|
+
grandparent_vm_id: Optional[Any] = None
|
|
451
|
+
ip: str
|
|
452
|
+
owner_id: str
|
|
453
|
+
parent_commit_id: Optional[Any] = None
|
|
454
|
+
state: VmState
|
|
455
|
+
vm_id: str
|
|
456
|
+
|
|
457
|
+
@dataclass
|
|
458
|
+
class VmResizeDiskRequest:
|
|
459
|
+
"""Request body for PATCH /api/vm/{vm_id}/disk"""
|
|
460
|
+
#: The new disk size in MiB. Must be strictly greater than the current size.
|
|
461
|
+
fs_size_mib: int
|
|
462
|
+
|
|
463
|
+
@dataclass
|
|
464
|
+
class VmCommitResponse:
|
|
465
|
+
"""The response body for POST /api/vm/{vm_id}/commit"""
|
|
466
|
+
#: The UUID of the newly-created commit
|
|
467
|
+
commit_id: str
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
# ── Params types for operations with query parameters ───────────────
|
|
471
|
+
|
|
472
|
+
@dataclass
|
|
473
|
+
class ResizeVmDiskParams:
|
|
474
|
+
"""If true, return an error immediately if the VM is still booting. Default: false"""
|
|
475
|
+
skip_wait_boot: Optional[bool] = None
|
|
476
|
+
|
|
477
|
+
@dataclass
|
|
478
|
+
class CreateNewRootVmParams:
|
|
479
|
+
"""If true, wait for the newly-created VM to finish booting before returning. Default: false."""
|
|
480
|
+
wait_boot: Optional[bool] = None
|
|
481
|
+
|
|
482
|
+
@dataclass
|
|
483
|
+
class VmLogsParams:
|
|
484
|
+
"""Byte offset into the log file (default: 0)"""
|
|
485
|
+
offset: Optional[int] = None
|
|
486
|
+
"""Maximum number of log entries to return"""
|
|
487
|
+
max_entries: Optional[int] = None
|
|
488
|
+
"""Filter by 'stdout' or 'stderr'"""
|
|
489
|
+
stream: Optional[str] = None
|
|
490
|
+
|
|
491
|
+
@dataclass
|
|
492
|
+
class BranchByRefParams:
|
|
493
|
+
"""Number of VMs to branch (optional; default 1)"""
|
|
494
|
+
count: Optional[int] = None
|
|
495
|
+
|
|
496
|
+
@dataclass
|
|
497
|
+
class BranchByTagParams:
|
|
498
|
+
"""Number of VMs to branch (optional; default 1)"""
|
|
499
|
+
count: Optional[int] = None
|
|
500
|
+
|
|
501
|
+
@dataclass
|
|
502
|
+
class BranchVmParams:
|
|
503
|
+
"""If true, keep VM paused after commit. Only applicable when branching a VM ID."""
|
|
504
|
+
keep_paused: Optional[bool] = None
|
|
505
|
+
"""If true, immediately return an error if VM is booting instead of waiting. Only applicable when branching a VM ID."""
|
|
506
|
+
skip_wait_boot: Optional[bool] = None
|
|
507
|
+
"""Number of VMs to branch (optional; default 1)"""
|
|
508
|
+
count: Optional[int] = None
|
|
509
|
+
|
|
510
|
+
@dataclass
|
|
511
|
+
class UpdateVmStateParams:
|
|
512
|
+
"""If true, error immediately if the VM is not finished booting. Defaults to false"""
|
|
513
|
+
skip_wait_boot: Optional[bool] = None
|
|
514
|
+
|
|
515
|
+
@dataclass
|
|
516
|
+
class DeleteVmParams:
|
|
517
|
+
"""If true, return an error immediately if the VM is still booting. Default: false"""
|
|
518
|
+
skip_wait_boot: Optional[bool] = None
|
|
519
|
+
|
|
520
|
+
@dataclass
|
|
521
|
+
class BranchByVmParams:
|
|
522
|
+
"""If true, keep VM paused after commit"""
|
|
523
|
+
keep_paused: Optional[bool] = None
|
|
524
|
+
"""If true, immediately return an error if VM is booting instead of waiting"""
|
|
525
|
+
skip_wait_boot: Optional[bool] = None
|
|
526
|
+
"""Number of VMs to branch (optional; default 1)"""
|
|
527
|
+
count: Optional[int] = None
|
|
528
|
+
|
|
529
|
+
@dataclass
|
|
530
|
+
class CommitVmParams:
|
|
531
|
+
"""If true, keep VM paused after commit"""
|
|
532
|
+
keep_paused: Optional[bool] = None
|
|
533
|
+
"""If true, return an error immediately if the VM is still booting. Default: false"""
|
|
534
|
+
skip_wait_boot: Optional[bool] = None
|
|
535
|
+
|
|
536
|
+
@dataclass
|
|
537
|
+
class ListDomainsParams:
|
|
538
|
+
"""Filter by VM ID"""
|
|
539
|
+
vm_id: Optional[str] = None
|
|
540
|
+
|
|
541
|
+
@dataclass
|
|
542
|
+
class BranchByCommitParams:
|
|
543
|
+
"""Number of VMs to branch (optional; default 1)"""
|
|
544
|
+
count: Optional[int] = None
|
|
545
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vers-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: SDK for Orchestrator Control Plane API
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/hdresearch/python-sdk
|
|
7
|
+
Project-URL: Repository, https://github.com/hdresearch/python-sdk
|
|
8
|
+
Keywords: vers,sdk,api,vm,orchestration
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: httpx>=0.24
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
vers_sdk/__init__.py,sha256=ugY30LkuyG5tchECMvhqHSsvtvoWVncOts2VS1uOlE4,743
|
|
2
|
+
vers_sdk/client.py,sha256=43PcyE5-6wpysExLYh9OTIKUmVVET-xMHDVh_QvuIh4,23419
|
|
3
|
+
vers_sdk/errors.py,sha256=riTyZFMt5rYVA960lODYuKSzym3EfjfSrmMyLXcD2p4,3521
|
|
4
|
+
vers_sdk/models.py,sha256=4JwwAiWbzU7HQtmxxLkHDUKWjD45w7vAEQbdecQPrSs,17565
|
|
5
|
+
vers_sdk-0.1.1.dist-info/METADATA,sha256=9Av1nDxBP34fM4OsCGDX4QqVzWUJ5_-_gDKON38KsAE,345
|
|
6
|
+
vers_sdk-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
vers_sdk-0.1.1.dist-info/top_level.txt,sha256=hE4Q_rjEOx9L3TbgqRzMTH5kz8BsvZU4sVUqmaG8Fh0,9
|
|
8
|
+
vers_sdk-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vers_sdk
|