remotivelabs-cli 0.0.25__py3-none-any.whl → 0.0.26__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 +59 -49
- 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 +11 -11
- cli/cloud/__init__.py +0 -1
- cli/cloud/auth.py +40 -35
- cli/cloud/auth_tokens.py +39 -36
- cli/cloud/brokers.py +24 -33
- cli/cloud/cloud_cli.py +9 -6
- cli/cloud/configs.py +19 -11
- cli/cloud/filestorage.py +63 -51
- cli/cloud/projects.py +10 -7
- cli/cloud/recordings.py +127 -108
- 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.26.dist-info}/METADATA +5 -3
- remotivelabs_cli-0.0.26.dist-info/RECORD +44 -0
- remotivelabs_cli-0.0.25.dist-info/RECORD +0 -44
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.26.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.26.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.26.dist-info}/entry_points.txt +0 -0
cli/cloud/rest_helper.py
CHANGED
@@ -5,9 +5,10 @@ import json
|
|
5
5
|
import logging
|
6
6
|
import os
|
7
7
|
import shutil
|
8
|
+
import sys
|
8
9
|
from importlib.metadata import version
|
9
10
|
from pathlib import Path
|
10
|
-
from typing import Dict, List, Union
|
11
|
+
from typing import Any, Dict, List, Union
|
11
12
|
|
12
13
|
import requests
|
13
14
|
from requests.exceptions import JSONDecodeError
|
@@ -24,210 +25,260 @@ if "REMOTIVE_CLOUD_HTTP_LOGGING" in os.environ:
|
|
24
25
|
requests_log = logging.getLogger("requests.packages.urllib3")
|
25
26
|
requests_log.setLevel(logging.DEBUG)
|
26
27
|
requests_log.propagate = True
|
27
|
-
global baseurl
|
28
|
-
global license_server_base_url
|
29
|
-
base_url = "https://cloud.remotivelabs.com"
|
30
|
-
license_server_base_url = "https://license.cloud.remotivelabs.com"
|
31
|
-
|
32
|
-
if "REMOTIVE_CLOUD_BASE_URL" in os.environ:
|
33
|
-
base_url = os.environ["REMOTIVE_CLOUD_BASE_URL"]
|
34
|
-
|
35
|
-
if "cloud-dev" in base_url:
|
36
|
-
license_server_base_url = "https://license.cloud-dev.remotivelabs.com"
|
37
|
-
|
38
|
-
# if 'REMOTIVE_CLOUD_AUTH_TOKEN' not in os.environ:
|
39
|
-
# print('export REMOTIVE_CLOUD_AUTH_TOKEN=auth must be set')
|
40
|
-
# exit(0)
|
41
|
-
|
42
|
-
# token = os.environ["REMOTIVE_CLOUD_AUTH_TOKEN"]
|
43
|
-
# headers = {"Authorization": "Bearer " + token}
|
44
|
-
|
45
|
-
headers = {}
|
46
|
-
org = ""
|
47
|
-
|
48
|
-
token = ""
|
49
|
-
|
50
|
-
cli_version = version("remotivelabs-cli")
|
51
|
-
|
52
|
-
|
53
|
-
def ensure_auth_token():
|
54
|
-
global token
|
55
|
-
|
56
|
-
# if 'REMOTIVE_CLOUD_ORGANISATION' not in os.environ:
|
57
|
-
# print('You must first set the organisation id to use: export REMOTIVE_CLOUD_ORGANISATION=organisationUid')
|
58
|
-
# raise typer.Exit()
|
59
|
-
global org
|
60
|
-
# org = os.environ["REMOTIVE_CLOUD_ORGANISATION"]
|
61
|
-
|
62
|
-
# if not exists(str(Path.home()) + "/.config/.remotive/cloud.secret.token"):
|
63
|
-
# print("Access token not found, please login first")
|
64
|
-
# raise typer.Exit()
|
65
|
-
|
66
|
-
# f = open(str(Path.home()) + "/.config/.remotive/cloud.secret.token", "r")
|
67
|
-
token = settings.read_token()
|
68
|
-
# os.environ['REMOTIVE_CLOUD_AUTH_TOKEN'] = token
|
69
|
-
global headers
|
70
|
-
|
71
|
-
headers["Authorization"] = "Bearer " + token.strip()
|
72
|
-
headers["User-Agent"] = f"remotivelabs-cli {cli_version}"
|
73
|
-
|
74
|
-
|
75
|
-
def handle_get(
|
76
|
-
url,
|
77
|
-
params=None,
|
78
|
-
return_response: bool = False,
|
79
|
-
allow_status_codes=None,
|
80
|
-
progress_label="Fetching...",
|
81
|
-
use_progress_indicator: bool = True,
|
82
|
-
timeout: int = 20,
|
83
|
-
):
|
84
|
-
if params is None:
|
85
|
-
params = {}
|
86
|
-
ensure_auth_token()
|
87
|
-
if use_progress_indicator:
|
88
|
-
with use_progress(progress_label):
|
89
|
-
r = requests.get(f"{base_url}{url}", headers=headers, params=params, timeout=timeout)
|
90
|
-
else:
|
91
|
-
r = requests.get(f"{base_url}{url}", headers=headers, params=params, timeout=timeout)
|
92
|
-
|
93
|
-
if return_response:
|
94
|
-
check_api_result(r, allow_status_codes)
|
95
|
-
return r
|
96
|
-
print_api_result(r)
|
97
|
-
|
98
|
-
|
99
|
-
def has_access(url, params={}):
|
100
|
-
ensure_auth_token()
|
101
|
-
r = requests.get(f"{base_url}{url}", headers=headers, params=params)
|
102
|
-
if r.status_code == 401:
|
103
|
-
return False
|
104
|
-
return True
|
105
|
-
|
106
|
-
|
107
|
-
def check_api_result(response, allow_status_codes: List[int] = None):
|
108
|
-
if response.status_code > 299:
|
109
|
-
if allow_status_codes is not None and response.status_code in allow_status_codes:
|
110
|
-
return
|
111
|
-
err_console.print(f":boom: [bold red]Got status code[/bold red]: {response.status_code}")
|
112
|
-
if response.status_code == 401:
|
113
|
-
err_console.print("Your token has expired, please login again")
|
114
|
-
else:
|
115
|
-
err_console.print(response.text)
|
116
|
-
exit(1)
|
117
|
-
|
118
|
-
|
119
|
-
def print_api_result(response):
|
120
|
-
if response.status_code >= 200 and response.status_code < 300:
|
121
|
-
if len(response.content) > 4:
|
122
|
-
try:
|
123
|
-
print(json.dumps(response.json()))
|
124
|
-
except JSONDecodeError:
|
125
|
-
err_console.print(":boom: [bold red]Json parse error[/bold red]: Please try again and report if the error persists")
|
126
|
-
|
127
|
-
exit(0)
|
128
|
-
else:
|
129
|
-
err_console.print(f":boom: [bold red]Got status code[/bold red]: {response.status_code}")
|
130
|
-
if response.status_code == 401:
|
131
|
-
err_console.print("Your token has expired, please login again")
|
132
|
-
else:
|
133
|
-
err_console.print(response.text)
|
134
|
-
exit(1)
|
135
|
-
# typer.Exit(1) did not work as expected
|
136
|
-
|
137
|
-
|
138
|
-
def handle_delete(url, params={}, quiet=False, success_msg="Successfully deleted", progress_label="Deleting..."):
|
139
|
-
ensure_auth_token()
|
140
|
-
with use_progress(progress_label):
|
141
|
-
r = requests.delete(f"{base_url}{url}", headers=headers, params=params)
|
142
|
-
if r.status_code == 200 or r.status_code == 204:
|
143
|
-
if not quiet:
|
144
|
-
print_api_result(r)
|
145
|
-
else:
|
146
|
-
print_api_result(r)
|
147
|
-
|
148
|
-
|
149
|
-
def handle_post(url, body=None, params={}, progress_label: str = "Processing...", return_response: bool = False):
|
150
|
-
ensure_auth_token()
|
151
|
-
headers["content-type"] = "application/json"
|
152
|
-
|
153
|
-
with use_progress(progress_label):
|
154
|
-
r = requests.post(f"{base_url}{url}", headers=headers, params=params, data=body)
|
155
|
-
|
156
|
-
if return_response:
|
157
|
-
check_api_result(r)
|
158
|
-
return r
|
159
28
|
|
160
|
-
print_api_result(r)
|
161
29
|
|
30
|
+
class RestHelper:
|
31
|
+
"""Static Class with various helper functions for the rest API"""
|
32
|
+
|
33
|
+
__base_url = "https://cloud.remotivelabs.com"
|
34
|
+
__license_server_base_url = "https://license.cloud.remotivelabs.com"
|
35
|
+
|
36
|
+
if "REMOTIVE_CLOUD_BASE_URL" in os.environ:
|
37
|
+
__base_url = os.environ["REMOTIVE_CLOUD_BASE_URL"]
|
38
|
+
|
39
|
+
if "cloud-dev" in __base_url:
|
40
|
+
__license_server_base_url = "https://license.cloud-dev.remotivelabs.com"
|
41
|
+
|
42
|
+
# if 'REMOTIVE_CLOUD_AUTH_TOKEN' not in os.environ:
|
43
|
+
# print('export REMOTIVE_CLOUD_AUTH_TOKEN=auth must be set')
|
44
|
+
# exit(0)
|
45
|
+
|
46
|
+
# token = os.environ["REMOTIVE_CLOUD_AUTH_TOKEN"]
|
47
|
+
# headers = {"Authorization": "Bearer " + token}
|
48
|
+
|
49
|
+
__headers: Dict[str, str] = {}
|
50
|
+
__org: str = ""
|
51
|
+
|
52
|
+
__token: str = ""
|
53
|
+
|
54
|
+
@staticmethod
|
55
|
+
def get_cli_version() -> str:
|
56
|
+
return version("remotivelabs-cli")
|
57
|
+
|
58
|
+
@staticmethod
|
59
|
+
def get_base_url() -> str:
|
60
|
+
return RestHelper.__base_url
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def get_license_server_base_url() -> str:
|
64
|
+
return RestHelper.__license_server_base_url
|
65
|
+
|
66
|
+
@staticmethod
|
67
|
+
def get_headers() -> Dict[str, str]:
|
68
|
+
return RestHelper.__headers
|
69
|
+
|
70
|
+
@staticmethod
|
71
|
+
def get_org() -> str:
|
72
|
+
return RestHelper.__org
|
73
|
+
|
74
|
+
@staticmethod
|
75
|
+
def get_token() -> str:
|
76
|
+
return RestHelper.__token
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
def ensure_auth_token() -> None:
|
80
|
+
# if 'REMOTIVE_CLOUD_ORGANISATION' not in os.environ:
|
81
|
+
# print('You must first set the organisation id to use: export REMOTIVE_CLOUD_ORGANISATION=organisationUid')
|
82
|
+
# raise typer.Exit()
|
83
|
+
# org = os.environ["REMOTIVE_CLOUD_ORGANISATION"]
|
84
|
+
|
85
|
+
# if not exists(str(Path.home()) + "/.config/.remotive/cloud.secret.token"):
|
86
|
+
# print("Access token not found, please login first")
|
87
|
+
# raise typer.Exit()
|
88
|
+
|
89
|
+
# f = open(str(Path.home()) + "/.config/.remotive/cloud.secret.token", "r")
|
90
|
+
token = settings.read_token()
|
91
|
+
# os.environ['REMOTIVE_CLOUD_AUTH_TOKEN'] = token
|
92
|
+
|
93
|
+
RestHelper.__headers["Authorization"] = "Bearer " + token.strip()
|
94
|
+
RestHelper.__headers["User-Agent"] = f"remotivelabs-cli {RestHelper.get_cli_version()}"
|
95
|
+
|
96
|
+
@staticmethod
|
97
|
+
def handle_get(
|
98
|
+
url: str,
|
99
|
+
params: Any = None,
|
100
|
+
return_response: bool = False,
|
101
|
+
allow_status_codes: List[int] | None = None,
|
102
|
+
progress_label: str = "Fetching...",
|
103
|
+
use_progress_indicator: bool = True,
|
104
|
+
timeout: int = 20,
|
105
|
+
) -> requests.Response | None:
|
106
|
+
# pylint: disable=R0913
|
107
|
+
# Returns a Response object if succesfull otherwise None
|
108
|
+
if params is None:
|
109
|
+
params = {}
|
110
|
+
RestHelper.ensure_auth_token()
|
111
|
+
if use_progress_indicator:
|
112
|
+
with RestHelper.use_progress(progress_label):
|
113
|
+
r = requests.get(f"{RestHelper.__base_url}{url}", headers=RestHelper.__headers, params=params, timeout=timeout)
|
114
|
+
else:
|
115
|
+
r = requests.get(f"{RestHelper.__base_url}{url}", headers=RestHelper.__headers, params=params, timeout=timeout)
|
162
116
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
117
|
+
if return_response:
|
118
|
+
RestHelper.check_api_result(r, allow_status_codes)
|
119
|
+
return r
|
120
|
+
RestHelper.print_api_result(r)
|
121
|
+
return requests.Response()
|
122
|
+
|
123
|
+
@staticmethod
|
124
|
+
def has_access(url: str, params: Any = {}) -> bool: # pylint: disable=W0102
|
125
|
+
RestHelper.ensure_auth_token()
|
126
|
+
r = requests.get(f"{RestHelper.__base_url}{url}", headers=RestHelper.__headers, params=params, timeout=60)
|
127
|
+
if r.status_code == 401:
|
128
|
+
return False
|
129
|
+
return True
|
130
|
+
|
131
|
+
@staticmethod
|
132
|
+
def check_api_result(response: requests.Response, allow_status_codes: List[int] | None = None) -> None:
|
133
|
+
if response.status_code > 299:
|
134
|
+
if allow_status_codes is not None and response.status_code in allow_status_codes:
|
135
|
+
return
|
136
|
+
err_console.print(f":boom: [bold red]Got status code[/bold red]: {response.status_code}")
|
137
|
+
if response.status_code == 401:
|
138
|
+
err_console.print("Your token has expired, please login again")
|
139
|
+
else:
|
140
|
+
err_console.print(response.text)
|
141
|
+
sys.exit(1)
|
142
|
+
|
143
|
+
@staticmethod
|
144
|
+
def print_api_result(response: requests.Response) -> None:
|
145
|
+
if response.status_code >= 200 and response.status_code < 300:
|
146
|
+
if len(response.content) >= 2:
|
147
|
+
try:
|
148
|
+
print(json.dumps(response.json()))
|
149
|
+
except JSONDecodeError:
|
150
|
+
err_console.print(":boom: [bold red]Json parse error[/bold red]: Please try again and report if the error persists")
|
151
|
+
sys.exit(0)
|
152
|
+
else:
|
153
|
+
err_console.print(f":boom: [bold red]Got status code[/bold red]: {response.status_code}")
|
154
|
+
if response.status_code == 401:
|
155
|
+
err_console.print("Your token has expired, please login again")
|
156
|
+
else:
|
157
|
+
err_console.print(response.text)
|
158
|
+
sys.exit(1)
|
159
|
+
# typer.Exit(1) did not work as expected
|
160
|
+
|
161
|
+
@staticmethod
|
162
|
+
def handle_delete( # pylint: disable=W0102
|
163
|
+
url: str, params: Any = {}, quiet: bool = False, progress_label: str = "Deleting..."
|
164
|
+
) -> None:
|
165
|
+
RestHelper.ensure_auth_token()
|
166
|
+
with RestHelper.use_progress(progress_label):
|
167
|
+
r = requests.delete(f"{RestHelper.__base_url}{url}", headers=RestHelper.__headers, params=params, timeout=60)
|
168
|
+
if r.status_code in (200, 204):
|
169
|
+
if not quiet:
|
170
|
+
RestHelper.print_api_result(r)
|
171
|
+
else:
|
172
|
+
RestHelper.print_api_result(r)
|
167
173
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
174
|
+
@staticmethod
|
175
|
+
def handle_post( # pylint: disable=W0102
|
176
|
+
url: str, body: Any = None, params: Any = {}, progress_label: str = "Processing...", return_response: bool = False
|
177
|
+
) -> requests.Response | None:
|
178
|
+
# Returns a Response object if succesfull otherwise, None
|
179
|
+
RestHelper.ensure_auth_token()
|
180
|
+
RestHelper.__headers["content-type"] = "application/json"
|
172
181
|
|
182
|
+
with RestHelper.use_progress(progress_label):
|
183
|
+
r = requests.post(f"{RestHelper.__base_url}{url}", headers=RestHelper.__headers, params=params, data=body, timeout=60)
|
173
184
|
|
174
|
-
def upload_file(
|
175
|
-
path: Union[str, Path], url: str, upload_headers: dict[str, str] = None, return_response: bool = False, progress_label="Uploading..."
|
176
|
-
):
|
177
|
-
ensure_auth_token()
|
178
|
-
if upload_headers is not None:
|
179
|
-
headers.update(upload_headers)
|
180
|
-
with open(path, "rb") as file:
|
181
|
-
with wrap_file(file, os.stat(path).st_size, description=progress_label) as f:
|
182
|
-
r = requests.post(f"{base_url}{url}", files={os.path.basename(path): f}, headers=headers)
|
183
185
|
if return_response:
|
184
|
-
check_api_result(r)
|
186
|
+
RestHelper.check_api_result(r)
|
185
187
|
return r
|
186
|
-
print_api_result(r)
|
187
188
|
|
189
|
+
RestHelper.print_api_result(r)
|
190
|
+
return None
|
191
|
+
|
192
|
+
@staticmethod
|
193
|
+
def handle_put(url: str, body: Any = None, params: Any = {}, return_response: bool = False) -> requests.Response | None: # pylint: disable=W0102
|
194
|
+
# Returns a Response object if succesfull otherwise, None
|
195
|
+
RestHelper.ensure_auth_token()
|
196
|
+
RestHelper.__headers["content-type"] = "application/json"
|
197
|
+
r = requests.put(f"{RestHelper.__base_url}{url}", headers=RestHelper.__headers, params=params, data=body, timeout=60)
|
188
198
|
|
189
|
-
def upload_file_with_signed_url(
|
190
|
-
path: Union[str, Path], url: str, upload_headers: dict[str, str], return_response: bool = False, progress_label="Uploading..."
|
191
|
-
):
|
192
|
-
with open(path, "rb") as file:
|
193
|
-
with wrap_file(file, os.stat(path).st_size, description=progress_label, transient=True) as f:
|
194
|
-
r = requests.put(url, data=f, headers=upload_headers)
|
195
199
|
if return_response:
|
196
|
-
check_api_result(r)
|
200
|
+
RestHelper.check_api_result(r)
|
197
201
|
return r
|
198
|
-
print_api_result(r)
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
202
|
+
RestHelper.print_api_result(r)
|
203
|
+
return None
|
204
|
+
|
205
|
+
@staticmethod
|
206
|
+
def upload_file(
|
207
|
+
path: Union[str, Path],
|
208
|
+
url: str,
|
209
|
+
upload_headers: Dict[str, str] | None = None,
|
210
|
+
return_response: bool = False,
|
211
|
+
progress_label: str = "Uploading...",
|
212
|
+
) -> requests.Response | None:
|
213
|
+
# Returns a Response object if succesfull otherwise, None
|
214
|
+
RestHelper.ensure_auth_token()
|
215
|
+
if upload_headers is not None:
|
216
|
+
RestHelper.__headers.update(upload_headers)
|
217
|
+
with open(path, "rb") as file:
|
218
|
+
with wrap_file(file, os.stat(path).st_size, description=progress_label) as f:
|
219
|
+
r = requests.post(
|
220
|
+
f"{RestHelper.__base_url}{url}", files={os.path.basename(path): f}, headers=RestHelper.__headers, timeout=60
|
221
|
+
)
|
222
|
+
if return_response:
|
223
|
+
RestHelper.check_api_result(r)
|
224
|
+
return r
|
225
|
+
RestHelper.print_api_result(r)
|
226
|
+
return None
|
227
|
+
|
228
|
+
@staticmethod
|
229
|
+
def upload_file_with_signed_url(
|
230
|
+
path: Union[str, Path],
|
231
|
+
url: str,
|
232
|
+
upload_headers: Dict[str, str],
|
233
|
+
return_response: bool = False,
|
234
|
+
progress_label: str = "Uploading...",
|
235
|
+
) -> requests.Response | None:
|
236
|
+
# Returns a Response object if succesfull otherwise, None
|
237
|
+
with open(path, "rb") as file:
|
238
|
+
with wrap_file(file, os.stat(path).st_size, description=progress_label, transient=False) as f:
|
239
|
+
r = requests.put(url, data=f, headers=upload_headers, timeout=60)
|
240
|
+
if return_response:
|
241
|
+
RestHelper.check_api_result(r)
|
242
|
+
return r
|
243
|
+
RestHelper.print_api_result(r)
|
244
|
+
return None
|
245
|
+
|
246
|
+
@staticmethod
|
247
|
+
def use_progress(label: str) -> Progress:
|
248
|
+
p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True)
|
249
|
+
p.add_task(label, total=1)
|
250
|
+
return p
|
251
|
+
|
252
|
+
@staticmethod
|
253
|
+
def download_file(save_file_name: str, url: str) -> None:
|
254
|
+
# Next download the actual file
|
255
|
+
download_resp = requests.get(url=url, stream=True, timeout=60)
|
256
|
+
if download_resp.status_code == 200:
|
257
|
+
content_length = int(download_resp.headers["Content-Length"])
|
258
|
+
with open(save_file_name, "wb") as out_file:
|
259
|
+
with wrap_file(
|
260
|
+
download_resp.raw, content_length, refresh_per_second=100, description=f"Downloading to {save_file_name}"
|
261
|
+
) as stream_with_progress:
|
262
|
+
shutil.copyfileobj(stream_with_progress, out_file)
|
263
|
+
else:
|
264
|
+
RestHelper.check_api_result(download_resp)
|
265
|
+
|
266
|
+
@staticmethod
|
267
|
+
def request_license(email: str, machine_id: Dict[str, Any]) -> str:
|
268
|
+
# Lets keep the email here so we have the same interface for both authenticated
|
269
|
+
# and not authenticated license requests.
|
270
|
+
# email will be validated in the license server to make sure it matches with the user of the
|
271
|
+
# access token so not any email is sent here
|
272
|
+
RestHelper.ensure_auth_token()
|
273
|
+
payload = {"id": email, "machine_id": machine_id}
|
274
|
+
b64_encoded_bytes = base64.encodebytes(json.dumps(payload).encode())
|
275
|
+
license_jsonb64 = {"licensejsonb64": b64_encoded_bytes.decode("utf-8")}
|
276
|
+
RestHelper.__headers["content-type"] = "application/json"
|
277
|
+
r = requests.post(
|
278
|
+
url=f"{RestHelper.__license_server_base_url}/api/license/request",
|
279
|
+
headers=RestHelper.__headers,
|
280
|
+
data=json.dumps(license_jsonb64),
|
281
|
+
timeout=60,
|
282
|
+
)
|
283
|
+
RestHelper.check_api_result(r)
|
284
|
+
return str(r.json()["license_data"])
|
cli/cloud/resumable_upload.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import os
|
2
|
+
import sys
|
2
3
|
|
3
4
|
import requests
|
4
5
|
from rich.progress import wrap_file
|
@@ -6,11 +7,11 @@ from rich.progress import wrap_file
|
|
6
7
|
from ..errors import ErrorPrinter
|
7
8
|
|
8
9
|
|
9
|
-
def __get_uploaded_bytes(upload_url):
|
10
|
+
def __get_uploaded_bytes(upload_url: str) -> int:
|
10
11
|
headers = {"Content-Range": "bytes */*"}
|
11
|
-
response = requests.put(upload_url, headers=headers)
|
12
|
+
response = requests.put(upload_url, headers=headers, timeout=60)
|
12
13
|
if response.status_code != 308:
|
13
|
-
raise
|
14
|
+
raise ValueError(f"Failed to retrieve upload status: {response.status_code} {response.text}")
|
14
15
|
|
15
16
|
# Parse the Range header to get the last byte uploaded
|
16
17
|
range_header = response.headers.get("Range")
|
@@ -20,7 +21,7 @@ def __get_uploaded_bytes(upload_url):
|
|
20
21
|
return 0
|
21
22
|
|
22
23
|
|
23
|
-
def upload_signed_url(signed_url, source_file_name, content_type):
|
24
|
+
def upload_signed_url(signed_url: str, source_file_name: str, content_type: str) -> None:
|
24
25
|
"""
|
25
26
|
Upload file to file storage with signed url and resumable upload.
|
26
27
|
Resumable upload will only work with the same URL and not if a new signed URL is requested with the
|
@@ -32,10 +33,10 @@ def upload_signed_url(signed_url, source_file_name, content_type):
|
|
32
33
|
|
33
34
|
file_size = os.path.getsize(source_file_name)
|
34
35
|
headers = {"x-goog-resumable": "start", "content-type": content_type}
|
35
|
-
response = requests.post(signed_url, headers=headers)
|
36
|
+
response = requests.post(signed_url, headers=headers, timeout=60)
|
36
37
|
if response.status_code not in (200, 201, 308):
|
37
38
|
ErrorPrinter.print_generic_error(f"Failed to upload file: {response.status_code} - {response.text}")
|
38
|
-
exit(1)
|
39
|
+
sys.exit(1)
|
39
40
|
|
40
41
|
upload_url = response.headers["Location"]
|
41
42
|
|
@@ -53,9 +54,9 @@ def upload_signed_url(signed_url, source_file_name, content_type):
|
|
53
54
|
chunk_end = min(chunk_start + chunk_size, file_size) - 1
|
54
55
|
chunk = file.read(chunk_end - chunk_start + 1)
|
55
56
|
headers = {"Content-Range": f"bytes {chunk_start}-{chunk_end}/{file_size}"}
|
56
|
-
response = requests.put(upload_url, headers=headers, data=chunk)
|
57
|
+
response = requests.put(upload_url, headers=headers, data=chunk, timeout=60)
|
57
58
|
if response.status_code not in (200, 201, 308):
|
58
59
|
ErrorPrinter.print_generic_error(f"Failed to upload file: {response.status_code} - {response.text}")
|
59
|
-
exit(1)
|
60
|
+
sys.exit(1)
|
60
61
|
|
61
62
|
print(f"File {source_file_name} uploaded successfully.")
|
cli/cloud/sample_recordings.py
CHANGED
@@ -2,7 +2,7 @@ import json
|
|
2
2
|
|
3
3
|
import typer
|
4
4
|
|
5
|
-
from . import
|
5
|
+
from .rest_helper import RestHelper as Rest
|
6
6
|
|
7
7
|
app = typer.Typer()
|
8
8
|
|
@@ -11,14 +11,14 @@ app = typer.Typer()
|
|
11
11
|
def do_import(
|
12
12
|
recording_session: str = typer.Argument(..., help="Recording session id"),
|
13
13
|
project: str = typer.Option(..., help="Project to import sample recording into", envvar="REMOTIVE_CLOUD_PROJECT"),
|
14
|
-
):
|
15
|
-
|
14
|
+
) -> None:
|
15
|
+
Rest.handle_post(url=f"/api/samples/files/recording/{recording_session}/copy", body=json.dumps({"projectUid": project}))
|
16
16
|
|
17
17
|
|
18
18
|
@app.command("list")
|
19
|
-
def list():
|
19
|
+
def list() -> None: # pylint: disable=W0622
|
20
20
|
"""
|
21
21
|
List available sample recordings
|
22
22
|
"""
|
23
23
|
|
24
|
-
|
24
|
+
Rest.handle_get("/api/samples/files/recording")
|
@@ -4,12 +4,12 @@ from pathlib import Path
|
|
4
4
|
|
5
5
|
import typer
|
6
6
|
|
7
|
-
from . import
|
7
|
+
from .rest_helper import RestHelper as Rest
|
8
8
|
|
9
9
|
app = typer.Typer()
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
CONFIG_DIR_NAME = str(Path.home()) + "/.config/.remotive/"
|
12
|
+
TOKEN_FILE_NAME = str(Path.home()) + "/.config/.remotive/cloud.secret.token2"
|
13
13
|
|
14
14
|
|
15
15
|
@app.command(name="create", help="Create new access token")
|
@@ -17,13 +17,16 @@ def create(
|
|
17
17
|
expire_in_days: int = typer.Option(default=365, help="Number of this token is valid"),
|
18
18
|
service_account: str = typer.Option(..., help="Service account name"),
|
19
19
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
20
|
-
):
|
21
|
-
response =
|
20
|
+
) -> None:
|
21
|
+
response = Rest.handle_post(
|
22
22
|
url=f"/api/project/{project}/admin/accounts/{service_account}/keys",
|
23
23
|
return_response=True,
|
24
24
|
body=json.dumps({"daysUntilExpiry": expire_in_days}),
|
25
25
|
)
|
26
26
|
|
27
|
+
if response is None:
|
28
|
+
return
|
29
|
+
|
27
30
|
if response.status_code == 200:
|
28
31
|
name = response.json()["name"]
|
29
32
|
write_token(f"service-account-{service_account}-{name}-token.json", response.text)
|
@@ -36,16 +39,16 @@ def create(
|
|
36
39
|
def list_keys(
|
37
40
|
service_account: str = typer.Option(..., help="Service account name"),
|
38
41
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
39
|
-
):
|
40
|
-
|
42
|
+
) -> None:
|
43
|
+
Rest.handle_get(f"/api/project/{project}/admin/accounts/{service_account}/keys")
|
41
44
|
|
42
45
|
|
43
46
|
@app.command(name="list-files")
|
44
|
-
def list_files():
|
47
|
+
def list_files() -> None:
|
45
48
|
"""
|
46
49
|
List personal access token files in remotivelabs config directory
|
47
50
|
"""
|
48
|
-
personal_files = filter(lambda f: f.startswith("service-account"), os.listdir(
|
51
|
+
personal_files = filter(lambda f: f.startswith("service-account"), os.listdir(CONFIG_DIR_NAME))
|
49
52
|
for file in personal_files:
|
50
53
|
print(file)
|
51
54
|
|
@@ -55,13 +58,12 @@ def revoke(
|
|
55
58
|
name: str = typer.Argument(..., help="Access token name"),
|
56
59
|
service_account: str = typer.Option(..., help="Service account name"),
|
57
60
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
58
|
-
):
|
59
|
-
|
61
|
+
) -> None:
|
62
|
+
Rest.handle_delete(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}")
|
60
63
|
|
61
64
|
|
62
|
-
def write_token(file, token):
|
65
|
+
def write_token(file: str, token: str) -> None:
|
63
66
|
path = str(Path.home()) + f"/.config/.remotive/{file}"
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
print(f"Secret token written to {path}")
|
67
|
+
with open(path, "w", encoding="utf8") as f:
|
68
|
+
f.write(token)
|
69
|
+
print(f"Secret token written to {path}")
|
cli/cloud/service_accounts.py
CHANGED
@@ -5,15 +5,15 @@ from typing import List
|
|
5
5
|
|
6
6
|
import typer
|
7
7
|
|
8
|
-
from . import rest_helper as rest
|
9
8
|
from . import service_account_tokens
|
9
|
+
from .rest_helper import RestHelper as Rest
|
10
10
|
|
11
11
|
app = typer.Typer()
|
12
12
|
|
13
13
|
|
14
14
|
@app.command(name="list", help="List service-accounts")
|
15
|
-
def list_service_accounts(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
16
|
-
|
15
|
+
def list_service_accounts(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
16
|
+
Rest.handle_get(f"/api/project/{project}/admin/accounts")
|
17
17
|
|
18
18
|
|
19
19
|
@app.command(name="create", help="Create service account")
|
@@ -21,9 +21,9 @@ def create_service_account(
|
|
21
21
|
name: str,
|
22
22
|
role: List[str] = typer.Option(..., help="Roles to apply"),
|
23
23
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
24
|
-
):
|
24
|
+
) -> None:
|
25
25
|
data = {"name": name, "roles": role}
|
26
|
-
|
26
|
+
Rest.handle_post(url=f"/api/project/{project}/admin/accounts", body=json.dumps(data))
|
27
27
|
|
28
28
|
|
29
29
|
@app.command(name="update", help="Update service account")
|
@@ -31,13 +31,13 @@ def update_service_account(
|
|
31
31
|
service_account: str = typer.Option(..., help="Service account name"),
|
32
32
|
role: List[str] = typer.Option(..., help="Roles to apply"),
|
33
33
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
34
|
-
):
|
35
|
-
|
34
|
+
) -> None:
|
35
|
+
Rest.handle_put(url=f"/api/project/{project}/admin/accounts/{service_account}", body=json.dumps({"roles": role}))
|
36
36
|
|
37
37
|
|
38
38
|
@app.command(name="delete", help="Delete service account")
|
39
|
-
def delete_service_account(name: str, project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
40
|
-
|
39
|
+
def delete_service_account(name: str, project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
40
|
+
Rest.handle_delete(url=f"/api/project/{project}/admin/accounts/{name}")
|
41
41
|
|
42
42
|
|
43
43
|
app.add_typer(service_account_tokens.app, name="tokens", help="Manage project service account access tokens")
|