primitive 0.2.58__tar.gz → 0.2.60__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.
- {primitive-0.2.58 → primitive-0.2.60}/.vscode/settings.json +3 -0
- {primitive-0.2.58 → primitive-0.2.60}/PKG-INFO +2 -1
- {primitive-0.2.58 → primitive-0.2.60}/pyproject.toml +1 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/__about__.py +1 -1
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/client.py +4 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/files/actions.py +73 -7
- primitive-0.2.60/src/primitive/files/commands.py +100 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/files/graphql/fragments.py +5 -2
- primitive-0.2.60/src/primitive/files/ui.py +37 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/graphql/sdk.py +1 -1
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/actions.py +97 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/commands.py +56 -19
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/graphql/mutations.py +18 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/jobs/actions.py +1 -0
- primitive-0.2.60/src/primitive/messaging/provider.py +127 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/monitor/actions.py +2 -0
- primitive-0.2.60/src/primitive/utils/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/actions.py +2 -2
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/printer.py +1 -1
- primitive-0.2.60/src/primitive/utils/x509.py +140 -0
- {primitive-0.2.58 → primitive-0.2.60}/uv.lock +11 -0
- primitive-0.2.58/src/primitive/files/commands.py +0 -43
- {primitive-0.2.58 → primitive-0.2.60}/.git-hooks/pre-commit +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/.gitattributes +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/.github/workflows/lint.yml +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/.github/workflows/publish.yml +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/.github/workflows/pyright.yml +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/.gitignore +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/.vscode/extensions.json +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/LICENSE.txt +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/Makefile +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/README.md +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/linux setup.md +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/agent/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/agent/actions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/agent/commands.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/agent/runner.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/agent/uploader.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/auth/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/auth/actions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/auth/commands.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/auth/graphql/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/auth/graphql/queries.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/cli.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/daemons/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/daemons/actions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/daemons/commands.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/daemons/launch_agents.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/daemons/launch_service.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/daemons/ui.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/exec/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/exec/actions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/exec/commands.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/exec/interactive.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/files/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/files/graphql/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/files/graphql/mutations.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/files/graphql/queries.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/git/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/git/actions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/git/commands.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/git/graphql/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/git/graphql/queries.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/graphql/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/graphql/relay.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/graphql/utility_fragments.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/android.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/graphql/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/graphql/fragments.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/graphql/queries.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/hardware/ui.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/jobs/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/jobs/commands.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/jobs/graphql/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/jobs/graphql/fragments.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/jobs/graphql/mutations.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/jobs/graphql/queries.py +0 -0
- {primitive-0.2.58/src/primitive/organizations → primitive-0.2.60/src/primitive/messaging}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/monitor/commands.py +0 -0
- {primitive-0.2.58/src/primitive/organizations/graphql → primitive-0.2.60/src/primitive/organizations}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/organizations/actions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/organizations/commands.py +0 -0
- {primitive-0.2.58/src/primitive/projects → primitive-0.2.60/src/primitive/organizations/graphql}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/organizations/graphql/fragments.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/organizations/graphql/mutations.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/organizations/graphql/queries.py +0 -0
- {primitive-0.2.58/src/primitive/projects/graphql → primitive-0.2.60/src/primitive/projects}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/projects/actions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/projects/commands.py +0 -0
- {primitive-0.2.58/src/primitive/provisioning → primitive-0.2.60/src/primitive/projects/graphql}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/projects/graphql/fragments.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/projects/graphql/mutations.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/projects/graphql/queries.py +0 -0
- {primitive-0.2.58/src/primitive/provisioning/graphql → primitive-0.2.60/src/primitive/provisioning}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/provisioning/actions.py +0 -0
- {primitive-0.2.58/src/primitive/reservations → primitive-0.2.60/src/primitive/provisioning/graphql}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/provisioning/graphql/queries.py +0 -0
- {primitive-0.2.58/src/primitive/reservations/graphql → primitive-0.2.60/src/primitive/reservations}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/reservations/actions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/reservations/commands.py +0 -0
- {primitive-0.2.58/src/primitive/utils → primitive-0.2.60/src/primitive/reservations/graphql}/__init__.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/reservations/graphql/fragments.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/reservations/graphql/mutations.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/reservations/graphql/queries.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/auth.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/cache.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/chunk_size.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/config.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/daemons.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/exceptions.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/logging.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/memory_size.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/psutil.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/shell.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/src/primitive/utils/text.py +0 -0
- {primitive-0.2.58 → primitive-0.2.60}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: primitive
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.60
|
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
|
@@ -22,6 +22,7 @@ Requires-Dist: click
|
|
22
22
|
Requires-Dist: gql[all]
|
23
23
|
Requires-Dist: loguru
|
24
24
|
Requires-Dist: paramiko[invoke]
|
25
|
+
Requires-Dist: pika>=1.3.2
|
25
26
|
Requires-Dist: psutil>=7.0.0
|
26
27
|
Requires-Dist: rich>=13.9.4
|
27
28
|
Requires-Dist: speedtest-cli
|
@@ -5,6 +5,8 @@ from loguru import logger
|
|
5
5
|
from rich.logging import RichHandler
|
6
6
|
from rich.traceback import install
|
7
7
|
|
8
|
+
from primitive.messaging.provider import MessagingProvider
|
9
|
+
|
8
10
|
from .agent.actions import Agent
|
9
11
|
from .auth.actions import Auth
|
10
12
|
from .daemons.actions import Daemons
|
@@ -80,6 +82,8 @@ class Primitive:
|
|
80
82
|
else:
|
81
83
|
self.host_config = {"username": "", "token": token, "transport": transport}
|
82
84
|
|
85
|
+
self.messaging: MessagingProvider = MessagingProvider(self)
|
86
|
+
|
83
87
|
self.auth: Auth = Auth(self)
|
84
88
|
self.organizations: Organizations = Organizations(self)
|
85
89
|
self.projects: Projects = Projects(self)
|
@@ -124,15 +124,39 @@ class Files(BaseAction):
|
|
124
124
|
return result
|
125
125
|
|
126
126
|
@guard
|
127
|
-
def
|
127
|
+
def files(
|
128
|
+
self,
|
129
|
+
file_id: Optional[str] = None,
|
130
|
+
file_name: Optional[str] = None,
|
131
|
+
organization_id: Optional[str] = None,
|
132
|
+
organization_slug: Optional[str] = None,
|
133
|
+
):
|
128
134
|
query = gql(files_list)
|
129
135
|
|
130
136
|
filters = {}
|
137
|
+
if not organization_id and not organization_slug:
|
138
|
+
whoami_result = self.primitive.auth.whoami()
|
139
|
+
default_organization = whoami_result.data["whoami"]["defaultOrganization"]
|
140
|
+
organization_id = default_organization["id"]
|
141
|
+
logger.info(
|
142
|
+
f"Using default organization ID: {default_organization.get('slug')} ({organization_id})"
|
143
|
+
)
|
144
|
+
if organization_slug and not organization_id:
|
145
|
+
organization = self.primitive.organizations.get_organization(
|
146
|
+
slug=organization_slug
|
147
|
+
)
|
148
|
+
organization_id = organization.get("id")
|
149
|
+
|
150
|
+
if organization_id:
|
151
|
+
filters["organization"] = {"id": organization_id}
|
152
|
+
|
131
153
|
if file_id:
|
132
|
-
filters["id"] =
|
154
|
+
filters["id"] = file_id
|
155
|
+
if file_name:
|
156
|
+
filters["fileName"] = {"exact": file_name}
|
133
157
|
|
134
158
|
variables = {
|
135
|
-
"first":
|
159
|
+
"first": 25,
|
136
160
|
"filters": filters,
|
137
161
|
}
|
138
162
|
result = self.primitive.session.execute(
|
@@ -242,9 +266,9 @@ class Files(BaseAction):
|
|
242
266
|
file_id = pending_file_create.get("id")
|
243
267
|
parts_details = pending_file_create.get("partsDetails")
|
244
268
|
else:
|
245
|
-
|
269
|
+
file_result = self.files(file_id=file_id)
|
246
270
|
parts_details = (
|
247
|
-
|
271
|
+
file_result.data.get("files")
|
248
272
|
.get("edges")[0]
|
249
273
|
.get("node")
|
250
274
|
.get("partsDetails")
|
@@ -340,14 +364,56 @@ class Files(BaseAction):
|
|
340
364
|
"fileObject": (path.name, open(path, "rb")),
|
341
365
|
}
|
342
366
|
|
343
|
-
session = create_requests_session(self.primitive.host_config)
|
367
|
+
session = create_requests_session(host_config=self.primitive.host_config)
|
344
368
|
transport = self.primitive.host_config.get("transport")
|
345
369
|
url = f"{transport}://{self.primitive.host}/"
|
346
370
|
response = session.post(url, files=body)
|
347
371
|
return response
|
348
372
|
|
349
|
-
def get_presigned_url(self, file_pk: str):
|
373
|
+
def get_presigned_url(self, file_pk: str) -> str:
|
350
374
|
transport = self.primitive.host_config.get("transport")
|
351
375
|
host = self.primitive.host_config.get("host")
|
352
376
|
file_access_url = f"{transport}://{host}/files/{file_pk}/presigned-url/"
|
353
377
|
return file_access_url
|
378
|
+
|
379
|
+
def download_file(
|
380
|
+
self,
|
381
|
+
file_name: str = "",
|
382
|
+
file_id: str = "",
|
383
|
+
organization_id: str = "",
|
384
|
+
organization_slug: str = "",
|
385
|
+
output_path: Path = Path().cwd(),
|
386
|
+
) -> Path:
|
387
|
+
file_pk = None
|
388
|
+
|
389
|
+
files_result = self.primitive.files.files(
|
390
|
+
file_id=file_id,
|
391
|
+
file_name=file_name,
|
392
|
+
organization_id=organization_id,
|
393
|
+
organization_slug=organization_slug,
|
394
|
+
)
|
395
|
+
if files_data := files_result.data:
|
396
|
+
file = files_data["files"]["edges"][0]["node"]
|
397
|
+
file_pk = file["pk"]
|
398
|
+
file_name = file["fileName"]
|
399
|
+
|
400
|
+
if not file_pk:
|
401
|
+
raise Exception(
|
402
|
+
"File not found on remote server. Please check file name or file id"
|
403
|
+
)
|
404
|
+
|
405
|
+
session = create_requests_session(host_config=self.primitive.host_config)
|
406
|
+
transport = self.primitive.host_config.get("transport")
|
407
|
+
url = f"{transport}://{self.primitive.host}/files/{file_pk}/stream/"
|
408
|
+
|
409
|
+
downloaded_file = output_path / file_name
|
410
|
+
|
411
|
+
with session.get(url, stream=True) as response:
|
412
|
+
response.raise_for_status()
|
413
|
+
with open(downloaded_file, "wb") as partial_downloaded_file:
|
414
|
+
for chunk in response.iter_content(chunk_size=8192):
|
415
|
+
if chunk:
|
416
|
+
partial_downloaded_file.write(chunk)
|
417
|
+
partial_downloaded_file.flush()
|
418
|
+
|
419
|
+
return downloaded_file
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import json
|
2
|
+
import typing
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
import click
|
6
|
+
|
7
|
+
from primitive.files.ui import render_files_table
|
8
|
+
|
9
|
+
from ..utils.printer import print_result
|
10
|
+
|
11
|
+
if typing.TYPE_CHECKING:
|
12
|
+
from ..client import Primitive
|
13
|
+
|
14
|
+
|
15
|
+
@click.group("files")
|
16
|
+
@click.pass_context
|
17
|
+
def cli(context):
|
18
|
+
"""Files"""
|
19
|
+
pass
|
20
|
+
|
21
|
+
|
22
|
+
@cli.command("upload")
|
23
|
+
@click.pass_context
|
24
|
+
@click.argument("path", type=click.Path(exists=True))
|
25
|
+
@click.option("--public", "-p", help="Is this a Public file", is_flag=True)
|
26
|
+
@click.option("--key-prefix", "-k", help="Key Prefix", default="")
|
27
|
+
@click.option("--direct", help="direct", is_flag=True)
|
28
|
+
def file_upload_command(context, path, public, key_prefix, direct):
|
29
|
+
"""File Upload"""
|
30
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
31
|
+
path = Path(path)
|
32
|
+
if direct:
|
33
|
+
result = primitive.files.upload_file_direct(
|
34
|
+
path, is_public=public, key_prefix=key_prefix
|
35
|
+
)
|
36
|
+
else:
|
37
|
+
result = primitive.files.upload_file_via_api(
|
38
|
+
path, is_public=public, key_prefix=key_prefix
|
39
|
+
)
|
40
|
+
try:
|
41
|
+
message = json.dumps(result.json())
|
42
|
+
except AttributeError:
|
43
|
+
message = "File Upload Failed"
|
44
|
+
|
45
|
+
print_result(message=message, context=context)
|
46
|
+
|
47
|
+
|
48
|
+
@cli.command("download")
|
49
|
+
@click.pass_context
|
50
|
+
@click.option("--file-id", help="File ID", required=False)
|
51
|
+
@click.option("--file-name", help="File Name", required=False)
|
52
|
+
@click.option("--output", help="Output Path", required=False, type=click.Path())
|
53
|
+
@click.option("--organization-id", help="Organization ID", required=False)
|
54
|
+
@click.option("--organization", help="Organization Slug", required=False)
|
55
|
+
def file_download_command(
|
56
|
+
context,
|
57
|
+
file_id=None,
|
58
|
+
file_name=None,
|
59
|
+
output=None,
|
60
|
+
organization_id=None,
|
61
|
+
organization=None,
|
62
|
+
):
|
63
|
+
"""File Download"""
|
64
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
65
|
+
|
66
|
+
if not file_id and not file_name:
|
67
|
+
raise click.UsageError("Either --id or --file-name must be provided.")
|
68
|
+
|
69
|
+
if not output:
|
70
|
+
output = Path().cwd()
|
71
|
+
else:
|
72
|
+
output = Path(output)
|
73
|
+
|
74
|
+
downloaded_file = primitive.files.download_file(
|
75
|
+
output_path=output,
|
76
|
+
file_id=file_id,
|
77
|
+
file_name=file_name,
|
78
|
+
organization_id=organization_id,
|
79
|
+
organization_slug=organization,
|
80
|
+
)
|
81
|
+
print_result(message=f"File downloaded to {downloaded_file}", context=context)
|
82
|
+
|
83
|
+
|
84
|
+
@cli.command("list")
|
85
|
+
@click.pass_context
|
86
|
+
@click.option("--organization-id", help="Organization ID", required=False)
|
87
|
+
@click.option("--organization", help="Organization Slug", required=False)
|
88
|
+
def list_command(context, organization_id=None, organization=None):
|
89
|
+
"""List Files"""
|
90
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
91
|
+
files_result = primitive.files.files(
|
92
|
+
organization_id=organization_id, organization_slug=organization
|
93
|
+
)
|
94
|
+
|
95
|
+
files = [file.get("node") for file in files_result.data.get("files").get("edges")]
|
96
|
+
|
97
|
+
if context.obj["JSON"]:
|
98
|
+
print_result(message=files, context=context)
|
99
|
+
else:
|
100
|
+
render_files_table(files)
|
@@ -4,7 +4,11 @@ fragment FileFragment on File {
|
|
4
4
|
pk
|
5
5
|
createdAt
|
6
6
|
updatedAt
|
7
|
-
createdBy
|
7
|
+
createdBy {
|
8
|
+
id
|
9
|
+
pk
|
10
|
+
username
|
11
|
+
}
|
8
12
|
location
|
9
13
|
fileName
|
10
14
|
fileSize
|
@@ -13,6 +17,5 @@ fragment FileFragment on File {
|
|
13
17
|
isComplete
|
14
18
|
partsDetails
|
15
19
|
humanReadableMemorySize
|
16
|
-
contents
|
17
20
|
}
|
18
21
|
"""
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from rich.table import Table
|
3
|
+
|
4
|
+
|
5
|
+
def render_files_table(file_list) -> None:
|
6
|
+
console = Console()
|
7
|
+
|
8
|
+
table = Table(show_header=True, header_style="bold #FFA800")
|
9
|
+
table.add_column("File Name")
|
10
|
+
table.add_column("File ID")
|
11
|
+
table.add_column("File Size (bytes)", justify="right")
|
12
|
+
|
13
|
+
for file in file_list:
|
14
|
+
file_name = file.get("fileName")
|
15
|
+
file_id = file.get("id")
|
16
|
+
file_size = file.get("fileSize")
|
17
|
+
|
18
|
+
table.add_row(
|
19
|
+
file_name,
|
20
|
+
file_id,
|
21
|
+
file_size,
|
22
|
+
)
|
23
|
+
|
24
|
+
console.print(table)
|
25
|
+
|
26
|
+
|
27
|
+
def file_status_string(file) -> str:
|
28
|
+
if file.get("isQuarantined"):
|
29
|
+
return "Quarantined"
|
30
|
+
if not file.get("isOnline"):
|
31
|
+
return "Offline"
|
32
|
+
if not file.get("isHealthy"):
|
33
|
+
return "Not healthy"
|
34
|
+
if not file.get("isAvailable"):
|
35
|
+
return "Not available"
|
36
|
+
else:
|
37
|
+
return "Available"
|
@@ -26,7 +26,7 @@ def create_session(
|
|
26
26
|
if fingerprint:
|
27
27
|
headers["x-primitive-fingerprint"] = fingerprint
|
28
28
|
|
29
|
-
transport = AIOHTTPTransport(url=url, headers=headers)
|
29
|
+
transport = AIOHTTPTransport(url=url, headers=headers, ssl=True)
|
30
30
|
session = Client(
|
31
31
|
transport=transport,
|
32
32
|
fetch_schema_from_transport=fetch_schema_from_transport,
|
@@ -4,15 +4,18 @@ import json
|
|
4
4
|
import platform
|
5
5
|
import subprocess
|
6
6
|
import typing
|
7
|
+
from dataclasses import dataclass
|
7
8
|
from shutil import which
|
8
9
|
from typing import Dict, List, Optional
|
9
10
|
|
10
11
|
import click
|
12
|
+
import psutil
|
11
13
|
from aiohttp import client_exceptions
|
12
14
|
from gql import gql
|
13
15
|
from loguru import logger
|
14
16
|
|
15
17
|
from primitive.graphql.relay import from_base64
|
18
|
+
from primitive.messaging.provider import MESSAGE_TYPES
|
16
19
|
from primitive.utils.memory_size import MemorySize
|
17
20
|
|
18
21
|
from ..utils.auth import guard
|
@@ -24,6 +27,7 @@ from .graphql.mutations import (
|
|
24
27
|
register_child_hardware_mutation,
|
25
28
|
register_hardware_mutation,
|
26
29
|
unregister_hardware_mutation,
|
30
|
+
hardware_certificate_create_mutation,
|
27
31
|
)
|
28
32
|
from .graphql.queries import (
|
29
33
|
hardware_details,
|
@@ -31,6 +35,7 @@ from .graphql.queries import (
|
|
31
35
|
nested_children_hardware_list,
|
32
36
|
)
|
33
37
|
|
38
|
+
|
34
39
|
if typing.TYPE_CHECKING:
|
35
40
|
pass
|
36
41
|
|
@@ -40,6 +45,12 @@ from primitive.utils.actions import BaseAction
|
|
40
45
|
from primitive.utils.shell import does_executable_exist
|
41
46
|
|
42
47
|
|
48
|
+
@dataclass
|
49
|
+
class CertificateCreateResult:
|
50
|
+
certificate_id: str
|
51
|
+
certificate_pem: str
|
52
|
+
|
53
|
+
|
43
54
|
class Hardware(BaseAction):
|
44
55
|
def __init__(self, *args, **kwargs) -> None:
|
45
56
|
super().__init__(*args, **kwargs)
|
@@ -280,6 +291,48 @@ class Hardware(BaseAction):
|
|
280
291
|
system_info["gpu_config"] = self._get_gpu_config()
|
281
292
|
return system_info
|
282
293
|
|
294
|
+
@guard
|
295
|
+
def certificate_create(
|
296
|
+
self, hardware_id: str, csr_pem: str
|
297
|
+
) -> CertificateCreateResult:
|
298
|
+
mutation = gql(hardware_certificate_create_mutation)
|
299
|
+
variables = {
|
300
|
+
"input": {
|
301
|
+
"hardwareId": hardware_id,
|
302
|
+
"csrPem": csr_pem,
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
if not self.primitive.session:
|
307
|
+
raise Exception("No active session available for certificate creation")
|
308
|
+
|
309
|
+
result = self.primitive.session.execute(
|
310
|
+
mutation,
|
311
|
+
variable_values=variables,
|
312
|
+
get_execution_result=True,
|
313
|
+
)
|
314
|
+
|
315
|
+
if result.errors:
|
316
|
+
message = " ".join([error.message for error in result.errors])
|
317
|
+
raise Exception(message)
|
318
|
+
|
319
|
+
if not result.data:
|
320
|
+
raise Exception(
|
321
|
+
"No data received from hardware certificate creation request"
|
322
|
+
)
|
323
|
+
|
324
|
+
hardware_certificate_create = result.data["hardwareCertificateCreate"]
|
325
|
+
|
326
|
+
if hardware_certificate_create["__typename"] == "OperationInfo":
|
327
|
+
messages = hardware_certificate_create["messages"]
|
328
|
+
message = " ".join([error["message"] for error in messages])
|
329
|
+
raise Exception(message)
|
330
|
+
|
331
|
+
return CertificateCreateResult(
|
332
|
+
certificate_id=hardware_certificate_create["id"],
|
333
|
+
certificate_pem=hardware_certificate_create["certificatePem"],
|
334
|
+
)
|
335
|
+
|
283
336
|
@guard
|
284
337
|
def register(self, organization_id: Optional[str] = None):
|
285
338
|
system_info = self.get_system_info()
|
@@ -375,6 +428,24 @@ class Hardware(BaseAction):
|
|
375
428
|
|
376
429
|
return result
|
377
430
|
|
431
|
+
def check_in(
|
432
|
+
self,
|
433
|
+
is_healthy: bool = True,
|
434
|
+
is_quarantined: bool = False,
|
435
|
+
is_available: bool = False,
|
436
|
+
is_online: bool = True,
|
437
|
+
stopping_agent: Optional[bool] = False,
|
438
|
+
):
|
439
|
+
message = {
|
440
|
+
"is_healthy": is_healthy,
|
441
|
+
"is_quarantined": is_quarantined,
|
442
|
+
"is_available": is_available,
|
443
|
+
"is_online": is_online,
|
444
|
+
}
|
445
|
+
self.primitive.messaging.send_message(
|
446
|
+
message_type=MESSAGE_TYPES.CHECK_IN, message=message
|
447
|
+
)
|
448
|
+
|
378
449
|
@guard
|
379
450
|
def check_in_http(
|
380
451
|
self,
|
@@ -667,3 +738,29 @@ class Hardware(BaseAction):
|
|
667
738
|
|
668
739
|
except Exception as exception:
|
669
740
|
logger.exception(f"Error checking in children: {exception}")
|
741
|
+
|
742
|
+
def push_metrics(self):
|
743
|
+
if self.primitive.messaging.ready:
|
744
|
+
self.primitive.messaging.send_message(
|
745
|
+
message_type=MESSAGE_TYPES.METRICS,
|
746
|
+
message=self.get_metrics(),
|
747
|
+
)
|
748
|
+
|
749
|
+
def get_metrics(self):
|
750
|
+
cpu_percent = psutil.cpu_percent(interval=1, percpu=False)
|
751
|
+
virtual_memory = psutil.virtual_memory()
|
752
|
+
disk_usage = psutil.disk_usage("/")
|
753
|
+
|
754
|
+
metrics = {
|
755
|
+
"cpu_percent": cpu_percent,
|
756
|
+
"memory_percent": virtual_memory.percent,
|
757
|
+
"memory_total": virtual_memory.total,
|
758
|
+
"memory_available": virtual_memory.available,
|
759
|
+
"memory_used": virtual_memory.used,
|
760
|
+
"memory_free": virtual_memory.free,
|
761
|
+
"disk_percent": disk_usage.percent,
|
762
|
+
"disk_total": disk_usage.total,
|
763
|
+
"disk_used": disk_usage.used,
|
764
|
+
"disk_free": disk_usage.free,
|
765
|
+
}
|
766
|
+
return metrics
|
@@ -1,15 +1,18 @@
|
|
1
|
+
from time import sleep
|
1
2
|
from typing import TYPE_CHECKING, Optional
|
2
3
|
|
3
4
|
import click
|
4
|
-
|
5
|
-
from
|
6
|
-
from .ui import render_hardware_table
|
5
|
+
from loguru import logger
|
6
|
+
from primitive.utils.printer import print_result
|
7
|
+
from primitive.hardware.ui import render_hardware_table
|
8
|
+
from primitive.utils.x509 import (
|
9
|
+
generate_csr_pem,
|
10
|
+
write_certificate_pem,
|
11
|
+
)
|
7
12
|
|
8
13
|
if TYPE_CHECKING:
|
9
14
|
from ..client import Primitive
|
10
15
|
|
11
|
-
from loguru import logger
|
12
|
-
|
13
16
|
|
14
17
|
@click.group()
|
15
18
|
@click.pass_context
|
@@ -33,8 +36,17 @@ def systeminfo_command(context):
|
|
33
36
|
type=str,
|
34
37
|
help="Organization [slug] to register hardware with",
|
35
38
|
)
|
39
|
+
@click.option(
|
40
|
+
"--issue-certificate",
|
41
|
+
is_flag=True,
|
42
|
+
show_default=True,
|
43
|
+
default=False,
|
44
|
+
help="Issue certificate.",
|
45
|
+
)
|
36
46
|
@click.pass_context
|
37
|
-
def register_command(
|
47
|
+
def register_command(
|
48
|
+
context, organization: Optional[str] = None, issue_certificate: bool = False
|
49
|
+
):
|
38
50
|
"""Register Hardware with Primitive"""
|
39
51
|
primitive: Primitive = context.obj.get("PRIMITIVE")
|
40
52
|
|
@@ -47,14 +59,30 @@ def register_command(context, organization: Optional[str] = None):
|
|
47
59
|
logger.info("Registering hardware with the default organization.")
|
48
60
|
|
49
61
|
result = primitive.hardware.register(organization_id=organization_id)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
62
|
+
hardware = result.data.get("registerHardware")
|
63
|
+
|
64
|
+
if not hardware:
|
65
|
+
print_result(
|
66
|
+
fg="red",
|
67
|
+
context=context,
|
68
|
+
message="There was an error registering this device. Please review the above logs.",
|
56
69
|
)
|
57
|
-
|
70
|
+
return
|
71
|
+
|
72
|
+
if issue_certificate:
|
73
|
+
certificate = primitive.hardware.certificate_create(
|
74
|
+
hardware_id=hardware["id"],
|
75
|
+
csr_pem=generate_csr_pem(
|
76
|
+
hardware_id=hardware["pk"],
|
77
|
+
),
|
78
|
+
)
|
79
|
+
write_certificate_pem(certificate.certificate_pem)
|
80
|
+
|
81
|
+
print_result(
|
82
|
+
fg="green",
|
83
|
+
context=context,
|
84
|
+
message="Hardware registered successfully.",
|
85
|
+
)
|
58
86
|
|
59
87
|
|
60
88
|
@cli.command("unregister")
|
@@ -77,12 +105,7 @@ def unregister_command(context):
|
|
77
105
|
def checkin_command(context):
|
78
106
|
"""Checkin Hardware with Primitive"""
|
79
107
|
primitive: Primitive = context.obj.get("PRIMITIVE")
|
80
|
-
|
81
|
-
if messages := check_in_http_result.data.get("checkIn").get("messages"):
|
82
|
-
print_result(message=messages, context=context, fg="yellow")
|
83
|
-
else:
|
84
|
-
message = "Hardware checked in successfully"
|
85
|
-
print_result(message=message, context=context, fg="green")
|
108
|
+
primitive.hardware.check_in()
|
86
109
|
|
87
110
|
|
88
111
|
@cli.command("list")
|
@@ -125,3 +148,17 @@ def get_command(context, hardware_identifier: str) -> None:
|
|
125
148
|
return
|
126
149
|
else:
|
127
150
|
render_hardware_table([hardware])
|
151
|
+
|
152
|
+
|
153
|
+
@cli.command("metrics")
|
154
|
+
@click.pass_context
|
155
|
+
@click.option("--watch", is_flag=True, help="Watch hardware metrics")
|
156
|
+
def metrics_command(context, watch: bool = False) -> None:
|
157
|
+
"""Get Hardware Metrics"""
|
158
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
159
|
+
if watch:
|
160
|
+
while True:
|
161
|
+
print_result(message=primitive.hardware.get_metrics(), context=context)
|
162
|
+
sleep(1)
|
163
|
+
else:
|
164
|
+
print_result(message=primitive.hardware.get_metrics(), context=context)
|
@@ -1,11 +1,29 @@
|
|
1
1
|
from primitive.graphql.utility_fragments import operation_info_fragment
|
2
2
|
|
3
|
+
hardware_certificate_create_mutation = (
|
4
|
+
operation_info_fragment
|
5
|
+
+ """
|
6
|
+
mutation hardwareCertificateCreate($input: HardwareCertificateCreateInput!) {
|
7
|
+
hardwareCertificateCreate(input: $input) {
|
8
|
+
__typename
|
9
|
+
... on HardwareCertificate {
|
10
|
+
id
|
11
|
+
certificatePem
|
12
|
+
}
|
13
|
+
...OperationInfoFragment
|
14
|
+
}
|
15
|
+
}
|
16
|
+
"""
|
17
|
+
)
|
18
|
+
|
3
19
|
register_hardware_mutation = (
|
4
20
|
operation_info_fragment
|
5
21
|
+ """
|
6
22
|
mutation registerHardware($input: RegisterHardwareInput!) {
|
7
23
|
registerHardware(input: $input) {
|
8
24
|
... on Hardware {
|
25
|
+
id
|
26
|
+
pk
|
9
27
|
fingerprint
|
10
28
|
}
|
11
29
|
...OperationInfoFragment
|