remotivelabs-cli 0.0.27__py3-none-any.whl → 0.0.29__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/export.py +1 -1
- cli/broker/lib/broker.py +3 -4
- cli/broker/signals.py +1 -1
- cli/cloud/auth.py +10 -53
- cli/cloud/auth_tokens.py +39 -48
- cli/cloud/brokers.py +3 -3
- cli/cloud/filestorage.py +20 -5
- cli/cloud/rest_helper.py +7 -3
- cli/cloud/resumable_upload.py +24 -1
- cli/cloud/service_account_tokens.py +10 -12
- cli/connect/protopie/protopie.py +1 -1
- cli/remotive.py +1 -2
- cli/settings.py +47 -8
- {remotivelabs_cli-0.0.27.dist-info → remotivelabs_cli-0.0.29.dist-info}/METADATA +1 -1
- {remotivelabs_cli-0.0.27.dist-info → remotivelabs_cli-0.0.29.dist-info}/RECORD +18 -20
- cli/__about__.py +0 -4
- cli/requirements.txt +0 -12
- {remotivelabs_cli-0.0.27.dist-info → remotivelabs_cli-0.0.29.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.0.27.dist-info → remotivelabs_cli-0.0.29.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.0.27.dist-info → remotivelabs_cli-0.0.29.dist-info}/entry_points.txt +0 -0
cli/broker/export.py
CHANGED
cli/broker/lib/broker.py
CHANGED
@@ -50,7 +50,7 @@ class Broker:
|
|
50
50
|
|
51
51
|
if api_key is None or api_key == "":
|
52
52
|
if url.startswith("https"):
|
53
|
-
self.intercept_channel = br.create_channel(url, None, settings.
|
53
|
+
self.intercept_channel = br.create_channel(url, None, settings.read_secret_token())
|
54
54
|
# TODO - Temporary solution to print proper error message, remove ENV once api-key is gone
|
55
55
|
os.environ["ACCESS_TOKEN"] = "true"
|
56
56
|
else:
|
@@ -261,7 +261,7 @@ class Broker:
|
|
261
261
|
keep_running = True
|
262
262
|
keep_running_during_recording = True
|
263
263
|
|
264
|
-
def exit_on_ctrlc(
|
264
|
+
def exit_on_ctrlc(_sig: Any, _frame: Any) -> None:
|
265
265
|
nonlocal keep_running
|
266
266
|
keep_running = False
|
267
267
|
nonlocal keep_running_during_recording
|
@@ -416,8 +416,7 @@ class Broker:
|
|
416
416
|
)
|
417
417
|
thread.start()
|
418
418
|
# wait for subscription to settle
|
419
|
-
|
420
|
-
return subscription # , thread
|
419
|
+
return self.q.get()
|
421
420
|
|
422
421
|
def validate_and_get_subscribed_signals(
|
423
422
|
self, subscribed_namespaces: List[str], subscribed_signals: List[str]
|
cli/broker/signals.py
CHANGED
cli/cloud/auth.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
import os
|
2
1
|
import time
|
3
2
|
import webbrowser
|
4
3
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
5
|
-
from pathlib import Path
|
6
4
|
from threading import Thread
|
7
5
|
from typing import Any
|
8
6
|
|
@@ -14,8 +12,6 @@ from cli import settings
|
|
14
12
|
from . import auth_tokens
|
15
13
|
from .rest_helper import RestHelper as Rest
|
16
14
|
|
17
|
-
APA = settings.CONFIG_DIR_NAME
|
18
|
-
|
19
15
|
HELP = """
|
20
16
|
Manage how you authenticate with our cloud platform
|
21
17
|
"""
|
@@ -23,22 +19,17 @@ Manage how you authenticate with our cloud platform
|
|
23
19
|
httpd: HTTPServer
|
24
20
|
|
25
21
|
app = typer.Typer(help=HELP)
|
26
|
-
|
27
22
|
app.add_typer(auth_tokens.app, name="tokens", help="Manage users personal access tokens")
|
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"
|
30
23
|
|
31
24
|
|
32
25
|
class S(BaseHTTPRequestHandler):
|
33
26
|
def _set_response(self) -> None:
|
34
27
|
self.send_response(200)
|
35
|
-
# self.send_response(301)
|
36
|
-
# self.send_header('Location', 'https://cloud.remotivelabs.com')
|
37
|
-
# self.end_headers()
|
38
28
|
self.send_header("Content-type", "text/html")
|
39
29
|
self.end_headers()
|
40
30
|
|
41
|
-
|
31
|
+
@override
|
32
|
+
def log_message(self, format: Any, *args: Any) -> None: # pylint: disable=W0622,
|
42
33
|
return
|
43
34
|
|
44
35
|
# Please do not change this into lowercase!
|
@@ -54,9 +45,7 @@ class S(BaseHTTPRequestHandler):
|
|
54
45
|
killerthread = Thread(target=httpd.shutdown)
|
55
46
|
killerthread.start()
|
56
47
|
|
57
|
-
|
58
|
-
os.makedirs(CONFIG_DIR_NAME)
|
59
|
-
write_token(path[1:])
|
48
|
+
settings.write_secret_token(path[1:])
|
60
49
|
print("Successfully logged on, you are ready to go with cli")
|
61
50
|
|
62
51
|
|
@@ -80,7 +69,11 @@ def login() -> None:
|
|
80
69
|
be the same as activating a personal access key or service-account access key.
|
81
70
|
"""
|
82
71
|
start_local_webserver()
|
83
|
-
webbrowser.open(
|
72
|
+
webbrowser.open(
|
73
|
+
f"{Rest.get_base_url()}/login?redirectUrl=http://localhost:{httpd.server_address[1]}",
|
74
|
+
new=1,
|
75
|
+
autoraise=True,
|
76
|
+
)
|
84
77
|
|
85
78
|
httpd.serve_forever()
|
86
79
|
|
@@ -98,46 +91,10 @@ def print_access_token() -> None:
|
|
98
91
|
"""
|
99
92
|
Print current active access token
|
100
93
|
"""
|
101
|
-
print(
|
94
|
+
print(settings.read_secret_token())
|
102
95
|
|
103
96
|
|
104
97
|
@app.command(help="Clear access token")
|
105
98
|
def logout() -> None:
|
106
|
-
|
99
|
+
settings.clear_secret_token()
|
107
100
|
print("Access token removed")
|
108
|
-
|
109
|
-
|
110
|
-
def read_token() -> str:
|
111
|
-
# f = open(token_file_name, "r")
|
112
|
-
# token = f.read()
|
113
|
-
# f.close()
|
114
|
-
return settings.read_token()
|
115
|
-
|
116
|
-
|
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
|
121
|
-
|
122
|
-
|
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
|
127
|
-
|
128
|
-
|
129
|
-
def write_token(token: str) -> None:
|
130
|
-
with open(TOKEN_FILE_NAME, "w", encoding="utf8") as f:
|
131
|
-
f.write(token)
|
132
|
-
|
133
|
-
|
134
|
-
# Key stuff
|
135
|
-
# f = open(str(Path.home())+ "/.remotivelabs/privatekey.json", "r")
|
136
|
-
# j = json.loads(f.read())
|
137
|
-
# print(j['privateKey'])
|
138
|
-
# key = load_pem_private_key(bytes(j['privateKey'],'UTF-8'), None)
|
139
|
-
# print(key.key_size)
|
140
|
-
#
|
141
|
-
# "exp": datetime.now(tz=timezone.utc)
|
142
|
-
# encoded = jwt.encode({"some": "payload"}, j['privateKey'] , algorithm="RS256", headers={"kid": j["keyId"]})
|
143
|
-
# print(encoded)
|
cli/cloud/auth_tokens.py
CHANGED
@@ -1,18 +1,16 @@
|
|
1
1
|
import json
|
2
|
-
import os
|
3
2
|
import sys
|
4
3
|
from json.decoder import JSONDecodeError
|
5
4
|
from pathlib import Path
|
6
5
|
|
7
6
|
import typer
|
8
7
|
|
8
|
+
from cli import settings
|
9
|
+
|
9
10
|
from .rest_helper import RestHelper as Rest
|
10
11
|
|
11
12
|
app = typer.Typer()
|
12
13
|
|
13
|
-
TOKEN_FILE_NAME = str(Path.home()) + "/.config/.remotive/cloud.secret.token"
|
14
|
-
CONFIG_DIR_NAME = str(Path.home()) + "/.config/.remotive/"
|
15
|
-
|
16
14
|
|
17
15
|
@app.command(name="create", help="Create and download a new personal access token")
|
18
16
|
def get_personal_access_token(activate: bool = typer.Option(False, help="Activate the token for use after download")) -> None: # pylint: disable=W0621
|
@@ -24,12 +22,12 @@ def get_personal_access_token(activate: bool = typer.Option(False, help="Activat
|
|
24
22
|
|
25
23
|
if response.status_code == 200:
|
26
24
|
name = response.json()["name"]
|
27
|
-
|
28
|
-
print(f"Personal access token written to {
|
25
|
+
pat_path = write_personal_token(name, response.text)
|
26
|
+
print(f"Personal access token written to {pat_path}")
|
29
27
|
if not activate:
|
30
|
-
print(f"Use 'remotive cloud auth tokens activate {
|
28
|
+
print(f"Use 'remotive cloud auth tokens activate {pat_path.name}' to use this access token from cli")
|
31
29
|
else:
|
32
|
-
do_activate(
|
30
|
+
do_activate(str(pat_path))
|
33
31
|
print("Token file activated and ready for use")
|
34
32
|
print("\033[93m This file contains secrets and must be kept safe")
|
35
33
|
else:
|
@@ -46,16 +44,17 @@ def list_personal_access_tokens() -> None:
|
|
46
44
|
@app.command(name="revoke")
|
47
45
|
def revoke(name_or_file: str = typer.Argument(help="Name or file path of the access token to revoke")) -> None:
|
48
46
|
"""
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
47
|
+
Revoke an access token by token name or path to a file containing that token
|
48
|
+
|
49
|
+
Name is found in the json file
|
50
|
+
```
|
51
|
+
{
|
52
|
+
"expires": "2034-07-31",
|
53
|
+
"token": "xxx",
|
54
|
+
"created": "2024-07-31T09:18:50.406+02:00",
|
55
|
+
"name": "token_name"
|
58
56
|
}
|
57
|
+
```
|
59
58
|
"""
|
60
59
|
name = name_or_file
|
61
60
|
if "." in name_or_file:
|
@@ -93,14 +92,15 @@ def activate(file: str = typer.Argument(..., help="File name")) -> None:
|
|
93
92
|
do_activate(file)
|
94
93
|
|
95
94
|
|
95
|
+
# TODO: Move parts of this to settings # pylint: disable=W0511
|
96
96
|
def do_activate(file: str) -> None:
|
97
97
|
# Best effort to read file
|
98
|
-
if
|
99
|
-
token_file = json.loads(read_file_with_path(file))
|
100
|
-
|
101
|
-
elif
|
98
|
+
if Path(file).exists():
|
99
|
+
token_file = json.loads(read_file_with_path(Path(file)))
|
100
|
+
settings.write_secret_token(token_file["token"])
|
101
|
+
elif (settings.CONFIG_DIR_PATH / file).exists():
|
102
102
|
token_file = json.loads(read_file(file))
|
103
|
-
|
103
|
+
settings.write_secret_token(token_file["token"])
|
104
104
|
else:
|
105
105
|
sys.stderr.write("File could not be found \n")
|
106
106
|
|
@@ -110,43 +110,34 @@ def list_files() -> None:
|
|
110
110
|
"""
|
111
111
|
List personal access token files in remotivelabs config directory
|
112
112
|
"""
|
113
|
-
personal_files =
|
113
|
+
personal_files = settings.list_personal_token_files()
|
114
114
|
for file in personal_files:
|
115
115
|
print(file)
|
116
116
|
|
117
117
|
|
118
|
+
# TODO: Move to settings # pylint: disable=W0511
|
118
119
|
def read_file(file: str) -> str:
|
119
120
|
"""
|
120
|
-
Reads a file using file path or if that does not exist check
|
121
|
+
Reads a file using file path or if that does not exist check in config directory
|
121
122
|
"""
|
122
|
-
path = file
|
123
|
-
if not
|
124
|
-
path =
|
125
|
-
if not
|
123
|
+
path = Path(file)
|
124
|
+
if not path.exists():
|
125
|
+
path = settings.CONFIG_DIR_PATH / file
|
126
|
+
if not path.exists():
|
126
127
|
sys.stderr.write(f"Failed to find file using {file} or {path}\n")
|
127
128
|
sys.exit(1)
|
128
|
-
with open(path, "r", encoding="utf8") as f:
|
129
|
-
token = f.read()
|
130
|
-
f.close()
|
131
|
-
return token
|
132
|
-
|
133
129
|
|
134
|
-
|
135
|
-
with open(file, "r", encoding="utf8") as f:
|
136
|
-
token = f.read()
|
137
|
-
f.close()
|
138
|
-
return token
|
130
|
+
return read_file_with_path(path)
|
139
131
|
|
140
132
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
f.
|
133
|
+
# TODO: Move to settings # pylint: disable=W0511
|
134
|
+
def read_file_with_path(path: Path) -> str:
|
135
|
+
with open(path, "r", encoding="utf8") as f:
|
136
|
+
return f.read()
|
145
137
|
|
146
138
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
return path
|
139
|
+
# TODO: Move to settings # pylint: disable=W0511
|
140
|
+
def write_personal_token(name: str, token: str) -> Path:
|
141
|
+
file = f"personal-token-{name}.json"
|
142
|
+
path = settings.CONFIG_DIR_PATH / file
|
143
|
+
return settings._write_settings_file(path, token) # pylint: disable=W0212
|
cli/cloud/brokers.py
CHANGED
@@ -83,16 +83,16 @@ def logs(
|
|
83
83
|
|
84
84
|
"""
|
85
85
|
|
86
|
-
def exit_on_ctrlc(
|
86
|
+
def exit_on_ctrlc(_sig: Any, _frame: Any) -> None:
|
87
87
|
wsapp.close()
|
88
88
|
os._exit(0)
|
89
89
|
|
90
90
|
os_signal.signal(os_signal.SIGINT, exit_on_ctrlc)
|
91
91
|
|
92
|
-
def on_message(
|
92
|
+
def on_message(_wsapp: Any, message: str) -> None:
|
93
93
|
print(message)
|
94
94
|
|
95
|
-
def on_error(
|
95
|
+
def on_error(_wsapp: Any, err: str) -> None: # pylint: disable=W0613
|
96
96
|
print("EXAMPLE error encountered: ", err)
|
97
97
|
|
98
98
|
Rest.ensure_auth_token()
|
cli/cloud/filestorage.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os.path
|
2
4
|
import sys
|
3
5
|
from pathlib import Path
|
@@ -96,11 +98,24 @@ def copy_file( # noqa: C901 # type: ignore[too-many-branches] # pylint: disabl
|
|
96
98
|
if source.startswith("rcs://"):
|
97
99
|
__copy_to_local(source=source, dest=dest, project=project)
|
98
100
|
else:
|
99
|
-
|
101
|
+
path = Path(source)
|
102
|
+
if path.is_dir():
|
103
|
+
print("is dir")
|
104
|
+
for file_path in path.rglob("*"):
|
105
|
+
if file_path.is_file():
|
106
|
+
print(file_path)
|
107
|
+
__copy_to_remote(source=source, dest=dest, project=project)
|
108
|
+
sys.exit(1)
|
109
|
+
else:
|
110
|
+
__copy_to_remote(source=source, dest=dest, project=project)
|
100
111
|
|
101
112
|
|
102
113
|
def __copy_to_remote(source: str, dest: str, project: str) -> None:
|
103
114
|
path = Path(source)
|
115
|
+
if path.is_dir():
|
116
|
+
print("is dir")
|
117
|
+
sys.exit(1)
|
118
|
+
|
104
119
|
if not path.exists():
|
105
120
|
ErrorPrinter.print_hint("Source file does not exist")
|
106
121
|
sys.exit(1)
|
@@ -111,11 +126,11 @@ def __copy_to_remote(source: str, dest: str, project: str) -> None:
|
|
111
126
|
res = Rest.handle_post(f"/api/project/{project}/files/storage{rcs_path}", return_response=True)
|
112
127
|
if res is None:
|
113
128
|
return
|
114
|
-
|
115
|
-
url =
|
116
|
-
|
129
|
+
json_res = res.json()
|
130
|
+
url = json_res["url"]
|
131
|
+
headers = json_res["headers"]
|
117
132
|
try:
|
118
|
-
upload.upload_signed_url(url, source,
|
133
|
+
upload.upload_signed_url(url, source, headers)
|
119
134
|
except IsADirectoryError:
|
120
135
|
ErrorPrinter.print_hint(f"Supplied source file '{source}' is a directory but must be a file")
|
121
136
|
|
cli/cloud/rest_helper.py
CHANGED
@@ -8,7 +8,7 @@ import shutil
|
|
8
8
|
import sys
|
9
9
|
from importlib.metadata import version
|
10
10
|
from pathlib import Path
|
11
|
-
from typing import Any, Dict, List, Union
|
11
|
+
from typing import Any, BinaryIO, Dict, List, Union, cast
|
12
12
|
|
13
13
|
import requests
|
14
14
|
from requests.exceptions import JSONDecodeError
|
@@ -87,7 +87,7 @@ class RestHelper:
|
|
87
87
|
# raise typer.Exit()
|
88
88
|
|
89
89
|
# f = open(str(Path.home()) + "/.config/.remotive/cloud.secret.token", "r")
|
90
|
-
token = settings.
|
90
|
+
token = settings.read_secret_token()
|
91
91
|
# os.environ['REMOTIVE_CLOUD_AUTH_TOKEN'] = token
|
92
92
|
|
93
93
|
RestHelper.__headers["Authorization"] = "Bearer " + token.strip()
|
@@ -256,8 +256,12 @@ class RestHelper:
|
|
256
256
|
if download_resp.status_code == 200:
|
257
257
|
content_length = int(download_resp.headers["Content-Length"])
|
258
258
|
with open(save_file_name, "wb") as out_file:
|
259
|
+
stream = cast(BinaryIO, download_resp.raw) # we know this is a binary stream, as stream=True is set in the request
|
259
260
|
with wrap_file(
|
260
|
-
|
261
|
+
stream,
|
262
|
+
content_length,
|
263
|
+
refresh_per_second=100,
|
264
|
+
description=f"Downloading to {save_file_name}",
|
261
265
|
) as stream_with_progress:
|
262
266
|
shutil.copyfileobj(stream_with_progress, out_file)
|
263
267
|
else:
|
cli/cloud/resumable_upload.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os
|
2
4
|
import sys
|
5
|
+
from typing import Dict
|
3
6
|
|
4
7
|
import requests
|
5
8
|
from rich.progress import wrap_file
|
@@ -21,11 +24,12 @@ def __get_uploaded_bytes(upload_url: str) -> int:
|
|
21
24
|
return 0
|
22
25
|
|
23
26
|
|
24
|
-
def
|
27
|
+
def with_resumable_upload_signed_url(signed_url: str, source_file_name: str, content_type: str) -> None:
|
25
28
|
"""
|
26
29
|
Upload file to file storage with signed url and resumable upload.
|
27
30
|
Resumable upload will only work with the same URL and not if a new signed URL is requested with the
|
28
31
|
same object id.
|
32
|
+
:param content_type:
|
29
33
|
:param signed_url:
|
30
34
|
:param source_file_name:
|
31
35
|
:return:
|
@@ -60,3 +64,22 @@ def upload_signed_url(signed_url: str, source_file_name: str, content_type: str)
|
|
60
64
|
sys.exit(1)
|
61
65
|
|
62
66
|
print(f"File {source_file_name} uploaded successfully.")
|
67
|
+
|
68
|
+
|
69
|
+
def upload_signed_url(signed_url: str, source_file_name: str, headers: Dict[str, str]) -> None:
|
70
|
+
"""
|
71
|
+
Upload file to file storage with signed url and resumable upload.
|
72
|
+
Resumable upload will only work with the same URL and not if a new signed URL is requested with the
|
73
|
+
same object id.
|
74
|
+
:param headers:
|
75
|
+
:param signed_url:
|
76
|
+
:param source_file_name:
|
77
|
+
:return:
|
78
|
+
"""
|
79
|
+
with open(source_file_name, "rb") as f:
|
80
|
+
response = requests.put(signed_url, headers=headers, timeout=60, data=f)
|
81
|
+
if response.status_code not in (200, 201, 308):
|
82
|
+
ErrorPrinter.print_generic_error(f"Failed to upload file: {response.status_code} - {response.text}")
|
83
|
+
sys.exit(1)
|
84
|
+
|
85
|
+
print(f"File {source_file_name} uploaded successfully.")
|
@@ -1,16 +1,14 @@
|
|
1
1
|
import json
|
2
|
-
import os
|
3
2
|
from pathlib import Path
|
4
3
|
|
5
4
|
import typer
|
6
5
|
|
6
|
+
from cli import settings
|
7
|
+
|
7
8
|
from .rest_helper import RestHelper as Rest
|
8
9
|
|
9
10
|
app = typer.Typer()
|
10
11
|
|
11
|
-
CONFIG_DIR_NAME = str(Path.home()) + "/.config/.remotive/"
|
12
|
-
TOKEN_FILE_NAME = str(Path.home()) + "/.config/.remotive/cloud.secret.token2"
|
13
|
-
|
14
12
|
|
15
13
|
@app.command(name="create", help="Create new access token")
|
16
14
|
def create(
|
@@ -29,7 +27,7 @@ def create(
|
|
29
27
|
|
30
28
|
if response.status_code == 200:
|
31
29
|
name = response.json()["name"]
|
32
|
-
|
30
|
+
write_sa_token(service_account, name, response.text)
|
33
31
|
else:
|
34
32
|
print(f"Got status code: {response.status_code}")
|
35
33
|
print(response.text)
|
@@ -48,8 +46,8 @@ def list_files() -> None:
|
|
48
46
|
"""
|
49
47
|
List personal access token files in remotivelabs config directory
|
50
48
|
"""
|
51
|
-
|
52
|
-
for file in
|
49
|
+
sa_files = settings.list_service_account_token_files()
|
50
|
+
for file in sa_files:
|
53
51
|
print(file)
|
54
52
|
|
55
53
|
|
@@ -62,8 +60,8 @@ def revoke(
|
|
62
60
|
Rest.handle_delete(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}")
|
63
61
|
|
64
62
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
# TODO: Move to settings # pylint: disable=W0511
|
64
|
+
def write_sa_token(service_account: str, name: str, token: str) -> Path:
|
65
|
+
file = f"service-account-{service_account}-{name}-token.json"
|
66
|
+
path = settings.CONFIG_DIR_PATH / file
|
67
|
+
return settings._write_settings_file(path, token) # pylint: disable=W0212
|
cli/connect/protopie/protopie.py
CHANGED
@@ -167,7 +167,7 @@ def do_connect(
|
|
167
167
|
if broker_url.startswith("https"):
|
168
168
|
if api_key is None:
|
169
169
|
print("No --api-key, reading token from file")
|
170
|
-
x_api_key = settings.
|
170
|
+
x_api_key = settings.read_secret_token()
|
171
171
|
else:
|
172
172
|
x_api_key = api_key
|
173
173
|
elif api_key is not None:
|
cli/remotive.py
CHANGED
@@ -42,8 +42,7 @@ def test_callback(value: int) -> None:
|
|
42
42
|
|
43
43
|
@app.callback()
|
44
44
|
def main(
|
45
|
-
|
46
|
-
the_version: bool = typer.Option(None, "--version", callback=version_callback, is_eager=False, help="Print current version"),
|
45
|
+
_the_version: bool = typer.Option(None, "--version", callback=version_callback, is_eager=False, help="Print current version"),
|
47
46
|
) -> None:
|
48
47
|
# Do other global stuff, handle other global options here
|
49
48
|
return
|
cli/settings.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import
|
1
|
+
from __future__ import annotations
|
2
|
+
|
2
3
|
import sys
|
3
4
|
from pathlib import Path
|
4
5
|
|
@@ -6,12 +7,19 @@ from rich.console import Console
|
|
6
7
|
|
7
8
|
err_console = Console(stderr=True)
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
# pylint: disable-next=W0511
|
11
|
+
# TODO: We probably want this to be both configurable, and testable. The best solution would probably be to refactor this module into a
|
12
|
+
# proper class, and configure it similar to logging.
|
13
|
+
CONFIG_DIR_PATH = Path.home() / ".config" / ".remotive/"
|
14
|
+
TOKEN_SECRET_FILE_PATH = CONFIG_DIR_PATH / "cloud.secret.token"
|
15
|
+
|
16
|
+
|
17
|
+
class InvalidSettingsFileError(Exception):
|
18
|
+
"""Raised when trying to access an invalid settings file or file path"""
|
11
19
|
|
12
20
|
|
13
|
-
def
|
14
|
-
if not
|
21
|
+
def read_secret_token() -> str:
|
22
|
+
if not TOKEN_SECRET_FILE_PATH.exists():
|
15
23
|
err_console.print(":boom: [bold red]Access failed[/bold red] - No access token found")
|
16
24
|
err_console.print("Login with [italic]remotive cloud auth login[/italic]")
|
17
25
|
err_console.print(
|
@@ -20,6 +28,37 @@ def read_token() -> str:
|
|
20
28
|
)
|
21
29
|
sys.exit(1)
|
22
30
|
|
23
|
-
|
24
|
-
|
25
|
-
|
31
|
+
return _read_file(TOKEN_SECRET_FILE_PATH)
|
32
|
+
|
33
|
+
|
34
|
+
def list_personal_token_files() -> list[Path]:
|
35
|
+
return [f for f in CONFIG_DIR_PATH.iterdir() if f.is_file() and f.name.startswith("personal-")]
|
36
|
+
|
37
|
+
|
38
|
+
def list_service_account_token_files() -> list[Path]:
|
39
|
+
return [f for f in CONFIG_DIR_PATH.iterdir() if f.is_file() and f.name.startswith("service-account-")]
|
40
|
+
|
41
|
+
|
42
|
+
def write_secret_token(secret: str) -> Path:
|
43
|
+
return _write_settings_file(TOKEN_SECRET_FILE_PATH, secret)
|
44
|
+
|
45
|
+
|
46
|
+
def clear_secret_token() -> None:
|
47
|
+
TOKEN_SECRET_FILE_PATH.unlink()
|
48
|
+
|
49
|
+
|
50
|
+
def _read_file(path: Path) -> str:
|
51
|
+
with open(path, "r", encoding="utf-8") as f:
|
52
|
+
return f.read()
|
53
|
+
|
54
|
+
|
55
|
+
def _write_settings_file(path: Path, data: str) -> Path:
|
56
|
+
if CONFIG_DIR_PATH not in path.parents:
|
57
|
+
raise InvalidSettingsFileError(f"file {path} not in settings dir {CONFIG_DIR_PATH}")
|
58
|
+
|
59
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
60
|
+
|
61
|
+
with open(path, "w", encoding="utf8") as f:
|
62
|
+
f.write(data)
|
63
|
+
|
64
|
+
return path
|
@@ -1,45 +1,43 @@
|
|
1
|
-
cli/__about__.py,sha256=qXVkxWb3aPCF-4MjQhB0wqL2GEblEH4Qwk70o29UkJk,122
|
2
1
|
cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
2
|
cli/broker/brokers.py,sha256=oUadEL6xQ4bhXucBH-ZjL67VuERf19kn1g240v_lEpg,3197
|
4
|
-
cli/broker/export.py,sha256=
|
3
|
+
cli/broker/export.py,sha256=3sG9i6ZwOQW6snu87NSzOL2_giQTYQMzQlpPg7z8n78,4431
|
5
4
|
cli/broker/files.py,sha256=_MVwitQ5Z9-lNDb3biXqnlkKti8rizTEw0nnAViussU,4181
|
6
5
|
cli/broker/lib/__about__.py,sha256=xnZ5V6ZcHW9dhWLWdMzVjYJbEnMKpeXm0_S_mbNzypE,141
|
7
|
-
cli/broker/lib/broker.py,sha256=
|
6
|
+
cli/broker/lib/broker.py,sha256=iBv-uegVD6awnhkukV50CcZywIybEO3qvI0YU47KcGo,23949
|
8
7
|
cli/broker/license_flows.py,sha256=qJplaeugkUiypFGPdEIl5Asqlf7W3geJ-wU-QbYMP_8,7216
|
9
8
|
cli/broker/licenses.py,sha256=Ddl243re8RoeP9CoWWbIzwDePQ9l8r7ixmbd1gqn8f0,3973
|
10
9
|
cli/broker/playback.py,sha256=hdDKXGPuIE3gcT-kgQltgn5jsPzK19Yh9hiNcgtkLX0,3992
|
11
10
|
cli/broker/record.py,sha256=Oa6hUpS0Dgnt0f6Ig33vl0Jy8wN7wMXfemaxXWjRVoQ,1414
|
12
11
|
cli/broker/scripting.py,sha256=8577_C6siOk90s4G1ItIfAoFIUAkS0ItUl5kqR0cD-k,3792
|
13
|
-
cli/broker/signals.py,sha256=
|
12
|
+
cli/broker/signals.py,sha256=llok_jUGWOzAiiQUK54uRDnDuonBOAYBDbPdnzCFdog,7075
|
14
13
|
cli/cloud/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
cli/cloud/auth.py,sha256=
|
16
|
-
cli/cloud/auth_tokens.py,sha256=
|
17
|
-
cli/cloud/brokers.py,sha256=
|
14
|
+
cli/cloud/auth.py,sha256=RBTDUGRBVsK28u-aeqzRIzHnW7FqH4KADlVlQEgBCww,2534
|
15
|
+
cli/cloud/auth_tokens.py,sha256=0U60Gk2-TnAUff5anZmTB1rOEninNvYy1o5ihCqgj8A,4525
|
16
|
+
cli/cloud/brokers.py,sha256=DNj79MTkPylKUQbr-iPUhQgfNJLAW8UehnvgpEmNH_k,3890
|
18
17
|
cli/cloud/cloud_cli.py,sha256=09YCHs8IivYsVJOsxlM5OMEqBdq3QUXtDsktcO8Kjyw,1263
|
19
18
|
cli/cloud/configs.py,sha256=xg3J-kaS-Pp0p9otV2cWl_oOWJzs_jZhXwFHz0gQxvc,4625
|
20
|
-
cli/cloud/filestorage.py,sha256=
|
19
|
+
cli/cloud/filestorage.py,sha256=cCPDYwCyJxP4V_qK1_Gnsg_T-zVsw6QaZdY_l4s4vC0,5445
|
21
20
|
cli/cloud/organisations.py,sha256=txKQmSQEpTmeqlqngai8pwgQQEvRgeDd0dT_VzZ7RNc,752
|
22
21
|
cli/cloud/projects.py,sha256=YrwPJClC2Sq_y1HjPd_tzaiv4GEnnsXSXHBhtQCPdK0,1431
|
23
22
|
cli/cloud/recordings.py,sha256=jnsc39CmIQQ3DUu5Mpe7wrr6aLCK870TRi6TcgGmiY0,24137
|
24
23
|
cli/cloud/recordings_playback.py,sha256=PRzftmvG2iePrL9f6qTEXVOnyJ-etcyzn5w9CCxcSto,11539
|
25
|
-
cli/cloud/rest_helper.py,sha256=
|
26
|
-
cli/cloud/resumable_upload.py,sha256=
|
24
|
+
cli/cloud/rest_helper.py,sha256=8XgqmxoeW8zM0tYWNAn7rABJdvgJFYfy0adOo66kys4,11499
|
25
|
+
cli/cloud/resumable_upload.py,sha256=sYThyhseXRniOMbctbO5p4BGVb9b7BXVBcmcZXwnClM,3550
|
27
26
|
cli/cloud/sample_recordings.py,sha256=OVX32U1dkkkJZysbgr5Dy515oOQKnwBAbZYzV_QUu1g,690
|
28
|
-
cli/cloud/service_account_tokens.py,sha256=
|
27
|
+
cli/cloud/service_account_tokens.py,sha256=263u1bRmBKfYsxL6TV6YjReUBUaVHWc3ETCd7AS3DTU,2297
|
29
28
|
cli/cloud/service_accounts.py,sha256=XOIPobUamCLIaufjyvb33XJDwy6uRqW5ZljZx3GYEfo,1659
|
30
29
|
cli/connect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
30
|
cli/connect/connect.py,sha256=U--6dtHxUlvE81J37rABFez4TbF7AXWOpZYZnL7sPMY,3994
|
32
|
-
cli/connect/protopie/protopie.py,sha256=
|
31
|
+
cli/connect/protopie/protopie.py,sha256=KBMbBwdkUVgV2X7AXTHweVqYVHv4akG875FVc36gsyg,6349
|
33
32
|
cli/errors.py,sha256=CXYArw1W82bRFwJkJ3tD-Ek1huKeah502DGMvPxHYFo,1366
|
34
|
-
cli/remotive.py,sha256=
|
35
|
-
cli/
|
36
|
-
cli/settings.py,sha256=MikGisXMNJTGtICBcLhfLZc2_ELCOaZmJspdLNwNRvY,833
|
33
|
+
cli/remotive.py,sha256=z834JeOwENyUM4bS74_zE95sGwu1efgfDVtCLKV5rV0,1789
|
34
|
+
cli/settings.py,sha256=A5rtp_1oix7Com5aHCAHdwJqxoV2LgxpYXwCe40v7oY,2072
|
37
35
|
cli/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
36
|
cli/tools/can/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
37
|
cli/tools/can/can.py,sha256=8uATViSFlpkdSiIm4fzbuQi1_m7V9Pym-K17TaJQRHU,2262
|
40
38
|
cli/tools/tools.py,sha256=0KU-hXR1f9xHP4BOG9A9eXfmICLmNuQCOU8ueF6iGg0,198
|
41
|
-
remotivelabs_cli-0.0.
|
42
|
-
remotivelabs_cli-0.0.
|
43
|
-
remotivelabs_cli-0.0.
|
44
|
-
remotivelabs_cli-0.0.
|
45
|
-
remotivelabs_cli-0.0.
|
39
|
+
remotivelabs_cli-0.0.29.dist-info/LICENSE,sha256=qDPP_yfuv1fF-u7EfexN-cN3M8aFgGVndGhGLovLKz0,608
|
40
|
+
remotivelabs_cli-0.0.29.dist-info/METADATA,sha256=72C3NkomgtEga_Fwr-zzm-N-93nYvflrGr51ulAuh4Q,1318
|
41
|
+
remotivelabs_cli-0.0.29.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
42
|
+
remotivelabs_cli-0.0.29.dist-info/entry_points.txt,sha256=lvDhPgagLqW_KTnLPCwKSqfYlEp-1uYVosRiPjsVj10,45
|
43
|
+
remotivelabs_cli-0.0.29.dist-info/RECORD,,
|
cli/__about__.py
DELETED
cli/requirements.txt
DELETED
File without changes
|
File without changes
|
File without changes
|