primitive 0.1.1__py3-none-any.whl → 0.1.2__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 +4 -0
- primitive/__init__.py +3 -0
- primitive/auth/__init__.py +0 -0
- primitive/auth/actions.py +43 -0
- primitive/auth/commands.py +77 -0
- primitive/cli.py +60 -0
- primitive/client.py +66 -0
- primitive/files/actions.py +76 -0
- primitive/files/commands.py +28 -0
- primitive/graphql/__init__.py +0 -0
- primitive/graphql/sdk.py +53 -0
- primitive/hardware/actions.py +447 -0
- primitive/hardware/commands.py +53 -0
- primitive/lint/actions.py +6 -0
- primitive/lint/commands.py +15 -0
- primitive/projects/__init__.py +0 -0
- primitive/projects/actions.py +55 -0
- primitive/simulations/__init__.py +0 -0
- primitive/simulations/actions.py +48 -0
- primitive/utils/actions.py +9 -0
- primitive/utils/config.py +34 -0
- primitive/utils/git.py +15 -0
- primitive/utils/memory_size.py +87 -0
- primitive/utils/printer.py +14 -0
- primitive-0.1.2.dist-info/METADATA +49 -0
- primitive-0.1.2.dist-info/RECORD +29 -0
- {primitive-0.1.1.dist-info → primitive-0.1.2.dist-info}/WHEEL +1 -1
- primitive-0.1.2.dist-info/entry_points.txt +2 -0
- primitive-0.1.2.dist-info/licenses/LICENSE.txt +9 -0
- primitive/main.py +0 -18
- primitive/scope/__init__.py +0 -2
- primitive/scope/simulator.py +0 -12
- primitive/scope/watch.py +0 -42
- primitive-0.1.1.dist-info/METADATA +0 -15
- primitive-0.1.1.dist-info/RECORD +0 -9
- primitive-0.1.1.dist-info/entry_points.txt +0 -3
@@ -0,0 +1,447 @@
|
|
1
|
+
import csv
|
2
|
+
import io
|
3
|
+
import json
|
4
|
+
import platform
|
5
|
+
from shutil import which
|
6
|
+
import subprocess
|
7
|
+
from typing import Dict, List
|
8
|
+
import click
|
9
|
+
from loguru import logger
|
10
|
+
from primitive.utils.memory_size import MemorySize
|
11
|
+
from gql import gql
|
12
|
+
from aiohttp import client_exceptions
|
13
|
+
from ..utils.config import update_config_file
|
14
|
+
|
15
|
+
import typing
|
16
|
+
|
17
|
+
if typing.TYPE_CHECKING:
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
from primitive.utils.actions import BaseAction
|
22
|
+
|
23
|
+
|
24
|
+
class Hardware(BaseAction):
|
25
|
+
def __init__(self, *args, **kwargs) -> None:
|
26
|
+
super().__init__(*args, **kwargs)
|
27
|
+
self.previous_state = {
|
28
|
+
"isHealthy": False,
|
29
|
+
"isQuarantined": False,
|
30
|
+
"isAvailable": False,
|
31
|
+
"isOnline": False,
|
32
|
+
}
|
33
|
+
|
34
|
+
def _get_darwin_system_profiler_values(self) -> Dict[str, str]:
|
35
|
+
system_profiler_hardware_data_type = subprocess.check_output(
|
36
|
+
["system_profiler", "SPHardwareDataType", "-json"]
|
37
|
+
)
|
38
|
+
system_profiler_hardware_data = json.loads(system_profiler_hardware_data_type)
|
39
|
+
data_type = system_profiler_hardware_data.get("SPHardwareDataType")[0]
|
40
|
+
return {
|
41
|
+
"apple_model_name": data_type.get("machine_model"),
|
42
|
+
"apple_model_identifier": data_type.get("machine_name"),
|
43
|
+
"apple_model_number": data_type.get("model_number"),
|
44
|
+
"physical_memory": data_type.get("physical_memory"),
|
45
|
+
"apple_serial_number": data_type.get("serial_number"),
|
46
|
+
}
|
47
|
+
|
48
|
+
def _get_supported_metal_device(self) -> int | None:
|
49
|
+
"""
|
50
|
+
Checks if metal hardware is supported. If so, the index
|
51
|
+
of the supported metal device is returned
|
52
|
+
"""
|
53
|
+
supported_metal_device = None
|
54
|
+
is_system_profiler_available = bool(which("system_profiler"))
|
55
|
+
if is_system_profiler_available:
|
56
|
+
system_profiler_display_data_type_command = (
|
57
|
+
"system_profiler SPDisplaysDataType -json"
|
58
|
+
)
|
59
|
+
try:
|
60
|
+
system_profiler_display_data_type_output = subprocess.check_output(
|
61
|
+
system_profiler_display_data_type_command.split(" ")
|
62
|
+
)
|
63
|
+
except subprocess.CalledProcessError as exception:
|
64
|
+
message = f"Error running system_profiler: {exception}"
|
65
|
+
logger.error(message)
|
66
|
+
return supported_metal_device
|
67
|
+
|
68
|
+
try:
|
69
|
+
system_profiler_display_data_type_json = json.loads(
|
70
|
+
system_profiler_display_data_type_output
|
71
|
+
)
|
72
|
+
except json.JSONDecodeError as exception:
|
73
|
+
message = f"Error decoding JSON: {exception}"
|
74
|
+
logger.error(message)
|
75
|
+
return supported_metal_device
|
76
|
+
|
77
|
+
# Checks if any attached displays have metal support
|
78
|
+
# Note, other devices here could be AMD GPUs or unconfigured Nvidia GPUs
|
79
|
+
for index, display in enumerate(
|
80
|
+
system_profiler_display_data_type_json["SPDisplaysDataType"]
|
81
|
+
):
|
82
|
+
if "spdisplays_mtlgpufamilysupport" in display:
|
83
|
+
supported_metal_device = index
|
84
|
+
return supported_metal_device
|
85
|
+
|
86
|
+
return supported_metal_device
|
87
|
+
|
88
|
+
def _get_gpu_config(self) -> List:
|
89
|
+
"""
|
90
|
+
For Nvidia based systems, nvidia-smi will be used to profile the gpu/s.
|
91
|
+
For Metal based systems, we will gather information from SPDisplaysDataType.
|
92
|
+
"""
|
93
|
+
gpu_config = []
|
94
|
+
|
95
|
+
# Check nvidia gpu availability
|
96
|
+
is_nvidia_smi_available = bool(which("nvidia-smi"))
|
97
|
+
if is_nvidia_smi_available:
|
98
|
+
nvidia_smi_query_gpu_csv_command = "nvidia-smi --query-gpu=gpu_name,driver_version,memory.total --format=csv" # noqa
|
99
|
+
try:
|
100
|
+
nvidia_smi_query_gpu_csv_output = subprocess.check_output(
|
101
|
+
nvidia_smi_query_gpu_csv_command.split(" "),
|
102
|
+
)
|
103
|
+
except subprocess.CalledProcessError as exception:
|
104
|
+
message = f"Command {nvidia_smi_query_gpu_csv_command} failed with exception: {exception}" # noqa
|
105
|
+
logger.error(message)
|
106
|
+
raise exception
|
107
|
+
|
108
|
+
try:
|
109
|
+
nvidia_smi_query_gpu_csv_decoded = (
|
110
|
+
nvidia_smi_query_gpu_csv_output.decode("utf-8")
|
111
|
+
.replace("\r", "")
|
112
|
+
.replace(", ", ",")
|
113
|
+
.lstrip("\n")
|
114
|
+
)
|
115
|
+
except UnicodeDecodeError as exception:
|
116
|
+
message = f"Error decoding: {exception}"
|
117
|
+
logger.error(message)
|
118
|
+
raise exception
|
119
|
+
|
120
|
+
nvidia_smi_query_gpu_csv_dict_reader = csv.DictReader(
|
121
|
+
io.StringIO(nvidia_smi_query_gpu_csv_decoded)
|
122
|
+
)
|
123
|
+
|
124
|
+
for gpu_info in nvidia_smi_query_gpu_csv_dict_reader:
|
125
|
+
# Refactor key into B
|
126
|
+
memory_total_in_mebibytes = gpu_info.pop("memory.total [MiB]")
|
127
|
+
memory_size = MemorySize(memory_total_in_mebibytes)
|
128
|
+
gpu_info["memory_total"] = memory_size.to_bytes()
|
129
|
+
|
130
|
+
gpu_config.append(gpu_info)
|
131
|
+
|
132
|
+
if platform.system() == "Darwin":
|
133
|
+
# Check Metal gpu availability
|
134
|
+
supported_metal_device = self._get_supported_metal_device()
|
135
|
+
if supported_metal_device is not None:
|
136
|
+
# Since Apple's SoC contains Metal,
|
137
|
+
# we query the system itself for total memory
|
138
|
+
system_profiler_hardware_data_type_command = (
|
139
|
+
"system_profiler SPHardwareDataType -json"
|
140
|
+
)
|
141
|
+
|
142
|
+
try:
|
143
|
+
system_profiler_hardware_data_type_output = subprocess.check_output(
|
144
|
+
system_profiler_hardware_data_type_command.split(" ")
|
145
|
+
)
|
146
|
+
except subprocess.CalledProcessError as exception:
|
147
|
+
message = f"Error running {system_profiler_hardware_data_type_command}: {exception}" # noqa
|
148
|
+
logger.error(message)
|
149
|
+
raise exception
|
150
|
+
|
151
|
+
try:
|
152
|
+
system_profiler_hardware_data_type_json = json.loads(
|
153
|
+
system_profiler_hardware_data_type_output
|
154
|
+
)
|
155
|
+
except json.JSONDecodeError as exception:
|
156
|
+
message = f"Error decoding JSON: {exception}" # noqa
|
157
|
+
logger.error(message)
|
158
|
+
raise exception
|
159
|
+
|
160
|
+
metal_device_json = system_profiler_hardware_data_type_json[
|
161
|
+
"SPHardwareDataType"
|
162
|
+
][supported_metal_device]
|
163
|
+
|
164
|
+
gpu_info = {}
|
165
|
+
gpu_info["name"] = metal_device_json.get("chip_type")
|
166
|
+
|
167
|
+
# Refactor key into B
|
168
|
+
physical_memory = metal_device_json.get("physical_memory")
|
169
|
+
memory_size = MemorySize(physical_memory)
|
170
|
+
gpu_info["memory_total"] = memory_size.to_bytes()
|
171
|
+
|
172
|
+
gpu_config.append(gpu_info)
|
173
|
+
|
174
|
+
# Raise an error if there is no valid gpu config
|
175
|
+
if not gpu_config:
|
176
|
+
message = "No valid gpu configuration"
|
177
|
+
logger.error(message)
|
178
|
+
raise NotImplementedError(message)
|
179
|
+
|
180
|
+
return gpu_config
|
181
|
+
|
182
|
+
def _get_windows_computer_service_product_values(self) -> Dict[str, str]:
|
183
|
+
windows_computer_service_product_csv_command = (
|
184
|
+
"cmd.exe /C wmic csproduct get Name, Vendor, Version, UUID /format:csv"
|
185
|
+
)
|
186
|
+
windows_computer_service_product_csv_output = subprocess.check_output(
|
187
|
+
windows_computer_service_product_csv_command.split(" "),
|
188
|
+
stderr=subprocess.DEVNULL,
|
189
|
+
)
|
190
|
+
windows_computer_service_product_csv_decoded = (
|
191
|
+
windows_computer_service_product_csv_output.decode("utf-8")
|
192
|
+
.replace("\r", "")
|
193
|
+
.lstrip("\n")
|
194
|
+
)
|
195
|
+
windows_computer_service_product_dict = csv.DictReader(
|
196
|
+
io.StringIO(windows_computer_service_product_csv_decoded)
|
197
|
+
)
|
198
|
+
csp_info = list(windows_computer_service_product_dict)[0]
|
199
|
+
return {
|
200
|
+
"windows_model_name": csp_info.get("Name", ""),
|
201
|
+
"windows_model_vendor": csp_info.get("Vendor", ""),
|
202
|
+
"windows_model_version": csp_info.get("Version", ""),
|
203
|
+
"windows_model_uuid": csp_info.get("UUID", ""),
|
204
|
+
}
|
205
|
+
|
206
|
+
def _get_windows_cpu_values(self) -> Dict[str, str]:
|
207
|
+
windows_cpu_csv_command = (
|
208
|
+
"cmd.exe /C wmic cpu get Name, MaxClockSpeed /format:csv" # noqa
|
209
|
+
)
|
210
|
+
windows_cpu_csv_output = subprocess.check_output(
|
211
|
+
windows_cpu_csv_command.split(" "),
|
212
|
+
stderr=subprocess.DEVNULL,
|
213
|
+
)
|
214
|
+
windows_cpu_csv_decoded = (
|
215
|
+
windows_cpu_csv_output.decode("utf-8").replace("\r", "").lstrip("\n")
|
216
|
+
)
|
217
|
+
windows_cpu_dict = csv.DictReader(io.StringIO(windows_cpu_csv_decoded))
|
218
|
+
cpu_info = list(windows_cpu_dict)[0]
|
219
|
+
return {
|
220
|
+
"cpu_brand": cpu_info.get("Name", "").strip(),
|
221
|
+
"cpu_max_clock_speed": cpu_info.get("MaxClockSpeed", ""),
|
222
|
+
}
|
223
|
+
|
224
|
+
def _get_ubuntu_values(self) -> Dict[str, str]:
|
225
|
+
get_machine_id_command = "cat /etc/machine-id"
|
226
|
+
machine_id = subprocess.check_output(
|
227
|
+
get_machine_id_command.split(" "),
|
228
|
+
stderr=subprocess.DEVNULL,
|
229
|
+
).decode("utf-8")
|
230
|
+
if machine_id:
|
231
|
+
return {"linux_machine_id": machine_id}
|
232
|
+
return {}
|
233
|
+
|
234
|
+
def get_system_info(self):
|
235
|
+
os_family = platform.system()
|
236
|
+
system_info = {}
|
237
|
+
if os_family == "Darwin":
|
238
|
+
system_info = {**system_info, **self._get_darwin_system_profiler_values()}
|
239
|
+
system_info["cpu_brand"] = (
|
240
|
+
subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"])
|
241
|
+
.strip()
|
242
|
+
.decode("utf-8")
|
243
|
+
)
|
244
|
+
system_info["apple_mac_os_version"] = platform.mac_ver()[0]
|
245
|
+
elif os_family == "Linux":
|
246
|
+
# Support for Linux-based VMs in Windows
|
247
|
+
if "WSL2" in platform.platform():
|
248
|
+
system_info = {
|
249
|
+
**system_info,
|
250
|
+
**self._get_windows_computer_service_product_values(),
|
251
|
+
**self._get_windows_cpu_values(),
|
252
|
+
}
|
253
|
+
else:
|
254
|
+
system_info = {**system_info, **self._get_ubuntu_values()}
|
255
|
+
elif os_family == "Windows":
|
256
|
+
system_info = {
|
257
|
+
**system_info,
|
258
|
+
**self._get_windows_computer_service_product_values(),
|
259
|
+
**self._get_windows_cpu_values(),
|
260
|
+
}
|
261
|
+
|
262
|
+
system_info["name"] = platform.node()
|
263
|
+
system_info["os_family"] = os_family
|
264
|
+
system_info["os_release"] = platform.release()
|
265
|
+
system_info["os_version"] = platform.version()
|
266
|
+
system_info["platform"] = platform.platform()
|
267
|
+
system_info["processor"] = platform.processor()
|
268
|
+
system_info["machine"] = platform.machine()
|
269
|
+
system_info["architecture"] = platform.architecture()[0]
|
270
|
+
system_info["cpu_cores"] = str(platform.os.cpu_count()) # type: ignore exits
|
271
|
+
system_info["gpu_config"] = self._get_gpu_config()
|
272
|
+
return system_info
|
273
|
+
|
274
|
+
def register(self):
|
275
|
+
system_info = self.get_system_info()
|
276
|
+
mutation = gql(
|
277
|
+
"""
|
278
|
+
mutation registerHardware($input: RegisterHardwareInput!) {
|
279
|
+
registerHardware(input: $input) {
|
280
|
+
... on Hardware {
|
281
|
+
fingerprint
|
282
|
+
}
|
283
|
+
... on OperationInfo {
|
284
|
+
messages {
|
285
|
+
kind
|
286
|
+
message
|
287
|
+
field
|
288
|
+
code
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
}
|
293
|
+
"""
|
294
|
+
)
|
295
|
+
input = {"systemInfo": system_info}
|
296
|
+
variables = {"input": input}
|
297
|
+
result = self.primitive.session.execute(mutation, variable_values=variables)
|
298
|
+
if messages := result.get("registerHardware").get("messages"):
|
299
|
+
for message in messages:
|
300
|
+
logger.enable("primitive")
|
301
|
+
if message.get("kind") == "ERROR":
|
302
|
+
logger.error(message.get("message"))
|
303
|
+
else:
|
304
|
+
logger.debug(message.get("message"))
|
305
|
+
return False
|
306
|
+
|
307
|
+
fingerprint = result.get("registerHardware").get("fingerprint")
|
308
|
+
|
309
|
+
self.primitive.host_config["fingerprint"] = fingerprint
|
310
|
+
self.primitive.full_config[self.primitive.host] = self.primitive.host_config
|
311
|
+
update_config_file(new_config=self.primitive.full_config)
|
312
|
+
|
313
|
+
# then check in that the hardware, validate that it is saved correctly
|
314
|
+
# and headers are set correctly
|
315
|
+
self.primitive.get_host_config()
|
316
|
+
self.check_in_http(is_healthy=True)
|
317
|
+
return True
|
318
|
+
|
319
|
+
def update_hardware_system_info(self):
|
320
|
+
"""
|
321
|
+
Updates hardware system information and returns the GraphQL response.
|
322
|
+
|
323
|
+
Returns:
|
324
|
+
dict: GraphQL response
|
325
|
+
Raises:
|
326
|
+
Exception: If no fingerprint is found or an error occurs
|
327
|
+
"""
|
328
|
+
|
329
|
+
fingerprint = self.primitive.host_config.get("fingerprint", None)
|
330
|
+
if not fingerprint:
|
331
|
+
message = (
|
332
|
+
"No fingerprint found. Please register: primitive hardware register"
|
333
|
+
)
|
334
|
+
raise Exception(message)
|
335
|
+
|
336
|
+
system_info = self.get_system_info()
|
337
|
+
new_state = {
|
338
|
+
"systemInfo": system_info,
|
339
|
+
}
|
340
|
+
|
341
|
+
mutation = gql(
|
342
|
+
"""
|
343
|
+
mutation hardwareUpdate($input: HardwareUpdateInput!) {
|
344
|
+
hardwareUpdate(input: $input) {
|
345
|
+
... on Hardware {
|
346
|
+
systemInfo
|
347
|
+
}
|
348
|
+
... on OperationInfo {
|
349
|
+
messages {
|
350
|
+
kind
|
351
|
+
message
|
352
|
+
field
|
353
|
+
code
|
354
|
+
}
|
355
|
+
}
|
356
|
+
}
|
357
|
+
}
|
358
|
+
"""
|
359
|
+
)
|
360
|
+
|
361
|
+
input = new_state
|
362
|
+
variables = {"input": input}
|
363
|
+
try:
|
364
|
+
result = self.primitive.session.execute(mutation, variable_values=variables)
|
365
|
+
except client_exceptions.ClientConnectorError as exception:
|
366
|
+
message = " [*] Failed to update hardware system info! "
|
367
|
+
logger.error(message)
|
368
|
+
raise exception
|
369
|
+
|
370
|
+
message = " [*] Updated hardware system info successfully! "
|
371
|
+
logger.info(message)
|
372
|
+
|
373
|
+
return result
|
374
|
+
|
375
|
+
def check_in_http(
|
376
|
+
self,
|
377
|
+
is_healthy: bool = True,
|
378
|
+
is_quarantined: bool = False,
|
379
|
+
is_available: bool = False,
|
380
|
+
is_online: bool = True,
|
381
|
+
):
|
382
|
+
fingerprint = self.primitive.host_config.get("fingerprint", None)
|
383
|
+
if not fingerprint:
|
384
|
+
message = (
|
385
|
+
"No fingerprint found. Please register: primitive hardware register"
|
386
|
+
)
|
387
|
+
raise Exception(message)
|
388
|
+
|
389
|
+
new_state = {
|
390
|
+
"isHealthy": is_healthy,
|
391
|
+
"isQuarantined": is_quarantined,
|
392
|
+
"isAvailable": is_available,
|
393
|
+
"isOnline": is_online,
|
394
|
+
}
|
395
|
+
|
396
|
+
mutation = gql(
|
397
|
+
"""
|
398
|
+
mutation checkIn($input: CheckInInput!) {
|
399
|
+
checkIn(input: $input) {
|
400
|
+
... on Hardware {
|
401
|
+
createdAt
|
402
|
+
updatedAt
|
403
|
+
lastCheckIn
|
404
|
+
}
|
405
|
+
... on OperationInfo {
|
406
|
+
messages {
|
407
|
+
kind
|
408
|
+
message
|
409
|
+
field
|
410
|
+
code
|
411
|
+
}
|
412
|
+
}
|
413
|
+
}
|
414
|
+
}
|
415
|
+
""" # noqa
|
416
|
+
)
|
417
|
+
input = new_state
|
418
|
+
variables = {"input": input}
|
419
|
+
try:
|
420
|
+
result = self.primitive.session.execute(mutation, variable_values=variables)
|
421
|
+
previous_state = self.previous_state
|
422
|
+
self.previous_state = new_state.copy()
|
423
|
+
|
424
|
+
message = " [*] Checked in successfully: "
|
425
|
+
for key, value in new_state.items():
|
426
|
+
if value != previous_state.get(key, None):
|
427
|
+
if value is True:
|
428
|
+
message = (
|
429
|
+
message
|
430
|
+
+ click.style(f"{key}: ")
|
431
|
+
+ click.style("💤")
|
432
|
+
+ click.style(" ==> ✅ ", fg="green")
|
433
|
+
)
|
434
|
+
else:
|
435
|
+
message = (
|
436
|
+
message
|
437
|
+
+ click.style(f"{key}: ")
|
438
|
+
+ click.style("✅")
|
439
|
+
+ click.style(" ==> 💤 ", fg="yellow")
|
440
|
+
)
|
441
|
+
logger.info(message)
|
442
|
+
|
443
|
+
return result
|
444
|
+
except client_exceptions.ClientConnectorError as exception:
|
445
|
+
message = " [*] Failed to check in! "
|
446
|
+
logger.error(message)
|
447
|
+
raise exception
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import click
|
2
|
+
|
3
|
+
from ..utils.printer import print_result
|
4
|
+
|
5
|
+
import typing
|
6
|
+
|
7
|
+
if typing.TYPE_CHECKING:
|
8
|
+
from ..client import Primitive
|
9
|
+
|
10
|
+
|
11
|
+
@click.group()
|
12
|
+
@click.pass_context
|
13
|
+
def cli(context):
|
14
|
+
"""Hardware"""
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
@cli.command("systeminfo")
|
19
|
+
@click.pass_context
|
20
|
+
def systeminfo_command(context):
|
21
|
+
"""Get System Info"""
|
22
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
23
|
+
message = primitive.hardware.get_system_info()
|
24
|
+
print_result(message=message, context=context)
|
25
|
+
|
26
|
+
|
27
|
+
@cli.command("register")
|
28
|
+
@click.pass_context
|
29
|
+
def register_command(context):
|
30
|
+
"""Register Hardware with Primitive"""
|
31
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
32
|
+
result = primitive.hardware.register()
|
33
|
+
color = "green" if result else "red"
|
34
|
+
if result:
|
35
|
+
message = "Hardware registered successfully"
|
36
|
+
else:
|
37
|
+
message = (
|
38
|
+
"There was an error registering this device. Please review the above logs."
|
39
|
+
)
|
40
|
+
print_result(message=message, context=context, fg=color)
|
41
|
+
|
42
|
+
|
43
|
+
@cli.command("checkin")
|
44
|
+
@click.pass_context
|
45
|
+
def checkin_command(context):
|
46
|
+
"""Checkin Hardware with Primitive"""
|
47
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
48
|
+
result = primitive.hardware.check_in_http()
|
49
|
+
if messages := result.get("checkIn").get("messages"):
|
50
|
+
print_result(message=messages, context=context, fg="yellow")
|
51
|
+
else:
|
52
|
+
message = "Hardware checked in successfully"
|
53
|
+
print_result(message=message, context=context, fg="green")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import click
|
2
|
+
|
3
|
+
|
4
|
+
import typing
|
5
|
+
|
6
|
+
if typing.TYPE_CHECKING:
|
7
|
+
from ..client import Primitive
|
8
|
+
|
9
|
+
|
10
|
+
@click.command("lint")
|
11
|
+
@click.pass_context
|
12
|
+
def cli(context):
|
13
|
+
"""Lint"""
|
14
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
15
|
+
primitive.lint.run_lint()
|
File without changes
|
@@ -0,0 +1,55 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
from gql import gql
|
3
|
+
|
4
|
+
|
5
|
+
from primitive.utils.actions import BaseAction
|
6
|
+
|
7
|
+
|
8
|
+
class Projects(BaseAction):
|
9
|
+
def get_job_run(self, id: str):
|
10
|
+
query = gql(
|
11
|
+
"""
|
12
|
+
query jobRun($id: GlobalID!) {
|
13
|
+
jobRun(id: $id) {
|
14
|
+
id
|
15
|
+
organization {
|
16
|
+
id
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
"""
|
21
|
+
)
|
22
|
+
variables = {"id": id}
|
23
|
+
result = self.primitive.session.execute(query, variable_values=variables)
|
24
|
+
return result
|
25
|
+
|
26
|
+
def job_run_update(
|
27
|
+
self,
|
28
|
+
id: str,
|
29
|
+
status: str = None,
|
30
|
+
conclusion: str = None,
|
31
|
+
file_ids: Optional[List[str]] = [],
|
32
|
+
):
|
33
|
+
mutation = gql(
|
34
|
+
"""
|
35
|
+
mutation jobRunUpdate($input: JobRunUpdateInput!) {
|
36
|
+
jobRunUpdate(input: $input) {
|
37
|
+
... on JobRun {
|
38
|
+
id
|
39
|
+
status
|
40
|
+
conclusion
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
"""
|
45
|
+
)
|
46
|
+
input = {"id": id}
|
47
|
+
if status:
|
48
|
+
input["status"] = status
|
49
|
+
if conclusion:
|
50
|
+
input["conclusion"] = conclusion
|
51
|
+
if file_ids and len(file_ids) > 0:
|
52
|
+
input["files"] = file_ids
|
53
|
+
variables = {"input": input}
|
54
|
+
result = self.primitive.session.execute(mutation, variable_values=variables)
|
55
|
+
return result
|
File without changes
|
@@ -0,0 +1,48 @@
|
|
1
|
+
from gql import gql
|
2
|
+
|
3
|
+
|
4
|
+
from primitive.utils.actions import BaseAction
|
5
|
+
|
6
|
+
|
7
|
+
class Simulations(BaseAction):
|
8
|
+
def trace_create(
|
9
|
+
self,
|
10
|
+
id_code: str,
|
11
|
+
module: str,
|
12
|
+
var_type: str,
|
13
|
+
var_size: int,
|
14
|
+
reference: str,
|
15
|
+
bit_index: str,
|
16
|
+
timescale_unit: str,
|
17
|
+
timescale_magnitude: int,
|
18
|
+
organization: str,
|
19
|
+
file: str,
|
20
|
+
job_run: str,
|
21
|
+
):
|
22
|
+
mutation = gql(
|
23
|
+
"""
|
24
|
+
mutation createTrace($input: TraceCreateInput!) {
|
25
|
+
traceCreate(input: $input) {
|
26
|
+
... on Trace {
|
27
|
+
id
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
"""
|
32
|
+
)
|
33
|
+
input = {
|
34
|
+
"idCode": id_code,
|
35
|
+
"module": module,
|
36
|
+
"varType": var_type,
|
37
|
+
"varSize": var_size,
|
38
|
+
"reference": reference,
|
39
|
+
"bitIndex": bit_index,
|
40
|
+
"timescaleUnit": timescale_unit,
|
41
|
+
"timescaleMagnitude": timescale_magnitude,
|
42
|
+
"organization": organization,
|
43
|
+
"file": file,
|
44
|
+
"jobRun": job_run,
|
45
|
+
}
|
46
|
+
variables = {"input": input}
|
47
|
+
result = self.primitive.session.execute(mutation, variable_values=variables)
|
48
|
+
return result
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import json
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Dict
|
4
|
+
|
5
|
+
HOME_DIRECTORY = Path.home()
|
6
|
+
PRIMITIVE_CREDENTIALS_FILEPATH = Path(
|
7
|
+
HOME_DIRECTORY / ".config" / "primitive" / "credentials.json"
|
8
|
+
)
|
9
|
+
|
10
|
+
|
11
|
+
def create_directory(filepath: Path = PRIMITIVE_CREDENTIALS_FILEPATH):
|
12
|
+
filepath.mkdir(parents=True, exist_ok=True)
|
13
|
+
|
14
|
+
|
15
|
+
def create_config_file(filepath: Path = PRIMITIVE_CREDENTIALS_FILEPATH):
|
16
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
17
|
+
filepath.touch()
|
18
|
+
with filepath.open("w") as json_file:
|
19
|
+
json.dump({}, json_file)
|
20
|
+
|
21
|
+
|
22
|
+
def update_config_file(
|
23
|
+
filepath: Path = PRIMITIVE_CREDENTIALS_FILEPATH, new_config: Dict = {}
|
24
|
+
):
|
25
|
+
existing_config = read_config_file(filepath=filepath)
|
26
|
+
merged_config = {**existing_config, **new_config}
|
27
|
+
with filepath.open("w") as json_file:
|
28
|
+
json.dump(merged_config, json_file, indent=2)
|
29
|
+
|
30
|
+
|
31
|
+
def read_config_file(filepath: Path = PRIMITIVE_CREDENTIALS_FILEPATH) -> Dict:
|
32
|
+
if not filepath.exists():
|
33
|
+
create_config_file(filepath=filepath)
|
34
|
+
return json.loads(filepath.read_text())
|
primitive/utils/git.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
import os
|
2
|
+
from loguru import logger
|
3
|
+
|
4
|
+
|
5
|
+
def download_source(github_access_token, git_repository, git_ref) -> None:
|
6
|
+
# Download code to current directory
|
7
|
+
logger.debug(f"Downloading source code from {git_repository} {git_ref}")
|
8
|
+
url = f"https://api.github.com/repos/{git_repository}/tarball/{git_ref}"
|
9
|
+
# TODO: switch to supbrocess.run or subprocess.Popen
|
10
|
+
result = os.system(
|
11
|
+
f"curl -s -L -H 'Accept: application/vnd.github+json' -H 'Authorization: Bearer {github_access_token}' -H 'X-GitHub-Api-Version: 2022-11-28' {url} | tar zx --strip-components 1 -C ."
|
12
|
+
)
|
13
|
+
|
14
|
+
if result != 0:
|
15
|
+
raise Exception("Failed to import repository.")
|