CGCSDK 0.1.10__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.
- cgcsdk-0.1.10.dist-info/LICENSE +0 -0
- cgcsdk-0.1.10.dist-info/METADATA +36 -0
- cgcsdk-0.1.10.dist-info/RECORD +44 -0
- cgcsdk-0.1.10.dist-info/WHEEL +5 -0
- cgcsdk-0.1.10.dist-info/entry_points.txt +2 -0
- cgcsdk-0.1.10.dist-info/top_level.txt +2 -0
- src/.env +9 -0
- src/__init__.py +9 -0
- src/cgc.py +30 -0
- src/commands/__init__.py +0 -0
- src/commands/auth/__init__.py +0 -0
- src/commands/auth/auth_cmd.py +70 -0
- src/commands/auth/auth_utils.py +151 -0
- src/commands/billing/__init__.py +0 -0
- src/commands/billing/billing_cmd.py +115 -0
- src/commands/billing/billing_utils.py +36 -0
- src/commands/cgc_cmd.py +18 -0
- src/commands/compute/__init__.py +0 -0
- src/commands/compute/compute_cmd.py +220 -0
- src/commands/compute/compute_responses.py +156 -0
- src/commands/compute/compute_utills.py +96 -0
- src/commands/compute/data_model.py +27 -0
- src/commands/volume/__init__.py +0 -0
- src/commands/volume/data_model.py +46 -0
- src/commands/volume/volume_cmd.py +237 -0
- src/commands/volume/volume_responses.py +257 -0
- src/commands/volume/volume_utils.py +22 -0
- src/config.py +0 -0
- src/telemetry/__init__.py +0 -0
- src/telemetry/basic.py +44 -0
- src/utils/__init__.py +0 -0
- src/utils/click_group.py +17 -0
- src/utils/config_utils.py +89 -0
- src/utils/consts/__init__.py +0 -0
- src/utils/consts/message_consts.py +12 -0
- src/utils/consts/telemetry_consts.py +0 -0
- src/utils/cryptography/__init__.py +0 -0
- src/utils/cryptography/aes_crypto.py +100 -0
- src/utils/cryptography/encryption_module.py +55 -0
- src/utils/cryptography/rsa_crypto.py +126 -0
- src/utils/message_utils.py +28 -0
- src/utils/prepare_headers.py +86 -0
- src/utils/response_utils.py +25 -0
- src/utils/version_control.py +67 -0
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cgcsdk
|
|
3
|
+
Version: 0.1.10
|
|
4
|
+
Summary: Comtegra GPU Cloud REST API client
|
|
5
|
+
Home-page: https://github.com/Comtegra/cgc
|
|
6
|
+
Author: Comtegra AI Team
|
|
7
|
+
Author-email: info@comtegra.pl
|
|
8
|
+
License: BSD 2-clause
|
|
9
|
+
Keywords: cloud,sdk
|
|
10
|
+
Classifier: Development Status :: 1 - Planning
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.4
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: click
|
|
23
|
+
Requires-Dist: python-dotenv
|
|
24
|
+
Requires-Dist: tabulate
|
|
25
|
+
Requires-Dist: pycryptodomex
|
|
26
|
+
Requires-Dist: paramiko (>=2.11)
|
|
27
|
+
Requires-Dist: statsd
|
|
28
|
+
Requires-Dist: requests
|
|
29
|
+
Requires-Dist: setuptools
|
|
30
|
+
|
|
31
|
+
# CGC Client - K8s Cloud
|
|
32
|
+
|
|
33
|
+
cgc command line client for k8s Comtegra cloud environment.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Coming soon...
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
src/.env,sha256=2QcyU5ZdShTvd0arn0aysqpZJ-pRYrXo6Pd5IUIUqI0,192
|
|
2
|
+
src/__init__.py,sha256=XTwjaEQ5cyKyl7CnQlaEpK65FH_FD0DUj8GNLvkfpB4,279
|
|
3
|
+
src/cgc.py,sha256=8HB8pkb-DH894vlrxMwAfAkbQj7NP2utxpbOxyDOLZc,836
|
|
4
|
+
src/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
src/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
src/commands/cgc_cmd.py,sha256=gY7EmChPbPwHlOIMQzDXwLYgdSZslLkNhQuwRaNrRCc,470
|
|
7
|
+
src/commands/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
src/commands/auth/auth_cmd.py,sha256=g1slgpbcjJF6ajNWGCkTUyJ7hzZyMA4ruvl7Et5xRNw,2759
|
|
9
|
+
src/commands/auth/auth_utils.py,sha256=iAy1L8XEv4i-CukB1LODqHQwT2ZihNiUM_TKO1Kc4Qg,5175
|
|
10
|
+
src/commands/billing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
src/commands/billing/billing_cmd.py,sha256=_40vL9JCJMBMjyZyQlISlqBugm35EjsrH3FDRJN5J04,3303
|
|
12
|
+
src/commands/billing/billing_utils.py,sha256=hB9haCbBsnyvftS5t_XfGw8Mnp2mH0ZEo4P7bfsMPmY,1313
|
|
13
|
+
src/commands/compute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
src/commands/compute/compute_cmd.py,sha256=5_S5jwCzKFyM0vzutcXTxtpkn5I-iiD1q_7yOouTC_Y,7101
|
|
15
|
+
src/commands/compute/compute_responses.py,sha256=P-r40lNmab0EO9rZ3OBcuhY9snN7tcrvsUhbH76zKFQ,6337
|
|
16
|
+
src/commands/compute/compute_utills.py,sha256=Cw_PPqw-MbStDCuxjZVWrWH3XPvA_SNfV9R48it2V1k,2844
|
|
17
|
+
src/commands/compute/data_model.py,sha256=y2XHsLau2wD5J8LJPoIlOOv8a5eYq5AIEK7MWheNcOI,584
|
|
18
|
+
src/commands/volume/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
src/commands/volume/data_model.py,sha256=c_aYwxoYOngW0yx-rQcUkF9LgugUp_CajTeOruI2_2o,976
|
|
20
|
+
src/commands/volume/volume_cmd.py,sha256=SfHOvw99G2VFtr6CT4RS0HfnPE41CnzhwGdJobOT8Qk,7990
|
|
21
|
+
src/commands/volume/volume_responses.py,sha256=8j44HgUiNikh2ZkeMwYIwq_JlgVJ63s7i-q_JESdkSI,10476
|
|
22
|
+
src/commands/volume/volume_utils.py,sha256=hxY6N7vmZ7Aitrb-gQ_rEKt_isOE3N_tX6tfnb3ZsIg,969
|
|
23
|
+
src/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
src/telemetry/basic.py,sha256=_358FABtHCfTbxD6gsYoGXJ_Dn0KTMyeGIKwS2HEDaA,952
|
|
25
|
+
src/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
src/utils/click_group.py,sha256=Dn0QJWHni4_9-j0tpFmYAXH5z3ooq1h52eBBxzbXhvE,571
|
|
27
|
+
src/utils/config_utils.py,sha256=SV0SVLzffJtV1rqV1gAn5kbYXi_OGca_lbTiCOMtCFQ,2718
|
|
28
|
+
src/utils/message_utils.py,sha256=U6zkcrMCCdXW-VAEHF6BMKdSkxGvVpsSHriQDzpWL38,696
|
|
29
|
+
src/utils/prepare_headers.py,sha256=5eiun_bAZ2JzrQyr2ZtZthSp0j4WU-m4DSjr_F2KPPc,2528
|
|
30
|
+
src/utils/response_utils.py,sha256=r4fiTCANLDMCC2eElQKcguKkaJ7AnU6_1x_bai5uaEA,849
|
|
31
|
+
src/utils/version_control.py,sha256=D0I3DnonnnMtXxJTGAHiM7aNgPR277CrRLMwfbTpjqY,2032
|
|
32
|
+
src/utils/consts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
src/utils/consts/message_consts.py,sha256=6MTsYRw5qwUZLVg5mt84sE3ZZOTRH9YdcT9JubPi_k0,654
|
|
34
|
+
src/utils/consts/telemetry_consts.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
+
src/utils/cryptography/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
+
src/utils/cryptography/aes_crypto.py,sha256=6x5Z_sPRlTVCSDCLg77LOcuUFP4oY1buTiYxhM9dXJ0,3433
|
|
37
|
+
src/utils/cryptography/encryption_module.py,sha256=oZgMqxwltQHSMgxS-Kc21gzvMbIIu1int0toRgI5E5I,2013
|
|
38
|
+
src/utils/cryptography/rsa_crypto.py,sha256=2g6IVe_UMZ0w_YpT6AWC3qwOepAK9MmXxjOAdXZ15Cc,4306
|
|
39
|
+
cgcsdk-0.1.10.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
+
cgcsdk-0.1.10.dist-info/METADATA,sha256=FZMRrBLJl52bRbo_p3Xb2ebpUDAmr-GZeLr39IJ7ElM,1107
|
|
41
|
+
cgcsdk-0.1.10.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
42
|
+
cgcsdk-0.1.10.dist-info/entry_points.txt,sha256=uaM2l0xK1cv3U7jjZqsHQCL-YdYyGKd5GnzP5ykvL9Q,36
|
|
43
|
+
cgcsdk-0.1.10.dist-info/top_level.txt,sha256=qixLkg2QFPjkS6Q4Af7kQ7NywvH_H33BAB7KVCyEy4w,12
|
|
44
|
+
cgcsdk-0.1.10.dist-info/RECORD,,
|
src/.env
ADDED
src/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cgc
|
|
3
|
+
Comtegra GPU Cloud REST API client
|
|
4
|
+
"""
|
|
5
|
+
from src.utils.version_control import get_version
|
|
6
|
+
|
|
7
|
+
__version__ = get_version()
|
|
8
|
+
__author__ = "Przemysław Kowalski <przemyslaw.kowalski@comtegra.pl>, Jakub Zboina <jakub.zboina@comtegra.pl>"
|
|
9
|
+
__credits__ = "Quantum Dev Labs"
|
src/cgc.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from src.commands.volume.volume_cmd import volume_group
|
|
4
|
+
from src.commands.compute.compute_cmd import compute_group
|
|
5
|
+
from src.commands.billing.billing_cmd import billing_group
|
|
6
|
+
from src.commands.auth.auth_cmd import auth_register
|
|
7
|
+
from src.commands.cgc_cmd import cgc_rm
|
|
8
|
+
from src.utils.version_control import get_version
|
|
9
|
+
from src.utils.click_group import CustomGroup
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group(cls=CustomGroup)
|
|
13
|
+
@click.version_option(get_version())
|
|
14
|
+
def cli():
|
|
15
|
+
|
|
16
|
+
"""CGC application developed by Comtegra S.A."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
cli.add_command(volume_group)
|
|
21
|
+
cli.add_command(compute_group)
|
|
22
|
+
cli.add_command(billing_group)
|
|
23
|
+
cli.add_command(auth_register)
|
|
24
|
+
cli.add_command(cgc_rm)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__" or __name__ == "src.cgc":
|
|
28
|
+
cli()
|
|
29
|
+
else:
|
|
30
|
+
raise Exception("This program is not intended for importing!")
|
src/commands/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import requests
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import pkg_resources
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
from src.utils.config_utils import get_config_path, get_namespace
|
|
9
|
+
from src.commands.auth import auth_utils
|
|
10
|
+
from src.utils.cryptography import rsa_crypto
|
|
11
|
+
from src.utils.config_utils import add_to_config
|
|
12
|
+
from src.utils.prepare_headers import get_api_url_and_prepare_headers_register
|
|
13
|
+
from src.telemetry.basic import increment_metric
|
|
14
|
+
from src.utils.message_utils import prepare_error_message, prepare_success_message
|
|
15
|
+
from src.utils.consts.message_consts import TIMEOUT_ERROR
|
|
16
|
+
from src.utils.click_group import CustomCommand
|
|
17
|
+
|
|
18
|
+
ENV_FILE_PATH = pkg_resources.resource_filename("src", ".env")
|
|
19
|
+
load_dotenv(dotenv_path=ENV_FILE_PATH, verbose=True)
|
|
20
|
+
TMP_DIR = os.path.join(get_config_path(), os.getenv("TMP_DIR"))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.command("register", cls=CustomCommand)
|
|
24
|
+
@click.option("--user_id", "-u", "user_id", prompt=True)
|
|
25
|
+
@click.option("--access_key", "-k", "access_key", prompt=True)
|
|
26
|
+
def auth_register(user_id: str, access_key: str):
|
|
27
|
+
"""Register a user in system using user id and access key.
|
|
28
|
+
\f
|
|
29
|
+
:param user_id: username received in invite
|
|
30
|
+
:type user_id: str
|
|
31
|
+
:param access_key: access key received in invite
|
|
32
|
+
:type access_key: str
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
url, headers = get_api_url_and_prepare_headers_register(user_id, access_key)
|
|
36
|
+
pub_key_bytes, priv_key_bytes = rsa_crypto.key_generate_pair()
|
|
37
|
+
payload = pub_key_bytes
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
res = requests.post(
|
|
41
|
+
url,
|
|
42
|
+
data=payload,
|
|
43
|
+
headers=headers,
|
|
44
|
+
allow_redirects=True,
|
|
45
|
+
timeout=10,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if res.status_code != 200:
|
|
49
|
+
increment_metric("auth.register.error")
|
|
50
|
+
if res.status_code == 401:
|
|
51
|
+
message = "Could not validate user id or access key."
|
|
52
|
+
click.echo(prepare_error_message(message))
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
message = "Could not register user. Please try again or contact support at support@comtegra.pl."
|
|
56
|
+
click.echo(prepare_error_message(message))
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
unzip_dir = auth_utils.save_and_unzip_file(res)
|
|
60
|
+
aes_key, passwd = auth_utils.get_aes_key_and_passwd(unzip_dir, priv_key_bytes)
|
|
61
|
+
|
|
62
|
+
add_to_config(user_id=user_id, passwd=passwd, aes_key=aes_key)
|
|
63
|
+
auth_utils.auth_create_api_key()
|
|
64
|
+
|
|
65
|
+
message = f"Register successful! You can now use the CLI. Config file is saved in {get_config_path()}"
|
|
66
|
+
click.echo(prepare_success_message(message))
|
|
67
|
+
increment_metric(f"{get_namespace()}.auth.register.ok")
|
|
68
|
+
shutil.rmtree(TMP_DIR)
|
|
69
|
+
except requests.exceptions.ReadTimeout:
|
|
70
|
+
click.echo(prepare_error_message(TIMEOUT_ERROR))
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import click, requests, json, os, shutil, glob, base64, urllib, pkg_resources
|
|
2
|
+
from dotenv import load_dotenv
|
|
3
|
+
|
|
4
|
+
from src.utils.config_utils import get_config_path, get_namespace
|
|
5
|
+
from src.telemetry.basic import increment_metric
|
|
6
|
+
from src.utils.config_utils import add_to_config
|
|
7
|
+
from src.utils.config_utils import read_from_cfg
|
|
8
|
+
from src.utils.cryptography import rsa_crypto
|
|
9
|
+
from src.utils import prepare_headers
|
|
10
|
+
from src.utils.message_utils import prepare_error_message
|
|
11
|
+
|
|
12
|
+
ENV_FILE_PATH = pkg_resources.resource_filename("src", ".env")
|
|
13
|
+
load_dotenv(dotenv_path=ENV_FILE_PATH, verbose=True)
|
|
14
|
+
|
|
15
|
+
API_HOST = os.getenv("API_HOST")
|
|
16
|
+
API_PORT = os.getenv("API_PORT")
|
|
17
|
+
API_URL = f"http://{API_HOST}:{API_PORT}"
|
|
18
|
+
CGC_SECRET = os.getenv("CGC_SECRET")
|
|
19
|
+
TMP_DIR = os.path.join(get_config_path(), os.getenv("TMP_DIR"))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_jwt() -> str:
|
|
23
|
+
"""Command to get JWT token and api key for user
|
|
24
|
+
|
|
25
|
+
:param username: _description_
|
|
26
|
+
:type username: str
|
|
27
|
+
:param password: _description_
|
|
28
|
+
:type password: str
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
user_id = urllib.parse.quote(read_from_cfg("user_id"))
|
|
32
|
+
passwd = urllib.parse.quote(read_from_cfg("passwd"))
|
|
33
|
+
url = f"{API_URL}/v1/api/user/create/token"
|
|
34
|
+
payload = f"grant_type=&username={user_id}&password={passwd}"
|
|
35
|
+
|
|
36
|
+
jwt_headers = {
|
|
37
|
+
"accept": "application/json",
|
|
38
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
39
|
+
}
|
|
40
|
+
try:
|
|
41
|
+
res = requests.post(
|
|
42
|
+
url,
|
|
43
|
+
payload,
|
|
44
|
+
headers=jwt_headers,
|
|
45
|
+
timeout=10,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if res.status_code != 200:
|
|
49
|
+
message = (
|
|
50
|
+
"Could not generate JWT. Try again or contact us at support@comtegra.pl"
|
|
51
|
+
)
|
|
52
|
+
click.echo(prepare_error_message(message))
|
|
53
|
+
increment_metric(f"{get_namespace()}.auth.jwt.error")
|
|
54
|
+
return None
|
|
55
|
+
increment_metric(f"{get_namespace()}.auth.jwt.ok")
|
|
56
|
+
|
|
57
|
+
data = json.loads(res.text)
|
|
58
|
+
jwt = data["access_token"]
|
|
59
|
+
return jwt
|
|
60
|
+
|
|
61
|
+
except requests.exceptions.ReadTimeout:
|
|
62
|
+
message = "Connection timed out. Try again or contact us at support@comtegra.pl"
|
|
63
|
+
click.echo(prepare_error_message(message))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def auth_create_api_key():
|
|
67
|
+
"""Command to create API key and API secret for user and save it to config file."""
|
|
68
|
+
url = f"{API_URL}/v1/api/user/create/api-key"
|
|
69
|
+
headers = prepare_headers.prepare_headers_api_key()
|
|
70
|
+
try:
|
|
71
|
+
res = requests.post(
|
|
72
|
+
url,
|
|
73
|
+
headers=headers,
|
|
74
|
+
timeout=10,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if res.status_code != 200:
|
|
78
|
+
message = "Could not generate API key. Try again or contact us at support@comtegra.pl"
|
|
79
|
+
click.echo(prepare_error_message(message))
|
|
80
|
+
increment_metric(f"{get_namespace()}.auth.api-key.error")
|
|
81
|
+
return
|
|
82
|
+
increment_metric(f"{get_namespace()}.auth.api-key.ok")
|
|
83
|
+
|
|
84
|
+
data = json.loads(res.text)
|
|
85
|
+
|
|
86
|
+
api_key = data["details"].get("_id")
|
|
87
|
+
secret = data["details"].get("secret")
|
|
88
|
+
|
|
89
|
+
add_to_config(api_key=api_key, api_secret=secret)
|
|
90
|
+
|
|
91
|
+
except requests.exceptions.ReadTimeout:
|
|
92
|
+
message = "Connection timed out. Try again or contact us at support@comtegra.pl"
|
|
93
|
+
click.echo(prepare_error_message(message))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def save_and_unzip_file(res: requests.Response) -> str:
|
|
97
|
+
"""Command to save file, unzip it and return path to its directory.
|
|
98
|
+
|
|
99
|
+
:param res: API response with file to save and unzip
|
|
100
|
+
:type res: requests.Response
|
|
101
|
+
:return: path to the directory containing the file
|
|
102
|
+
:rtype: str
|
|
103
|
+
"""
|
|
104
|
+
zip_file = res.headers.get("content-disposition").split('"')[1]
|
|
105
|
+
namespace = zip_file.split("---")[-1].split(".")[0]
|
|
106
|
+
add_to_config(namespace=namespace)
|
|
107
|
+
|
|
108
|
+
if not os.path.isdir(TMP_DIR):
|
|
109
|
+
os.makedirs(TMP_DIR)
|
|
110
|
+
zip_file_path = f"{TMP_DIR}/{zip_file}"
|
|
111
|
+
with open(zip_file_path, "wb") as f:
|
|
112
|
+
f.write(res.content)
|
|
113
|
+
|
|
114
|
+
unzip_dir = zip_file_path[:-4]
|
|
115
|
+
shutil.unpack_archive(zip_file_path, unzip_dir)
|
|
116
|
+
|
|
117
|
+
return unzip_dir
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_aes_key_and_passwd(unzip_dir: str, priv_key_bytes: bytes):
|
|
121
|
+
"""Command to get AES key and password from the unzipped file.
|
|
122
|
+
|
|
123
|
+
:param unzip_dir: path to the directory containing the file
|
|
124
|
+
:type unzip_dir: str
|
|
125
|
+
:param priv_key_bytes: private key bytes
|
|
126
|
+
:type priv_key_bytes: bytes
|
|
127
|
+
:return: AES key and password
|
|
128
|
+
:rtype: str, str
|
|
129
|
+
"""
|
|
130
|
+
encrypted_passwd_path = ""
|
|
131
|
+
encrypted_aes_path = ""
|
|
132
|
+
for file in glob.glob(
|
|
133
|
+
f"{unzip_dir}/**/*encrypted*",
|
|
134
|
+
recursive=True,
|
|
135
|
+
):
|
|
136
|
+
if file.endswith("priv"):
|
|
137
|
+
encrypted_aes_path = f"{file}"
|
|
138
|
+
elif file.endswith("password"):
|
|
139
|
+
encrypted_passwd_path = f"{file}"
|
|
140
|
+
|
|
141
|
+
rsa_key = rsa_crypto.import_create_RSAKey(priv_key_bytes)
|
|
142
|
+
|
|
143
|
+
with open(encrypted_aes_path, "rb") as aes, open(
|
|
144
|
+
encrypted_passwd_path, "rb"
|
|
145
|
+
) as pwd:
|
|
146
|
+
aes_key = rsa_crypto.decrypt_rsa(aes.read(), rsa_key).decode("ascii")
|
|
147
|
+
passwd = base64.b64decode(rsa_crypto.decrypt_rsa(pwd.read(), rsa_key)).decode(
|
|
148
|
+
"utf-8"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return aes_key, passwd
|
|
File without changes
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import click
|
|
3
|
+
import pkg_resources
|
|
4
|
+
import requests
|
|
5
|
+
from tabulate import tabulate
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from src.commands.billing.billing_utils import (
|
|
10
|
+
get_status_list_to_print,
|
|
11
|
+
get_status_list_headers,
|
|
12
|
+
)
|
|
13
|
+
from src.telemetry.basic import increment_metric
|
|
14
|
+
from src.utils.config_utils import get_namespace
|
|
15
|
+
from src.utils.message_utils import prepare_error_message
|
|
16
|
+
from src.utils.prepare_headers import get_api_url_and_prepare_headers
|
|
17
|
+
from src.utils.response_utils import response_precheck
|
|
18
|
+
from src.utils.click_group import CustomGroup, CustomCommand
|
|
19
|
+
|
|
20
|
+
ENV_FILE_PATH = pkg_resources.resource_filename("src", ".env")
|
|
21
|
+
load_dotenv(dotenv_path=ENV_FILE_PATH, verbose=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
API_URL = os.getenv("API_URL")
|
|
25
|
+
CGC_SECRET = os.getenv("CGC_SECRET")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.group("billing", cls=CustomGroup)
|
|
29
|
+
def billing_group():
|
|
30
|
+
"""
|
|
31
|
+
Access and manage billing information.
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@billing_group.command("status", cls=CustomCommand)
|
|
37
|
+
def billing_status():
|
|
38
|
+
"""
|
|
39
|
+
Shows billing status for user namespace
|
|
40
|
+
"""
|
|
41
|
+
metric_ok = f"{get_namespace()}.billing.status.ok"
|
|
42
|
+
metric_error = f"{get_namespace()}.billing.status.error"
|
|
43
|
+
|
|
44
|
+
api_url, headers = get_api_url_and_prepare_headers()
|
|
45
|
+
url = f"{api_url}/v1/api/billing/status"
|
|
46
|
+
try:
|
|
47
|
+
response = requests.get(
|
|
48
|
+
url=url,
|
|
49
|
+
headers=headers,
|
|
50
|
+
timeout=10,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
response_precheck(response, metric_error)
|
|
54
|
+
|
|
55
|
+
if response.status_code != 200:
|
|
56
|
+
increment_metric(metric_error)
|
|
57
|
+
message = "Error occuerd while getting billing status. Try again or contact us at support@comtegra.pl"
|
|
58
|
+
click.echo(prepare_error_message(message))
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
data = response.json()
|
|
62
|
+
total_cost = data["details"]["costs"]["cost_total"]
|
|
63
|
+
namespace = data["details"]["namespace"]
|
|
64
|
+
costs_list = data["details"]["costs"]["details"]
|
|
65
|
+
if not costs_list:
|
|
66
|
+
click.echo("No costs found")
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
costs_list_to_print = get_status_list_to_print(costs_list)
|
|
70
|
+
list_headers = get_status_list_headers()
|
|
71
|
+
click.echo(tabulate(costs_list_to_print, headers=list_headers))
|
|
72
|
+
click.echo(f"\nTotal cost for namespace {namespace}: {total_cost:.2f}\n")
|
|
73
|
+
increment_metric(metric_ok)
|
|
74
|
+
except requests.exceptions.ReadTimeout:
|
|
75
|
+
message = "Connection timed out. Try again or contact us at support@comtegra.pl"
|
|
76
|
+
increment_metric(metric_error)
|
|
77
|
+
click.echo(prepare_error_message(message))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@billing_group.command("pay", cls=CustomCommand)
|
|
81
|
+
def billing_pay():
|
|
82
|
+
"""
|
|
83
|
+
Interface for easy payment
|
|
84
|
+
"""
|
|
85
|
+
click.echo("Initializing payment!")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@click.group("fvat", cls=CustomGroup)
|
|
89
|
+
def fvat_group():
|
|
90
|
+
"""
|
|
91
|
+
Invoice management
|
|
92
|
+
"""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@fvat_group.command("ls", cls=CustomCommand)
|
|
97
|
+
def fvat_ls():
|
|
98
|
+
"""
|
|
99
|
+
Lists all invoices for user namespace
|
|
100
|
+
"""
|
|
101
|
+
click.echo("Listing all invoices!")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@fvat_group.command("id", cls=CustomCommand)
|
|
105
|
+
@click.argument(
|
|
106
|
+
"invoice_id",
|
|
107
|
+
)
|
|
108
|
+
def fvat_id(invoice_id: str):
|
|
109
|
+
"""
|
|
110
|
+
Opens invoice with given ID
|
|
111
|
+
"""
|
|
112
|
+
click.echo(f"Opening invoice {invoice_id}!")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
billing_group.add_command(fvat_group)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
def get_status_list_to_print(costs_list: list):
|
|
2
|
+
"""Method format data in list of voulmes to be displayed in table and calculate total size of volumes
|
|
3
|
+
|
|
4
|
+
:param list_of_volumes: list of volumes to be formatted
|
|
5
|
+
:type list_of_volumes: list
|
|
6
|
+
:return: list of volumes to be displayed in table and total size of volumes
|
|
7
|
+
:rtype: list, int
|
|
8
|
+
"""
|
|
9
|
+
list_to_print = []
|
|
10
|
+
for cost in costs_list:
|
|
11
|
+
rent_start = cost["rent_start"]
|
|
12
|
+
rent_end = cost["rent_end"]
|
|
13
|
+
rent_time = f"{int(cost['rent_time'])} s"
|
|
14
|
+
|
|
15
|
+
total_cost = f"{float(cost['cost_total']):.2f} pln"
|
|
16
|
+
resources = cost["resources"]
|
|
17
|
+
name = resources["name"]
|
|
18
|
+
rent_type = cost["type"]
|
|
19
|
+
if rent_type == "events_compute":
|
|
20
|
+
entity = resources["entity"]
|
|
21
|
+
elif rent_type == "events_volume":
|
|
22
|
+
entity = "volume"
|
|
23
|
+
row_list = [entity, name, rent_start, rent_end, rent_time, total_cost]
|
|
24
|
+
list_to_print.append(row_list)
|
|
25
|
+
list_to_print.sort(key=lambda d: f"{d[0]} {d[2]}")
|
|
26
|
+
return list_to_print
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_status_list_headers():
|
|
30
|
+
"""Generates headers for billing status command
|
|
31
|
+
|
|
32
|
+
:return: list of headers
|
|
33
|
+
:rtype: list
|
|
34
|
+
"""
|
|
35
|
+
headers = ["entity", "name", "start", "end", "time", "cost"]
|
|
36
|
+
return headers
|
src/commands/cgc_cmd.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from src.commands.compute.compute_cmd import compute_delete
|
|
4
|
+
from src.utils.version_control import check_version
|
|
5
|
+
from src.utils.click_group import CustomCommand
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command("rm", cls=CustomCommand)
|
|
9
|
+
@click.argument("name", type=click.STRING)
|
|
10
|
+
def cgc_rm(name: str):
|
|
11
|
+
"""
|
|
12
|
+
Delete a compute pod in user namespace
|
|
13
|
+
\f
|
|
14
|
+
:param name: name of pod to delete
|
|
15
|
+
:type name: str
|
|
16
|
+
"""
|
|
17
|
+
check_version()
|
|
18
|
+
compute_delete(name)
|
|
File without changes
|