remotivelabs-cli 0.2.3__py3-none-any.whl → 0.3.1__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.
Potentially problematic release.
This version of remotivelabs-cli might be problematic. Click here for more details.
- cli/broker/__init__.py +36 -0
- cli/broker/discovery.py +43 -0
- cli/broker/export.py +6 -36
- cli/broker/files.py +12 -12
- cli/broker/lib/broker.py +132 -106
- cli/broker/lib/client.py +224 -0
- cli/broker/lib/helper.py +277 -0
- cli/broker/lib/signalcreator.py +196 -0
- cli/broker/license_flows.py +11 -13
- cli/broker/playback.py +10 -10
- cli/broker/record.py +4 -4
- cli/broker/scripting.py +6 -9
- cli/broker/signals.py +17 -19
- cli/cloud/__init__.py +17 -0
- cli/cloud/auth/cmd.py +74 -33
- cli/cloud/auth/login.py +42 -54
- cli/cloud/auth_tokens.py +40 -247
- cli/cloud/brokers.py +5 -9
- cli/cloud/configs.py +4 -17
- cli/cloud/licenses/__init__.py +0 -0
- cli/cloud/licenses/cmd.py +14 -0
- cli/cloud/organisations.py +12 -17
- cli/cloud/projects.py +3 -3
- cli/cloud/recordings.py +35 -61
- cli/cloud/recordings_playback.py +22 -22
- cli/cloud/resumable_upload.py +6 -6
- cli/cloud/service_account_tokens.py +4 -3
- cli/cloud/storage/cmd.py +2 -3
- cli/cloud/storage/copy.py +2 -1
- cli/connect/connect.py +4 -4
- cli/connect/protopie/protopie.py +22 -30
- cli/remotive.py +16 -26
- cli/settings/__init__.py +1 -2
- cli/settings/config_file.py +2 -0
- cli/settings/core.py +146 -146
- cli/settings/migration/migrate_config_file.py +13 -6
- cli/settings/migration/migration_tools.py +6 -4
- cli/settings/state_file.py +12 -4
- cli/tools/can/can.py +4 -7
- cli/topology/__init__.py +3 -0
- cli/topology/cmd.py +60 -83
- cli/topology/start_trial.py +105 -0
- cli/typer/typer_utils.py +3 -6
- cli/utils/console.py +61 -0
- cli/utils/rest_helper.py +33 -31
- cli/utils/versions.py +7 -19
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.1.dist-info}/METADATA +3 -2
- remotivelabs_cli-0.3.1.dist-info/RECORD +74 -0
- cli/broker/brokers.py +0 -93
- cli/cloud/cloud_cli.py +0 -29
- cli/errors.py +0 -44
- remotivelabs_cli-0.2.3.dist-info/RECORD +0 -67
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.1.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.1.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.1.dist-info}/entry_points.txt +0 -0
cli/cloud/auth/login.py
CHANGED
|
@@ -13,19 +13,16 @@ from typing import Any, Optional, Tuple
|
|
|
13
13
|
from urllib.parse import parse_qs, urlparse
|
|
14
14
|
|
|
15
15
|
import typer
|
|
16
|
-
from rich.console import Console
|
|
17
16
|
from typing_extensions import override
|
|
18
17
|
|
|
19
18
|
from cli.cloud.auth_tokens import do_activate, prompt_to_set_org
|
|
20
|
-
from cli.
|
|
21
|
-
from cli.settings import TokenNotFoundError, settings
|
|
19
|
+
from cli.settings import settings
|
|
22
20
|
from cli.settings.token_file import TokenFile
|
|
21
|
+
from cli.utils.console import print_generic_error, print_newline, print_success, print_unformatted, print_url
|
|
23
22
|
from cli.utils.rest_helper import RestHelper as Rest
|
|
24
23
|
|
|
25
24
|
httpd: HTTPServer
|
|
26
25
|
|
|
27
|
-
console = Console()
|
|
28
|
-
|
|
29
26
|
|
|
30
27
|
def generate_pkce_pair() -> Tuple[str, str]:
|
|
31
28
|
"""
|
|
@@ -79,8 +76,7 @@ class S(BaseHTTPRequestHandler):
|
|
|
79
76
|
allow_status_codes=[401, 400],
|
|
80
77
|
)
|
|
81
78
|
if res.status_code != 200:
|
|
82
|
-
|
|
83
|
-
ErrorPrinter.print_generic_error(
|
|
79
|
+
print_generic_error(
|
|
84
80
|
"Failed to fetch token. Please try again, if the problem persists please reach out to support@remotivelabs.com"
|
|
85
81
|
)
|
|
86
82
|
self.wfile.write(
|
|
@@ -95,7 +91,6 @@ class S(BaseHTTPRequestHandler):
|
|
|
95
91
|
"""Successfully setup CLI, you may close this window now. Return to your terminal to continue""".encode("utf-8")
|
|
96
92
|
)
|
|
97
93
|
access_token = res.json()["access_token"]
|
|
98
|
-
|
|
99
94
|
global short_lived_token # noqa: PLW0603
|
|
100
95
|
short_lived_token = access_token
|
|
101
96
|
|
|
@@ -109,7 +104,7 @@ class S(BaseHTTPRequestHandler):
|
|
|
109
104
|
Run `remotive cloud auth login` to try again.
|
|
110
105
|
""".encode("utf-8")
|
|
111
106
|
)
|
|
112
|
-
|
|
107
|
+
print_generic_error("You did not grant access to RemotiveCloud, login aborted")
|
|
113
108
|
elif error_value == "user_not_exists":
|
|
114
109
|
self.wfile.write(
|
|
115
110
|
"""
|
|
@@ -120,22 +115,19 @@ class S(BaseHTTPRequestHandler):
|
|
|
120
115
|
Once you are signed up, Run `remotive cloud auth login` again.
|
|
121
116
|
""".encode("utf-8")
|
|
122
117
|
)
|
|
123
|
-
|
|
118
|
+
print_generic_error(
|
|
124
119
|
"To use RemotiveCLI you must first sign up at https://cloud.remotivelabs.com and approve our agreements"
|
|
125
120
|
)
|
|
126
121
|
else:
|
|
127
122
|
self.wfile.write(f"Unknown error {error_value}, please contact support@remotivelabs.com".encode("utf-8"))
|
|
128
|
-
|
|
123
|
+
print_generic_error(f"Unexpected error {error_value}, please contact support@remotivelabs.com")
|
|
129
124
|
sys.exit(1)
|
|
130
125
|
|
|
131
126
|
|
|
132
127
|
def prepare_local_webserver(server_class: type = HTTPServer, handler_class: type = S, port: Optional[int] = None) -> None:
|
|
133
128
|
if port is None:
|
|
134
129
|
env_val = os.getenv("REMOTIVE_LOGIN_CALLBACK_PORT" or "")
|
|
135
|
-
if env_val and env_val.isdigit()
|
|
136
|
-
port = int(env_val)
|
|
137
|
-
else:
|
|
138
|
-
port = 0
|
|
130
|
+
port = int(env_val) if env_val and env_val.isdigit() else 0
|
|
139
131
|
|
|
140
132
|
server_address = ("", port)
|
|
141
133
|
global httpd # noqa: PLW0603
|
|
@@ -153,7 +145,6 @@ def create_personal_token() -> None:
|
|
|
153
145
|
email = token["account"]["email"]
|
|
154
146
|
existing_file = settings.get_token_file_by_email(email=email)
|
|
155
147
|
if existing_file is not None:
|
|
156
|
-
# ErrorPrinter.print_hint(f"Revoking and deleting existing credentials [remove_me]{existing_file.name}")
|
|
157
148
|
res = Rest.handle_patch(
|
|
158
149
|
f"/api/me/keys/{existing_file.name}/revoke",
|
|
159
150
|
quiet=True,
|
|
@@ -170,18 +161,17 @@ def create_personal_token() -> None:
|
|
|
170
161
|
|
|
171
162
|
settings.add_personal_token(response.text, activate=True)
|
|
172
163
|
|
|
173
|
-
|
|
164
|
+
print_success("Logged in")
|
|
174
165
|
|
|
175
166
|
|
|
176
167
|
def _do_prompt_to_use_existing_credentials() -> Optional[TokenFile]:
|
|
177
|
-
|
|
178
|
-
if len(
|
|
168
|
+
token_files = settings.list_personal_token_files()
|
|
169
|
+
if len(token_files) > 0:
|
|
179
170
|
should_select_token = typer.confirm(
|
|
180
171
|
"You have credentials available already, would you like to choose one of these instead?", default=True
|
|
181
172
|
)
|
|
182
173
|
if should_select_token:
|
|
183
174
|
token = do_activate(token_name=None)
|
|
184
|
-
# token = list_and_select_personal_token(skip_prompt=False, include_service_accounts=True)
|
|
185
175
|
if token is not None:
|
|
186
176
|
return token
|
|
187
177
|
# TODO - fix so this is not needed
|
|
@@ -189,7 +179,7 @@ def _do_prompt_to_use_existing_credentials() -> Optional[TokenFile]:
|
|
|
189
179
|
return None
|
|
190
180
|
|
|
191
181
|
|
|
192
|
-
def login(headless: bool = False) -> bool: # noqa: C901,
|
|
182
|
+
def login(headless: bool = False) -> bool: # noqa: C901, PLR0915
|
|
193
183
|
"""
|
|
194
184
|
Initiate login
|
|
195
185
|
"""
|
|
@@ -197,31 +187,30 @@ def login(headless: bool = False) -> bool: # noqa: C901, PLR0912, PLR0915
|
|
|
197
187
|
#
|
|
198
188
|
# Check login.md flowchart for better understanding
|
|
199
189
|
#
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
except TokenNotFoundError:
|
|
218
|
-
#
|
|
219
|
-
# 2. If no token was found, let user choose an existing if exists
|
|
220
|
-
#
|
|
221
|
-
token = _do_prompt_to_use_existing_credentials()
|
|
222
|
-
if token is not None:
|
|
190
|
+
|
|
191
|
+
active_token_file = settings.get_active_token_file()
|
|
192
|
+
if active_token_file:
|
|
193
|
+
# check if the active token is valid, if not, prompt user to select a new token
|
|
194
|
+
if not Rest.has_access("/api/whoami") or active_token_file.is_expired():
|
|
195
|
+
settings.clear_active_account()
|
|
196
|
+
newly_activated_token_file = _do_prompt_to_use_existing_credentials()
|
|
197
|
+
if newly_activated_token_file:
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
# intentional fall through to login since we have no token
|
|
201
|
+
|
|
202
|
+
else:
|
|
203
|
+
# no active token, prompt user to select a new token
|
|
204
|
+
newly_activated_token_file = _do_prompt_to_use_existing_credentials()
|
|
205
|
+
if newly_activated_token_file:
|
|
223
206
|
return True
|
|
224
207
|
|
|
208
|
+
# intentional fall through to login since we have no token
|
|
209
|
+
|
|
210
|
+
#
|
|
211
|
+
# 2. Login in if no valid token found...
|
|
212
|
+
#
|
|
213
|
+
|
|
225
214
|
prepare_local_webserver()
|
|
226
215
|
|
|
227
216
|
def force_use_webserver_callback() -> bool:
|
|
@@ -234,8 +223,8 @@ def login(headless: bool = False) -> bool: # noqa: C901, PLR0912, PLR0915
|
|
|
234
223
|
"""
|
|
235
224
|
This will print a url the will trigger a callback later so the webserver must be up and running.
|
|
236
225
|
"""
|
|
237
|
-
|
|
238
|
-
|
|
226
|
+
print_unformatted("Copy the following link in a browser to login to cloud, and complete the sign-in prompts:")
|
|
227
|
+
print_newline()
|
|
239
228
|
|
|
240
229
|
url = (
|
|
241
230
|
f"{Rest.get_base_frontend_url()}/login"
|
|
@@ -245,15 +234,15 @@ def login(headless: bool = False) -> bool: # noqa: C901, PLR0912, PLR0915
|
|
|
245
234
|
f"&code_challenge={code_challenge}"
|
|
246
235
|
f"&redirect_uri=http://localhost:{httpd.server_address[1]}"
|
|
247
236
|
)
|
|
248
|
-
|
|
237
|
+
print_url(url)
|
|
249
238
|
httpd.serve_forever()
|
|
250
239
|
|
|
251
240
|
def login_headless() -> None:
|
|
252
241
|
"""
|
|
253
242
|
Full headless, opens a browser and expects a auth code to be entered and exchanged for the token
|
|
254
243
|
"""
|
|
255
|
-
|
|
256
|
-
|
|
244
|
+
print_unformatted("Copy the following link in a browser to login to cloud, and complete the sign-in prompts:")
|
|
245
|
+
print_newline()
|
|
257
246
|
|
|
258
247
|
url = (
|
|
259
248
|
f"{Rest.get_base_frontend_url()}/login"
|
|
@@ -262,7 +251,7 @@ def login(headless: bool = False) -> bool: # noqa: C901, PLR0912, PLR0915
|
|
|
262
251
|
f"&response_type=code"
|
|
263
252
|
f"&code_challenge={code_challenge}"
|
|
264
253
|
)
|
|
265
|
-
|
|
254
|
+
print_url(url)
|
|
266
255
|
|
|
267
256
|
code = typer.prompt(
|
|
268
257
|
"Once finished, enter the verification code provided in your browser",
|
|
@@ -275,12 +264,11 @@ def login(headless: bool = False) -> bool: # noqa: C901, PLR0912, PLR0915
|
|
|
275
264
|
allow_status_codes=[401],
|
|
276
265
|
)
|
|
277
266
|
if res.status_code == 401:
|
|
278
|
-
|
|
267
|
+
print_generic_error(
|
|
279
268
|
"Failed to fetch token. Please try again, if the problem persists please reach out to support@remotivelabs.com"
|
|
280
269
|
)
|
|
281
270
|
sys.exit(1)
|
|
282
271
|
access_token = res.json()["access_token"]
|
|
283
|
-
# res = Rest.handle_get("/api/whoami", return_response=True, access_token=access_token)
|
|
284
272
|
global short_lived_token # noqa: PLW0603
|
|
285
273
|
short_lived_token = access_token
|
|
286
274
|
create_personal_token()
|
|
@@ -301,10 +289,10 @@ def login(headless: bool = False) -> bool: # noqa: C901, PLR0912, PLR0915
|
|
|
301
289
|
)
|
|
302
290
|
|
|
303
291
|
if not could_open:
|
|
304
|
-
|
|
292
|
+
print_generic_error(
|
|
305
293
|
"Could not open a browser on this machine, this is likely because you are in an environment where no browser is available"
|
|
306
294
|
)
|
|
307
|
-
|
|
295
|
+
print_newline()
|
|
308
296
|
if force_use_webserver_callback():
|
|
309
297
|
login_with_callback_but_copy_url()
|
|
310
298
|
create_personal_token()
|
cli/cloud/auth_tokens.py
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List,
|
|
3
|
+
from typing import List, Optional
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
|
-
from rich.console import Console
|
|
7
6
|
from rich.table import Table
|
|
8
7
|
|
|
9
|
-
from cli.api.cloud import tokens
|
|
10
8
|
from cli.cloud.organisations import do_select_default_org
|
|
11
|
-
from cli.
|
|
12
|
-
from cli.settings import TokenNotFoundError, settings
|
|
9
|
+
from cli.settings import settings
|
|
13
10
|
from cli.settings.token_file import TokenFile
|
|
14
|
-
from cli.
|
|
11
|
+
from cli.utils.console import print_generic_error, print_generic_message, print_hint, print_newline, print_success, print_unformatted
|
|
15
12
|
from cli.utils.rest_helper import RestHelper as Rest
|
|
16
13
|
|
|
17
|
-
console = Console(stderr=False)
|
|
18
|
-
err_console = Console(stderr=True)
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
PromptType = Literal["activate", "login"]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _prompt_choice( # noqa: C901, PLR0912
|
|
15
|
+
def _prompt_choice( # noqa: C901
|
|
26
16
|
choices: List[TokenFile],
|
|
27
17
|
skip_prompt: bool = False,
|
|
28
18
|
info_message: Optional[str] = None,
|
|
29
19
|
) -> Optional[TokenFile]:
|
|
30
|
-
accounts = settings.
|
|
20
|
+
accounts = settings.list_accounts()
|
|
31
21
|
|
|
32
22
|
table = Table("#", "Active", "Type", "Token", "Account", "Created", "Expires")
|
|
33
23
|
|
|
@@ -37,11 +27,10 @@ def _prompt_choice( # noqa: C901, PLR0912
|
|
|
37
27
|
for token in choices:
|
|
38
28
|
account = accounts.get(token.account.email)
|
|
39
29
|
if account and account.credentials_file:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
except TokenNotFoundError:
|
|
30
|
+
token_file = settings.get_token_file(account.credentials_file)
|
|
31
|
+
if token_file and token_file.name in (token.name or ""):
|
|
32
|
+
included_tokens.append(token)
|
|
33
|
+
else:
|
|
45
34
|
excluded_tokens.append(token)
|
|
46
35
|
else:
|
|
47
36
|
excluded_tokens.append(token)
|
|
@@ -51,13 +40,7 @@ def _prompt_choice( # noqa: C901, PLR0912
|
|
|
51
40
|
|
|
52
41
|
included_tokens.sort(key=lambda token: token.created, reverse=True)
|
|
53
42
|
|
|
54
|
-
|
|
55
|
-
try:
|
|
56
|
-
return settings.get_active_token_file()
|
|
57
|
-
except TokenNotFoundError:
|
|
58
|
-
return None
|
|
59
|
-
|
|
60
|
-
active_token = get_active_token_or_none()
|
|
43
|
+
active_token = settings.get_active_token_file()
|
|
61
44
|
active_token_index = None
|
|
62
45
|
for idx, choice in enumerate(included_tokens, start=1):
|
|
63
46
|
is_active = active_token is not None and active_token.name == choice.name
|
|
@@ -72,14 +55,14 @@ def _prompt_choice( # noqa: C901, PLR0912
|
|
|
72
55
|
str(choice.created),
|
|
73
56
|
str(choice.expires),
|
|
74
57
|
)
|
|
75
|
-
|
|
58
|
+
print_unformatted(table)
|
|
76
59
|
|
|
77
60
|
if skip_prompt:
|
|
78
61
|
return None
|
|
79
62
|
|
|
80
|
-
|
|
81
|
-
if info_message
|
|
82
|
-
|
|
63
|
+
print_newline()
|
|
64
|
+
if info_message:
|
|
65
|
+
print_generic_message(info_message)
|
|
83
66
|
|
|
84
67
|
selection = typer.prompt(
|
|
85
68
|
f"Enter the number(# 1-{len(included_tokens)}) of the account to select (q to quit)",
|
|
@@ -98,62 +81,8 @@ def _prompt_choice( # noqa: C901, PLR0912
|
|
|
98
81
|
return _prompt_choice(included_tokens, skip_prompt, info_message)
|
|
99
82
|
|
|
100
83
|
|
|
101
|
-
# @app.command(name="create")
|
|
102
|
-
def create(
|
|
103
|
-
activate: bool = typer.Option(False, help="Activate the token for use after download"),
|
|
104
|
-
) -> None:
|
|
105
|
-
"""
|
|
106
|
-
Create a new personal access token in [bold]cloud[/bold] and download locally
|
|
107
|
-
"""
|
|
108
|
-
response = tokens.create()
|
|
109
|
-
pat = settings.add_personal_token(response.text())
|
|
110
|
-
print(f"Personal access token added: {pat.name}")
|
|
111
|
-
|
|
112
|
-
if not activate:
|
|
113
|
-
print(f"Use 'remotive cloud auth tokens activate {pat.name}' to use this access token from cli")
|
|
114
|
-
else:
|
|
115
|
-
settings.activate_token(pat)
|
|
116
|
-
print("Token file activated and ready for use")
|
|
117
|
-
print("\033[93m This file contains secrets and must be kept safe")
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# @app.command(name="list", help="List personal credentials in [bold]cloud[/bold]")
|
|
121
|
-
def list_tokens() -> None:
|
|
122
|
-
Rest.handle_get("/api/me/keys")
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
# @app.command(name="revoke")
|
|
126
|
-
def revoke(
|
|
127
|
-
name: str = typer.Argument(help="Access token name"),
|
|
128
|
-
delete: bool = typer.Option(True, help="Also delete token"),
|
|
129
|
-
) -> None:
|
|
130
|
-
"""
|
|
131
|
-
Revoke personal credentials in cloud and removes the file from filesystem
|
|
132
|
-
|
|
133
|
-
If cloud token is not found but token is found on file system it will delete it and
|
|
134
|
-
vice versa.
|
|
135
|
-
"""
|
|
136
|
-
_revoke_and_delete_personal_token(name, delete)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# @app.command(name="activate")
|
|
140
|
-
def activate(
|
|
141
|
-
token_name: str = typer.Argument(..., help="Token path, filename or name to activate"),
|
|
142
|
-
) -> None:
|
|
143
|
-
"""
|
|
144
|
-
Activate a credential file to be used for authentication using filename, path or name.
|
|
145
|
-
|
|
146
|
-
This will be used as the current access token in all requests.
|
|
147
|
-
"""
|
|
148
|
-
try:
|
|
149
|
-
token_file = settings.get_token_file(token_name)
|
|
150
|
-
settings.activate_token(token_file)
|
|
151
|
-
except TokenNotFoundError:
|
|
152
|
-
err_console.print(f":boom: [bold red] Error: [/bold red] Token with filename or name {token_name} could not be found")
|
|
153
|
-
|
|
154
|
-
|
|
155
84
|
def prompt_to_set_org() -> None:
|
|
156
|
-
active_account = settings.
|
|
85
|
+
active_account = settings.get_active_account()
|
|
157
86
|
if active_account and not active_account.default_organization:
|
|
158
87
|
set_default_organisation = typer.confirm(
|
|
159
88
|
"You have not set a default organization\nWould you like to choose one now?",
|
|
@@ -164,45 +93,32 @@ def prompt_to_set_org() -> None:
|
|
|
164
93
|
do_select_default_org(get=False)
|
|
165
94
|
|
|
166
95
|
|
|
167
|
-
@app.command("activate")
|
|
168
|
-
def select_personal_token(
|
|
169
|
-
token_name: str = typer.Argument(None, help="Name, filename or path to a credentials file"),
|
|
170
|
-
) -> None:
|
|
171
|
-
"""
|
|
172
|
-
Activates is setting the current active credentials to use by the CLI, this can be done by specifying a name
|
|
173
|
-
of the token or getting prompted and choosing from existing.
|
|
174
|
-
"""
|
|
175
|
-
do_activate(token_name)
|
|
176
|
-
|
|
177
|
-
|
|
178
96
|
def do_activate(token_name: Optional[str]) -> Optional[TokenFile]:
|
|
179
|
-
if token_name
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return token_file
|
|
184
|
-
except TokenNotFoundError:
|
|
185
|
-
err_console.print(f":boom: [bold red] Error: [/bold red] Token with filename or name {token_name} could not be found")
|
|
97
|
+
if token_name:
|
|
98
|
+
token_file = settings.get_token_file(token_name)
|
|
99
|
+
if not token_file:
|
|
100
|
+
print_generic_error(f"Token with filename or name {token_name} could not be found")
|
|
186
101
|
return None
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
102
|
+
return settings.activate_token(token_file)
|
|
103
|
+
|
|
104
|
+
token_files = settings.list_personal_token_files()
|
|
105
|
+
token_files.extend(settings.list_service_account_token_files())
|
|
106
|
+
if len(token_files) > 0:
|
|
107
|
+
token_selected = list_and_select_personal_token(include_service_accounts=True)
|
|
108
|
+
if token_selected is not None:
|
|
109
|
+
is_logged_in = Rest.has_access("/api/whoami")
|
|
110
|
+
if not is_logged_in:
|
|
111
|
+
print_generic_error("Could not access RemotiveCloud with selected token")
|
|
112
|
+
else:
|
|
113
|
+
print_success("Access to RemotiveCloud granted")
|
|
114
|
+
# Only select default if activate was done with selection and successful
|
|
115
|
+
# and not SA since SA cannot list available organizations
|
|
116
|
+
if token_selected.type == "authorized_user":
|
|
117
|
+
prompt_to_set_org()
|
|
118
|
+
return token_selected
|
|
203
119
|
|
|
204
|
-
|
|
205
|
-
|
|
120
|
+
print_hint("No credentials available, login to activate credentials")
|
|
121
|
+
return None
|
|
206
122
|
|
|
207
123
|
|
|
208
124
|
def list_and_select_personal_token(
|
|
@@ -210,10 +126,10 @@ def list_and_select_personal_token(
|
|
|
210
126
|
include_service_accounts: bool = False,
|
|
211
127
|
info_message: Optional[str] = None,
|
|
212
128
|
) -> Optional[TokenFile]:
|
|
213
|
-
personal_tokens = settings.
|
|
129
|
+
personal_tokens = settings.list_personal_token_files()
|
|
214
130
|
|
|
215
131
|
if include_service_accounts:
|
|
216
|
-
sa_tokens = settings.
|
|
132
|
+
sa_tokens = settings.list_service_account_token_files()
|
|
217
133
|
personal_tokens.extend(sa_tokens)
|
|
218
134
|
|
|
219
135
|
selected_token = _prompt_choice(personal_tokens, skip_prompt=skip_prompt, info_message=info_message)
|
|
@@ -221,126 +137,3 @@ def list_and_select_personal_token(
|
|
|
221
137
|
settings.activate_token(selected_token)
|
|
222
138
|
|
|
223
139
|
return selected_token
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
# @app.command("select-revoke")
|
|
227
|
-
def select_revoke_personal_token() -> None:
|
|
228
|
-
"""
|
|
229
|
-
Prompts a user to select one of the credential files to revoke and delete
|
|
230
|
-
"""
|
|
231
|
-
personal_tokens = settings.list_personal_tokens()
|
|
232
|
-
sa_tokens = settings.list_service_account_tokens()
|
|
233
|
-
personal_tokens.extend(sa_tokens)
|
|
234
|
-
|
|
235
|
-
is_logged_in = Rest.has_access("/api/whoami")
|
|
236
|
-
if not is_logged_in:
|
|
237
|
-
ErrorPrinter.print_hint("You must be logged in")
|
|
238
|
-
raise typer.Exit(0)
|
|
239
|
-
|
|
240
|
-
# merged = _merge_local_tokens_with_cloud(personal_tokens)
|
|
241
|
-
|
|
242
|
-
selected_token = _prompt_choice(personal_tokens)
|
|
243
|
-
|
|
244
|
-
if selected_token is not None:
|
|
245
|
-
_revoke_and_delete_personal_token(selected_token.name, True)
|
|
246
|
-
# Rest.handle_patch(f"/api/me/keys/{selected_token.name}/revoke", quiet=True, access_token=selected_token.token)
|
|
247
|
-
# Rest.handle_delete(f"/api/me/keys/{selected_token.name}", quiet=True, access_token=selected_token.token)
|
|
248
|
-
# settings.remove_token_file(selected_token.name)
|
|
249
|
-
# active_token = settings.get_active_token_file()
|
|
250
|
-
# if active_token.name == selected_token.name:
|
|
251
|
-
# settings.clear_active_token()
|
|
252
|
-
# select_revoke_personal_token()
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
# @app.command("test-all")
|
|
256
|
-
def test_all_personal_tokens() -> None:
|
|
257
|
-
"""
|
|
258
|
-
Tests each credential file to see if it is valid
|
|
259
|
-
"""
|
|
260
|
-
personal_tokens = settings.list_personal_tokens()
|
|
261
|
-
personal_tokens.extend(settings.list_service_account_tokens())
|
|
262
|
-
if len(personal_tokens) == 0:
|
|
263
|
-
console.print("No personal tokens found on disk")
|
|
264
|
-
return
|
|
265
|
-
|
|
266
|
-
for token in personal_tokens:
|
|
267
|
-
r = Rest.handle_get(
|
|
268
|
-
"/api/whoami",
|
|
269
|
-
allow_status_codes=[401],
|
|
270
|
-
access_token=token.token,
|
|
271
|
-
use_progress_indicator=True,
|
|
272
|
-
return_response=True,
|
|
273
|
-
)
|
|
274
|
-
if r.status_code == 200:
|
|
275
|
-
if token.account is not None:
|
|
276
|
-
console.print(f"{token.account.email} ({token.name}) :white_check_mark:")
|
|
277
|
-
else:
|
|
278
|
-
console.print(f"{token.name} :white_check_mark:")
|
|
279
|
-
elif token.account is not None:
|
|
280
|
-
console.print(f"{token.account.email} ({token.name}) :x: Failed")
|
|
281
|
-
else:
|
|
282
|
-
console.print(f"{token.name} :x: Failed")
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
# @app.command(name="list-service-account-tokens-files")
|
|
286
|
-
def list_sats_files() -> None:
|
|
287
|
-
"""
|
|
288
|
-
List service account access token files in remotivelabs config directory
|
|
289
|
-
"""
|
|
290
|
-
service_account_files = settings.list_service_account_token_files()
|
|
291
|
-
for file in service_account_files:
|
|
292
|
-
print(file)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
@app.command(name="list")
|
|
296
|
-
def list_pats_files(
|
|
297
|
-
accounts: bool = typer.Option(True, help="Lists all available accounts"),
|
|
298
|
-
files: bool = typer.Option(False, help="Shows all token files in config directory"),
|
|
299
|
-
) -> None:
|
|
300
|
-
"""
|
|
301
|
-
Lists available credential files on filesystem
|
|
302
|
-
"""
|
|
303
|
-
|
|
304
|
-
if accounts:
|
|
305
|
-
list_and_select_personal_token(skip_prompt=True, include_service_accounts=True, info_message="hello")
|
|
306
|
-
|
|
307
|
-
if files:
|
|
308
|
-
personal_files = settings.list_personal_token_files()
|
|
309
|
-
service_account_files = settings.list_service_account_token_files()
|
|
310
|
-
personal_files.extend(service_account_files)
|
|
311
|
-
for file in personal_files:
|
|
312
|
-
print(file)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def _revoke_and_delete_personal_token(name: str, delete: bool) -> None:
|
|
316
|
-
token_file = None
|
|
317
|
-
|
|
318
|
-
# First we try to find the file and make sure its not the currently active
|
|
319
|
-
try:
|
|
320
|
-
token_file = settings.get_token_file(name)
|
|
321
|
-
active_token = settings.get_active_token_file()
|
|
322
|
-
if token_file.name == active_token.name:
|
|
323
|
-
ErrorPrinter.print_hint("You cannot revoke the current active token")
|
|
324
|
-
return
|
|
325
|
-
except TokenNotFoundError:
|
|
326
|
-
pass
|
|
327
|
-
|
|
328
|
-
# The lets try to revoke from cloud
|
|
329
|
-
res_revoke = tokens.revoke(name)
|
|
330
|
-
if delete:
|
|
331
|
-
res_delete = tokens.delete(name)
|
|
332
|
-
if res_delete.is_success:
|
|
333
|
-
ErrorPrinter.print_generic_message("Token successfully revoked and deleted")
|
|
334
|
-
else:
|
|
335
|
-
ErrorPrinter.print_hint(f"Failed to revoke and delete token in cloud: {res_delete.status_code}")
|
|
336
|
-
elif res_revoke.is_success:
|
|
337
|
-
ErrorPrinter.print_generic_message("Token successfully revoked")
|
|
338
|
-
else:
|
|
339
|
-
ErrorPrinter.print_hint("Failed to revoke and delete token in cloud")
|
|
340
|
-
|
|
341
|
-
# Finally try to remove the file if exists
|
|
342
|
-
if token_file is not None:
|
|
343
|
-
settings.remove_token_file(token_file.name)
|
|
344
|
-
console.print("Successfully deleted token on filesystem")
|
|
345
|
-
else:
|
|
346
|
-
ErrorPrinter.print_hint("Token not found on filesystem")
|
cli/cloud/brokers.py
CHANGED
|
@@ -10,6 +10,7 @@ import typer
|
|
|
10
10
|
import websocket
|
|
11
11
|
|
|
12
12
|
from cli.typer import typer_utils
|
|
13
|
+
from cli.utils.console import print_generic_message
|
|
13
14
|
from cli.utils.rest_helper import RestHelper as Rest
|
|
14
15
|
|
|
15
16
|
app = typer_utils.create_typer()
|
|
@@ -61,12 +62,6 @@ def start(
|
|
|
61
62
|
silent: bool = typer.Option(False, help="Optional specific tag/version"),
|
|
62
63
|
api_key: str = typer.Option("", help="Start with your own api-key"),
|
|
63
64
|
) -> None:
|
|
64
|
-
# with Progress(
|
|
65
|
-
# SpinnerColumn(),
|
|
66
|
-
# TextColumn("[progress.description]{task.description}"),
|
|
67
|
-
# transient=True,
|
|
68
|
-
# ) as progress:
|
|
69
|
-
# progress.add_task(description=f"Starting broker {name}...", total=None)
|
|
70
65
|
do_start(name, project, api_key, tag, return_response=silent)
|
|
71
66
|
|
|
72
67
|
|
|
@@ -81,7 +76,6 @@ def logs(
|
|
|
81
76
|
Exposes broker logs history or real-time tail of the broker.
|
|
82
77
|
|
|
83
78
|
When using --tail option, --history always skipped even if supplied
|
|
84
|
-
|
|
85
79
|
"""
|
|
86
80
|
|
|
87
81
|
def exit_on_ctrlc(_sig: Any, _frame: Any) -> None:
|
|
@@ -91,10 +85,12 @@ def logs(
|
|
|
91
85
|
os_signal.signal(os_signal.SIGINT, exit_on_ctrlc)
|
|
92
86
|
|
|
93
87
|
def on_message(_wsapp: Any, message: str) -> None:
|
|
94
|
-
print
|
|
88
|
+
# TODO: use log instead of print for debug information?
|
|
89
|
+
print_generic_message(message)
|
|
95
90
|
|
|
96
91
|
def on_error(_wsapp: Any, err: str) -> None:
|
|
97
|
-
print
|
|
92
|
+
# TODO: use log instead of print for debug information?
|
|
93
|
+
print_generic_message(f"Error encountered: {err}")
|
|
98
94
|
|
|
99
95
|
Rest.ensure_auth_token()
|
|
100
96
|
# This will work with both http -> ws and https -> wss
|
cli/cloud/configs.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import os.path
|
|
2
2
|
import shutil
|
|
3
|
-
import sys
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
import requests
|
|
@@ -8,6 +7,7 @@ import typer
|
|
|
8
7
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
9
8
|
|
|
10
9
|
from cli.typer import typer_utils
|
|
10
|
+
from cli.utils.console import print_generic_error, print_success
|
|
11
11
|
from cli.utils.rest_helper import RestHelper as Rest
|
|
12
12
|
|
|
13
13
|
app = typer_utils.create_typer()
|
|
@@ -102,21 +102,8 @@ def download(
|
|
|
102
102
|
if download_resp.status_code == 200:
|
|
103
103
|
with open(signal_db_file, "wb") as out_file:
|
|
104
104
|
shutil.copyfileobj(download_resp.raw, out_file)
|
|
105
|
-
|
|
105
|
+
print_success(f"{signal_db_file} downloaded")
|
|
106
106
|
else:
|
|
107
|
-
|
|
107
|
+
print_generic_error(f"Got unexpected status {download_resp.status_code}")
|
|
108
108
|
else:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
# @app.command()
|
|
113
|
-
# def upload(file: str, project: str = typer.Option(..., help="Project ID", envvar='REMOTIVE_CLOUD_PROJECT')):
|
|
114
|
-
# files = {'upload_file': open(file, 'rb')}
|
|
115
|
-
# values = {'DB': 'photcat', 'OUT': 'csv', 'SHORT': 'short'}
|
|
116
|
-
# rest.headers["content-type"] = "application/octet-stream"
|
|
117
|
-
# r = requests.get(f"{rest.base_url}/api/project/{project}/files/recording/upload/{file}", headers=rest.headers)
|
|
118
|
-
# print(r.status_code)
|
|
119
|
-
# print(r.text)
|
|
120
|
-
|
|
121
|
-
# pylint: disable=C0301
|
|
122
|
-
# 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'
|
|
109
|
+
print_generic_error(f"Got unexpected status {get_signed_url_resp.status_code}\n")
|
|
File without changes
|