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/topology/cmd.py
CHANGED
|
@@ -1,102 +1,79 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import dataclasses
|
|
4
|
-
import datetime
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
3
|
import typer
|
|
8
|
-
from rich.console import Console
|
|
9
4
|
|
|
10
|
-
from cli.
|
|
11
|
-
|
|
5
|
+
from cli.topology.start_trial import (
|
|
6
|
+
MissingOrganizationError,
|
|
7
|
+
NoActiveAccountError,
|
|
8
|
+
NotAuthorizedError,
|
|
9
|
+
NotAuthorizedToStartTrialError,
|
|
10
|
+
NotSignedInError,
|
|
11
|
+
SubscriptionExpiredError,
|
|
12
|
+
start_trial,
|
|
13
|
+
)
|
|
12
14
|
from cli.typer import typer_utils
|
|
13
|
-
from cli.utils.
|
|
15
|
+
from cli.utils.console import print_generic_error, print_generic_message, print_hint
|
|
14
16
|
|
|
15
17
|
HELP = """
|
|
16
|
-
RemotiveTopology
|
|
18
|
+
Manage RemotiveTopology resources
|
|
17
19
|
"""
|
|
18
|
-
console = Console()
|
|
19
|
-
app = typer_utils.create_typer(help=HELP)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@dataclasses.dataclass
|
|
23
|
-
class Subscription:
|
|
24
|
-
type: str
|
|
25
|
-
display_name: str
|
|
26
|
-
feature: str
|
|
27
|
-
start_date: str # TODO: add datetime
|
|
28
|
-
end_date: str # TODO: add datetime
|
|
29
|
-
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
subscription_type = subscription_info.get("subscriptionType")
|
|
33
|
-
end_date_str = subscription_info.get("endDate")
|
|
34
|
-
now = datetime.datetime.now()
|
|
35
|
-
|
|
36
|
-
def parse_date(date_str: str | None) -> datetime.datetime | None:
|
|
37
|
-
return datetime.datetime.fromisoformat(date_str) if date_str else None
|
|
38
|
-
|
|
39
|
-
expires = parse_date(end_date_str)
|
|
40
|
-
|
|
41
|
-
if subscription_type == "trial":
|
|
42
|
-
if expires and expires < now:
|
|
43
|
-
console.print(f"Your Topology trial expired {end_date_str}, please contact support@remotivelabs.com")
|
|
44
|
-
else:
|
|
45
|
-
console.print(f"You already have an active topology trial, it expires {end_date_str}")
|
|
46
|
-
|
|
47
|
-
elif subscription_type == "paid":
|
|
48
|
-
if expires and expires < now:
|
|
49
|
-
console.print(f"Topology subscription has ended, expired {end_date_str}")
|
|
50
|
-
else:
|
|
51
|
-
console.print(f"You already have an active topology subscription, it expires {end_date_str or 'Never'}")
|
|
52
|
-
|
|
53
|
-
else:
|
|
54
|
-
ErrorPrinter.print_generic_error("Unexpected exception, please contact support@remotivelabs.com")
|
|
55
|
-
raise typer.Exit(1)
|
|
21
|
+
app = typer_utils.create_typer(rich_markup_mode="rich", help=HELP)
|
|
56
22
|
|
|
57
23
|
|
|
58
24
|
@app.command("start-trial")
|
|
59
|
-
def
|
|
25
|
+
def start_trial_cmd( # noqa: C901
|
|
60
26
|
organization: str = typer.Option(None, help="Organization to start trial for", envvar="REMOTIVE_CLOUD_ORGANIZATION"),
|
|
61
27
|
) -> None:
|
|
62
28
|
"""
|
|
63
|
-
Allows you ta start a 30 day trial subscription for running RemotiveTopology
|
|
29
|
+
Allows you ta start a 30 day trial subscription for running RemotiveTopology.
|
|
64
30
|
|
|
31
|
+
You can read more at https://docs.remotivelabs.com/docs/remotive-topology.
|
|
65
32
|
"""
|
|
66
|
-
RestHelper.use_progress("Checking access tokens...", transient=True)
|
|
67
33
|
try:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
34
|
+
subscription = start_trial(organization)
|
|
35
|
+
if subscription.type == "trial":
|
|
36
|
+
print_generic_message(f"topology trial active, expires {subscription.end_date}")
|
|
37
|
+
elif subscription.type == "paid":
|
|
38
|
+
print_generic_message(f"you already have a topology subscription, expires {subscription.end_date or 'Never'}")
|
|
39
|
+
|
|
40
|
+
except NotSignedInError:
|
|
41
|
+
print_generic_error(
|
|
42
|
+
"You must first sign in to RemotiveCloud, please use [bold]remotive cloud auth login[/bold] to sign-in"
|
|
43
|
+
"This requires a RemotiveCloud account, if you do not have an account you can sign-up at https://cloud.remotivelabs.com"
|
|
44
|
+
)
|
|
45
|
+
raise typer.Exit(2)
|
|
46
|
+
|
|
47
|
+
except NoActiveAccountError:
|
|
48
|
+
print_hint(
|
|
49
|
+
"You have not actived your account, please run [bold]remotive cloud auth activate[/bold] to choose an account"
|
|
50
|
+
"or [bold]remotive cloud auth login[/bold] to sign-in"
|
|
51
|
+
)
|
|
52
|
+
raise typer.Exit(3)
|
|
53
|
+
|
|
54
|
+
except NotAuthorizedError:
|
|
55
|
+
print_hint(
|
|
56
|
+
"Your current active credentials are not valid, please run [bold]remotive cloud auth login[/bold] to sign-in again."
|
|
57
|
+
"This requires a RemotiveCloud account, if you do not have an account you can sign-up at https://cloud.remotivelabs.com"
|
|
58
|
+
)
|
|
59
|
+
raise typer.Exit(4)
|
|
60
|
+
|
|
61
|
+
except MissingOrganizationError:
|
|
62
|
+
print_hint("You have not specified any organization and no default organization is set")
|
|
63
|
+
raise typer.Exit(5)
|
|
64
|
+
|
|
65
|
+
except NotAuthorizedToStartTrialError as e:
|
|
66
|
+
print_generic_error(f"You are not allowed to start-trial topology in organization {e.organization}")
|
|
67
|
+
raise typer.Exit(6)
|
|
68
|
+
|
|
69
|
+
except SubscriptionExpiredError as e:
|
|
70
|
+
if e.subscription.type == "trial":
|
|
71
|
+
print_generic_error(f"Your Topology trial expired {e.subscription.end_date}, please contact support@remotivelabs.com")
|
|
72
|
+
raise typer.Exit(7)
|
|
73
|
+
|
|
74
|
+
print_generic_error(f"Your Topology subscription has expired {e.subscription.end_date}, please contact support@remotivelabs.com")
|
|
75
|
+
raise typer.Exit(7)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print_generic_error(f"Unexpected error: {e}")
|
|
98
79
|
raise typer.Exit(1)
|
|
99
|
-
else:
|
|
100
|
-
subscription_info = sub.json()
|
|
101
|
-
_print_current_subscription(subscription_info)
|
|
102
|
-
return
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import date
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from cli.settings import settings
|
|
9
|
+
from cli.settings.config_file import Account
|
|
10
|
+
from cli.utils.rest_helper import RestHelper
|
|
11
|
+
from cli.utils.time import parse_date
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NoActiveAccountError(Exception):
|
|
15
|
+
"""Raised when the user has no active account, but there are available accounts to activate"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NotSignedInError(Exception):
|
|
19
|
+
"""Raised when the user has no active account, and no available accounts to activate"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NotAuthorizedError(Exception):
|
|
23
|
+
"""Raised when the user is not authorized"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, account: Account):
|
|
26
|
+
self.account = account
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MissingOrganizationError(Exception):
|
|
30
|
+
"""Raised when the user has not specified an organization and no default organization is set"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class NotAuthorizedToStartTrialError(Exception):
|
|
34
|
+
"""Raised when the user is not authorized to start a topology trial"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, account: Account, organization: str):
|
|
37
|
+
self.account = account
|
|
38
|
+
self.organization = organization
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SubscriptionExpiredError(Exception):
|
|
42
|
+
"""Raised when the subscription has expired"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, subscription: Subscription):
|
|
45
|
+
self.subscription = subscription
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Subscription:
|
|
50
|
+
type: str
|
|
51
|
+
display_name: str
|
|
52
|
+
feature: str
|
|
53
|
+
start_date: date
|
|
54
|
+
end_date: date
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def from_dict(data: Any) -> Subscription:
|
|
58
|
+
if not isinstance(data, dict):
|
|
59
|
+
raise ValueError(f"Invalid subscription data {data}")
|
|
60
|
+
|
|
61
|
+
return Subscription(
|
|
62
|
+
type=data["subscriptionType"],
|
|
63
|
+
display_name=data["displayName"],
|
|
64
|
+
feature=data["feature"],
|
|
65
|
+
start_date=parse_date(data["startDate"]),
|
|
66
|
+
end_date=parse_date(data["endDate"]),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def start_trial(organization: str | None = None) -> Subscription:
|
|
71
|
+
"""
|
|
72
|
+
Start a 30 day trial subscription for running RemotiveTopology.
|
|
73
|
+
|
|
74
|
+
# TODO: move authentication (and basic authorization) to a logged_in decorator
|
|
75
|
+
"""
|
|
76
|
+
active_account = settings.get_active_account()
|
|
77
|
+
active_token_file = settings.get_active_token_file()
|
|
78
|
+
|
|
79
|
+
if not active_account or not active_token_file:
|
|
80
|
+
if len(settings.list_accounts()) == 0:
|
|
81
|
+
raise NotSignedInError()
|
|
82
|
+
raise NoActiveAccountError()
|
|
83
|
+
|
|
84
|
+
if not RestHelper.has_access("/api/whoami"):
|
|
85
|
+
raise NotAuthorizedError(account=active_account)
|
|
86
|
+
|
|
87
|
+
valid_org = organization or active_account.default_organization
|
|
88
|
+
if not valid_org:
|
|
89
|
+
raise MissingOrganizationError()
|
|
90
|
+
|
|
91
|
+
res = RestHelper.handle_get(f"/api/bu/{valid_org}/features/topology", return_response=True, allow_status_codes=[403, 404])
|
|
92
|
+
if res.status_code == 403:
|
|
93
|
+
raise NotAuthorizedToStartTrialError(account=active_account, organization=valid_org)
|
|
94
|
+
if res.status_code == 404:
|
|
95
|
+
created = RestHelper.handle_post(f"/api/bu/{valid_org}/features/topology", return_response=True)
|
|
96
|
+
subscription = Subscription.from_dict(created.json())
|
|
97
|
+
else:
|
|
98
|
+
# 200 OK means we already have a valid subscription
|
|
99
|
+
subscription = Subscription.from_dict(res.json())
|
|
100
|
+
|
|
101
|
+
# check subscription validity
|
|
102
|
+
if subscription.end_date < datetime.datetime.now().date():
|
|
103
|
+
raise SubscriptionExpiredError(subscription=subscription)
|
|
104
|
+
|
|
105
|
+
return subscription
|
cli/typer/typer_utils.py
CHANGED
|
@@ -2,24 +2,21 @@ from typing import Any
|
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
4
|
from click import Context
|
|
5
|
-
from rich.console import Console
|
|
6
5
|
from typer.core import TyperGroup
|
|
7
6
|
|
|
7
|
+
from cli.utils.console import print_generic_message
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
class OrderCommands(TyperGroup):
|
|
10
11
|
def list_commands(self, _ctx: Context): # type: ignore
|
|
11
12
|
return list(self.commands)
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
console = Console()
|
|
15
|
-
|
|
16
|
-
|
|
17
15
|
def create_typer(**kwargs: Any) -> typer.Typer:
|
|
18
16
|
"""Create a Typer instance with default settings."""
|
|
19
|
-
# return typer.Typer(no_args_is_help=True, **kwargs)
|
|
20
17
|
return typer.Typer(cls=OrderCommands, no_args_is_help=True, invoke_without_command=True, **kwargs)
|
|
21
18
|
|
|
22
19
|
|
|
23
20
|
def print_padded(label: str, right_text: str, length: int = 30) -> None:
|
|
24
21
|
padded_label = label.ljust(length) # pad to 30 characters
|
|
25
|
-
|
|
22
|
+
print_generic_message(f"{padded_label} {right_text}")
|
cli/utils/console.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import grpc
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
err_console = Console(stderr=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def print_grpc_error(error: grpc.RpcError) -> None:
|
|
15
|
+
if error.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
16
|
+
is_access_token = os.environ["ACCESS_TOKEN"]
|
|
17
|
+
if is_access_token is not None and is_access_token == "true":
|
|
18
|
+
err_console.print(f":boom: [bold red]Authentication failed[/bold red]: {error.details()}")
|
|
19
|
+
err_console.print("Please login again")
|
|
20
|
+
else:
|
|
21
|
+
err_console.print(":boom: [bold red]Authentication failed[/bold red]")
|
|
22
|
+
err_console.print("Failed to verify api-key")
|
|
23
|
+
else:
|
|
24
|
+
err_console.print(f":boom: [bold red]Unexpected error, status code[/bold red]: {error.code()}")
|
|
25
|
+
err_console.print(error.details())
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def print_hint(message: str) -> None:
|
|
30
|
+
err_console.print(f":point_right: [bold]{message}[/bold]")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def print_generic_error(message: str) -> None:
|
|
34
|
+
err_console.print(f":boom: [bold red]Failed[/bold red]: {message}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def print_success(message: str) -> None:
|
|
38
|
+
console.print(f"[bold green]Success![/bold green] {message}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def print_generic_message(message: str) -> None:
|
|
42
|
+
console.print(f"[bold]{message}[/bold]")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def print_newline() -> None:
|
|
46
|
+
"""TODO: is this needed?"""
|
|
47
|
+
console.print("\n")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def print_url(url: str) -> None:
|
|
51
|
+
console.print(url, style="bold", soft_wrap=True)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def print_unformatted(message: Any) -> None:
|
|
55
|
+
"""TODO: should we allow this?"""
|
|
56
|
+
console.print(message)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def print_unformatted_to_stderr(message: Any) -> None:
|
|
60
|
+
"""TODO: should we allow this?"""
|
|
61
|
+
err_console.print(message)
|
cli/utils/rest_helper.py
CHANGED
|
@@ -12,14 +12,11 @@ from typing import Any, BinaryIO, Dict, List, Optional, Union, cast
|
|
|
12
12
|
|
|
13
13
|
import requests
|
|
14
14
|
from requests.exceptions import JSONDecodeError
|
|
15
|
-
from rich.console import Console
|
|
16
15
|
from rich.progress import Progress, SpinnerColumn, TextColumn, wrap_file
|
|
17
16
|
|
|
18
|
-
from cli.
|
|
19
|
-
from cli.settings import TokenNotFoundError, settings
|
|
17
|
+
from cli.settings import settings
|
|
20
18
|
from cli.utils import versions
|
|
21
|
-
|
|
22
|
-
err_console = Console(stderr=True)
|
|
19
|
+
from cli.utils.console import print_generic_error, print_generic_message, print_hint, print_unformatted, print_unformatted_to_stderr
|
|
23
20
|
|
|
24
21
|
if "REMOTIVE_CLOUD_HTTP_LOGGING" in os.environ:
|
|
25
22
|
logging.basicConfig()
|
|
@@ -84,29 +81,27 @@ class RestHelper:
|
|
|
84
81
|
|
|
85
82
|
@staticmethod
|
|
86
83
|
def ensure_auth_token(quiet: bool = False, access_token: Optional[str] = None) -> None:
|
|
87
|
-
|
|
84
|
+
"""
|
|
85
|
+
TODO: remove setting org, as we already set the default organization as env in remotive.py?
|
|
86
|
+
TODO: don't sys.exit, raise error instead
|
|
87
|
+
"""
|
|
88
88
|
if "REMOTIVE_CLOUD_ORGANIZATION" not in os.environ:
|
|
89
|
-
active_account = settings.
|
|
89
|
+
active_account = settings.get_active_account()
|
|
90
90
|
if active_account:
|
|
91
91
|
org = active_account.default_organization
|
|
92
92
|
if org:
|
|
93
93
|
os.environ["REMOTIVE_CLOUD_ORGANIZATION"] = org
|
|
94
94
|
|
|
95
|
-
token =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
except TokenNotFoundError:
|
|
104
|
-
if quiet:
|
|
105
|
-
return
|
|
106
|
-
ErrorPrinter.print_hint("you are not logged in, please login using [green]remotive cloud auth login[/green]")
|
|
107
|
-
sys.exit(1)
|
|
95
|
+
token = access_token
|
|
96
|
+
if not token:
|
|
97
|
+
token = os.environ.get("REMOTIVE_CLOUD_ACCESS_TOKEN", settings.get_active_token())
|
|
98
|
+
if not token:
|
|
99
|
+
if quiet:
|
|
100
|
+
return
|
|
101
|
+
print_hint("you are not logged in, please login using [green]remotive cloud auth login[/green]")
|
|
102
|
+
sys.exit(1)
|
|
108
103
|
|
|
109
|
-
RestHelper.__headers["Authorization"] = f"Bearer {token.strip()
|
|
104
|
+
RestHelper.__headers["Authorization"] = f"Bearer {token.strip()}"
|
|
110
105
|
|
|
111
106
|
@staticmethod
|
|
112
107
|
def handle_get( # noqa: PLR0913
|
|
@@ -160,38 +155,45 @@ class RestHelper:
|
|
|
160
155
|
|
|
161
156
|
@staticmethod
|
|
162
157
|
def check_api_result(response: requests.Response, allow_status_codes: List[int] | None = None) -> None:
|
|
158
|
+
"""
|
|
159
|
+
TODO: don't sys.exit, raise error instead
|
|
160
|
+
"""
|
|
163
161
|
if response.status_code == 426: # CLI upgrade
|
|
164
|
-
|
|
162
|
+
print_hint(response.text)
|
|
165
163
|
sys.exit(1)
|
|
166
164
|
if response.status_code > 299:
|
|
167
165
|
if allow_status_codes is not None and response.status_code in allow_status_codes:
|
|
168
166
|
return
|
|
169
|
-
|
|
167
|
+
print_generic_error(f"Got status code: {response.status_code}")
|
|
170
168
|
if response.status_code == 401:
|
|
171
|
-
|
|
169
|
+
print_generic_message("Your token is not valid or has expired, please login again or activate another account")
|
|
172
170
|
else:
|
|
173
|
-
|
|
171
|
+
print_unformatted_to_stderr(response.text)
|
|
174
172
|
sys.exit(1)
|
|
175
173
|
|
|
176
174
|
@staticmethod
|
|
177
175
|
def print_api_result(response: requests.Response) -> None:
|
|
176
|
+
"""
|
|
177
|
+
TODO: don't sys.exit, raise error instead
|
|
178
|
+
TODO: dont print from here, return and let caller print instead
|
|
179
|
+
"""
|
|
178
180
|
if response.status_code == 426: # CLI upgrade
|
|
179
|
-
|
|
181
|
+
print_hint(response.text)
|
|
180
182
|
sys.exit(1)
|
|
181
183
|
|
|
182
184
|
if response.status_code >= 200 and response.status_code < 300:
|
|
183
185
|
if len(response.content) >= 2:
|
|
184
186
|
try:
|
|
185
|
-
|
|
187
|
+
print_unformatted(json.dumps(response.json()))
|
|
186
188
|
except JSONDecodeError:
|
|
187
|
-
|
|
189
|
+
print_generic_error("Json parse error: Please try again and report if the error persists")
|
|
188
190
|
sys.exit(0)
|
|
189
191
|
else:
|
|
190
|
-
|
|
192
|
+
print_generic_error(f"Got status code: {response.status_code}")
|
|
191
193
|
if response.status_code == 401:
|
|
192
|
-
|
|
194
|
+
print_generic_message("Your token is not valid or has expired, please login again or activate another account")
|
|
193
195
|
else:
|
|
194
|
-
|
|
196
|
+
print_unformatted_to_stderr(response.text)
|
|
195
197
|
sys.exit(1)
|
|
196
198
|
|
|
197
199
|
@staticmethod
|
cli/utils/versions.py
CHANGED
|
@@ -5,14 +5,13 @@ import json
|
|
|
5
5
|
import os
|
|
6
6
|
import platform
|
|
7
7
|
import urllib.request
|
|
8
|
-
from datetime import timedelta
|
|
9
8
|
from importlib import metadata as importlib_metadata
|
|
10
9
|
from importlib.metadata import version as python_project_version
|
|
11
10
|
|
|
12
11
|
from packaging.version import InvalidVersion, Version
|
|
13
12
|
|
|
14
|
-
from cli.errors import ErrorPrinter
|
|
15
13
|
from cli.settings import Settings
|
|
14
|
+
from cli.utils.console import print_hint
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
def cli_version() -> str:
|
|
@@ -85,26 +84,17 @@ def check_for_update(settings: Settings) -> None:
|
|
|
85
84
|
# Make it possible to disable update check, i.e in CI
|
|
86
85
|
if os.environ.get("PYTHON_DISABLE_UPDATE_CHECK"):
|
|
87
86
|
return
|
|
88
|
-
|
|
87
|
+
|
|
88
|
+
# Check if we are allowed to perform an update check
|
|
89
|
+
if not settings.should_perform_update_check():
|
|
90
|
+
return
|
|
89
91
|
|
|
90
92
|
# Determine current version
|
|
93
|
+
project = "remotivelabs-cli"
|
|
91
94
|
cur = cli_version() or _installed_version(project)
|
|
92
95
|
if not cur:
|
|
93
96
|
return # unknown version → skip silently
|
|
94
97
|
|
|
95
|
-
state = settings.get_state_file()
|
|
96
|
-
if not state.last_update_check_time:
|
|
97
|
-
if os.environ.get("RUNS_IN_DOCKER"):
|
|
98
|
-
# To prevent that we always check update in docker due to ephemeral disks we write an "old" check if the state
|
|
99
|
-
# is missing. If no disk is mounted we will never get the update check but if its mounted properly we will get
|
|
100
|
-
# it on the second attempt. This is good enough
|
|
101
|
-
last_update_check_time = (datetime.datetime.now() - timedelta(hours=10)).isoformat()
|
|
102
|
-
settings.set_last_update_check_time(last_update_check_time)
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
elif not state.should_perform_update_check():
|
|
106
|
-
return
|
|
107
|
-
|
|
108
98
|
# We end up here if last_update_check_time is None or should_perform_update_check is true
|
|
109
99
|
include_prereleases = Version(cur).is_prerelease or Version(cur).is_devrelease
|
|
110
100
|
|
|
@@ -127,6 +117,4 @@ def _print_update_info(cur: str, latest: str) -> None:
|
|
|
127
117
|
else "upgrade with: pipx install -U remotivelabs-cli"
|
|
128
118
|
)
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
f"Update available: remotivelabs-cli {cur} → {latest} , ({instructions}) we always recommend to use latest version"
|
|
132
|
-
)
|
|
120
|
+
print_hint(f"Update available: remotivelabs-cli {cur} → {latest} , ({instructions}) we always recommend to use latest version")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: remotivelabs-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: CLI for operating RemotiveCloud and RemotiveBroker
|
|
5
5
|
Author: Johan Rask
|
|
6
6
|
Author-email: johan.rask@remotivelabs.com
|
|
@@ -20,7 +20,8 @@ Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
|
|
20
20
|
Requires-Dist: pyjwt (>=2.6,<3.0)
|
|
21
21
|
Requires-Dist: python-can (>=4.3.1)
|
|
22
22
|
Requires-Dist: python-socketio (>=4.6.1)
|
|
23
|
-
Requires-Dist: remotivelabs-broker (>=0.1
|
|
23
|
+
Requires-Dist: remotivelabs-broker (>=0.9.1,<0.10.0)
|
|
24
|
+
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
24
25
|
Requires-Dist: rich (>=13.7.0,<13.8.0)
|
|
25
26
|
Requires-Dist: trogon (>=0.5.0)
|
|
26
27
|
Requires-Dist: typer (==0.12.5)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
cli/.DS_Store,sha256=7HTaExsH9zU3sluA0MFtZuyzNmSnUmH2Sh09uoek84E,8196
|
|
2
|
+
cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
cli/api/cloud/tokens.py,sha256=3UKfVM3NvZX_ynpPAXZ_cnrPAIIgg0vA1FuOzj8TV2o,1631
|
|
4
|
+
cli/broker/__init__.py,sha256=8Xxn02p9VTKEZ7JYrDhioLT5eREMrFnDwapja45prL8,1348
|
|
5
|
+
cli/broker/discovery.py,sha256=oQcNhdc17SUZre9WYgPKG41-YDP8FrDhRrF5tk6eaRM,1522
|
|
6
|
+
cli/broker/export.py,sha256=29n_Nuvvv1FM7uUkOPs-DaNWULI4IaC5yufNxLy1FPQ,3519
|
|
7
|
+
cli/broker/files.py,sha256=o-mi7GtGn8vfO6r1hSRY97rDhMQAWfse2lh8Y78llN8,4229
|
|
8
|
+
cli/broker/lib/__about__.py,sha256=xnZ5V6ZcHW9dhWLWdMzVjYJbEnMKpeXm0_S_mbNzypE,141
|
|
9
|
+
cli/broker/lib/broker.py,sha256=ncVRHDS5C_D2hW_LQXxl58EbzT49v85NWE7iNGIg9Fo,26293
|
|
10
|
+
cli/broker/lib/client.py,sha256=2Nwrd_PY1gqQNqkWc9QTJVFDMAZebAzVnCFUTqiArwE,8540
|
|
11
|
+
cli/broker/lib/helper.py,sha256=nNQy0nXD5lDg2mzslRbBPmhzAgjMDJuRBYHj5pG2ZZw,9283
|
|
12
|
+
cli/broker/lib/signalcreator.py,sha256=CDInuSAmNrOlEya_br8CGZR1VNAXY7CXkF9s3mQUpHk,8201
|
|
13
|
+
cli/broker/license_flows.py,sha256=L2PeitmcJPqbHEzb9YBJpsrNDXn92hu2JKdcYXYVJKk,7349
|
|
14
|
+
cli/broker/licenses.py,sha256=jIuLB2qBGflzSjm952CBnErpzs7iIkmEgx9L8GDAPNc,4021
|
|
15
|
+
cli/broker/playback.py,sha256=1N8jTTgd2rw3VRbpyzP3I-06ritceeYxc_uEKst-8j8,4110
|
|
16
|
+
cli/broker/record.py,sha256=FjpeCbd1pUcDcpgF7QGLuBpecv4x4BFOGwELO8De3go,1440
|
|
17
|
+
cli/broker/scripting.py,sha256=lD8GuoVa8Ku4E2BUAq-USfMqG4utZMRgr61jePogykM,3747
|
|
18
|
+
cli/broker/signals.py,sha256=1xldsY3SVcVp-EEsrd6_uoBjrTRMSEONHpOA6SmWxJ4,8245
|
|
19
|
+
cli/cloud/__init__.py,sha256=xFaDg2zho_glvDBDB0_8HRNICVwHeYFEz-yMhzTcCjI,987
|
|
20
|
+
cli/cloud/auth/__init__.py,sha256=MtQ01-n8CgZb9Y_SvxwZUgj44Yo0dFAU3_XwhQiUYtw,54
|
|
21
|
+
cli/cloud/auth/cmd.py,sha256=lv9D0Q7G1RgI2IO1vkz_VVBA_STCPmdJax3QkeiMwg4,3968
|
|
22
|
+
cli/cloud/auth/login.py,sha256=3kc9L6-HOBwzf4Fxis2SBm_q5ObTuzSn4Ic_GkLF0Jk,11121
|
|
23
|
+
cli/cloud/auth_tokens.py,sha256=ExIsCmzoM_MCaYQ5aL4j0Cdyq81IFJ4HJAt-zn1H2L4,5061
|
|
24
|
+
cli/cloud/brokers.py,sha256=cXYKm_-CYJZH0muXAPwUMKIiqBiW6Y9WG5kmkQb3FuU,3849
|
|
25
|
+
cli/cloud/configs.py,sha256=atInBsh16woPmjC0dpnRTpbY4WRgseBOWe8VwpqwO_o,3745
|
|
26
|
+
cli/cloud/licenses/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
cli/cloud/licenses/cmd.py,sha256=zq-Cc5OdftDyUR4dDqGnCmeNF62XR_kYC3mYXEKFZCw,477
|
|
28
|
+
cli/cloud/organisations.py,sha256=zFjqOnbO0_RGatzRXeHfvxi_T65-9wM0jTrtnSyn3_c,4033
|
|
29
|
+
cli/cloud/projects.py,sha256=90EAB8aXGah90brCfJpV6TzIeNy0x9y0F-eETUBtQhs,1597
|
|
30
|
+
cli/cloud/recordings.py,sha256=af3F2z1oLXbalB1UyAgq3B5ZAb0RwRlv69fpw6_GnrY,23728
|
|
31
|
+
cli/cloud/recordings_playback.py,sha256=UpLwbjl0ZGXBGLtSlfQcOKqG5uIdJzUxR688ZpQTEIA,11449
|
|
32
|
+
cli/cloud/resumable_upload.py,sha256=stdjvM3ARXUN5CEuVkRW-xoHmeYXIu0mw5VXUb_50FM,3721
|
|
33
|
+
cli/cloud/sample_recordings.py,sha256=RmuT-a2iMwGj3LXVcPkV5l66uFcf7nyWyJciUjnYkk4,721
|
|
34
|
+
cli/cloud/service_account_tokens.py,sha256=o2VJn9YxUjtSDvRUB8ezT8gKKWqKJNFs0lUD9-e0MPc,2870
|
|
35
|
+
cli/cloud/service_accounts.py,sha256=AiktZW5QTbT6sAPJi4ubETOqbBDAIt4LOE-TZmIiIkk,2586
|
|
36
|
+
cli/cloud/storage/__init__.py,sha256=ijl9WwU5D4oASbwrFKJurYsBUyzwZCOhcdTQYj-ZSeM,159
|
|
37
|
+
cli/cloud/storage/cmd.py,sha256=pF5NP_TIssHK-AQX3df-aYjXWrhjtoaT4GWWQKp2TAI,2961
|
|
38
|
+
cli/cloud/storage/copy.py,sha256=Na0FrBrCJ2xfuFfelmz_p-_yM-ZMyORRF5FHbd7I_W8,3289
|
|
39
|
+
cli/cloud/storage/uri_or_path.py,sha256=DLlyr0RAV-DRlL2C36U-jvUqwiLIlkw7c3mJ7SSGMdI,1158
|
|
40
|
+
cli/cloud/uri.py,sha256=QZCus--KJQlVwGCOzZqiglvj8VvSRKxfVvN33Pilgyg,3616
|
|
41
|
+
cli/connect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
cli/connect/connect.py,sha256=4bsDIcToIyqjuD2HCMWK6sMnpykdHOz5axL-aGFZyXM,4197
|
|
43
|
+
cli/connect/protopie/protopie.py,sha256=0S-GCNiWpTvXzJEsFSht9HoziSzu_6hXZMvTp4xAP40,6470
|
|
44
|
+
cli/remotive.py,sha256=EwgjtTjR5EmGo39PFpbF74v2nm3K2E9A1kon89R2KiE,3854
|
|
45
|
+
cli/settings/__init__.py,sha256=JsMr0E_hsM6IRHYeJUrlLBGyKnPdR4cDJd08-TjX274,665
|
|
46
|
+
cli/settings/config_file.py,sha256=QwsrVGB7JTqFNXlLkbWVcRSveW0HsKzU6Jl8mHqjdO8,3586
|
|
47
|
+
cli/settings/core.py,sha256=_UNs3B0t1AhuwciMNWsHFWZMV9kTEkoSlmow8QlkeXw,10999
|
|
48
|
+
cli/settings/migration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
cli/settings/migration/migrate_all_token_files.py,sha256=xoVvAqn_tGskEW148uf3xZx1mpJKUnERMTcBo0nkCnI,3010
|
|
50
|
+
cli/settings/migration/migrate_config_file.py,sha256=S8kyn3ZXbkej2TRLPcVDcYpvk2iW6kGo38OIutEZayo,2217
|
|
51
|
+
cli/settings/migration/migrate_legacy_dirs.py,sha256=N0t2io3bT_ub8BcVPw1CeQ4eeexRUiu3jXq3DL018OE,1819
|
|
52
|
+
cli/settings/migration/migrate_token_file.py,sha256=Fp7Z_lNqSdoWY05TYwFW2QH8q9QhmB2TYSok6hV1Mic,1530
|
|
53
|
+
cli/settings/migration/migration_tools.py,sha256=D00cQ3RsXkCPgGBGOvpx1_Ul8GfUEqfRbvbv70WN81s,1386
|
|
54
|
+
cli/settings/state_file.py,sha256=rN6JNZP9ULZsuossk3M7jPhoIUgogAAcQGs4SJ-rI-4,2162
|
|
55
|
+
cli/settings/token_file.py,sha256=9GZh44eaXtn30EA06mzQ-69yBT06ig03doqfUHisAw0,3921
|
|
56
|
+
cli/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
+
cli/tools/can/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
|
+
cli/tools/can/can.py,sha256=ovTqzsjJP-z9H5RiJ3f_OXMpELqM3d09JY4JfPpT3-Q,2171
|
|
59
|
+
cli/tools/tools.py,sha256=jhLfrFDqkmWV3eBAzNwBf6WgDGrz7sOhgVCia36Twn8,232
|
|
60
|
+
cli/topology/__init__.py,sha256=MyorYLmC2wAd_z5GdQENEYLgoCiNYgwQ16BOX93IBtI,52
|
|
61
|
+
cli/topology/cmd.py,sha256=vtAw-vIJxf2dogVaqqUm8jRzUJrC7fVuov3sSV7l0sc,3101
|
|
62
|
+
cli/topology/start_trial.py,sha256=GNB2E-Ia18UpXQZ4fViGq5-pRq69vinDBLwzImsz2GY,3414
|
|
63
|
+
cli/typer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
|
+
cli/typer/typer_utils.py,sha256=eUrVOEOKY_MnfiJMAxvEurP1Q79z9pq83mJjocP8lhI,686
|
|
65
|
+
cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
|
+
cli/utils/console.py,sha256=-IczptBi42GdgNRQAYbgFvSV-lIgVF749H9xNgSMgOo,1740
|
|
67
|
+
cli/utils/rest_helper.py,sha256=ZHc7TMNVa07kJWDQ_OEoeaGhzYu8BinSXJJX8R_wX_8,14135
|
|
68
|
+
cli/utils/time.py,sha256=TEKcNZ-pQoJ7cZ6hQmVD0sTRwRm2rBy51-MuDNdO4S4,296
|
|
69
|
+
cli/utils/versions.py,sha256=mHs_BCk0-wwPfCHIIQBlpFkgipSWkw9jUaZ-USH8ODY,3824
|
|
70
|
+
remotivelabs_cli-0.3.1.dist-info/LICENSE,sha256=qDPP_yfuv1fF-u7EfexN-cN3M8aFgGVndGhGLovLKz0,608
|
|
71
|
+
remotivelabs_cli-0.3.1.dist-info/METADATA,sha256=8RommjeI-h_CqdlUXy3oSdYCPZFUCRhP2xciLRgddFk,1521
|
|
72
|
+
remotivelabs_cli-0.3.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
73
|
+
remotivelabs_cli-0.3.1.dist-info/entry_points.txt,sha256=lvDhPgagLqW_KTnLPCwKSqfYlEp-1uYVosRiPjsVj10,45
|
|
74
|
+
remotivelabs_cli-0.3.1.dist-info/RECORD,,
|