remotivelabs-cli 0.2.3__py3-none-any.whl → 0.3.0__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/lib/broker.py +120 -89
- cli/broker/lib/client.py +224 -0
- cli/broker/lib/helper.py +278 -0
- cli/broker/lib/signalcreator.py +196 -0
- cli/cloud/__init__.py +17 -0
- cli/cloud/auth/cmd.py +71 -33
- cli/cloud/auth/login.py +26 -28
- cli/cloud/auth_tokens.py +34 -237
- cli/cloud/licenses/__init__.py +0 -0
- cli/cloud/licenses/cmd.py +14 -0
- cli/cloud/organisations.py +4 -7
- cli/cloud/service_account_tokens.py +1 -1
- cli/connect/protopie/protopie.py +1 -1
- cli/remotive.py +12 -19
- cli/settings/__init__.py +1 -2
- cli/settings/config_file.py +2 -0
- cli/settings/core.py +142 -144
- cli/settings/migration/migrate_config_file.py +13 -6
- cli/settings/migration/migration_tools.py +4 -3
- cli/settings/state_file.py +12 -4
- cli/topology/cmd.py +5 -6
- cli/utils/rest_helper.py +11 -16
- cli/utils/versions.py +5 -15
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.0.dist-info}/METADATA +3 -2
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.0.dist-info}/RECORD +28 -24
- cli/cloud/cloud_cli.py +0 -29
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.0.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.0.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.0.dist-info}/entry_points.txt +0 -0
cli/cloud/__init__.py
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from cli.cloud import auth, brokers, configs, organisations, projects, recordings, sample_recordings, service_accounts, storage
|
|
2
|
+
from cli.cloud.licenses.cmd import licenses
|
|
3
|
+
from cli.typer import typer_utils
|
|
4
|
+
|
|
5
|
+
app = typer_utils.create_typer()
|
|
6
|
+
|
|
7
|
+
app.command(name="licenses", help="List licenses for an organization")(licenses)
|
|
8
|
+
|
|
9
|
+
app.add_typer(organisations.app, name="organizations", help="Manage organizations")
|
|
10
|
+
app.add_typer(projects.app, name="projects", help="Manage projects")
|
|
11
|
+
app.add_typer(auth.app, name="auth")
|
|
12
|
+
app.add_typer(brokers.app, name="brokers", help="Manage cloud broker lifecycle")
|
|
13
|
+
app.add_typer(recordings.app, name="recordings", help="Manage recordings")
|
|
14
|
+
app.add_typer(configs.app, name="signal-databases", help="Manage signal databases")
|
|
15
|
+
app.add_typer(storage.app, name="storage")
|
|
16
|
+
app.add_typer(service_accounts.app, name="service-accounts", help="Manage project service account keys")
|
|
17
|
+
app.add_typer(sample_recordings.app, name="samples", help="Manage sample recordings")
|
cli/cloud/auth/cmd.py
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import os
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
6
8
|
|
|
7
9
|
from cli.cloud.auth.login import login as do_login
|
|
8
10
|
from cli.errors import ErrorPrinter
|
|
9
|
-
from cli.settings import
|
|
11
|
+
from cli.settings import settings
|
|
10
12
|
from cli.typer import typer_utils
|
|
11
13
|
from cli.utils.rest_helper import RestHelper as Rest
|
|
12
14
|
|
|
13
15
|
from .. import auth_tokens
|
|
14
16
|
|
|
17
|
+
console = Console(stderr=False)
|
|
18
|
+
|
|
15
19
|
HELP = """
|
|
16
20
|
Manage how you authenticate with our cloud platform
|
|
17
21
|
"""
|
|
18
22
|
app = typer_utils.create_typer(help=HELP)
|
|
19
|
-
# app.add_typer(auth_tokens.app, name="credentials", help="Manage account credentials")
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
@app.command(name="login")
|
|
@@ -36,11 +39,7 @@ def whoami() -> None:
|
|
|
36
39
|
"""
|
|
37
40
|
Validates authentication and fetches your account information
|
|
38
41
|
"""
|
|
39
|
-
|
|
40
|
-
Rest.handle_get("/api/whoami")
|
|
41
|
-
except TokenNotFoundError as e:
|
|
42
|
-
ErrorPrinter.print_hint(str(e))
|
|
43
|
-
sys.exit(1)
|
|
42
|
+
Rest.handle_get("/api/whoami")
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
@app.command()
|
|
@@ -50,39 +49,78 @@ def print_access_token(
|
|
|
50
49
|
"""
|
|
51
50
|
Print current active access token or the token for the specified account
|
|
52
51
|
"""
|
|
53
|
-
if account
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
except TokenNotFoundError:
|
|
52
|
+
if not account:
|
|
53
|
+
active_token = settings.get_active_token() or os.getenv("REMOTIVE_CLOUD_ACCESS_TOKEN", None)
|
|
54
|
+
if not active_token:
|
|
57
55
|
ErrorPrinter.print_generic_error("You have no active account", exit_code=1)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
print(active_token)
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
accounts = settings.list_accounts()
|
|
62
|
+
if account not in accounts:
|
|
63
|
+
ErrorPrinter.print_generic_error(f"No account for {account} was found", exit_code=1)
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
token_file_name = accounts[account].credentials_file
|
|
67
|
+
token_file = settings.get_token_file(token_file_name)
|
|
68
|
+
if not token_file:
|
|
69
|
+
ErrorPrinter.print_generic_error(f"Token file for {account} could not be found", exit_code=1)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
print(token_file.token)
|
|
68
73
|
|
|
69
74
|
|
|
70
75
|
def print_access_token_file() -> None:
|
|
71
76
|
"""
|
|
72
77
|
Print current active token and its metadata
|
|
73
78
|
"""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
active_token_file = settings.get_active_token_file()
|
|
80
|
+
if not active_token_file:
|
|
81
|
+
ErrorPrinter.print_generic_error("You have no active account", exit_code=1)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
print(active_token_file)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command(name="deactivate")
|
|
88
|
+
def deactivate() -> None:
|
|
89
|
+
"""
|
|
90
|
+
Clears active account
|
|
91
|
+
"""
|
|
92
|
+
settings.clear_active_account()
|
|
93
|
+
print("Account no longer active")
|
|
79
94
|
|
|
80
95
|
|
|
81
|
-
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
@app.command("activate")
|
|
97
|
+
def activate(token_name: str = typer.Argument(None, help="Name, filename or path to a credentials file")) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Set the active account
|
|
100
|
+
"""
|
|
101
|
+
auth_tokens.do_activate(token_name)
|
|
85
102
|
|
|
86
103
|
|
|
87
|
-
app.command("
|
|
88
|
-
|
|
104
|
+
@app.command(name="list")
|
|
105
|
+
def list() -> None:
|
|
106
|
+
"""
|
|
107
|
+
Lists available credential files on filesystem
|
|
108
|
+
"""
|
|
109
|
+
accounts = settings.list_accounts()
|
|
110
|
+
|
|
111
|
+
table = Table("#", "Active", "Type", "Token", "Account", "Organization", "Created", "Expires")
|
|
112
|
+
for idx, (email, account) in enumerate(accounts.items(), start=1):
|
|
113
|
+
token_file = settings.get_token_file_by_email(email)
|
|
114
|
+
is_active = settings.is_active_account(email)
|
|
115
|
+
|
|
116
|
+
table.add_row(
|
|
117
|
+
f"[yellow]{idx}",
|
|
118
|
+
":white_check_mark:" if is_active else "",
|
|
119
|
+
"unknown" if not token_file else "user" if token_file.type == "authorized_user" else "sa",
|
|
120
|
+
token_file.name if token_file else "",
|
|
121
|
+
f"[bold]{email}[/bold]",
|
|
122
|
+
account.default_organization if account.default_organization else "",
|
|
123
|
+
str(token_file.created) if token_file else "",
|
|
124
|
+
str(token_file.expires) if token_file else "",
|
|
125
|
+
)
|
|
126
|
+
console.print(table)
|
cli/cloud/auth/login.py
CHANGED
|
@@ -18,7 +18,7 @@ from typing_extensions import override
|
|
|
18
18
|
|
|
19
19
|
from cli.cloud.auth_tokens import do_activate, prompt_to_set_org
|
|
20
20
|
from cli.errors import ErrorPrinter
|
|
21
|
-
from cli.settings import
|
|
21
|
+
from cli.settings import settings
|
|
22
22
|
from cli.settings.token_file import TokenFile
|
|
23
23
|
from cli.utils.rest_helper import RestHelper as Rest
|
|
24
24
|
|
|
@@ -174,14 +174,13 @@ def create_personal_token() -> None:
|
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
def _do_prompt_to_use_existing_credentials() -> Optional[TokenFile]:
|
|
177
|
-
|
|
178
|
-
if len(
|
|
177
|
+
token_files = settings.list_personal_token_files()
|
|
178
|
+
if len(token_files) > 0:
|
|
179
179
|
should_select_token = typer.confirm(
|
|
180
180
|
"You have credentials available already, would you like to choose one of these instead?", default=True
|
|
181
181
|
)
|
|
182
182
|
if should_select_token:
|
|
183
183
|
token = do_activate(token_name=None)
|
|
184
|
-
# token = list_and_select_personal_token(skip_prompt=False, include_service_accounts=True)
|
|
185
184
|
if token is not None:
|
|
186
185
|
return token
|
|
187
186
|
# TODO - fix so this is not needed
|
|
@@ -189,7 +188,7 @@ def _do_prompt_to_use_existing_credentials() -> Optional[TokenFile]:
|
|
|
189
188
|
return None
|
|
190
189
|
|
|
191
190
|
|
|
192
|
-
def login(headless: bool = False) -> bool: # noqa: C901,
|
|
191
|
+
def login(headless: bool = False) -> bool: # noqa: C901, PLR0915
|
|
193
192
|
"""
|
|
194
193
|
Initiate login
|
|
195
194
|
"""
|
|
@@ -197,31 +196,30 @@ def login(headless: bool = False) -> bool: # noqa: C901, PLR0912, PLR0915
|
|
|
197
196
|
#
|
|
198
197
|
# Check login.md flowchart for better understanding
|
|
199
198
|
#
|
|
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:
|
|
199
|
+
|
|
200
|
+
active_token_file = settings.get_active_token_file()
|
|
201
|
+
if active_token_file:
|
|
202
|
+
# check if the active token is valid, if not, prompt user to select a new token
|
|
203
|
+
if not Rest.has_access("/api/whoami") or active_token_file.is_expired():
|
|
204
|
+
settings.clear_active_account()
|
|
205
|
+
newly_activated_token_file = _do_prompt_to_use_existing_credentials()
|
|
206
|
+
if newly_activated_token_file:
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
# intentional fall through to login since we have no token
|
|
210
|
+
|
|
211
|
+
else:
|
|
212
|
+
# no active token, prompt user to select a new token
|
|
213
|
+
newly_activated_token_file = _do_prompt_to_use_existing_credentials()
|
|
214
|
+
if newly_activated_token_file:
|
|
223
215
|
return True
|
|
224
216
|
|
|
217
|
+
# intentional fall through to login since we have no token
|
|
218
|
+
|
|
219
|
+
#
|
|
220
|
+
# 2. Login in if no valid token found...
|
|
221
|
+
#
|
|
222
|
+
|
|
225
223
|
prepare_local_webserver()
|
|
226
224
|
|
|
227
225
|
def force_use_webserver_callback() -> bool:
|
cli/cloud/auth_tokens.py
CHANGED
|
@@ -1,33 +1,27 @@
|
|
|
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
6
|
from rich.console import Console
|
|
7
7
|
from rich.table import Table
|
|
8
8
|
|
|
9
|
-
from cli.api.cloud import tokens
|
|
10
9
|
from cli.cloud.organisations import do_select_default_org
|
|
11
10
|
from cli.errors import ErrorPrinter
|
|
12
|
-
from cli.settings import
|
|
11
|
+
from cli.settings import settings
|
|
13
12
|
from cli.settings.token_file import TokenFile
|
|
14
|
-
from cli.typer import typer_utils
|
|
15
13
|
from cli.utils.rest_helper import RestHelper as Rest
|
|
16
14
|
|
|
17
15
|
console = Console(stderr=False)
|
|
18
16
|
err_console = Console(stderr=True)
|
|
19
17
|
|
|
20
|
-
app = typer_utils.create_typer()
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _prompt_choice( # noqa: C901, PLR0912
|
|
19
|
+
def _prompt_choice( # noqa: C901
|
|
26
20
|
choices: List[TokenFile],
|
|
27
21
|
skip_prompt: bool = False,
|
|
28
22
|
info_message: Optional[str] = None,
|
|
29
23
|
) -> Optional[TokenFile]:
|
|
30
|
-
accounts = settings.
|
|
24
|
+
accounts = settings.list_accounts()
|
|
31
25
|
|
|
32
26
|
table = Table("#", "Active", "Type", "Token", "Account", "Created", "Expires")
|
|
33
27
|
|
|
@@ -37,11 +31,10 @@ def _prompt_choice( # noqa: C901, PLR0912
|
|
|
37
31
|
for token in choices:
|
|
38
32
|
account = accounts.get(token.account.email)
|
|
39
33
|
if account and account.credentials_file:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
except TokenNotFoundError:
|
|
34
|
+
token_file = settings.get_token_file(account.credentials_file)
|
|
35
|
+
if token_file and token_file.name in (token.name or ""):
|
|
36
|
+
included_tokens.append(token)
|
|
37
|
+
else:
|
|
45
38
|
excluded_tokens.append(token)
|
|
46
39
|
else:
|
|
47
40
|
excluded_tokens.append(token)
|
|
@@ -51,13 +44,7 @@ def _prompt_choice( # noqa: C901, PLR0912
|
|
|
51
44
|
|
|
52
45
|
included_tokens.sort(key=lambda token: token.created, reverse=True)
|
|
53
46
|
|
|
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()
|
|
47
|
+
active_token = settings.get_active_token_file()
|
|
61
48
|
active_token_index = None
|
|
62
49
|
for idx, choice in enumerate(included_tokens, start=1):
|
|
63
50
|
is_active = active_token is not None and active_token.name == choice.name
|
|
@@ -98,62 +85,8 @@ def _prompt_choice( # noqa: C901, PLR0912
|
|
|
98
85
|
return _prompt_choice(included_tokens, skip_prompt, info_message)
|
|
99
86
|
|
|
100
87
|
|
|
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
88
|
def prompt_to_set_org() -> None:
|
|
156
|
-
active_account = settings.
|
|
89
|
+
active_account = settings.get_active_account()
|
|
157
90
|
if active_account and not active_account.default_organization:
|
|
158
91
|
set_default_organisation = typer.confirm(
|
|
159
92
|
"You have not set a default organization\nWould you like to choose one now?",
|
|
@@ -164,45 +97,32 @@ def prompt_to_set_org() -> None:
|
|
|
164
97
|
do_select_default_org(get=False)
|
|
165
98
|
|
|
166
99
|
|
|
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
100
|
def do_activate(token_name: Optional[str]) -> Optional[TokenFile]:
|
|
179
|
-
if token_name
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
settings.activate_token(token_file)
|
|
183
|
-
return token_file
|
|
184
|
-
except TokenNotFoundError:
|
|
101
|
+
if token_name:
|
|
102
|
+
token_file = settings.get_token_file(token_name)
|
|
103
|
+
if not token_file:
|
|
185
104
|
err_console.print(f":boom: [bold red] Error: [/bold red] Token with filename or name {token_name} could not be found")
|
|
186
105
|
return None
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
106
|
+
return settings.activate_token(token_file)
|
|
107
|
+
|
|
108
|
+
token_files = settings.list_personal_token_files()
|
|
109
|
+
token_files.extend(settings.list_service_account_token_files())
|
|
110
|
+
if len(token_files) > 0:
|
|
111
|
+
token_selected = list_and_select_personal_token(include_service_accounts=True)
|
|
112
|
+
if token_selected is not None:
|
|
113
|
+
is_logged_in = Rest.has_access("/api/whoami")
|
|
114
|
+
if not is_logged_in:
|
|
115
|
+
ErrorPrinter.print_generic_error("Could not access RemotiveCloud with selected token")
|
|
116
|
+
else:
|
|
117
|
+
console.print("[green]Success![/green] Access to RemotiveCloud granted")
|
|
118
|
+
# Only select default if activate was done with selection and successful
|
|
119
|
+
# and not SA since SA cannot list available organizations
|
|
120
|
+
if token_selected.type == "authorized_user":
|
|
121
|
+
prompt_to_set_org()
|
|
122
|
+
return token_selected
|
|
203
123
|
|
|
204
|
-
|
|
205
|
-
|
|
124
|
+
ErrorPrinter.print_hint("No credentials available, login to activate credentials")
|
|
125
|
+
return None
|
|
206
126
|
|
|
207
127
|
|
|
208
128
|
def list_and_select_personal_token(
|
|
@@ -210,10 +130,10 @@ def list_and_select_personal_token(
|
|
|
210
130
|
include_service_accounts: bool = False,
|
|
211
131
|
info_message: Optional[str] = None,
|
|
212
132
|
) -> Optional[TokenFile]:
|
|
213
|
-
personal_tokens = settings.
|
|
133
|
+
personal_tokens = settings.list_personal_token_files()
|
|
214
134
|
|
|
215
135
|
if include_service_accounts:
|
|
216
|
-
sa_tokens = settings.
|
|
136
|
+
sa_tokens = settings.list_service_account_token_files()
|
|
217
137
|
personal_tokens.extend(sa_tokens)
|
|
218
138
|
|
|
219
139
|
selected_token = _prompt_choice(personal_tokens, skip_prompt=skip_prompt, info_message=info_message)
|
|
@@ -221,126 +141,3 @@ def list_and_select_personal_token(
|
|
|
221
141
|
settings.activate_token(selected_token)
|
|
222
142
|
|
|
223
143
|
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")
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from cli.typer import typer_utils
|
|
4
|
+
from cli.utils.rest_helper import RestHelper
|
|
5
|
+
|
|
6
|
+
app = typer_utils.create_typer()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command(help="List licenses for an organization")
|
|
10
|
+
def licenses(
|
|
11
|
+
organization: str = typer.Option(..., help="Organization ID", envvar="REMOTIVE_CLOUD_ORGANIZATION"),
|
|
12
|
+
filter_option: str = typer.Option("all", help="all, valid, expired"),
|
|
13
|
+
) -> None:
|
|
14
|
+
RestHelper.handle_get(f"/api/bu/{organization}/licenses", {"filter": filter_option})
|
cli/cloud/organisations.py
CHANGED
|
@@ -26,10 +26,8 @@ class Organisation:
|
|
|
26
26
|
def _prompt_choice(choices: List[Organisation]) -> Optional[Organisation]:
|
|
27
27
|
table = Table("#", "Name", "Uid", "Default")
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
current_default_org = config.accounts[token.account.email].default_organization if token is not None else None
|
|
29
|
+
account = settings.get_active_account()
|
|
30
|
+
current_default_org = account.default_organization if account else None
|
|
33
31
|
|
|
34
32
|
for idx, choice in enumerate(choices, start=1):
|
|
35
33
|
table.add_row(
|
|
@@ -74,7 +72,7 @@ def do_select_default_org(organisation_uid: Optional[str] = None, get: bool = Fa
|
|
|
74
72
|
Note that service-accounts does Not have permission to list organizations and will get a 403 Forbidden response so you must
|
|
75
73
|
select the organization uid as argument
|
|
76
74
|
"""
|
|
77
|
-
active_account = settings.
|
|
75
|
+
active_account = settings.get_active_account()
|
|
78
76
|
if get:
|
|
79
77
|
if active_account and active_account.default_organization:
|
|
80
78
|
console.print(active_account.default_organization)
|
|
@@ -85,7 +83,7 @@ def do_select_default_org(organisation_uid: Optional[str] = None, get: bool = Fa
|
|
|
85
83
|
else:
|
|
86
84
|
if active_account:
|
|
87
85
|
token = settings.get_token_file(active_account.credentials_file)
|
|
88
|
-
if token.type != "authorized_user":
|
|
86
|
+
if token and token.type != "authorized_user":
|
|
89
87
|
ErrorPrinter.print_hint(
|
|
90
88
|
"You must supply the organization name as argument when using a service-account since the "
|
|
91
89
|
"service-account is not allowed to list"
|
|
@@ -99,7 +97,6 @@ def do_select_default_org(organisation_uid: Optional[str] = None, get: bool = Fa
|
|
|
99
97
|
selected = _prompt_choice(orgs)
|
|
100
98
|
|
|
101
99
|
if selected is not None:
|
|
102
|
-
settings.get_cli_config()
|
|
103
100
|
typer.echo(f"Default organisation: {selected.display_name} (uid: {selected.uid})")
|
|
104
101
|
settings.set_default_organisation(selected.uid)
|
|
105
102
|
|
|
@@ -56,6 +56,6 @@ def revoke(
|
|
|
56
56
|
Rest.handle_patch(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}/revoke", quiet=True)
|
|
57
57
|
if delete:
|
|
58
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.
|
|
59
|
+
token_with_name = [token for token in settings.list_service_account_token_files() if token.name == name]
|
|
60
60
|
if len(token_with_name) > 0:
|
|
61
61
|
settings.remove_token_file(name)
|
cli/connect/protopie/protopie.py
CHANGED
|
@@ -11,12 +11,12 @@ from typing import Any, Dict, List, Tuple, Union
|
|
|
11
11
|
|
|
12
12
|
import grpc
|
|
13
13
|
import socketio
|
|
14
|
-
from remotivelabs.broker.sync import BrokerException, Client, SignalIdentifier, SignalsInFrame
|
|
15
14
|
from rich import print as pretty_print
|
|
16
15
|
from rich.console import Console
|
|
17
16
|
from socketio.exceptions import ConnectionError as SocketIoConnectionError
|
|
18
17
|
|
|
19
18
|
from cli.broker.lib.broker import SubscribableSignal
|
|
19
|
+
from cli.broker.lib.client import BrokerException, Client, SignalIdentifier, SignalsInFrame
|
|
20
20
|
from cli.errors import ErrorPrinter
|
|
21
21
|
from cli.settings import settings
|
|
22
22
|
|