primitive 0.2.66__tar.gz → 0.2.70__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.
Potentially problematic release.
This version of primitive might be problematic. Click here for more details.
- {primitive-0.2.66 → primitive-0.2.70}/PKG-INFO +1 -1
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/__about__.py +1 -1
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/agent/actions.py +8 -5
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/agent/runner.py +61 -17
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/cli.py +2 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/client.py +2 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/files/actions.py +23 -1
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/actions.py +226 -1
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/commands.py +13 -2
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/monitor/actions.py +10 -5
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/network/actions.py +83 -34
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/network/commands.py +17 -6
- primitive-0.2.70/src/primitive/network/ssh.py +126 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/network/ui.py +9 -3
- primitive-0.2.70/src/primitive/operating_systems/actions.py +260 -0
- primitive-0.2.70/src/primitive/operating_systems/commands.py +268 -0
- primitive-0.2.70/src/primitive/operating_systems/graphql/mutations.py +32 -0
- primitive-0.2.70/src/primitive/operating_systems/graphql/queries.py +36 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/organizations/actions.py +6 -0
- primitive-0.2.70/src/primitive/reservations/graphql/__init__.py +0 -0
- primitive-0.2.70/src/primitive/utils/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/cache.py +11 -0
- primitive-0.2.70/src/primitive/utils/checksums.py +44 -0
- primitive-0.2.70/testing_containers/README.md +13 -0
- primitive-0.2.70/testing_containers/fedora.Containerfile +19 -0
- primitive-0.2.70/testing_containers/ubuntu.Containerfile +19 -0
- {primitive-0.2.66 → primitive-0.2.70}/.git-hooks/pre-commit +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/.gitattributes +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/.github/workflows/lint.yml +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/.github/workflows/publish.yml +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/.github/workflows/pyright.yml +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/.gitignore +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/.vscode/extensions.json +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/.vscode/settings.json +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/LICENSE.txt +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/Makefile +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/README.md +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/linux setup.md +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/pyproject.toml +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/agent/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/agent/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/agent/uploader.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/auth/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/auth/actions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/auth/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/auth/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/auth/graphql/queries.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/daemons/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/daemons/actions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/daemons/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/daemons/launch_agents.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/daemons/launch_service.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/daemons/ui.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/exec/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/exec/actions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/exec/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/exec/interactive.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/files/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/files/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/files/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/files/graphql/fragments.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/files/graphql/mutations.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/files/graphql/queries.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/files/ui.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/git/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/git/actions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/git/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/git/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/git/graphql/queries.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/graphql/relay.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/graphql/sdk.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/graphql/utility_fragments.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/android.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/graphql/fragments.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/graphql/mutations.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/graphql/queries.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/hardware/ui.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/jobs/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/jobs/actions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/jobs/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/jobs/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/jobs/graphql/fragments.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/jobs/graphql/mutations.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/jobs/graphql/queries.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/messaging/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/messaging/provider.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/monitor/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/network/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/network/redfish.py +0 -0
- {primitive-0.2.66/src/primitive/organizations → primitive-0.2.70/src/primitive/operating_systems}/__init__.py +0 -0
- {primitive-0.2.66/src/primitive/organizations → primitive-0.2.70/src/primitive/operating_systems}/graphql/__init__.py +0 -0
- {primitive-0.2.66/src/primitive/projects → primitive-0.2.70/src/primitive/organizations}/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/organizations/commands.py +0 -0
- {primitive-0.2.66/src/primitive/projects → primitive-0.2.70/src/primitive/organizations}/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/organizations/graphql/fragments.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/organizations/graphql/mutations.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/organizations/graphql/queries.py +0 -0
- {primitive-0.2.66/src/primitive/provisioning → primitive-0.2.70/src/primitive/projects}/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/projects/actions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/projects/commands.py +0 -0
- {primitive-0.2.66/src/primitive/provisioning → primitive-0.2.70/src/primitive/projects}/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/projects/graphql/fragments.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/projects/graphql/mutations.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/projects/graphql/queries.py +0 -0
- {primitive-0.2.66/src/primitive/reservations → primitive-0.2.70/src/primitive/provisioning}/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/provisioning/actions.py +0 -0
- {primitive-0.2.66/src/primitive/reservations → primitive-0.2.70/src/primitive/provisioning}/graphql/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/provisioning/graphql/queries.py +0 -0
- {primitive-0.2.66/src/primitive/utils → primitive-0.2.70/src/primitive/reservations}/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/reservations/actions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/reservations/commands.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/reservations/graphql/fragments.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/reservations/graphql/mutations.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/reservations/graphql/queries.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/actions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/auth.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/chunk_size.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/config.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/daemons.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/exceptions.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/logging.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/memory_size.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/printer.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/psutil.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/shell.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/text.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/src/primitive/utils/x509.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/tests/__init__.py +0 -0
- {primitive-0.2.66 → primitive-0.2.70}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: primitive
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.70
|
|
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
|
|
@@ -96,11 +96,14 @@ class Agent(BaseAction):
|
|
|
96
96
|
job_run_status = job_run_data.get("status", None)
|
|
97
97
|
|
|
98
98
|
hardware_id = hardware.get("id", None) if hardware else None
|
|
99
|
-
execution_hardware_id =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
execution_hardware_id = None
|
|
100
|
+
if job_run_data:
|
|
101
|
+
execution_hardware = job_run_data.get("executionHardware", None)
|
|
102
|
+
execution_hardware_id = (
|
|
103
|
+
execution_hardware.get("id", None)
|
|
104
|
+
if execution_hardware
|
|
105
|
+
else None
|
|
106
|
+
)
|
|
104
107
|
target_hardware_id = None
|
|
105
108
|
|
|
106
109
|
if (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import os
|
|
3
3
|
import shutil
|
|
4
|
+
import time
|
|
4
5
|
import typing
|
|
5
6
|
from enum import Enum
|
|
6
7
|
from pathlib import Path, PurePath
|
|
@@ -9,6 +10,7 @@ from datetime import datetime, timezone
|
|
|
9
10
|
|
|
10
11
|
from loguru import logger
|
|
11
12
|
|
|
13
|
+
from primitive.network.ssh import wait_for_ssh
|
|
12
14
|
from primitive.utils.cache import get_artifacts_cache, get_logs_cache, get_sources_cache
|
|
13
15
|
from primitive.utils.logging import fmt, log_context
|
|
14
16
|
from primitive.utils.psutil import kill_process_and_children
|
|
@@ -230,7 +232,13 @@ class Runner:
|
|
|
230
232
|
commands = task["cmd"].strip().split("\n")
|
|
231
233
|
|
|
232
234
|
for i, cmd in enumerate(commands):
|
|
235
|
+
if cmd.strip() == "":
|
|
236
|
+
continue
|
|
237
|
+
if cmd.strip().startswith("#"):
|
|
238
|
+
logger.debug(f"Skipping comment line: {cmd.strip()}")
|
|
239
|
+
continue
|
|
233
240
|
if cmd == "oobpowercycle":
|
|
241
|
+
logger.info("Performing out-of-band power cycle")
|
|
234
242
|
from primitive.network.redfish import RedfishClient
|
|
235
243
|
|
|
236
244
|
bmc_host = self.target_hardware_secret.get("bmcHostname", None)
|
|
@@ -253,15 +261,32 @@ class Runner:
|
|
|
253
261
|
)
|
|
254
262
|
redfish.compute_system_reset(system_id="1", reset_type="ForceRestart")
|
|
255
263
|
if self.target_hardware_id:
|
|
256
|
-
self.primitive.hardware.
|
|
264
|
+
await self.primitive.hardware.aupdate_hardware(
|
|
257
265
|
hardware_id=self.target_hardware_id,
|
|
258
266
|
is_online=False,
|
|
259
267
|
is_rebooting=True,
|
|
260
|
-
start_rebooting_at=datetime.now(timezone.utc),
|
|
268
|
+
start_rebooting_at=str(datetime.now(timezone.utc)),
|
|
269
|
+
)
|
|
270
|
+
logger.info(
|
|
271
|
+
"Box rebooting, waiting 30 seconds before beginning SSH connection."
|
|
272
|
+
)
|
|
273
|
+
time.sleep(30)
|
|
274
|
+
wait_for_ssh(
|
|
275
|
+
hostname=self.target_hardware_secret.get("hostname"),
|
|
276
|
+
username=self.target_hardware_secret.get("username"),
|
|
277
|
+
password=self.target_hardware_secret.get("password"),
|
|
278
|
+
port=22,
|
|
279
|
+
)
|
|
280
|
+
logger.info("Reboot successful, SSH is now available")
|
|
281
|
+
await self.primitive.hardware.aupdate_hardware(
|
|
282
|
+
hardware_id=self.target_hardware_id,
|
|
283
|
+
is_online=True,
|
|
284
|
+
is_rebooting=False,
|
|
261
285
|
)
|
|
262
286
|
continue
|
|
263
287
|
|
|
264
288
|
if cmd == "pxeboot":
|
|
289
|
+
logger.info("Setting next boot to PXE and rebooting")
|
|
265
290
|
from primitive.network.redfish import RedfishClient
|
|
266
291
|
|
|
267
292
|
bmc_host = self.target_hardware_secret.get("bmcHostname", None)
|
|
@@ -290,20 +315,35 @@ class Runner:
|
|
|
290
315
|
)
|
|
291
316
|
redfish.compute_system_reset(system_id="1", reset_type="ForceRestart")
|
|
292
317
|
if self.target_hardware_id:
|
|
293
|
-
self.primitive.hardware.
|
|
318
|
+
await self.primitive.hardware.aupdate_hardware(
|
|
294
319
|
hardware_id=self.target_hardware_id,
|
|
295
320
|
is_online=False,
|
|
296
321
|
is_rebooting=True,
|
|
297
|
-
start_rebooting_at=datetime.now(timezone.utc),
|
|
322
|
+
start_rebooting_at=str(datetime.now(timezone.utc)),
|
|
323
|
+
)
|
|
324
|
+
logger.info(
|
|
325
|
+
"Box rebooting, waiting 30 seconds before beginning SSH connection."
|
|
326
|
+
)
|
|
327
|
+
time.sleep(30)
|
|
328
|
+
wait_for_ssh(
|
|
329
|
+
hostname=self.target_hardware_secret.get("hostname"),
|
|
330
|
+
username=self.target_hardware_secret.get("username"),
|
|
331
|
+
password=self.target_hardware_secret.get("password"),
|
|
332
|
+
port=22,
|
|
333
|
+
)
|
|
334
|
+
logger.info("PXE boot successful, SSH is now available")
|
|
335
|
+
await self.primitive.hardware.aupdate_hardware(
|
|
336
|
+
hardware_id=self.target_hardware_id,
|
|
337
|
+
is_online=True,
|
|
338
|
+
is_rebooting=False,
|
|
298
339
|
)
|
|
299
340
|
continue
|
|
300
341
|
|
|
301
|
-
args = ["/bin/bash", "-c", cmd]
|
|
302
342
|
if self.target_hardware_secret:
|
|
303
343
|
username = self.target_hardware_secret.get("username")
|
|
304
344
|
password = self.target_hardware_secret.get("password")
|
|
305
345
|
hostname = self.target_hardware_secret.get("hostname")
|
|
306
|
-
|
|
346
|
+
command_args = [
|
|
307
347
|
"sshpass",
|
|
308
348
|
"-p",
|
|
309
349
|
password,
|
|
@@ -316,15 +356,18 @@ class Runner:
|
|
|
316
356
|
"IdentitiesOnly=yes",
|
|
317
357
|
f"{username}@{hostname}",
|
|
318
358
|
"--",
|
|
319
|
-
f
|
|
359
|
+
f"{cmd}",
|
|
320
360
|
]
|
|
361
|
+
print(" ".join(command_args))
|
|
362
|
+
else:
|
|
363
|
+
command_args = ["/bin/bash", "--login", "-c", cmd]
|
|
321
364
|
|
|
322
365
|
logger.info(
|
|
323
366
|
f"Executing command {i + 1}/{len(commands)}: {cmd} at {self.source_dir / task.get('workdir', '')}"
|
|
324
367
|
)
|
|
325
368
|
|
|
326
369
|
process = await asyncio.create_subprocess_exec(
|
|
327
|
-
*
|
|
370
|
+
*command_args,
|
|
328
371
|
env=self.modified_env,
|
|
329
372
|
cwd=str(Path(self.source_dir / task.get("workdir", ""))),
|
|
330
373
|
stdout=asyncio.subprocess.PIPE,
|
|
@@ -388,15 +431,16 @@ class Runner:
|
|
|
388
431
|
|
|
389
432
|
@log_context(label="cleanup")
|
|
390
433
|
def cleanup(self) -> None:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
434
|
+
if stores := self.config.get("stores"):
|
|
435
|
+
for glob in stores:
|
|
436
|
+
# Glob relative to the source directory
|
|
437
|
+
matches = self.source_dir.rglob(glob)
|
|
438
|
+
|
|
439
|
+
for match in matches:
|
|
440
|
+
relative_path = PurePath(match).relative_to(self.source_dir)
|
|
441
|
+
dest = Path(get_artifacts_cache(self.job_run["id"]) / relative_path)
|
|
442
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
443
|
+
Path(match).replace(dest)
|
|
400
444
|
|
|
401
445
|
shutil.rmtree(path=self.source_dir)
|
|
402
446
|
|
|
@@ -18,6 +18,7 @@ from .projects.commands import cli as projects_commands
|
|
|
18
18
|
from .reservations.commands import cli as reservations_commands
|
|
19
19
|
from .monitor.commands import cli as monitor_commands
|
|
20
20
|
from .network.commands import cli as network_commands
|
|
21
|
+
from .operating_systems.commands import cli as operating_system_commands
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@click.group()
|
|
@@ -75,6 +76,7 @@ cli.add_command(reservations_commands, "reservations")
|
|
|
75
76
|
cli.add_command(exec_commands, "exec")
|
|
76
77
|
cli.add_command(monitor_commands, "monitor")
|
|
77
78
|
cli.add_command(network_commands, "network")
|
|
79
|
+
cli.add_command(operating_system_commands, "operating-systems")
|
|
78
80
|
|
|
79
81
|
if __name__ == "__main__":
|
|
80
82
|
cli(obj={})
|
|
@@ -17,6 +17,7 @@ from .hardware.actions import Hardware
|
|
|
17
17
|
from .jobs.actions import Jobs
|
|
18
18
|
from .monitor.actions import Monitor
|
|
19
19
|
from .network.actions import Network
|
|
20
|
+
from .operating_systems.actions import OperatingSystems
|
|
20
21
|
from .organizations.actions import Organizations
|
|
21
22
|
from .projects.actions import Projects
|
|
22
23
|
from .provisioning.actions import Provisioning
|
|
@@ -99,6 +100,7 @@ class Primitive:
|
|
|
99
100
|
self.provisioning: Provisioning = Provisioning(self)
|
|
100
101
|
self.monitor: Monitor = Monitor(self)
|
|
101
102
|
self.network: Network = Network(self)
|
|
103
|
+
self.operating_systems: OperatingSystems = OperatingSystems(self)
|
|
102
104
|
|
|
103
105
|
def get_host_config(self):
|
|
104
106
|
self.full_config = read_config_file()
|
|
@@ -62,6 +62,7 @@ class Files(BaseAction):
|
|
|
62
62
|
chunk_size: int,
|
|
63
63
|
number_of_parts: int,
|
|
64
64
|
is_public: bool = False,
|
|
65
|
+
organization_id: Optional[str] = None,
|
|
65
66
|
):
|
|
66
67
|
mutation = gql(pending_file_create_mutation)
|
|
67
68
|
input = {
|
|
@@ -74,6 +75,8 @@ class Files(BaseAction):
|
|
|
74
75
|
"chunkSize": chunk_size,
|
|
75
76
|
"numberOfParts": number_of_parts,
|
|
76
77
|
}
|
|
78
|
+
if organization_id:
|
|
79
|
+
input["organizationId"] = organization_id
|
|
77
80
|
variables = {"input": input}
|
|
78
81
|
result = self.primitive.session.execute(
|
|
79
82
|
mutation, variable_values=variables, get_execution_result=True
|
|
@@ -223,6 +226,7 @@ class Files(BaseAction):
|
|
|
223
226
|
is_public: bool = False,
|
|
224
227
|
key_prefix: str = "",
|
|
225
228
|
file_id: Optional[str] = None,
|
|
229
|
+
organization_id: Optional[str] = None,
|
|
226
230
|
):
|
|
227
231
|
if path.exists() is False:
|
|
228
232
|
raise Exception(f"File {path} does not exist.")
|
|
@@ -262,6 +266,7 @@ class Files(BaseAction):
|
|
|
262
266
|
is_public=is_public,
|
|
263
267
|
chunk_size=chunk_size,
|
|
264
268
|
number_of_parts=number_of_parts,
|
|
269
|
+
organization_id=organization_id,
|
|
265
270
|
)
|
|
266
271
|
file_id = pending_file_create.get("id")
|
|
267
272
|
parts_details = pending_file_create.get("partsDetails")
|
|
@@ -332,6 +337,7 @@ class Files(BaseAction):
|
|
|
332
337
|
path: Path,
|
|
333
338
|
is_public: bool = False,
|
|
334
339
|
key_prefix: str = "",
|
|
340
|
+
organization_id: Optional[str] = None,
|
|
335
341
|
):
|
|
336
342
|
"""
|
|
337
343
|
This method uploads a file via the Primitive API.
|
|
@@ -347,6 +353,11 @@ class Files(BaseAction):
|
|
|
347
353
|
+ file_path
|
|
348
354
|
+ """\", "keyPrefix": \""""
|
|
349
355
|
+ key_prefix
|
|
356
|
+
+ (
|
|
357
|
+
f'", "organizationId": "{organization_id}'
|
|
358
|
+
if organization_id
|
|
359
|
+
else ""
|
|
360
|
+
)
|
|
350
361
|
+ """\" } } }"""
|
|
351
362
|
) # noqa
|
|
352
363
|
|
|
@@ -356,6 +367,11 @@ class Files(BaseAction):
|
|
|
356
367
|
+ file_path
|
|
357
368
|
+ """\", "keyPrefix": \""""
|
|
358
369
|
+ key_prefix
|
|
370
|
+
+ (
|
|
371
|
+
f'", "organizationId": "{organization_id}'
|
|
372
|
+
if organization_id
|
|
373
|
+
else ""
|
|
374
|
+
)
|
|
359
375
|
+ """\" } } }"""
|
|
360
376
|
) # noqa
|
|
361
377
|
body = {
|
|
@@ -385,6 +401,7 @@ class Files(BaseAction):
|
|
|
385
401
|
output_path: Path = Path().cwd(),
|
|
386
402
|
) -> Path:
|
|
387
403
|
file_pk = None
|
|
404
|
+
file_size = None
|
|
388
405
|
|
|
389
406
|
files_result = self.primitive.files.files(
|
|
390
407
|
file_id=file_id,
|
|
@@ -396,6 +413,7 @@ class Files(BaseAction):
|
|
|
396
413
|
file = files_data["files"]["edges"][0]["node"]
|
|
397
414
|
file_pk = file["pk"]
|
|
398
415
|
file_name = file["fileName"]
|
|
416
|
+
file_size = int(file["fileSize"])
|
|
399
417
|
|
|
400
418
|
if not file_pk:
|
|
401
419
|
raise Exception(
|
|
@@ -404,7 +422,11 @@ class Files(BaseAction):
|
|
|
404
422
|
|
|
405
423
|
session = create_requests_session(host_config=self.primitive.host_config)
|
|
406
424
|
transport = self.primitive.host_config.get("transport")
|
|
407
|
-
|
|
425
|
+
|
|
426
|
+
if file_size and file_size < 5 * 1024 * 1024:
|
|
427
|
+
url = f"{transport}://{self.primitive.host}/files/{file_pk}/stream/"
|
|
428
|
+
else:
|
|
429
|
+
url = f"{transport}://{self.primitive.host}/files/{file_pk}/presigned-url/"
|
|
408
430
|
|
|
409
431
|
downloaded_file = output_path / file_name
|
|
410
432
|
|
|
@@ -283,7 +283,177 @@ class Hardware(BaseAction):
|
|
|
283
283
|
return {"linux_machine_id": machine_id}
|
|
284
284
|
return {}
|
|
285
285
|
|
|
286
|
-
def
|
|
286
|
+
def _get_homebrew_installed(self):
|
|
287
|
+
try:
|
|
288
|
+
raw = json.loads(
|
|
289
|
+
subprocess.check_output(["brew", "info", "--json=v2", "--installed"])
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if isinstance(raw, Dict):
|
|
293
|
+
raw_formulae = raw.get("formulae", [])
|
|
294
|
+
raw_casks = raw.get("casks", [])
|
|
295
|
+
|
|
296
|
+
formulae = []
|
|
297
|
+
casks = []
|
|
298
|
+
|
|
299
|
+
if isinstance(raw_formulae, List):
|
|
300
|
+
for raw_formula in raw_formulae:
|
|
301
|
+
try:
|
|
302
|
+
formulae.append(
|
|
303
|
+
{
|
|
304
|
+
"full_name": raw_formula["full_name"],
|
|
305
|
+
"tap": raw_formula["tap"],
|
|
306
|
+
"version": raw_formula["versions"]["stable"],
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
except Exception:
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
if isinstance(raw_casks, List):
|
|
313
|
+
for raw_cask in raw_casks:
|
|
314
|
+
try:
|
|
315
|
+
casks.append(
|
|
316
|
+
{
|
|
317
|
+
"full_token": raw_cask["full_token"],
|
|
318
|
+
"name": raw_cask["name"][0],
|
|
319
|
+
"version": raw_cask["installed"],
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
except Exception:
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
"formulae": formulae,
|
|
327
|
+
"casks": casks,
|
|
328
|
+
}
|
|
329
|
+
except Exception:
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
def _get_darwin_md_application(self, application_path: str):
|
|
333
|
+
lines = (
|
|
334
|
+
subprocess.check_output(
|
|
335
|
+
[
|
|
336
|
+
"mdls",
|
|
337
|
+
"-name",
|
|
338
|
+
"kMDItemDisplayName",
|
|
339
|
+
"-name",
|
|
340
|
+
"kMDItemVersion",
|
|
341
|
+
"-name",
|
|
342
|
+
"kMDItemCFBundleIdentifier",
|
|
343
|
+
application_path,
|
|
344
|
+
]
|
|
345
|
+
)
|
|
346
|
+
.strip()
|
|
347
|
+
.decode("utf-8")
|
|
348
|
+
.split("\n")
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
item = {
|
|
352
|
+
"path": application_path,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
for line in lines:
|
|
356
|
+
raw_key, raw_value = line.split(" = ")
|
|
357
|
+
|
|
358
|
+
key = raw_key.strip()
|
|
359
|
+
|
|
360
|
+
if raw_value == "(null)":
|
|
361
|
+
item[key] = None
|
|
362
|
+
elif raw_value.startswith('"') and raw_value.endswith('"'):
|
|
363
|
+
item[key] = raw_value[1:-1]
|
|
364
|
+
|
|
365
|
+
return item
|
|
366
|
+
|
|
367
|
+
def _get_darwin_md_applications(self):
|
|
368
|
+
import concurrent.futures
|
|
369
|
+
|
|
370
|
+
try:
|
|
371
|
+
application_paths = (
|
|
372
|
+
subprocess.check_output(["mdfind", "kMDItemKind == 'Application'"])
|
|
373
|
+
.strip()
|
|
374
|
+
.decode("utf-8")
|
|
375
|
+
.split("\n")
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
379
|
+
return list(
|
|
380
|
+
executor.map(self._get_darwin_md_application, application_paths)
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
except Exception:
|
|
384
|
+
pass
|
|
385
|
+
|
|
386
|
+
def _get_fedora_installed_packages(self):
|
|
387
|
+
try:
|
|
388
|
+
lines = (
|
|
389
|
+
subprocess.check_output(
|
|
390
|
+
[
|
|
391
|
+
"dnf",
|
|
392
|
+
"repoquery",
|
|
393
|
+
"--installed",
|
|
394
|
+
"--qf",
|
|
395
|
+
"%{name} , %{version}\n",
|
|
396
|
+
]
|
|
397
|
+
)
|
|
398
|
+
.decode("utf-8")
|
|
399
|
+
.strip()
|
|
400
|
+
.split("\n")
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
items = []
|
|
404
|
+
|
|
405
|
+
for line in lines:
|
|
406
|
+
try:
|
|
407
|
+
name, version = line.split(" , ")
|
|
408
|
+
|
|
409
|
+
items.append(
|
|
410
|
+
{
|
|
411
|
+
"name": name,
|
|
412
|
+
"version": version,
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
except Exception:
|
|
416
|
+
pass
|
|
417
|
+
return items
|
|
418
|
+
except Exception:
|
|
419
|
+
pass
|
|
420
|
+
|
|
421
|
+
def _get_ubuntu_installed_packages(self):
|
|
422
|
+
try:
|
|
423
|
+
lines = (
|
|
424
|
+
subprocess.check_output(["apt", "list", "--installed"])
|
|
425
|
+
.decode("utf-8")
|
|
426
|
+
.strip()
|
|
427
|
+
.split("\n")
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
lines.pop()
|
|
431
|
+
|
|
432
|
+
items = []
|
|
433
|
+
|
|
434
|
+
for line in lines:
|
|
435
|
+
try:
|
|
436
|
+
columns = line.split()
|
|
437
|
+
|
|
438
|
+
if len(columns) < 2:
|
|
439
|
+
continue
|
|
440
|
+
|
|
441
|
+
name, version, *_ = columns
|
|
442
|
+
items.append(
|
|
443
|
+
{
|
|
444
|
+
"name": name.split("/")[0],
|
|
445
|
+
"version": version,
|
|
446
|
+
}
|
|
447
|
+
)
|
|
448
|
+
except Exception:
|
|
449
|
+
pass
|
|
450
|
+
|
|
451
|
+
return items
|
|
452
|
+
|
|
453
|
+
except Exception:
|
|
454
|
+
pass
|
|
455
|
+
|
|
456
|
+
def get_system_info(self, with_installed_applications: bool = False):
|
|
287
457
|
os_family = platform.system()
|
|
288
458
|
system_info = {}
|
|
289
459
|
if os_family == "Darwin":
|
|
@@ -294,6 +464,13 @@ class Hardware(BaseAction):
|
|
|
294
464
|
.decode("utf-8")
|
|
295
465
|
)
|
|
296
466
|
system_info["apple_mac_os_version"] = platform.mac_ver()[0]
|
|
467
|
+
system_info["homebrew_installed"] = self._get_homebrew_installed()
|
|
468
|
+
|
|
469
|
+
if with_installed_applications:
|
|
470
|
+
system_info["darwin_md_applications"] = (
|
|
471
|
+
self._get_darwin_md_applications()
|
|
472
|
+
)
|
|
473
|
+
|
|
297
474
|
elif os_family == "Linux":
|
|
298
475
|
# Support for Linux-based VMs in Windows
|
|
299
476
|
if "WSL2" in platform.platform():
|
|
@@ -304,6 +481,18 @@ class Hardware(BaseAction):
|
|
|
304
481
|
}
|
|
305
482
|
else:
|
|
306
483
|
system_info = {**system_info, **self._get_ubuntu_values()}
|
|
484
|
+
system_info["linux_release"] = platform.freedesktop_os_release()
|
|
485
|
+
|
|
486
|
+
match system_info["linux_release"]["ID"]:
|
|
487
|
+
case "ubuntu":
|
|
488
|
+
system_info["ubuntu_installed_packages"] = (
|
|
489
|
+
self._get_ubuntu_installed_packages()
|
|
490
|
+
)
|
|
491
|
+
case "fedora":
|
|
492
|
+
system_info["fedora_installed_packages"] = (
|
|
493
|
+
self._get_fedora_installed_packages()
|
|
494
|
+
)
|
|
495
|
+
|
|
307
496
|
elif os_family == "Windows":
|
|
308
497
|
system_info = {
|
|
309
498
|
**system_info,
|
|
@@ -981,3 +1170,39 @@ class Hardware(BaseAction):
|
|
|
981
1170
|
logger.info(message)
|
|
982
1171
|
|
|
983
1172
|
return result
|
|
1173
|
+
|
|
1174
|
+
@guard
|
|
1175
|
+
async def aupdate_hardware(
|
|
1176
|
+
self,
|
|
1177
|
+
hardware_id: str,
|
|
1178
|
+
is_online: Optional[bool] = None,
|
|
1179
|
+
is_rebooting: Optional[bool] = None,
|
|
1180
|
+
start_rebooting_at: Optional[str] = None,
|
|
1181
|
+
):
|
|
1182
|
+
new_state: dict = {
|
|
1183
|
+
"id": hardware_id,
|
|
1184
|
+
}
|
|
1185
|
+
if is_online is not None:
|
|
1186
|
+
new_state["isOnline"] = is_online
|
|
1187
|
+
if is_rebooting is not None:
|
|
1188
|
+
new_state["isRebooting"] = is_rebooting
|
|
1189
|
+
if start_rebooting_at is not None:
|
|
1190
|
+
new_state["startRebootingAt"] = start_rebooting_at
|
|
1191
|
+
|
|
1192
|
+
mutation = gql(hardware_update_mutation)
|
|
1193
|
+
|
|
1194
|
+
input = new_state
|
|
1195
|
+
variables = {"input": input}
|
|
1196
|
+
try:
|
|
1197
|
+
result = await self.primitive.session.execute_async(
|
|
1198
|
+
mutation, variable_values=variables, get_execution_result=True
|
|
1199
|
+
)
|
|
1200
|
+
except client_exceptions.ClientConnectorError as exception:
|
|
1201
|
+
message = "Failed to update hardware! "
|
|
1202
|
+
logger.exception(message)
|
|
1203
|
+
raise exception
|
|
1204
|
+
|
|
1205
|
+
message = "Updated hardware successfully! "
|
|
1206
|
+
logger.info(message)
|
|
1207
|
+
|
|
1208
|
+
return result
|
|
@@ -22,11 +22,22 @@ def cli(context):
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@cli.command("systeminfo")
|
|
25
|
+
@click.option(
|
|
26
|
+
"--with-installed-applications",
|
|
27
|
+
is_flag=True,
|
|
28
|
+
default=False,
|
|
29
|
+
help="Only for MacOS, list installed applications using metadata commands",
|
|
30
|
+
)
|
|
25
31
|
@click.pass_context
|
|
26
|
-
def systeminfo_command(
|
|
32
|
+
def systeminfo_command(
|
|
33
|
+
context,
|
|
34
|
+
with_installed_applications: bool = False,
|
|
35
|
+
):
|
|
27
36
|
"""Get System Info"""
|
|
28
37
|
primitive: Primitive = context.obj.get("PRIMITIVE")
|
|
29
|
-
message = primitive.hardware.get_system_info(
|
|
38
|
+
message = primitive.hardware.get_system_info(
|
|
39
|
+
with_installed_applications=with_installed_applications
|
|
40
|
+
)
|
|
30
41
|
print_result(message=message, context=context)
|
|
31
42
|
|
|
32
43
|
|
|
@@ -191,11 +191,16 @@ class Monitor(BaseAction):
|
|
|
191
191
|
job_run_id = None
|
|
192
192
|
else:
|
|
193
193
|
hardware_id = hardware.get("id", None) if hardware else None
|
|
194
|
-
execution_hardware_id =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
execution_hardware_id = None
|
|
195
|
+
if job_run_data:
|
|
196
|
+
execution_hardware = job_run_data.get(
|
|
197
|
+
"executionHardware", None
|
|
198
|
+
)
|
|
199
|
+
execution_hardware_id = (
|
|
200
|
+
execution_hardware.get("id", None)
|
|
201
|
+
if execution_hardware
|
|
202
|
+
else None
|
|
203
|
+
)
|
|
199
204
|
|
|
200
205
|
if (
|
|
201
206
|
hardware_id is not None
|