primitive 0.2.30__py3-none-any.whl → 0.2.32__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.30"
4
+ __version__ = "0.2.32"
@@ -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):
@@ -116,30 +120,26 @@ class Runner:
116
120
  )
117
121
 
118
122
  # Attempt to parse the job yaml file
119
- logger.info(f"Scanning directory for job file {self.job['slug']}")
120
- yaml_file = Path(self.source_dir / ".primitive" / f"{self.job['slug']}.yaml")
121
- yml_file = Path(self.source_dir / ".primitive" / f"{self.job['slug']}.yml")
123
+ job_filename = self.job_settings["repositoryFilename"]
124
+ logger.info(f"Scanning directory for job file {job_filename}")
122
125
 
123
- if yaml_file.exists() and yml_file.exists():
124
- logger.error(
125
- f"Found two job descriptions with the same slug: {self.job['slug']}"
126
- )
127
- raise FileExistsError
126
+ job_config_file = Path(self.source_dir / ".primitive" / job_filename)
128
127
 
129
- if yaml_file.exists() or yml_file.exists():
128
+ if job_config_file.exists():
130
129
  logger.info(f"Found job description for {self.job['slug']}")
131
- config_file = yaml_file if yaml_file.exists() else yml_file
132
- self.config = yaml.load(open(config_file, "r"), Loader=Loader)[
133
- self.job["name"]
134
- ]
130
+ self.config = yaml.load(open(job_config_file, "r"), Loader=Loader)
135
131
  else:
136
132
  logger.error(
137
- f"No job description with matching slug '{self.job['slug']}' found"
133
+ f"No job description with matching filename '{job_filename}' found"
138
134
  )
139
135
  raise FileNotFoundError
140
136
 
141
137
  # Setup initial process environment
142
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
+ }
143
143
  self.initial_env["PRIMITIVE_GIT_SHA"] = str(self.job_run["gitCommit"]["sha"])
