remotivelabs-cli 0.0.42__py3-none-any.whl → 0.1.0a2__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/.DS_Store +0 -0
- cli/api/cloud/tokens.py +62 -0
- cli/broker/brokers.py +0 -1
- cli/broker/export.py +4 -4
- cli/broker/lib/broker.py +9 -13
- cli/broker/license_flows.py +1 -1
- cli/broker/scripting.py +2 -1
- cli/broker/signals.py +9 -10
- cli/cloud/auth/cmd.py +37 -13
- cli/cloud/auth/login.py +279 -24
- cli/cloud/auth_tokens.py +319 -12
- cli/cloud/brokers.py +3 -4
- cli/cloud/cloud_cli.py +5 -5
- cli/cloud/configs.py +1 -2
- cli/cloud/organisations.py +101 -2
- cli/cloud/projects.py +5 -6
- cli/cloud/recordings.py +9 -16
- cli/cloud/recordings_playback.py +6 -8
- cli/cloud/sample_recordings.py +2 -3
- cli/cloud/service_account_tokens.py +21 -5
- cli/cloud/service_accounts.py +32 -4
- cli/cloud/storage/cmd.py +1 -1
- cli/cloud/storage/copy.py +3 -4
- cli/connect/connect.py +1 -1
- cli/connect/protopie/protopie.py +12 -14
- cli/errors.py +6 -1
- cli/remotive.py +30 -6
- cli/settings/__init__.py +1 -2
- cli/settings/config_file.py +92 -0
- cli/settings/core.py +188 -45
- cli/settings/migrate_all_token_files.py +74 -0
- cli/settings/migrate_token_file.py +52 -0
- cli/settings/token_file.py +69 -4
- cli/tools/can/can.py +2 -2
- cli/typer/typer_utils.py +18 -1
- cli/utils/__init__.py +0 -0
- cli/{cloud → utils}/rest_helper.py +114 -39
- {remotivelabs_cli-0.0.42.dist-info → remotivelabs_cli-0.1.0a2.dist-info}/METADATA +6 -4
- remotivelabs_cli-0.1.0a2.dist-info/RECORD +59 -0
- {remotivelabs_cli-0.0.42.dist-info → remotivelabs_cli-0.1.0a2.dist-info}/WHEEL +1 -1
- cli/settings/cmd.py +0 -72
- remotivelabs_cli-0.0.42.dist-info/RECORD +0 -54
- {remotivelabs_cli-0.0.42.dist-info → remotivelabs_cli-0.1.0a2.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.0.42.dist-info → remotivelabs_cli-0.1.0a2.dist-info}/entry_points.txt +0 -0
cli/cloud/brokers.py
CHANGED
@@ -10,14 +10,13 @@ import typer
|
|
10
10
|
import websocket
|
11
11
|
|
12
12
|
from cli.typer import typer_utils
|
13
|
-
|
14
|
-
from .rest_helper import RestHelper as Rest
|
13
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
15
14
|
|
16
15
|
app = typer_utils.create_typer()
|
17
16
|
|
18
17
|
|
19
18
|
@app.command("list", help="Lists brokers in project")
|
20
|
-
def list(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
19
|
+
def list(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
21
20
|
Rest.handle_get(f"/api/project/{project}/brokers")
|
22
21
|
|
23
22
|
|
@@ -94,7 +93,7 @@ def logs(
|
|
94
93
|
def on_message(_wsapp: Any, message: str) -> None:
|
95
94
|
print(message)
|
96
95
|
|
97
|
-
def on_error(_wsapp: Any, err: str) -> None:
|
96
|
+
def on_error(_wsapp: Any, err: str) -> None:
|
98
97
|
print("EXAMPLE error encountered: ", err)
|
99
98
|
|
100
99
|
Rest.ensure_auth_token()
|
cli/cloud/cloud_cli.py
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
import typer
|
2
2
|
|
3
3
|
from cli.cloud import auth, brokers, configs, organisations, projects, recordings, sample_recordings, service_accounts, storage
|
4
|
-
from cli.cloud.rest_helper import RestHelper
|
5
4
|
from cli.typer import typer_utils
|
5
|
+
from cli.utils.rest_helper import RestHelper
|
6
6
|
|
7
7
|
app = typer_utils.create_typer()
|
8
8
|
|
9
9
|
|
10
|
-
@app.command(help="List licenses for an
|
10
|
+
@app.command(help="List licenses for an organization")
|
11
11
|
def licenses(
|
12
|
-
|
12
|
+
organization: str = typer.Option(..., help="Organization ID", envvar="REMOTIVE_CLOUD_ORGANIZATION"),
|
13
13
|
filter_option: str = typer.Option("all", help="all, valid, expired"),
|
14
14
|
) -> None:
|
15
|
-
RestHelper.handle_get(f"/api/bu/{
|
15
|
+
RestHelper.handle_get(f"/api/bu/{organization}/licenses", {"filter": filter_option})
|
16
16
|
|
17
17
|
|
18
|
-
app.add_typer(organisations.app, name="
|
18
|
+
app.add_typer(organisations.app, name="organizations", help="Manage organizations")
|
19
19
|
app.add_typer(projects.app, name="projects", help="Manage projects")
|
20
20
|
app.add_typer(auth.app, name="auth")
|
21
21
|
app.add_typer(brokers.app, name="brokers", help="Manage cloud broker lifecycle")
|
cli/cloud/configs.py
CHANGED
@@ -8,8 +8,7 @@ import typer
|
|
8
8
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
9
9
|
|
10
10
|
from cli.typer import typer_utils
|
11
|
-
|
12
|
-
from .rest_helper import RestHelper as Rest
|
11
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
13
12
|
|
14
13
|
app = typer_utils.create_typer()
|
15
14
|
|
cli/cloud/organisations.py
CHANGED
@@ -1,12 +1,111 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import json
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import List, Optional
|
6
|
+
|
7
|
+
import typer
|
8
|
+
from rich.console import Console
|
9
|
+
from rich.table import Table
|
2
10
|
|
3
|
-
from cli.
|
11
|
+
from cli.errors import ErrorPrinter
|
12
|
+
from cli.settings import settings
|
4
13
|
from cli.typer import typer_utils
|
14
|
+
from cli.utils.rest_helper import RestHelper
|
5
15
|
|
16
|
+
console = Console(stderr=False)
|
6
17
|
app = typer_utils.create_typer()
|
7
18
|
|
8
19
|
|
9
|
-
@
|
20
|
+
@dataclass
|
21
|
+
class Organisation:
|
22
|
+
display_name: str
|
23
|
+
uid: str
|
24
|
+
|
25
|
+
|
26
|
+
def _prompt_choice(choices: List[Organisation]) -> Optional[Organisation]:
|
27
|
+
table = Table("#", "Name", "Uid", "Default")
|
28
|
+
|
29
|
+
token = settings.get_active_token_file()
|
30
|
+
config = settings.get_cli_config()
|
31
|
+
|
32
|
+
current_default_org = config.accounts[token.account.email].default_organization if token is not None else None
|
33
|
+
|
34
|
+
for idx, choice in enumerate(choices, start=1):
|
35
|
+
table.add_row(
|
36
|
+
f"[yellow]{idx}",
|
37
|
+
f"[bold]{choice.display_name}[/bold]",
|
38
|
+
choice.uid,
|
39
|
+
":thumbsup:" if current_default_org is not None and current_default_org == choice.uid else "",
|
40
|
+
)
|
41
|
+
console.print(table)
|
42
|
+
|
43
|
+
typer.echo("")
|
44
|
+
selection = typer.prompt(f"Enter the number(# 1-{len(choices)}) of the organization to select (or q to quit)")
|
45
|
+
|
46
|
+
if selection == "q":
|
47
|
+
return None
|
48
|
+
try:
|
49
|
+
index = int(selection) - 1
|
50
|
+
if 0 <= index < len(choices):
|
51
|
+
return choices[index]
|
52
|
+
raise ValueError
|
53
|
+
except ValueError:
|
54
|
+
typer.echo("Invalid choice, please try again")
|
55
|
+
return _prompt_choice(choices)
|
56
|
+
|
57
|
+
|
58
|
+
@app.command("default")
|
59
|
+
def select_default_org(
|
60
|
+
organization_uid: str = typer.Argument(None, help="Organization uid or empty to select one"),
|
61
|
+
get: bool = typer.Option(False, help="Print current default organization"),
|
62
|
+
) -> None:
|
63
|
+
do_select_default_org(organization_uid, get)
|
64
|
+
|
65
|
+
|
66
|
+
def do_select_default_org(organisation_uid: Optional[str] = None, get: bool = False) -> None:
|
67
|
+
r"""
|
68
|
+
Set default organization for the currently activated user, empty to choose from available organizations or organization uid as argument
|
69
|
+
|
70
|
+
remotive cloud organizations default my_org \[set specific org uid]
|
71
|
+
remotive cloud organizations default \[select one from prompt]
|
72
|
+
remotive cloud organizations default --get \[print current default]
|
73
|
+
|
74
|
+
Note that service-accounts does Not have permission to list organizations and will get a 403 Forbidden response so you must
|
75
|
+
select the organization uid as argument
|
76
|
+
"""
|
77
|
+
if get:
|
78
|
+
default_organisation = settings.get_cli_config().get_active_default_organisation()
|
79
|
+
if default_organisation is not None:
|
80
|
+
console.print(default_organisation)
|
81
|
+
else:
|
82
|
+
console.print("No default organization set")
|
83
|
+
elif organisation_uid is not None:
|
84
|
+
settings.set_default_organisation(organisation_uid)
|
85
|
+
else:
|
86
|
+
account = settings.get_cli_config().get_active()
|
87
|
+
if account is not None:
|
88
|
+
token = settings.get_token_file(account.credentials_name)
|
89
|
+
if token.type != "authorized_user":
|
90
|
+
ErrorPrinter.print_hint(
|
91
|
+
"You must supply the organization name as argument when using a service-account since the "
|
92
|
+
"service-account is not allowed to list"
|
93
|
+
)
|
94
|
+
return
|
95
|
+
|
96
|
+
r = RestHelper.handle_get("/api/bu", return_response=True)
|
97
|
+
orgs = r.json()
|
98
|
+
orgs = [Organisation(display_name=o["organisation"]["displayName"], uid=o["organisation"]["uid"]) for o in orgs]
|
99
|
+
|
100
|
+
selected = _prompt_choice(orgs)
|
101
|
+
|
102
|
+
if selected is not None:
|
103
|
+
settings.get_cli_config()
|
104
|
+
typer.echo(f"Default organisation: {selected.display_name} (uid: {selected.uid})")
|
105
|
+
settings.set_default_organisation(selected.uid)
|
106
|
+
|
107
|
+
|
108
|
+
@app.command(name="list", help="List your available organizations")
|
10
109
|
def list_orgs() -> None:
|
11
110
|
r = RestHelper.handle_get("/api/bu", return_response=True)
|
12
111
|
orgs = [{"uid": org["organisation"]["uid"], "displayName": org["organisation"]["displayName"]} for org in r.json()]
|
cli/cloud/projects.py
CHANGED
@@ -3,15 +3,14 @@ import json
|
|
3
3
|
import typer
|
4
4
|
|
5
5
|
from cli.typer import typer_utils
|
6
|
-
|
7
|
-
from .rest_helper import RestHelper as Rest
|
6
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
8
7
|
|
9
8
|
app = typer_utils.create_typer()
|
10
9
|
|
11
10
|
|
12
11
|
@app.command(name="list", help="List your projects")
|
13
|
-
def list_projects(
|
14
|
-
r = Rest.handle_get(url=f"/api/bu/{
|
12
|
+
def list_projects(organization: str = typer.Option(..., help="Organization ID", envvar="REMOTIVE_CLOUD_ORGANIZATION")) -> None:
|
13
|
+
r = Rest.handle_get(url=f"/api/bu/{organization}/me", return_response=True)
|
15
14
|
if r is None:
|
16
15
|
return
|
17
16
|
|
@@ -28,7 +27,7 @@ def list_projects(organisation: str = typer.Option(..., help="Organisation ID",
|
|
28
27
|
@app.command(name="create")
|
29
28
|
def create_project(
|
30
29
|
project_uid: str = typer.Argument(help="Project UID"),
|
31
|
-
|
30
|
+
organization: str = typer.Option(..., help="Organization ID", envvar="REMOTIVE_CLOUD_ORGANIZATION"),
|
32
31
|
project_display_name: str = typer.Option(default="", help="Project display name"),
|
33
32
|
) -> None:
|
34
33
|
create_project_req = {
|
@@ -37,7 +36,7 @@ def create_project(
|
|
37
36
|
"description": "",
|
38
37
|
}
|
39
38
|
|
40
|
-
Rest.handle_post(url=f"/api/bu/{
|
39
|
+
Rest.handle_post(url=f"/api/bu/{organization}/project", body=json.dumps(create_project_req))
|
41
40
|
|
42
41
|
|
43
42
|
@app.command(name="delete")
|
cli/cloud/recordings.py
CHANGED
@@ -19,11 +19,11 @@ from typing_extensions import Annotated
|
|
19
19
|
from cli.cloud.uri import URI
|
20
20
|
from cli.errors import ErrorPrinter
|
21
21
|
from cli.typer import typer_utils
|
22
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
23
|
+
from cli.utils.rest_helper import err_console
|
22
24
|
|
23
25
|
from ..broker.lib.broker import Broker
|
24
26
|
from .recordings_playback import app as playback_app
|
25
|
-
from .rest_helper import RestHelper as Rest
|
26
|
-
from .rest_helper import err_console
|
27
27
|
|
28
28
|
app = typer_utils.create_typer()
|
29
29
|
app.add_typer(playback_app, name="playback")
|
@@ -129,7 +129,6 @@ def mount( # noqa: C901
|
|
129
129
|
transformation_name: str = typer.Option("default", help="Specify a custom signal transformation to use"),
|
130
130
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
131
131
|
) -> None:
|
132
|
-
# pylint: disable=R0912
|
133
132
|
Rest.ensure_auth_token()
|
134
133
|
|
135
134
|
Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
|
@@ -230,7 +229,7 @@ def delete_recording_file(
|
|
230
229
|
|
231
230
|
|
232
231
|
@app.command()
|
233
|
-
def upload( # noqa: C901
|
232
|
+
def upload( # noqa: C901, PLR0912, PLR0915
|
234
233
|
path: Path = typer.Argument(
|
235
234
|
...,
|
236
235
|
exists=True,
|
@@ -244,7 +243,6 @@ def upload( # noqa: C901
|
|
244
243
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
245
244
|
recording_session: Optional[str] = typer.Option(default=None, help="Optional existing recording to upload file to"),
|
246
245
|
) -> None:
|
247
|
-
# pylint: disable=R0912,R0914,R0915
|
248
246
|
"""
|
249
247
|
Uploads a recording to RemotiveCloud.
|
250
248
|
Except for recordings from RemotiveBroker you can also upload Can ASC (.asc), Can BLF(.blf) and Can LOG (.log, .txt)
|
@@ -281,8 +279,7 @@ def upload( # noqa: C901
|
|
281
279
|
return
|
282
280
|
|
283
281
|
# Exact same as in cloud console
|
284
|
-
def get_processing_message(step: str) -> str:
|
285
|
-
# pylint: disable=R0911
|
282
|
+
def get_processing_message(step: str) -> str: # noqa: PLR0911
|
286
283
|
if step == "REQUESTED":
|
287
284
|
return "Preparing file..."
|
288
285
|
if step == "VALIDATING":
|
@@ -333,9 +330,9 @@ def upload( # noqa: C901
|
|
333
330
|
err_console.print(f":boom: [bold red]Got status code[/bold red]: {upload_response.status_code} {upload_response.text}")
|
334
331
|
|
335
332
|
|
336
|
-
# TODO - Change to use Path for directory
|
333
|
+
# TODO - Change to use Path for directory
|
337
334
|
@app.command()
|
338
|
-
def upload_broker_configuration(
|
335
|
+
def upload_broker_configuration(
|
339
336
|
directory: Path = typer.Argument(
|
340
337
|
...,
|
341
338
|
exists=True,
|
@@ -353,8 +350,6 @@ def upload_broker_configuration( # noqa: C901
|
|
353
350
|
"""
|
354
351
|
Uploads a broker configuration directory
|
355
352
|
"""
|
356
|
-
# pylint: disable=R0914,R0915
|
357
|
-
|
358
353
|
# Must end with /
|
359
354
|
|
360
355
|
#
|
@@ -431,7 +426,7 @@ def upload_broker_configuration( # noqa: C901
|
|
431
426
|
path = file["local_path"]
|
432
427
|
url = upload_urls[key]
|
433
428
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
434
|
-
r = requests.put(url, open(path, "rb"), headers=headers, timeout=60)
|
429
|
+
r = requests.put(url, open(path, "rb"), headers=headers, timeout=60)
|
435
430
|
if r.status_code != 200:
|
436
431
|
print("Failed to upload broker configuration")
|
437
432
|
print(r.status_code)
|
@@ -529,11 +524,9 @@ def stop(
|
|
529
524
|
_do_change_playback_mode("stop", recording_session, broker, project)
|
530
525
|
|
531
526
|
|
532
|
-
#
|
533
|
-
def _do_change_playback_mode(
|
527
|
+
def _do_change_playback_mode( # noqa: PLR0912
|
534
528
|
mode: str, recording_session: str, broker_name: Optional[str], project: str, seconds: Optional[int] = None
|
535
|
-
) -> None:
|
536
|
-
# pylint: disable=R0912,R0914,R0915
|
529
|
+
) -> None:
|
537
530
|
response = Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
|
538
531
|
if response is None:
|
539
532
|
return
|
cli/cloud/recordings_playback.py
CHANGED
@@ -15,10 +15,10 @@ from rich import print as rich_print
|
|
15
15
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
16
16
|
|
17
17
|
from cli.errors import ErrorPrinter
|
18
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
18
19
|
|
19
20
|
from ..broker.lib.broker import Broker, SubscribableSignal
|
20
21
|
from ..typer import typer_utils
|
21
|
-
from .rest_helper import RestHelper as Rest
|
22
22
|
|
23
23
|
app = typer_utils.create_typer(
|
24
24
|
help="""
|
@@ -101,9 +101,8 @@ def read_scripted_code_file(file_path: Path) -> bytes:
|
|
101
101
|
return file.read()
|
102
102
|
|
103
103
|
|
104
|
-
# pylint: disable=R0913
|
105
104
|
@app.command()
|
106
|
-
def subscribe(
|
105
|
+
def subscribe( # noqa: PLR0913
|
107
106
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
108
107
|
broker: str = typer.Option(None, help="Broker to use"),
|
109
108
|
signal: List[str] = typer.Option(None, help="Signal names to subscribe to, mandatory when not using script"),
|
@@ -156,12 +155,12 @@ def subscribe(
|
|
156
155
|
except grpc.RpcError as rpc_error:
|
157
156
|
ErrorPrinter.print_grpc_error(rpc_error)
|
158
157
|
|
159
|
-
except Exception as e:
|
158
|
+
except Exception as e:
|
160
159
|
ErrorPrinter.print_generic_error(str(e))
|
161
160
|
sys.exit(1)
|
162
161
|
|
163
162
|
|
164
|
-
def _do_change_playback_mode( # noqa: C901
|
163
|
+
def _do_change_playback_mode( # noqa: C901, PLR0913, PLR0912
|
165
164
|
mode: str,
|
166
165
|
recording_session: str,
|
167
166
|
brokerstr: str,
|
@@ -169,8 +168,7 @@ def _do_change_playback_mode( # noqa: C901
|
|
169
168
|
seconds: int | None = None,
|
170
169
|
progress_on_play: bool = False,
|
171
170
|
repeat: bool = False,
|
172
|
-
) -> None:
|
173
|
-
# pylint: disable=R0913,R0912
|
171
|
+
) -> None:
|
174
172
|
response = Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
|
175
173
|
if response is None:
|
176
174
|
return
|
@@ -247,7 +245,7 @@ def _verify_recording_on_broker(broker: Broker, recording_session: str, mode: st
|
|
247
245
|
ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
|
248
246
|
sys.exit(1)
|
249
247
|
except grpc.RpcError as rpc_error:
|
250
|
-
if rpc_error.code() == grpc.StatusCode.NOT_FOUND:
|
248
|
+
if rpc_error.code() == grpc.StatusCode.NOT_FOUND:
|
251
249
|
ErrorPrinter.print_generic_error(f"You must use mount to prepare a recording before you can use {mode}")
|
252
250
|
ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
|
253
251
|
else:
|
cli/cloud/sample_recordings.py
CHANGED
@@ -3,8 +3,7 @@ import json
|
|
3
3
|
import typer
|
4
4
|
|
5
5
|
from cli.typer import typer_utils
|
6
|
-
|
7
|
-
from .rest_helper import RestHelper as Rest
|
6
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
8
7
|
|
9
8
|
app = typer_utils.create_typer()
|
10
9
|
|
@@ -18,7 +17,7 @@ def do_import(
|
|
18
17
|
|
19
18
|
|
20
19
|
@app.command("list")
|
21
|
-
def list() -> None:
|
20
|
+
def list() -> None:
|
22
21
|
"""
|
23
22
|
List available sample recordings
|
24
23
|
"""
|
@@ -4,13 +4,12 @@ import typer
|
|
4
4
|
|
5
5
|
from cli.settings import settings
|
6
6
|
from cli.typer import typer_utils
|
7
|
-
|
8
|
-
from .rest_helper import RestHelper as Rest
|
7
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
9
8
|
|
10
9
|
app = typer_utils.create_typer()
|
11
10
|
|
12
11
|
|
13
|
-
# TODO: add add interactive flag to set target directory
|
12
|
+
# TODO: add add interactive flag to set target directory
|
14
13
|
@app.command(name="create", help="Create and download a new service account access token")
|
15
14
|
def create(
|
16
15
|
expire_in_days: int = typer.Option(default=365, help="Number of this token is valid"),
|
@@ -23,7 +22,7 @@ def create(
|
|
23
22
|
body=json.dumps({"daysUntilExpiry": expire_in_days}),
|
24
23
|
)
|
25
24
|
|
26
|
-
sat = settings.add_service_account_token(
|
25
|
+
sat = settings.add_service_account_token(response.text)
|
27
26
|
print(f"Service account access token added: {sat.name}")
|
28
27
|
print("\033[93m This file contains secrets and must be kept safe")
|
29
28
|
|
@@ -36,10 +35,27 @@ def list_tokens(
|
|
36
35
|
Rest.handle_get(f"/api/project/{project}/admin/accounts/{service_account}/keys")
|
37
36
|
|
38
37
|
|
38
|
+
@app.command(name="describe", help="Describe service account access token")
|
39
|
+
def describe(
|
40
|
+
name: str = typer.Argument(..., help="Access token name"),
|
41
|
+
service_account: str = typer.Option(..., help="Service account name"),
|
42
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
43
|
+
) -> None:
|
44
|
+
Rest.handle_get(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}")
|
45
|
+
|
46
|
+
|
39
47
|
@app.command(name="revoke", help="Revoke service account access token")
|
40
48
|
def revoke(
|
41
49
|
name: str = typer.Argument(..., help="Access token name"),
|
50
|
+
delete: bool = typer.Option(True, help="Also deletes the token after revocation"),
|
42
51
|
service_account: str = typer.Option(..., help="Service account name"),
|
43
52
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
44
53
|
) -> None:
|
45
|
-
Rest.
|
54
|
+
res = Rest.handle_get(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}", return_response=True)
|
55
|
+
if not res.json()["revoked"]:
|
56
|
+
Rest.handle_patch(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}/revoke", quiet=True)
|
57
|
+
if delete:
|
58
|
+
Rest.handle_delete(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}", quiet=True)
|
59
|
+
token_with_name = [token for token in settings.list_service_account_tokens() if token.name == name]
|
60
|
+
if len(token_with_name) > 0:
|
61
|
+
settings.remove_token_file(name)
|
cli/cloud/service_accounts.py
CHANGED
@@ -6,9 +6,9 @@ from typing import List
|
|
6
6
|
import typer
|
7
7
|
|
8
8
|
from cli.typer import typer_utils
|
9
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
9
10
|
|
10
11
|
from . import service_account_tokens
|
11
|
-
from .rest_helper import RestHelper as Rest
|
12
12
|
|
13
13
|
app = typer_utils.create_typer()
|
14
14
|
|
@@ -18,7 +18,27 @@ def list_service_accounts(project: str = typer.Option(..., help="Project ID", en
|
|
18
18
|
Rest.handle_get(f"/api/project/{project}/admin/accounts")
|
19
19
|
|
20
20
|
|
21
|
-
|
21
|
+
ROLES_DESCRIPTION = """
|
22
|
+
[bold]Supported roles [/bold]
|
23
|
+
project/admin - Full project support, view, edit, delete and manage users
|
24
|
+
project/user - View, edit, upload, delete but no admin
|
25
|
+
project/viewer - View only
|
26
|
+
project/storageCreator - Can upload to storage but not view, overwrite or delete
|
27
|
+
org/topologyRunner - Can start RemotiveTopology
|
28
|
+
"""
|
29
|
+
|
30
|
+
|
31
|
+
@app.command(
|
32
|
+
name="create",
|
33
|
+
help=f"""
|
34
|
+
Create a new service account with one or more roles.
|
35
|
+
|
36
|
+
[bold]Must only use lowercase letters, digits, or dashes, and must not contain -- or end with a dash.[/bold]
|
37
|
+
|
38
|
+
remotive cloud service-accounts create --role project/user --role project/storageCreator
|
39
|
+
{ROLES_DESCRIPTION}
|
40
|
+
""",
|
41
|
+
)
|
22
42
|
def create_service_account(
|
23
43
|
name: str,
|
24
44
|
role: List[str] = typer.Option(..., help="Roles to apply"),
|
@@ -28,9 +48,17 @@ def create_service_account(
|
|
28
48
|
Rest.handle_post(url=f"/api/project/{project}/admin/accounts", body=json.dumps(data))
|
29
49
|
|
30
50
|
|
31
|
-
@app.command(
|
51
|
+
@app.command(
|
52
|
+
name="update",
|
53
|
+
help=f"""
|
54
|
+
Update an existing service account with one or more roles.
|
55
|
+
|
56
|
+
remotive cloud service-accounts update --role project/user --role project/storageCreator
|
57
|
+
{ROLES_DESCRIPTION}
|
58
|
+
""",
|
59
|
+
)
|
32
60
|
def update_service_account(
|
33
|
-
service_account: str = typer.
|
61
|
+
service_account: str = typer.Argument(..., help="Service account name"),
|
34
62
|
role: List[str] = typer.Option(..., help="Roles to apply"),
|
35
63
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
36
64
|
) -> None:
|
cli/cloud/storage/cmd.py
CHANGED
@@ -3,13 +3,13 @@ import sys
|
|
3
3
|
import typer
|
4
4
|
from typing_extensions import Annotated
|
5
5
|
|
6
|
-
from cli.cloud.rest_helper import RestHelper as Rest
|
7
6
|
from cli.cloud.storage.copy import copy
|
8
7
|
from cli.cloud.storage.uri_or_path import UriOrPath
|
9
8
|
from cli.cloud.storage.uri_or_path import uri as uri_parser
|
10
9
|
from cli.cloud.uri import URI, InvalidURIError, JoinURIError
|
11
10
|
from cli.errors import ErrorPrinter
|
12
11
|
from cli.typer import typer_utils
|
12
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
13
13
|
|
14
14
|
HELP = """
|
15
15
|
Manage files ([yellow]Beta feature not available for all customers[/yellow])
|
cli/cloud/storage/copy.py
CHANGED
@@ -3,9 +3,9 @@ from __future__ import annotations
|
|
3
3
|
import json
|
4
4
|
from pathlib import Path
|
5
5
|
|
6
|
-
from cli.cloud.rest_helper import RestHelper as Rest
|
7
6
|
from cli.cloud.resumable_upload import upload_signed_url
|
8
7
|
from cli.cloud.uri import URI
|
8
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
9
9
|
|
10
10
|
_RCS_STORAGE_PATH = "/api/project/{project}/files/storage{path}"
|
11
11
|
|
@@ -73,9 +73,8 @@ def _download(source: URI, dest: Path, project: str, overwrite: bool = False) ->
|
|
73
73
|
# create a target file name if destination is a dir
|
74
74
|
dest = dest / source.filename
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
raise FileNotFoundError(f"Destination directory {dest.parent} does not exist")
|
76
|
+
elif not dest.parent.is_dir() or not dest.parent.exists():
|
77
|
+
raise FileNotFoundError(f"Destination directory {dest.parent} does not exist")
|
79
78
|
|
80
79
|
if dest.exists() and not overwrite:
|
81
80
|
raise FileExistsError(f"Destination file {dest} already exists")
|
cli/connect/connect.py
CHANGED
cli/connect/protopie/protopie.py
CHANGED
@@ -1,26 +1,25 @@
|
|
1
1
|
# type: ignore
|
2
|
-
# pylint: skip-file
|
3
2
|
from __future__ import annotations
|
4
3
|
|
5
4
|
import json
|
6
5
|
import os
|
6
|
+
import sys
|
7
7
|
import time
|
8
8
|
import traceback
|
9
9
|
from pathlib import Path
|
10
10
|
from typing import Any, Dict, List, Tuple, Union
|
11
11
|
|
12
12
|
import grpc
|
13
|
-
import socketio
|
13
|
+
import socketio
|
14
14
|
from remotivelabs.broker.sync import BrokerException, Client, SignalIdentifier, SignalsInFrame
|
15
15
|
from rich import print as pretty_print
|
16
16
|
from rich.console import Console
|
17
|
-
from socketio.exceptions import ConnectionError as SocketIoConnectionError
|
17
|
+
from socketio.exceptions import ConnectionError as SocketIoConnectionError
|
18
18
|
|
19
19
|
from cli.broker.lib.broker import SubscribableSignal
|
20
20
|
from cli.errors import ErrorPrinter
|
21
21
|
from cli.settings import settings
|
22
22
|
|
23
|
-
global PP_CONNECT_APP_NAME
|
24
23
|
PP_CONNECT_APP_NAME = "RemotiveBridge"
|
25
24
|
|
26
25
|
io = socketio.Client()
|
@@ -34,13 +33,13 @@ x_api_key: str
|
|
34
33
|
broker: Any
|
35
34
|
|
36
35
|
|
37
|
-
@io.on("connect")
|
36
|
+
@io.on("connect")
|
38
37
|
def on_connect() -> None:
|
39
38
|
print("Connected to ProtoPie Connect")
|
40
39
|
io.emit("ppBridgeApp", {"name": PP_CONNECT_APP_NAME})
|
41
40
|
io.emit("PLUGIN_STARTED", {"name": PP_CONNECT_APP_NAME})
|
42
41
|
|
43
|
-
global is_connected
|
42
|
+
global is_connected # noqa: PLW0603
|
44
43
|
is_connected = True
|
45
44
|
|
46
45
|
|
@@ -90,7 +89,7 @@ def _connect_to_broker(
|
|
90
89
|
signals, namespaces, sub = get_signals_and_namespaces(config, signals_to_subscribe_to)
|
91
90
|
|
92
91
|
def on_signals(frame: SignalsInFrame) -> None:
|
93
|
-
global _has_received_signal
|
92
|
+
global _has_received_signal # noqa: PLW0603
|
94
93
|
if not _has_received_signal:
|
95
94
|
pretty_print("Bridge-app is properly receiving signals, you are good to go :thumbsup:")
|
96
95
|
_has_received_signal = True
|
@@ -151,10 +150,9 @@ def grpc_connect(
|
|
151
150
|
except Exception as e:
|
152
151
|
print(traceback.format_exc())
|
153
152
|
err_console.print(f":boom: {e}")
|
154
|
-
# exit(1)
|
155
153
|
|
156
154
|
|
157
|
-
def do_connect(
|
155
|
+
def do_connect( # noqa: PLR0913
|
158
156
|
address: str,
|
159
157
|
broker_url: str,
|
160
158
|
api_key: Union[str, None],
|
@@ -163,9 +161,9 @@ def do_connect(
|
|
163
161
|
expression: Union[str, None],
|
164
162
|
on_change_only: bool = False,
|
165
163
|
) -> None:
|
166
|
-
global broker
|
167
|
-
global x_api_key
|
168
|
-
global config_path
|
164
|
+
global broker # noqa: PLW0603
|
165
|
+
global x_api_key # noqa: PLW0603
|
166
|
+
global config_path # noqa: PLW0603
|
169
167
|
broker = broker_url
|
170
168
|
|
171
169
|
if broker_url.startswith("https"):
|
@@ -188,8 +186,8 @@ def do_connect(
|
|
188
186
|
except SocketIoConnectionError as e:
|
189
187
|
err_console.print(":boom: [bold red]Failed to connect to ProtoPie Connect[/bold red]")
|
190
188
|
err_console.print(e)
|
191
|
-
exit(1)
|
189
|
+
sys.exit(1)
|
192
190
|
except Exception as e:
|
193
191
|
err_console.print(":boom: [bold red]Unexpected error[/bold red]")
|
194
192
|
err_console.print(e)
|
195
|
-
exit(1)
|
193
|
+
sys.exit(1)
|
cli/errors.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os
|
2
4
|
import sys
|
5
|
+
from typing import Optional
|
3
6
|
|
4
7
|
import grpc
|
5
8
|
from rich.console import Console
|
@@ -31,8 +34,10 @@ class ErrorPrinter:
|
|
31
34
|
err_console.print(f":point_right: [bold]{message}[/bold]")
|
32
35
|
|
33
36
|
@staticmethod
|
34
|
-
def print_generic_error(message: str) -> None:
|
37
|
+
def print_generic_error(message: str, exit_code: Optional[int] = None) -> None:
|
35
38
|
err_console.print(f":boom: [bold red]Failed[/bold red]: {message}")
|
39
|
+
if exit_code is not None:
|
40
|
+
sys.exit(exit_code)
|
36
41
|
|
37
42
|
@staticmethod
|
38
43
|
def print_generic_message(message: str) -> None:
|