primitive 0.2.31__py3-none-any.whl → 0.2.33__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.
primitive/__about__.py CHANGED
@@ -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.31"
4
+ __version__ = "0.2.33"
@@ -100,10 +100,6 @@ class Agent(BaseAction):
100
100
  # This should probably eventually be another daemon?
101
101
  uploader.scan()
102
102
 
103
- JobRun.objects.filter_by(
104
- job_run_id=api_job_run.get("id"),
105
- ).delete()
106
-
107
103
  sleep(5)
108
104
  except KeyboardInterrupt:
109
105
  logger.info("[agent] Stopping primitive agent...")
primitive/agent/runner.py CHANGED
@@ -25,7 +25,11 @@ if typing.TYPE_CHECKING:
25
25
 
26
26
  CHUNK_SIZE = 64 * 1024
27
27
  ENV_VAR_LOOKUP_START = "_ENV_VAR_LOOKUP_START"
28
+ START_DELIMITER_SIZE = len(bytes(ENV_VAR_LOOKUP_START, encoding="utf-8"))
28
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
29
33
 
30
34
 
31
35
  class Task(TypedDict):
@@ -132,6 +136,10 @@ class Runner:
132
136
 
133
137
  # Setup initial process environment
134
138
  self.initial_env = os.environ
139
+ self.initial_env = {
140
+ **self.initial_env,
141
+ **self.primitive.jobs.get_job_secrets_for_job_run(self.job_run["id"]),
142
+ }
135
143
  self.initial_env["PRIMITIVE_GIT_SHA"] = str(self.job_run["gitCommit"]["sha"])