144
144
  self.initial_env["PRIMITIVE_GIT_BRANCH"] = str(
145
145
  self.job_run["gitCommit"]["branch"]
@@ -261,12 +261,7 @@ class Runner:
261
261
  args = [
262
262
  "/bin/bash",
263
263
  "-c",
264
- (
265
- f"{cmd} "
266
- f"&& stdbuf -oL echo && stdbuf -oL echo '{ENV_VAR_LOOKUP_START}' "
267
- "&& env "
268
- f"&& stdbuf -oL echo && echo '{ENV_VAR_LOOKUP_END}'"
269
- ),
264
+ f"{cmd} && echo -n '{ENV_VAR_LOOKUP_START}' && env && echo -n '{ENV_VAR_LOOKUP_END}'",
270
265
  ]
271
266
 
272
267
  process = await asyncio.create_subprocess_exec(
@@ -310,28 +305,32 @@ class Runner:
310
305
  async def log_cmd(self, process, stream, tags: Dict = {}) -> bool:
311
306
  failure_detected = False
312
307
  parse_environment = False
313
- env_lines = []
308
+ last_chunk_buffer = b""
309
+ environment_buffer = b""
314
310
  while chunk := await stream.read(CHUNK_SIZE):
315
- processed_lines = await self.read_chunk(chunk)
316
- single_line = len(processed_lines) == 1
317
- empty_chunk = len(processed_lines) == 0
318
-
319
- if empty_chunk:
320
- # Ignore the empty lines
311
+ if parse_environment:
312
+ environment_buffer += chunk
321
313
  continue
322
- elif single_line and ENV_VAR_LOOKUP_START in processed_lines[0]:
323
- # 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])
324
323
  parse_environment = True
325
- continue
326
- elif single_line and ENV_VAR_LOOKUP_END in processed_lines[0]:
327
- # Done reading environment variables
328
- parse_environment = False
329
- self.modified_env = env_to_dict(env_lines)
330
- continue
324
+ else:
325
+ processed_lines = await self.read_chunk(full_chunk)
331
326
 
332
- if parse_environment:
333
- env_lines.extend(processed_lines)
334
- 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
+ )
335
334
 
336
335
  # Handle logging
337
336
  parse_logs = self.job_settings["parseLogs"]
@@ -364,6 +363,16 @@ class Runner:
364
363
 
365
364
  logger.bind(tag=tag).log(level.value, line)
366
365
 
366
+ start_index = environment_buffer.find(
367
+ bytes(ENV_VAR_LOOKUP_END, encoding="utf-8")
368
+ )
369
+ if parse_environment and start_index == -1:
370
+ logger.error("Environment variable buffer did not contain end delimiter")
371
+ failure_detected = True
372
+ return failure_detected
373
+
374
+ environment_buffer = environment_buffer[:start_index]
375
+ self.modified_env = env_to_dict(environment_buffer)
367
376
  return failure_detected
368
377
 
369
378
  async def read_chunk(self, chunk: bytes) -> List[str]:
@@ -132,7 +132,7 @@ class LaunchAgent(Daemon):
132
132
  <key>ProgramArguments</key>
133
133
  <array>
134
134
  <string>{self.executable}</string>
135
- <string>{self.command}</string>
135
+ {"".join([f"<string>{arg}</string>" for arg in self.command.split(" ") if arg.strip() != ""])}
136
136
  </array>
137
137
  <key>RunAtLoad</key>
138
138
  <true/>
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"]
@@ -34,6 +34,7 @@ fragment JobRunFragment on JobRun {
34
34
  parseLogs
35
35
  parseStderr
36
36
  failureLevel
37
+ repositoryFilename
37
38
  }
38
39
  gitCommit {
39
40
  sha
@@ -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.30
3
+ Version: 0.2.32
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=RmCzeHEVe-XHumqPgZrfYW5qjcAtsXdA16gAEzHGmjQ,130
1
+ primitive/__about__.py,sha256=oC0l5fABhgagaC-_r-5QZ1VPn7hy8tdJID7wI0iTSow,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=CoRyReO3jPV8B7vILVWdszFD4GVop7HsVEUo1hoRXjo,14556
8
+ primitive/agent/runner.py,sha256=e4HBhK16zuhTpKAdKsgoij6G4YjMTn9JQo2YTLRI8i8,15008
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
@@ -15,7 +15,7 @@ primitive/auth/graphql/queries.py,sha256=jhrr_VFzHIn8vcVprMIzUx7V4kkWYdR6CKMKPoV
15
15
  primitive/daemons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  primitive/daemons/actions.py,sha256=dqhxgIDxiJlcN6xvkzgBerN215KvbBKKdWteRpT_ICs,2730
17
17
  primitive/daemons/commands.py,sha256=Xt4qFymNrDLdHJhRnEH_4Re-2xX6w1OT-chV9k7dFCs,2670
18
- primitive/daemons/launch_agents.py,sha256=5MFDacs_IZd6PCm4VRNq9dPEYIDivkydBzqex1ggkFY,7740
18
+ primitive/daemons/launch_agents.py,sha256=cCxsvCBFmGlKOiBINsKi_NxcozLC8yPw3w6pxqnz-qI,7803
19
19
  primitive/daemons/launch_service.py,sha256=iuklHeuEqadlf8U1n9xFg4ZG1EKdK2jyaPI-VTlpJ4I,7907
20
20
  primitive/daemons/ui.py,sha256=Af3OJWJ0jdGlb1nfA5yaGYdhBEqqpM8zP2U2vUQdCbw,1236
21
21
  primitive/db/base.py,sha256=mH7f2d_jiyxJSSx9Gk53QBXRa3LiKBsBjkFgvmtH1WA,83
@@ -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
- primitive/jobs/graphql/fragments.py,sha256=1_ZttT7dx36KDC3DClJz9M8LMpsPwXySBygHSiUEcGg,619
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.30.dist-info/METADATA,sha256=WAlddgCvqYTWwuqvUNQJIyGHtGZgNFB0cAWKP2PvWiY,3569
100
- primitive-0.2.30.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
- primitive-0.2.30.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
102
- primitive-0.2.30.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
103
- primitive-0.2.30.dist-info/RECORD,,
99
+ primitive-0.2.32.dist-info/METADATA,sha256=z-2bverx3L8RAyVNxxYWBv_-3UjdTgfZbk_ety8Swlo,3569
100
+ primitive-0.2.32.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
+ primitive-0.2.32.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
102
+ primitive-0.2.32.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
103
+ primitive-0.2.32.dist-info/RECORD,,