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.
Files changed (42) hide show
  1. cli/broker/brokers.py +2 -2
  2. cli/broker/export.py +5 -5
  3. cli/broker/files.py +5 -5
  4. cli/broker/lib/broker.py +59 -49
  5. cli/broker/license_flows.py +11 -9
  6. cli/broker/licenses.py +2 -2
  7. cli/broker/playback.py +14 -34
  8. cli/broker/record.py +3 -3
  9. cli/broker/scripting.py +4 -4
  10. cli/broker/signals.py +11 -11
  11. cli/cloud/__init__.py +0 -1
  12. cli/cloud/auth.py +40 -35
  13. cli/cloud/auth_tokens.py +39 -36
  14. cli/cloud/brokers.py +24 -33
  15. cli/cloud/cloud_cli.py +9 -6
  16. cli/cloud/configs.py +19 -11
  17. cli/cloud/filestorage.py +63 -51
  18. cli/cloud/projects.py +10 -7
  19. cli/cloud/recordings.py +127 -108
  20. cli/cloud/recordings_playback.py +52 -39
  21. cli/cloud/rest_helper.py +247 -196
  22. cli/cloud/resumable_upload.py +9 -8
  23. cli/cloud/sample_recordings.py +5 -5
  24. cli/cloud/service_account_tokens.py +18 -16
  25. cli/cloud/service_accounts.py +9 -9
  26. cli/connect/__init__.py +0 -1
  27. cli/connect/connect.py +7 -6
  28. cli/connect/protopie/protopie.py +32 -16
  29. cli/errors.py +6 -5
  30. cli/remotive.py +13 -9
  31. cli/requirements.txt +4 -1
  32. cli/settings.py +9 -9
  33. cli/tools/__init__.py +0 -1
  34. cli/tools/can/__init__.py +0 -1
  35. cli/tools/can/can.py +8 -8
  36. cli/tools/tools.py +2 -2
  37. {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.26.dist-info}/METADATA +5 -3
  38. remotivelabs_cli-0.0.26.dist-info/RECORD +44 -0
  39. remotivelabs_cli-0.0.25.dist-info/RECORD +0 -44
  40. {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.26.dist-info}/LICENSE +0 -0
  41. {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.26.dist-info}/WHEEL +0 -0
  42. {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
- def handle_put(url, body=None, params={}, return_response: bool = False):
164
- ensure_auth_token()
165
- headers["content-type"] = "application/json"
166
- r = requests.put(f"{base_url}{url}", headers=headers, params=params, data=body)
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
- if return_response:
169
- check_api_result(r)
170
- return r
171
- print_api_result(r)
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
- def use_progress(label: str):
202
- p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True)
203
- p.add_task(label, total=1)
204
- return p
205
-
206
-
207
- def download_file(save_file_name: str, url: str):
208
- # Next download the actual file
209
- download_resp = requests.get(url=url, stream=True)
210
- if download_resp.status_code == 200:
211
- content_length = int(download_resp.headers["Content-Length"])
212
- with open(save_file_name, "wb") as out_file:
213
- with wrap_file(
214
- download_resp.raw, content_length, refresh_per_second=100, description=f"Downloading to {save_file_name}"
215
- ) as stream_with_progress:
216
- shutil.copyfileobj(stream_with_progress, out_file)
217
- else:
218
- check_api_result(download_resp)
219
-
220
-
221
- def request_license(email: str, machine_id: Dict[str, any]) -> str:
222
- # Lets keep the email here so we have the same interface for both authenticated
223
- # and not authenticated license requests.
224
- # email will be validated in the license server to make sure it matches with the user of the
225
- # access token so not any email is sent here
226
- ensure_auth_token()
227
- payload = {"id": email, "machine_id": machine_id}
228
- b64_encoded_bytes = base64.encodebytes(json.dumps(payload).encode())
229
- license_jsonb64 = {"licensejsonb64": b64_encoded_bytes.decode("utf-8")}
230
- headers["content-type"] = "application/json"
231
- r = requests.post(url=f"{license_server_base_url}/api/license/request", headers=headers, data=json.dumps(license_jsonb64))
232
- check_api_result(r)
233
- return r.json()["license_data"]
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"])
@@ -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 Exception(f"Failed to retrieve upload status: {response.status_code} {response.text}")
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.")
@@ -2,7 +2,7 @@ import json
2
2
 
3
3
  import typer
4
4
 
5
- from . import rest_helper as rest
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
- rest.handle_post(url=f"/api/samples/files/recording/{recording_session}/copy", body=json.dumps({"projectUid": project}))
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
- rest.handle_get("/api/samples/files/recording")
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 rest_helper as rest
7
+ from .rest_helper import RestHelper as Rest
8
8
 
9
9
  app = typer.Typer()
10
10
 
11
- config_dir_name = str(Path.home()) + "/.config/.remotive/"
12
- token_file_name = str(Path.home()) + "/.config/.remotive/cloud.secret.token2"
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 = rest.handle_post(
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
- rest.handle_get(f"/api/project/{project}/admin/accounts/{service_account}/keys")
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(config_dir_name))
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
- rest.handle_delete(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}")
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
- f = open(path, "w")
65
- f.write(token)
66
- f.close()
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}")
@@ -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
- rest.handle_get(f"/api/project/{project}/admin/accounts")
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
- rest.handle_post(url=f"/api/project/{project}/admin/accounts", body=json.dumps(data))
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
- rest.handle_put(url=f"/api/project/{project}/admin/accounts/{service_account}", body=json.dumps(role))
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
- rest.handle_delete(url=f"/api/project/{project}/admin/accounts/{name}")
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")