primitive 0.1.60__py3-none-any.whl → 0.1.63__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- primitive/__about__.py +1 -1
- primitive/agent/actions.py +12 -1
- primitive/agent/runner.py +2 -1
- primitive/agent/uploader.py +20 -22
- primitive/client.py +13 -10
- primitive/exec/actions.py +26 -1
- primitive/exec/commands.py +1 -1
- primitive/exec/interactive.py +99 -0
- primitive/files/actions.py +125 -17
- primitive/files/commands.py +17 -5
- primitive/files/graphql/mutations.py +27 -6
- primitive/hardware/actions.py +1 -1
- primitive/hardware/graphql/fragments.py +2 -0
- primitive/provisioning/__init__.py +0 -0
- primitive/provisioning/actions.py +62 -0
- primitive/provisioning/graphql/__init__.py +0 -0
- primitive/provisioning/graphql/queries.py +5 -0
- primitive/reservations/graphql/fragments.py +1 -1
- primitive/sim/actions.py +5 -9
- primitive/utils/verible.py +3 -3
- {primitive-0.1.60.dist-info → primitive-0.1.63.dist-info}/METADATA +3 -3
- {primitive-0.1.60.dist-info → primitive-0.1.63.dist-info}/RECORD +25 -20
- {primitive-0.1.60.dist-info → primitive-0.1.63.dist-info}/WHEEL +1 -1
- {primitive-0.1.60.dist-info → primitive-0.1.63.dist-info}/entry_points.txt +0 -0
- {primitive-0.1.60.dist-info → primitive-0.1.63.dist-info}/licenses/LICENSE.txt +0 -0
primitive/__about__.py
CHANGED
primitive/agent/actions.py
CHANGED
@@ -20,7 +20,7 @@ class Agent(BaseAction):
|
|
20
20
|
logger.info(" [*] primitive")
|
21
21
|
logger.info(f" [*] Version: {__version__}")
|
22
22
|
|
23
|
-
# Create cache dir if it
|
23
|
+
# Create cache dir if it doesn't exist
|
24
24
|
cache_dir = get_sources_cache()
|
25
25
|
|
26
26
|
# Create uploader
|
@@ -53,6 +53,11 @@ class Agent(BaseAction):
|
|
53
53
|
logger.debug("Active Reservation:")
|
54
54
|
logger.debug(f"Node ID: {active_reservation_id}")
|
55
55
|
logger.debug(f"PK: {active_reservation_pk}")
|
56
|
+
|
57
|
+
logger.debug("Running pre provisioning steps for reservation.")
|
58
|
+
self.primitive.provisioning.add_reservation_authorized_keys(
|
59
|
+
reservation_id=active_reservation_id
|
60
|
+
)
|
56
61
|
else:
|
57
62
|
if (
|
58
63
|
hardware["activeReservation"] is None
|
@@ -62,6 +67,12 @@ class Agent(BaseAction):
|
|
62
67
|
logger.debug("Previous Reservation is Complete:")
|
63
68
|
logger.debug(f"Node ID: {active_reservation_id}")
|
64
69
|
logger.debug(f"PK: {active_reservation_pk}")
|
70
|
+
logger.debug(
|
71
|
+
"Running cleanup provisioning steps for reservation."
|
72
|
+
)
|
73
|
+
self.primitive.provisioning.remove_reservation_authorized_keys(
|
74
|
+
reservation_id=active_reservation_id
|
75
|
+
)
|
65
76
|
active_reservation_id = None
|
66
77
|
active_reservation_pk = None
|
67
78
|
|
primitive/agent/runner.py
CHANGED
@@ -191,7 +191,8 @@ class AgentRunner:
|
|
191
191
|
logger.remove(self.logger_handle)
|
192
192
|
|
193
193
|
self.logger_handle = logger.add(
|
194
|
-
Path(self.logs_dir / f"{label}_{{time}}.primitive.log"),
|
194
|
+
Path(self.logs_dir / f"{label}_{{time}}.primitive.log"),
|
195
|
+
rotation=self.max_log_size,
|
195
196
|
)
|
196
197
|
|
197
198
|
def provision(self) -> Optional[Dict]:
|
primitive/agent/uploader.py
CHANGED
@@ -2,8 +2,10 @@ import typing
|
|
2
2
|
from typing import Dict
|
3
3
|
import shutil
|
4
4
|
import os
|
5
|
-
from loguru import logger
|
6
5
|
from pathlib import Path, PurePath
|
6
|
+
|
7
|
+
from loguru import logger
|
8
|
+
|
7
9
|
from ..utils.cache import get_artifacts_cache, get_logs_cache
|
8
10
|
|
9
11
|
if typing.TYPE_CHECKING:
|
@@ -17,12 +19,6 @@ class Uploader:
|
|
17
19
|
):
|
18
20
|
self.primitive = primitive
|
19
21
|
|
20
|
-
def upload_file(self, path: Path, prefix: str, job_run_id: str) -> str:
|
21
|
-
file_upload_response = self.primitive.files.file_upload(
|
22
|
-
path, key_prefix=prefix, job_run_id=job_run_id
|
23
|
-
)
|
24
|
-
return file_upload_response.json()["data"]["fileUpload"]["id"]
|
25
|
-
|
26
22
|
def upload_dir(self, cache: Path) -> Dict:
|
27
23
|
file_ids = []
|
28
24
|
job_run_id = cache.name
|
@@ -49,11 +45,12 @@ class Uploader:
|
|
49
45
|
)
|
50
46
|
|
51
47
|
for file in files:
|
52
|
-
|
53
|
-
file,
|
54
|
-
|
48
|
+
response = self.primitive.files.upload_file_via_api(
|
49
|
+
path=file,
|
50
|
+
key_prefix=str(PurePath(file).relative_to(cache.parent).parent),
|
55
51
|
job_run_id=job_run_id,
|
56
52
|
)
|
53
|
+
upload_id = response.json()["data"]["fileUpload"]["id"]
|
57
54
|
|
58
55
|
if upload_id:
|
59
56
|
file_ids.append(upload_id)
|
@@ -66,23 +63,24 @@ class Uploader:
|
|
66
63
|
|
67
64
|
return {job_run_id: file_ids}
|
68
65
|
|
69
|
-
|
70
66
|
def scan(self) -> None:
|
71
67
|
# Scan artifacts directory
|
72
68
|
artifacts_dir = get_artifacts_cache()
|
73
69
|
logs_dir = get_logs_cache()
|
74
70
|
|
75
|
-
artifacts = sorted(
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
71
|
+
artifacts = sorted(
|
72
|
+
[
|
73
|
+
artifacts_cache
|
74
|
+
for artifacts_cache in artifacts_dir.iterdir()
|
75
|
+
if artifacts_cache.is_dir()
|
76
|
+
],
|
77
|
+
key=lambda p: p.stat().st_ctime,
|
78
|
+
)
|
80
79
|
|
81
|
-
logs = sorted(
|
82
|
-
logs_cache
|
83
|
-
|
84
|
-
|
85
|
-
], key=lambda p: p.stat().st_ctime)
|
80
|
+
logs = sorted(
|
81
|
+
[logs_cache for logs_cache in logs_dir.iterdir() if logs_cache.is_dir()],
|
82
|
+
key=lambda p: p.stat().st_ctime,
|
83
|
+
)
|
86
84
|
|
87
85
|
log_files = {
|
88
86
|
job_id: files
|
@@ -97,7 +95,7 @@ class Uploader:
|
|
97
95
|
}
|
98
96
|
|
99
97
|
files_by_id = {
|
100
|
-
job_id: log_files.get(job_id, []) + artifact_files.get(job_id, [])
|
98
|
+
job_id: log_files.get(job_id, []) + artifact_files.get(job_id, [])
|
101
99
|
for job_id in log_files.keys() | artifact_files.keys()
|
102
100
|
}
|
103
101
|
|
primitive/client.py
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
import sys
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
|
5
|
+
from .agent.actions import Agent
|
2
6
|
from .auth.actions import Auth
|
3
|
-
from .
|
4
|
-
from .
|
7
|
+
from .daemons.actions import Daemons
|
8
|
+
from .exec.actions import Exec
|
5
9
|
from .files.actions import Files
|
6
|
-
from .sim.actions import Sim
|
7
|
-
from .hardware.actions import Hardware
|
8
|
-
from .lint.actions import Lint
|
9
|
-
from .agent.actions import Agent
|
10
10
|
from .git.actions import Git
|
11
|
-
from .
|
11
|
+
from .hardware.actions import Hardware
|
12
12
|
from .jobs.actions import Jobs
|
13
|
+
from .lint.actions import Lint
|
13
14
|
from .organizations.actions import Organizations
|
14
|
-
from .
|
15
|
+
from .projects.actions import Projects
|
16
|
+
from .provisioning.actions import Provisioning
|
15
17
|
from .reservations.actions import Reservations
|
16
|
-
|
17
|
-
from
|
18
|
+
from .sim.actions import Sim
|
19
|
+
from .utils.config import read_config_file
|
18
20
|
|
19
21
|
logger.disable("primitive")
|
20
22
|
|
@@ -67,6 +69,7 @@ class Primitive:
|
|
67
69
|
self.git: Git = Git(self)
|
68
70
|
self.daemons: Daemons = Daemons(self)
|
69
71
|
self.exec: Exec = Exec(self)
|
72
|
+
self.provisioning: Provisioning = Provisioning(self)
|
70
73
|
|
71
74
|
def get_host_config(self):
|
72
75
|
self.full_config = read_config_file()
|
primitive/exec/actions.py
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
import typing
|
2
2
|
|
3
|
+
from primitive.exec.interactive import interactive_shell
|
4
|
+
|
3
5
|
if typing.TYPE_CHECKING:
|
4
6
|
pass
|
5
7
|
|
6
8
|
|
9
|
+
from paramiko import SSHClient
|
10
|
+
|
7
11
|
from primitive.utils.actions import BaseAction
|
8
12
|
|
9
13
|
|
@@ -41,7 +45,28 @@ class Exec(BaseAction):
|
|
41
45
|
reservation_id=reservation["id"], desired_status="in_progress"
|
42
46
|
)
|
43
47
|
|
44
|
-
|
48
|
+
ssh_hostname = hardware["hostname"]
|
49
|
+
ssh_username = hardware["ssh_username"]
|
50
|
+
|
51
|
+
ssh_client = SSHClient()
|
52
|
+
ssh_client.load_system_host_keys()
|
53
|
+
|
54
|
+
ssh_client.connect(
|
55
|
+
hostname=ssh_hostname,
|
56
|
+
username=ssh_username,
|
57
|
+
)
|
58
|
+
|
59
|
+
if command:
|
60
|
+
formatted_command = " ".join(command)
|
61
|
+
stdin, stdout, stderr = ssh_client.exec_command(formatted_command)
|
62
|
+
print(stdout.read())
|
63
|
+
ssh_client.close()
|
64
|
+
else:
|
65
|
+
channel = ssh_client.get_transport().open_session()
|
66
|
+
channel.get_pty()
|
67
|
+
channel.invoke_shell()
|
68
|
+
interactive_shell(channel)
|
69
|
+
ssh_client.close()
|
45
70
|
|
46
71
|
if created_reservation_on_behalf_of_user:
|
47
72
|
print("Cleaning up reservation.")
|
primitive/exec/commands.py
CHANGED
@@ -13,7 +13,7 @@ if typing.TYPE_CHECKING:
|
|
13
13
|
type=str,
|
14
14
|
required=True,
|
15
15
|
)
|
16
|
-
@click.argument("command", nargs=-1, required=
|
16
|
+
@click.argument("command", nargs=-1, required=False)
|
17
17
|
def cli(context, hardware_identifier: str, command: str) -> None:
|
18
18
|
"""Exec"""
|
19
19
|
primitive: Primitive = context.obj.get("PRIMITIVE")
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# https://github.com/paramiko/paramiko/blob/main/demos/interactive.py
|
2
|
+
|
3
|
+
import socket
|
4
|
+
import sys
|
5
|
+
|
6
|
+
|
7
|
+
def _to_unicode(s):
|
8
|
+
"""
|
9
|
+
decode a string as ascii or utf8 if possible (as required by the sftp
|
10
|
+
protocol). if neither works, just return a byte string because the server
|
11
|
+
probably doesn't know the filename's encoding.
|
12
|
+
"""
|
13
|
+
try:
|
14
|
+
return s.encode("ascii")
|
15
|
+
except (UnicodeError, AttributeError):
|
16
|
+
try:
|
17
|
+
return s.decode("utf-8")
|
18
|
+
except UnicodeError:
|
19
|
+
return s
|
20
|
+
|
21
|
+
|
22
|
+
# windows does not have termios...
|
23
|
+
try:
|
24
|
+
import termios
|
25
|
+
import tty
|
26
|
+
|
27
|
+
has_termios = True
|
28
|
+
except ImportError:
|
29
|
+
has_termios = False
|
30
|
+
|
31
|
+
|
32
|
+
def interactive_shell(chan):
|
33
|
+
if has_termios:
|
34
|
+
posix_shell(chan)
|
35
|
+
else:
|
36
|
+
windows_shell(chan)
|
37
|
+
|
38
|
+
|
39
|
+
def posix_shell(chan):
|
40
|
+
import select
|
41
|
+
|
42
|
+
oldtty = termios.tcgetattr(sys.stdin)
|
43
|
+
try:
|
44
|
+
tty.setraw(sys.stdin.fileno())
|
45
|
+
tty.setcbreak(sys.stdin.fileno())
|
46
|
+
chan.settimeout(0.0)
|
47
|
+
|
48
|
+
while True:
|
49
|
+
r, w, e = select.select([chan, sys.stdin], [], [])
|
50
|
+
if chan in r:
|
51
|
+
try:
|
52
|
+
x = _to_unicode(chan.recv(1024))
|
53
|
+
if len(x) == 0:
|
54
|
+
sys.stdout.write("\r\n*** EOF\r\n")
|
55
|
+
break
|
56
|
+
sys.stdout.write(x)
|
57
|
+
sys.stdout.flush()
|
58
|
+
except socket.timeout:
|
59
|
+
pass
|
60
|
+
if sys.stdin in r:
|
61
|
+
x = sys.stdin.read(1)
|
62
|
+
if len(x) == 0:
|
63
|
+
break
|
64
|
+
chan.send(x)
|
65
|
+
|
66
|
+
finally:
|
67
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
|
68
|
+
|
69
|
+
|
70
|
+
# thanks to Mike Looijmans for this code
|
71
|
+
def windows_shell(chan):
|
72
|
+
import threading
|
73
|
+
|
74
|
+
sys.stdout.write(
|
75
|
+
"Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n"
|
76
|
+
)
|
77
|
+
|
78
|
+
def writeall(sock):
|
79
|
+
while True:
|
80
|
+
data = sock.recv(256)
|
81
|
+
if not data:
|
82
|
+
sys.stdout.write("\r\n*** EOF ***\r\n\r\n")
|
83
|
+
sys.stdout.flush()
|
84
|
+
break
|
85
|
+
sys.stdout.write(data)
|
86
|
+
sys.stdout.flush()
|
87
|
+
|
88
|
+
writer = threading.Thread(target=writeall, args=(chan,))
|
89
|
+
writer.start()
|
90
|
+
|
91
|
+
try:
|
92
|
+
while True:
|
93
|
+
d = sys.stdin.read(1)
|
94
|
+
if not d:
|
95
|
+
break
|
96
|
+
chan.send(d)
|
97
|
+
except EOFError:
|
98
|
+
# user hit ^Z or F6
|
99
|
+
pass
|
primitive/files/actions.py
CHANGED
@@ -1,34 +1,85 @@
|
|
1
|
+
import hashlib
|
2
|
+
import sys
|
3
|
+
import threading
|
1
4
|
from pathlib import Path
|
5
|
+
from typing import Dict, Optional
|
2
6
|
|
7
|
+
import requests
|
3
8
|
from gql import gql
|
9
|
+
from loguru import logger
|
4
10
|
|
5
11
|
from primitive.graphql.sdk import create_requests_session
|
6
12
|
from primitive.utils.actions import BaseAction
|
7
13
|
|
8
14
|
from ..utils.auth import guard
|
9
|
-
from .graphql.mutations import
|
15
|
+
from .graphql.mutations import (
|
16
|
+
file_update_mutation,
|
17
|
+
pending_file_create_mutation,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
# this class can be used in multithreaded S3 client uploader
|
22
|
+
# this requires getting an S3 access token to this machine however
|
23
|
+
# we are using presigned urls instead at this time Oct 29th, 2024
|
24
|
+
class ProgressPercentage(object):
|
25
|
+
def __init__(self, filepath: Path) -> None:
|
26
|
+
self._filename = filepath.name
|
27
|
+
self._size = float(filepath.stat().st_size)
|
28
|
+
self._seen_so_far = 0
|
29
|
+
self._lock = threading.Lock()
|
30
|
+
|
31
|
+
def __call__(self, bytes_amount):
|
32
|
+
# To simplify, assume this is hooked up to a single filename
|
33
|
+
with self._lock:
|
34
|
+
self._seen_so_far += bytes_amount
|
35
|
+
percentage = (self._seen_so_far / self._size) * 100
|
36
|
+
sys.stdout.write(
|
37
|
+
"\r%s %s / %s (%.2f%%)"
|
38
|
+
% (self._filename, self._seen_so_far, self._size, percentage)
|
39
|
+
)
|
40
|
+
sys.stdout.flush()
|
10
41
|
|
11
42
|
|
12
43
|
class Files(BaseAction):
|
13
|
-
|
14
|
-
def trace_create(
|
44
|
+
def _pending_file_create(
|
15
45
|
self,
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
46
|
+
file_name: str,
|
47
|
+
file_size: int,
|
48
|
+
file_checksum: str,
|
49
|
+
file_path: str,
|
50
|
+
key_prefix: str,
|
51
|
+
is_public: bool = False,
|
22
52
|
):
|
23
|
-
mutation = gql(
|
53
|
+
mutation = gql(pending_file_create_mutation)
|
24
54
|
input = {
|
25
|
-
"
|
26
|
-
"
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"
|
30
|
-
"
|
55
|
+
"filePath": file_path,
|
56
|
+
"fileName": file_name,
|
57
|
+
"fileSize": file_size,
|
58
|
+
"fileChecksum": file_checksum,
|
59
|
+
"keyPrefix": key_prefix,
|
60
|
+
"isPublic": is_public,
|
61
|
+
}
|
62
|
+
variables = {"input": input}
|
63
|
+
result = self.primitive.session.execute(
|
64
|
+
mutation, variable_values=variables, get_execution_result=True
|
65
|
+
)
|
66
|
+
return result.data.get("pendingFileCreate")
|
67
|
+
|
68
|
+
def _update_file_status(
|
69
|
+
self,
|
70
|
+
file_id: str,
|
71
|
+
is_uploading: Optional[bool] = None,
|
72
|
+
is_complete: Optional[bool] = None,
|
73
|
+
):
|
74
|
+
mutation = gql(file_update_mutation)
|
75
|
+
input: Dict[str, str | bool] = {
|
76
|
+
"id": file_id,
|
31
77
|
}
|
78
|
+
if is_uploading is not None:
|
79
|
+
input["isUploading"] = is_uploading
|
80
|
+
if is_complete is not None:
|
81
|
+
input["isComplete"] = is_complete
|
82
|
+
|
32
83
|
variables = {"input": input}
|
33
84
|
result = self.primitive.session.execute(
|
34
85
|
mutation, variable_values=variables, get_execution_result=True
|
@@ -36,13 +87,70 @@ class Files(BaseAction):
|
|
36
87
|
return result
|
37
88
|
|
38
89
|
@guard
|
39
|
-
def
|
90
|
+
def upload_file_direct(
|
91
|
+
self,
|
92
|
+
path: Path,
|
93
|
+
is_public: False,
|
94
|
+
key_prefix: str = "",
|
95
|
+
file_id: Optional[str] = None,
|
96
|
+
):
|
97
|
+
logger.enable("primitive")
|
98
|
+
if path.exists() is False:
|
99
|
+
raise Exception(f"File {path} does not exist.")
|
100
|
+
|
101
|
+
file_size = path.stat().st_size
|
102
|
+
if file_size == 0:
|
103
|
+
raise Exception(f"{path} is empty.")
|
104
|
+
|
105
|
+
file_checksum = hashlib.md5(path.read_bytes()).hexdigest()
|
106
|
+
|
107
|
+
if not file_id:
|
108
|
+
pending_file_create = self._pending_file_create(
|
109
|
+
file_name=path.name,
|
110
|
+
file_size=path.stat().st_size,
|
111
|
+
file_checksum=file_checksum,
|
112
|
+
file_path=str(path),
|
113
|
+
key_prefix=key_prefix,
|
114
|
+
is_public=is_public,
|
115
|
+
)
|
116
|
+
file_id = pending_file_create.get("id")
|
117
|
+
presigned_url = pending_file_create.get("presignedUrlForUpload")
|
118
|
+
|
119
|
+
if not file_id:
|
120
|
+
raise Exception("No file_id found or provided.")
|
121
|
+
if not presigned_url:
|
122
|
+
raise Exception("No presigned_url returned.")
|
123
|
+
|
124
|
+
self._update_file_status(file_id, is_uploading=True)
|
125
|
+
with open(path, "rb") as object_file:
|
126
|
+
object_text = object_file.read()
|
127
|
+
response = requests.put(presigned_url, data=object_text)
|
128
|
+
if response.ok:
|
129
|
+
logger.info(f"File {path} uploaded successfully.")
|
130
|
+
update_file_status_result = self._update_file_status(
|
131
|
+
file_id, is_uploading=False, is_complete=True
|
132
|
+
)
|
133
|
+
else:
|
134
|
+
message = f"Failed to upload file {path}. {response.status_code}: {response.text}"
|
135
|
+
logger.error(message)
|
136
|
+
raise Exception(message)
|
137
|
+
file_pk = update_file_status_result.data.get("fileUpdate").get("pk")
|
138
|
+
file_access_url = f"{self.primitive.host_config.get("transport")}://{self.primitive.host}/files/{file_pk}/presigned-url/"
|
139
|
+
logger.info(f"Available at: {file_access_url}")
|
140
|
+
return update_file_status_result
|
141
|
+
|
142
|
+
@guard
|
143
|
+
def upload_file_via_api(
|
40
144
|
self,
|
41
145
|
path: Path,
|
42
146
|
is_public: bool = False,
|
43
147
|
key_prefix: str = "",
|
44
148
|
job_run_id: str = "",
|
45
149
|
):
|
150
|
+
"""
|
151
|
+
This method uploads a file via the Primitive API.
|
152
|
+
This does NOT upload the file straight to S3
|
153
|
+
"""
|
46
154
|
file_path = str(path.resolve())
|
47
155
|
if path.exists() is False:
|
48
156
|
raise FileNotFoundError(f"File not found at {file_path}")
|
primitive/files/commands.py
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
import json
|
2
|
+
import typing
|
3
|
+
from pathlib import Path
|
4
|
+
|
1
5
|
import click
|
6
|
+
|
2
7
|
from ..utils.printer import print_result
|
3
|
-
from pathlib import Path
|
4
|
-
import typing
|
5
8
|
|
6
9
|
if typing.TYPE_CHECKING:
|
7
10
|
from ..client import Primitive
|
@@ -19,10 +22,19 @@ def cli(context):
|
|
19
22
|
@click.argument("path", type=click.Path(exists=True))
|
20
23
|
@click.option("--public", "-p", help="Is this a Public file", is_flag=True)
|
21
24
|
@click.option("--key-prefix", "-k", help="Key Prefix", default="")
|
22
|
-
|
25
|
+
@click.option("--direct", "-k", help="direct", is_flag=True)
|
26
|
+
def file_upload_command(context, path, public, key_prefix, direct):
|
23
27
|
"""File Upload"""
|
24
28
|
primitive: Primitive = context.obj.get("PRIMITIVE")
|
25
29
|
path = Path(path)
|
26
|
-
|
27
|
-
|
30
|
+
if direct:
|
31
|
+
result = primitive.files.upload_file_direct(
|
32
|
+
path, is_public=public, key_prefix=key_prefix
|
33
|
+
)
|
34
|
+
else:
|
35
|
+
result = primitive.files.upload_file_via_api(
|
36
|
+
path, is_public=public, key_prefix=key_prefix
|
37
|
+
)
|
38
|
+
|
39
|
+
message = json.dumps(result.data)
|
28
40
|
print_result(message=message, context=context)
|
@@ -1,11 +1,32 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
from primitive.graphql.utility_fragments import operation_info_fragment
|
2
|
+
|
3
|
+
file_update_mutation = (
|
4
|
+
operation_info_fragment
|
5
|
+
+ """
|
6
|
+
mutation fileUpdate($input: FileInputPartial!) {
|
7
|
+
fileUpdate(input: $input) {
|
8
|
+
... on File {
|
5
9
|
id
|
6
|
-
|
7
|
-
signalName
|
10
|
+
pk
|
8
11
|
}
|
12
|
+
...OperationInfoFragment
|
9
13
|
}
|
10
14
|
}
|
11
15
|
"""
|
16
|
+
)
|
17
|
+
|
18
|
+
pending_file_create_mutation = (
|
19
|
+
operation_info_fragment
|
20
|
+
+ """
|
21
|
+
mutation pendingFileCreate($input: PendingFileCreateInput!) {
|
22
|
+
pendingFileCreate(input: $input) {
|
23
|
+
... on File {
|
24
|
+
id
|
25
|
+
pk
|
26
|
+
presignedUrlForUpload
|
27
|
+
}
|
28
|
+
...OperationInfoFragment
|
29
|
+
}
|
30
|
+
}
|
31
|
+
"""
|
32
|
+
)
|
primitive/hardware/actions.py
CHANGED
File without changes
|
@@ -0,0 +1,62 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from gql import gql
|
4
|
+
|
5
|
+
from primitive.utils.actions import BaseAction
|
6
|
+
|
7
|
+
from ..utils.auth import guard
|
8
|
+
from .graphql.queries import authorized_keys_query
|
9
|
+
|
10
|
+
HOME_DIRECTORY = Path.home()
|
11
|
+
AUTHORIZED_KEYS_FILEPATH = Path(HOME_DIRECTORY / ".ssh" / "authorized_keys")
|
12
|
+
|
13
|
+
|
14
|
+
class Provisioning(BaseAction):
|
15
|
+
@guard
|
16
|
+
def get_authorized_keys(self, reservation_id: str) -> str:
|
17
|
+
variables = {
|
18
|
+
"reservationId": reservation_id,
|
19
|
+
}
|
20
|
+
query = gql(authorized_keys_query)
|
21
|
+
result = self.primitive.session.execute(
|
22
|
+
query, variable_values=variables, get_execution_result=True
|
23
|
+
)
|
24
|
+
return result.data["authorizedKeys"]
|
25
|
+
|
26
|
+
def add_reservation_authorized_keys(self, reservation_id: str) -> None:
|
27
|
+
AUTHORIZED_KEYS_BACKUP_FILEPATH = Path(
|
28
|
+
HOME_DIRECTORY / ".ssh" / f"authorized_keys.bak-{reservation_id}"
|
29
|
+
)
|
30
|
+
|
31
|
+
if AUTHORIZED_KEYS_FILEPATH.exists():
|
32
|
+
AUTHORIZED_KEYS_BACKUP_FILEPATH.write_text(
|
33
|
+
AUTHORIZED_KEYS_FILEPATH.read_text()
|
34
|
+
)
|
35
|
+
else:
|
36
|
+
AUTHORIZED_KEYS_FILEPATH.touch()
|
37
|
+
|
38
|
+
authorized_keys = self.get_authorized_keys(reservation_id=reservation_id)
|
39
|
+
|
40
|
+
AUTHORIZED_KEYS_FILEPATH.write_text(
|
41
|
+
AUTHORIZED_KEYS_FILEPATH.read_text()
|
42
|
+
+ f"\n## START PRIMITIVE SSH PUBLIC KEYS FOR RESERVATION ID {reservation_id}\n"
|
43
|
+
+ authorized_keys
|
44
|
+
+ f"\n## END PRIMITIVE SSH PUBLIC KEYS FOR RESERVATION ID {reservation_id}\n"
|
45
|
+
)
|
46
|
+
|
47
|
+
# self.primitive.sshd.reload()
|
48
|
+
|
49
|
+
def remove_reservation_authorized_keys(self, reservation_id: str) -> None:
|
50
|
+
AUTHORIZED_KEYS_BACKUP_FILEPATH = Path(
|
51
|
+
HOME_DIRECTORY / ".ssh" / f"authorized_keys.bak-{reservation_id}"
|
52
|
+
)
|
53
|
+
|
54
|
+
if AUTHORIZED_KEYS_BACKUP_FILEPATH.exists():
|
55
|
+
AUTHORIZED_KEYS_FILEPATH.write_text(
|
56
|
+
AUTHORIZED_KEYS_BACKUP_FILEPATH.read_text()
|
57
|
+
)
|
58
|
+
AUTHORIZED_KEYS_BACKUP_FILEPATH.unlink()
|
59
|
+
else:
|
60
|
+
AUTHORIZED_KEYS_FILEPATH.unlink()
|
61
|
+
|
62
|
+
# self.primitive.sshd.reload()
|
File without changes
|
primitive/sim/actions.py
CHANGED
@@ -46,10 +46,6 @@ class Sim(BaseAction):
|
|
46
46
|
|
47
47
|
return True, message
|
48
48
|
|
49
|
-
def upload_file(self, path: Path, prefix: str) -> str:
|
50
|
-
file_upload_response = self.primitive.files.file_upload(path, key_prefix=prefix)
|
51
|
-
return file_upload_response.json()["data"]["fileUpload"]["id"]
|
52
|
-
|
53
49
|
def collect_artifacts(self, source: Path, job_run_id: str) -> None:
|
54
50
|
# Parse VCD artifacts using rust binding
|
55
51
|
# TODO: eventually make this smarter, only parsing VCDs for failed tests
|
@@ -72,12 +68,12 @@ class Sim(BaseAction):
|
|
72
68
|
)
|
73
69
|
for file_path in files:
|
74
70
|
try:
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
prefix=f"{job_run_id}/{str(PurePath(file_path).relative_to(Path(source)).parent)}",
|
79
|
-
)
|
71
|
+
key_prefix = f"{job_run_id}/{str(PurePath(file_path).relative_to(Path(source)).parent)}"
|
72
|
+
file_upload_response = self.primitive.files.upload_file_via_api(
|
73
|
+
file_path, key_prefix=key_prefix
|
80
74
|
)
|
75
|
+
file_id = file_upload_response.json()["data"]["fileUpload"]["id"]
|
76
|
+
file_ids.append(file_id)
|
81
77
|
except FileNotFoundError:
|
82
78
|
logger.warning(f"{file_path} not found...")
|
83
79
|
|
primitive/utils/verible.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
import tarfile
|
2
|
-
import requests
|
3
|
-
from .shell import add_path_to_shell
|
4
|
-
from .cache import get_deps_cache
|
5
2
|
|
3
|
+
import requests
|
6
4
|
from loguru import logger
|
7
5
|
|
6
|
+
from .cache import get_deps_cache
|
7
|
+
from .shell import add_path_to_shell
|
8
8
|
|
9
9
|
VERIBLE_MAC_OS_LINK = "https://github.com/chipsalliance/verible/releases/download/v0.0-3752-g8b64887e/verible-v0.0-3752-g8b64887e-macOS.tar.gz"
|
10
10
|
VERIBLE_WINDOWS_64_OS_LINK = "https://github.com/chipsalliance/verible/releases/download/v0.0-3752-g8b64887e/verible-v0.0-3752-g8b64887e-win64.zip"
|
@@ -1,12 +1,11 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: primitive
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.63
|
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
|
7
7
|
Author-email: Dylan Stein <dylan@primitive.tech>, Chase Zimmerman <chase@primitive.tech>
|
8
|
-
License
|
9
|
-
License-File: LICENSE.txt
|
8
|
+
License: MIT
|
10
9
|
Classifier: Development Status :: 4 - Beta
|
11
10
|
Classifier: Programming Language :: Python
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
@@ -21,6 +20,7 @@ Requires-Python: >=3.11
|
|
21
20
|
Requires-Dist: click
|
22
21
|
Requires-Dist: gql[all]
|
23
22
|
Requires-Dist: loguru
|
23
|
+
Requires-Dist: paramiko[all]
|
24
24
|
Requires-Dist: primitive-pal==0.1.4
|
25
25
|
Requires-Dist: pyyaml
|
26
26
|
Description-Content-Type: text/markdown
|
@@ -1,14 +1,14 @@
|
|
1
|
-
primitive/__about__.py,sha256=
|
1
|
+
primitive/__about__.py,sha256=uiVaG3uarbeydunpM548F3cr2wnnZhtxTkRYjlZVUoU,130
|
2
2
|
primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
|
3
3
|
primitive/cli.py,sha256=CGmWiqqCLMHtHGOUPuf3tVO6VvChBZ1VdSwCCglnBgA,2582
|
4
|
-
primitive/client.py,sha256=
|
4
|
+
primitive/client.py,sha256=P7cCDperMu3pxlmRAP-H2owM-cj1kRlZWrqujxnWa4o,2473
|
5
5
|
primitive/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
primitive/agent/actions.py,sha256=
|
6
|
+
primitive/agent/actions.py,sha256=Hosy2o2FntfBtcNqqHuMFq9dm99EVfySy0v2JGeufvc,6474
|
7
7
|
primitive/agent/commands.py,sha256=-dVDilELfkGfbZB7qfEPs77Dm1oT62qJj4tsIk4KoxI,254
|
8
8
|
primitive/agent/process.py,sha256=LVI-RB4a0YEuXUTYMXKL5Xi9euNwUI2nxj00mv8EFOg,2253
|
9
9
|
primitive/agent/provision.py,sha256=rmwnro1K5F8mwtd45XAq7RVQmpDWnbBCQ8X_qgWhm3M,1546
|
10
|
-
primitive/agent/runner.py,sha256=
|
11
|
-
primitive/agent/uploader.py,sha256=
|
10
|
+
primitive/agent/runner.py,sha256=xt0Ty-dAc8VatNIZwBZUJbC3mHXLpywpRVVyr_RvVYw,7142
|
11
|
+
primitive/agent/uploader.py,sha256=W-aXUgKZvcm9LbTXq8su_cgBl_mFrmcFfkkU9t8W04Q,3002
|
12
12
|
primitive/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
primitive/auth/actions.py,sha256=MPsG9LcKcOPwA7gZ9Ewk0PZJhTQvIrGfODdz4GxSzgA,999
|
14
14
|
primitive/auth/commands.py,sha256=JahUq0E2e7Xa-FX1WEUv7TgM6ieDvNH4VwRRtxAW7HE,2340
|
@@ -20,13 +20,14 @@ primitive/daemons/commands.py,sha256=-Muh-6ib4uAVtPn_67AcMrDwuCwYlCnRQozCi2Xurmk
|
|
20
20
|
primitive/daemons/launch_agents.py,sha256=qovt32gwpjGDd82z_SY5EGCUjaUyNA49pZFajZsw3eE,4796
|
21
21
|
primitive/daemons/launch_service.py,sha256=FPB9qKEjhllRfEpct0ng2L9lpIaGJbQwn1JdFT8uBA8,5600
|
22
22
|
primitive/exec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
-
primitive/exec/actions.py,sha256=
|
24
|
-
primitive/exec/commands.py,sha256=
|
23
|
+
primitive/exec/actions.py,sha256=hgUYi_G69SPbMRpVZv8EjeqIy41apXZIIw9lcg37zAY,2633
|
24
|
+
primitive/exec/commands.py,sha256=66LO2kkJC-ynNZQpUCXv4Ol15QoacdSZAHblePDcmLo,510
|
25
|
+
primitive/exec/interactive.py,sha256=TscY6s2ZysijidKPheq6y-fCErUVLS0zcdTW8XyFWGI,2435
|
25
26
|
primitive/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
|
-
primitive/files/actions.py,sha256=
|
27
|
-
primitive/files/commands.py,sha256=
|
27
|
+
primitive/files/actions.py,sha256=q33aP7UvCFfhhJ8iOnvI57jOpobBUaJspRN4_3vJApU,6675
|
28
|
+
primitive/files/commands.py,sha256=x1fxixMrZFvYZGeQb3u5ElsbmWXMmYGq0f_zZArGp8Q,1084
|
28
29
|
primitive/files/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
|
-
primitive/files/graphql/mutations.py,sha256=
|
30
|
+
primitive/files/graphql/mutations.py,sha256=SWxq6rwVWhouiuC72--Avpg9vybURFxmxiwkMY6dX7E,642
|
30
31
|
primitive/git/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
32
|
primitive/git/actions.py,sha256=0KHeHViZZqIhF6-Eqvhs0g_UmglqyWrOQKElQCm6jVw,1506
|
32
33
|
primitive/git/commands.py,sha256=sCeSjkRgSEjCEsB5seXgB_h6xfk0KpvMvzMKoRfUbRA,1177
|
@@ -37,10 +38,10 @@ primitive/graphql/relay.py,sha256=bmij2AjdpURQ6GGVCxwWhauF-r_SxuAU2oJ4sDbLxpI,72
|
|
37
38
|
primitive/graphql/sdk.py,sha256=BhCGmDtc4sNnH8CxbQSJyFwOZ-ZSqMtjsxMB3JRBhPw,1456
|
38
39
|
primitive/graphql/utility_fragments.py,sha256=uIjwILC4QtWNyO5vu77VjQf_p0jvP3A9q_6zRq91zqs,303
|
39
40
|
primitive/hardware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
primitive/hardware/actions.py,sha256=
|
41
|
+
primitive/hardware/actions.py,sha256=jtthNgRyeRD8txt4WqEZskPtsDWU2Yg2gJZLSrEx1io,18603
|
41
42
|
primitive/hardware/commands.py,sha256=_HaWOdRQSkhnA1xZZHZWgadSQ9Gijxtnzg2vc_IDSMA,1854
|
42
43
|
primitive/hardware/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
|
-
primitive/hardware/graphql/fragments.py,sha256=
|
44
|
+
primitive/hardware/graphql/fragments.py,sha256=PuqhW42fKUvRqOli5W7nOs2RfJ8FruSQC1gKBp3psBQ,271
|
44
45
|
primitive/hardware/graphql/mutations.py,sha256=Zd6HxnIgTJ9mJQAfKJkdeDfstcPAal6Bj38pnKb_RuI,904
|
45
46
|
primitive/hardware/graphql/queries.py,sha256=dhihQwr4O7zxDNRjeNWhkAXaSDOBsK-uqIczEGy1XLI,430
|
46
47
|
primitive/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -67,15 +68,19 @@ primitive/projects/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
67
68
|
primitive/projects/graphql/fragments.py,sha256=02F5nyI8i-ML_bV5FFHUgFWM5bBBfjmz_tkP-4QOXjU,127
|
68
69
|
primitive/projects/graphql/mutations.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
69
70
|
primitive/projects/graphql/queries.py,sha256=nFaVf6YOHA2L_FTgIUdRK-80hYTmv1a1X5ac7QPMp1k,646
|
71
|
+
primitive/provisioning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
72
|
+
primitive/provisioning/actions.py,sha256=IYZYAbtomtZtlkqDaBxx4e7PFKGkRNqek_tABH6q_zY,2116
|
73
|
+
primitive/provisioning/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
74
|
+
primitive/provisioning/graphql/queries.py,sha256=cBtuKa6shoatYZfKSnQoPJP6B8g8y3QhFqJ_pkvMcG0,134
|
70
75
|
primitive/reservations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
76
|
primitive/reservations/actions.py,sha256=XjjF0UJAgKryuSJqakLMAWshZIbuM-DkTmdU95cANs4,4434
|
72
77
|
primitive/reservations/commands.py,sha256=OwWWE9DrvtrVBcBki0bKTOqCzCQk090c0rPIAt89JLY,2243
|
73
78
|
primitive/reservations/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
74
|
-
primitive/reservations/graphql/fragments.py,sha256=
|
79
|
+
primitive/reservations/graphql/fragments.py,sha256=_TQfJeHky-Hh3WCHWobQ6-A1lpSvU-YkS0V9cqj2nOU,476
|
75
80
|
primitive/reservations/graphql/mutations.py,sha256=IqzwQL7OclN7RpIcidrTQo9cGYofY7wqoBOdnY0pwN8,651
|
76
81
|
primitive/reservations/graphql/queries.py,sha256=x31wTRelskX2fc0fx2qrY7XT1q74nvzLv_Xef3o9weg,746
|
77
82
|
primitive/sim/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
|
-
primitive/sim/actions.py,sha256=
|
83
|
+
primitive/sim/actions.py,sha256=oR77UmCp6PxDEuKvoNejeHOG6E5r6uHax3G9OZYoofM,4810
|
79
84
|
primitive/sim/commands.py,sha256=8PaOfL1MO6qxTn7mNVRnBU1X2wa3gk_mlbAhBW6MnI0,591
|
80
85
|
primitive/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
81
86
|
primitive/utils/actions.py,sha256=HOFrmM3-0A_A3NS84MqrZ6JmQEiiPSoDqEeuu6b_qfQ,196
|
@@ -87,9 +92,9 @@ primitive/utils/git.py,sha256=1qNOu8X-33CavmrD580BmrFhD_WVO9PGWHUUboXJR_g,663
|
|
87
92
|
primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIUk,3058
|
88
93
|
primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
|
89
94
|
primitive/utils/shell.py,sha256=j7E1YwgNWw57dFHVfEbqRNVcPHX0xDefX2vFSNgeI_8,1648
|
90
|
-
primitive/utils/verible.py,sha256=
|
91
|
-
primitive-0.1.
|
92
|
-
primitive-0.1.
|
93
|
-
primitive-0.1.
|
94
|
-
primitive-0.1.
|
95
|
-
primitive-0.1.
|
95
|
+
primitive/utils/verible.py,sha256=Zb5NUISvcaIgEvgCDBWr-GCoceMa79Tcwvr5Wl9lfnA,2252
|
96
|
+
primitive-0.1.63.dist-info/METADATA,sha256=o-IZgpjA7lYJYn-sxuRBDA4EXxHA_c57G1U8Q3dcZzE,3774
|
97
|
+
primitive-0.1.63.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
98
|
+
primitive-0.1.63.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
|
99
|
+
primitive-0.1.63.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
|
100
|
+
primitive-0.1.63.dist-info/RECORD,,
|
File without changes
|
File without changes
|