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.
Files changed (84) hide show
  1. remotivelabs/cli/__init__.py +0 -0
  2. remotivelabs/cli/api/cloud/tokens.py +62 -0
  3. remotivelabs/cli/broker/__init__.py +33 -0
  4. remotivelabs/cli/broker/defaults.py +1 -0
  5. remotivelabs/cli/broker/discovery.py +43 -0
  6. remotivelabs/cli/broker/export.py +92 -0
  7. remotivelabs/cli/broker/files.py +119 -0
  8. remotivelabs/cli/broker/lib/__about__.py +4 -0
  9. remotivelabs/cli/broker/lib/broker.py +625 -0
  10. remotivelabs/cli/broker/lib/client.py +224 -0
  11. remotivelabs/cli/broker/lib/helper.py +277 -0
  12. remotivelabs/cli/broker/lib/signalcreator.py +196 -0
  13. remotivelabs/cli/broker/license_flows.py +167 -0
  14. remotivelabs/cli/broker/licenses.py +98 -0
  15. remotivelabs/cli/broker/playback.py +117 -0
  16. remotivelabs/cli/broker/record.py +41 -0
  17. remotivelabs/cli/broker/recording_session/__init__.py +3 -0
  18. remotivelabs/cli/broker/recording_session/client.py +67 -0
  19. remotivelabs/cli/broker/recording_session/cmd.py +254 -0
  20. remotivelabs/cli/broker/recording_session/time.py +49 -0
  21. remotivelabs/cli/broker/scripting.py +129 -0
  22. remotivelabs/cli/broker/signals.py +220 -0
  23. remotivelabs/cli/broker/version.py +31 -0
  24. remotivelabs/cli/cloud/__init__.py +17 -0
  25. remotivelabs/cli/cloud/auth/__init__.py +3 -0
  26. remotivelabs/cli/cloud/auth/cmd.py +128 -0
  27. remotivelabs/cli/cloud/auth/login.py +283 -0
  28. remotivelabs/cli/cloud/auth_tokens.py +149 -0
  29. remotivelabs/cli/cloud/brokers.py +109 -0
  30. remotivelabs/cli/cloud/configs.py +109 -0
  31. remotivelabs/cli/cloud/licenses/__init__.py +0 -0
  32. remotivelabs/cli/cloud/licenses/cmd.py +14 -0
  33. remotivelabs/cli/cloud/organisations.py +112 -0
  34. remotivelabs/cli/cloud/projects.py +44 -0
  35. remotivelabs/cli/cloud/recordings.py +580 -0
  36. remotivelabs/cli/cloud/recordings_playback.py +274 -0
  37. remotivelabs/cli/cloud/resumable_upload.py +87 -0
  38. remotivelabs/cli/cloud/sample_recordings.py +25 -0
  39. remotivelabs/cli/cloud/service_account_tokens.py +62 -0
  40. remotivelabs/cli/cloud/service_accounts.py +72 -0
  41. remotivelabs/cli/cloud/storage/__init__.py +5 -0
  42. remotivelabs/cli/cloud/storage/cmd.py +76 -0
  43. remotivelabs/cli/cloud/storage/copy.py +86 -0
  44. remotivelabs/cli/cloud/storage/uri_or_path.py +45 -0
  45. remotivelabs/cli/cloud/uri.py +113 -0
  46. remotivelabs/cli/connect/__init__.py +0 -0
  47. remotivelabs/cli/connect/connect.py +118 -0
  48. remotivelabs/cli/connect/protopie/protopie.py +185 -0
  49. remotivelabs/cli/py.typed +0 -0
  50. remotivelabs/cli/remotive.py +123 -0
  51. remotivelabs/cli/settings/__init__.py +20 -0
  52. remotivelabs/cli/settings/config_file.py +113 -0
  53. remotivelabs/cli/settings/core.py +333 -0
  54. remotivelabs/cli/settings/migration/__init__.py +0 -0
  55. remotivelabs/cli/settings/migration/migrate_all_token_files.py +80 -0
  56. remotivelabs/cli/settings/migration/migrate_config_file.py +64 -0
  57. remotivelabs/cli/settings/migration/migrate_legacy_dirs.py +50 -0
  58. remotivelabs/cli/settings/migration/migrate_token_file.py +52 -0
  59. remotivelabs/cli/settings/migration/migration_tools.py +38 -0
  60. remotivelabs/cli/settings/state_file.py +67 -0
  61. remotivelabs/cli/settings/token_file.py +128 -0
  62. remotivelabs/cli/tools/__init__.py +0 -0
  63. remotivelabs/cli/tools/can/__init__.py +0 -0
  64. remotivelabs/cli/tools/can/can.py +78 -0
  65. remotivelabs/cli/tools/tools.py +9 -0
  66. remotivelabs/cli/topology/__init__.py +28 -0
  67. remotivelabs/cli/topology/all.py +322 -0
  68. remotivelabs/cli/topology/cli/__init__.py +3 -0
  69. remotivelabs/cli/topology/cli/run_in_docker.py +58 -0
  70. remotivelabs/cli/topology/cli/topology_cli.py +16 -0
  71. remotivelabs/cli/topology/cmd.py +130 -0
  72. remotivelabs/cli/topology/start_trial.py +134 -0
  73. remotivelabs/cli/typer/__init__.py +0 -0
  74. remotivelabs/cli/typer/typer_utils.py +27 -0
  75. remotivelabs/cli/utils/__init__.py +0 -0
  76. remotivelabs/cli/utils/console.py +99 -0
  77. remotivelabs/cli/utils/rest_helper.py +369 -0
  78. remotivelabs/cli/utils/time.py +11 -0
  79. remotivelabs/cli/utils/versions.py +120 -0
  80. remotivelabs_cli-0.5.0a1.dist-info/METADATA +51 -0
  81. remotivelabs_cli-0.5.0a1.dist-info/RECORD +84 -0
  82. remotivelabs_cli-0.5.0a1.dist-info/WHEEL +4 -0
  83. remotivelabs_cli-0.5.0a1.dist-info/entry_points.txt +3 -0
  84. remotivelabs_cli-0.5.0a1.dist-info/licenses/LICENSE +17 -0
