remotivelabs-cli 0.0.41__py3-none-any.whl → 0.1.0a1__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 +25 -6
- cli/cloud/auth/login.py +279 -24
- cli/cloud/auth_tokens.py +295 -12
- cli/cloud/brokers.py +3 -4
- cli/cloud/cloud_cli.py +2 -2
- cli/cloud/configs.py +1 -2
- cli/cloud/organisations.py +92 -20
- cli/cloud/projects.py +1 -2
- 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/remotive.py +30 -6
- cli/settings/__init__.py +1 -2
- cli/settings/config_file.py +85 -0
- cli/settings/core.py +195 -46
- 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 +109 -38
- {remotivelabs_cli-0.0.41.dist-info → remotivelabs_cli-0.1.0a1.dist-info}/METADATA +6 -4
- remotivelabs_cli-0.1.0a1.dist-info/RECORD +59 -0
- {remotivelabs_cli-0.0.41.dist-info → remotivelabs_cli-0.1.0a1.dist-info}/WHEEL +1 -1
- cli/settings/cmd.py +0 -72
- remotivelabs_cli-0.0.41.dist-info/RECORD +0 -54
- {remotivelabs_cli-0.0.41.dist-info → remotivelabs_cli-0.1.0a1.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.0.41.dist-info → remotivelabs_cli-0.1.0a1.dist-info}/entry_points.txt +0 -0
cli/cloud/auth_tokens.py
CHANGED
@@ -1,33 +1,316 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import List, Optional, Union
|
4
|
+
|
1
5
|
import typer
|
6
|
+
from rich.console import Console
|
7
|
+
from rich.table import Table
|
2
8
|
|
3
|
-
from cli.
|
9
|
+
from cli.api.cloud import tokens
|
10
|
+
from cli.cloud.organisations import do_select_default_org
|
11
|
+
from cli.errors import ErrorPrinter
|
12
|
+
from cli.settings import TokenFile, TokenNotFoundError, settings
|
4
13
|
from cli.typer import typer_utils
|
14
|
+
from cli.utils.rest_helper import RestHelper as Rest
|
5
15
|
|
6
|
-
|
16
|
+
console = Console(stderr=False)
|
17
|
+
err_console = Console(stderr=True)
|
7
18
|
|
8
19
|
app = typer_utils.create_typer()
|
9
20
|
|
10
21
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
22
|
+
def _prompt_choice( # noqa: C901, PLR0912
|
23
|
+
choices: List[TokenFile],
|
24
|
+
skip_prompt: bool = False,
|
25
|
+
info_message: Optional[str] = None,
|
26
|
+
) -> Optional[Union[TokenFile, None]]:
|
27
|
+
accounts = settings.get_cli_config().accounts
|
28
|
+
|
29
|
+
try:
|
30
|
+
active_account = settings.get_cli_config().get_active()
|
31
|
+
except TokenNotFoundError:
|
32
|
+
active_account = None
|
33
|
+
|
34
|
+
table = Table("#", "Active", "Type", "Token", "Account", "Created", "Expires")
|
35
|
+
|
36
|
+
included_tokens: list[TokenFile] = []
|
37
|
+
excluded_tokens: list[TokenFile] = []
|
38
|
+
|
39
|
+
for token in choices:
|
40
|
+
account = accounts.get(token.account.email)
|
41
|
+
if account and account.credentials_name and account.credentials_name in (token.name or ""):
|
42
|
+
included_tokens.append(token)
|
43
|
+
else:
|
44
|
+
excluded_tokens.append(token)
|
45
|
+
if len(excluded_tokens) > 0:
|
46
|
+
err_console.print("The following credentials were not included in account config and cannot be activated")
|
47
|
+
for token in excluded_tokens:
|
48
|
+
err_console.print(f" * {token.name} - {token.account.email}")
|
49
|
+
|
50
|
+
if len(included_tokens) == 0:
|
51
|
+
return None
|
52
|
+
|
53
|
+
if info_message is not None:
|
54
|
+
console.print(info_message)
|
55
|
+
|
56
|
+
included_tokens.sort(key=lambda token: token.created, reverse=True)
|
57
|
+
|
58
|
+
for idx, choice in enumerate(included_tokens, start=1):
|
59
|
+
table.add_row(
|
60
|
+
f"[yellow]{idx}",
|
61
|
+
":white_check_mark:" if active_account is not None and active_account.credentials_name == choice.name else "",
|
62
|
+
"user" if choice.type == "authorized_user" else "sa",
|
63
|
+
choice.name,
|
64
|
+
f"[bold]{choice.account.email if choice.account else 'unknown'}[/bold]",
|
65
|
+
str(choice.created),
|
66
|
+
str(choice.expires),
|
67
|
+
)
|
68
|
+
|
69
|
+
# console.print("It seems like you have access tokens from previous login, you can select one of these instead of logging in")
|
70
|
+
console.print(table)
|
71
|
+
|
72
|
+
if skip_prompt:
|
73
|
+
return None
|
74
|
+
|
75
|
+
typer.echo("")
|
76
|
+
selection = typer.prompt(f"Enter the number(# 1-{len(included_tokens)}) of the token to select (or q to skip)")
|
77
|
+
|
78
|
+
if selection == "q":
|
79
|
+
return None
|
80
|
+
try:
|
81
|
+
index = int(selection) - 1
|
82
|
+
if 0 <= index < len(included_tokens):
|
83
|
+
return included_tokens[index]
|
84
|
+
raise ValueError
|
85
|
+
except ValueError:
|
86
|
+
typer.echo("Invalid choice, please try again")
|
87
|
+
return _prompt_choice(included_tokens, skip_prompt, info_message)
|
88
|
+
|
89
|
+
|
90
|
+
# @app.command(name="create")
|
91
|
+
def create(
|
92
|
+
activate: bool = typer.Option(False, help="Activate the token for use after download"),
|
93
|
+
) -> None:
|
94
|
+
"""
|
95
|
+
Create a new personal access token in [bold]cloud[/bold] and download locally
|
96
|
+
"""
|
97
|
+
response = tokens.create()
|
98
|
+
pat = settings.add_personal_token(response.text())
|
16
99
|
print(f"Personal access token added: {pat.name}")
|
17
100
|
|
18
101
|
if not activate:
|
19
102
|
print(f"Use 'remotive cloud auth tokens activate {pat.name}' to use this access token from cli")
|
20
103
|
else:
|
21
|
-
settings.activate_token(pat
|
104
|
+
settings.activate_token(pat)
|
22
105
|
print("Token file activated and ready for use")
|
23
106
|
print("\033[93m This file contains secrets and must be kept safe")
|
24
107
|
|
25
108
|
|
26
|
-
@app.command(name="list", help="List personal
|
109
|
+
# @app.command(name="list", help="List personal credentials in [bold]cloud[/bold]")
|
27
110
|
def list_tokens() -> None:
|
28
111
|
Rest.handle_get("/api/me/keys")
|
29
112
|
|
30
113
|
|
31
|
-
@app.command(name="revoke"
|
32
|
-
def revoke(
|
33
|
-
|
114
|
+
# @app.command(name="revoke")
|
115
|
+
def revoke(
|
116
|
+
name: str = typer.Argument(help="Access token name"),
|
117
|
+
delete: bool = typer.Option(True, help="Also delete token"),
|
118
|
+
) -> None:
|
119
|
+
"""
|
120
|
+
Revoke personal credentials in cloud and removes the file from filesystem
|
121
|
+
|
122
|
+
If cloud token is not found but token is found on file system it will delete it and
|
123
|
+
vice versa.
|
124
|
+
"""
|
125
|
+
_revoke_and_delete_personal_token(name, delete)
|
126
|
+
|
127
|
+
|
128
|
+
# @app.command(name="activate")
|
129
|
+
def activate(
|
130
|
+
token_name: str = typer.Argument(..., help="Token path, filename or name to activate"),
|
131
|
+
) -> None:
|
132
|
+
"""
|
133
|
+
Activate a credential file to be used for authentication using filename, path or name.
|
134
|
+
|
135
|
+
This will be used as the current access token in all requests.
|
136
|
+
"""
|
137
|
+
try:
|
138
|
+
token_file = settings.get_token_file(token_name)
|
139
|
+
settings.activate_token(token_file)
|
140
|
+
except TokenNotFoundError:
|
141
|
+
err_console.print(f":boom: [bold red] Error: [/bold red] Token with filename or name {token_name} could not be found")
|
142
|
+
|
143
|
+
|
144
|
+
@app.command("activate")
|
145
|
+
def select_personal_token(
|
146
|
+
token_name: str = typer.Argument(None, help="Name, filename or path to a credentials file"),
|
147
|
+
) -> None:
|
148
|
+
"""
|
149
|
+
Prompts user to select and activate one of the available credential files
|
150
|
+
"""
|
151
|
+
if token_name is not None:
|
152
|
+
token_selected = True
|
153
|
+
try:
|
154
|
+
token_file = settings.get_token_file(token_name)
|
155
|
+
settings.activate_token(token_file)
|
156
|
+
except TokenNotFoundError:
|
157
|
+
token_selected = False
|
158
|
+
err_console.print(f":boom: [bold red] Error: [/bold red] Token with filename or name {token_name} could not be found")
|
159
|
+
else:
|
160
|
+
token_files = settings.list_personal_tokens()
|
161
|
+
token_files.extend(settings.list_service_account_tokens())
|
162
|
+
if len(token_files) > 0:
|
163
|
+
token_selected = list_and_select_personal_token(include_service_accounts=True)
|
164
|
+
|
165
|
+
if token_selected:
|
166
|
+
is_logged_in = Rest.has_access("/api/whoami")
|
167
|
+
if not is_logged_in:
|
168
|
+
ErrorPrinter.print_generic_error("Could not access RemotiveCloud with selected token")
|
169
|
+
else:
|
170
|
+
console.print("[green]Success![/green] Access to RemotiveCloud granted")
|
171
|
+
else:
|
172
|
+
ErrorPrinter.print_hint("No credentials available, login to activate credentials")
|
173
|
+
|
174
|
+
if settings.get_cli_config().get_active_default_organisation() is None:
|
175
|
+
set_default_organisation = typer.confirm(
|
176
|
+
"You have not set a default organisation\nWould you like to choose one now?",
|
177
|
+
abort=False,
|
178
|
+
default=True,
|
179
|
+
)
|
180
|
+
if set_default_organisation:
|
181
|
+
do_select_default_org(get=False)
|
182
|
+
|
183
|
+
|
184
|
+
def list_and_select_personal_token(
|
185
|
+
skip_prompt: bool = False,
|
186
|
+
include_service_accounts: bool = False,
|
187
|
+
info_message: Optional[str] = None,
|
188
|
+
) -> bool:
|
189
|
+
personal_tokens = settings.list_personal_tokens()
|
190
|
+
|
191
|
+
if include_service_accounts:
|
192
|
+
sa_tokens = settings.list_service_account_tokens()
|
193
|
+
personal_tokens.extend(sa_tokens)
|
194
|
+
|
195
|
+
# merged = _merge_local_tokens_with_cloud(personal_tokens)
|
196
|
+
|
197
|
+
selected_token = _prompt_choice(personal_tokens, skip_prompt=skip_prompt, info_message=info_message)
|
198
|
+
if selected_token is not None:
|
199
|
+
settings.activate_token(selected_token)
|
200
|
+
return True
|
201
|
+
return False
|
202
|
+
|
203
|
+
|
204
|
+
# @app.command("select-revoke")
|
205
|
+
def select_revoke_personal_token() -> None:
|
206
|
+
"""
|
207
|
+
Prompts a user to select one of the credential files to revoke and delete
|
208
|
+
"""
|
209
|
+
personal_tokens = settings.list_personal_tokens()
|
210
|
+
sa_tokens = settings.list_service_account_tokens()
|
211
|
+
personal_tokens.extend(sa_tokens)
|
212
|
+
|
213
|
+
is_logged_in = Rest.has_access("/api/whoami")
|
214
|
+
if not is_logged_in:
|
215
|
+
ErrorPrinter.print_hint("You must be logged in")
|
216
|
+
raise typer.Exit(0)
|
217
|
+
|
218
|
+
# merged = _merge_local_tokens_with_cloud(personal_tokens)
|
219
|
+
|
220
|
+
selected_token = _prompt_choice(personal_tokens)
|
221
|
+
|
222
|
+
if selected_token is not None:
|
223
|
+
_revoke_and_delete_personal_token(selected_token.name, True)
|
224
|
+
# Rest.handle_patch(f"/api/me/keys/{selected_token.name}/revoke", quiet=True, access_token=selected_token.token)
|
225
|
+
# Rest.handle_delete(f"/api/me/keys/{selected_token.name}", quiet=True, access_token=selected_token.token)
|
226
|
+
# settings.remove_token_file(selected_token.name)
|
227
|
+
# active_token = settings.get_active_token_file()
|
228
|
+
# if active_token.name == selected_token.name:
|
229
|
+
# settings.clear_active_token()
|
230
|
+
# select_revoke_personal_token()
|
231
|
+
|
232
|
+
|
233
|
+
# @app.command("test-all")
|
234
|
+
def test_all_personal_tokens() -> None:
|
235
|
+
"""
|
236
|
+
Tests each credential file to see if it is valid
|
237
|
+
"""
|
238
|
+
personal_tokens = settings.list_personal_tokens()
|
239
|
+
personal_tokens.extend(settings.list_service_account_tokens())
|
240
|
+
if len(personal_tokens) == 0:
|
241
|
+
console.print("No personal tokens found on disk")
|
242
|
+
return
|
243
|
+
|
244
|
+
for token in personal_tokens:
|
245
|
+
r = Rest.handle_get(
|
246
|
+
"/api/whoami",
|
247
|
+
allow_status_codes=[401],
|
248
|
+
access_token=token.token,
|
249
|
+
use_progress_indicator=True,
|
250
|
+
return_response=True,
|
251
|
+
)
|
252
|
+
if r.status_code == 200:
|
253
|
+
if token.account is not None:
|
254
|
+
console.print(f"{token.account.email} ({token.name}) :white_check_mark:")
|
255
|
+
else:
|
256
|
+
console.print(f"{token.name} :white_check_mark:")
|
257
|
+
elif token.account is not None:
|
258
|
+
console.print(f"{token.account.email} ({token.name}) :x: Failed")
|
259
|
+
else:
|
260
|
+
console.print(f"{token.name} :x: Failed")
|
261
|
+
|
262
|
+
|
263
|
+
# @app.command(name="list-service-account-tokens-files")
|
264
|
+
def list_sats_files() -> None:
|
265
|
+
"""
|
266
|
+
List service account access token files in remotivelabs config directory
|
267
|
+
"""
|
268
|
+
service_account_files = settings.list_service_account_token_files()
|
269
|
+
for file in service_account_files:
|
270
|
+
print(file)
|
271
|
+
|
272
|
+
|
273
|
+
@app.command(name="list")
|
274
|
+
def list_pats_files() -> None:
|
275
|
+
"""
|
276
|
+
Lists available credential files on filesystem
|
277
|
+
"""
|
278
|
+
personal_files = settings.list_personal_token_files()
|
279
|
+
service_account_files = settings.list_service_account_token_files()
|
280
|
+
personal_files.extend(service_account_files)
|
281
|
+
for file in personal_files:
|
282
|
+
print(file)
|
283
|
+
|
284
|
+
|
285
|
+
def _revoke_and_delete_personal_token(name: str, delete: bool) -> None:
|
286
|
+
token_file = None
|
287
|
+
|
288
|
+
# First we try to find the file and make sure its not the currently active
|
289
|
+
try:
|
290
|
+
token_file = settings.get_token_file(name)
|
291
|
+
active_token = settings.get_active_token_file()
|
292
|
+
if token_file.name == active_token.name:
|
293
|
+
ErrorPrinter.print_hint("You cannot revoke the current active token")
|
294
|
+
return
|
295
|
+
except TokenNotFoundError:
|
296
|
+
pass
|
297
|
+
|
298
|
+
# The lets try to revoke from cloud
|
299
|
+
res_revoke = tokens.revoke(name)
|
300
|
+
if delete:
|
301
|
+
res_delete = tokens.delete(name)
|
302
|
+
if res_delete.is_success:
|
303
|
+
ErrorPrinter.print_generic_message("Token successfully revoked and deleted")
|
304
|
+
else:
|
305
|
+
ErrorPrinter.print_hint(f"Failed to revoke and delete token in cloud: {res_delete.status_code}")
|
306
|
+
elif res_revoke.is_success:
|
307
|
+
ErrorPrinter.print_generic_message("Token successfully revoked")
|
308
|
+
else:
|
309
|
+
ErrorPrinter.print_hint("Failed to revoke and delete token in cloud")
|
310
|
+
|
311
|
+
# Finally try to remove the file if exists
|
312
|
+
if token_file is not None:
|
313
|
+
settings.remove_token_file(token_file.name)
|
314
|
+
console.print("Successfully deleted token on filesystem")
|
315
|
+
else:
|
316
|
+
ErrorPrinter.print_hint("Token not found on filesystem")
|
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,8 +1,8 @@
|
|
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
|
|
@@ -15,7 +15,7 @@ def licenses(
|
|
15
15
|
RestHelper.handle_get(f"/api/bu/{organisation}/licenses", {"filter": filter_option})
|
16
16
|
|
17
17
|
|
18
|
-
app.add_typer(organisations.app, name="organisations", help="Manage
|
18
|
+
app.add_typer(organisations.app, name="organisations", 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,29 +1,101 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import json
|
2
|
-
import
|
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
|
3
10
|
|
4
|
-
from cli.
|
11
|
+
from cli.settings import settings
|
5
12
|
from cli.typer import typer_utils
|
13
|
+
from cli.utils.rest_helper import RestHelper
|
6
14
|
|
15
|
+
console = Console(stderr=False)
|
7
16
|
app = typer_utils.create_typer()
|
8
17
|
|
9
18
|
|
10
|
-
@
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
@dataclass
|
20
|
+
class Organisation:
|
21
|
+
display_name: str
|
22
|
+
uid: str
|
23
|
+
|
24
|
+
|
25
|
+
def _prompt_choice(choices: List[Organisation]) -> Optional[Organisation]:
|
26
|
+
table = Table("#", "Name", "Uid", "Default")
|
27
|
+
|
28
|
+
token = settings.get_active_token_file()
|
29
|
+
config = settings.get_cli_config()
|
30
|
+
|
31
|
+
current_default_org = config.accounts[token.account.email].default_organization if token is not None else None
|
32
|
+
|
33
|
+
for idx, choice in enumerate(choices, start=1):
|
34
|
+
table.add_row(
|
35
|
+
f"[yellow]{idx}",
|
36
|
+
f"[bold]{choice.display_name}[/bold]",
|
37
|
+
choice.uid,
|
38
|
+
":thumbsup:" if current_default_org is not None and current_default_org == choice.uid else "",
|
24
39
|
)
|
25
|
-
|
40
|
+
console.print(table)
|
41
|
+
|
42
|
+
typer.echo("")
|
43
|
+
selection = typer.prompt(f"Enter the number(# 1-{len(choices)}) of the organisation to select (or q to quit)")
|
44
|
+
|
45
|
+
if selection == "q":
|
46
|
+
return None
|
47
|
+
try:
|
48
|
+
index = int(selection) - 1
|
49
|
+
if 0 <= index < len(choices):
|
50
|
+
return choices[index]
|
51
|
+
raise ValueError
|
52
|
+
except ValueError:
|
53
|
+
typer.echo("Invalid choice, please try again")
|
54
|
+
return _prompt_choice(choices)
|
55
|
+
|
56
|
+
|
57
|
+
@app.command("default")
|
58
|
+
def select_default_org(
|
59
|
+
organisation_uid: str = typer.Argument(None, help="Organisation uid or empty to select one"),
|
60
|
+
get: bool = typer.Option(False, help="Print current default organisation"),
|
61
|
+
) -> None:
|
62
|
+
do_select_default_org(organisation_uid, get)
|
63
|
+
|
64
|
+
|
65
|
+
def do_select_default_org(organisation_uid: Optional[str] = None, get: bool = False) -> None:
|
66
|
+
r"""
|
67
|
+
Set default organisation for the currently activated user, empty to choose from available organizations or organization uid as argument
|
68
|
+
|
69
|
+
remotive cloud organizations default my_org \[set specific org uid]
|
70
|
+
remotive cloud organizations default \[select one from prompt]
|
71
|
+
remotive cloud organizations default --get \[print current default]
|
72
|
+
|
73
|
+
Note that service-accounts does Not have permission to list organizations and will get a 403 Forbidden response so you must
|
74
|
+
select the organization uid as argument
|
75
|
+
"""
|
76
|
+
if get:
|
77
|
+
default_organisation = settings.get_cli_config().get_active_default_organisation()
|
78
|
+
if default_organisation is not None:
|
79
|
+
console.print(default_organisation)
|
80
|
+
else:
|
81
|
+
console.print("No default organisation set")
|
82
|
+
elif organisation_uid is not None:
|
83
|
+
settings.set_default_organisation(organisation_uid)
|
26
84
|
else:
|
27
|
-
|
28
|
-
|
29
|
-
|
85
|
+
r = RestHelper.handle_get("/api/bu", return_response=True)
|
86
|
+
orgs = r.json()
|
87
|
+
orgs = [Organisation(display_name=o["organisation"]["displayName"], uid=o["organisation"]["uid"]) for o in orgs]
|
88
|
+
|
89
|
+
selected = _prompt_choice(orgs)
|
90
|
+
|
91
|
+
if selected is not None:
|
92
|
+
settings.get_cli_config()
|
93
|
+
typer.echo(f"Default organisation: {selected.display_name} (uid: {selected.uid})")
|
94
|
+
settings.set_default_organisation(selected.uid)
|
95
|
+
|
96
|
+
|
97
|
+
@app.command(name="list", help="List your available organizations")
|
98
|
+
def list_orgs() -> None:
|
99
|
+
r = RestHelper.handle_get("/api/bu", return_response=True)
|
100
|
+
orgs = [{"uid": org["organisation"]["uid"], "displayName": org["organisation"]["displayName"]} for org in r.json()]
|
101
|
+
print(json.dumps(orgs))
|
cli/cloud/projects.py
CHANGED
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
|
"""
|