primitive 0.2.2__tar.gz → 0.2.4__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.2 → primitive-0.2.4}/PKG-INFO +1 -1
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/__about__.py +1 -1
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/agent/runner.py +86 -38
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/git/actions.py +5 -5
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/shell.py +9 -3
- {primitive-0.2.2 → primitive-0.2.4}/.git-hooks/pre-commit +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/.gitattributes +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/.github/workflows/lint.yml +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/.github/workflows/publish.yml +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/.gitignore +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/.vscode/settings.json +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/LICENSE.txt +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/Makefile +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/README.md +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/linux setup.md +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/pyproject.toml +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/agent/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/agent/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/agent/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/agent/uploader.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/auth/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/auth/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/auth/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/auth/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/auth/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/cli.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/client.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/daemons/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/daemons/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/daemons/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/daemons/launch_agents.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/daemons/launch_service.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/exec/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/exec/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/exec/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/exec/interactive.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/files/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/files/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/files/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/files/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/files/graphql/fragments.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/files/graphql/mutations.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/files/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/git/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/git/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/git/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/git/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/graphql/relay.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/graphql/sdk.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/graphql/utility_fragments.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/hardware/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/hardware/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/hardware/android.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/hardware/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/hardware/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/hardware/graphql/fragments.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/hardware/graphql/mutations.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/hardware/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/jobs/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/jobs/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/jobs/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/jobs/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/jobs/graphql/fragments.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/jobs/graphql/mutations.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/jobs/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/organizations/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/organizations/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/organizations/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/organizations/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/organizations/graphql/fragments.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/organizations/graphql/mutations.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/organizations/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/projects/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/projects/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/projects/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/projects/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/projects/graphql/fragments.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/projects/graphql/mutations.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/projects/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/provisioning/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/provisioning/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/provisioning/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/provisioning/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/reservations/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/reservations/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/reservations/commands.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/reservations/graphql/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/reservations/graphql/fragments.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/reservations/graphql/mutations.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/reservations/graphql/queries.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/actions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/auth.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/cache.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/chunk_size.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/config.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/exceptions.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/memory_size.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/printer.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/src/primitive/utils/text.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/tests/__init__.py +0 -0
- {primitive-0.2.2 → primitive-0.2.4}/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.4
|
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,6 @@
|
|
1
1
|
import os
|
2
|
-
import typing
|
3
2
|
import re
|
3
|
+
import typing
|
4
4
|
import shutil
|
5
5
|
from typing import Dict, TypedDict, List
|
6
6
|
from abc import abstractmethod
|
@@ -9,8 +9,8 @@ from pathlib import Path, PurePath
|
|
9
9
|
from loguru import logger
|
10
10
|
import yaml
|
11
11
|
import asyncio
|
12
|
-
from primitive.utils.shell import env_string_to_dict
|
13
12
|
from ..utils.cache import get_artifacts_cache, get_sources_cache, get_logs_cache
|
13
|
+
from ..utils.shell import env_to_dict
|
14
14
|
|
15
15
|
try:
|
16
16
|
from yaml import CLoader as Loader
|
@@ -20,6 +20,7 @@ except ImportError:
|
|
20
20
|
if typing.TYPE_CHECKING:
|
21
21
|
import primitive.client
|
22
22
|
|
23
|
+
CHUNK_SIZE = 64 * 1024
|
23
24
|
ENV_VAR_LOOKUP_START = "_ENV_VAR_LOOKUP_START"
|
24
25
|
ENV_VAR_LOOKUP_END = "_ENV_VAR_LOOKUP_END"
|
25
26
|
|
@@ -49,6 +50,16 @@ class LogLevel(Enum):
|
|
49
50
|
WARNING = "WARNING"
|
50
51
|
|
51
52
|
|
53
|
+
# Log Counter
|
54
|
+
class LogCounter:
|
55
|
+
count = 0
|
56
|
+
|
57
|
+
@classmethod
|
58
|
+
def next(cls) -> int:
|
59
|
+
cls.count += 1
|
60
|
+
return cls.count
|
61
|
+
|
62
|
+
|
52
63
|
class Runner:
|
53
64
|
def __init__(
|
54
65
|
self,
|
@@ -158,16 +169,24 @@ class Runner:
|
|
158
169
|
|
159
170
|
async def run_task(self, task: Task) -> bool:
|
160
171
|
for cmd in task["cmd"].strip().split("\n"):
|
172
|
+
# Adding an additional echo and utilizing stdbuf to force line buffering
|
173
|
+
# This ensures that the environment variables and starting delimiter are
|
174
|
+
# always in a new chunk, vastly simplifying our parsing logic
|
161
175
|
args = [
|
162
176
|
"/bin/bash",
|
163
177
|
"-c",
|
164
|
-
|
178
|
+
(
|
179
|
+
f"{cmd} "
|
180
|
+
f"&& stdbuf -oL echo && stdbuf -oL echo '{ENV_VAR_LOOKUP_START}' "
|
181
|
+
"&& env "
|
182
|
+
f"&& stdbuf -oL echo && echo '{ENV_VAR_LOOKUP_END}'"
|
183
|
+
),
|
165
184
|
]
|
166
185
|
|
167
186
|
process = await asyncio.create_subprocess_exec(
|
168
187
|
*args,
|
169
188
|
env=self.modified_env,
|
170
|
-
cwd=str(Path(self.source_dir / task
|
189
|
+
cwd=str(Path(self.source_dir / task.get("workdir", ""))),
|
171
190
|
stdout=asyncio.subprocess.PIPE,
|
172
191
|
stderr=asyncio.subprocess.PIPE,
|
173
192
|
)
|
@@ -205,52 +224,80 @@ class Runner:
|
|
205
224
|
|
206
225
|
async def log_cmd(self, process, stream, tags: Dict = {}) -> bool:
|
207
226
|
failure_detected = False
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
227
|
+
parse_environment = False
|
228
|
+
env_lines = []
|
229
|
+
while chunk := await stream.read(CHUNK_SIZE):
|
230
|
+
processed_lines = await self.read_chunk(chunk)
|
231
|
+
single_line = len(processed_lines) == 1
|
232
|
+
empty_chunk = len(processed_lines) == 0
|
233
|
+
|
234
|
+
if empty_chunk:
|
235
|
+
# Ignore the empty lines
|
236
|
+
continue
|
237
|
+
elif single_line and ENV_VAR_LOOKUP_START in processed_lines[0]:
|
238
|
+
# Next chunk will contain the environent variables
|
239
|
+
parse_environment = True
|
240
|
+
continue
|
241
|
+
elif single_line and ENV_VAR_LOOKUP_END in processed_lines[0]:
|
242
|
+
# Done reading environment variables
|
243
|
+
parse_environment = False
|
244
|
+
self.modified_env = env_to_dict(env_lines)
|
245
|
+
continue
|
218
246
|
|
219
|
-
|
247
|
+
if parse_environment:
|
248
|
+
env_lines.extend(processed_lines)
|
220
249
|
continue
|
221
250
|
|
222
251
|
# Handle logging
|
223
252
|
parse_logs = self.job_settings["parseLogs"]
|
224
253
|
parse_stderr = self.job_settings["parseStderr"]
|
225
254
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
255
|
+
for line in processed_lines:
|
256
|
+
level = LogLevel.INFO
|
257
|
+
tag = None
|
258
|
+
if (parse_logs and "error" in line.lower()) or (
|
259
|
+
parse_stderr and stream is process.stderr
|
260
|
+
):
|
261
|
+
level = LogLevel.ERROR
|
262
|
+
elif parse_logs and "warning" in line.lower():
|
263
|
+
level = LogLevel.WARNING
|
264
|
+
|
265
|
+
failure_detected = (
|
266
|
+
level == LogLevel.ERROR
|
267
|
+
and self.job_settings["failureLevel"] >= FailureLevel.ERROR
|
268
|
+
) or (
|
269
|
+
level == LogLevel.WARNING
|
270
|
+
and self.job_settings["failureLevel"] >= FailureLevel.WARNING
|
271
|
+
)
|
242
272
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
273
|
+
# Tag on the first matching regex in the list
|
274
|
+
for tag_key, regex in tags.items():
|
275
|
+
pattern = re.compile(regex)
|
276
|
+
if pattern.match(line):
|
277
|
+
tag = tag_key
|
278
|
+
break
|
249
279
|
|
250
|
-
|
280
|
+
logger.bind(tag=tag).log(level.value, line)
|
251
281
|
|
252
282
|
return failure_detected
|
253
283
|
|
284
|
+
async def read_chunk(self, chunk: bytes) -> List[str]:
|
285
|
+
"""Converts a chunk of bytes into proper lines."""
|
286
|
+
lines = []
|
287
|
+
current_line = ""
|
288
|
+
for char in chunk.decode():
|
289
|
+
if char in ("\n", "\r"):
|
290
|
+
lines.append(current_line)
|
291
|
+
current_line = ""
|
292
|
+
else:
|
293
|
+
current_line += char
|
294
|
+
|
295
|
+
# Handle extra characters in the buffer
|
296
|
+
if len(current_line) > 0:
|
297
|
+
lines.append(current_line)
|
298
|
+
|
299
|
+
return [line for line in lines if len(line) > 0]
|
300
|
+
|
254
301
|
async def monitor_cmd(self, process) -> bool:
|
255
302
|
while process.returncode is None:
|
256
303
|
status = await self.primitive.jobs.aget_job_status(self.job_run["id"])
|
@@ -304,6 +351,7 @@ class Runner:
|
|
304
351
|
context += f"{tag} | " if tag else " | "
|
305
352
|
|
306
353
|
log = (
|
354
|
+
f"{LogCounter.next()} | "
|
307
355
|
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS!UTC}</green> | "
|
308
356
|
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
309
357
|
"<level>{level}</level> | "
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from pathlib import Path
|
2
2
|
import shutil
|
3
|
-
from subprocess import run, CalledProcessError
|
3
|
+
from subprocess import run, CalledProcessError
|
4
4
|
|
5
5
|
from gql import gql
|
6
6
|
from loguru import logger
|
@@ -46,8 +46,8 @@ class Git(BaseAction):
|
|
46
46
|
run(
|
47
47
|
["git", "clone", url, source_dir, "--no-checkout"],
|
48
48
|
check=True,
|
49
|
-
stdout=DEVNULL,
|
50
|
-
stderr=DEVNULL,
|
49
|
+
# stdout=DEVNULL,
|
50
|
+
# stderr=DEVNULL,
|
51
51
|
)
|
52
52
|
except CalledProcessError:
|
53
53
|
raise Exception("Failed to download repository")
|
@@ -57,8 +57,8 @@ class Git(BaseAction):
|
|
57
57
|
["git", "checkout", git_ref],
|
58
58
|
check=True,
|
59
59
|
cwd=source_dir,
|
60
|
-
stdout=DEVNULL,
|
61
|
-
stderr=DEVNULL,
|
60
|
+
# stdout=DEVNULL,
|
61
|
+
# stderr=DEVNULL,
|
62
62
|
)
|
63
63
|
except CalledProcessError:
|
64
64
|
# Clean up directory if checkout failed
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import subprocess
|
2
2
|
from pathlib import Path
|
3
3
|
from shutil import which
|
4
|
-
from typing import Dict
|
4
|
+
from typing import Dict, Union, List
|
5
5
|
|
6
6
|
|
7
7
|
def add_path_to_shell(path: Path):
|
@@ -35,8 +35,14 @@ def add_path_to_shell(path: Path):
|
|
35
35
|
return True
|
36
36
|
|
37
37
|
|
38
|
-
def
|
39
|
-
lines =
|
38
|
+
def env_to_dict(env_vars: Union[str, List[str]]) -> Dict:
|
39
|
+
lines = None
|
40
|
+
if isinstance(env_vars, list):
|
41
|
+
lines = env_vars
|
42
|
+
elif isinstance(env_vars, str):
|
43
|
+
lines = env_vars.splitlines()
|
44
|
+
else:
|
45
|
+
raise ValueError("Unsupported type. Env_vars must be a list or a string")
|
40
46
|
|
41
47
|
current_key = None
|
42
48
|
current_value = []
|
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
|