@@ -0,0 +1,109 @@
1
+ import os.path
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+ import requests
6
+ import typer
7
+ from rich.progress import Progress, SpinnerColumn, TextColumn
8
+
9
+ from remotivelabs.cli.typer import typer_utils
10
+ from remotivelabs.cli.utils.console import print_generic_error, print_success
11
+ from remotivelabs.cli.utils.rest_helper import RestHelper as Rest
12
+
13
+ app = typer_utils.create_typer()
14
+
15
+
16
+ @app.command("list")
17
+ def list_signal_databases(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
18
+ """
19
+ List available signal databases in project
20
+ """
21
+ Rest.handle_get(f"/api/project/{project}/files/config")
22
+
23
+
24
+ @app.command("delete")
25
+ def delete(
26
+ signal_db_file: str = typer.Argument("", help="Signal database file"),
27
+ project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
28
+ ) -> None:
29
+ """
30
+ Deletes the specified signal database
31
+ """
32
+ Rest.handle_delete(f"/api/project/{project}/files/config/{signal_db_file}")
33
+
34
+
35
+ @app.command()
36
+ def upload(
37
+ path: Path = typer.Argument(
38
+ ...,
39
+ exists=True,
40
+ file_okay=True,
41
+ dir_okay=False,
42
+ writable=False,
43
+ readable=True,
44
+ resolve_path=True,
45
+ help="Path to signal database file to upload",
46
+ ),
47
+ project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
48
+ ) -> None:
49
+ """
50
+ Uploads signal database to project
51
+ """
52
+ res_text = Rest.handle_put(url=f"/api/project/{project}/files/config/{os.path.basename(path)}/uploadfile", return_response=True)
53
+ if res_text is not None:
54
+ res_json = res_text.json()
55
+ Rest.upload_file_with_signed_url(
56
+ path=path,
57
+ url=res_json["url"],
58
+ upload_headers={"Content-Type": "application/octet-stream"},
59
+ progress_label=f"Uploading {path}...",
60
+ )
61
+
62
+
63
+ @app.command()
64
+ def describe(
65
+ signal_db_file: str = typer.Argument("", help="Signal database file"),
66
+ project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
67
+ ) -> None:
68
+ """
69
+ Shows all metadata related to this signal database
70
+ """
71
+ Rest.handle_get(f"/api/project/{project}/files/config/{signal_db_file}")
72
+
73
+
74
+ @app.command()
75
+ def download(
76
+ signal_db_file: str = typer.Argument("", help="Signal database file"),
77
+ project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
78
+ ) -> None:
79
+ """
80
+ Downloads the specified signal database to disk
81
+ """
82
+ with Progress(
83
+ SpinnerColumn(),
84
+ TextColumn("[progress.description]{task.description}"),
85
+ transient=True,
86
+ ) as progress:
87
+ Rest.ensure_auth_token()
88
+
89
+ progress.add_task(description=f"Downloading {signal_db_file}", total=None)
90
+
91
+ # First request the download url from cloud. This is a public signed url that is valid
92
+ # for a short period of time
93
+ get_signed_url_resp = requests.get(
94
+ f"{Rest.get_base_url()}/api/project/{project}/files/config/{signal_db_file}/download",
95
+ headers=Rest.get_headers(),
96
+ allow_redirects=True,
97
+ timeout=60,
98
+ )
99
+ if get_signed_url_resp.status_code == 200:
100
+ # Next download the actual file
101
+ download_resp = requests.get(url=get_signed_url_resp.text, stream=True, timeout=60)
102
+ if download_resp.status_code == 200:
103
+ with open(signal_db_file, "wb") as out_file:
104
+ shutil.copyfileobj(download_resp.raw, out_file)
105
+ print_success(f"{signal_db_file} downloaded")
106
+ else:
107
+ print_generic_error(f"Got unexpected status {download_resp.status_code}")
108
+ else:
109
+ print_generic_error(f"Got unexpected status {get_signed_url_resp.status_code}\n")
File without changes
@@ -0,0 +1,14 @@
1
+ import typer
2
+
3
+ from remotivelabs.cli.typer import typer_utils
4
+ from remotivelabs.cli.utils.rest_helper import RestHelper
5
+
6
+ app = typer_utils.create_typer()
7
+
8
+
9
+ @app.command(help="List licenses for an organization")
10
+ def licenses(
11
+ organization: str = typer.Option(..., help="Organization ID", envvar="REMOTIVE_CLOUD_ORGANIZATION"),
12
+ filter_option: str = typer.Option("all", help="all, valid, expired"),
13
+ ) -> None:
14
+ RestHelper.handle_get(f"/api/bu/{organization}/licenses", {"filter": filter_option})
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from typing import List, Optional
6
+
7
+ import typer
8
+ from rich.table import Table
9
+
10
+ from remotivelabs.cli.settings import settings
11
+ from remotivelabs.cli.typer import typer_utils
12
+ from remotivelabs.cli.utils.console import (
13
+ print_generic_error,
14
+ print_generic_message,
15
+ print_hint,
16
+ print_newline,
17
+ print_unformatted_to_stderr,
18
+ )
19
+ from remotivelabs.cli.utils.rest_helper import RestHelper
20
+
21
+ app = typer_utils.create_typer()
22
+
23
+
24
+ @dataclass
25
+ class Organisation:
26
+ display_name: str
27
+ uid: str
28
+
29
+
30
+ def _prompt_choice(choices: List[Organisation]) -> Optional[Organisation]:
31
+ table = Table("#", "Name", "Uid", "Default")
32
+
33
+ account = settings.get_active_account()
34
+ current_default_org = account.default_organization if account else None
35
+
36
+ for idx, choice in enumerate(choices, start=1):
37
+ table.add_row(
38
+ f"[yellow]{idx}",
39
+ f"[bold]{choice.display_name}[/bold]",
40
+ choice.uid,
41
+ ":thumbsup:" if current_default_org is not None and current_default_org == choice.uid else "",
42
+ )
43
+ print_unformatted_to_stderr(table)
44
+
45
+ print_newline()
46
+ selection = typer.prompt(f"Enter the number(# 1-{len(choices)}) of the organization to select (or q to quit)")
47
+
48
+ if selection == "q":
49
+ return None
50
+ try:
51
+ index = int(selection) - 1
52
+ if 0 <= index < len(choices):
53
+ return choices[index]
54
+ raise ValueError
55
+ except ValueError:
56
+ print_generic_error("Invalid choice, please try again")
57
+ return _prompt_choice(choices)
58
+
59
+
60
+ @app.command("default")
61
+ def select_default_org(
62
+ organization_uid: str = typer.Argument(None, help="Organization uid or empty to select one"),
63
+ get: bool = typer.Option(False, help="Print current default organization"),
64
+ ) -> None:
65
+ do_select_default_org(organization_uid, get)
66
+
67
+
68
+ def do_select_default_org(organisation_uid: Optional[str] = None, get: bool = False) -> None:
69
+ r"""
70
+ Set default organization for the currently activated user, empty to choose from available organizations or organization uid as argument
71
+
72
+ remotive cloud organizations default my_org \[set specific org uid]
73
+ remotive cloud organizations default \[select one from prompt]
74
+ remotive cloud organizations default --get \[print current default]
75
+
76
+ Note that service-accounts does Not have permission to list organizations and will get a 403 Forbidden response so you must
77
+ select the organization uid as argument
78
+ """
79
+ active_account = settings.get_active_account()
80
+ if get:
81
+ if active_account and active_account.default_organization:
82
+ print_unformatted_to_stderr(active_account.default_organization)
83
+ else:
84
+ print_unformatted_to_stderr("No default organization set")
85
+ elif organisation_uid is not None:
86
+ settings.set_default_organisation(organisation_uid)
87
+ else:
88
+ if active_account:
89
+ token = settings.get_token_file(active_account.credentials_file)
90
+ if token and token.type != "authorized_user":
91
+ print_hint(
92
+ "You must supply the organization name as argument when using a service-account since the "
93
+ "service-account is not allowed to list"
94
+ )
95
+ return
96
+
97
+ r = RestHelper.handle_get("/api/bu", return_response=True)
98
+ orgs = r.json()
99
+ orgs = [Organisation(display_name=o["organisation"]["displayName"], uid=o["organisation"]["uid"]) for o in orgs]
100
+
101
+ selected = _prompt_choice(orgs)
102
+
103
+ if selected is not None:
104
+ typer.echo(f"Default organisation: {selected.display_name} (uid: {selected.uid})")
105
+ settings.set_default_organisation(selected.uid)
106
+
107
+
108
+ @app.command(name="list", help="List your available organizations")
109
+ def list_orgs() -> None:
110
+ r = RestHelper.handle_get("/api/bu", return_response=True)
111
+ orgs = [{"uid": org["organisation"]["uid"], "displayName": org["organisation"]["displayName"]} for org in r.json()]
112
+ print_generic_message(json.dumps(orgs))
@@ -0,0 +1,44 @@
1
+ import json
2
+
3
+ import typer
4
+
5
+ from remotivelabs.cli.typer import typer_utils
6
+ from remotivelabs.cli.utils.console import print_generic_error, print_generic_message
7
+ from remotivelabs.cli.utils.rest_helper import RestHelper as Rest
8
+
9
+ app = typer_utils.create_typer()
10
+
11
+
12
+ @app.command(name="list", help="List your projects")
13
+ def list_projects(organization: str = typer.Option(..., help="Organization ID", envvar="REMOTIVE_CLOUD_ORGANIZATION")) -> None:
14
+ r = Rest.handle_get(url=f"/api/bu/{organization}/me", return_response=True)
15
+ if r is None:
16
+ return
17
+
18
+ if r.status_code == 200:
19
+ # extract the project uid parts
20
+ projects = r.json()["projects"]
21
+ projects = map(lambda p: p["uid"], projects)
22
+ print_generic_message(json.dumps(list(projects)))
23
+ else:
24
+ print_generic_error(f"Got unexpected status {r.status_code}\n")
25
+
26
+
27
+ @app.command(name="create")
28
+ def create_project(
29
+ project_uid: str = typer.Argument(help="Project UID"),
30
+ organization: str = typer.Option(..., help="Organization ID", envvar="REMOTIVE_CLOUD_ORGANIZATION"),
31
+ project_display_name: str = typer.Option(default="", help="Project display name"),
32
+ ) -> None:
33
+ create_project_req = {
34
+ "uid": project_uid,
35
+ "displayName": project_display_name if project_display_name != "" else project_uid,
36
+ "description": "",
37
+ }
38
+
39
+ Rest.handle_post(url=f"/api/bu/{organization}/project", body=json.dumps(create_project_req))
40
+
41
+
42
+ @app.command(name="delete")
43
+ def delete(project: str = typer.Argument(help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
44
+ Rest.handle_delete(url=f"/api/project/{project}")