primitive 0.2.35__py3-none-any.whl → 0.2.37__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 +1 -1
- primitive/agent/actions.py +12 -3
- primitive/agent/commands.py +3 -2
- primitive/agent/runner.py +49 -73
- primitive/utils/logging.py +45 -0
- {primitive-0.2.35.dist-info → primitive-0.2.37.dist-info}/METADATA +1 -1
- {primitive-0.2.35.dist-info → primitive-0.2.37.dist-info}/RECORD +10 -9
- {primitive-0.2.35.dist-info → primitive-0.2.37.dist-info}/WHEEL +0 -0
- {primitive-0.2.35.dist-info → primitive-0.2.37.dist-info}/entry_points.txt +0 -0
- {primitive-0.2.35.dist-info → primitive-0.2.37.dist-info}/licenses/LICENSE.txt +0 -0
primitive/__about__.py
CHANGED
primitive/agent/actions.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import sys
|
2
2
|
from time import sleep
|
3
|
+
from typing import Optional
|
3
4
|
|
4
5
|
from loguru import logger
|
5
6
|
|
@@ -12,9 +13,7 @@ from primitive.utils.actions import BaseAction
|
|
12
13
|
|
13
14
|
|
14
15
|
class Agent(BaseAction):
|
15
|
-
def execute(
|
16
|
-
self,
|
17
|
-
):
|
16
|
+
def execute(self, job_run_id: Optional[str] = None):
|
18
17
|
logger.remove()
|
19
18
|
logger.add(
|
20
19
|
sink=sys.stderr,
|
@@ -26,6 +25,12 @@ class Agent(BaseAction):
|
|
26
25
|
logger.info("primitive agent")
|
27
26
|
logger.info(f"Version: {__version__}")
|
28
27
|
|
28
|
+
# TODO: tighten logic for determining if we're running in a container
|
29
|
+
RUNNING_IN_CONTAINER = False
|
30
|
+
if job_run_id is not None:
|
31
|
+
logger.info("Running in container...")
|
32
|
+
RUNNING_IN_CONTAINER = True
|
33
|
+
|
29
34
|
# Wait for monitor to make database
|
30
35
|
wait_for_db()
|
31
36
|
|
@@ -100,6 +105,10 @@ class Agent(BaseAction):
|
|
100
105
|
# This should probably eventually be another daemon?
|
101
106
|
uploader.scan()
|
102
107
|
|
108
|
+
if RUNNING_IN_CONTAINER:
|
109
|
+
logger.info("Running in container, exiting after job run")
|
110
|
+
break
|
111
|
+
|
103
112
|
sleep(5)
|
104
113
|
except KeyboardInterrupt:
|
105
114
|
logger.info("Stopping primitive agent...")
|
primitive/agent/commands.py
CHANGED
@@ -7,8 +7,9 @@ if typing.TYPE_CHECKING:
|
|
7
7
|
|
8
8
|
|
9
9
|
@click.command("agent")
|
10
|
+
@click.option("--job-run-id", type=str, help="Explicit Job Run to pull")
|
10
11
|
@click.pass_context
|
11
|
-
def cli(context):
|
12
|
+
def cli(context, job_run_id: typing.Optional[str] = None):
|
12
13
|
"""agent"""
|
13
14
|
primitive: Primitive = context.obj.get("PRIMITIVE")
|
14
|
-
primitive.agent.execute()
|
15
|
+
primitive.agent.execute(job_run_id=job_run_id)
|
primitive/agent/runner.py
CHANGED
@@ -3,7 +3,6 @@ import os
|
|
3
3
|
import re
|
4
4
|
import shutil
|
5
5
|
import typing
|
6
|
-
from abc import abstractmethod
|
7
6
|
from enum import Enum, IntEnum
|
8
7
|
from pathlib import Path, PurePath
|
9
8
|
from typing import Dict, List, TypedDict
|
@@ -13,6 +12,7 @@ from loguru import logger
|
|
13
12
|
|
14
13
|
from ..db.models import JobRun
|
15
14
|
from ..utils.cache import get_artifacts_cache, get_logs_cache, get_sources_cache
|
15
|
+
from ..utils.logging import fmt, log_context
|
16
16
|
from ..utils.shell import env_to_dict
|
17
17
|
|
18
18
|
try:
|
@@ -57,16 +57,6 @@ class LogLevel(Enum):
|
|
57
57
|
WARNING = "WARNING"
|
58
58
|
|
59
59
|
|
60
|
-
# Log Counter
|
61
|
-
class LogCounter:
|
62
|
-
count = 0
|
63
|
-
|
64
|
-
@classmethod
|
65
|
-
def next(cls) -> int:
|
66
|
-
cls.count += 1
|
67
|
-
return cls.count
|
68
|
-
|
69
|
-
|
70
60
|
class Runner:
|
71
61
|
def __init__(
|
72
62
|
self,
|
@@ -79,7 +69,7 @@ class Runner:
|
|
79
69
|
self.job_run = job_run
|
80
70
|
self.job_settings = job_run["jobSettings"]
|
81
71
|
self.config = None
|
82
|
-
self.source_dir: Path = None
|
72
|
+
self.source_dir: Path | None = None
|
83
73
|
self.initial_env = {}
|
84
74
|
self.modified_env = {}
|
85
75
|
self.file_logger = None
|
@@ -91,11 +81,11 @@ class Runner:
|
|
91
81
|
self.file_logger = logger.add(
|
92
82
|
Path(get_logs_cache(self.job_run["id"]) / log_name),
|
93
83
|
rotation=max_log_size,
|
94
|
-
format=
|
84
|
+
format=fmt,
|
95
85
|
backtrace=True,
|
96
|
-
diagnose=True,
|
97
86
|
)
|
98
87
|
|
88
|
+
@log_context(label="setup")
|
99
89
|
def setup(self) -> None:
|
100
90
|
# Attempt to download the job source code
|
101
91
|
git_repo_full_name = self.job_run["gitCommit"]["repoFullName"]
|
@@ -155,66 +145,62 @@ class Runner:
|
|
155
145
|
self.job_run["gitCommit"]["repoFullName"]
|
156
146
|
)
|
157
147
|
|
148
|
+
@log_context(label="execute")
|
158
149
|
def execute(self) -> None:
|
159
150
|
logger.info(f"Executing {self.job['slug']} job")
|
160
151
|
self.primitive.jobs.job_run_update(
|
161
152
|
self.job_run["id"], status="request_in_progress"
|
162
153
|
)
|
163
|
-
self.modified_env = {**self.initial_env}
|
164
154
|
|
155
|
+
self.modified_env = {**self.initial_env}
|
165
156
|
task_failed = False
|
166
157
|
cancelled = False
|
167
158
|
|
168
159
|
for task in self.config["executes"]:
|
169
|
-
#
|
170
|
-
#
|
171
|
-
|
160
|
+
# Everything inside this loop should be contextualized with the task label
|
161
|
+
# this way we aren't jumping back and forth between the task label and "execute"
|
162
|
+
with logger.contextualize(label=task["label"]):
|
163
|
+
# the get status check here is to ensure that if cancel is called
|
164
|
+
# while one task is running, we do not run any OTHER labeled tasks
|
165
|
+
# THIS is required for MULTI STEP JOBS
|
166
|
+
status = self.primitive.jobs.get_job_status(self.job_run["id"])
|
167
|
+
status_value = status.data["jobRun"]["status"]
|
168
|
+
conclusion_value = status.data["jobRun"]["conclusion"]
|
169
|
+
|
170
|
+
if status_value == "completed" and conclusion_value == "cancelled":
|
171
|
+
cancelled = True
|
172
|
+
break
|
173
|
+
|
174
|
+
# Everything within this block should be contextualized as user logs
|
175
|
+
with logger.contextualize(type="user"):
|
176
|
+
with asyncio.Runner() as async_runner:
|
177
|
+
if task_failed := async_runner.run(self.run_task(task)):
|
178
|
+
break
|
179
|
+
|
180
|
+
# FOR NONE MULTI STEP JOBS
|
181
|
+
# we still have to check that the job was cancelled here as well
|
182
|
+
with logger.contextualize(label="conclusion"):
|
172
183
|
status = self.primitive.jobs.get_job_status(self.job_run["id"])
|
173
184
|
status_value = status.data["jobRun"]["status"]
|
174
185
|
conclusion_value = status.data["jobRun"]["conclusion"]
|
175
|
-
|
176
186
|
if status_value == "completed" and conclusion_value == "cancelled":
|
177
187
|
cancelled = True
|
178
|
-
break
|
179
188
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
break
|
189
|
+
if cancelled:
|
190
|
+
logger.warning("Job cancelled by user")
|
191
|
+
return
|
184
192
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
193
|
+
conclusion = "success"
|
194
|
+
if task_failed:
|
195
|
+
conclusion = "failure"
|
196
|
+
else:
|
197
|
+
logger.success(f"Completed {self.job['slug']} job")
|
189
198
|
|
190
|
-
# FOR NONE MULTI STEP JOBS
|
191
|
-
# we still have to check that the job was cancelled here as well
|
192
|
-
status = self.primitive.jobs.get_job_status(self.job_run["id"])
|
193
|
-
status_value = status.data["jobRun"]["status"]
|
194
|
-
conclusion_value = status.data["jobRun"]["conclusion"]
|
195
|
-
if status_value == "completed" and conclusion_value == "cancelled":
|
196
|
-
cancelled = True
|
197
|
-
|
198
|
-
if cancelled:
|
199
|
-
logger.warning("Job cancelled by user")
|
200
199
|
self.primitive.jobs.job_run_update(
|
201
200
|
self.job_run["id"],
|
202
|
-
|
201
|
+
status="request_completed",
|
202
|
+
conclusion=conclusion,
|
203
203
|
)
|
204
|
-
return
|
205
|
-
|
206
|
-
conclusion = "success"
|
207
|
-
if task_failed:
|
208
|
-
conclusion = "failure"
|
209
|
-
else:
|
210
|
-
logger.success(f"Completed {self.job['slug']} job")
|
211
|
-
|
212
|
-
self.primitive.jobs.job_run_update(
|
213
|
-
self.job_run["id"],
|
214
|
-
status="request_completed",
|
215
|
-
conclusion=conclusion,
|
216
|
-
number_of_files_produced=number_of_files_produced,
|
217
|
-
)
|
218
204
|
|
219
205
|
def get_number_of_files_produced(self) -> int:
|
220
206
|
"""Returns the number of files produced by the job."""
|
@@ -315,6 +301,7 @@ class Runner:
|
|
315
301
|
logger.error(f"Task {task['label']} failed on '{cmd}'")
|
316
302
|
return True
|
317
303
|
|
304
|
+
logger.success(f"Completed {task['label']} task")
|
318
305
|
return False
|
319
306
|
|
320
307
|
async def log_cmd(self, process, stream, tags: Dict = {}) -> bool:
|
@@ -409,9 +396,8 @@ class Runner:
|
|
409
396
|
|
410
397
|
return [line for line in lines if len(line) > 0]
|
411
398
|
|
399
|
+
@log_context(label="cleanup")
|
412
400
|
def cleanup(self) -> None:
|
413
|
-
logger.remove(self.file_logger)
|
414
|
-
|
415
401
|
if "stores" not in self.config:
|
416
402
|
return
|
417
403
|
|
@@ -427,23 +413,13 @@ class Runner:
|
|
427
413
|
|
428
414
|
shutil.rmtree(path=self.source_dir)
|
429
415
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
context += f"{tag} | " if tag else " | "
|
438
|
-
|
439
|
-
log = (
|
440
|
-
f"{LogCounter.next()} | "
|
441
|
-
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS!UTC}</green> | "
|
442
|
-
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
443
|
-
"<level>{level}</level> | "
|
444
|
-
f"{context}"
|
445
|
-
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
446
|
-
"<level>{message}</level>\n"
|
416
|
+
number_of_files_produced = self.get_number_of_files_produced()
|
417
|
+
logger.info(
|
418
|
+
f"Produced {number_of_files_produced} files for {self.job['slug']} job"
|
419
|
+
)
|
420
|
+
self.primitive.jobs.job_run_update(
|
421
|
+
self.job_run["id"],
|
422
|
+
number_of_files_produced=number_of_files_produced,
|
447
423
|
)
|
448
424
|
|
449
|
-
|
425
|
+
logger.remove(self.file_logger)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import json
|
2
|
+
from datetime import timezone
|
3
|
+
from functools import wraps
|
4
|
+
|
5
|
+
from loguru import logger
|
6
|
+
|
7
|
+
|
8
|
+
def log_context(**context):
|
9
|
+
def decorator(func):
|
10
|
+
@wraps(func)
|
11
|
+
def wrapper(*args, **kwargs):
|
12
|
+
with logger.contextualize(**context):
|
13
|
+
return func(*args, **kwargs)
|
14
|
+
|
15
|
+
return wrapper
|
16
|
+
|
17
|
+
return decorator
|
18
|
+
|
19
|
+
|
20
|
+
def fmt(record) -> str:
|
21
|
+
extra = record["extra"]
|
22
|
+
label = extra.get("label", None)
|
23
|
+
tag = extra.get("tag", None)
|
24
|
+
type = extra.get("type", "system")
|
25
|
+
|
26
|
+
context_object = {
|
27
|
+
"label": label,
|
28
|
+
"type": type,
|
29
|
+
"utc": record["time"]
|
30
|
+
.astimezone(timezone.utc)
|
31
|
+
.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3],
|
32
|
+
"level": record["level"].name,
|
33
|
+
"name": record["name"],
|
34
|
+
"function": record["function"],
|
35
|
+
"line": record["line"],
|
36
|
+
"message": record["message"],
|
37
|
+
}
|
38
|
+
|
39
|
+
if tag:
|
40
|
+
context_object["tag"] = tag
|
41
|
+
|
42
|
+
# Loguru will fail if you return a string that doesn't select
|
43
|
+
# something within its record
|
44
|
+
record["extra"]["serialized"] = json.dumps(context_object)
|
45
|
+
return "{extra[serialized]}\n"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: primitive
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.37
|
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=
|
1
|
+
primitive/__about__.py,sha256=k_4SlrPFVlD5xhSWVsBLRpaxZRnyooKrAmVPOvghuL0,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=
|
7
|
-
primitive/agent/commands.py,sha256=
|
8
|
-
primitive/agent/runner.py,sha256=
|
6
|
+
primitive/agent/actions.py,sha256=vq4_CZy7Mn37jUg-JPuJlMLtcx4sMXp6tUNvXGZOOyQ,4128
|
7
|
+
primitive/agent/commands.py,sha256=kqa-PGqmzS-APd4BSMAkX4l8SdK5N1PRQwq9S8jBjsw,390
|
8
|
+
primitive/agent/runner.py,sha256=ZlFC7Eq0NXOSCOvMtDu9gWKF2sa4QMkeG-wH-R-9iOc,15463
|
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
|
@@ -92,12 +92,13 @@ primitive/utils/chunk_size.py,sha256=PAuVuirUTA9oRXyjo1c6MWxo31WVBRkWMuWw-AS58Bw
|
|
92
92
|
primitive/utils/config.py,sha256=DlFM5Nglo22WPtbpZSVtH7NX-PTMaKYlcrUE7GPRG4c,1058
|
93
93
|
primitive/utils/daemons.py,sha256=mSoSHitiGfS4KYAEK9sKsiv_YcACHKgY3qISnDpUUIE,1086
|
94
94
|
primitive/utils/exceptions.py,sha256=DrYHTcCAJGC7cCUwOx_FmdlVLWRdpzvDvpLb82heppE,311
|
95
|
+
primitive/utils/logging.py,sha256=vpwu-hByZC1BgJfUi6iSfAxzCobP_zg9-99EUf80KtY,1132
|
95
96
|
primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIUk,3058
|
96
97
|
primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
|
97
98
|
primitive/utils/shell.py,sha256=Z4zxmOaSyGCrS0D6I436iQci-ewHLt4UxVg1CD9Serc,2171
|
98
99
|
primitive/utils/text.py,sha256=XiESMnlhjQ534xE2hMNf08WehE1SKaYFRNih0MmnK0k,829
|
99
|
-
primitive-0.2.
|
100
|
-
primitive-0.2.
|
101
|
-
primitive-0.2.
|
102
|
-
primitive-0.2.
|
103
|
-
primitive-0.2.
|
100
|
+
primitive-0.2.37.dist-info/METADATA,sha256=r-F7XhBMe84oxARfJ9ZMbwVAcV5jQojsGrhxvWlK9E0,3569
|
101
|
+
primitive-0.2.37.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
102
|
+
primitive-0.2.37.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
|
103
|
+
primitive-0.2.37.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
|
104
|
+
primitive-0.2.37.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|