primitive 0.2.10__py3-none-any.whl → 0.2.12__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 +62 -120
- primitive/agent/commands.py +2 -1
- primitive/agent/runner.py +43 -34
- primitive/agent/uploader.py +2 -2
- primitive/cli.py +2 -0
- primitive/client.py +41 -16
- primitive/daemons/actions.py +48 -62
- primitive/daemons/commands.py +68 -22
- primitive/daemons/launch_agents.py +205 -122
- primitive/daemons/launch_service.py +224 -164
- primitive/daemons/ui.py +41 -0
- primitive/db/base.py +5 -0
- primitive/db/models.py +88 -0
- primitive/db/sqlite.py +34 -0
- primitive/exec/actions.py +0 -1
- primitive/files/actions.py +0 -1
- primitive/hardware/actions.py +11 -10
- primitive/hardware/commands.py +1 -68
- primitive/hardware/ui.py +67 -0
- primitive/monitor/actions.py +199 -0
- primitive/monitor/commands.py +13 -0
- primitive/reservations/actions.py +0 -2
- primitive/utils/auth.py +0 -2
- primitive/utils/daemons.py +54 -0
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/METADATA +3 -1
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/RECORD +30 -22
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/WHEEL +0 -0
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/entry_points.txt +0 -0
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/licenses/LICENSE.txt +0 -0
primitive/__about__.py
CHANGED
primitive/agent/actions.py
CHANGED
@@ -6,7 +6,8 @@ from loguru import logger
|
|
6
6
|
from primitive.__about__ import __version__
|
7
7
|
from primitive.utils.actions import BaseAction
|
8
8
|
|
9
|
-
from ..
|
9
|
+
from ..db import sqlite
|
10
|
+
from ..db.models import JobRun
|
10
11
|
from .runner import Runner
|
11
12
|
from .uploader import Uploader
|
12
13
|
|
@@ -15,154 +16,95 @@ class Agent(BaseAction):
|
|
15
16
|
def execute(
|
16
17
|
self,
|
17
18
|
):
|
18
|
-
logger.enable("primitive")
|
19
19
|
logger.remove()
|
20
20
|
logger.add(
|
21
21
|
sink=sys.stderr,
|
22
|
-
|
22
|
+
format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <level>{message}</level>",
|
23
23
|
backtrace=True,
|
24
24
|
diagnose=True,
|
25
|
+
level="DEBUG" if self.primitive.DEBUG else "INFO",
|
25
26
|
)
|
26
|
-
logger.info("
|
27
|
-
logger.info(f"
|
27
|
+
logger.info("[*] primitive agent")
|
28
|
+
logger.info(f"[*] Version: {__version__}")
|
29
|
+
|
30
|
+
# Initialize the database
|
31
|
+
sqlite.init()
|
28
32
|
|
29
33
|
# Create uploader
|
30
34
|
uploader = Uploader(primitive=self.primitive)
|
31
35
|
|
32
|
-
# self.primitive.hardware.update_hardware_system_info()
|
33
|
-
try:
|
34
|
-
# hey stupid:
|
35
|
-
# do not set is_available to True here, it will mess up the reservation logic
|
36
|
-
# only set is_available after we've checked that no active reservation is present
|
37
|
-
# setting is_available of the parent also effects the children,
|
38
|
-
# which may have active reservations as well
|
39
|
-
self.primitive.hardware.check_in_http(is_online=True)
|
40
|
-
except Exception as exception:
|
41
|
-
logger.exception(f"Error checking in hardware: {exception}")
|
42
|
-
sys.exit(1)
|
43
|
-
|
44
36
|
try:
|
45
|
-
active_reservation_id = None
|
46
|
-
active_reservation_pk = None
|
47
|
-
|
48
37
|
while True:
|
49
38
|
logger.debug("Scanning for files to upload...")
|
50
39
|
uploader.scan()
|
51
40
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
hardware = self.primitive.hardware.get_own_hardware_details()
|
56
|
-
|
57
|
-
if hardware["activeReservation"]:
|
58
|
-
if (
|
59
|
-
hardware["activeReservation"]["id"] != active_reservation_id
|
60
|
-
or hardware["activeReservation"]["pk"] != active_reservation_pk
|
61
|
-
):
|
62
|
-
logger.warning("New reservation for this hardware.")
|
63
|
-
active_reservation_id = hardware["activeReservation"]["id"]
|
64
|
-
active_reservation_pk = hardware["activeReservation"]["pk"]
|
65
|
-
logger.debug("Active Reservation:")
|
66
|
-
logger.debug(f"Node ID: {active_reservation_id}")
|
67
|
-
logger.debug(f"PK: {active_reservation_pk}")
|
68
|
-
|
69
|
-
logger.debug("Running pre provisioning steps for reservation.")
|
70
|
-
self.primitive.provisioning.add_reservation_authorized_keys(
|
71
|
-
reservation_id=active_reservation_id
|
72
|
-
)
|
73
|
-
else:
|
74
|
-
if (
|
75
|
-
hardware["activeReservation"] is None
|
76
|
-
and active_reservation_id is not None
|
77
|
-
# and hardware["isAvailable"] NOTE: this condition was causing the CLI to get into a loop searching for job runs
|
78
|
-
):
|
79
|
-
logger.debug("Previous Reservation is Complete:")
|
80
|
-
logger.debug(f"Node ID: {active_reservation_id}")
|
81
|
-
logger.debug(f"PK: {active_reservation_pk}")
|
82
|
-
logger.debug(
|
83
|
-
"Running cleanup provisioning steps for reservation."
|
84
|
-
)
|
85
|
-
self.primitive.provisioning.remove_reservation_authorized_keys(
|
86
|
-
reservation_id=active_reservation_id
|
87
|
-
)
|
88
|
-
active_reservation_id = None
|
89
|
-
active_reservation_pk = None
|
90
|
-
|
91
|
-
if not active_reservation_id:
|
92
|
-
self.primitive.hardware.check_in_http(
|
93
|
-
is_available=True, is_online=True
|
94
|
-
)
|
41
|
+
db_job_run = JobRun.objects.first()
|
42
|
+
|
43
|
+
if not db_job_run:
|
95
44
|
sleep_amount = 5
|
96
45
|
logger.debug(
|
97
|
-
f"No
|
46
|
+
f"No pending job runs... [sleeping {sleep_amount} seconds]"
|
98
47
|
)
|
99
48
|
sleep(sleep_amount)
|
100
49
|
continue
|
101
50
|
|
102
|
-
|
103
|
-
|
51
|
+
api_job_run_data = self.primitive.jobs.get_job_run(
|
52
|
+
id=db_job_run.job_run_id,
|
104
53
|
)
|
105
54
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
if not pending_job_runs:
|
111
|
-
sleep_amount = 5
|
112
|
-
logger.debug(
|
113
|
-
f"Waiting for Job Runs... [sleeping {sleep_amount} seconds]"
|
55
|
+
if not api_job_run_data or not api_job_run_data.data:
|
56
|
+
logger.error(
|
57
|
+
f"Job Run {db_job_run.job_run_id} not found in API, deleting from DB"
|
114
58
|
)
|
115
|
-
|
59
|
+
JobRun.objects.filter_by(job_run_id=db_job_run.job_run_id).delete()
|
116
60
|
continue
|
117
61
|
|
118
|
-
|
119
|
-
logger.debug("Found pending Job Run")
|
120
|
-
logger.debug(f"Job Run ID: {job_run['id']}")
|
121
|
-
logger.debug(f"Job Name: {job_run['job']['name']}")
|
62
|
+
api_job_run = api_job_run_data.data["jobRun"]
|
122
63
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
64
|
+
logger.debug("Found pending Job Run")
|
65
|
+
logger.debug(f"Job Run ID: {api_job_run.get('id')}")
|
66
|
+
logger.debug(f"Job Name: {api_job_run.get('name')}")
|
67
|
+
|
68
|
+
runner = Runner(
|
69
|
+
primitive=self.primitive,
|
70
|
+
job_run=api_job_run,
|
71
|
+
# max_log_size=500 * 1024,
|
72
|
+
)
|
73
|
+
|
74
|
+
try:
|
75
|
+
runner.setup()
|
76
|
+
except Exception as exception:
|
77
|
+
logger.exception(
|
78
|
+
f"Exception while initializing runner: {exception}"
|
127
79
|
)
|
80
|
+
self.primitive.jobs.job_run_update(
|
81
|
+
id=api_job_run.get("id"),
|
82
|
+
status="request_completed",
|
83
|
+
conclusion="failure",
|
84
|
+
)
|
85
|
+
JobRun.objects.filter_by(job_run_id=api_job_run.get("id")).delete()
|
86
|
+
continue
|
128
87
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
)
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
status="request_completed",
|
149
|
-
conclusion="failure",
|
150
|
-
)
|
151
|
-
finally:
|
152
|
-
runner.cleanup()
|
153
|
-
|
154
|
-
# NOTE: also run scan here to force upload of artifacts
|
155
|
-
# This should probably eventuall be another daemon?
|
156
|
-
uploader.scan()
|
88
|
+
try:
|
89
|
+
runner.execute()
|
90
|
+
except Exception as exception:
|
91
|
+
logger.exception(f"Exception while executing job: {exception}")
|
92
|
+
self.primitive.jobs.job_run_update(
|
93
|
+
id=api_job_run.get("id"),
|
94
|
+
status="request_completed",
|
95
|
+
conclusion="failure",
|
96
|
+
)
|
97
|
+
finally:
|
98
|
+
runner.cleanup()
|
99
|
+
|
100
|
+
# NOTE: also run scan here to force upload of artifacts
|
101
|
+
# This should probably eventually be another daemon?
|
102
|
+
uploader.scan()
|
103
|
+
|
104
|
+
JobRun.objects.filter_by(
|
105
|
+
job_run_id=api_job_run.get("id"),
|
106
|
+
).delete()
|
157
107
|
|
158
108
|
sleep(5)
|
159
109
|
except KeyboardInterrupt:
|
160
|
-
logger.info("
|
161
|
-
try:
|
162
|
-
self.primitive.hardware.check_in_http(
|
163
|
-
is_available=False, is_online=False, stopping_agent=True
|
164
|
-
)
|
165
|
-
except P_CLI_100 as exception:
|
166
|
-
logger.error(" [*] Error stopping primitive.")
|
167
|
-
logger.error(str(exception))
|
168
|
-
sys.exit()
|
110
|
+
logger.info("[*] Stopping primitive agent...")
|
primitive/agent/commands.py
CHANGED
primitive/agent/runner.py
CHANGED
@@ -2,7 +2,6 @@ import asyncio
|
|
2
2
|
import os
|
3
3
|
import re
|
4
4
|
import shutil
|
5
|
-
import time
|
6
5
|
import typing
|
7
6
|
from abc import abstractmethod
|
8
7
|
from enum import Enum, IntEnum
|
@@ -12,6 +11,7 @@ from typing import Dict, List, TypedDict
|
|
12
11
|
import yaml
|
13
12
|
from loguru import logger
|
14
13
|
|
14
|
+
from ..db.models import JobRun
|
15
15
|
from ..utils.cache import get_artifacts_cache, get_logs_cache, get_sources_cache
|
16
16
|
from ..utils.shell import env_to_dict
|
17
17
|
|
@@ -80,8 +80,6 @@ class Runner:
|
|
80
80
|
self.modified_env = {}
|
81
81
|
self.file_logger = None
|
82
82
|
|
83
|
-
logger.enable("primitive")
|
84
|
-
|
85
83
|
# If max_log_size set to <= 0, disable file logging
|
86
84
|
if max_log_size > 0:
|
87
85
|
log_name = f"{self.job['slug']}_{self.job_run['jobRunNumber']}_{{time}}.primitive.log"
|
@@ -158,22 +156,52 @@ class Runner:
|
|
158
156
|
self.modified_env = {**self.initial_env}
|
159
157
|
|
160
158
|
task_failed = False
|
161
|
-
|
159
|
+
cancelled = False
|
160
|
+
|
162
161
|
for task in self.config["executes"]:
|
162
|
+
# the get status check here is to ensure that if cancel is called
|
163
|
+
# while one task is running, we do not run any OTHER laebeled tasks
|
164
|
+
# THIS is required for MULTI STEP JOBS
|
165
|
+
status = self.primitive.jobs.get_job_status(self.job_run["id"])
|
166
|
+
status_value = status.data["jobRun"]["status"]
|
167
|
+
conclusion_value = status.data["jobRun"]["conclusion"]
|
168
|
+
|
169
|
+
if status_value == "completed" and conclusion_value == "cancelled":
|
170
|
+
cancelled = True
|
171
|
+
break
|
172
|
+
|
163
173
|
with logger.contextualize(label=task["label"]):
|
164
174
|
with asyncio.Runner() as async_runner:
|
165
175
|
if task_failed := async_runner.run(self.run_task(task)):
|
166
176
|
break
|
167
177
|
|
178
|
+
number_of_files_produced = self.get_number_of_files_produced()
|
179
|
+
logger.info(
|
180
|
+
f"Produced {number_of_files_produced} files for {self.job['slug']} job"
|
181
|
+
)
|
182
|
+
|
183
|
+
# FOR NONE MULTI STEP JOBS
|
184
|
+
# we still have to check that the job was cancelled here as well
|
185
|
+
status = self.primitive.jobs.get_job_status(self.job_run["id"])
|
186
|
+
status_value = status.data["jobRun"]["status"]
|
187
|
+
conclusion_value = status.data["jobRun"]["conclusion"]
|
188
|
+
if status_value == "completed" and conclusion_value == "cancelled":
|
189
|
+
cancelled = True
|
190
|
+
|
191
|
+
if cancelled:
|
192
|
+
logger.warning("Job cancelled by user")
|
193
|
+
self.primitive.jobs.job_run_update(
|
194
|
+
self.job_run["id"],
|
195
|
+
number_of_files_produced=number_of_files_produced,
|
196
|
+
)
|
197
|
+
return
|
198
|
+
|
199
|
+
conclusion = "success"
|
168
200
|
if task_failed:
|
169
201
|
conclusion = "failure"
|
170
202
|
else:
|
171
203
|
logger.success(f"Completed {self.job['slug']} job")
|
172
204
|
|
173
|
-
number_of_files_produced = self.get_number_of_files_produced()
|
174
|
-
logger.info(
|
175
|
-
f"Produced {number_of_files_produced} files for {self.job['slug']} job"
|
176
|
-
)
|
177
205
|
self.primitive.jobs.job_run_update(
|
178
206
|
self.job_run["id"],
|
179
207
|
status="request_completed",
|
@@ -249,24 +277,24 @@ class Runner:
|
|
249
277
|
stderr=asyncio.subprocess.PIPE,
|
250
278
|
)
|
251
279
|
|
252
|
-
|
253
|
-
|
280
|
+
JobRun.objects.filter_by(job_run_id=self.job_run["id"]).update(
|
281
|
+
{"pid": process.pid}
|
282
|
+
)
|
254
283
|
|
255
|
-
stdout_failed, stderr_failed
|
284
|
+
stdout_failed, stderr_failed = await asyncio.gather(
|
256
285
|
self.log_cmd(
|
257
286
|
process=process, stream=process.stdout, tags=task.get("tags", {})
|
258
287
|
),
|
259
288
|
self.log_cmd(
|
260
289
|
process=process, stream=process.stderr, tags=task.get("tags", {})
|
261
290
|
),
|
262
|
-
monitor_task,
|
263
291
|
)
|
264
292
|
|
265
293
|
returncode = await process.wait()
|
266
294
|
|
267
|
-
|
268
|
-
|
269
|
-
|
295
|
+
JobRun.objects.filter_by(job_run_id=self.job_run["id"]).update(
|
296
|
+
{"pid": None}
|
297
|
+
)
|
270
298
|
|
271
299
|
if returncode > 0:
|
272
300
|
logger.error(
|
@@ -355,25 +383,6 @@ class Runner:
|
|
355
383
|
|
356
384
|
return [line for line in lines if len(line) > 0]
|
357
385
|
|
358
|
-
def monitor_cmd(self, process) -> bool:
|
359
|
-
while process.returncode is None:
|
360
|
-
status = self.primitive.jobs.get_job_status(self.job_run["id"])
|
361
|
-
|
362
|
-
status_value = status.data["jobRun"]["status"]
|
363
|
-
conclusion_value = status.data["jobRun"]["conclusion"]
|
364
|
-
|
365
|
-
if status_value == "completed" and conclusion_value == "cancelled":
|
366
|
-
try:
|
367
|
-
process.terminate()
|
368
|
-
except ProcessLookupError:
|
369
|
-
pass
|
370
|
-
|
371
|
-
return True
|
372
|
-
|
373
|
-
time.sleep(10)
|
374
|
-
|
375
|
-
return False
|
376
|
-
|
377
386
|
def cleanup(self) -> None:
|
378
387
|
logger.remove(self.file_logger)
|
379
388
|
|
primitive/agent/uploader.py
CHANGED
@@ -50,8 +50,8 @@ class Uploader:
|
|
50
50
|
path=file,
|
51
51
|
key_prefix=str(PurePath(file).relative_to(cache.parent).parent),
|
52
52
|
)
|
53
|
-
except Exception as
|
54
|
-
if "is empty" in str(
|
53
|
+
except Exception as exception:
|
54
|
+
if "is empty" in str(exception):
|
55
55
|
logger.warning(f"{file} is empty, skipping upload")
|
56
56
|
continue
|
57
57
|
|
primitive/cli.py
CHANGED
@@ -16,6 +16,7 @@ from .jobs.commands import cli as jobs_commands
|
|
16
16
|
from .organizations.commands import cli as organizations_commands
|
17
17
|
from .projects.commands import cli as projects_commands
|
18
18
|
from .reservations.commands import cli as reservations_commands
|
19
|
+
from .monitor.commands import cli as monitor_commands
|
19
20
|
|
20
21
|
|
21
22
|
@click.group()
|
@@ -71,6 +72,7 @@ cli.add_command(organizations_commands, "organizations")
|
|
71
72
|
cli.add_command(projects_commands, "projects")
|
72
73
|
cli.add_command(reservations_commands, "reservations")
|
73
74
|
cli.add_command(exec_commands, "exec")
|
75
|
+
cli.add_command(monitor_commands, "monitor")
|
74
76
|
|
75
77
|
if __name__ == "__main__":
|
76
78
|
cli(obj={})
|
primitive/client.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
import sys
|
2
|
-
|
3
1
|
from gql import Client
|
4
2
|
from loguru import logger
|
3
|
+
from rich.logging import RichHandler
|
4
|
+
from rich.traceback import install
|
5
|
+
from typing import Optional
|
5
6
|
|
6
7
|
from .agent.actions import Agent
|
7
8
|
from .auth.actions import Auth
|
@@ -15,10 +16,9 @@ from .organizations.actions import Organizations
|
|
15
16
|
from .projects.actions import Projects
|
16
17
|
from .provisioning.actions import Provisioning
|
17
18
|
from .reservations.actions import Reservations
|
19
|
+
from .monitor.actions import Monitor
|
18
20
|
from .utils.config import read_config_file
|
19
21
|
|
20
|
-
logger.disable("primitive")
|
21
|
-
|
22
22
|
|
23
23
|
class Primitive:
|
24
24
|
def __init__(
|
@@ -26,24 +26,48 @@ class Primitive:
|
|
26
26
|
host: str = "api.primitive.tech",
|
27
27
|
DEBUG: bool = False,
|
28
28
|
JSON: bool = False,
|
29
|
-
token: str = None,
|
30
|
-
transport: str = None,
|
29
|
+
token: Optional[str] = None,
|
30
|
+
transport: Optional[str] = None,
|
31
31
|
) -> None:
|
32
32
|
self.host: str = host
|
33
|
-
self.session: Client = None
|
33
|
+
self.session: Optional[Client] = None
|
34
34
|
self.DEBUG: bool = DEBUG
|
35
35
|
self.JSON: bool = JSON
|
36
36
|
|
37
|
+
# Enable tracebacks with local variables
|
37
38
|
if self.DEBUG:
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
)
|
39
|
+
install(show_locals=True)
|
40
|
+
|
41
|
+
# Configure rich logging handler
|
42
|
+
rich_handler = RichHandler(
|
43
|
+
rich_tracebacks=self.DEBUG, # Pretty tracebacks
|
44
|
+
markup=True, # Allow Rich markup tags
|
45
|
+
show_time=self.DEBUG, # Show timestamps
|
46
|
+
show_level=self.DEBUG, # Show log levels
|
47
|
+
show_path=self.DEBUG, # Hide source path (optional)
|
48
|
+
)
|
49
|
+
|
50
|
+
def formatter(record) -> str:
|
51
|
+
match record["level"].name:
|
52
|
+
case "ERROR":
|
53
|
+
return "[bold red]Error>[/bold red] {name}:{function}:{line} - {message}"
|
54
|
+
case "CRITICAL":
|
55
|
+
return "[italic bold red]Critical>[/italic bold red] {name}:{function}:{line} - {message}"
|
56
|
+
case "WARNING":
|
57
|
+
return "[bold yellow]Warning>[/bold yellow] {message}"
|
58
|
+
case _:
|
59
|
+
return "[#666666]>[/#666666] {message}"
|
60
|
+
|
61
|
+
logger.remove()
|
62
|
+
logger.add(
|
63
|
+
sink=rich_handler,
|
64
|
+
format="{message}" if self.DEBUG else formatter,
|
65
|
+
level="DEBUG" if self.DEBUG else "INFO",
|
66
|
+
backtrace=self.DEBUG,
|
67
|
+
)
|
68
|
+
|
69
|
+
# Nothing will print here if DEBUG is false
|
70
|
+
logger.debug("Debug mode enabled")
|
47
71
|
|
48
72
|
# Generate full or partial host config
|
49
73
|
if not token and not transport:
|
@@ -67,6 +91,7 @@ class Primitive:
|
|
67
91
|
self.daemons: Daemons = Daemons(self)
|
68
92
|
self.exec: Exec = Exec(self)
|
69
93
|
self.provisioning: Provisioning = Provisioning(self)
|
94
|
+
self.monitor: Monitor = Monitor(self)
|
70
95
|
|
71
96
|
def get_host_config(self):
|
72
97
|
self.full_config = read_config_file()
|
primitive/daemons/actions.py
CHANGED
@@ -1,24 +1,13 @@
|
|
1
1
|
import platform
|
2
2
|
import typing
|
3
|
+
from typing import Dict, Optional, List
|
3
4
|
|
4
5
|
if typing.TYPE_CHECKING:
|
5
6
|
from ..client import Primitive
|
6
7
|
|
7
|
-
from .launch_agents import
|
8
|
-
|
9
|
-
|
10
|
-
start_launch_agent,
|
11
|
-
stop_launch_agent,
|
12
|
-
view_launch_agent_logs,
|
13
|
-
)
|
14
|
-
|
15
|
-
from .launch_service import (
|
16
|
-
full_service_install,
|
17
|
-
full_service_uninstall,
|
18
|
-
start_service,
|
19
|
-
stop_service,
|
20
|
-
view_service_logs,
|
21
|
-
)
|
8
|
+
from .launch_agents import LaunchAgent
|
9
|
+
from .launch_service import LaunchService
|
10
|
+
from ..utils.daemons import Daemon
|
22
11
|
|
23
12
|
|
24
13
|
class Daemons:
|
@@ -26,50 +15,47 @@ class Daemons:
|
|
26
15
|
self.primitive: Primitive = primitive
|
27
16
|
self.os_family = platform.system()
|
28
17
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
view_service_logs()
|
74
|
-
elif self.os_family == "Windows":
|
75
|
-
print("Not Implemented")
|
18
|
+
match self.os_family:
|
19
|
+
case "Darwin":
|
20
|
+
self.daemons: Dict[str, Daemon] = {
|
21
|
+
"agent": LaunchAgent("tech.primitive.agent"),
|
22
|
+
"monitor": LaunchAgent("tech.primitive.monitor"),
|
23
|
+
}
|
24
|
+
case "Linux":
|
25
|
+
self.daemons: Dict[str, Daemon] = {
|
26
|
+
"agent": LaunchService("tech.primitive.agent"),
|
27
|
+
"monitor": LaunchService("tech.primitive.monitor"),
|
28
|
+
}
|
29
|
+
case _:
|
30
|
+
raise NotImplementedError(f"{self.os_family} is not supported.")
|
31
|
+
|
32
|
+
def install(self, name: Optional[str]) -> bool:
|
33
|
+
if name:
|
34
|
+
return self.daemons[name].install()
|
35
|
+
else:
|
36
|
+
return all([daemon.install() for daemon in self.daemons.values()])
|
37
|
+
|
38
|
+
def uninstall(self, name: Optional[str]) -> bool:
|
39
|
+
if name:
|
40
|
+
return self.daemons[name].uninstall()
|
41
|
+
else:
|
42
|
+
return all([daemon.uninstall() for daemon in self.daemons.values()])
|
43
|
+
|
44
|
+
def stop(self, name: Optional[str]) -> bool:
|
45
|
+
if name:
|
46
|
+
return self.daemons[name].stop()
|
47
|
+
else:
|
48
|
+
return all([daemon.stop() for daemon in self.daemons.values()])
|
49
|
+
|
50
|
+
def start(self, name: Optional[str]) -> bool:
|
51
|
+
if name:
|
52
|
+
return self.daemons[name].start()
|
53
|
+
else:
|
54
|
+
return all([daemon.start() for daemon in self.daemons.values()])
|
55
|
+
|
56
|
+
def list(self) -> List[Daemon]:
|
57
|
+
"""List all daemons"""
|
58
|
+
return list(self.daemons.values())
|
59
|
+
|
60
|
+
def logs(self, name: str) -> None:
|
61
|
+
self.daemons[name].view_logs()
|