primitive 0.2.3__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.
Files changed (104) hide show
  1. {primitive-0.2.3 → primitive-0.2.4}/PKG-INFO +1 -1
  2. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/__about__.py +1 -1
  3. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/agent/runner.py +85 -37
  4. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/shell.py +9 -3
  5. {primitive-0.2.3 → primitive-0.2.4}/.git-hooks/pre-commit +0 -0
  6. {primitive-0.2.3 → primitive-0.2.4}/.gitattributes +0 -0
  7. {primitive-0.2.3 → primitive-0.2.4}/.github/workflows/lint.yml +0 -0
  8. {primitive-0.2.3 → primitive-0.2.4}/.github/workflows/publish.yml +0 -0
  9. {primitive-0.2.3 → primitive-0.2.4}/.gitignore +0 -0
  10. {primitive-0.2.3 → primitive-0.2.4}/.vscode/settings.json +0 -0
  11. {primitive-0.2.3 → primitive-0.2.4}/LICENSE.txt +0 -0
  12. {primitive-0.2.3 → primitive-0.2.4}/Makefile +0 -0
  13. {primitive-0.2.3 → primitive-0.2.4}/README.md +0 -0
  14. {primitive-0.2.3 → primitive-0.2.4}/linux setup.md +0 -0
  15. {primitive-0.2.3 → primitive-0.2.4}/pyproject.toml +0 -0
  16. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/__init__.py +0 -0
  17. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/agent/__init__.py +0 -0
  18. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/agent/actions.py +0 -0
  19. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/agent/commands.py +0 -0
  20. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/agent/uploader.py +0 -0
  21. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/auth/__init__.py +0 -0
  22. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/auth/actions.py +0 -0
  23. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/auth/commands.py +0 -0
  24. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/auth/graphql/__init__.py +0 -0
  25. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/auth/graphql/queries.py +0 -0
  26. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/cli.py +0 -0
  27. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/client.py +0 -0
  28. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/daemons/__init__.py +0 -0
  29. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/daemons/actions.py +0 -0
  30. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/daemons/commands.py +0 -0
  31. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/daemons/launch_agents.py +0 -0
  32. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/daemons/launch_service.py +0 -0
  33. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/exec/__init__.py +0 -0
  34. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/exec/actions.py +0 -0
  35. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/exec/commands.py +0 -0
  36. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/exec/interactive.py +0 -0
  37. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/files/__init__.py +0 -0
  38. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/files/actions.py +0 -0
  39. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/files/commands.py +0 -0
  40. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/files/graphql/__init__.py +0 -0
  41. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/files/graphql/fragments.py +0 -0
  42. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/files/graphql/mutations.py +0 -0
  43. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/files/graphql/queries.py +0 -0
  44. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/git/__init__.py +0 -0
  45. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/git/actions.py +0 -0
  46. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/git/commands.py +0 -0
  47. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/git/graphql/__init__.py +0 -0
  48. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/git/graphql/queries.py +0 -0
  49. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/graphql/__init__.py +0 -0
  50. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/graphql/relay.py +0 -0
  51. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/graphql/sdk.py +0 -0
  52. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/graphql/utility_fragments.py +0 -0
  53. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/hardware/__init__.py +0 -0
  54. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/hardware/actions.py +0 -0
  55. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/hardware/android.py +0 -0
  56. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/hardware/commands.py +0 -0
  57. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/hardware/graphql/__init__.py +0 -0
  58. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/hardware/graphql/fragments.py +0 -0
  59. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/hardware/graphql/mutations.py +0 -0
  60. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/hardware/graphql/queries.py +0 -0
  61. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/jobs/__init__.py +0 -0
  62. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/jobs/actions.py +0 -0
  63. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/jobs/commands.py +0 -0
  64. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/jobs/graphql/__init__.py +0 -0
  65. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/jobs/graphql/fragments.py +0 -0
  66. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/jobs/graphql/mutations.py +0 -0
  67. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/jobs/graphql/queries.py +0 -0
  68. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/organizations/__init__.py +0 -0
  69. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/organizations/actions.py +0 -0
  70. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/organizations/commands.py +0 -0
  71. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/organizations/graphql/__init__.py +0 -0
  72. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/organizations/graphql/fragments.py +0 -0
  73. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/organizations/graphql/mutations.py +0 -0
  74. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/organizations/graphql/queries.py +0 -0
  75. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/projects/__init__.py +0 -0
  76. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/projects/actions.py +0 -0
  77. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/projects/commands.py +0 -0
  78. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/projects/graphql/__init__.py +0 -0
  79. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/projects/graphql/fragments.py +0 -0
  80. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/projects/graphql/mutations.py +0 -0
  81. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/projects/graphql/queries.py +0 -0
  82. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/provisioning/__init__.py +0 -0
  83. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/provisioning/actions.py +0 -0
  84. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/provisioning/graphql/__init__.py +0 -0
  85. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/provisioning/graphql/queries.py +0 -0
  86. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/reservations/__init__.py +0 -0
  87. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/reservations/actions.py +0 -0
  88. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/reservations/commands.py +0 -0
  89. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/reservations/graphql/__init__.py +0 -0
  90. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/reservations/graphql/fragments.py +0 -0
  91. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/reservations/graphql/mutations.py +0 -0
  92. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/reservations/graphql/queries.py +0 -0
  93. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/__init__.py +0 -0
  94. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/actions.py +0 -0
  95. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/auth.py +0 -0
  96. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/cache.py +0 -0
  97. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/chunk_size.py +0 -0
  98. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/config.py +0 -0
  99. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/exceptions.py +0 -0
  100. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/memory_size.py +0 -0
  101. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/printer.py +0 -0
  102. {primitive-0.2.3 → primitive-0.2.4}/src/primitive/utils/text.py +0 -0
  103. {primitive-0.2.3 → primitive-0.2.4}/tests/__init__.py +0 -0
  104. {primitive-0.2.3 → 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
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,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2024-present Dylan Stein <dylan@primitive.tech>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.2.3"
4
+ __version__ = "0.2.4"
@@ -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,10 +169,18 @@ 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
- f"{cmd} && echo '{ENV_VAR_LOOKUP_START}' && env && echo '{ENV_VAR_LOOKUP_END}'",
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(
@@ -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
- while line := await stream.readline():
209
- raw_data = line.decode()
210
-
211
- # handle env vars
212
- if ENV_VAR_LOOKUP_START in raw_data:
213
- env_vars_string = ""
214
- while env_line := await stream.readline():
215
- if ENV_VAR_LOOKUP_END in env_line.decode():
216
- break
217
- env_vars_string += env_line.decode()
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
- self.modified_env = env_string_to_dict(env_vars_string)
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
- level = LogLevel.INFO
227
- tag = None
228
- if (parse_logs and "error" in raw_data.lower()) or (
229
- parse_stderr and stream is process.stderr
230
- ):
231
- level = LogLevel.ERROR
232
- elif parse_logs and "warning" in raw_data.lower():
233
- level = LogLevel.WARNING
234
-
235
- failure_detected = (
236
- level == LogLevel.ERROR
237
- and self.job_settings["failureLevel"] >= FailureLevel.ERROR
238
- ) or (
239
- level == LogLevel.WARNING
240
- and self.job_settings["failureLevel"] >= FailureLevel.WARNING
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
- # Tag on the first matching regex in the list
244
- for tag_key, regex in tags.items():
245
- pattern = re.compile(regex)
246
- if pattern.match(raw_data):
247
- tag = tag_key
248
- break
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
- logger.bind(tag=tag).log(level.value, raw_data.rstrip())
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,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 env_string_to_dict(env_str: str) -> Dict:
39
- lines = env_str.splitlines()
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