remotivelabs-cli 0.3.0__py3-none-any.whl → 0.3.2__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 +13 -18
- cli/broker/lib/helper.py +6 -7
- 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/auth/cmd.py +17 -14
- cli/cloud/auth/login.py +16 -26
- cli/cloud/auth_tokens.py +9 -13
- cli/cloud/brokers.py +5 -9
- cli/cloud/configs.py +4 -17
- cli/cloud/organisations.py +8 -10
- 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 +3 -2
- 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 +21 -29
- cli/remotive.py +4 -7
- cli/settings/core.py +4 -2
- cli/settings/migration/migration_tools.py +2 -1
- cli/tools/can/can.py +4 -7
- cli/topology/__init__.py +3 -0
- cli/topology/cmd.py +61 -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 +23 -16
- cli/utils/versions.py +2 -4
- {remotivelabs_cli-0.3.0.dist-info → remotivelabs_cli-0.3.2.dist-info}/METADATA +1 -1
- remotivelabs_cli-0.3.2.dist-info/RECORD +74 -0
- cli/broker/brokers.py +0 -93
- cli/errors.py +0 -44
- remotivelabs_cli-0.3.0.dist-info/RECORD +0 -71
- {remotivelabs_cli-0.3.0.dist-info → remotivelabs_cli-0.3.2.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.3.0.dist-info → remotivelabs_cli-0.3.2.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.3.0.dist-info → remotivelabs_cli-0.3.2.dist-info}/entry_points.txt +0 -0
cli/remotive.py
CHANGED
|
@@ -3,12 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
|
-
from rich import print as rich_print
|
|
7
|
-
from rich.console import Console
|
|
8
6
|
from trogon import Trogon
|
|
9
7
|
from typer.main import get_group
|
|
10
8
|
|
|
11
|
-
from cli.broker
|
|
9
|
+
from cli.broker import app as broker_app
|
|
12
10
|
from cli.cloud import app as cloud_app
|
|
13
11
|
from cli.connect.connect import app as connect_app
|
|
14
12
|
from cli.settings import Settings, settings
|
|
@@ -19,8 +17,7 @@ from cli.tools.tools import app as tools_app
|
|
|
19
17
|
from cli.topology.cmd import app as topology_app
|
|
20
18
|
from cli.typer import typer_utils
|
|
21
19
|
from cli.utils import versions
|
|
22
|
-
|
|
23
|
-
err_console = Console(stderr=True)
|
|
20
|
+
from cli.utils.console import print_generic_error, print_generic_message
|
|
24
21
|
|
|
25
22
|
|
|
26
23
|
def is_featue_flag_enabled(env_var: str) -> bool:
|
|
@@ -48,7 +45,7 @@ def version_callback(value: bool) -> None:
|
|
|
48
45
|
|
|
49
46
|
def test_callback(value: int) -> None:
|
|
50
47
|
if value:
|
|
51
|
-
|
|
48
|
+
print_generic_message(str(value))
|
|
52
49
|
raise typer.Exit()
|
|
53
50
|
|
|
54
51
|
|
|
@@ -72,7 +69,7 @@ def run_migrations(settings: Settings) -> None:
|
|
|
72
69
|
migrate_config_file(settings.config_file_path, settings)
|
|
73
70
|
|
|
74
71
|
if has_migrated_tokens:
|
|
75
|
-
|
|
72
|
+
print_generic_error("Migrated old credentials and configuration files, you may need to login again or activate correct credentials")
|
|
76
73
|
|
|
77
74
|
|
|
78
75
|
def set_default_org_as_env(settings: Settings) -> None:
|
cli/settings/core.py
CHANGED
|
@@ -8,13 +8,13 @@ from typing import Optional
|
|
|
8
8
|
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
|
|
11
|
-
from cli.errors import ErrorPrinter
|
|
12
11
|
from cli.settings import config_file as cf
|
|
13
12
|
from cli.settings import state_file as sf
|
|
14
13
|
from cli.settings import token_file as tf
|
|
15
14
|
from cli.settings.config_file import Account, ConfigFile
|
|
16
15
|
from cli.settings.state_file import StateFile
|
|
17
16
|
from cli.settings.token_file import TokenFile
|
|
17
|
+
from cli.utils.console import print_hint
|
|
18
18
|
|
|
19
19
|
err_console = Console(stderr=True)
|
|
20
20
|
|
|
@@ -63,11 +63,13 @@ class Settings:
|
|
|
63
63
|
def set_default_organisation(self, organisation: str) -> None:
|
|
64
64
|
"""
|
|
65
65
|
Set the default organization for the active account
|
|
66
|
+
|
|
67
|
+
TODO: Raise error, dont sys.exit
|
|
66
68
|
"""
|
|
67
69
|
config = self._get_cli_config()
|
|
68
70
|
active_account = config.get_active_account()
|
|
69
71
|
if not active_account:
|
|
70
|
-
|
|
72
|
+
print_hint("You must have an account activated in order to set default organization")
|
|
71
73
|
sys.exit(1)
|
|
72
74
|
active_account.default_organization = organisation
|
|
73
75
|
self._write_config_file(config)
|
|
@@ -4,6 +4,7 @@ from itertools import chain
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
from cli.settings.token_file import TokenFile
|
|
7
|
+
from cli.utils.console import print_generic_message
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def list_token_files(config_dir: Path) -> list[TokenFile]:
|
|
@@ -20,7 +21,7 @@ def list_token_files(config_dir: Path) -> list[TokenFile]:
|
|
|
20
21
|
token_file = TokenFile.from_json_str(file.read_text())
|
|
21
22
|
token_files.append(token_file)
|
|
22
23
|
except Exception:
|
|
23
|
-
|
|
24
|
+
print_generic_message(f"warning: invalid token file {file}. Consider removing it.")
|
|
24
25
|
return token_files
|
|
25
26
|
|
|
26
27
|
|
cli/tools/can/can.py
CHANGED
|
@@ -2,12 +2,9 @@ from pathlib import Path
|
|
|
2
2
|
|
|
3
3
|
import can
|
|
4
4
|
import typer
|
|
5
|
-
from rich.console import Console
|
|
6
5
|
|
|
7
6
|
from cli.typer import typer_utils
|
|
8
|
-
|
|
9
|
-
err_console = Console(stderr=True)
|
|
10
|
-
console = Console()
|
|
7
|
+
from cli.utils.console import print_generic_error, print_success
|
|
11
8
|
|
|
12
9
|
HELP = """
|
|
13
10
|
CAN related tools
|
|
@@ -49,7 +46,7 @@ def convert(
|
|
|
49
46
|
for msg in reader:
|
|
50
47
|
writer.on_message_received(msg)
|
|
51
48
|
except Exception as e:
|
|
52
|
-
|
|
49
|
+
print_generic_error(f"Failed to convert file: {e}")
|
|
53
50
|
|
|
54
51
|
|
|
55
52
|
@app.command("validate")
|
|
@@ -76,6 +73,6 @@ def validate(
|
|
|
76
73
|
for msg in reader:
|
|
77
74
|
if print_to_terminal:
|
|
78
75
|
writer.on_message_received(msg)
|
|
79
|
-
|
|
76
|
+
print_success(f"{in_file} verified")
|
|
80
77
|
except Exception as e:
|
|
81
|
-
|
|
78
|
+
print_generic_error(f"Failed to convert file: {e}")
|
cli/topology/__init__.py
ADDED
cli/topology/cmd.py
CHANGED
|
@@ -1,101 +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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
33
|
+
try:
|
|
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}")
|
|
97
79
|
raise typer.Exit(1)
|
|
98
|
-
else:
|
|
99
|
-
subscription_info = sub.json()
|
|
100
|
-
_print_current_subscription(subscription_info)
|
|
101
|
-
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(soft_wrap=True)
|
|
11
|
+
err_console = Console(stderr=True, soft_wrap=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")
|
|
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.errors import ErrorPrinter
|
|
19
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,7 +81,10 @@ 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
89
|
active_account = settings.get_active_account()
|
|
90
90
|
if active_account:
|
|
@@ -98,7 +98,7 @@ class RestHelper:
|
|
|
98
98
|
if not token:
|
|
99
99
|
if quiet:
|
|
100
100
|
return
|
|
101
|
-
|
|
101
|
+
print_hint("you are not logged in, please login using [green]remotive cloud auth login[/green]")
|
|
102
102
|
sys.exit(1)
|
|
103
103
|
|
|
104
104
|
RestHelper.__headers["Authorization"] = f"Bearer {token.strip()}"
|
|
@@ -155,38 +155,45 @@ class RestHelper:
|
|
|
155
155
|
|
|
156
156
|
@staticmethod
|
|
157
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
|
+
"""
|
|
158
161
|
if response.status_code == 426: # CLI upgrade
|
|
159
|
-
|
|
162
|
+
print_hint(response.text)
|
|
160
163
|
sys.exit(1)
|
|
161
164
|
if response.status_code > 299:
|
|
162
165
|
if allow_status_codes is not None and response.status_code in allow_status_codes:
|
|
163
166
|
return
|
|
164
|
-
|
|
167
|
+
print_generic_error(f"Got status code: {response.status_code}")
|
|
165
168
|
if response.status_code == 401:
|
|
166
|
-
|
|
169
|
+
print_generic_message("Your token is not valid or has expired, please login again or activate another account")
|
|
167
170
|
else:
|
|
168
|
-
|
|
171
|
+
print_unformatted_to_stderr(response.text)
|
|
169
172
|
sys.exit(1)
|
|
170
173
|
|
|
171
174
|
@staticmethod
|
|
172
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
|
+
"""
|
|
173
180
|
if response.status_code == 426: # CLI upgrade
|
|
174
|
-
|
|
181
|
+
print_hint(response.text)
|
|
175
182
|
sys.exit(1)
|
|
176
183
|
|
|
177
184
|
if response.status_code >= 200 and response.status_code < 300:
|
|
178
185
|
if len(response.content) >= 2:
|
|
179
186
|
try:
|
|
180
|
-
|
|
187
|
+
print_unformatted(json.dumps(response.json()))
|
|
181
188
|
except JSONDecodeError:
|
|
182
|
-
|
|
189
|
+
print_generic_error("Json parse error: Please try again and report if the error persists")
|
|
183
190
|
sys.exit(0)
|
|
184
191
|
else:
|
|
185
|
-
|
|
192
|
+
print_generic_error(f"Got status code: {response.status_code}")
|
|
186
193
|
if response.status_code == 401:
|
|
187
|
-
|
|
194
|
+
print_generic_message("Your token is not valid or has expired, please login again or activate another account")
|
|
188
195
|
else:
|
|
189
|
-
|
|
196
|
+
print_unformatted_to_stderr(response.text)
|
|
190
197
|
sys.exit(1)
|
|
191
198
|
|
|
192
199
|
@staticmethod
|
cli/utils/versions.py
CHANGED
|
@@ -10,8 +10,8 @@ from importlib.metadata import version as python_project_version
|
|
|
10
10
|
|
|
11
11
|
from packaging.version import InvalidVersion, Version
|
|
12
12
|
|
|
13
|
-
from cli.errors import ErrorPrinter
|
|
14
13
|
from cli.settings import Settings
|
|
14
|
+
from cli.utils.console import print_hint
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def cli_version() -> str:
|
|
@@ -117,6 +117,4 @@ def _print_update_info(cur: str, latest: str) -> None:
|
|
|
117
117
|
else "upgrade with: pipx install -U remotivelabs-cli"
|
|
118
118
|
)
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
f"Update available: remotivelabs-cli {cur} → {latest} , ({instructions}) we always recommend to use latest version"
|
|
122
|
-
)
|
|
120
|
+
print_hint(f"Update available: remotivelabs-cli {cur} → {latest} , ({instructions}) we always recommend to use latest version")
|