primitive 0.2.46__tar.gz → 0.2.48__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.
- {primitive-0.2.46 → primitive-0.2.48}/PKG-INFO +1 -1
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/__about__.py +1 -1
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/agent/runner.py +29 -119
- {primitive-0.2.46 → primitive-0.2.48}/.git-hooks/pre-commit +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/.gitattributes +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/.github/workflows/lint.yml +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/.github/workflows/publish.yml +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/.github/workflows/pyright.yml +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/.gitignore +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/.vscode/extensions.json +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/.vscode/settings.json +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/LICENSE.txt +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/Makefile +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/README.md +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/linux setup.md +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/pyproject.toml +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/agent/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/agent/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/agent/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/agent/uploader.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/auth/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/auth/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/auth/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/auth/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/auth/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/cli.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/client.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/daemons/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/daemons/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/daemons/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/daemons/launch_agents.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/daemons/launch_service.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/daemons/ui.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/exec/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/exec/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/exec/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/exec/interactive.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/files/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/files/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/files/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/files/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/files/graphql/fragments.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/files/graphql/mutations.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/files/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/git/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/git/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/git/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/git/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/git/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/graphql/relay.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/graphql/sdk.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/graphql/utility_fragments.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/android.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/graphql/fragments.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/graphql/mutations.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/hardware/ui.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/jobs/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/jobs/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/jobs/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/jobs/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/jobs/graphql/fragments.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/jobs/graphql/mutations.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/jobs/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/monitor/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/monitor/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/organizations/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/organizations/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/organizations/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/organizations/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/organizations/graphql/fragments.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/organizations/graphql/mutations.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/organizations/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/projects/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/projects/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/projects/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/projects/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/projects/graphql/fragments.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/projects/graphql/mutations.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/projects/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/provisioning/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/provisioning/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/provisioning/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/provisioning/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/reservations/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/reservations/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/reservations/commands.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/reservations/graphql/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/reservations/graphql/fragments.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/reservations/graphql/mutations.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/reservations/graphql/queries.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/actions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/auth.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/cache.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/chunk_size.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/config.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/daemons.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/exceptions.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/logging.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/memory_size.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/printer.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/psutil.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/shell.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/src/primitive/utils/text.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/tests/__init__.py +0 -0
- {primitive-0.2.46 → primitive-0.2.48}/uv.lock +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: primitive
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.48
|
4
4
|
Project-URL: Documentation, https://github.com//primitivecorp/primitive-cli#readme
|
5
5
|
Project-URL: Issues, https://github.com//primitivecorp/primitive-cli/issues
|
6
6
|
Project-URL: Source, https://github.com//primitivecorp/primitive-cli
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import asyncio
|
2
2
|
import os
|
3
|
-
import re
|
4
3
|
import shutil
|
5
4
|
import typing
|
6
5
|
from enum import Enum, IntEnum
|
@@ -13,7 +12,6 @@ from loguru import logger
|
|
13
12
|
from primitive.utils.cache import get_artifacts_cache, get_logs_cache, get_sources_cache
|
14
13
|
from primitive.utils.logging import fmt, log_context
|
15
14
|
from primitive.utils.psutil import kill_process_and_children
|
16
|
-
from primitive.utils.shell import env_to_dict
|
17
15
|
|
18
16
|
try:
|
19
17
|
from yaml import CLoader as Loader
|
@@ -23,13 +21,7 @@ except ImportError:
|
|
23
21
|
if typing.TYPE_CHECKING:
|
24
22
|
import primitive.client
|
25
23
|
|
26
|
-
|
27
|
-
ENV_VAR_LOOKUP_START = "_ENV_VAR_LOOKUP_START"
|
28
|
-
START_DELIMITER_SIZE = len(bytes(ENV_VAR_LOOKUP_START, encoding="utf-8"))
|
29
|
-
ENV_VAR_LOOKUP_END = "_ENV_VAR_LOOKUP_END"
|
30
|
-
END_DELIMITER_SIZE = len(bytes(ENV_VAR_LOOKUP_END, encoding="utf-8"))
|
31
|
-
|
32
|
-
assert CHUNK_SIZE > START_DELIMITER_SIZE + END_DELIMITER_SIZE
|
24
|
+
BUFFER_SIZE = 4096
|
33
25
|
|
34
26
|
|
35
27
|
class Task(TypedDict):
|
@@ -69,7 +61,6 @@ class Runner:
|
|
69
61
|
self.job_run = job_run
|
70
62
|
self.job_settings = job_run["jobSettings"]
|
71
63
|
self.config = None
|
72
|
-
self.source_dir: Path | None = None
|
73
64
|
self.initial_env = {}
|
74
65
|
self.modified_env = {}
|
75
66
|
self.file_logger = None
|
@@ -140,6 +131,11 @@ class Runner:
|
|
140
131
|
self.job_run["gitCommit"]["repoFullName"]
|
141
132
|
)
|
142
133
|
|
134
|
+
if "VIRTUAL_ENV" in self.initial_env:
|
135
|
+
del self.initial_env["VIRTUAL_ENV"]
|
136
|
+
if "SHELL" in self.initial_env:
|
137
|
+
self.initial_env["SHELL"] = "/bin/bash"
|
138
|
+
|
143
139
|
@log_context(label="execute")
|
144
140
|
def execute_job_run(self) -> None:
|
145
141
|
self.modified_env = {**self.initial_env}
|
@@ -253,13 +249,11 @@ class Runner:
|
|
253
249
|
# Adding an additional echo and utilizing stdbuf to force line buffering
|
254
250
|
# This ensures that the environment variables and starting delimiter are
|
255
251
|
# always in a new chunk, vastly simplifying our parsing logic
|
256
|
-
args = [
|
257
|
-
"/bin/bash",
|
258
|
-
"-c",
|
259
|
-
f"{cmd} && echo -n '{ENV_VAR_LOOKUP_START}' && env && echo -n '{ENV_VAR_LOOKUP_END}'",
|
260
|
-
]
|
252
|
+
args = ["/bin/bash", "-c", cmd]
|
261
253
|
|
262
|
-
logger.info(
|
254
|
+
logger.info(
|
255
|
+
f"Executing command {i + 1}/{len(commands)}: {cmd} at {self.source_dir / task.get('workdir', '')}"
|
256
|
+
)
|
263
257
|
|
264
258
|
process = await asyncio.create_subprocess_exec(
|
265
259
|
*args,
|
@@ -281,13 +275,9 @@ class Runner:
|
|
281
275
|
kill_process_and_children(pid=process.pid)
|
282
276
|
return False
|
283
277
|
|
284
|
-
|
285
|
-
self.log_cmd(
|
286
|
-
|
287
|
-
),
|
288
|
-
self.log_cmd(
|
289
|
-
process=process, stream=process.stderr, tags=task.get("tags", {})
|
290
|
-
),
|
278
|
+
await asyncio.gather(
|
279
|
+
self.log_cmd(stream=process.stdout, level=LogLevel.INFO),
|
280
|
+
self.log_cmd(stream=process.stderr, level=LogLevel.ERROR),
|
291
281
|
)
|
292
282
|
|
293
283
|
returncode = await process.wait()
|
@@ -301,106 +291,26 @@ class Runner:
|
|
301
291
|
f"Task {task['label']} failed on '{cmd}' with return code {returncode}"
|
302
292
|
)
|
303
293
|
return True
|
304
|
-
elif stdout_failed or stderr_failed:
|
305
|
-
logger.error(f"Task {task['label']} failed on '{cmd}'")
|
306
|
-
return True
|
307
294
|
|
308
295
|
logger.success(f"Completed {task['label']} task")
|
309
296
|
return False
|
310
297
|
|
311
|
-
async def log_cmd(
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
environment_buffer = full_chunk[start_index + START_DELIMITER_SIZE :]
|
328
|
-
processed_lines = await self.read_chunk(full_chunk[:start_index])
|
329
|
-
parse_environment = True
|
330
|
-
else:
|
331
|
-
processed_lines = await self.read_chunk(full_chunk)
|
332
|
-
|
333
|
-
while (
|
334
|
-
len(last_chunk_buffer) < START_DELIMITER_SIZE
|
335
|
-
and len(processed_lines) > 0
|
336
|
-
):
|
337
|
-
last_chunk_buffer += bytes(
|
338
|
-
processed_lines.pop() + "\n", encoding="utf-8"
|
339
|
-
)
|
340
|
-
|
341
|
-
# Handle logging
|
342
|
-
parse_logs = self.job_settings["parseLogs"]
|
343
|
-
parse_stderr = self.job_settings["parseStderr"]
|
344
|
-
|
345
|
-
for line in processed_lines:
|
346
|
-
level = LogLevel.INFO
|
347
|
-
tag = None
|
348
|
-
if (parse_logs and "error" in line.lower()) or (
|
349
|
-
parse_stderr and stream is process.stderr
|
350
|
-
):
|
351
|
-
level = LogLevel.ERROR
|
352
|
-
elif parse_logs and "warning" in line.lower():
|
353
|
-
level = LogLevel.WARNING
|
354
|
-
|
355
|
-
# If we already detected a failure, skip checking
|
356
|
-
if not failure_detected:
|
357
|
-
failure_detected = (
|
358
|
-
level == LogLevel.ERROR
|
359
|
-
and self.job_settings["failureLevel"] >= FailureLevel.ERROR
|
360
|
-
) or (
|
361
|
-
level == LogLevel.WARNING
|
362
|
-
and self.job_settings["failureLevel"] >= FailureLevel.WARNING
|
363
|
-
)
|
364
|
-
|
365
|
-
# Tag on the first matching regex in the list
|
366
|
-
for tag_key, regex in tags.items():
|
367
|
-
pattern = re.compile(regex)
|
368
|
-
if pattern.match(line):
|
369
|
-
tag = tag_key
|
370
|
-
break
|
371
|
-
|
372
|
-
logger.bind(tag=tag).log(level.value, line)
|
373
|
-
|
374
|
-
start_index = environment_buffer.find(
|
375
|
-
bytes(ENV_VAR_LOOKUP_END, encoding="utf-8")
|
376
|
-
)
|
377
|
-
if parse_environment and start_index == -1:
|
378
|
-
logger.error("Environment variable buffer did not contain end delimiter")
|
379
|
-
failure_detected = True
|
380
|
-
return failure_detected
|
381
|
-
|
382
|
-
environment_buffer = environment_buffer[:start_index]
|
383
|
-
new_env_vars = env_to_dict(environment_buffer)
|
384
|
-
if len(new_env_vars.keys()) > 0:
|
385
|
-
self.modified_env = {**self.modified_env, **new_env_vars}
|
386
|
-
return failure_detected
|
387
|
-
|
388
|
-
async def read_chunk(self, chunk: bytes) -> List[str]:
|
389
|
-
"""Converts a chunk of bytes into proper lines."""
|
390
|
-
lines = []
|
391
|
-
current_line = ""
|
392
|
-
for char in chunk.decode():
|
393
|
-
if char in ("\n", "\r"):
|
394
|
-
lines.append(current_line)
|
395
|
-
current_line = ""
|
396
|
-
else:
|
397
|
-
current_line += char
|
398
|
-
|
399
|
-
# Handle extra characters in the buffer
|
400
|
-
if len(current_line) > 0:
|
401
|
-
lines.append(current_line)
|
402
|
-
|
403
|
-
return [line for line in lines if len(line) > 0]
|
298
|
+
async def log_cmd(
|
299
|
+
self,
|
300
|
+
stream: asyncio.StreamReader | None,
|
301
|
+
level: LogLevel,
|
302
|
+
):
|
303
|
+
buffer = bytearray()
|
304
|
+
while stream and not stream.at_eof():
|
305
|
+
chunk = await stream.read(BUFFER_SIZE)
|
306
|
+
if not chunk:
|
307
|
+
break
|
308
|
+
buffer += chunk
|
309
|
+
while b"\n" in buffer:
|
310
|
+
line, _, buffer = buffer.partition(b"\n")
|
311
|
+
logger.log(level.value, line.decode(errors="replace"))
|
312
|
+
if buffer:
|
313
|
+
logger.log(level.value, buffer.decode(errors="replace"))
|
404
314
|
|
405
315
|
@log_context(label="cleanup")
|
406
316
|
def cleanup(self) -> None:
|
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
|
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
|
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
|