remotivelabs-cli 0.2.2__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 +35 -247
- cli/cloud/licenses/__init__.py +0 -0
- cli/cloud/licenses/cmd.py +14 -0
- cli/cloud/organisations.py +8 -12
- cli/cloud/service_account_tokens.py +1 -1
- cli/connect/protopie/protopie.py +1 -1
- cli/remotive.py +17 -26
- cli/settings/__init__.py +1 -2
- cli/settings/config_file.py +72 -52
- cli/settings/core.py +173 -168
- cli/settings/migration/migrate_config_file.py +15 -10
- cli/settings/migration/migration_tools.py +4 -3
- cli/settings/state_file.py +56 -21
- cli/settings/token_file.py +13 -21
- cli/topology/cmd.py +6 -6
- cli/utils/rest_helper.py +20 -28
- cli/utils/{version_check.py → versions.py} +30 -20
- {remotivelabs_cli-0.2.2.dist-info → remotivelabs_cli-0.3.0.dist-info}/METADATA +3 -3
- {remotivelabs_cli-0.2.2.dist-info → remotivelabs_cli-0.3.0.dist-info}/RECORD +29 -25
- cli/cloud/cloud_cli.py +0 -29
- {remotivelabs_cli-0.2.2.dist-info → remotivelabs_cli-0.3.0.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.2.2.dist-info → remotivelabs_cli-0.3.0.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.2.2.dist-info → remotivelabs_cli-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|
cli/remotive.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from importlib.metadata import version
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
from rich import print as rich_print
|
|
@@ -10,17 +9,16 @@ from trogon import Trogon
|
|
|
10
9
|
from typer.main import get_group
|
|
11
10
|
|
|
12
11
|
from cli.broker.brokers import app as broker_app
|
|
13
|
-
from cli.cloud
|
|
12
|
+
from cli.cloud import app as cloud_app
|
|
14
13
|
from cli.connect.connect import app as connect_app
|
|
15
|
-
from cli.settings import settings
|
|
16
|
-
from cli.settings.core import Settings
|
|
14
|
+
from cli.settings import Settings, settings
|
|
17
15
|
from cli.settings.migration.migrate_all_token_files import migrate_any_legacy_tokens
|
|
18
16
|
from cli.settings.migration.migrate_config_file import migrate_config_file
|
|
19
17
|
from cli.settings.migration.migrate_legacy_dirs import migrate_legacy_settings_dirs
|
|
20
18
|
from cli.tools.tools import app as tools_app
|
|
21
19
|
from cli.topology.cmd import app as topology_app
|
|
22
20
|
from cli.typer import typer_utils
|
|
23
|
-
from cli.utils
|
|
21
|
+
from cli.utils import versions
|
|
24
22
|
|
|
25
23
|
err_console = Console(stderr=True)
|
|
26
24
|
|
|
@@ -45,8 +43,7 @@ For documentation - https://docs.remotivelabs.com
|
|
|
45
43
|
|
|
46
44
|
def version_callback(value: bool) -> None:
|
|
47
45
|
if value:
|
|
48
|
-
|
|
49
|
-
typer.echo(my_version)
|
|
46
|
+
typer.echo(f"remotivelabs-cli {versions.cli_version()} ({versions.platform_info()})")
|
|
50
47
|
|
|
51
48
|
|
|
52
49
|
def test_callback(value: int) -> None:
|
|
@@ -56,7 +53,7 @@ def test_callback(value: int) -> None:
|
|
|
56
53
|
|
|
57
54
|
|
|
58
55
|
def check_for_newer_version(settings: Settings) -> None:
|
|
59
|
-
check_for_update(
|
|
56
|
+
versions.check_for_update(settings)
|
|
60
57
|
|
|
61
58
|
|
|
62
59
|
def run_migrations(settings: Settings) -> None:
|
|
@@ -84,9 +81,9 @@ def set_default_org_as_env(settings: Settings) -> None:
|
|
|
84
81
|
This has to be done early before it is read
|
|
85
82
|
"""
|
|
86
83
|
if "REMOTIVE_CLOUD_ORGANIZATION" not in os.environ:
|
|
87
|
-
|
|
88
|
-
if
|
|
89
|
-
os.environ["REMOTIVE_CLOUD_ORGANIZATION"] =
|
|
84
|
+
active_account = settings.get_active_account()
|
|
85
|
+
if active_account and active_account.default_organization:
|
|
86
|
+
os.environ["REMOTIVE_CLOUD_ORGANIZATION"] = active_account.default_organization
|
|
90
87
|
|
|
91
88
|
|
|
92
89
|
@app.callback()
|
|
@@ -115,21 +112,15 @@ def tui(ctx: typer.Context) -> None:
|
|
|
115
112
|
|
|
116
113
|
|
|
117
114
|
app.add_typer(broker_app, name="broker", help="Manage a single broker - local or cloud")
|
|
118
|
-
app.add_typer(
|
|
119
|
-
cloud_app,
|
|
120
|
-
name="cloud",
|
|
121
|
-
help="Manage resources in RemotiveCloud",
|
|
122
|
-
)
|
|
115
|
+
app.add_typer(cloud_app, name="cloud", help="Manage resources in RemotiveCloud")
|
|
123
116
|
app.add_typer(connect_app, name="connect", help="Integrations with other systems")
|
|
124
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
|
|
125
123
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
name="topology",
|
|
130
|
-
help="""
|
|
131
|
-
RemotiveTopology actions
|
|
132
|
-
|
|
133
|
-
Read more at https://docs.remotivelabs.com/docs/remotive-topology
|
|
134
|
-
""",
|
|
135
|
-
)
|
|
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
|
@@ -1,42 +1,48 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import dataclasses
|
|
4
|
-
import json
|
|
5
|
-
from dataclasses import dataclass
|
|
6
3
|
from typing import Any, Optional
|
|
7
4
|
|
|
8
|
-
from
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
9
6
|
|
|
10
7
|
from cli.settings.token_file import TokenFile
|
|
11
8
|
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def dumps(config: ConfigFile) -> str:
|
|
19
|
-
return json.dumps(dataclasses.asdict(config), default=str)
|
|
10
|
+
class Account(BaseModel):
|
|
11
|
+
"""
|
|
12
|
+
Account represents an account in the configuration file.
|
|
20
13
|
|
|
14
|
+
TODO: Add email field to Account
|
|
15
|
+
"""
|
|
21
16
|
|
|
22
|
-
@dataclass
|
|
23
|
-
class Account:
|
|
24
17
|
credentials_file: str
|
|
25
18
|
default_organization: Optional[str] = None
|
|
26
|
-
# Add project as well
|
|
27
19
|
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
class ConfigFile(BaseModel):
|
|
22
|
+
"""
|
|
23
|
+
ConfigFile represents the configuration file for the CLI.
|
|
24
|
+
|
|
25
|
+
TODO: Should all setters return a new instance of the ConfigFile?
|
|
26
|
+
"""
|
|
27
|
+
|
|
31
28
|
version: str = "1.0"
|
|
32
29
|
active: Optional[str] = None
|
|
33
|
-
accounts: dict[str, Account] =
|
|
30
|
+
accounts: dict[str, Account] = Field(default_factory=dict)
|
|
31
|
+
|
|
32
|
+
@model_validator(mode="before")
|
|
33
|
+
@classmethod
|
|
34
|
+
def _validate_json_data(cls, json_data: Any) -> Any:
|
|
35
|
+
"""Try to migrate old formats and missing fields as best we can."""
|
|
36
|
+
if not isinstance(json_data, dict):
|
|
37
|
+
return json_data
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
# If the active account is not in accounts, remove it
|
|
40
|
+
if "active" in json_data and json_data["active"] not in json_data["accounts"]:
|
|
41
|
+
del json_data["active"]
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
return json_data
|
|
44
|
+
|
|
45
|
+
def get_active_account(self) -> Optional[Account]:
|
|
40
46
|
if not self.active:
|
|
41
47
|
return None
|
|
42
48
|
account = self.get_account(self.active)
|
|
@@ -44,50 +50,64 @@ class ConfigFile:
|
|
|
44
50
|
raise KeyError(f"Activated account {self.active} is not a valid account")
|
|
45
51
|
return account
|
|
46
52
|
|
|
47
|
-
def
|
|
53
|
+
def activate_account(self, email: str) -> None:
|
|
48
54
|
account = self.get_account(email)
|
|
49
55
|
if not account:
|
|
50
56
|
raise KeyError(f"Account {email} does not exists")
|
|
51
57
|
self.active = email
|
|
52
58
|
|
|
59
|
+
def _update_account(self, email: str, **updates: Any) -> None:
|
|
60
|
+
"""TODO: Consider using model_copy and always return a new instance of ConfigFile"""
|
|
61
|
+
existing_account = self.get_account(email)
|
|
62
|
+
if existing_account:
|
|
63
|
+
updated_account = existing_account.model_copy(update=updates)
|
|
64
|
+
else:
|
|
65
|
+
updated_account = Account(**updates)
|
|
66
|
+
|
|
67
|
+
new_accounts = {**self.accounts, email: updated_account}
|
|
68
|
+
self.accounts = new_accounts
|
|
69
|
+
|
|
70
|
+
def init_account(self, email: str, token_file: TokenFile) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Create a new account with the given email and token file.
|
|
73
|
+
"""
|
|
74
|
+
self._update_account(email, credentials_file=token_file.get_token_file_name())
|
|
75
|
+
|
|
76
|
+
def set_default_organization_for_account(self, email: str, default_organization: Optional[str] = None) -> None:
|
|
77
|
+
if not self.get_account(email):
|
|
78
|
+
raise KeyError(f"Account with email {email} has not been initialized with token")
|
|
79
|
+
self._update_account(email, default_organization=default_organization)
|
|
80
|
+
|
|
53
81
|
def get_account(self, email: str) -> Optional[Account]:
|
|
54
|
-
|
|
55
|
-
return None
|
|
56
|
-
return self.accounts.get(email, None)
|
|
82
|
+
return self.accounts.get(email)
|
|
57
83
|
|
|
58
84
|
def remove_account(self, email: str) -> None:
|
|
59
|
-
|
|
60
|
-
self.accounts.pop(email, None)
|
|
85
|
+
self.accounts.pop(email, None)
|
|
61
86
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_json_str(cls, data: str) -> ConfigFile:
|
|
89
|
+
return cls.model_validate_json(data)
|
|
65
90
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
else:
|
|
70
|
-
account.credentials_file = token_file.get_token_file_name()
|
|
71
|
-
self.accounts[email] = account
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_dict(cls, data: dict[str, Any]) -> ConfigFile:
|
|
93
|
+
return cls.model_validate(data)
|
|
72
94
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
self.accounts = {}
|
|
95
|
+
def to_json_str(self) -> str:
|
|
96
|
+
return self.model_dump_json()
|
|
76
97
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
raise KeyError(f"Account with email {email} has not been initialized with token")
|
|
98
|
+
def to_dict(self) -> dict[str, Any]:
|
|
99
|
+
return self.model_dump()
|
|
80
100
|
|
|
81
|
-
# Update only fields explicitly passed
|
|
82
|
-
if default_organization is not None:
|
|
83
|
-
account.default_organization = default_organization
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
def loads(data: str) -> ConfigFile:
|
|
103
|
+
"""
|
|
104
|
+
Creates a ConfigFile from a JSON string.
|
|
105
|
+
"""
|
|
106
|
+
return ConfigFile.from_json_str(data)
|
|
86
107
|
|
|
87
|
-
@staticmethod
|
|
88
|
-
def from_json_str(data: str) -> ConfigFile:
|
|
89
|
-
return loads(data)
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
109
|
+
def dumps(config: ConfigFile) -> str:
|
|
110
|
+
"""
|
|
111
|
+
Returns the JSON string representation of the ConfigFile.
|
|
112
|
+
"""
|
|
113
|
+
return config.to_json_str()
|