squarepeg 0.2.0__tar.gz → 0.2.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.
- {squarepeg-0.2.0 → squarepeg-0.2.1}/PKG-INFO +1 -1
- {squarepeg-0.2.0 → squarepeg-0.2.1}/pyproject.toml +1 -1
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/runner.py +49 -17
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg.egg-info/PKG-INFO +1 -1
- {squarepeg-0.2.0 → squarepeg-0.2.1}/README.md +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/setup.cfg +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/__init__.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/__main__.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/cli.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/config.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/dockerargs.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/env_interpolation.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/errors.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/__init__.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/apierrors.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/dryrun.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/events.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/logs.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/orphans.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/podstate.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/k8s/session.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/labels.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/log.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/manifest.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/merge.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/naming.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/quantities.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/runspec.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/ui.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg/volumes.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg.egg-info/SOURCES.txt +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg.egg-info/dependency_links.txt +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg.egg-info/entry_points.txt +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg.egg-info/requires.txt +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/squarepeg.egg-info/top_level.txt +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_apierrors.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_cli.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_config.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_dockerargs.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_dryrun.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_env_interpolation.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_events.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_integration.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_logs.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_manifest.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_merge.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_naming.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_orphans.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_podstate.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_quantities.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_runner.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_session.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_ui.py +0 -0
- {squarepeg-0.2.0 → squarepeg-0.2.1}/tests/test_volumes.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "squarepeg"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Run a docker-run-style command as a Kubernetes Pod or Job, streaming its output like a local process"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -22,9 +22,9 @@ from squarepeg.runspec import RunSpec
|
|
|
22
22
|
# kept as module-level aliases: existing callers/tests referencing these names still work
|
|
23
23
|
_wrap_api_exception = wrap_api_exception
|
|
24
24
|
|
|
25
|
-
# how long a single watch call may block waiting for the pod to become terminal,
|
|
26
|
-
#
|
|
27
|
-
TERMINAL_WATCH_POLL_SECONDS =
|
|
25
|
+
# how long a single watch call may block waiting for the pod to become terminal, once it
|
|
26
|
+
# has already started; the loop simply re-watches if this elapses.
|
|
27
|
+
TERMINAL_WATCH_POLL_SECONDS = 5
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def create_resource(session, spec: RunSpec, manifest: dict) -> None:
|
|
@@ -49,13 +49,17 @@ def discover_job_pod(session, job_name: str, timeout: int) -> str:
|
|
|
49
49
|
):
|
|
50
50
|
return event["object"].metadata.name
|
|
51
51
|
except ApiException as exc:
|
|
52
|
-
raise _wrap_api_exception(
|
|
52
|
+
raise _wrap_api_exception(
|
|
53
|
+
exc, f"failed to watch for the pod created by job {job_name!r}"
|
|
54
|
+
) from exc
|
|
53
55
|
finally:
|
|
54
56
|
watcher.stop()
|
|
55
57
|
raise RunnerError(f"timed out waiting for job {job_name!r} to create a pod")
|
|
56
58
|
|
|
57
59
|
|
|
58
|
-
def wait_until_running_or_terminal(
|
|
60
|
+
def wait_until_running_or_terminal(
|
|
61
|
+
session, pod_name: str, timeout: int, *, quiet: bool = False
|
|
62
|
+
) -> str:
|
|
59
63
|
"""Wait until the pod is Running/Succeeded/Failed, or fail fast on a known-bad waiting reason.
|
|
60
64
|
|
|
61
65
|
This bounds only the *startup* window (image pull, scheduling, container create); once the
|
|
@@ -82,17 +86,23 @@ def _watch_for_start(session, pod_name: str, timeout: int) -> str:
|
|
|
82
86
|
for cs in pod.status.container_statuses or []:
|
|
83
87
|
waiting = cs.state.waiting
|
|
84
88
|
if waiting is not None and waiting.reason in FAIL_FAST_REASONS:
|
|
85
|
-
raise RunnerError(
|
|
89
|
+
raise RunnerError(
|
|
90
|
+
f"container failed to start ({waiting.reason}): {waiting.message}"
|
|
91
|
+
)
|
|
86
92
|
if phase in ("Running", "Succeeded", "Failed"):
|
|
87
93
|
return phase
|
|
88
94
|
except ApiException as exc:
|
|
89
95
|
raise _wrap_api_exception(exc, f"failed to watch pod {pod_name!r}") from exc
|
|
90
96
|
finally:
|
|
91
97
|
watcher.stop()
|
|
92
|
-
raise RunnerError(
|
|
98
|
+
raise RunnerError(
|
|
99
|
+
f"timed out after {timeout}s waiting for pod {pod_name!r} to start"
|
|
100
|
+
)
|
|
93
101
|
|
|
94
102
|
|
|
95
|
-
def wait_for_terminal(
|
|
103
|
+
def wait_for_terminal(
|
|
104
|
+
session, pod_name: str, stop_event: threading.Event
|
|
105
|
+
) -> str | None:
|
|
96
106
|
"""Wait, with no overall timeout, until the pod is Succeeded/Failed, or stop_event is set."""
|
|
97
107
|
while not stop_event.is_set():
|
|
98
108
|
watcher = watch.Watch()
|
|
@@ -116,7 +126,9 @@ def wait_for_terminal(session, pod_name: str, stop_event: threading.Event) -> st
|
|
|
116
126
|
return None
|
|
117
127
|
|
|
118
128
|
|
|
119
|
-
def extract_exit_code(
|
|
129
|
+
def extract_exit_code(
|
|
130
|
+
session, pod_name: str, container_name: str
|
|
131
|
+
) -> tuple[int, str | None]:
|
|
120
132
|
try:
|
|
121
133
|
pod = session.core.read_namespaced_pod(pod_name, session.namespace)
|
|
122
134
|
except ApiException as exc:
|
|
@@ -168,7 +180,9 @@ class _InterruptHandler:
|
|
|
168
180
|
level="warn",
|
|
169
181
|
)
|
|
170
182
|
else:
|
|
171
|
-
chatter(
|
|
183
|
+
chatter(
|
|
184
|
+
f"leaving {self.mode} {self.name!r} running", quiet=False, level="warn"
|
|
185
|
+
)
|
|
172
186
|
|
|
173
187
|
@property
|
|
174
188
|
def interrupted(self) -> bool:
|
|
@@ -186,7 +200,10 @@ def run_manifest(session, spec: RunSpec, manifest: dict) -> int:
|
|
|
186
200
|
with _InterruptHandler(name, spec.mode, spec.quiet) as handler:
|
|
187
201
|
log_thread = None
|
|
188
202
|
try:
|
|
189
|
-
with ui.Status(
|
|
203
|
+
with ui.Status(
|
|
204
|
+
f"creating {spec.mode} {name!r} in namespace {session.namespace!r}",
|
|
205
|
+
quiet=spec.quiet,
|
|
206
|
+
):
|
|
190
207
|
create_resource(session, spec, manifest)
|
|
191
208
|
|
|
192
209
|
if spec.mode == "job":
|
|
@@ -207,7 +224,9 @@ def run_manifest(session, spec: RunSpec, manifest: dict) -> int:
|
|
|
207
224
|
slow_after=20,
|
|
208
225
|
spinner_delay=0.0,
|
|
209
226
|
):
|
|
210
|
-
phase = wait_until_running_or_terminal(
|
|
227
|
+
phase = wait_until_running_or_terminal(
|
|
228
|
+
session, pod_name, spec.timeout, quiet=spec.quiet
|
|
229
|
+
)
|
|
211
230
|
|
|
212
231
|
chatter(
|
|
213
232
|
f"streaming logs from pod {pod_name!r} (waiting for it to finish; Ctrl+C to stop)",
|
|
@@ -243,9 +262,15 @@ def run_manifest(session, spec: RunSpec, manifest: dict) -> int:
|
|
|
243
262
|
if handler.interrupted:
|
|
244
263
|
raise InterruptError(f"interrupted while running {spec.mode} {name!r}")
|
|
245
264
|
|
|
246
|
-
exit_code, reason = extract_exit_code(
|
|
265
|
+
exit_code, reason = extract_exit_code(
|
|
266
|
+
session, pod_name, spec.container_name
|
|
267
|
+
)
|
|
247
268
|
if reason == "OOMKilled":
|
|
248
|
-
chatter(
|
|
269
|
+
chatter(
|
|
270
|
+
f"container was OOMKilled (exit code {exit_code})",
|
|
271
|
+
quiet=spec.quiet,
|
|
272
|
+
level="error",
|
|
273
|
+
)
|
|
249
274
|
return exit_code
|
|
250
275
|
finally:
|
|
251
276
|
handler.stop_event.set()
|
|
@@ -261,9 +286,16 @@ def run_manifest(session, spec: RunSpec, manifest: dict) -> int:
|
|
|
261
286
|
level="warn",
|
|
262
287
|
)
|
|
263
288
|
if spec.orphan_sweep and not handler.abandoned:
|
|
264
|
-
own_run_id =
|
|
289
|
+
own_run_id = (
|
|
290
|
+
manifest.get("metadata", {}).get("labels", {}).get(RUN_ID_LABEL)
|
|
291
|
+
)
|
|
265
292
|
try:
|
|
266
|
-
with ui.Status(
|
|
293
|
+
with ui.Status(
|
|
294
|
+
"sweeping orphaned resources from previous runs",
|
|
295
|
+
quiet=spec.quiet,
|
|
296
|
+
):
|
|
267
297
|
sweep_orphans(session, spec, exclude_run_id=own_run_id)
|
|
268
298
|
except Exception as exc:
|
|
269
|
-
chatter(
|
|
299
|
+
chatter(
|
|
300
|
+
f"orphan sweep skipped: {exc}", quiet=spec.quiet, level="warn"
|
|
301
|
+
)
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|