remotivelabs-cli 0.5.0a1__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.
- remotivelabs/cli/__init__.py +0 -0
- remotivelabs/cli/api/cloud/tokens.py +62 -0
- remotivelabs/cli/broker/__init__.py +33 -0
- remotivelabs/cli/broker/defaults.py +1 -0
- remotivelabs/cli/broker/discovery.py +43 -0
- remotivelabs/cli/broker/export.py +92 -0
- remotivelabs/cli/broker/files.py +119 -0
- remotivelabs/cli/broker/lib/__about__.py +4 -0
- remotivelabs/cli/broker/lib/broker.py +625 -0
- remotivelabs/cli/broker/lib/client.py +224 -0
- remotivelabs/cli/broker/lib/helper.py +277 -0
- remotivelabs/cli/broker/lib/signalcreator.py +196 -0
- remotivelabs/cli/broker/license_flows.py +167 -0
- remotivelabs/cli/broker/licenses.py +98 -0
- remotivelabs/cli/broker/playback.py +117 -0
- remotivelabs/cli/broker/record.py +41 -0
- remotivelabs/cli/broker/recording_session/__init__.py +3 -0
- remotivelabs/cli/broker/recording_session/client.py +67 -0
- remotivelabs/cli/broker/recording_session/cmd.py +254 -0
- remotivelabs/cli/broker/recording_session/time.py +49 -0
- remotivelabs/cli/broker/scripting.py +129 -0
- remotivelabs/cli/broker/signals.py +220 -0
- remotivelabs/cli/broker/version.py +31 -0
- remotivelabs/cli/cloud/__init__.py +17 -0
- remotivelabs/cli/cloud/auth/__init__.py +3 -0
- remotivelabs/cli/cloud/auth/cmd.py +128 -0
- remotivelabs/cli/cloud/auth/login.py +283 -0
- remotivelabs/cli/cloud/auth_tokens.py +149 -0
- remotivelabs/cli/cloud/brokers.py +109 -0
- remotivelabs/cli/cloud/configs.py +109 -0
- remotivelabs/cli/cloud/licenses/__init__.py +0 -0
- remotivelabs/cli/cloud/licenses/cmd.py +14 -0
- remotivelabs/cli/cloud/organisations.py +112 -0
- remotivelabs/cli/cloud/projects.py +44 -0
- remotivelabs/cli/cloud/recordings.py +580 -0
- remotivelabs/cli/cloud/recordings_playback.py +274 -0
- remotivelabs/cli/cloud/resumable_upload.py +87 -0
- remotivelabs/cli/cloud/sample_recordings.py +25 -0
- remotivelabs/cli/cloud/service_account_tokens.py +62 -0
- remotivelabs/cli/cloud/service_accounts.py +72 -0
- remotivelabs/cli/cloud/storage/__init__.py +5 -0
- remotivelabs/cli/cloud/storage/cmd.py +76 -0
- remotivelabs/cli/cloud/storage/copy.py +86 -0
- remotivelabs/cli/cloud/storage/uri_or_path.py +45 -0
- remotivelabs/cli/cloud/uri.py +113 -0
- remotivelabs/cli/connect/__init__.py +0 -0
- remotivelabs/cli/connect/connect.py +118 -0
- remotivelabs/cli/connect/protopie/protopie.py +185 -0
- remotivelabs/cli/py.typed +0 -0
- remotivelabs/cli/remotive.py +123 -0
- remotivelabs/cli/settings/__init__.py +20 -0
- remotivelabs/cli/settings/config_file.py +113 -0
- remotivelabs/cli/settings/core.py +333 -0
- remotivelabs/cli/settings/migration/__init__.py +0 -0
- remotivelabs/cli/settings/migration/migrate_all_token_files.py +80 -0
- remotivelabs/cli/settings/migration/migrate_config_file.py +64 -0
- remotivelabs/cli/settings/migration/migrate_legacy_dirs.py +50 -0
- remotivelabs/cli/settings/migration/migrate_token_file.py +52 -0
- remotivelabs/cli/settings/migration/migration_tools.py +38 -0
- remotivelabs/cli/settings/state_file.py +67 -0
- remotivelabs/cli/settings/token_file.py +128 -0
- remotivelabs/cli/tools/__init__.py +0 -0
- remotivelabs/cli/tools/can/__init__.py +0 -0
- remotivelabs/cli/tools/can/can.py +78 -0
- remotivelabs/cli/tools/tools.py +9 -0
- remotivelabs/cli/topology/__init__.py +28 -0
- remotivelabs/cli/topology/all.py +322 -0
- remotivelabs/cli/topology/cli/__init__.py +3 -0
- remotivelabs/cli/topology/cli/run_in_docker.py +58 -0
- remotivelabs/cli/topology/cli/topology_cli.py +16 -0
- remotivelabs/cli/topology/cmd.py +130 -0
- remotivelabs/cli/topology/start_trial.py +134 -0
- remotivelabs/cli/typer/__init__.py +0 -0
- remotivelabs/cli/typer/typer_utils.py +27 -0
- remotivelabs/cli/utils/__init__.py +0 -0
- remotivelabs/cli/utils/console.py +99 -0
- remotivelabs/cli/utils/rest_helper.py +369 -0
- remotivelabs/cli/utils/time.py +11 -0
- remotivelabs/cli/utils/versions.py +120 -0
- remotivelabs_cli-0.5.0a1.dist-info/METADATA +51 -0
- remotivelabs_cli-0.5.0a1.dist-info/RECORD +84 -0
- remotivelabs_cli-0.5.0a1.dist-info/WHEEL +4 -0
- remotivelabs_cli-0.5.0a1.dist-info/entry_points.txt +3 -0
- remotivelabs_cli-0.5.0a1.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from remotivelabs.cli.settings.token_file import TokenFile, TokenFileAccount
|
|
4
|
+
from remotivelabs.cli.utils.rest_helper import RestHelper
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InvalidTokenError(Exception):
|
|
8
|
+
"""Raised when a token is invalid."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UnsupportedTokenVersionError(Exception):
|
|
12
|
+
"""Raised when a token version is not supported."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def migrate_legacy_token(token: TokenFile) -> TokenFile:
|
|
16
|
+
"""
|
|
17
|
+
Migrate a token from a legacy format to the latest format.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
token: The token to migrate.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
TokenFile: The migrated token.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
InvalidTokenError: If the token is invalid.
|
|
27
|
+
UnsupportedTokenVersionError: If the token version is not supported.
|
|
28
|
+
"""
|
|
29
|
+
# use a naive approach to compare versions for now
|
|
30
|
+
version = float(token.version)
|
|
31
|
+
|
|
32
|
+
# already migrated
|
|
33
|
+
if version >= 1.1:
|
|
34
|
+
return token
|
|
35
|
+
|
|
36
|
+
if version == 1.0:
|
|
37
|
+
res = RestHelper.handle_get("/api/whoami", return_response=True, allow_status_codes=[401, 400, 403], access_token=token.token)
|
|
38
|
+
if res.status_code != 200:
|
|
39
|
+
raise InvalidTokenError(f"Token {token.name} is invalid")
|
|
40
|
+
|
|
41
|
+
email = res.json()["email"]
|
|
42
|
+
return TokenFile(
|
|
43
|
+
version="1.1",
|
|
44
|
+
type=token.type,
|
|
45
|
+
name=token.name,
|
|
46
|
+
token=token.token,
|
|
47
|
+
created=token.created,
|
|
48
|
+
expires=token.expires,
|
|
49
|
+
account=TokenFileAccount(email=email),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
raise UnsupportedTokenVersionError(f"Unsupported token version: {token.version}")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from itertools import chain
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from remotivelabs.cli.settings.token_file import TokenFile
|
|
7
|
+
from remotivelabs.cli.utils.console import print_generic_message
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list_token_files(config_dir: Path) -> list[TokenFile]:
|
|
11
|
+
"""
|
|
12
|
+
List all token files in the config directory
|
|
13
|
+
|
|
14
|
+
Note! Dont use settings, as that will couple settings to the old config and token formats we want to migrate away from.
|
|
15
|
+
"""
|
|
16
|
+
token_files = []
|
|
17
|
+
patterns = ["personal-token-*.json", "service-account-token-*.json"]
|
|
18
|
+
files = list(chain.from_iterable(config_dir.glob(pattern) for pattern in patterns))
|
|
19
|
+
for file in files:
|
|
20
|
+
try:
|
|
21
|
+
token_file = TokenFile.from_json_str(file.read_text())
|
|
22
|
+
token_files.append(token_file)
|
|
23
|
+
except Exception:
|
|
24
|
+
print_generic_message(f"warning: invalid token file {file}. Consider removing it.")
|
|
25
|
+
return token_files
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_token_file(cred_name: str, config_dir: Path) -> TokenFile | None:
|
|
29
|
+
"""
|
|
30
|
+
Get the token file for a given credentials name.
|
|
31
|
+
|
|
32
|
+
Note! Dont use settings, as that will couple settings to the old config and token formats we want to migrate away from.
|
|
33
|
+
"""
|
|
34
|
+
token_files = list_token_files(config_dir)
|
|
35
|
+
matches = [token_file for token_file in token_files if token_file.name == cred_name]
|
|
36
|
+
if len(matches) != 1:
|
|
37
|
+
return None
|
|
38
|
+
return matches[0]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from remotivelabs.cli.utils.time import parse_datetime
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StateFile(BaseModel):
|
|
13
|
+
"""
|
|
14
|
+
Contains CLI state and other application specific data.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
version: str = "1.0"
|
|
18
|
+
last_update_check_time: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
def should_perform_update_check(self) -> bool:
|
|
21
|
+
"""
|
|
22
|
+
Check if we should perform an update check.
|
|
23
|
+
|
|
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.
|
|
28
|
+
"""
|
|
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
|
|
36
|
+
return True # Returning True will trigger a check, which will properly set last_update_check_time
|
|
37
|
+
|
|
38
|
+
seconds = (datetime.now() - parse_datetime(self.last_update_check_time)).seconds
|
|
39
|
+
return (seconds / 3600) > 2
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_json_str(cls, data: str) -> StateFile:
|
|
43
|
+
return cls.model_validate_json(data)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_dict(cls, data: dict[str, Any]) -> StateFile:
|
|
47
|
+
return cls.model_validate(data)
|
|
48
|
+
|
|
49
|
+
def to_json_str(self) -> str:
|
|
50
|
+
return self.model_dump_json()
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict[str, Any]:
|
|
53
|
+
return self.model_dump()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def loads(data: str) -> StateFile:
|
|
57
|
+
"""
|
|
58
|
+
Creates a StateFile from a JSON string.
|
|
59
|
+
"""
|
|
60
|
+
return StateFile.from_json_str(data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def dumps(state: StateFile) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Returns the JSON string representation of the StateFile.
|
|
66
|
+
"""
|
|
67
|
+
return state.to_json_str()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from datetime import date, datetime
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator
|
|
8
|
+
|
|
9
|
+
from remotivelabs.cli.utils.time import parse_date
|
|
10
|
+
|
|
11
|
+
DEFAULT_EMAIL = "unknown@remotivecloud.com"
|
|
12
|
+
PERSONAL_TOKEN_FILE_PREFIX = "personal-token-"
|
|
13
|
+
SERVICE_ACCOUNT_TOKEN_FILE_PREFIX = "service-account-token-"
|
|
14
|
+
|
|
15
|
+
TokenType = Literal["authorized_user", "service_account"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _email_to_safe_filename(email: str) -> str:
|
|
19
|
+
"""Replace any invalid character with an underscore"""
|
|
20
|
+
return re.sub(r'[<>:"/\\|?*]', "_", email)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _parse_token_type(token: str) -> TokenType:
|
|
24
|
+
if token.startswith("pa"):
|
|
25
|
+
return "authorized_user"
|
|
26
|
+
if token.startswith("sa"):
|
|
27
|
+
return "service_account"
|
|
28
|
+
raise ValueError(f"Unknown token type for token: {token}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TokenFileAccount(BaseModel):
|
|
32
|
+
"""
|
|
33
|
+
TokenFileAccount represents the account information for a token file.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
email: EmailStr = DEFAULT_EMAIL
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TokenFile(BaseModel):
|
|
40
|
+
"""
|
|
41
|
+
TokenFile represents a token file for the CLI.
|
|
42
|
+
|
|
43
|
+
TODO: Should all setters return a new instance of the TokenFile?
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
version: str = "1.0"
|
|
47
|
+
type: TokenType
|
|
48
|
+
name: str
|
|
49
|
+
token: str
|
|
50
|
+
created: date
|
|
51
|
+
expires: date
|
|
52
|
+
account: TokenFileAccount = Field(default_factory=TokenFileAccount)
|
|
53
|
+
|
|
54
|
+
@field_validator("created", "expires", mode="before")
|
|
55
|
+
@classmethod
|
|
56
|
+
def _validate_parse_date(cls, value: str | date) -> date:
|
|
57
|
+
if isinstance(value, date):
|
|
58
|
+
return value
|
|
59
|
+
return parse_date(value)
|
|
60
|
+
|
|
61
|
+
@model_validator(mode="before")
|
|
62
|
+
@classmethod
|
|
63
|
+
def _validate_json_data(cls, json_data: Any) -> Any:
|
|
64
|
+
"""
|
|
65
|
+
Try to migrate old formats and missing fields as best we can.
|
|
66
|
+
|
|
67
|
+
NOTE: If we ever need to add a new version (like 2.0), we should add explicit classes for each version (e.g. TokenFileV1,
|
|
68
|
+
TokenFileV2, etc.), each with their own fields. This will allow us to migrate to new versions without breaking
|
|
69
|
+
backwards compatibility.
|
|
70
|
+
"""
|
|
71
|
+
if not isinstance(json_data, dict):
|
|
72
|
+
return json_data
|
|
73
|
+
|
|
74
|
+
if "version" not in json_data:
|
|
75
|
+
json_data["version"] = "1.0"
|
|
76
|
+
|
|
77
|
+
if "type" not in json_data and "token" in json_data:
|
|
78
|
+
json_data["type"] = _parse_token_type(json_data["token"])
|
|
79
|
+
|
|
80
|
+
if "account" not in json_data:
|
|
81
|
+
json_data["account"] = {"email": DEFAULT_EMAIL}
|
|
82
|
+
elif isinstance(json_data["account"], str):
|
|
83
|
+
json_data["account"] = {"email": json_data["account"]}
|
|
84
|
+
|
|
85
|
+
return json_data
|
|
86
|
+
|
|
87
|
+
def get_token_file_name(self) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Returns the name of the token_file following a predictable naming format.
|
|
90
|
+
"""
|
|
91
|
+
email = _email_to_safe_filename(self.account.email) if self.account is not None else "unknown"
|
|
92
|
+
if self.type == "authorized_user":
|
|
93
|
+
return f"{PERSONAL_TOKEN_FILE_PREFIX}{self.name}-{email}.json"
|
|
94
|
+
return f"{SERVICE_ACCOUNT_TOKEN_FILE_PREFIX}{self.name}-{email}.json"
|
|
95
|
+
|
|
96
|
+
def is_expired(self) -> bool:
|
|
97
|
+
return datetime.today().date() > self.expires
|
|
98
|
+
|
|
99
|
+
def expires_in_days(self) -> int:
|
|
100
|
+
return (self.expires - datetime.today().date()).days
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def from_json_str(cls, data: str) -> TokenFile:
|
|
104
|
+
return cls.model_validate_json(data)
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def from_dict(cls, data: dict[str, Any]) -> TokenFile:
|
|
108
|
+
return cls.model_validate(data)
|
|
109
|
+
|
|
110
|
+
def to_json_str(self) -> str:
|
|
111
|
+
return self.model_dump_json()
|
|
112
|
+
|
|
113
|
+
def to_dict(self) -> dict[str, Any]:
|
|
114
|
+
return self.model_dump()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def loads(data: str) -> TokenFile:
|
|
118
|
+
"""
|
|
119
|
+
Creates a TokenFile from a JSON string.
|
|
120
|
+
"""
|
|
121
|
+
return TokenFile.from_json_str(data)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def dumps(token_file: TokenFile) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Returns the JSON string representation of the TokenFile.
|
|
127
|
+
"""
|
|
128
|
+
return token_file.to_json_str()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import can
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from remotivelabs.cli.typer import typer_utils
|
|
7
|
+
from remotivelabs.cli.utils.console import print_generic_error, print_success
|
|
8
|
+
|
|
9
|
+
HELP = """
|
|
10
|
+
CAN related tools
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
app = typer_utils.create_typer(help=HELP)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command("convert")
|
|
17
|
+
def convert(
|
|
18
|
+
in_file: Path = typer.Argument(
|
|
19
|
+
exists=True,
|
|
20
|
+
file_okay=True,
|
|
21
|
+
dir_okay=False,
|
|
22
|
+
writable=False,
|
|
23
|
+
readable=True,
|
|
24
|
+
resolve_path=True,
|
|
25
|
+
help="File to convert from (.blf .asc .log)",
|
|
26
|
+
),
|
|
27
|
+
out_file: Path = typer.Argument(
|
|
28
|
+
exists=False,
|
|
29
|
+
file_okay=True,
|
|
30
|
+
dir_okay=False,
|
|
31
|
+
writable=True,
|
|
32
|
+
readable=True,
|
|
33
|
+
resolve_path=True,
|
|
34
|
+
help="File to convert to (.blf .asc .log)",
|
|
35
|
+
),
|
|
36
|
+
) -> None:
|
|
37
|
+
r"""
|
|
38
|
+
Converts between ASC, BLF and LOG files. Files must end with .asc, .blf or .log.
|
|
39
|
+
|
|
40
|
+
remotive tools can convert \[my_file.blf|.log|.asc] \[my_file.blf|.log|.asc]
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
with can.LogReader(in_file, relative_timestamp=False) as reader:
|
|
44
|
+
try:
|
|
45
|
+
with can.Logger(out_file) as writer:
|
|
46
|
+
for msg in reader:
|
|
47
|
+
writer.on_message_received(msg)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
print_generic_error(f"Failed to convert file: {e}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@app.command("validate")
|
|
53
|
+
def validate(
|
|
54
|
+
in_file: Path = typer.Argument(
|
|
55
|
+
exists=True,
|
|
56
|
+
file_okay=True,
|
|
57
|
+
dir_okay=False,
|
|
58
|
+
writable=False,
|
|
59
|
+
readable=True,
|
|
60
|
+
resolve_path=True,
|
|
61
|
+
help="File to validate (.blf .asc .log)",
|
|
62
|
+
),
|
|
63
|
+
print_to_terminal: bool = typer.Option(False, help="Print file contents to terminal"),
|
|
64
|
+
) -> None:
|
|
65
|
+
r"""
|
|
66
|
+
Validates that the input file is an ASC, BLF and LOG file
|
|
67
|
+
|
|
68
|
+
remotive tools can validate \[my_file.blf|.log|.asc]
|
|
69
|
+
"""
|
|
70
|
+
with can.LogReader(in_file, relative_timestamp=False) as reader:
|
|
71
|
+
try:
|
|
72
|
+
with can.Printer() as writer:
|
|
73
|
+
for msg in reader:
|
|
74
|
+
if print_to_terminal:
|
|
75
|
+
writer.on_message_received(msg)
|
|
76
|
+
print_success(f"{in_file} verified")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print_generic_error(f"Failed to convert file: {e}")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from remotivelabs.cli.tools.can.can import app as can_app
|
|
2
|
+
from remotivelabs.cli.typer import typer_utils
|
|
3
|
+
|
|
4
|
+
HELP_TEXT = """
|
|
5
|
+
CLI tools unrelated to cloud or broker
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
app = typer_utils.create_typer(help=HELP_TEXT)
|
|
9
|
+
app.add_typer(can_app, name="can", help="CAN tools")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from remotivelabs.cli.topology.all import app
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContainerEngine(str, Enum):
|
|
9
|
+
docker = "docker"
|
|
10
|
+
podman = "podman"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.callback()
|
|
14
|
+
def service_callback(
|
|
15
|
+
ctx: typer.Context,
|
|
16
|
+
topology_cmd: str = typer.Option(
|
|
17
|
+
None, envvar="REMOTIVE_TOPOLOGY_COMMAND", hidden=True, help="Optional path to RemotiveTopology command"
|
|
18
|
+
),
|
|
19
|
+
topology_image: str = typer.Option(None, envvar="REMOTIVE_TOPOLOGY_IMAGE", help="Optional docker image for RemotiveTopology "),
|
|
20
|
+
container_engine: ContainerEngine = typer.Option(ContainerEngine.docker, envvar="CONTAINER_ENGINE", help="Specify container engine"),
|
|
21
|
+
) -> None:
|
|
22
|
+
ctx.obj = ctx.obj or {}
|
|
23
|
+
ctx.obj["topology_cmd"] = topology_cmd
|
|
24
|
+
ctx.obj["topology_image"] = topology_image
|
|
25
|
+
ctx.obj["container_engine"] = container_engine.name
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = ["app"]
|