remotivelabs-cli 0.0.24__py3-none-any.whl → 0.0.26__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.
- cli/broker/brokers.py +2 -2
- cli/broker/export.py +5 -5
- cli/broker/files.py +5 -5
- cli/broker/lib/broker.py +59 -49
- cli/broker/license_flows.py +11 -9
- cli/broker/licenses.py +2 -2
- cli/broker/playback.py +14 -34
- cli/broker/record.py +3 -3
- cli/broker/scripting.py +4 -4
- cli/broker/signals.py +11 -11
- cli/cloud/__init__.py +0 -1
- cli/cloud/auth.py +40 -35
- cli/cloud/auth_tokens.py +39 -36
- cli/cloud/brokers.py +24 -33
- cli/cloud/cloud_cli.py +11 -7
- cli/cloud/configs.py +19 -11
- cli/cloud/filestorage.py +155 -0
- cli/cloud/projects.py +10 -7
- cli/cloud/recordings.py +127 -108
- cli/cloud/recordings_playback.py +52 -39
- cli/cloud/rest_helper.py +248 -190
- cli/cloud/resumable_upload.py +62 -0
- cli/cloud/sample_recordings.py +5 -5
- cli/cloud/service_account_tokens.py +18 -16
- cli/cloud/service_accounts.py +9 -9
- cli/connect/__init__.py +0 -1
- cli/connect/connect.py +7 -6
- cli/connect/protopie/protopie.py +32 -16
- cli/errors.py +6 -5
- cli/remotive.py +13 -9
- cli/requirements.txt +4 -1
- cli/settings.py +9 -9
- cli/tools/__init__.py +0 -1
- cli/tools/can/__init__.py +0 -1
- cli/tools/can/can.py +8 -8
- cli/tools/tools.py +2 -2
- {remotivelabs_cli-0.0.24.dist-info → remotivelabs_cli-0.0.26.dist-info}/METADATA +6 -4
- remotivelabs_cli-0.0.26.dist-info/RECORD +44 -0
- remotivelabs_cli-0.0.24.dist-info/RECORD +0 -42
- {remotivelabs_cli-0.0.24.dist-info → remotivelabs_cli-0.0.26.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.0.24.dist-info → remotivelabs_cli-0.0.26.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.0.24.dist-info → remotivelabs_cli-0.0.26.dist-info}/entry_points.txt +0 -0
cli/cloud/configs.py
CHANGED
@@ -7,22 +7,22 @@ import requests
|
|
7
7
|
import typer
|
8
8
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
9
9
|
|
10
|
-
from . import
|
10
|
+
from .rest_helper import RestHelper as Rest
|
11
11
|
|
12
12
|
app = typer.Typer()
|
13
13
|
|
14
14
|
|
15
15
|
@app.command("list")
|
16
|
-
def list_signal_databases(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
17
|
-
|
16
|
+
def list_signal_databases(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
17
|
+
Rest.handle_get(f"/api/project/{project}/files/config")
|
18
18
|
|
19
19
|
|
20
20
|
@app.command("delete")
|
21
21
|
def delete(
|
22
22
|
signal_db_file: str = typer.Argument("", help="Signal database file"),
|
23
23
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
24
|
-
):
|
25
|
-
|
24
|
+
) -> None:
|
25
|
+
Rest.handle_delete(f"/api/project/{project}/files/config/{signal_db_file}")
|
26
26
|
|
27
27
|
|
28
28
|
@app.command()
|
@@ -38,35 +38,42 @@ def upload(
|
|
38
38
|
help="Path to signal database file to upload",
|
39
39
|
),
|
40
40
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
41
|
-
):
|
41
|
+
) -> None:
|
42
42
|
"""
|
43
43
|
Uploads signal database to project
|
44
44
|
"""
|
45
|
-
|
45
|
+
res = Rest.handle_put(url=f"/api/project/{project}/files/config/{os.path.basename(path)}/uploadfile", return_response=True)
|
46
|
+
if res is not None:
|
47
|
+
Rest.upload_file_with_signed_url(
|
48
|
+
path=path, url=res.text, upload_headers={"Content-Type": "application/octet-stream"}, progress_label=f"Uploading {path}..."
|
49
|
+
)
|
46
50
|
|
47
51
|
|
48
52
|
@app.command()
|
49
53
|
def download(
|
50
54
|
signal_db_file: str = typer.Argument("", help="Signal database file"),
|
51
55
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
52
|
-
):
|
56
|
+
) -> None:
|
53
57
|
with Progress(
|
54
58
|
SpinnerColumn(),
|
55
59
|
TextColumn("[progress.description]{task.description}"),
|
56
60
|
transient=True,
|
57
61
|
) as progress:
|
58
|
-
|
62
|
+
Rest.ensure_auth_token()
|
59
63
|
|
60
64
|
progress.add_task(description=f"Downloading {signal_db_file}", total=None)
|
61
65
|
|
62
66
|
# First request the download url from cloud. This is a public signed url that is valid
|
63
67
|
# for a short period of time
|
64
68
|
get_signed_url_resp = requests.get(
|
65
|
-
f"{
|
69
|
+
f"{Rest.get_base_url()}/api/project/{project}/files/config/{signal_db_file}/download",
|
70
|
+
headers=Rest.get_headers(),
|
71
|
+
allow_redirects=True,
|
72
|
+
timeout=60,
|
66
73
|
)
|
67
74
|
if get_signed_url_resp.status_code == 200:
|
68
75
|
# Next download the actual file
|
69
|
-
download_resp = requests.get(url=get_signed_url_resp.text, stream=True)
|
76
|
+
download_resp = requests.get(url=get_signed_url_resp.text, stream=True, timeout=60)
|
70
77
|
if download_resp.status_code == 200:
|
71
78
|
with open(signal_db_file, "wb") as out_file:
|
72
79
|
shutil.copyfileobj(download_resp.raw, out_file)
|
@@ -86,4 +93,5 @@ def download(
|
|
86
93
|
# print(r.status_code)
|
87
94
|
# print(r.text)
|
88
95
|
|
96
|
+
# pylint: disable=C0301
|
89
97
|
# curl -X PUT -H 'Content-Type: application/octet-stream' --upload-file docker-compose.yml 'https://storage.googleapis.com/beamylabs-fileuploads-dev/projects/beamyhack/recording/myrecording?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=recordings-upload-account%40beamycloud-dev.iam.gserviceaccount.com%2F20220729%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20220729T134012Z&X-Goog-Expires=3000&X-Goog-SignedHeaders=content-type%3Bhost&X-Goog-Signature=d1fa7639349d6453aebfce8814d6e5685af03952d07aa4e3cb0d44dba7cf5e572f684c8120dba17cbc7ea6a0ef5450542a3c745c65e04272b34265d0ddcf1b67e6f2b5bfa446264a62d77bd7faabf45ad6bd2aec5225f57004b0a31cfe0480cba063a3807d86346b1da99ecbae3f3e6da8f44f06396dfc1fdc6f89e475abdf969142cef6f369f03aff41000c8abb28aa82185246746fd6c16b6b381baa2d586382a3d3067b6376ddba2b55b2b6f9d942913a1cbfbc61491ba6a615d7d5a0d9a476c357431143e9cea1411dfad9f01b1e1176dc8c056cbf08cccfd401a55d63c19d038f3ab42b712abc48d759047ac07862c4fae937c341e19b568bb60a4e4086'
|
cli/cloud/filestorage.py
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
import os.path
|
2
|
+
import sys
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
import typer
|
6
|
+
|
7
|
+
from ..errors import ErrorPrinter
|
8
|
+
from . import resumable_upload as upload
|
9
|
+
from .rest_helper import RestHelper as Rest
|
10
|
+
|
11
|
+
app = typer.Typer(
|
12
|
+
rich_markup_mode="rich",
|
13
|
+
help="""
|
14
|
+
Manage files ([yellow]Beta feature not available for all customers[/yellow])
|
15
|
+
|
16
|
+
Copy file from local to remote storage and vice versa, list and delete files.
|
17
|
+
|
18
|
+
""",
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
@app.command(name="ls")
|
23
|
+
def list_files(
|
24
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
25
|
+
prefix: str = typer.Argument(default="rcs://", help="Remote storage path"),
|
26
|
+
) -> None:
|
27
|
+
"""
|
28
|
+
Listing remote files
|
29
|
+
|
30
|
+
This will list files and directories in project top level directory
|
31
|
+
remotive cloud storage ls rcs://
|
32
|
+
|
33
|
+
This will list all files and directories matching the path
|
34
|
+
remotive cloud storage ls rcs://fileOrDirectoryPrefix
|
35
|
+
|
36
|
+
This will list all files and directories in the specified directory
|
37
|
+
remotive cloud storage ls rcs://fileOrDirectory/
|
38
|
+
"""
|
39
|
+
|
40
|
+
if prefix.startswith("rcs://"):
|
41
|
+
prefix = __check_rcs_path(prefix)
|
42
|
+
else:
|
43
|
+
ErrorPrinter.print_hint("Path must start with rcs://")
|
44
|
+
sys.exit(1)
|
45
|
+
|
46
|
+
Rest.handle_get(
|
47
|
+
f"/api/project/{project}/files/storage{prefix}",
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
@app.command(name="rm")
|
52
|
+
def delete_file(
|
53
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
54
|
+
path: str = typer.Argument(default=..., help="Remote storage path to file to delete"),
|
55
|
+
) -> None:
|
56
|
+
"""
|
57
|
+
[red]Deletes[/red] a file from remote storage, this cannot be undone :fire:
|
58
|
+
|
59
|
+
[white]remotive cloud storage rm rcs://directory/filename[/white]
|
60
|
+
"""
|
61
|
+
if path.startswith("rcs://"):
|
62
|
+
prefix = __check_rcs_path(path)
|
63
|
+
else:
|
64
|
+
ErrorPrinter.print_hint("Path must start with rcs://")
|
65
|
+
sys.exit(1)
|
66
|
+
|
67
|
+
Rest.handle_delete(
|
68
|
+
f"/api/project/{project}/files/storage{prefix}",
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
@app.command(name="cp")
|
73
|
+
def copy_file( # noqa: C901 # type: ignore[too-many-branches] # pylint: disable=no-member
|
74
|
+
source: str = typer.Argument(default=..., help="Remote or local path to source file"),
|
75
|
+
dest: str = typer.Argument(default=..., help="Remote or local path to destination file"),
|
76
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
77
|
+
) -> None:
|
78
|
+
"""
|
79
|
+
Copies a file to or from remote storage
|
80
|
+
|
81
|
+
remotive cloud storage cp rcs://dir/filename .
|
82
|
+
remotive cloud storage cp rcs://dir/filename filename
|
83
|
+
|
84
|
+
remotive cloud storage cp filename rcs://dir/
|
85
|
+
remotive cloud storage cp filename rcs://dir/filename
|
86
|
+
"""
|
87
|
+
|
88
|
+
if not source.startswith("rcs://") and not dest.startswith("rcs://"):
|
89
|
+
ErrorPrinter.print_hint("Source or destination path must be an rcs:// path")
|
90
|
+
sys.exit(2)
|
91
|
+
|
92
|
+
if source.startswith("rcs://") and dest.startswith("rcs://"):
|
93
|
+
ErrorPrinter.print_hint("Currently one of source and destination path must be a local path")
|
94
|
+
sys.exit(2)
|
95
|
+
|
96
|
+
if source.startswith("rcs://"):
|
97
|
+
__copy_to_local(source=source, dest=dest, project=project)
|
98
|
+
else:
|
99
|
+
__copy_to_remote(source=source, dest=dest, project=project)
|
100
|
+
|
101
|
+
|
102
|
+
def __copy_to_remote(source: str, dest: str, project: str) -> None:
|
103
|
+
path = Path(source)
|
104
|
+
if not path.exists():
|
105
|
+
ErrorPrinter.print_hint("Source file does not exist")
|
106
|
+
sys.exit(1)
|
107
|
+
filename = source.rsplit("/", 1)[-1]
|
108
|
+
rcs_path = __check_rcs_path(dest)
|
109
|
+
if rcs_path.endswith("/"):
|
110
|
+
rcs_path = rcs_path + filename
|
111
|
+
res = Rest.handle_post(f"/api/project/{project}/files/storage{rcs_path}", return_response=True)
|
112
|
+
if res is None:
|
113
|
+
return
|
114
|
+
json = res.json()
|
115
|
+
url = json["url"]
|
116
|
+
content_type = json["contentType"]
|
117
|
+
try:
|
118
|
+
upload.upload_signed_url(url, source, content_type)
|
119
|
+
except IsADirectoryError:
|
120
|
+
ErrorPrinter.print_hint(f"Supplied source file '{source}' is a directory but must be a file")
|
121
|
+
|
122
|
+
|
123
|
+
def __copy_to_local(source: str, dest: str, project: str) -> None:
|
124
|
+
rcs_path = __check_rcs_path(source)
|
125
|
+
filename = source.rsplit("/", 1)[-1]
|
126
|
+
path = Path(dest)
|
127
|
+
if path.is_dir():
|
128
|
+
if not path.exists():
|
129
|
+
ErrorPrinter.print_generic_error("Destination directory does not exist")
|
130
|
+
sys.exit(1)
|
131
|
+
else:
|
132
|
+
dest = os.path.join(path.absolute(), filename)
|
133
|
+
|
134
|
+
else:
|
135
|
+
if not path.parent.is_dir() or not path.parent.exists():
|
136
|
+
ErrorPrinter.print_generic_error("Destination directory does not exist")
|
137
|
+
sys.exit(1)
|
138
|
+
dest = str(Path(dest).absolute())
|
139
|
+
|
140
|
+
res = Rest.handle_get(
|
141
|
+
f"/api/project/{project}/files/storage{rcs_path}?download=true",
|
142
|
+
return_response=True,
|
143
|
+
)
|
144
|
+
if res is None:
|
145
|
+
return
|
146
|
+
|
147
|
+
Rest.download_file(save_file_name=dest, url=res.text)
|
148
|
+
|
149
|
+
|
150
|
+
def __check_rcs_path(path: str) -> str:
|
151
|
+
rcs_path = path.replace("rcs://", "/")
|
152
|
+
if rcs_path.startswith("/."):
|
153
|
+
ErrorPrinter.print_hint("Invalid path")
|
154
|
+
sys.exit(1)
|
155
|
+
return rcs_path
|
cli/cloud/projects.py
CHANGED
@@ -2,14 +2,17 @@ import json
|
|
2
2
|
|
3
3
|
import typer
|
4
4
|
|
5
|
-
from . import
|
5
|
+
from .rest_helper import RestHelper as Rest
|
6
6
|
|
7
7
|
app = typer.Typer()
|
8
8
|
|
9
9
|
|
10
10
|
@app.command(name="list", help="List your projects")
|
11
|
-
def list_projects(organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION")):
|
12
|
-
r =
|
11
|
+
def list_projects(organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION")) -> None:
|
12
|
+
r = Rest.handle_get(url=f"/api/bu/{organisation}/me", return_response=True)
|
13
|
+
if r is None:
|
14
|
+
return
|
15
|
+
|
13
16
|
if r.status_code == 200:
|
14
17
|
# extract the project uid parts
|
15
18
|
projects = r.json()["projects"]
|
@@ -25,16 +28,16 @@ def create_project(
|
|
25
28
|
organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION"),
|
26
29
|
project_uid: str = typer.Option(..., help="Project UID"),
|
27
30
|
project_display_name: str = typer.Option(default="", help="Project display name"),
|
28
|
-
):
|
31
|
+
) -> None:
|
29
32
|
create_project_req = {
|
30
33
|
"uid": project_uid,
|
31
34
|
"displayName": project_display_name if project_display_name != "" else project_uid,
|
32
35
|
"description": "",
|
33
36
|
}
|
34
37
|
|
35
|
-
|
38
|
+
Rest.handle_post(url=f"/api/bu/{organisation}/project", body=json.dumps(create_project_req))
|
36
39
|
|
37
40
|
|
38
41
|
@app.command(name="delete")
|
39
|
-
def delete(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
40
|
-
|
42
|
+
def delete(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
43
|
+
Rest.handle_delete(url=f"/api/project/{project}")
|