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/cloud/brokers.py
CHANGED
@@ -1,49 +1,40 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import json
|
2
4
|
import os
|
3
5
|
import signal as os_signal
|
6
|
+
from typing import Any, Union
|
4
7
|
|
8
|
+
import requests
|
5
9
|
import typer
|
6
10
|
import websocket
|
7
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
8
11
|
|
9
|
-
from . import
|
12
|
+
from .rest_helper import RestHelper as Rest
|
10
13
|
|
11
14
|
app = typer.Typer()
|
12
15
|
|
13
16
|
|
14
17
|
@app.command("list", help="Lists brokers in project")
|
15
|
-
def list(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
16
|
-
|
18
|
+
def list(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None: # pylint: disable=W0622
|
19
|
+
Rest.handle_get(f"/api/project/{project}/brokers")
|
17
20
|
|
18
21
|
|
19
22
|
@app.command("describe", help="Shows details about a specific broker")
|
20
|
-
def describe(name: str, project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
21
|
-
|
23
|
+
def describe(name: str, project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
24
|
+
Rest.handle_get(f"/api/project/{project}/brokers/{name}")
|
22
25
|
|
23
26
|
|
24
27
|
@app.command("delete", help="Stops and deletes a broker from project")
|
25
|
-
def stop(name: str, project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
26
|
-
|
27
|
-
SpinnerColumn(),
|
28
|
-
TextColumn("[progress.description]{task.description}"),
|
29
|
-
transient=True,
|
30
|
-
) as progress:
|
31
|
-
progress.add_task(description=f"Deleting broker {name}...", total=None)
|
32
|
-
rest.handle_delete(f"/api/project/{project}/brokers/{name}")
|
28
|
+
def stop(name: str, project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
29
|
+
Rest.handle_delete(f"/api/project/{project}/brokers/{name}")
|
33
30
|
|
34
31
|
|
35
32
|
@app.command(help="Deletes your personal broker from project")
|
36
|
-
def delete_personal(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
37
|
-
|
38
|
-
SpinnerColumn(),
|
39
|
-
TextColumn("[progress.description]{task.description}"),
|
40
|
-
transient=True,
|
41
|
-
) as progress:
|
42
|
-
progress.add_task(description="Deleting personal broker...", total=None)
|
43
|
-
rest.handle_delete(f"/api/project/{project}/brokers/personal")
|
33
|
+
def delete_personal(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
34
|
+
Rest.handle_delete(f"/api/project/{project}/brokers/personal")
|
44
35
|
|
45
36
|
|
46
|
-
def do_start(name: str, project: str, api_key: str, tag: str, return_response: bool = False):
|
37
|
+
def do_start(name: str, project: str, api_key: str, tag: str, return_response: bool = False) -> Union[requests.Response, None]:
|
47
38
|
if tag == "":
|
48
39
|
tag_to_use = None
|
49
40
|
else:
|
@@ -53,7 +44,7 @@ def do_start(name: str, project: str, api_key: str, tag: str, return_response: b
|
|
53
44
|
body = {"size": "S", "tag": tag_to_use}
|
54
45
|
else:
|
55
46
|
body = {"size": "S", "apiKey": api_key, "tag": tag_to_use}
|
56
|
-
return
|
47
|
+
return Rest.handle_post(
|
57
48
|
f"/api/project/{project}/brokers/{name}",
|
58
49
|
body=json.dumps(body),
|
59
50
|
return_response=return_response,
|
@@ -68,7 +59,7 @@ def start(
|
|
68
59
|
tag: str = typer.Option("", help="Optional specific tag/version"),
|
69
60
|
silent: bool = typer.Option(False, help="Optional specific tag/version"),
|
70
61
|
api_key: str = typer.Option("", help="Start with your own api-key"),
|
71
|
-
):
|
62
|
+
) -> None:
|
72
63
|
# with Progress(
|
73
64
|
# SpinnerColumn(),
|
74
65
|
# TextColumn("[progress.description]{task.description}"),
|
@@ -84,7 +75,7 @@ def logs(
|
|
84
75
|
tail: bool = typer.Option(False, help="Tail the broker log"),
|
85
76
|
history_minutes: str = typer.Option(10, help="History in minutes minutes to fetch"),
|
86
77
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
87
|
-
):
|
78
|
+
) -> None:
|
88
79
|
"""
|
89
80
|
Exposes broker logs history or real-time tail of the broker.
|
90
81
|
|
@@ -92,30 +83,30 @@ def logs(
|
|
92
83
|
|
93
84
|
"""
|
94
85
|
|
95
|
-
def exit_on_ctrlc(
|
86
|
+
def exit_on_ctrlc(__sig: Any, __frame: Any) -> None:
|
96
87
|
wsapp.close()
|
97
88
|
os._exit(0)
|
98
89
|
|
99
90
|
os_signal.signal(os_signal.SIGINT, exit_on_ctrlc)
|
100
91
|
|
101
|
-
def on_message(
|
92
|
+
def on_message(__wsapp: Any, message: str) -> None:
|
102
93
|
print(message)
|
103
94
|
|
104
|
-
def on_error(wsapp, err):
|
95
|
+
def on_error(wsapp: Any, err: str) -> None: # pylint: disable=W0613
|
105
96
|
print("EXAMPLE error encountered: ", err)
|
106
97
|
|
107
|
-
|
98
|
+
Rest.ensure_auth_token()
|
108
99
|
# This will work with both http -> ws and https -> wss
|
109
|
-
ws_url =
|
100
|
+
ws_url = Rest.get_base_url().replace("http", "ws")
|
110
101
|
|
111
102
|
if tail:
|
112
103
|
q = "?tail=yes"
|
113
|
-
elif history_minutes != 10:
|
104
|
+
elif history_minutes != "10":
|
114
105
|
q = f"?history={history_minutes}"
|
115
106
|
else:
|
116
107
|
q = ""
|
117
108
|
|
118
109
|
wsapp = websocket.WebSocketApp(
|
119
|
-
f"{ws_url}/api/project/{project}/brokers/{broker_name}/logs{q}", header=
|
110
|
+
f"{ws_url}/api/project/{project}/brokers/{broker_name}/logs{q}", header=Rest.get_headers(), on_message=on_message, on_error=on_error
|
120
111
|
)
|
121
112
|
wsapp.run_forever()
|
cli/cloud/cloud_cli.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
import json
|
2
|
-
|
3
1
|
import typer
|
4
2
|
|
5
|
-
from . import
|
6
|
-
|
3
|
+
from cli.cloud.rest_helper import RestHelper
|
4
|
+
|
5
|
+
from . import auth, brokers, configs, filestorage, organisations, projects, recordings, sample_recordings, service_accounts
|
7
6
|
|
8
7
|
app = typer.Typer()
|
9
8
|
|
@@ -11,22 +10,12 @@ app = typer.Typer()
|
|
11
10
|
@app.command(help="List licenses for an organisation")
|
12
11
|
def licenses(
|
13
12
|
organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION"),
|
14
|
-
|
15
|
-
):
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@app.command(help="List your available organisations")
|
20
|
-
def organisations():
|
21
|
-
r = rest.handle_get("/api/home", return_response=True)
|
22
|
-
if r.status_code == 200:
|
23
|
-
j = list(map(lambda x: x["billableUnitUser"]["billableUnit"]["uid"], r.json()))
|
24
|
-
print(json.dumps(j))
|
25
|
-
else:
|
26
|
-
print(f"Got status code: {r.status_code}")
|
27
|
-
print(r.text)
|
13
|
+
filter_option: str = typer.Option("all", help="all, valid, expired"),
|
14
|
+
) -> None:
|
15
|
+
RestHelper.handle_get(f"/api/bu/{organisation}/licenses", {"filter": filter_option})
|
28
16
|
|
29
17
|
|
18
|
+
app.add_typer(organisations.app, name="organisations", help="Manage organisations")
|
30
19
|
app.add_typer(projects.app, name="projects", help="Manage projects")
|
31
20
|
app.add_typer(auth.app, name="auth")
|
32
21
|
app.add_typer(brokers.app, name="brokers", help="Manage cloud broker lifecycle")
|
cli/cloud/configs.py
CHANGED
@@ -7,22 +7,28 @@ import requests
|
|
7
7
|
import typer
|
8
8
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
9
9
|
|
10
|
-
from . import
|
10
|
+
from .rest_helper import RestHelper as Rest
|
11
11
|
|
12
12
|
app = typer.Typer()
|
13
13
|
|
14
14
|
|
15
15
|
@app.command("list")
|
16
|
-
def list_signal_databases(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
|
17
|
-
|
16
|
+
def list_signal_databases(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
17
|
+
"""
|
18
|
+
List available signal databases in project
|
19
|
+
"""
|
20
|
+
Rest.handle_get(f"/api/project/{project}/files/config")
|
18
21
|
|
19
22
|
|
20
23
|
@app.command("delete")
|
21
24
|
def delete(
|
22
25
|
signal_db_file: str = typer.Argument("", help="Signal database file"),
|
23
26
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
24
|
-
):
|
25
|
-
|
27
|
+
) -> None:
|
28
|
+
"""
|
29
|
+
Deletes the specified signal database
|
30
|
+
"""
|
31
|
+
Rest.handle_delete(f"/api/project/{project}/files/config/{signal_db_file}")
|
26
32
|
|
27
33
|
|
28
34
|
@app.command()
|
@@ -38,35 +44,45 @@ def upload(
|
|
38
44
|
help="Path to signal database file to upload",
|
39
45
|
),
|
40
46
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
41
|
-
):
|
47
|
+
) -> None:
|
42
48
|
"""
|
43
49
|
Uploads signal database to project
|
44
50
|
"""
|
45
|
-
|
51
|
+
res = Rest.handle_put(url=f"/api/project/{project}/files/config/{os.path.basename(path)}/uploadfile", return_response=True)
|
52
|
+
if res is not None:
|
53
|
+
Rest.upload_file_with_signed_url(
|
54
|
+
path=path, url=res.text, upload_headers={"Content-Type": "application/octet-stream"}, progress_label=f"Uploading {path}..."
|
55
|
+
)
|
46
56
|
|
47
57
|
|
48
58
|
@app.command()
|
49
59
|
def download(
|
50
60
|
signal_db_file: str = typer.Argument("", help="Signal database file"),
|
51
61
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
52
|
-
):
|
62
|
+
) -> None:
|
63
|
+
"""
|
64
|
+
Downloads the specified signal database to disk
|
65
|
+
"""
|
53
66
|
with Progress(
|
54
67
|
SpinnerColumn(),
|
55
68
|
TextColumn("[progress.description]{task.description}"),
|
56
69
|
transient=True,
|
57
70
|
) as progress:
|
58
|
-
|
71
|
+
Rest.ensure_auth_token()
|
59
72
|
|
60
73
|
progress.add_task(description=f"Downloading {signal_db_file}", total=None)
|
61
74
|
|
62
75
|
# First request the download url from cloud. This is a public signed url that is valid
|
63
76
|
# for a short period of time
|
64
77
|
get_signed_url_resp = requests.get(
|
65
|
-
f"{
|
78
|
+
f"{Rest.get_base_url()}/api/project/{project}/files/config/{signal_db_file}/download",
|
79
|
+
headers=Rest.get_headers(),
|
80
|
+
allow_redirects=True,
|
81
|
+
timeout=60,
|
66
82
|
)
|
67
83
|
if get_signed_url_resp.status_code == 200:
|
68
84
|
# Next download the actual file
|
69
|
-
download_resp = requests.get(url=get_signed_url_resp.text, stream=True)
|
85
|
+
download_resp = requests.get(url=get_signed_url_resp.text, stream=True, timeout=60)
|
70
86
|
if download_resp.status_code == 200:
|
71
87
|
with open(signal_db_file, "wb") as out_file:
|
72
88
|
shutil.copyfileobj(download_resp.raw, out_file)
|
@@ -86,4 +102,5 @@ def download(
|
|
86
102
|
# print(r.status_code)
|
87
103
|
# print(r.text)
|
88
104
|
|
105
|
+
# pylint: disable=C0301
|
89
106
|
# curl -X PUT -H 'Content-Type: application/octet-stream' --upload-file docker-compose.yml 'https://storage.googleapis.com/beamylabs-fileuploads-dev/projects/beamyhack/recording/myrecording?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=recordings-upload-account%40beamycloud-dev.iam.gserviceaccount.com%2F20220729%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20220729T134012Z&X-Goog-Expires=3000&X-Goog-SignedHeaders=content-type%3Bhost&X-Goog-Signature=d1fa7639349d6453aebfce8814d6e5685af03952d07aa4e3cb0d44dba7cf5e572f684c8120dba17cbc7ea6a0ef5450542a3c745c65e04272b34265d0ddcf1b67e6f2b5bfa446264a62d77bd7faabf45ad6bd2aec5225f57004b0a31cfe0480cba063a3807d86346b1da99ecbae3f3e6da8f44f06396dfc1fdc6f89e475abdf969142cef6f369f03aff41000c8abb28aa82185246746fd6c16b6b381baa2d586382a3d3067b6376ddba2b55b2b6f9d942913a1cbfbc61491ba6a615d7d5a0d9a476c357431143e9cea1411dfad9f01b1e1176dc8c056cbf08cccfd401a55d63c19d038f3ab42b712abc48d759047ac07862c4fae937c341e19b568bb60a4e4086'
|
cli/cloud/filestorage.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
import os.path
|
2
|
+
import sys
|
2
3
|
from pathlib import Path
|
3
4
|
|
4
5
|
import typer
|
5
6
|
|
6
7
|
from ..errors import ErrorPrinter
|
7
|
-
from . import rest_helper as rest
|
8
8
|
from . import resumable_upload as upload
|
9
|
+
from .rest_helper import RestHelper as Rest
|
9
10
|
|
10
11
|
app = typer.Typer(
|
11
12
|
rich_markup_mode="rich",
|
@@ -22,7 +23,7 @@ Copy file from local to remote storage and vice versa, list and delete files.
|
|
22
23
|
def list_files(
|
23
24
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
24
25
|
prefix: str = typer.Argument(default="rcs://", help="Remote storage path"),
|
25
|
-
):
|
26
|
+
) -> None:
|
26
27
|
"""
|
27
28
|
Listing remote files
|
28
29
|
|
@@ -40,9 +41,9 @@ def list_files(
|
|
40
41
|
prefix = __check_rcs_path(prefix)
|
41
42
|
else:
|
42
43
|
ErrorPrinter.print_hint("Path must start with rcs://")
|
43
|
-
exit(1)
|
44
|
+
sys.exit(1)
|
44
45
|
|
45
|
-
|
46
|
+
Rest.handle_get(
|
46
47
|
f"/api/project/{project}/files/storage{prefix}",
|
47
48
|
)
|
48
49
|
|
@@ -51,7 +52,7 @@ def list_files(
|
|
51
52
|
def delete_file(
|
52
53
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
53
54
|
path: str = typer.Argument(default=..., help="Remote storage path to file to delete"),
|
54
|
-
):
|
55
|
+
) -> None:
|
55
56
|
"""
|
56
57
|
[red]Deletes[/red] a file from remote storage, this cannot be undone :fire:
|
57
58
|
|
@@ -61,19 +62,19 @@ def delete_file(
|
|
61
62
|
prefix = __check_rcs_path(path)
|
62
63
|
else:
|
63
64
|
ErrorPrinter.print_hint("Path must start with rcs://")
|
64
|
-
exit(1)
|
65
|
+
sys.exit(1)
|
65
66
|
|
66
|
-
|
67
|
+
Rest.handle_delete(
|
67
68
|
f"/api/project/{project}/files/storage{prefix}",
|
68
69
|
)
|
69
70
|
|
70
71
|
|
71
72
|
@app.command(name="cp")
|
72
|
-
def copy_file(
|
73
|
+
def copy_file( # noqa: C901 # type: ignore[too-many-branches] # pylint: disable=no-member
|
73
74
|
source: str = typer.Argument(default=..., help="Remote or local path to source file"),
|
74
75
|
dest: str = typer.Argument(default=..., help="Remote or local path to destination file"),
|
75
76
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
76
|
-
):
|
77
|
+
) -> None:
|
77
78
|
"""
|
78
79
|
Copies a file to or from remote storage
|
79
80
|
|
@@ -86,58 +87,69 @@ def copy_file(
|
|
86
87
|
|
87
88
|
if not source.startswith("rcs://") and not dest.startswith("rcs://"):
|
88
89
|
ErrorPrinter.print_hint("Source or destination path must be an rcs:// path")
|
89
|
-
exit(2)
|
90
|
+
sys.exit(2)
|
90
91
|
|
91
92
|
if source.startswith("rcs://") and dest.startswith("rcs://"):
|
92
93
|
ErrorPrinter.print_hint("Currently one of source and destination path must be a local path")
|
93
|
-
exit(2)
|
94
|
+
sys.exit(2)
|
94
95
|
|
95
96
|
if source.startswith("rcs://"):
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
97
|
+
__copy_to_local(source=source, dest=dest, project=project)
|
98
|
+
else:
|
99
|
+
__copy_to_remote(source=source, dest=dest, project=project)
|
100
|
+
|
101
|
+
|
102
|
+
def __copy_to_remote(source: str, dest: str, project: str) -> None:
|
103
|
+
path = Path(source)
|
104
|
+
if not path.exists():
|
105
|
+
ErrorPrinter.print_hint("Source file does not exist")
|
106
|
+
sys.exit(1)
|
107
|
+
filename = source.rsplit("/", 1)[-1]
|
108
|
+
rcs_path = __check_rcs_path(dest)
|
109
|
+
if rcs_path.endswith("/"):
|
110
|
+
rcs_path = rcs_path + filename
|
111
|
+
res = Rest.handle_post(f"/api/project/{project}/files/storage{rcs_path}", return_response=True)
|
112
|
+
if res is None:
|
113
|
+
return
|
114
|
+
json = res.json()
|
115
|
+
url = json["url"]
|
116
|
+
content_type = json["contentType"]
|
117
|
+
try:
|
118
|
+
upload.upload_signed_url(url, source, content_type)
|
119
|
+
except IsADirectoryError:
|
120
|
+
ErrorPrinter.print_hint(f"Supplied source file '{source}' is a directory but must be a file")
|
121
|
+
|
122
|
+
|
123
|
+
def __copy_to_local(source: str, dest: str, project: str) -> None:
|
124
|
+
rcs_path = __check_rcs_path(source)
|
125
|
+
filename = source.rsplit("/", 1)[-1]
|
126
|
+
path = Path(dest)
|
127
|
+
if path.is_dir():
|
128
|
+
if not path.exists():
|
129
|
+
ErrorPrinter.print_generic_error("Destination directory does not exist")
|
130
|
+
sys.exit(1)
|
106
131
|
else:
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
132
|
+
dest = os.path.join(path.absolute(), filename)
|
133
|
+
|
134
|
+
else:
|
135
|
+
if not path.parent.is_dir() or not path.parent.exists():
|
136
|
+
ErrorPrinter.print_generic_error("Destination directory does not exist")
|
137
|
+
sys.exit(1)
|
138
|
+
dest = str(Path(dest).absolute())
|
139
|
+
|
140
|
+
res = Rest.handle_get(
|
141
|
+
f"/api/project/{project}/files/storage{rcs_path}?download=true",
|
142
|
+
return_response=True,
|
143
|
+
)
|
144
|
+
if res is None:
|
145
|
+
return
|
111
146
|
|
112
|
-
|
113
|
-
f"/api/project/{project}/files/storage{rcs_path}?download=true",
|
114
|
-
return_response=True,
|
115
|
-
)
|
147
|
+
Rest.download_file(save_file_name=dest, url=res.text)
|
116
148
|
|
117
|
-
rest.download_file(save_file_name=dest, url=res.text)
|
118
149
|
|
119
|
-
|
120
|
-
path = Path(source)
|
121
|
-
if not path.exists():
|
122
|
-
ErrorPrinter.print_hint("Source file does not exist")
|
123
|
-
exit(1)
|
124
|
-
filename = source.rsplit("/", 1)[-1]
|
125
|
-
rcs_path = __check_rcs_path(dest)
|
126
|
-
if rcs_path.endswith("/"):
|
127
|
-
rcs_path = rcs_path + filename
|
128
|
-
res = rest.handle_post(f"/api/project/{project}/files/storage{rcs_path}", return_response=True)
|
129
|
-
json = res.json()
|
130
|
-
url = json["url"]
|
131
|
-
content_type = json["contentType"]
|
132
|
-
try:
|
133
|
-
upload.upload_signed_url(url, source, content_type)
|
134
|
-
except IsADirectoryError:
|
135
|
-
ErrorPrinter.print_hint(f"Supplied source file '{source}' is a directory but must be a file")
|
136
|
-
|
137
|
-
|
138
|
-
def __check_rcs_path(path: str):
|
150
|
+
def __check_rcs_path(path: str) -> str:
|
139
151
|
rcs_path = path.replace("rcs://", "/")
|
140
152
|
if rcs_path.startswith("/."):
|
141
153
|
ErrorPrinter.print_hint("Invalid path")
|
142
|
-
exit(1)
|
154
|
+
sys.exit(1)
|
143
155
|
return rcs_path
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import json
|
2
|
+
import sys
|
3
|
+
|
4
|
+
import typer
|
5
|
+
|
6
|
+
from cli.cloud.rest_helper import RestHelper
|
7
|
+
|
8
|
+
app = typer.Typer()
|
9
|
+
|
10
|
+
|
11
|
+
@app.command(name="list", help="List your available organisations")
|
12
|
+
def list_orgs() -> None:
|
13
|
+
r = RestHelper.handle_get("/api/home", return_response=True)
|
14
|
+
if r is None:
|
15
|
+
return
|
16
|
+
if r.status_code == 200:
|
17
|
+
j = list(
|
18
|
+
map(
|
19
|
+
lambda x: {
|
20
|
+
"uid": x["billableUnitUser"]["billableUnit"]["uid"],
|
21
|
+
"displayName": x["billableUnitUser"]["billableUnit"]["displayName"],
|
22
|
+
},
|
23
|
+
r.json(),
|
24
|
+
)
|
25
|
+
)
|
26
|
+
print(json.dumps(j))
|
27
|
+
else:
|
28
|
+
print(f"Got status code: {r.status_code}")
|
29
|
+
print(r.text)
|
30
|
+
sys.exit(1)
|
cli/cloud/projects.py
CHANGED
@@ -2,14 +2,17 @@ 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
|
|
9
9
|
|
10
10
|
@app.command(name="list", help="List your projects")
|
11
|
-
def list_projects(organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION")):
|
12
|
-
r =
|
11
|
+
def list_projects(organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION")) -> None:
|
12
|
+
r = Rest.handle_get(url=f"/api/bu/{organisation}/me", return_response=True)
|
13
|
+
if r is None:
|
14
|
+
return
|
15
|
+
|
13
16
|
if r.status_code == 200:
|
14
17
|
# extract the project uid parts
|
15
18
|
projects = r.json()["projects"]
|
@@ -22,19 +25,19 @@ def list_projects(organisation: str = typer.Option(..., help="Organisation ID",
|
|
22
25
|
|
23
26
|
@app.command(name="create")
|
24
27
|
def create_project(
|
28
|
+
project_uid: str = typer.Argument(help="Project UID"),
|
25
29
|
organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION"),
|
26
|
-
project_uid: str = typer.Option(..., help="Project UID"),
|
27
30
|
project_display_name: str = typer.Option(default="", help="Project display name"),
|
28
|
-
):
|
31
|
+
) -> None:
|
29
32
|
create_project_req = {
|
30
33
|
"uid": project_uid,
|
31
34
|
"displayName": project_display_name if project_display_name != "" else project_uid,
|
32
35
|
"description": "",
|
33
36
|
}
|
34
37
|
|
35
|
-
|
38
|
+
Rest.handle_post(url=f"/api/bu/{organisation}/project", body=json.dumps(create_project_req))
|
36
39
|
|
37
40
|
|
38
41
|
@app.command(name="delete")
|
39
|
-
def delete(project: str = typer.
|
40
|
-
|
42
|
+
def delete(project: str = typer.Argument(help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
|
43
|
+
Rest.handle_delete(url=f"/api/project/{project}")
|