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/remotive.py
CHANGED
|
@@ -9,10 +9,9 @@ from trogon import Trogon
|
|
|
9
9
|
from typer.main import get_group
|
|
10
10
|
|
|
11
11
|
from cli.broker.brokers import app as broker_app
|
|
12
|
-
from cli.cloud
|
|
12
|
+
from cli.cloud import app as cloud_app
|
|
13
13
|
from cli.connect.connect import app as connect_app
|
|
14
|
-
from cli.settings import settings
|
|
15
|
-
from cli.settings.core import Settings
|
|
14
|
+
from cli.settings import Settings, settings
|
|
16
15
|
from cli.settings.migration.migrate_all_token_files import migrate_any_legacy_tokens
|
|
17
16
|
from cli.settings.migration.migrate_config_file import migrate_config_file
|
|
18
17
|
from cli.settings.migration.migrate_legacy_dirs import migrate_legacy_settings_dirs
|
|
@@ -82,7 +81,7 @@ def set_default_org_as_env(settings: Settings) -> None:
|
|
|
82
81
|
This has to be done early before it is read
|
|
83
82
|
"""
|
|
84
83
|
if "REMOTIVE_CLOUD_ORGANIZATION" not in os.environ:
|
|
85
|
-
active_account = settings.
|
|
84
|
+
active_account = settings.get_active_account()
|
|
86
85
|
if active_account and active_account.default_organization:
|
|
87
86
|
os.environ["REMOTIVE_CLOUD_ORGANIZATION"] = active_account.default_organization
|
|
88
87
|
|
|
@@ -113,21 +112,15 @@ def tui(ctx: typer.Context) -> None:
|
|
|
113
112
|
|
|
114
113
|
|
|
115
114
|
app.add_typer(broker_app, name="broker", help="Manage a single broker - local or cloud")
|
|
116
|
-
app.add_typer(
|
|
117
|
-
cloud_app,
|
|
118
|
-
name="cloud",
|
|
119
|
-
help="Manage resources in RemotiveCloud",
|
|
120
|
-
)
|
|
115
|
+
app.add_typer(cloud_app, name="cloud", help="Manage resources in RemotiveCloud")
|
|
121
116
|
app.add_typer(connect_app, name="connect", help="Integrations with other systems")
|
|
122
117
|
app.add_typer(tools_app, name="tools")
|
|
118
|
+
app.add_typer(
|
|
119
|
+
topology_app,
|
|
120
|
+
name="topology",
|
|
121
|
+
help="""
|
|
122
|
+
Interact and manage RemotiveTopology resources
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
name="topology",
|
|
128
|
-
help="""
|
|
129
|
-
RemotiveTopology actions
|
|
130
|
-
|
|
131
|
-
Read more at https://docs.remotivelabs.com/docs/remotive-topology
|
|
132
|
-
""",
|
|
133
|
-
)
|
|
124
|
+
Read more at https://docs.remotivelabs.com/docs/remotive-topology
|
|
125
|
+
""",
|
|
126
|
+
)
|
cli/settings/__init__.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from cli.settings.config_file import Account, ConfigFile
|
|
2
2
|
from cli.settings.config_file import dumps as dumps_config_file
|
|
3
3
|
from cli.settings.config_file import loads as loads_config_file
|
|
4
|
-
from cli.settings.core import InvalidSettingsFilePathError, Settings,
|
|
4
|
+
from cli.settings.core import InvalidSettingsFilePathError, Settings, settings
|
|
5
5
|
from cli.settings.token_file import TokenFile
|
|
6
6
|
from cli.settings.token_file import dumps as dumps_token_file
|
|
7
7
|
from cli.settings.token_file import loads as loads_token_file
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"settings",
|
|
11
|
-
"TokenNotFoundError",
|
|
12
11
|
"InvalidSettingsFilePathError",
|
|
13
12
|
"Settings",
|
|
14
13
|
"TokenFile",
|
cli/settings/config_file.py
CHANGED
cli/settings/core.py
CHANGED
|
@@ -3,18 +3,16 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import stat
|
|
5
5
|
import sys
|
|
6
|
-
from json import JSONDecodeError
|
|
7
6
|
from pathlib import Path
|
|
8
|
-
from typing import Optional
|
|
7
|
+
from typing import Optional
|
|
9
8
|
|
|
10
|
-
from pydantic import ValidationError
|
|
11
9
|
from rich.console import Console
|
|
12
10
|
|
|
13
11
|
from cli.errors import ErrorPrinter
|
|
14
12
|
from cli.settings import config_file as cf
|
|
15
13
|
from cli.settings import state_file as sf
|
|
16
14
|
from cli.settings import token_file as tf
|
|
17
|
-
from cli.settings.config_file import ConfigFile
|
|
15
|
+
from cli.settings.config_file import Account, ConfigFile
|
|
18
16
|
from cli.settings.state_file import StateFile
|
|
19
17
|
from cli.settings.token_file import TokenFile
|
|
20
18
|
|
|
@@ -24,27 +22,17 @@ CONFIG_DIR_PATH = Path.home() / ".config" / "remotive"
|
|
|
24
22
|
CLI_CONFIG_FILE_NAME = "config.json"
|
|
25
23
|
CLI_INTERNAL_STATE_FILE_NAME = "app-state.json"
|
|
26
24
|
|
|
27
|
-
TokenFileMetadata = Tuple[TokenFile, Path]
|
|
28
|
-
|
|
29
25
|
|
|
30
26
|
class InvalidSettingsFilePathError(Exception):
|
|
31
27
|
"""Raised when trying to access an invalid settings file or file path"""
|
|
32
28
|
|
|
33
29
|
|
|
34
|
-
class TokenNotFoundError(Exception):
|
|
35
|
-
"""Raised when a token cannot be found in settings"""
|
|
36
|
-
|
|
37
|
-
|
|
38
30
|
class Settings:
|
|
39
31
|
"""
|
|
40
32
|
Settings handles tokens and other config for the remotive CLI
|
|
41
33
|
|
|
42
|
-
TODO: return None instead of raising errors when no active account is found
|
|
43
|
-
TODO: be consisten in how we update (and write) state to the different files
|
|
44
34
|
TODO: migrate away from singleton instance
|
|
45
|
-
TODO:
|
|
46
|
-
TODO: what about active token when removing a token?
|
|
47
|
-
TODO: list tokens should use better listing logic
|
|
35
|
+
TODO: How do we handle REMOTIVE_CLOUD_ACCESS_TOKEN in combination with active account? What takes precedence?
|
|
48
36
|
"""
|
|
49
37
|
|
|
50
38
|
config_dir: Path
|
|
@@ -60,17 +48,23 @@ class Settings:
|
|
|
60
48
|
if not self.state_file_path.exists():
|
|
61
49
|
self._write_state_file(StateFile())
|
|
62
50
|
|
|
63
|
-
def
|
|
51
|
+
def _get_cli_config(self) -> ConfigFile:
|
|
64
52
|
return self._read_config_file()
|
|
65
53
|
|
|
66
|
-
def
|
|
54
|
+
def _get_state_file(self) -> StateFile:
|
|
67
55
|
return self._read_state_file()
|
|
68
56
|
|
|
57
|
+
def should_perform_update_check(self) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
Check if we should perform an update check.
|
|
60
|
+
"""
|
|
61
|
+
return self._get_state_file().should_perform_update_check()
|
|
62
|
+
|
|
69
63
|
def set_default_organisation(self, organisation: str) -> None:
|
|
70
64
|
"""
|
|
71
65
|
Set the default organization for the active account
|
|
72
66
|
"""
|
|
73
|
-
config = self.
|
|
67
|
+
config = self._get_cli_config()
|
|
74
68
|
active_account = config.get_active_account()
|
|
75
69
|
if not active_account:
|
|
76
70
|
ErrorPrinter.print_hint("You must have an account activated in order to set default organization")
|
|
@@ -78,39 +72,52 @@ class Settings:
|
|
|
78
72
|
active_account.default_organization = organisation
|
|
79
73
|
self._write_config_file(config)
|
|
80
74
|
|
|
81
|
-
def
|
|
75
|
+
def get_active_account(self) -> Account | None:
|
|
82
76
|
"""
|
|
83
|
-
Get the current active
|
|
77
|
+
Get the current active account
|
|
78
|
+
|
|
79
|
+
TODO: Add email field to Account
|
|
84
80
|
"""
|
|
85
|
-
|
|
86
|
-
return token_file.token
|
|
81
|
+
return self._get_cli_config().get_active_account()
|
|
87
82
|
|
|
88
|
-
def get_active_token_file(self) -> TokenFile:
|
|
83
|
+
def get_active_token_file(self) -> TokenFile | None:
|
|
89
84
|
"""
|
|
90
|
-
Get the current active
|
|
85
|
+
Get the token file for the current active account
|
|
91
86
|
"""
|
|
92
|
-
active_account = self.
|
|
93
|
-
if
|
|
94
|
-
raise TokenNotFoundError("No active account found")
|
|
87
|
+
active_account = self.get_active_account()
|
|
88
|
+
return self._read_token_file(active_account.credentials_file) if active_account else None
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
def get_active_token(self) -> str | None:
|
|
91
|
+
"""
|
|
92
|
+
Get the token secret for the current active account
|
|
93
|
+
"""
|
|
94
|
+
token_file = self.get_active_token_file()
|
|
95
|
+
return token_file.token if token_file else None
|
|
98
96
|
|
|
99
|
-
def activate_token(self, token_file: TokenFile) ->
|
|
97
|
+
def activate_token(self, token_file: TokenFile) -> TokenFile:
|
|
100
98
|
"""
|
|
101
99
|
Activate a token by name or path
|
|
102
100
|
|
|
103
101
|
The token secret will be set as the current active secret.
|
|
102
|
+
|
|
103
|
+
Returns the activated token file
|
|
104
104
|
"""
|
|
105
|
-
config = self.
|
|
105
|
+
config = self._get_cli_config()
|
|
106
106
|
config.activate_account(token_file.account.email)
|
|
107
107
|
self._write_config_file(config)
|
|
108
|
+
return token_file
|
|
109
|
+
|
|
110
|
+
def is_active_account(self, email: str) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Returns True if the given email is the active account
|
|
113
|
+
"""
|
|
114
|
+
return self._get_cli_config().active == email
|
|
108
115
|
|
|
109
|
-
def
|
|
116
|
+
def clear_active_account(self) -> None:
|
|
110
117
|
"""
|
|
111
118
|
Clear the current active token
|
|
112
119
|
"""
|
|
113
|
-
config = self.
|
|
120
|
+
config = self._get_cli_config()
|
|
114
121
|
config.active = None
|
|
115
122
|
self._write_config_file(config)
|
|
116
123
|
|
|
@@ -120,78 +127,56 @@ class Settings:
|
|
|
120
127
|
|
|
121
128
|
If multiple tokens are found, the first one is returned.
|
|
122
129
|
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return tokens[0]
|
|
126
|
-
tokens = [t for t in self.list_service_account_tokens() if t.account is not None and t.account.email == email]
|
|
127
|
-
if len(tokens) > 0:
|
|
128
|
-
return tokens[0]
|
|
129
|
-
return None
|
|
130
|
+
accounts = self._get_cli_config().accounts.get(email)
|
|
131
|
+
return self._read_token_file(accounts.credentials_file) if accounts else None
|
|
130
132
|
|
|
131
|
-
def get_token_file(self, name: str) -> TokenFile:
|
|
133
|
+
def get_token_file(self, name: str) -> TokenFile | None:
|
|
132
134
|
"""
|
|
133
135
|
Get a token file by name or path
|
|
134
136
|
"""
|
|
135
137
|
# 1. Try relative path
|
|
136
138
|
if (self.config_dir / name).exists():
|
|
137
|
-
return self._read_token_file(
|
|
138
|
-
|
|
139
|
-
# 2. Try absolute path
|
|
140
|
-
if Path(name).exists():
|
|
141
|
-
return self._read_token_file(Path(name))
|
|
139
|
+
return self._read_token_file(name)
|
|
142
140
|
|
|
143
|
-
#
|
|
144
|
-
return self._get_token_by_name(name)
|
|
141
|
+
# 2. Try name
|
|
142
|
+
return self._get_token_by_name(name)
|
|
145
143
|
|
|
146
144
|
def remove_token_file(self, name: str) -> None:
|
|
147
145
|
"""
|
|
148
146
|
Remove a token file by name or path
|
|
149
|
-
|
|
150
|
-
TODO: what about manually downloaded tokens?
|
|
151
147
|
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
token_file = self.get_token_file(name)
|
|
149
|
+
if not token_file:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
# If the token is active, clear it first
|
|
153
|
+
email = token_file.account.email
|
|
154
|
+
if self.is_active_account(email):
|
|
155
|
+
self.clear_active_account()
|
|
156
156
|
|
|
157
|
-
#
|
|
158
|
-
path = self.
|
|
159
|
-
|
|
157
|
+
# Remove the token file
|
|
158
|
+
path = self.config_dir / self._get_cli_config().accounts[email].credentials_file
|
|
159
|
+
path.unlink()
|
|
160
|
+
|
|
161
|
+
# Remove the account from the config file
|
|
162
|
+
config = self._get_cli_config()
|
|
163
|
+
config.remove_account(email)
|
|
164
|
+
self._write_config_file(config)
|
|
160
165
|
|
|
161
166
|
def add_personal_token(self, token: str, activate: bool = False, overwrite_if_exists: bool = False) -> TokenFile:
|
|
162
167
|
"""
|
|
163
168
|
Add a personal token
|
|
164
169
|
"""
|
|
165
|
-
|
|
166
|
-
if
|
|
170
|
+
token_file = tf.loads(token)
|
|
171
|
+
if token_file.type != "authorized_user":
|
|
167
172
|
raise ValueError("Token type MUST be authorized_user")
|
|
168
173
|
|
|
169
|
-
|
|
170
|
-
path = self.config_dir / file_name
|
|
171
|
-
if path.exists() and not overwrite_if_exists:
|
|
172
|
-
raise FileExistsError(f"Token file already exists: {path}")
|
|
173
|
-
|
|
174
|
-
self._write_token_file(path, file)
|
|
175
|
-
cli_config = self.get_cli_config()
|
|
176
|
-
cli_config.init_account(email=file.account.email, token_file=file)
|
|
177
|
-
self._write_config_file(cli_config)
|
|
174
|
+
token_file = self.add_token_as_account(token_file, overwrite_if_exists)
|
|
178
175
|
|
|
179
176
|
if activate:
|
|
180
|
-
self.activate_token(
|
|
181
|
-
|
|
182
|
-
return file
|
|
177
|
+
self.activate_token(token_file)
|
|
183
178
|
|
|
184
|
-
|
|
185
|
-
"""
|
|
186
|
-
List all personal tokens
|
|
187
|
-
"""
|
|
188
|
-
return [f[0] for f in self._list_personal_tokens()]
|
|
189
|
-
|
|
190
|
-
def list_personal_token_files(self) -> list[Path]:
|
|
191
|
-
"""
|
|
192
|
-
List paths to all personal token files
|
|
193
|
-
"""
|
|
194
|
-
return [f[1] for f in self._list_personal_tokens()]
|
|
179
|
+
return token_file
|
|
195
180
|
|
|
196
181
|
def add_service_account_token(self, token: str, overwrite_if_exists: bool = False) -> TokenFile:
|
|
197
182
|
"""
|
|
@@ -201,29 +186,74 @@ class Settings:
|
|
|
201
186
|
if token_file.type != "service_account":
|
|
202
187
|
raise ValueError("Token type MUST be service_account")
|
|
203
188
|
|
|
204
|
-
|
|
205
|
-
|
|
189
|
+
return self.add_token_as_account(token_file, overwrite_if_exists)
|
|
190
|
+
|
|
191
|
+
def add_token_as_account(self, token_file: TokenFile, overwrite_if_exists: bool = False) -> TokenFile:
|
|
192
|
+
"""
|
|
193
|
+
Add an account to the config file
|
|
194
|
+
"""
|
|
195
|
+
file_name = token_file.get_token_file_name()
|
|
196
|
+
path = self.config_dir / file_name
|
|
206
197
|
if path.exists() and not overwrite_if_exists:
|
|
207
198
|
raise FileExistsError(f"Token file already exists: {path}")
|
|
208
199
|
|
|
209
200
|
self._write_token_file(path, token_file)
|
|
210
|
-
cli_config = self.
|
|
201
|
+
cli_config = self._get_cli_config()
|
|
211
202
|
cli_config.init_account(email=token_file.account.email, token_file=token_file)
|
|
212
203
|
self._write_config_file(cli_config)
|
|
213
204
|
|
|
214
205
|
return token_file
|
|
215
206
|
|
|
216
|
-
def
|
|
207
|
+
def list_accounts(self) -> dict[str, Account]:
|
|
217
208
|
"""
|
|
218
|
-
List all
|
|
209
|
+
List all accounts
|
|
219
210
|
"""
|
|
220
|
-
return
|
|
211
|
+
return self._get_cli_config().accounts
|
|
221
212
|
|
|
222
|
-
def
|
|
213
|
+
def list_personal_accounts(self) -> dict[str, Account]:
|
|
223
214
|
"""
|
|
224
|
-
List
|
|
215
|
+
List all personal accounts
|
|
216
|
+
|
|
217
|
+
TODO: add account type to Account
|
|
225
218
|
"""
|
|
226
|
-
|
|
219
|
+
accounts = self.list_accounts()
|
|
220
|
+
return {
|
|
221
|
+
email: account
|
|
222
|
+
for email, account in accounts.items()
|
|
223
|
+
if self._read_token_file(account.credentials_file).type == "authorized_user"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
def list_service_accounts(self) -> dict[str, Account]:
|
|
227
|
+
"""
|
|
228
|
+
List all personal accounts
|
|
229
|
+
|
|
230
|
+
TODO: add account type to Account
|
|
231
|
+
"""
|
|
232
|
+
accounts = self.list_accounts()
|
|
233
|
+
return {
|
|
234
|
+
email: account
|
|
235
|
+
for email, account in accounts.items()
|
|
236
|
+
if self._read_token_file(account.credentials_file).type == "service_account"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
def list_token_files(self) -> list[TokenFile]:
|
|
240
|
+
"""
|
|
241
|
+
List all token files
|
|
242
|
+
"""
|
|
243
|
+
accounts = self._get_cli_config().accounts.values()
|
|
244
|
+
return [self._read_token_file(account.credentials_file) for account in accounts]
|
|
245
|
+
|
|
246
|
+
def list_personal_token_files(self) -> list[TokenFile]:
|
|
247
|
+
"""
|
|
248
|
+
List all personal token files
|
|
249
|
+
"""
|
|
250
|
+
return [token_file for token_file in self.list_token_files() if token_file.type == "authorized_user"]
|
|
251
|
+
|
|
252
|
+
def list_service_account_token_files(self) -> list[TokenFile]:
|
|
253
|
+
"""
|
|
254
|
+
List all service account token files
|
|
255
|
+
"""
|
|
256
|
+
return [token_file for token_file in self.list_token_files() if token_file.type == "service_account"]
|
|
227
257
|
|
|
228
258
|
def set_last_update_check_time(self, timestamp: str) -> None:
|
|
229
259
|
"""
|
|
@@ -233,78 +263,46 @@ class Settings:
|
|
|
233
263
|
state.last_update_check_time = timestamp
|
|
234
264
|
self._write_state_file(state)
|
|
235
265
|
|
|
236
|
-
def
|
|
237
|
-
return self._list_token_files(prefix=tf.PERSONAL_TOKEN_FILE_PREFIX)
|
|
238
|
-
|
|
239
|
-
def _list_service_account_tokens(self) -> list[TokenFileMetadata]:
|
|
240
|
-
return self._list_token_files(prefix=tf.SERVICE_ACCOUNT_TOKEN_FILE_PREFIX)
|
|
241
|
-
|
|
242
|
-
def _get_token_by_name(self, name: str) -> TokenFileMetadata:
|
|
243
|
-
token_files = self._list_token_files()
|
|
244
|
-
matches = [token_file for token_file in token_files if token_file[0].name == name]
|
|
245
|
-
if len(matches) != 1:
|
|
246
|
-
raise TokenNotFoundError(f"Ambiguous token file name {name}, found {len(matches)} files")
|
|
247
|
-
return matches[0]
|
|
248
|
-
|
|
249
|
-
def _list_token_files(self, prefix: str = "") -> list[TokenFileMetadata]:
|
|
266
|
+
def _get_token_by_name(self, name: str) -> TokenFile | None:
|
|
250
267
|
"""
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
TODO: improve is_valid_json and is_valid_token_file using token_file parsing instead
|
|
268
|
+
Token name is only available as a property of TokenFile, so we must iterate over all tokens to find the right one
|
|
254
269
|
"""
|
|
270
|
+
token_files = self.list_token_files()
|
|
271
|
+
matches = [token_file for token_file in token_files if token_file.name == name]
|
|
272
|
+
if len(matches) != 1:
|
|
273
|
+
return None
|
|
274
|
+
return matches[0]
|
|
255
275
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
self._read_token_file(path)
|
|
259
|
-
return True
|
|
260
|
-
except (JSONDecodeError, ValidationError):
|
|
261
|
-
# TODO - this should be printed but printing it here causes it to be displayed to many times
|
|
262
|
-
# err_console.print(f"File is not valid json, skipping. {path}")
|
|
263
|
-
return False
|
|
264
|
-
|
|
265
|
-
def is_valid_token_file(path: Path) -> bool:
|
|
266
|
-
is_token_file = path.name.startswith(tf.SERVICE_ACCOUNT_TOKEN_FILE_PREFIX) or path.name.startswith(
|
|
267
|
-
tf.PERSONAL_TOKEN_FILE_PREFIX
|
|
268
|
-
)
|
|
269
|
-
has_correct_prefix = path.is_file() and path.name.startswith(prefix)
|
|
270
|
-
is_cli_config = path == self.config_file_path
|
|
271
|
-
is_present_in_cli_config_accounts = any(
|
|
272
|
-
path.name == account.credentials_file for account in self.get_cli_config().accounts.values()
|
|
273
|
-
)
|
|
274
|
-
return is_token_file and is_valid_json(path) and has_correct_prefix and not is_cli_config and is_present_in_cli_config_accounts
|
|
275
|
-
|
|
276
|
-
paths = [path for path in self.config_dir.iterdir() if is_valid_token_file(path)]
|
|
277
|
-
return [(self._read_token_file(token_file), token_file) for token_file in paths]
|
|
278
|
-
|
|
279
|
-
def _read_token_file(self, path: Path) -> TokenFile:
|
|
276
|
+
def _read_token_file(self, file_name: str) -> TokenFile:
|
|
277
|
+
path = self.config_dir / file_name
|
|
280
278
|
data = self._read_file(path)
|
|
281
279
|
return tf.loads(data)
|
|
282
280
|
|
|
281
|
+
def _write_token_file(self, path: Path, token: TokenFile) -> Path:
|
|
282
|
+
data = tf.dumps(token)
|
|
283
|
+
return self._write_file(path, data)
|
|
284
|
+
|
|
283
285
|
def _read_config_file(self) -> ConfigFile:
|
|
284
286
|
data = self._read_file(self.config_file_path)
|
|
285
287
|
return cf.loads(data)
|
|
286
288
|
|
|
289
|
+
def _write_config_file(self, config: ConfigFile) -> Path:
|
|
290
|
+
data = cf.dumps(config)
|
|
291
|
+
return self._write_file(self.config_file_path, data)
|
|
292
|
+
|
|
287
293
|
def _read_state_file(self) -> StateFile:
|
|
288
294
|
data = self._read_file(self.state_file_path)
|
|
289
295
|
return sf.loads(data)
|
|
290
296
|
|
|
297
|
+
def _write_state_file(self, state: StateFile) -> Path:
|
|
298
|
+
data = sf.dumps(state)
|
|
299
|
+
return self._write_file(self.state_file_path, data)
|
|
300
|
+
|
|
291
301
|
def _read_file(self, path: Path) -> str:
|
|
292
302
|
if not path.exists():
|
|
293
303
|
raise FileNotFoundError(f"File could not be found: {path}")
|
|
294
304
|
return path.read_text(encoding="utf-8")
|
|
295
305
|
|
|
296
|
-
def _write_token_file(self, path: Path, token: TokenFile) -> Path:
|
|
297
|
-
data = tf.dumps(token)
|
|
298
|
-
return self._write_file(path, data)
|
|
299
|
-
|
|
300
|
-
def _write_config_file(self, config: ConfigFile) -> Path:
|
|
301
|
-
data = cf.dumps(config)
|
|
302
|
-
return self._write_file(self.config_file_path, data)
|
|
303
|
-
|
|
304
|
-
def _write_state_file(self, state: StateFile) -> Path:
|
|
305
|
-
data = sf.dumps(state)
|
|
306
|
-
return self._write_file(self.state_file_path, data)
|
|
307
|
-
|
|
308
306
|
def _write_file(self, path: Path, data: str) -> Path:
|
|
309
307
|
if self.config_dir not in path.parents:
|
|
310
308
|
raise InvalidSettingsFilePathError(f"file {path} not in settings dir {self.config_dir}")
|
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Any, Optional
|
|
7
7
|
|
|
8
8
|
from cli.settings.config_file import ConfigFile, loads
|
|
9
|
-
from cli.settings.core import Settings
|
|
9
|
+
from cli.settings.core import Settings
|
|
10
10
|
from cli.settings.migration.migration_tools import get_token_file
|
|
11
11
|
|
|
12
12
|
|
|
@@ -21,13 +21,20 @@ def migrate_account_data(config: dict[str, Any], settings: Settings) -> Optional
|
|
|
21
21
|
cred_name = account_info.pop("credentials_name", None)
|
|
22
22
|
if not cred_name:
|
|
23
23
|
continue
|
|
24
|
+
|
|
25
|
+
# found legacy account, try to migrate it, or drop it...
|
|
24
26
|
found_old = True
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
|
|
28
|
+
token_file = get_token_file(cred_name, settings.config_dir)
|
|
29
|
+
if not token_file:
|
|
30
|
+
sys.stderr.write(f"Dropping account {account_email!r}: credentials file for {cred_name} not found")
|
|
31
|
+
to_delete.append(account_email)
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
cred_file = token_file.get_token_file_name()
|
|
35
|
+
if not cred_file:
|
|
36
|
+
sys.stderr.write(f"Dropping account {account_email!r}: credentials file for {cred_name} not found")
|
|
29
37
|
to_delete.append(account_email)
|
|
30
|
-
sys.stderr.write(f"Dropping account {account_email!r}: token file for {cred_name} not found")
|
|
31
38
|
continue
|
|
32
39
|
|
|
33
40
|
account_info["credentials_file"] = cred_file
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from itertools import chain
|
|
2
4
|
from pathlib import Path
|
|
3
5
|
|
|
4
|
-
from cli.settings.core import TokenNotFoundError
|
|
5
6
|
from cli.settings.token_file import TokenFile
|
|
6
7
|
|
|
7
8
|
|
|
@@ -23,7 +24,7 @@ def list_token_files(config_dir: Path) -> list[TokenFile]:
|
|
|
23
24
|
return token_files
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
def get_token_file(cred_name: str, config_dir: Path) -> TokenFile:
|
|
27
|
+
def get_token_file(cred_name: str, config_dir: Path) -> TokenFile | None:
|
|
27
28
|
"""
|
|
28
29
|
Get the token file for a given credentials name.
|
|
29
30
|
|
|
@@ -32,5 +33,5 @@ def get_token_file(cred_name: str, config_dir: Path) -> TokenFile:
|
|
|
32
33
|
token_files = list_token_files(config_dir)
|
|
33
34
|
matches = [token_file for token_file in token_files if token_file.name == cred_name]
|
|
34
35
|
if len(matches) != 1:
|
|
35
|
-
|
|
36
|
+
return None
|
|
36
37
|
return matches[0]
|
cli/settings/state_file.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime, timedelta
|
|
4
5
|
from typing import Any, Optional
|
|
5
6
|
|
|
6
7
|
from pydantic import BaseModel
|
|
@@ -10,9 +11,7 @@ from cli.utils.time import parse_datetime
|
|
|
10
11
|
|
|
11
12
|
class StateFile(BaseModel):
|
|
12
13
|
"""
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
TODO: Should all setters return a new instance of the StateFile?
|
|
14
|
+
Contains CLI state and other application specific data.
|
|
16
15
|
"""
|
|
17
16
|
|
|
18
17
|
version: str = "1.0"
|
|
@@ -23,8 +22,17 @@ class StateFile(BaseModel):
|
|
|
23
22
|
Check if we should perform an update check.
|
|
24
23
|
|
|
25
24
|
Returns True if the last update check time is older than 2 hours.
|
|
25
|
+
|
|
26
|
+
For Docker environments, returns False and sets a backdated timestamp
|
|
27
|
+
to prevent constant update checks due to ephemeral disks.
|
|
26
28
|
"""
|
|
27
29
|
if not self.last_update_check_time:
|
|
30
|
+
if os.environ.get("RUNS_IN_DOCKER"):
|
|
31
|
+
# To prevent that we always check update in docker due to ephemeral disks we write an "old" check if the state
|
|
32
|
+
# is missing. If no disk is mounted we will never get the update check but if its mounted properly we will get
|
|
33
|
+
# it on the second attempt. This is good enough
|
|
34
|
+
self.last_update_check_time = (datetime.now() - timedelta(hours=10)).isoformat()
|
|
35
|
+
return False
|
|
28
36
|
return True # Returning True will trigger a check, which will properly set last_update_check_time
|
|
29
37
|
|
|
30
38
|
seconds = (datetime.now() - parse_datetime(self.last_update_check_time)).seconds
|
cli/topology/cmd.py
CHANGED
|
@@ -8,7 +8,7 @@ import typer
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
|
|
10
10
|
from cli.errors import ErrorPrinter
|
|
11
|
-
from cli.settings import
|
|
11
|
+
from cli.settings import settings
|
|
12
12
|
from cli.typer import typer_utils
|
|
13
13
|
from cli.utils.rest_helper import RestHelper
|
|
14
14
|
|
|
@@ -64,9 +64,8 @@ def start_trial(
|
|
|
64
64
|
|
|
65
65
|
"""
|
|
66
66
|
RestHelper.use_progress("Checking access tokens...", transient=True)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
except TokenNotFoundError:
|
|
67
|
+
active_token = settings.get_active_token_file()
|
|
68
|
+
if not active_token:
|
|
70
69
|
if len(settings.list_personal_token_files()) == 0:
|
|
71
70
|
console.print(
|
|
72
71
|
"You must first sign in to RemotiveCloud, please use [bold]remotive cloud auth login[/bold] to sign-in"
|
|
@@ -74,7 +73,7 @@ def start_trial(
|
|
|
74
73
|
)
|
|
75
74
|
else:
|
|
76
75
|
console.print(
|
|
77
|
-
"You have not
|
|
76
|
+
"You have not actived your account, please run [bold]remotive cloud auth activate[/bold] to choose an account"
|
|
78
77
|
"or [bold]remotive cloud auth login[/bold] to sign-in"
|
|
79
78
|
)
|
|
80
79
|
return
|
|
@@ -84,7 +83,7 @@ def start_trial(
|
|
|
84
83
|
ErrorPrinter.print_generic_message("Your current active credentials are not valid")
|
|
85
84
|
raise typer.Exit(1)
|
|
86
85
|
|
|
87
|
-
active_account = settings.
|
|
86
|
+
active_account = settings.get_active_account()
|
|
88
87
|
if active_account and not organization and not active_account.default_organization:
|
|
89
88
|
ErrorPrinter.print_hint("You have not specified any organization and no default organization is set")
|
|
90
89
|
raise typer.Exit(1)
|