136
144
  self.initial_env["PRIMITIVE_GIT_BRANCH"] = str(
137
145
  self.job_run["gitCommit"]["branch"]
@@ -253,12 +261,7 @@ class Runner:
253
261
  args = [
254
262
  "/bin/bash",
255
263
  "-c",
256
- (
257
- f"{cmd} "
258
- f"&& stdbuf -oL echo && stdbuf -oL echo '{ENV_VAR_LOOKUP_START}' "
259
- "&& env "
260
- f"&& stdbuf -oL echo && echo '{ENV_VAR_LOOKUP_END}'"
261
- ),
264
+ f"{cmd} && echo -n '{ENV_VAR_LOOKUP_START}' && env && echo -n '{ENV_VAR_LOOKUP_END}'",
262
265
  ]
263
266
 
264
267
  process = await asyncio.create_subprocess_exec(
@@ -302,28 +305,32 @@ class Runner:
302
305
  async def log_cmd(self, process, stream, tags: Dict = {}) -> bool:
303
306
  failure_detected = False
304
307
  parse_environment = False
305
- env_lines = []
308
+ last_chunk_buffer = b""
309
+ environment_buffer = b""
306
310
  while chunk := await stream.read(CHUNK_SIZE):
307
- processed_lines = await self.read_chunk(chunk)
308
- single_line = len(processed_lines) == 1
309
- empty_chunk = len(processed_lines) == 0
310
-
311
- if empty_chunk:
312
- # Ignore the empty lines
311
+ if parse_environment:
312
+ environment_buffer += chunk
313
313
  continue
314
- elif single_line and ENV_VAR_LOOKUP_START in processed_lines[0]:
315
- # Next chunk will contain the environent variables
314
+
315
+ # First, look for start delimiter in chunk
316
+ full_chunk = last_chunk_buffer + chunk
317
+ last_chunk_buffer = b""
318
+ start_index = full_chunk.find(bytes(ENV_VAR_LOOKUP_START, encoding="utf-8"))
319
+
320
+ if start_index != -1:
321
+ environment_buffer = full_chunk[start_index + START_DELIMITER_SIZE :]
322
+ processed_lines = await self.read_chunk(full_chunk[:start_index])
316
323
  parse_environment = True
317
- continue
318
- elif single_line and ENV_VAR_LOOKUP_END in processed_lines[0]:
319
- # Done reading environment variables
320
- parse_environment = False
321
- self.modified_env = env_to_dict(env_lines)
322
- continue
324
+ else:
325
+ processed_lines = await self.read_chunk(full_chunk)
323
326
 
324
- if parse_environment:
325
- env_lines.extend(processed_lines)
326
- continue
327
+ while (
328
+ len(last_chunk_buffer) < START_DELIMITER_SIZE
329
+ and len(processed_lines) > 0
330
+ ):
331
+ last_chunk_buffer += bytes(
332
+ processed_lines.pop() + "\n", encoding="utf-8"
333
+ )
327
334
 
328
335
  # Handle logging
329
336
  parse_logs = self.job_settings["parseLogs"]
@@ -339,13 +346,15 @@ class Runner:
339
346
  elif parse_logs and "warning" in line.lower():
340
347
  level = LogLevel.WARNING
341
348
 
342
- failure_detected = (
343
- level == LogLevel.ERROR
344
- and self.job_settings["failureLevel"] >= FailureLevel.ERROR
345
- ) or (
346
- level == LogLevel.WARNING
347
- and self.job_settings["failureLevel"] >= FailureLevel.WARNING
348
- )
349
+ # If we already detected a failure, skip checking
350
+ if not failure_detected:
351
+ failure_detected = (
352
+ level == LogLevel.ERROR
353
+ and self.job_settings["failureLevel"] >= FailureLevel.ERROR
354
+ ) or (
355
+ level == LogLevel.WARNING
356
+ and self.job_settings["failureLevel"] >= FailureLevel.WARNING
357
+ )
349
358
 
350
359
  # Tag on the first matching regex in the list
351
360
  for tag_key, regex in tags.items():
@@ -356,6 +365,16 @@ class Runner:
356
365
 
357
366
  logger.bind(tag=tag).log(level.value, line)
358
367
 
368
+ start_index = environment_buffer.find(
369
+ bytes(ENV_VAR_LOOKUP_END, encoding="utf-8")
370
+ )
371
+ if parse_environment and start_index == -1:
372
+ logger.error("Environment variable buffer did not contain end delimiter")
373
+ failure_detected = True
374
+ return failure_detected
375
+
376
+ environment_buffer = environment_buffer[:start_index]
377
+ self.modified_env = env_to_dict(environment_buffer)
359
378
  return failure_detected
360
379
 
361
380
  async def read_chunk(self, chunk: bytes) -> List[str]:
primitive/jobs/actions.py CHANGED
@@ -11,6 +11,7 @@ from .graphql.queries import (
11
11
  job_run_query,
12
12
  job_run_status_query,
13
13
  job_runs_query,
14
+ job_secrets_for_job_run_query,
14
15
  jobs_query,
15
16
  )
16
17
 
@@ -163,3 +164,12 @@ class Jobs(BaseAction):
163
164
  query, variable_values=variables, get_execution_result=True
164
165
  )
165
166
  return result
167
+
168
+ @guard
169
+ def get_job_secrets_for_job_run(self, id: str):
170
+ query = gql(job_secrets_for_job_run_query)
171
+ variables = {"jobRunId": id}
172
+ result = self.primitive.session.execute(
173
+ query, variable_values=variables, get_execution_result=True
174
+ )
175
+ return result.data["jobSecretsForJobRun"]
@@ -98,3 +98,10 @@ query jobRun($id: GlobalID!) {
98
98
  }
99
99
  """
100
100
  )
101
+
102
+
103
+ job_secrets_for_job_run_query = """
104
+ query jobSecretsForJobRun($jobRunId: GlobalID!) {
105
+ jobSecretsForJobRun(jobRunId: $jobRunId)
106
+ }
107
+ """
primitive/utils/shell.py CHANGED
@@ -35,12 +35,14 @@ def add_path_to_shell(path: Path):
35
35
  return True
36
36
 
37
37
 
38
- def env_to_dict(env_vars: Union[str, List[str]]) -> Dict:
38
+ def env_to_dict(env_vars: Union[bytes, str, List[str]]) -> Dict:
39
39
  lines = None
40
40
  if isinstance(env_vars, list):
41
41
  lines = env_vars
42
42
  elif isinstance(env_vars, str):
43
43
  lines = env_vars.splitlines()
44
+ elif isinstance(env_vars, bytes):
45
+ lines = env_vars.decode("utf-8").splitlines()
44
46
  else:
45
47
  raise ValueError("Unsupported type. Env_vars must be a list or a string")
46
48
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: primitive
3
- Version: 0.2.31
3
+ Version: 0.2.33
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,11 +1,11 @@
1
- primitive/__about__.py,sha256=LH2fP6k1EgAIeykRYn1baGNEf_y6ro_QPuG8jugYJpo,130
1
+ primitive/__about__.py,sha256=VgaNzMy13Zl_7Ph2LzRKGJY9WBIR3X3iXpy-0CtxYTQ,130
2
2
  primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
3
3
  primitive/cli.py,sha256=g7EtHI9MATAB0qQu5w-WzbXtxz_8zu8z5E7sETmMkKU,2509
4
4
  primitive/client.py,sha256=h8WZVnQylVe0vbpuyC8YZHl2JyITSPC-1HbUcmrE5pc,3623
5
5
  primitive/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- primitive/agent/actions.py,sha256=HDktV5FwtD19Bieg653w-ZWp7dQHTOj7cQx0-Aiae84,3836
6
+ primitive/agent/actions.py,sha256=JLnHCtu0AgeHRZccCc2I39mziLixmj9EKtKeYmvBE7A,3700
7
7
  primitive/agent/commands.py,sha256=cK7d3OcN5Z65gQWVZFQ-Y9ddw9Pes4f9OVBpeMsj5sE,255
8
- primitive/agent/runner.py,sha256=SGI_vcFfey-VUH3gQasVJRgzPeSLMlP6FBjSImJqbOA,14180
8
+ primitive/agent/runner.py,sha256=duF0iQCvf23ZOKRUk5e3yL2wQz9pr-XnpPijFqmb4uc,15143
9
9
  primitive/agent/uploader.py,sha256=ZzrzsajNBogwEC7mT6Ejy0h2Jd9axMYGzt9pbCvVMlk,3171
10
10
  primitive/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  primitive/auth/actions.py,sha256=9NIEXJ1BNJutJs6AMMSjMN_ziONUAUhY_xHwojYJCLA,942
@@ -51,12 +51,12 @@ primitive/hardware/graphql/fragments.py,sha256=kI6qnTNjaEaUr-C6eD55COphtueVYbYOW
51
51
  primitive/hardware/graphql/mutations.py,sha256=_4Hkbfik9Ron4T-meulu6T-9FR_BZjyPNwn745MPksU,1484
52
52
  primitive/hardware/graphql/queries.py,sha256=I86uLuOSjHSph11Y5MVCYko5Js7hoiEZ-cEoPTc4J-k,1392
53
53
  primitive/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- primitive/jobs/actions.py,sha256=lOfmjzZ7XvqLyP0f-ilyfYwQQZuNgUb4qxFCpf1d93M,5076
54
+ primitive/jobs/actions.py,sha256=Fx2cPc1x09nRasOVtjhPjNRJ-jNoi3RJhXqC3verD9s,5444
55
55
  primitive/jobs/commands.py,sha256=MxPCkBEYW_eLNqgCRYeyj7ZcLOFAWfpVZlqDR2Y_S0o,830
56
56
  primitive/jobs/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  primitive/jobs/graphql/fragments.py,sha256=pS6jXZ9yxvPKjKx50zpwzARJHYlg7NQLYCm1voiuCzI,642
58
58
  primitive/jobs/graphql/mutations.py,sha256=8ASvCmwQh7cMeeiykOdYaYVryG8FRIuVF6v_J8JJZuw,219
59
- primitive/jobs/graphql/queries.py,sha256=BrU_GnLjK0bTAmWsLSmGEUea7EM8MqTKxN1Qp6sSjwc,1597
59
+ primitive/jobs/graphql/queries.py,sha256=ZxNmm-WovytbggNuKRnwa0kc26T34_0yhqkoqx-2uj0,1736
60
60
  primitive/monitor/actions.py,sha256=aYe5OfgCxhapXbcvz7vSlIMAcLOFRcAUWmdBZ8H7UWs,10889
61
61
  primitive/monitor/commands.py,sha256=VDlEL_Qpm_ysHxug7VpI0cVAZ0ny6AS91Y58D7F1zkU,409
62
62
  primitive/organizations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -94,10 +94,10 @@ primitive/utils/daemons.py,sha256=mSoSHitiGfS4KYAEK9sKsiv_YcACHKgY3qISnDpUUIE,10
94
94
  primitive/utils/exceptions.py,sha256=DrYHTcCAJGC7cCUwOx_FmdlVLWRdpzvDvpLb82heppE,311
95
95
  primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIUk,3058
96
96
  primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
97
- primitive/utils/shell.py,sha256=jWzb7ky7p987dJas6ZvarK3IJNZ5cwBXcryRWb9Uh6U,2072
97
+ primitive/utils/shell.py,sha256=Z4zxmOaSyGCrS0D6I436iQci-ewHLt4UxVg1CD9Serc,2171
98
98
  primitive/utils/text.py,sha256=XiESMnlhjQ534xE2hMNf08WehE1SKaYFRNih0MmnK0k,829
99
- primitive-0.2.31.dist-info/METADATA,sha256=jEXyjNe3Uv2BFG9MZAUt0Em4YMU950c4T_xUq26f7NY,3569
100
- primitive-0.2.31.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
- primitive-0.2.31.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
102
- primitive-0.2.31.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
103
- primitive-0.2.31.dist-info/RECORD,,
99
+ primitive-0.2.33.dist-info/METADATA,sha256=d38zBbaz0OFTGWtf-ZmSEUSWJX1NgmWf3aotjjZkMA8,3569
100
+ primitive-0.2.33.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
+ primitive-0.2.33.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
102
+ primitive-0.2.33.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
103
+ primitive-0.2.33.dist-info/RECORD,,