primitive 0.1.58__tar.gz → 0.1.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.1.60/.vscode/settings.json +22 -0
- {primitive-0.1.58 → primitive-0.1.60}/PKG-INFO +1 -1
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/__about__.py +1 -1
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/agent/actions.py +8 -4
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/agent/process.py +2 -2
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/agent/provision.py +2 -8
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/agent/runner.py +22 -12
- primitive-0.1.60/src/primitive/agent/uploader.py +106 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/auth/actions.py +5 -12
- primitive-0.1.60/src/primitive/auth/graphql/queries.py +13 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/cli.py +11 -5
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/client.py +4 -0
- primitive-0.1.60/src/primitive/exec/actions.py +50 -0
- primitive-0.1.60/src/primitive/exec/commands.py +22 -0
- primitive-0.1.60/src/primitive/files/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/files/actions.py +9 -16
- primitive-0.1.60/src/primitive/files/graphql/__init__.py +0 -0
- primitive-0.1.60/src/primitive/files/graphql/mutations.py +11 -0
- primitive-0.1.60/src/primitive/git/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/git/actions.py +11 -11
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/git/commands.py +7 -6
- primitive-0.1.60/src/primitive/git/graphql/__init__.py +0 -0
- primitive-0.1.60/src/primitive/git/graphql/queries.py +7 -0
- primitive-0.1.60/src/primitive/graphql/__init__.py +0 -0
- primitive-0.1.60/src/primitive/graphql/relay.py +32 -0
- primitive-0.1.60/src/primitive/graphql/utility_fragments.py +19 -0
- primitive-0.1.60/src/primitive/hardware/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/hardware/actions.py +74 -121
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/hardware/commands.py +15 -5
- primitive-0.1.60/src/primitive/hardware/graphql/__init__.py +0 -0
- primitive-0.1.60/src/primitive/hardware/graphql/fragments.py +22 -0
- primitive-0.1.60/src/primitive/hardware/graphql/mutations.py +45 -0
- primitive-0.1.60/src/primitive/hardware/graphql/queries.py +31 -0
- primitive-0.1.60/src/primitive/jobs/__init__.py +0 -0
- primitive-0.1.60/src/primitive/jobs/actions.py +162 -0
- primitive-0.1.60/src/primitive/jobs/graphql/__init__.py +0 -0
- primitive-0.1.60/src/primitive/jobs/graphql/fragments.py +47 -0
- primitive-0.1.60/src/primitive/jobs/graphql/mutations.py +11 -0
- primitive-0.1.60/src/primitive/jobs/graphql/queries.py +100 -0
- primitive-0.1.60/src/primitive/lint/__init__.py +0 -0
- primitive-0.1.60/src/primitive/organizations/__init__.py +0 -0
- primitive-0.1.60/src/primitive/organizations/actions.py +41 -0
- primitive-0.1.60/src/primitive/organizations/graphql/__init__.py +0 -0
- primitive-0.1.60/src/primitive/organizations/graphql/fragments.py +10 -0
- primitive-0.1.60/src/primitive/organizations/graphql/mutations.py +0 -0
- primitive-0.1.60/src/primitive/organizations/graphql/queries.py +38 -0
- primitive-0.1.60/src/primitive/projects/__init__.py +0 -0
- primitive-0.1.60/src/primitive/projects/actions.py +38 -0
- primitive-0.1.60/src/primitive/projects/graphql/__init__.py +0 -0
- primitive-0.1.60/src/primitive/projects/graphql/fragments.py +10 -0
- primitive-0.1.60/src/primitive/projects/graphql/mutations.py +0 -0
- primitive-0.1.60/src/primitive/projects/graphql/queries.py +36 -0
- primitive-0.1.60/src/primitive/reservations/__init__.py +0 -0
- primitive-0.1.60/src/primitive/reservations/actions.py +134 -0
- primitive-0.1.60/src/primitive/reservations/commands.py +67 -0
- primitive-0.1.60/src/primitive/reservations/graphql/__init__.py +0 -0
- primitive-0.1.60/src/primitive/reservations/graphql/fragments.py +40 -0
- primitive-0.1.60/src/primitive/reservations/graphql/mutations.py +29 -0
- primitive-0.1.60/src/primitive/reservations/graphql/queries.py +47 -0
- primitive-0.1.60/src/primitive/sim/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/sim/actions.py +12 -9
- primitive-0.1.60/src/primitive/utils/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/cache.py +14 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/shell.py +25 -0
- primitive-0.1.58/.vscode/settings.json +0 -11
- primitive-0.1.58/src/primitive/agent/uploader.py +0 -77
- primitive-0.1.58/src/primitive/jobs/actions.py +0 -331
- primitive-0.1.58/src/primitive/organizations/actions.py +0 -86
- primitive-0.1.58/src/primitive/projects/actions.py +0 -80
- {primitive-0.1.58 → primitive-0.1.60}/.git-hooks/pre-commit +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/.gitattributes +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/.github/workflows/lint.yml +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/.github/workflows/publish.yml +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/.gitignore +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/LICENSE.txt +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/Makefile +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/README.md +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/linux setup.md +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/pyproject.toml +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/__init__.py +0 -0
- {primitive-0.1.58/src/primitive/auth → primitive-0.1.60/src/primitive/agent}/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/agent/commands.py +0 -0
- {primitive-0.1.58/src/primitive/git → primitive-0.1.60/src/primitive/auth}/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/auth/commands.py +0 -0
- {primitive-0.1.58/src/primitive → primitive-0.1.60/src/primitive/auth}/graphql/__init__.py +0 -0
- {primitive-0.1.58/src/primitive/projects → primitive-0.1.60/src/primitive/daemons}/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/daemons/actions.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/daemons/commands.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/daemons/launch_agents.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/daemons/launch_service.py +0 -0
- {primitive-0.1.58/src/primitive/sim → primitive-0.1.60/src/primitive/exec}/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/files/commands.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/graphql/sdk.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/jobs/commands.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/lint/actions.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/lint/commands.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/organizations/commands.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/projects/commands.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/sim/commands.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/actions.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/auth.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/config.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/files.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/git.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/memory_size.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/printer.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/src/primitive/utils/verible.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/tests/__init__.py +0 -0
- {primitive-0.1.58 → primitive-0.1.60}/uv.lock +0 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"cSpell.words": [
|
3
|
+
"Checkin",
|
4
|
+
"loguru",
|
5
|
+
"machdep",
|
6
|
+
"testsuites",
|
7
|
+
"untar",
|
8
|
+
"Untaring",
|
9
|
+
"VERIBLE",
|
10
|
+
"verilog",
|
11
|
+
"workdir"
|
12
|
+
],
|
13
|
+
"[python]": {
|
14
|
+
"editor.defaultFormatter": "charliermarsh.ruff",
|
15
|
+
"editor.formatOnSave": true,
|
16
|
+
"editor.codeActionsOnSave": {
|
17
|
+
"source.fixAll": "explicit"
|
18
|
+
}
|
19
|
+
},
|
20
|
+
"ruff.fixAll": true,
|
21
|
+
"ruff.organizeImports": true
|
22
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: primitive
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.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
|
@@ -1,9 +1,12 @@
|
|
1
|
-
import sys
|
2
1
|
import shutil
|
2
|
+
import sys
|
3
3
|
from time import sleep
|
4
|
-
|
4
|
+
|
5
5
|
from loguru import logger
|
6
|
+
|
6
7
|
from primitive.__about__ import __version__
|
8
|
+
from primitive.utils.actions import BaseAction
|
9
|
+
|
7
10
|
from ..utils.cache import get_sources_cache
|
8
11
|
from .runner import AgentRunner
|
9
12
|
from .uploader import Uploader
|
@@ -73,12 +76,12 @@ class Agent(BaseAction):
|
|
73
76
|
sleep(sleep_amount)
|
74
77
|
continue
|
75
78
|
|
76
|
-
|
79
|
+
job_runs_result = self.primitive.jobs.get_job_runs(
|
77
80
|
status="pending", first=1, reservation_id=active_reservation_id
|
78
81
|
)
|
79
82
|
|
80
83
|
pending_job_runs = [
|
81
|
-
edge["node"] for edge in
|
84
|
+
edge["node"] for edge in job_runs_result.data["jobRuns"]["edges"]
|
82
85
|
]
|
83
86
|
|
84
87
|
if not pending_job_runs:
|
@@ -126,6 +129,7 @@ class Agent(BaseAction):
|
|
126
129
|
source_dir=source_dir,
|
127
130
|
job_id=job_run["id"],
|
128
131
|
job_slug=job_run["job"]["slug"],
|
132
|
+
max_log_size=500 * 1024,
|
129
133
|
)
|
130
134
|
except Exception as e:
|
131
135
|
# Log Error
|
@@ -34,7 +34,7 @@ class Process:
|
|
34
34
|
self.sel.register(self.process.stdout, selectors.EVENT_READ)
|
35
35
|
self.sel.register(self.process.stderr, selectors.EVENT_READ)
|
36
36
|
|
37
|
-
def
|
37
|
+
def log(self):
|
38
38
|
for key, _ in self.sel.select():
|
39
39
|
data = key.fileobj.readline()
|
40
40
|
if not data:
|
@@ -48,7 +48,7 @@ class Process:
|
|
48
48
|
|
49
49
|
def wait(self):
|
50
50
|
while True:
|
51
|
-
self.
|
51
|
+
self.log()
|
52
52
|
if not self.is_running():
|
53
53
|
break
|
54
54
|
|
@@ -2,6 +2,7 @@ import sys
|
|
2
2
|
from subprocess import Popen, PIPE
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Dict
|
5
|
+
from ..utils.shell import env_string_to_dict
|
5
6
|
|
6
7
|
|
7
8
|
class ProvisionPython:
|
@@ -34,14 +35,7 @@ class ProvisionPython:
|
|
34
35
|
output, _ = proc.communicate()
|
35
36
|
|
36
37
|
# Split the output into lines and parse it into a dictionary
|
37
|
-
env_vars =
|
38
|
-
|
39
|
-
for line in output.splitlines():
|
40
|
-
var_line = line.split("=", 1)
|
41
|
-
|
42
|
-
if len(var_line) == 2:
|
43
|
-
key, value = var_line
|
44
|
-
env_vars[key] = value
|
38
|
+
env_vars = env_string_to_dict(output)
|
45
39
|
|
46
40
|
cmd = f"python -m pip install -r {self.requirements_path}"
|
47
41
|
proc = Popen(
|
@@ -1,14 +1,15 @@
|
|
1
|
-
import yaml
|
2
|
-
import typing
|
3
1
|
import os
|
4
2
|
import threading
|
5
|
-
|
6
|
-
from typing import TypedDict, Iterable, List, Optional, Dict
|
3
|
+
import typing
|
7
4
|
from pathlib import Path, PurePath
|
5
|
+
from time import sleep
|
6
|
+
from typing import Dict, Iterable, List, Optional, TypedDict
|
7
|
+
|
8
|
+
import yaml
|
8
9
|
from loguru import logger
|
9
10
|
from .process import Process
|
10
11
|
from .provision import ProvisionPython
|
11
|
-
from ..utils.cache import get_artifacts_cache
|
12
|
+
from ..utils.cache import get_artifacts_cache, get_logs_cache
|
12
13
|
from ..utils.files import find_files_for_extension
|
13
14
|
|
14
15
|
try:
|
@@ -54,12 +55,11 @@ class AgentRunner:
|
|
54
55
|
self.job_slug = job_slug
|
55
56
|
self.max_log_size = max_log_size
|
56
57
|
self.artifacts_dir = get_artifacts_cache(self.job_id)
|
58
|
+
self.logs_dir = get_logs_cache(self.job_id)
|
59
|
+
self.logger_handle = None
|
57
60
|
|
58
61
|
logger.enable("primitive")
|
59
|
-
self.
|
60
|
-
Path(self.artifacts_dir / "runner_{time}.log"),
|
61
|
-
rotation=self.max_log_size, # Rotate when the log file reaches 10MB
|
62
|
-
)
|
62
|
+
self.swap_logs(label="init")
|
63
63
|
|
64
64
|
logger.info(f"Scanning directory for job {self.job_slug}")
|
65
65
|
|
@@ -111,6 +111,9 @@ class AgentRunner:
|
|
111
111
|
conclusion = None
|
112
112
|
total_errors = 0
|
113
113
|
for step in self.steps():
|
114
|
+
# Swap logger
|
115
|
+
self.swap_logs(label=step["name"])
|
116
|
+
|
114
117
|
logger.info(f"Beginning step {step['name']}")
|
115
118
|
|
116
119
|
# Update workdir
|
@@ -136,7 +139,7 @@ class AgentRunner:
|
|
136
139
|
while proc.is_running():
|
137
140
|
# Check job status
|
138
141
|
status = self.primitive.jobs.get_job_status(self.job_id)
|
139
|
-
status_value = status["jobRun"]["status"]
|
142
|
+
status_value = status.data["jobRun"]["status"]
|
140
143
|
|
141
144
|
# TODO: Should probably use request_cancelled or something
|
142
145
|
# once we change it, we'll have to call conclude w/ cancelled status
|
@@ -182,6 +185,15 @@ class AgentRunner:
|
|
182
185
|
logger.info(f"Completed {self.job_slug} job")
|
183
186
|
logger.remove(self.logger_handle)
|
184
187
|
|
188
|
+
def swap_logs(self, label: str):
|
189
|
+
# Remove Handle
|
190
|
+
if self.logger_handle:
|
191
|
+
logger.remove(self.logger_handle)
|
192
|
+
|
193
|
+
self.logger_handle = logger.add(
|
194
|
+
Path(self.logs_dir / f"{label}_{{time}}.primitive.log"), rotation=self.max_log_size
|
195
|
+
)
|
196
|
+
|
185
197
|
def provision(self) -> Optional[Dict]:
|
186
198
|
match self.job["provision"]:
|
187
199
|
case "python":
|
@@ -197,8 +209,6 @@ class AgentRunner:
|
|
197
209
|
return prov.create_env()
|
198
210
|
|
199
211
|
def collect_artifacts(self, step: JobStep) -> None:
|
200
|
-
# str(PurePath(file_path).relative_to(Path(source))
|
201
|
-
|
202
212
|
# Search each artifact type
|
203
213
|
for artifact in step["artifacts"]:
|
204
214
|
files = find_files_for_extension(self.source_dir, artifact["extension"])
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import typing
|
2
|
+
from typing import Dict
|
3
|
+
import shutil
|
4
|
+
import os
|
5
|
+
from loguru import logger
|
6
|
+
from pathlib import Path, PurePath
|
7
|
+
from ..utils.cache import get_artifacts_cache, get_logs_cache
|
8
|
+
|
9
|
+
if typing.TYPE_CHECKING:
|
10
|
+
import primitive.client
|
11
|
+
|
12
|
+
|
13
|
+
class Uploader:
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
primitive: "primitive.client.Primitive",
|
17
|
+
):
|
18
|
+
self.primitive = primitive
|
19
|
+
|
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
|
+
def upload_dir(self, cache: Path) -> Dict:
|
27
|
+
file_ids = []
|
28
|
+
job_run_id = cache.name
|
29
|
+
|
30
|
+
files = None
|
31
|
+
has_walk = getattr(cache, "walk", None)
|
32
|
+
if has_walk:
|
33
|
+
files = sorted(
|
34
|
+
[
|
35
|
+
current_path / file
|
36
|
+
for current_path, _, current_path_files in cache.walk()
|
37
|
+
for file in current_path_files
|
38
|
+
],
|
39
|
+
key=lambda p: p.stat().st_size,
|
40
|
+
)
|
41
|
+
else:
|
42
|
+
files = sorted(
|
43
|
+
[
|
44
|
+
Path(Path(current_path) / file)
|
45
|
+
for current_path, _, current_path_files in os.walk(cache)
|
46
|
+
for file in current_path_files
|
47
|
+
],
|
48
|
+
key=lambda p: p.stat().st_size,
|
49
|
+
)
|
50
|
+
|
51
|
+
for file in files:
|
52
|
+
upload_id = self.upload_file(
|
53
|
+
file,
|
54
|
+
prefix=str(PurePath(file).relative_to(cache.parent).parent),
|
55
|
+
job_run_id=job_run_id,
|
56
|
+
)
|
57
|
+
|
58
|
+
if upload_id:
|
59
|
+
file_ids.append(upload_id)
|
60
|
+
continue
|
61
|
+
|
62
|
+
logger.error(f"Unable to upload file {file}")
|
63
|
+
|
64
|
+
# Clean up job cache
|
65
|
+
shutil.rmtree(path=cache)
|
66
|
+
|
67
|
+
return {job_run_id: file_ids}
|
68
|
+
|
69
|
+
|
70
|
+
def scan(self) -> None:
|
71
|
+
# Scan artifacts directory
|
72
|
+
artifacts_dir = get_artifacts_cache()
|
73
|
+
logs_dir = get_logs_cache()
|
74
|
+
|
75
|
+
artifacts = sorted([
|
76
|
+
artifacts_cache
|
77
|
+
for artifacts_cache in artifacts_dir.iterdir()
|
78
|
+
if artifacts_cache.is_dir()
|
79
|
+
], key=lambda p: p.stat().st_ctime)
|
80
|
+
|
81
|
+
logs = sorted([
|
82
|
+
logs_cache
|
83
|
+
for logs_cache in logs_dir.iterdir()
|
84
|
+
if logs_cache.is_dir()
|
85
|
+
], key=lambda p: p.stat().st_ctime)
|
86
|
+
|
87
|
+
log_files = {
|
88
|
+
job_id: files
|
89
|
+
for log_path in logs
|
90
|
+
for job_id, files in self.upload_dir(log_path).items()
|
91
|
+
}
|
92
|
+
|
93
|
+
artifact_files = {
|
94
|
+
job_id: files
|
95
|
+
for artifact_path in artifacts
|
96
|
+
for job_id, files in self.upload_dir(artifact_path).items()
|
97
|
+
}
|
98
|
+
|
99
|
+
files_by_id = {
|
100
|
+
job_id: log_files.get(job_id, []) + artifact_files.get(job_id, [])
|
101
|
+
for job_id in log_files.keys() | artifact_files.keys()
|
102
|
+
}
|
103
|
+
|
104
|
+
# Update job run
|
105
|
+
for job_id, files in files_by_id.items():
|
106
|
+
self.primitive.jobs.job_run_update(id=job_id, file_ids=files)
|
@@ -1,23 +1,16 @@
|
|
1
1
|
from gql import gql
|
2
2
|
|
3
|
-
from ..utils.config import read_config_file, update_config_file
|
4
|
-
from ..utils.auth import guard
|
5
|
-
|
6
3
|
from primitive.utils.actions import BaseAction
|
7
4
|
|
5
|
+
from ..utils.auth import guard
|
6
|
+
from ..utils.config import read_config_file, update_config_file
|
7
|
+
from .graphql.queries import whoami_query
|
8
|
+
|
8
9
|
|
9
10
|
class Auth(BaseAction):
|
10
11
|
@guard
|
11
12
|
def whoami(self):
|
12
|
-
query = gql(
|
13
|
-
"""
|
14
|
-
query whoami {
|
15
|
-
whoami {
|
16
|
-
username
|
17
|
-
}
|
18
|
-
}
|
19
|
-
"""
|
20
|
-
)
|
13
|
+
query = gql(whoami_query)
|
21
14
|
|
22
15
|
result = self.primitive.session.execute(query, get_execution_result=True)
|
23
16
|
|
@@ -1,18 +1,22 @@
|
|
1
1
|
import os
|
2
2
|
import sys
|
3
|
+
|
3
4
|
import click
|
5
|
+
|
4
6
|
from .__about__ import __version__
|
5
|
-
from .
|
7
|
+
from .agent.commands import cli as agent_commands
|
6
8
|
from .auth.commands import config_command, whoami_command
|
9
|
+
from .client import Primitive
|
10
|
+
from .daemons.commands import cli as daemons_commands
|
11
|
+
from .exec.commands import cli as exec_commands
|
7
12
|
from .files.commands import cli as file_commands
|
8
|
-
from .hardware.commands import cli as hardware_commands
|
9
|
-
from .lint.commands import cli as lint_commands
|
10
|
-
from .agent.commands import cli as agent_commands
|
11
13
|
from .git.commands import cli as git_commands
|
12
|
-
from .
|
14
|
+
from .hardware.commands import cli as hardware_commands
|
13
15
|
from .jobs.commands import cli as jobs_commands
|
16
|
+
from .lint.commands import cli as lint_commands
|
14
17
|
from .organizations.commands import cli as organizations_commands
|
15
18
|
from .projects.commands import cli as projects_commands
|
19
|
+
from .reservations.commands import cli as reservations_commands
|
16
20
|
from .sim.commands import cli as sim_commands
|
17
21
|
|
18
22
|
|
@@ -69,6 +73,8 @@ cli.add_command(jobs_commands, "jobs")
|
|
69
73
|
cli.add_command(organizations_commands, "organizations")
|
70
74
|
cli.add_command(projects_commands, "projects")
|
71
75
|
cli.add_command(sim_commands, "sim")
|
76
|
+
cli.add_command(reservations_commands, "reservations")
|
77
|
+
cli.add_command(exec_commands, "exec")
|
72
78
|
|
73
79
|
if __name__ == "__main__":
|
74
80
|
cli(obj={})
|
@@ -11,6 +11,8 @@ from .git.actions import Git
|
|
11
11
|
from .daemons.actions import Daemons
|
12
12
|
from .jobs.actions import Jobs
|
13
13
|
from .organizations.actions import Organizations
|
14
|
+
from .exec.actions import Exec
|
15
|
+
from .reservations.actions import Reservations
|
14
16
|
|
15
17
|
from loguru import logger
|
16
18
|
|
@@ -58,11 +60,13 @@ class Primitive:
|
|
58
60
|
self.jobs: Jobs = Jobs(self)
|
59
61
|
self.files: Files = Files(self)
|
60
62
|
self.sim: Sim = Sim(self)
|
63
|
+
self.reservations: Reservations = Reservations(self)
|
61
64
|
self.hardware: Hardware = Hardware(self)
|
62
65
|
self.lint: Lint = Lint(self)
|
63
66
|
self.agent: Agent = Agent(self)
|
64
67
|
self.git: Git = Git(self)
|
65
68
|
self.daemons: Daemons = Daemons(self)
|
69
|
+
self.exec: Exec = Exec(self)
|
66
70
|
|
67
71
|
def get_host_config(self):
|
68
72
|
self.full_config = read_config_file()
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
if typing.TYPE_CHECKING:
|
4
|
+
pass
|
5
|
+
|
6
|
+
|
7
|
+
from primitive.utils.actions import BaseAction
|
8
|
+
|
9
|
+
|
10
|
+
class Exec(BaseAction):
|
11
|
+
def __init__(self, *args, **kwargs) -> None:
|
12
|
+
super().__init__(*args, **kwargs)
|
13
|
+
|
14
|
+
def execute_command(self, hardware_identifier: str, command: str) -> None:
|
15
|
+
hardware = self.primitive.hardware.get_hardware_from_slug_or_id(
|
16
|
+
hardware_identifier=hardware_identifier
|
17
|
+
)
|
18
|
+
|
19
|
+
# since we found hardware, we need to check that the user:
|
20
|
+
# - has a valid reservation on it
|
21
|
+
# - OR if the device is free we can reserve it
|
22
|
+
|
23
|
+
# if we create a reservation on behalf of the user, we need to release it after
|
24
|
+
created_reservation_on_behalf_of_user = False
|
25
|
+
|
26
|
+
if active_reservation := hardware["activeReservation"]:
|
27
|
+
active_reservation_id = active_reservation["id"]
|
28
|
+
reservation_result = self.primitive.reservations.get_reservation(
|
29
|
+
reservation_id=active_reservation_id
|
30
|
+
)
|
31
|
+
reservation = reservation_result.data["reservation"]
|
32
|
+
else:
|
33
|
+
reservation_result = self.primitive.reservations.create_reservation(
|
34
|
+
requested_hardware_ids=[hardware["id"]],
|
35
|
+
reason="Executing command from Primitive CLI",
|
36
|
+
)
|
37
|
+
reservation = reservation_result.data["reservationCreate"]
|
38
|
+
created_reservation_on_behalf_of_user = True
|
39
|
+
|
40
|
+
reservation = self.primitive.reservations.wait_for_reservation_status(
|
41
|
+
reservation_id=reservation["id"], desired_status="in_progress"
|
42
|
+
)
|
43
|
+
|
44
|
+
print(f"Executing command: {command} on {hardware['name']}")
|
45
|
+
|
46
|
+
if created_reservation_on_behalf_of_user:
|
47
|
+
print("Cleaning up reservation.")
|
48
|
+
self.primitive.reservations.release_reservation(
|
49
|
+
reservation_or_hardware_identifier=reservation["id"]
|
50
|
+
)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
import click
|
4
|
+
|
5
|
+
if typing.TYPE_CHECKING:
|
6
|
+
from ..client import Primitive
|
7
|
+
|
8
|
+
|
9
|
+
@click.command("exec")
|
10
|
+
@click.pass_context
|
11
|
+
@click.argument(
|
12
|
+
"hardware_identifier",
|
13
|
+
type=str,
|
14
|
+
required=True,
|
15
|
+
)
|
16
|
+
@click.argument("command", nargs=-1, required=True)
|
17
|
+
def cli(context, hardware_identifier: str, command: str) -> None:
|
18
|
+
"""Exec"""
|
19
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
20
|
+
primitive.exec.execute_command(
|
21
|
+
hardware_identifier=hardware_identifier, command=command
|
22
|
+
)
|
File without changes
|
@@ -1,10 +1,13 @@
|
|
1
1
|
from pathlib import Path
|
2
|
+
|
2
3
|
from gql import gql
|
3
|
-
from primitive.graphql.sdk import create_requests_session
|
4
|
-
from ..utils.auth import guard
|
5
4
|
|
5
|
+
from primitive.graphql.sdk import create_requests_session
|
6
6
|
from primitive.utils.actions import BaseAction
|
7
7
|
|
8
|
+
from ..utils.auth import guard
|
9
|
+
from .graphql.mutations import create_trace_mutation
|
10
|
+
|
8
11
|
|
9
12
|
class Files(BaseAction):
|
10
13
|
@guard
|
@@ -17,19 +20,7 @@ class Files(BaseAction):
|
|
17
20
|
is_vector: bool,
|
18
21
|
size: int,
|
19
22
|
):
|
20
|
-
mutation = gql(
|
21
|
-
"""
|
22
|
-
mutation createTrace($input: TraceCreateInput!) {
|
23
|
-
traceCreate(input: $input) {
|
24
|
-
... on Trace {
|
25
|
-
id
|
26
|
-
signalId
|
27
|
-
signalName
|
28
|
-
}
|
29
|
-
}
|
30
|
-
}
|
31
|
-
"""
|
32
|
-
)
|
23
|
+
mutation = gql(create_trace_mutation)
|
33
24
|
input = {
|
34
25
|
"fileId": file_id,
|
35
26
|
"signalId": signal_id,
|
@@ -39,7 +30,9 @@ class Files(BaseAction):
|
|
39
30
|
"size": size,
|
40
31
|
}
|
41
32
|
variables = {"input": input}
|
42
|
-
result = self.primitive.session.execute(
|
33
|
+
result = self.primitive.session.execute(
|
34
|
+
mutation, variable_values=variables, get_execution_result=True
|
35
|
+
)
|
43
36
|
return result
|
44
37
|
|
45
38
|
@guard
|
File without changes
|
File without changes
|
@@ -1,26 +1,26 @@
|
|
1
|
+
import os
|
1
2
|
from pathlib import Path
|
3
|
+
|
4
|
+
from gql import gql
|
5
|
+
from loguru import logger
|
6
|
+
|
2
7
|
from primitive.utils.actions import BaseAction
|
8
|
+
|
3
9
|
from ..utils.auth import guard
|
4
|
-
from
|
5
|
-
import os
|
10
|
+
from .graphql.queries import github_app_token_query
|
6
11
|
|
7
12
|
|
8
13
|
class Git(BaseAction):
|
9
14
|
@guard
|
10
15
|
def get_github_access_token(self) -> str:
|
11
|
-
query =
|
12
|
-
query githubAppToken{
|
13
|
-
githubAppToken {
|
14
|
-
token
|
15
|
-
}
|
16
|
-
}
|
17
|
-
"""
|
18
|
-
|
16
|
+
query = gql(github_app_token_query)
|
19
17
|
filters = {}
|
20
18
|
variables = {
|
21
19
|
"filters": filters,
|
22
20
|
}
|
23
|
-
result = self.primitive.session.execute(
|
21
|
+
result = self.primitive.session.execute(
|
22
|
+
query, variable_values=variables, get_execution_result=True
|
23
|
+
)
|
24
24
|
return result
|
25
25
|
|
26
26
|
def download_git_repository_at_ref(
|
@@ -1,8 +1,10 @@
|
|
1
|
-
import
|
2
|
-
from pathlib import Path
|
1
|
+
import os
|
3
2
|
import typing
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
import click
|
6
|
+
|
4
7
|
from ..utils.printer import print_result
|
5
|
-
import os
|
6
8
|
|
7
9
|
if typing.TYPE_CHECKING:
|
8
10
|
from ..client import Primitive
|
@@ -39,11 +41,10 @@ def download_ref_command(
|
|
39
41
|
destination: Path = Path.cwd(),
|
40
42
|
):
|
41
43
|
primitive: Primitive = context.obj.get("PRIMITIVE")
|
42
|
-
|
44
|
+
path = primitive.git.download_git_repository_at_ref(
|
43
45
|
git_repo_full_name=git_repo_full_name,
|
44
46
|
git_ref=git_ref,
|
45
47
|
github_access_token=github_access_token,
|
46
48
|
destination=destination,
|
47
49
|
)
|
48
|
-
|
49
|
-
print_result(message, context=context, fg=fg)
|
50
|
+
print_result(message=path, context=context)
|
File without changes
|
File without changes
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import base64
|
2
|
+
from typing import Tuple
|
3
|
+
|
4
|
+
|
5
|
+
def from_base64(value: str) -> Tuple[str, str]:
|
6
|
+
"""
|
7
|
+
FROM:
|
8
|
+
https://github.com/strawberry-graphql/strawberry/blob/main/strawberry/relay/utils.py#L16C1-L40C1
|
9
|
+
|
10
|
+
Parse the base64 encoded relay value.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
value:
|
14
|
+
The value to be parsed
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
A tuple of (TypeName, NodeID).
|
18
|
+
|
19
|
+
Raises:
|
20
|
+
ValueError:
|
21
|
+
If the value is not in the expected format
|
22
|
+
|
23
|
+
"""
|
24
|
+
try:
|
25
|
+
res = base64.b64decode(value.encode()).decode().split(":", 1)
|
26
|
+
except Exception as e:
|
27
|
+
raise ValueError(str(e)) from e
|
28
|
+
|
29
|
+
if len(res) != 2:
|
30
|
+
raise ValueError(f"{res} expected to contain only 2 items")
|
31
|
+
|
32
|
+
return res[0], res[1]
|