remotivelabs-cli 0.0.25__py3-none-any.whl → 0.0.27__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.
- cli/broker/brokers.py +2 -2
- cli/broker/export.py +5 -5
- cli/broker/files.py +5 -5
- cli/broker/lib/broker.py +82 -51
- cli/broker/license_flows.py +11 -9
- cli/broker/licenses.py +2 -2
- cli/broker/playback.py +14 -34
- cli/broker/record.py +3 -3
- cli/broker/scripting.py +4 -4
- cli/broker/signals.py +20 -12
- cli/cloud/__init__.py +0 -1
- cli/cloud/auth.py +40 -35
- cli/cloud/auth_tokens.py +73 -37
- cli/cloud/brokers.py +24 -33
- cli/cloud/cloud_cli.py +7 -18
- cli/cloud/configs.py +28 -11
- cli/cloud/filestorage.py +63 -51
- cli/cloud/organisations.py +30 -0
- cli/cloud/projects.py +11 -8
- cli/cloud/recordings.py +148 -117
- cli/cloud/recordings_playback.py +52 -39
- cli/cloud/rest_helper.py +247 -196
- cli/cloud/resumable_upload.py +9 -8
- cli/cloud/sample_recordings.py +5 -5
- cli/cloud/service_account_tokens.py +18 -16
- cli/cloud/service_accounts.py +9 -9
- cli/connect/__init__.py +0 -1
- cli/connect/connect.py +7 -6
- cli/connect/protopie/protopie.py +32 -16
- cli/errors.py +6 -5
- cli/remotive.py +13 -9
- cli/requirements.txt +4 -1
- cli/settings.py +9 -9
- cli/tools/__init__.py +0 -1
- cli/tools/can/__init__.py +0 -1
- cli/tools/can/can.py +8 -8
- cli/tools/tools.py +2 -2
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/METADATA +5 -3
- remotivelabs_cli-0.0.27.dist-info/RECORD +45 -0
- remotivelabs_cli-0.0.25.dist-info/RECORD +0 -44
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/entry_points.txt +0 -0
cli/broker/playback.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import List
|
3
|
+
from typing import Dict, List
|
4
4
|
|
5
5
|
import grpc
|
6
6
|
import typer
|
@@ -9,7 +9,15 @@ from cli.errors import ErrorPrinter
|
|
9
9
|
|
10
10
|
from .lib.broker import Broker
|
11
11
|
|
12
|
-
app = typer.Typer(help=help)
|
12
|
+
app = typer.Typer(help=help) # type: ignore
|
13
|
+
|
14
|
+
|
15
|
+
def recording_and_namespace(recording: str) -> Dict[str, str]:
|
16
|
+
splitted = recording.split("::")
|
17
|
+
if len(splitted) != 2:
|
18
|
+
print("Invalid --recording option, expected file_name::namespace")
|
19
|
+
raise typer.Exit(1)
|
20
|
+
return {"recording": splitted[0], "namespace": splitted[1]}
|
13
21
|
|
14
22
|
|
15
23
|
@app.command()
|
@@ -17,7 +25,7 @@ def play(
|
|
17
25
|
recording: List[str] = typer.Option(..., help="Which recording and which namespace to play"),
|
18
26
|
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
19
27
|
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
20
|
-
):
|
28
|
+
) -> None:
|
21
29
|
"""
|
22
30
|
Play recording files on broker.
|
23
31
|
|
@@ -26,13 +34,6 @@ def play(
|
|
26
34
|
remotive broker playback play --recording myrecording_can0::can0 --recording myrecording_can1::can1
|
27
35
|
"""
|
28
36
|
|
29
|
-
def recording_and_namespace(recording: str):
|
30
|
-
splitted = recording.split("::")
|
31
|
-
if len(splitted) != 2:
|
32
|
-
print("Invalid --recording option, expected file_name::namespace")
|
33
|
-
raise typer.Exit(1)
|
34
|
-
return {"recording": splitted[0], "namespace": splitted[1]}
|
35
|
-
|
36
37
|
rec = list(map(recording_and_namespace, recording))
|
37
38
|
try:
|
38
39
|
broker = Broker(url, api_key)
|
@@ -48,7 +49,7 @@ def stop(
|
|
48
49
|
recording: List[str] = typer.Option(..., help="Which recording and which namespace to stop"),
|
49
50
|
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
50
51
|
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
51
|
-
):
|
52
|
+
) -> None:
|
52
53
|
"""
|
53
54
|
Stop recordings that are beeing played on brokers are done with the same syntax as when you start them.
|
54
55
|
|
@@ -57,13 +58,6 @@ def stop(
|
|
57
58
|
remotive broker playback stop --recording myrecording_can0::can0 --recording myrecording_can1::can1
|
58
59
|
"""
|
59
60
|
|
60
|
-
def recording_and_namespace(recording: str):
|
61
|
-
splitted = recording.split("::")
|
62
|
-
if len(splitted) != 2:
|
63
|
-
print("Invalid --recording option, expected file_name::namespace")
|
64
|
-
raise typer.Exit(1)
|
65
|
-
return {"recording": splitted[0], "namespace": splitted[1]}
|
66
|
-
|
67
61
|
rec = list(map(recording_and_namespace, recording))
|
68
62
|
|
69
63
|
try:
|
@@ -79,7 +73,7 @@ def pause(
|
|
79
73
|
recording: List[str] = typer.Option(..., help="Which recording and which namespace to stop"),
|
80
74
|
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
81
75
|
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
82
|
-
):
|
76
|
+
) -> None:
|
83
77
|
"""
|
84
78
|
Pause recordings that are beeing played on brokers are done with the same syntax as when you start them.
|
85
79
|
|
@@ -88,13 +82,6 @@ def pause(
|
|
88
82
|
remotive broker playback pause --recording myrecording_can0::can0 --recording myrecording_can1::can1
|
89
83
|
"""
|
90
84
|
|
91
|
-
def recording_and_namespace(recording: str):
|
92
|
-
splitted = recording.split("::")
|
93
|
-
if len(splitted) != 2:
|
94
|
-
print("Invalid --recording option, expected file_name::namespace")
|
95
|
-
raise typer.Exit(1)
|
96
|
-
return {"recording": splitted[0], "namespace": splitted[1]}
|
97
|
-
|
98
85
|
rec = list(map(recording_and_namespace, recording))
|
99
86
|
try:
|
100
87
|
broker = Broker(url, api_key)
|
@@ -110,7 +97,7 @@ def seek(
|
|
110
97
|
seconds: float = typer.Option(..., help="Target offset in seconds"),
|
111
98
|
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
112
99
|
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
113
|
-
):
|
100
|
+
) -> None:
|
114
101
|
"""
|
115
102
|
Seeks to a position in seconds into the recording
|
116
103
|
|
@@ -121,13 +108,6 @@ def seek(
|
|
121
108
|
|
122
109
|
broker = Broker(url, api_key)
|
123
110
|
|
124
|
-
def recording_and_namespace(recording: str):
|
125
|
-
splitted = recording.split("::")
|
126
|
-
if len(splitted) != 2:
|
127
|
-
print("Invalid --recording option, expected file_name::namespace")
|
128
|
-
raise typer.Exit(1)
|
129
|
-
return {"recording": splitted[0], "namespace": splitted[1]}
|
130
|
-
|
131
111
|
rec = list(map(recording_and_namespace, recording))
|
132
112
|
|
133
113
|
try:
|
cli/broker/record.py
CHANGED
@@ -9,7 +9,7 @@ from cli.errors import ErrorPrinter
|
|
9
9
|
|
10
10
|
from .lib.broker import Broker
|
11
11
|
|
12
|
-
app = typer.Typer(help=help)
|
12
|
+
app = typer.Typer(help=help) # type: ignore
|
13
13
|
|
14
14
|
|
15
15
|
@app.command()
|
@@ -18,7 +18,7 @@ def start(
|
|
18
18
|
namespace: List[str] = typer.Option(..., help="Namespace to record"),
|
19
19
|
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
20
20
|
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
21
|
-
):
|
21
|
+
) -> None:
|
22
22
|
try:
|
23
23
|
broker = Broker(url, api_key)
|
24
24
|
broker.record_multiple(namespace, filename)
|
@@ -32,7 +32,7 @@ def stop(
|
|
32
32
|
namespace: List[str] = typer.Option(..., help="Namespace to record"),
|
33
33
|
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
34
34
|
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
35
|
-
):
|
35
|
+
) -> None:
|
36
36
|
try:
|
37
37
|
broker = Broker(url, api_key)
|
38
38
|
broker.stop_multiple(namespace, filename)
|
cli/broker/scripting.py
CHANGED
@@ -16,7 +16,7 @@ app = typer.Typer(
|
|
16
16
|
)
|
17
17
|
|
18
18
|
|
19
|
-
def write(signal_name, script):
|
19
|
+
def write(signal_name: str, script: str) -> None:
|
20
20
|
path = f"{signal_name}.lua"
|
21
21
|
f = open(path, "w")
|
22
22
|
f.write(script)
|
@@ -29,7 +29,7 @@ def new_script(
|
|
29
29
|
input_signal: List[str] = typer.Option(..., help="Required input signal names"),
|
30
30
|
output_signal: str = typer.Option(..., help="Name of output signal"),
|
31
31
|
save: bool = typer.Option(False, help="Save file to disk - Default stored as __output_signal__.lua"),
|
32
|
-
):
|
32
|
+
) -> None:
|
33
33
|
def to_subscribable_signal(sig: str) -> tuple[str, str]:
|
34
34
|
arr = sig.split(":")
|
35
35
|
if len(arr) != 2:
|
@@ -39,7 +39,7 @@ def new_script(
|
|
39
39
|
|
40
40
|
signals_to_subscribe_to = list(map(to_subscribable_signal, input_signal))
|
41
41
|
|
42
|
-
def to_local_signal(sig_name: tuple[str, str]):
|
42
|
+
def to_local_signal(sig_name: tuple[str, str]) -> str:
|
43
43
|
t = Template(
|
44
44
|
"""
|
45
45
|
{
|
@@ -51,7 +51,7 @@ def new_script(
|
|
51
51
|
|
52
52
|
local_signals = ",".join(list(map(to_local_signal, signals_to_subscribe_to)))
|
53
53
|
|
54
|
-
def to_subscribe_pattern(sig_name: tuple[str, str]):
|
54
|
+
def to_subscribe_pattern(sig_name: tuple[str, str]) -> str:
|
55
55
|
t = Template(
|
56
56
|
"""
|
57
57
|
if (signals["$sig_name"] ~= nil) then
|
cli/broker/signals.py
CHANGED
@@ -5,10 +5,10 @@ import numbers
|
|
5
5
|
import os
|
6
6
|
import signal as os_signal
|
7
7
|
from pathlib import Path
|
8
|
-
from typing import List, TypedDict
|
8
|
+
from typing import Any, Dict, Iterable, List, TypedDict
|
9
9
|
|
10
10
|
import grpc
|
11
|
-
import plotext as plt
|
11
|
+
import plotext as plt # type: ignore
|
12
12
|
import typer
|
13
13
|
from rich import print as rich_rprint
|
14
14
|
|
@@ -16,7 +16,7 @@ from cli.errors import ErrorPrinter
|
|
16
16
|
|
17
17
|
from .lib.broker import Broker, SubscribableSignal
|
18
18
|
|
19
|
-
app = typer.Typer(help=help)
|
19
|
+
app = typer.Typer(help=help) # type: ignore
|
20
20
|
|
21
21
|
|
22
22
|
# signal_values:list = list()
|
@@ -24,17 +24,25 @@ app = typer.Typer(help=help)
|
|
24
24
|
|
25
25
|
class Signals(TypedDict):
|
26
26
|
name: str
|
27
|
-
signals: List
|
27
|
+
signals: List[Any]
|
28
28
|
|
29
29
|
|
30
|
-
signal_values = {}
|
30
|
+
signal_values: Dict[Any, Any] = {}
|
31
31
|
|
32
32
|
|
33
|
-
@app.command(help="List
|
33
|
+
@app.command(name="list", help="List frame and signal metadata on broker")
|
34
|
+
def list_signals(
|
35
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
36
|
+
api_key: str = typer.Option(None, help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
37
|
+
) -> None:
|
38
|
+
signal_names(url, api_key)
|
39
|
+
|
40
|
+
|
41
|
+
@app.command(help="List signals names on broker", deprecated=True)
|
34
42
|
def signal_names(
|
35
43
|
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
36
44
|
api_key: str = typer.Option(None, help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
37
|
-
):
|
45
|
+
) -> None:
|
38
46
|
try:
|
39
47
|
broker = Broker(url, api_key)
|
40
48
|
# print("Listing available signals")
|
@@ -73,7 +81,7 @@ def subscribe( # noqa: C901
|
|
73
81
|
x_plot: bool = typer.Option(default=False, help="Experimental: Plot the signal in terminal. Note graphs are not " "aligned by time"),
|
74
82
|
x_plot_size: int = typer.Option(default=100, help="Experimental: how many points show for each plot"),
|
75
83
|
# samples: int = typer.Option(default=0, he)
|
76
|
-
):
|
84
|
+
) -> None:
|
77
85
|
"""
|
78
86
|
Subscribe to a selection of signals
|
79
87
|
|
@@ -100,10 +108,10 @@ def subscribe( # noqa: C901
|
|
100
108
|
|
101
109
|
plt.title("Signals")
|
102
110
|
|
103
|
-
def exit_on_ctrlc(sig, frame):
|
111
|
+
def exit_on_ctrlc(sig: Any, frame: Any) -> None:
|
104
112
|
os._exit(0)
|
105
113
|
|
106
|
-
def on_frame_plot(x):
|
114
|
+
def on_frame_plot(x: Iterable[Any]) -> None:
|
107
115
|
global signal_values
|
108
116
|
|
109
117
|
plt.clt() # to clear the terminal
|
@@ -144,7 +152,7 @@ def subscribe( # noqa: C901
|
|
144
152
|
plt.sleep(0.001) # to add
|
145
153
|
plt.show()
|
146
154
|
|
147
|
-
def on_frame_print(x):
|
155
|
+
def on_frame_print(x: Iterable[Any]) -> None:
|
148
156
|
rich_rprint(json.dumps(list(x)))
|
149
157
|
|
150
158
|
os_signal.signal(os_signal.SIGINT, exit_on_ctrlc)
|
@@ -181,7 +189,7 @@ def subscribe( # noqa: C901
|
|
181
189
|
def namespaces(
|
182
190
|
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
183
191
|
api_key: str = typer.Option(None, help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
184
|
-
):
|
192
|
+
) -> None:
|
185
193
|
try:
|
186
194
|
broker = Broker(url, api_key)
|
187
195
|
namespaces_json = broker.list_namespaces()
|
cli/cloud/__init__.py
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
|
cli/cloud/auth.py
CHANGED
@@ -4,29 +4,33 @@ import webbrowser
|
|
4
4
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
5
5
|
from pathlib import Path
|
6
6
|
from threading import Thread
|
7
|
+
from typing import Any
|
7
8
|
|
8
9
|
import typer
|
10
|
+
from typing_extensions import override
|
9
11
|
|
10
12
|
from cli import settings
|
11
13
|
|
12
14
|
from . import auth_tokens
|
13
|
-
from . import
|
15
|
+
from .rest_helper import RestHelper as Rest
|
14
16
|
|
15
|
-
|
17
|
+
APA = settings.CONFIG_DIR_NAME
|
16
18
|
|
17
|
-
|
19
|
+
HELP = """
|
18
20
|
Manage how you authenticate with our cloud platform
|
19
21
|
"""
|
20
22
|
|
21
|
-
|
23
|
+
httpd: HTTPServer
|
24
|
+
|
25
|
+
app = typer.Typer(help=HELP)
|
22
26
|
|
23
27
|
app.add_typer(auth_tokens.app, name="tokens", help="Manage users personal access tokens")
|
24
|
-
|
25
|
-
|
28
|
+
CONFIG_DIR_NAME = settings.CONFIG_DIR_NAME # str(Path.home()) + "/.config/.remotive/"
|
29
|
+
TOKEN_FILE_NAME = settings.TOKEN_FILE_NAME # str(Path.home()) + "/.config/.remotive/cloud.secret.token"
|
26
30
|
|
27
31
|
|
28
32
|
class S(BaseHTTPRequestHandler):
|
29
|
-
def _set_response(self):
|
33
|
+
def _set_response(self) -> None:
|
30
34
|
self.send_response(200)
|
31
35
|
# self.send_response(301)
|
32
36
|
# self.send_header('Location', 'https://cloud.remotivelabs.com')
|
@@ -34,10 +38,13 @@ class S(BaseHTTPRequestHandler):
|
|
34
38
|
self.send_header("Content-type", "text/html")
|
35
39
|
self.end_headers()
|
36
40
|
|
37
|
-
def log_message(self, format, *args):
|
41
|
+
def log_message(self, format: Any, *args: Any) -> None: # pylint: disable=W0622
|
38
42
|
return
|
39
43
|
|
40
|
-
|
44
|
+
# Please do not change this into lowercase!
|
45
|
+
@override
|
46
|
+
# type: ignore
|
47
|
+
def do_GET(self): # pylint: disable=invalid-name,
|
41
48
|
self._set_response()
|
42
49
|
self.wfile.write("Successfully setup CLI, return to your terminal to continue".encode("utf-8"))
|
43
50
|
path = self.path
|
@@ -47,15 +54,15 @@ class S(BaseHTTPRequestHandler):
|
|
47
54
|
killerthread = Thread(target=httpd.shutdown)
|
48
55
|
killerthread.start()
|
49
56
|
|
50
|
-
if not os.path.exists(
|
51
|
-
os.makedirs(
|
57
|
+
if not os.path.exists(CONFIG_DIR_NAME):
|
58
|
+
os.makedirs(CONFIG_DIR_NAME)
|
52
59
|
write_token(path[1:])
|
53
60
|
print("Successfully logged on, you are ready to go with cli")
|
54
61
|
|
55
62
|
|
56
|
-
def start_local_webserver(server_class=HTTPServer, handler_class=S, port=0):
|
63
|
+
def start_local_webserver(server_class: type = HTTPServer, handler_class: type = S, port: int = 0) -> None:
|
57
64
|
server_address = ("", port)
|
58
|
-
global httpd
|
65
|
+
global httpd # pylint: disable=W0603
|
59
66
|
httpd = server_class(server_address, handler_class)
|
60
67
|
|
61
68
|
|
@@ -65,7 +72,7 @@ def start_local_webserver(server_class=HTTPServer, handler_class=S, port=0):
|
|
65
72
|
|
66
73
|
|
67
74
|
@app.command(name="login")
|
68
|
-
def login():
|
75
|
+
def login() -> None:
|
69
76
|
"""
|
70
77
|
Login to the cli using browser
|
71
78
|
|
@@ -73,20 +80,21 @@ def login():
|
|
73
80
|
be the same as activating a personal access key or service-account access key.
|
74
81
|
"""
|
75
82
|
start_local_webserver()
|
76
|
-
webbrowser.open(f"{
|
83
|
+
webbrowser.open(f"{Rest.get_base_url()}/login?redirectUrl=http://localhost:{httpd.server_address[1]}", new=1, autoraise=True)
|
84
|
+
|
77
85
|
httpd.serve_forever()
|
78
86
|
|
79
87
|
|
80
88
|
@app.command()
|
81
|
-
def whoami():
|
89
|
+
def whoami() -> None:
|
82
90
|
"""
|
83
91
|
Validates authentication and fetches your user information
|
84
92
|
"""
|
85
|
-
|
93
|
+
Rest.handle_get("/api/whoami")
|
86
94
|
|
87
95
|
|
88
96
|
@app.command()
|
89
|
-
def print_access_token():
|
97
|
+
def print_access_token() -> None:
|
90
98
|
"""
|
91
99
|
Print current active access token
|
92
100
|
"""
|
@@ -94,36 +102,33 @@ def print_access_token():
|
|
94
102
|
|
95
103
|
|
96
104
|
@app.command(help="Clear access token")
|
97
|
-
def logout():
|
98
|
-
os.remove(settings.
|
105
|
+
def logout() -> None:
|
106
|
+
os.remove(settings.TOKEN_FILE_NAME)
|
99
107
|
print("Access token removed")
|
100
108
|
|
101
109
|
|
102
|
-
def read_token():
|
110
|
+
def read_token() -> str:
|
103
111
|
# f = open(token_file_name, "r")
|
104
112
|
# token = f.read()
|
105
113
|
# f.close()
|
106
114
|
return settings.read_token()
|
107
115
|
|
108
116
|
|
109
|
-
def read_file_with_path(file):
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
return token
|
117
|
+
def read_file_with_path(file: str) -> str:
|
118
|
+
with open(file, "r", encoding="utf8") as f:
|
119
|
+
token = f.read()
|
120
|
+
return token
|
114
121
|
|
115
122
|
|
116
|
-
def read_file(file):
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
return token
|
123
|
+
def read_file(file: str) -> str:
|
124
|
+
with open(str(Path.home()) + f"/.config/.remotive/{file}", "r", encoding="utf8") as f:
|
125
|
+
token = f.read()
|
126
|
+
return token
|
121
127
|
|
122
128
|
|
123
|
-
def write_token(token):
|
124
|
-
|
125
|
-
|
126
|
-
f.close()
|
129
|
+
def write_token(token: str) -> None:
|
130
|
+
with open(TOKEN_FILE_NAME, "w", encoding="utf8") as f:
|
131
|
+
f.write(token)
|
127
132
|
|
128
133
|
|
129
134
|
# Key stuff
|
cli/cloud/auth_tokens.py
CHANGED
@@ -1,22 +1,26 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
3
|
import sys
|
4
|
+
from json.decoder import JSONDecodeError
|
4
5
|
from pathlib import Path
|
5
6
|
|
6
7
|
import typer
|
7
8
|
|
8
|
-
from . import
|
9
|
+
from .rest_helper import RestHelper as Rest
|
9
10
|
|
10
11
|
app = typer.Typer()
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
TOKEN_FILE_NAME = str(Path.home()) + "/.config/.remotive/cloud.secret.token"
|
14
|
+
CONFIG_DIR_NAME = str(Path.home()) + "/.config/.remotive/"
|
14
15
|
|
15
16
|
|
16
17
|
@app.command(name="create", help="Create and download a new personal access token")
|
17
|
-
def get_personal_access_token(activate: bool = typer.Option(False, help="Activate the token for use after download")):
|
18
|
-
|
19
|
-
response =
|
18
|
+
def get_personal_access_token(activate: bool = typer.Option(False, help="Activate the token for use after download")) -> None: # pylint: disable=W0621
|
19
|
+
Rest.ensure_auth_token()
|
20
|
+
response = Rest.handle_post(url="/api/me/keys", return_response=True)
|
21
|
+
|
22
|
+
if response is None:
|
23
|
+
return
|
20
24
|
|
21
25
|
if response.status_code == 200:
|
22
26
|
name = response.json()["name"]
|
@@ -34,19 +38,42 @@ def get_personal_access_token(activate: bool = typer.Option(False, help="Activat
|
|
34
38
|
|
35
39
|
|
36
40
|
@app.command(name="list", help="List personal access tokens")
|
37
|
-
def list_personal_access_tokens():
|
38
|
-
|
39
|
-
|
41
|
+
def list_personal_access_tokens() -> None:
|
42
|
+
Rest.ensure_auth_token()
|
43
|
+
Rest.handle_get("/api/me/keys")
|
44
|
+
|
40
45
|
|
46
|
+
@app.command(name="revoke")
|
47
|
+
def revoke(name_or_file: str = typer.Argument(help="Name or file path of the access token to revoke")) -> None:
|
48
|
+
"""
|
49
|
+
Revoke an access token by token name or path to a file containing that token
|
41
50
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
51
|
+
Name is found in the json file
|
52
|
+
|
53
|
+
{
|
54
|
+
"expires": "2034-07-31",
|
55
|
+
"token": "xxx",
|
56
|
+
"created": "2024-07-31T09:18:50.406+02:00",
|
57
|
+
"name": "token_name"
|
58
|
+
}
|
59
|
+
"""
|
60
|
+
name = name_or_file
|
61
|
+
if "." in name_or_file:
|
62
|
+
json_str = read_file(name_or_file)
|
63
|
+
try:
|
64
|
+
name = json.loads(json_str)["name"]
|
65
|
+
except JSONDecodeError:
|
66
|
+
sys.stderr.write("Failed to parse json, make sure its a correct access token file\n")
|
67
|
+
sys.exit(1)
|
68
|
+
except KeyError:
|
69
|
+
sys.stderr.write("Json does not contain a name property, make sure its a correct access token file\n")
|
70
|
+
sys.exit(1)
|
71
|
+
Rest.ensure_auth_token()
|
72
|
+
Rest.handle_delete(f"/api/me/keys/{name}")
|
46
73
|
|
47
74
|
|
48
75
|
@app.command()
|
49
|
-
def describe(file: str = typer.
|
76
|
+
def describe(file: str = typer.Argument(help="File name")) -> None:
|
50
77
|
"""
|
51
78
|
Show contents of specified access token file
|
52
79
|
"""
|
@@ -54,7 +81,7 @@ def describe(file: str = typer.Option(..., help="File name")):
|
|
54
81
|
|
55
82
|
|
56
83
|
@app.command()
|
57
|
-
def activate(file: str = typer.Argument(..., help="File name")):
|
84
|
+
def activate(file: str = typer.Argument(..., help="File name")) -> None:
|
58
85
|
"""
|
59
86
|
Activate a access token file to be used for authentication.
|
60
87
|
|
@@ -66,7 +93,7 @@ def activate(file: str = typer.Argument(..., help="File name")):
|
|
66
93
|
do_activate(file)
|
67
94
|
|
68
95
|
|
69
|
-
def do_activate(file: str):
|
96
|
+
def do_activate(file: str) -> None:
|
70
97
|
# Best effort to read file
|
71
98
|
if os.path.exists(file):
|
72
99
|
token_file = json.loads(read_file_with_path(file))
|
@@ -79,38 +106,47 @@ def do_activate(file: str):
|
|
79
106
|
|
80
107
|
|
81
108
|
@app.command(name="list-files")
|
82
|
-
def list_files():
|
109
|
+
def list_files() -> None:
|
83
110
|
"""
|
84
111
|
List personal access token files in remotivelabs config directory
|
85
112
|
"""
|
86
|
-
personal_files = filter(lambda f: f.startswith("personal"), os.listdir(
|
113
|
+
personal_files = filter(lambda f: f.startswith("personal"), os.listdir(CONFIG_DIR_NAME))
|
87
114
|
for file in personal_files:
|
88
115
|
print(file)
|
89
116
|
|
90
117
|
|
91
|
-
def read_file(file):
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
118
|
+
def read_file(file: str) -> str:
|
119
|
+
"""
|
120
|
+
Reads a file using file path or if that does not exist check under ~/.config/.remotive
|
121
|
+
"""
|
122
|
+
path = file
|
123
|
+
if not Path(file).exists():
|
124
|
+
path = str(Path.home()) + f"/.config/.remotive/{file}"
|
125
|
+
if not Path(path).exists():
|
126
|
+
sys.stderr.write(f"Failed to find file using {file} or {path}\n")
|
127
|
+
sys.exit(1)
|
128
|
+
with open(path, "r", encoding="utf8") as f:
|
129
|
+
token = f.read()
|
130
|
+
f.close()
|
131
|
+
return token
|
96
132
|
|
97
133
|
|
98
|
-
def read_file_with_path(file):
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
134
|
+
def read_file_with_path(file: str) -> str:
|
135
|
+
with open(file, "r", encoding="utf8") as f:
|
136
|
+
token = f.read()
|
137
|
+
f.close()
|
138
|
+
return token
|
103
139
|
|
104
140
|
|
105
|
-
def write_token(token):
|
106
|
-
|
107
|
-
|
108
|
-
|
141
|
+
def write_token(token: str) -> None:
|
142
|
+
with open(TOKEN_FILE_NAME, "w", encoding="utf8") as f:
|
143
|
+
f.write(token)
|
144
|
+
f.close()
|
109
145
|
|
110
146
|
|
111
|
-
def write_personal_token(file: str, token: str):
|
147
|
+
def write_personal_token(file: str, token: str) -> str:
|
112
148
|
path = str(Path.home()) + f"/.config/.remotive/{file}"
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
149
|
+
with open(path, "w", encoding="utf8") as f:
|
150
|
+
f.write(token)
|
151
|
+
f.close()
|
152
|
+
return path
|