cgcsdk 0.8.5__tar.gz → 0.8.7__tar.gz

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 (86) hide show
  1. {cgcsdk-0.8.5/cgcsdk.egg-info → cgcsdk-0.8.7}/PKG-INFO +1 -1
  2. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/.env +1 -1
  3. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/CHANGELOG.md +20 -0
  4. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/cgc.py +2 -0
  5. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/auth/__init__.py +5 -0
  6. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/auth/auth_cmd.py +16 -4
  7. cgcsdk-0.8.7/cgc/commands/auth/auth_responses.py +35 -0
  8. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/auth/auth_utils.py +1 -2
  9. cgcsdk-0.8.7/cgc/commands/cgc_cmd.py +117 -0
  10. cgcsdk-0.8.7/cgc/commands/cgc_helpers.py +33 -0
  11. cgcsdk-0.8.7/cgc/sdk/redis.py +88 -0
  12. cgcsdk-0.8.7/cgc/utils/__init__.py +113 -0
  13. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/config_utils.py +14 -13
  14. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/consts/env_consts.py +4 -1
  15. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/consts/message_consts.py +4 -1
  16. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/message_utils.py +7 -1
  17. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/prepare_headers.py +3 -0
  18. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/requests_helper.py +1 -1
  19. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/response_utils.py +5 -1
  20. {cgcsdk-0.8.5 → cgcsdk-0.8.7/cgcsdk.egg-info}/PKG-INFO +1 -1
  21. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgcsdk.egg-info/SOURCES.txt +1 -0
  22. cgcsdk-0.8.5/cgc/commands/auth/auth_responses.py +0 -27
  23. cgcsdk-0.8.5/cgc/commands/cgc_cmd.py +0 -64
  24. cgcsdk-0.8.5/cgc/sdk/redis.py +0 -51
  25. cgcsdk-0.8.5/cgc/utils/cryptography/__init__.py +0 -0
  26. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/LICENSE +0 -0
  27. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/MANIFEST.in +0 -0
  28. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/README.md +0 -0
  29. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/__init__.py +0 -0
  30. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/__init__.py +0 -0
  31. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/billing/__init__.py +0 -0
  32. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/billing/billing_cmd.py +0 -0
  33. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/billing/billing_responses.py +0 -0
  34. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/billing/billing_utils.py +0 -0
  35. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/cgc_cmd_responses.py +0 -0
  36. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/compute/__init__.py +0 -0
  37. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/compute/compute_cmd.py +0 -0
  38. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/compute/compute_models.py +0 -0
  39. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/compute/compute_responses.py +0 -0
  40. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/compute/compute_utills.py +0 -0
  41. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/db/__init__.py +0 -0
  42. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/db/db_cmd.py +0 -0
  43. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/debug/__init__.py +0 -0
  44. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/debug/debug_cmd.py +0 -0
  45. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/exceptions.py +0 -0
  46. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/resource/__init__.py +0 -0
  47. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/resource/resource_cmd.py +0 -0
  48. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/volume/__init__.py +0 -0
  49. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/volume/data_model.py +0 -0
  50. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/volume/volume_cmd.py +0 -0
  51. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/volume/volume_responses.py +0 -0
  52. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/commands/volume/volume_utils.py +0 -0
  53. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/config.py +0 -0
  54. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/sdk/__init__.py +0 -0
  55. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/sdk/handlers.py +0 -0
  56. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/sdk/mongodb.py +0 -0
  57. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/sdk/postgresql.py +0 -0
  58. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/server.crt +0 -0
  59. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/telemetry/__init__.py +0 -0
  60. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/telemetry/basic.py +0 -0
  61. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/__init__.py +0 -0
  62. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/desired_responses/test_billing_invoice.txt +0 -0
  63. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/desired_responses/test_billing_status.txt +0 -0
  64. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/desired_responses/test_billing_stop_events_compute.txt +0 -0
  65. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/desired_responses/test_billing_stop_events_volume.txt +0 -0
  66. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/desired_responses/test_compute_list.txt +0 -0
  67. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/desired_responses/test_compute_list_no_labels.txt +0 -0
  68. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/desired_responses/test_tabulate_response.txt +0 -0
  69. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/desired_responses/test_volume_list.txt +0 -0
  70. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/tests/responses_tests.py +0 -0
  71. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/click_group.py +0 -0
  72. {cgcsdk-0.8.5/cgc/utils → cgcsdk-0.8.7/cgc/utils/consts}/__init__.py +0 -0
  73. {cgcsdk-0.8.5/cgc/utils/consts → cgcsdk-0.8.7/cgc/utils/cryptography}/__init__.py +0 -0
  74. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/cryptography/aes_crypto.py +0 -0
  75. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/cryptography/encryption_module.py +0 -0
  76. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/cryptography/rsa_crypto.py +0 -0
  77. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/custom_exceptions.py +0 -0
  78. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/update.py +0 -0
  79. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgc/utils/version_control.py +0 -0
  80. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgcsdk.egg-info/dependency_links.txt +0 -0
  81. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgcsdk.egg-info/entry_points.txt +0 -0
  82. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgcsdk.egg-info/requires.txt +0 -0
  83. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/cgcsdk.egg-info/top_level.txt +0 -0
  84. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/pyproject.toml +0 -0
  85. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/setup.cfg +0 -0
  86. {cgcsdk-0.8.5 → cgcsdk-0.8.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cgcsdk
3
- Version: 0.8.5
3
+ Version: 0.8.7
4
4
  Summary: Comtegra GPU Cloud REST API client
5
5
  Author: Comtegra AI Team
6
6
  Author-email: ai@comtegra.pl
@@ -7,4 +7,4 @@ CONFIG_FILE_NAME = cfg.json
7
7
  TMP_DIR = .tmp
8
8
  RELEASE = 0
9
9
  MAJOR_VERSION = 8
10
- MINOR_VERSION = 5
10
+ MINOR_VERSION = 7
@@ -1,5 +1,25 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.8.7
4
+
5
+ Release on May 23, 2023.
6
+
7
+ * hotfix: development changed .env file making it broken for new users
8
+
9
+ ## 0.8.6
10
+
11
+ Release on May 19, 2023.
12
+
13
+ * added get_redis_client_async() for RedisConnector
14
+ * get_redis_access() - async_client: bool, new parameter
15
+ * new command: cgc context switch [number]
16
+ * switch between user contexts
17
+ * new command: cgc context list
18
+ * list all configuration files
19
+ * updated command: cgc register
20
+ * allow to have multiple user contexts
21
+ * increased timeout for requests to 30 sec
22
+
3
23
  ## 0.8.5
4
24
 
5
25
  Release on Apr 21, 2023.
@@ -15,6 +15,7 @@ from cgc.commands.cgc_cmd import (
15
15
  cgc_status,
16
16
  sending_telemetry_permission,
17
17
  resource_events,
18
+ context_group,
18
19
  )
19
20
  from cgc.utils.version_control import check_version, _get_version
20
21
  from cgc.utils.click_group import CustomGroup
@@ -28,6 +29,7 @@ def cli():
28
29
 
29
30
 
30
31
  cli.add_command(debug_group)
32
+ cli.add_command(context_group)
31
33
  cli.add_command(auth_register)
32
34
  cli.add_command(api_keys_group)
33
35
  cli.add_command(volume_group)
@@ -8,3 +8,8 @@ class AuthCommandException(ResponseException):
8
8
  class NoNamespaceInConfig(AuthCommandException):
9
9
  def __init__(self) -> None:
10
10
  super().__init__(f"Namespace not readable from config file.")
11
+
12
+
13
+ class NoConfigFileFound(AuthCommandException):
14
+ def __init__(self) -> None:
15
+ super().__init__(f"Config does not exists.")
@@ -7,12 +7,16 @@ from cgc.commands.auth.auth_responses import (
7
7
  from cgc.commands.auth.auth_utils import (
8
8
  auth_create_api_key_with_save,
9
9
  )
10
-
11
10
  from cgc.utils.prepare_headers import get_url_and_prepare_headers_register
12
11
  from cgc.utils.cryptography import rsa_crypto
13
12
  from cgc.utils.click_group import CustomCommand, CustomGroup
14
13
  from cgc.utils.requests_helper import call_api, EndpointTypes
15
14
  from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
15
+ from cgc.utils import (
16
+ check_if_config_exist,
17
+ require_confirm_loop,
18
+ find_first_available_config_name,
19
+ )
16
20
 
17
21
 
18
22
  @click.group("api-keys", cls=CustomGroup, hidden=True)
@@ -24,9 +28,9 @@ def api_keys_group():
24
28
 
25
29
 
26
30
  @click.command("register", cls=CustomCommand)
27
- @click.option("--user_id", "-u", "user_id", prompt=True)
28
- @click.option("--access_key", "-k", "access_key", prompt=True)
29
- def auth_register(user_id: str, access_key: str):
31
+ # @click.option("--user_id", "-u", "user_id", prompt=True)
32
+ # @click.option("--access_key", "-k", "access_key", prompt=True)
33
+ def auth_register(config_filename: str = "cfg.json"):
30
34
  """Register a user in system using user id and access key.\n
31
35
  Enabling/Disabling Telemetry sending is available, if set to yes CGC will send
32
36
  usage metrics for application improvements purposes.
@@ -40,6 +44,13 @@ def auth_register(user_id: str, access_key: str):
40
44
  :type telemetry_sending: bool
41
45
  """
42
46
 
47
+ if check_if_config_exist(config_filename):
48
+ click.echo("Already registered.")
49
+ require_confirm_loop("Do you want to add new context?")
50
+ config_filename = find_first_available_config_name()
51
+
52
+ user_id = input("User ID: ")
53
+ access_key = input("Access key: ")
43
54
  url, headers = get_url_and_prepare_headers_register(user_id, access_key)
44
55
  metric = "auth.register"
45
56
  pub_key_bytes, priv_key_bytes = rsa_crypto.key_generate_pair()
@@ -56,6 +67,7 @@ def auth_register(user_id: str, access_key: str):
56
67
  retrieve_and_validate_response_send_metric(__res, metric, False),
57
68
  user_id,
58
69
  priv_key_bytes,
70
+ config_filename,
59
71
  )
60
72
  )
61
73
 
@@ -0,0 +1,35 @@
1
+ import shutil
2
+ import os
3
+
4
+ from cgc.commands.auth import auth_utils
5
+ from cgc.utils.consts.env_consts import TMP_DIR, get_config_file_name
6
+ from cgc.utils.config_utils import get_config_path, save_to_config
7
+ from cgc.utils.message_utils import key_error_decorator_for_helpers
8
+
9
+
10
+ @key_error_decorator_for_helpers
11
+ def auth_register_response(response, user_id, priv_key_bytes, config_filename) -> str:
12
+ TMP_DIR_PATH = os.path.join(get_config_path(), TMP_DIR)
13
+ unzip_dir, namespace = auth_utils.save_and_unzip_file(response)
14
+ aes_key, password = auth_utils.get_aes_key_and_password(unzip_dir, priv_key_bytes)
15
+
16
+ os.environ["CONFIG_FILE_NAME"] = config_filename
17
+ save_to_config(
18
+ user_id=user_id, password=password, aes_key=aes_key, namespace=namespace
19
+ )
20
+ auth_utils.auth_create_api_key_with_save()
21
+ shutil.rmtree(TMP_DIR_PATH)
22
+ # config.json
23
+ if config_filename == "config.json":
24
+ return f"Register successful! You can now use the CLI. Saved data to:{os.path.join(get_config_path(),config_filename)}\n\
25
+ Consider backup this file. It stores data accessible only to you with which you can access CGC platform."
26
+ return f"New context created successfully! \nNew config file saved to: {os.path.join(get_config_path(),config_filename)}\n\
27
+ Consider backup this file. It stores data accessible only to you with which you can access CGC platform.\n \n\
28
+ To switch context use \ncgc context switch"
29
+
30
+
31
+ @key_error_decorator_for_helpers
32
+ def login_successful_response():
33
+ return f"Successfully logged in, created new API key pair.\n\
34
+ Saved data to: {os.path.join(get_config_path(), get_config_file_name())}.\n\
35
+ Consider backup this file. It stores data accessible only to you with which you can access CGC platform."
@@ -76,7 +76,6 @@ def save_and_unzip_file(res: requests.Response) -> str:
76
76
  """
77
77
  zip_file = res.headers.get("content-disposition").split('"')[1]
78
78
  namespace = zip_file.split("---")[-1].split(".")[0]
79
- save_to_config(namespace=namespace)
80
79
 
81
80
  if not os.path.isdir(TMP_DIR_PATH):
82
81
  os.makedirs(TMP_DIR_PATH)
@@ -87,7 +86,7 @@ def save_and_unzip_file(res: requests.Response) -> str:
87
86
  unzip_dir = zip_file_path[:-4]
88
87
  shutil.unpack_archive(zip_file_path, unzip_dir)
89
88
 
90
- return unzip_dir
89
+ return unzip_dir, namespace
91
90
 
92
91
 
93
92
  def get_aes_key_and_password(unzip_dir: str, priv_key_bytes: bytes):
@@ -0,0 +1,117 @@
1
+ import click
2
+ import json
3
+
4
+ from cgc.commands.cgc_cmd_responses import cgc_status_response
5
+ from cgc.commands.resource.resource_cmd import resource_delete
6
+ from cgc.utils.requests_helper import call_api, EndpointTypes
7
+ from cgc.utils.click_group import CustomCommand, CustomGroup
8
+ from cgc.utils.prepare_headers import get_api_url_and_prepare_headers
9
+ from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
10
+ from cgc.telemetry.basic import telemetry_permission_set
11
+ from cgc.commands.compute.compute_responses import compute_logs_response
12
+ from cgc.commands.auth.auth_cmd import auth_register
13
+ from cgc.utils import set_environment_data, check_if_config_exist, list_all_config_files
14
+ from cgc.commands.cgc_helpers import table_of_user_context_files
15
+ from cgc.utils.config_utils import config_path
16
+
17
+
18
+ @click.command("rm", cls=CustomCommand)
19
+ @click.argument("name", type=click.STRING)
20
+ def cgc_rm(name: str):
21
+ """
22
+ Delete an app in user namespace
23
+ """
24
+ resource_delete(name)
25
+
26
+
27
+ @click.command("events", cls=CustomCommand)
28
+ @click.argument("app_name", type=click.STRING)
29
+ def resource_events(app_name: str):
30
+ """Get events of given app"""
31
+ api_url, headers = get_api_url_and_prepare_headers()
32
+ url = f"{api_url}/v1/api/resource/get_pod_events"
33
+ metric = "resource.events"
34
+ __payload = {"name": app_name}
35
+ __res = call_api(
36
+ request=EndpointTypes.get,
37
+ url=url,
38
+ headers=headers,
39
+ data=json.dumps(__payload),
40
+ )
41
+ click.echo(
42
+ compute_logs_response(retrieve_and_validate_response_send_metric(__res, metric))
43
+ )
44
+
45
+
46
+ @click.command("status", cls=CustomCommand)
47
+ def cgc_status():
48
+ """Lists available and used resources"""
49
+ api_url, headers = get_api_url_and_prepare_headers()
50
+ url = f"{api_url}/v1/api/resource/status"
51
+ metric = "resource.status"
52
+ __res = call_api(
53
+ request=EndpointTypes.get,
54
+ url=url,
55
+ headers=headers,
56
+ )
57
+ click.echo(
58
+ cgc_status_response(retrieve_and_validate_response_send_metric(__res, metric))
59
+ )
60
+
61
+
62
+ @click.command("telemetry", cls=CustomCommand)
63
+ def sending_telemetry_permission():
64
+ """Changing permission for sending telemetry"""
65
+
66
+ click.echo(
67
+ f"Sending telemetry is now {'enabled' if telemetry_permission_set() else 'disabled'}"
68
+ )
69
+
70
+
71
+ @click.group(name="context", cls=CustomGroup)
72
+ def context_group():
73
+ """
74
+ Switch between namespaces (contexts) that are at your disposal.
75
+ """
76
+
77
+
78
+ @context_group.command("switch", cls=CustomCommand)
79
+ @click.argument("number", type=click.INT)
80
+ # @click.pass_context
81
+ def switch_context(number: int):
82
+ """Set which namespace config should be used. After switching context your next command will be run in given namespace that corresponds to user namespace"""
83
+ file_name = f"{number}.json" if number > 1 else "cfg.json"
84
+
85
+ if not check_if_config_exist(file_name):
86
+ click.echo("Selected context does not exist.")
87
+ click.echo("To get all available contexts use:")
88
+ click.echo("cgc context list")
89
+ exit(0)
90
+ # user_id = input("User ID: ")
91
+ # access_key = input("Access key: ")
92
+ # ctx.invoke(
93
+ # auth_register,
94
+ # user_id=user_id,
95
+ # access_key=access_key,
96
+ # config_filename=file_name,
97
+ # )
98
+ set_environment_data("CONFIG_FILE_NAME", file_name)
99
+ click.echo(f"Context file changed to: {file_name}")
100
+
101
+
102
+ @context_group.command("list", cls=CustomCommand)
103
+ def list_context():
104
+ """List all namespaces available to you"""
105
+ click.echo(table_of_user_context_files(list_all_config_files()))
106
+
107
+
108
+ @context_group.command("folder", cls=CustomCommand)
109
+ def folder_of_contexts():
110
+ """Check location of config files in case that you need to export them."""
111
+ click.echo(f"All config files are located: {config_path}")
112
+ click.echo(
113
+ "If you'd like to use them on a different machine just copy all the files to corresponding folder."
114
+ )
115
+ click.echo(
116
+ "You can check location of that folder on different machine with the same command."
117
+ )
@@ -0,0 +1,33 @@
1
+ from typing import List
2
+ from cgc.utils import quick_sort
3
+ from cgc.utils.config_utils import read_from_cfg
4
+ from tabulate import tabulate
5
+
6
+
7
+ def table_of_user_context_files(config_files: List[str]):
8
+ # print tabulate of: [context NR | namespace | user_id]
9
+ headers = ["Context No.", "Namespace", "User ID"]
10
+ contexts = []
11
+ contexts_nrs = []
12
+ for file in config_files:
13
+ file_context = []
14
+ file_context.append(
15
+ int(file.split(".")[0]) if file != "cfg.json" else 1
16
+ ) # should never throw exception with good config_file list
17
+ contexts_nrs.append(file_context[0])
18
+ file_data = read_from_cfg(None, file)
19
+ values_to_read = ["namespace", "user_id"]
20
+ for k in values_to_read:
21
+ try:
22
+ value = file_data[k]
23
+ except KeyError:
24
+ value = None
25
+ file_context.append(value)
26
+ contexts.append(file_context)
27
+
28
+ contexts_nrs_sorted = quick_sort(contexts_nrs)
29
+ contexts_sorted = []
30
+ for context in contexts_nrs_sorted:
31
+ contexts_sorted.append(contexts[contexts_nrs.index(context)])
32
+
33
+ return tabulate(contexts_sorted, headers=headers)
@@ -0,0 +1,88 @@
1
+ from redis.asyncio import redis as redis_async
2
+ import redis
3
+
4
+
5
+ class RedisConnector:
6
+ redis_client_async = None
7
+ redis_client = None
8
+
9
+ def __init__(
10
+ self, host: str, password: str = None, decode_responses: bool = False
11
+ ) -> None:
12
+ self._host = host
13
+ assert type(host) is str
14
+ "host must be a str containing redis app name"
15
+ self._password = password
16
+ self._decode_responses = decode_responses
17
+
18
+ def connect(self, async_client: bool = False):
19
+ while True:
20
+ try:
21
+ if not async_client:
22
+ self.redis_client = redis.Redis(
23
+ host=self._host,
24
+ port=6379,
25
+ password=self._password,
26
+ decode_responses=self._decode_responses,
27
+ )
28
+ else:
29
+ self.redis_client_async = redis_async.Redis(
30
+ host=self._host,
31
+ port=6379,
32
+ password=self._password,
33
+ decode_responses=self._decode_responses,
34
+ )
35
+ print(f"Connected to Redis: {self._host}")
36
+ break
37
+ except (redis.ConnectionError,) as e:
38
+ print(f"Redis connection error: {e}")
39
+ print(f"retrying to connect...")
40
+
41
+ def get_redis_client(self):
42
+ if self.redis_client is None:
43
+ self.connect()
44
+ return self.redis_client
45
+
46
+ def get_redis_client_async(self):
47
+ if self.redis_client_async is None:
48
+ self.connect(async_client=True)
49
+ return self.redis_client_async
50
+
51
+
52
+ def get_redis_access(
53
+ app_name: str,
54
+ password: str,
55
+ decode_responses: bool = False,
56
+ restart: bool = False,
57
+ async_client=False,
58
+ ):
59
+ global _redis_access
60
+ global _redis_access_async
61
+
62
+ def init_access(async_client=False):
63
+ global _redis_access
64
+ global _redis_access_async
65
+
66
+ if not async_client:
67
+ _redis_access = RedisConnector(
68
+ host=app_name, password=password, decode_responses=decode_responses
69
+ )
70
+ else:
71
+ _redis_access_async = RedisConnector(
72
+ host=app_name, password=password, decode_responses=decode_responses
73
+ )
74
+
75
+ try:
76
+ if not async_client:
77
+ if not isinstance(_redis_access, RedisConnector) or restart:
78
+ init_access()
79
+ else:
80
+ if not isinstance(_redis_access_async, RedisConnector) or restart:
81
+ init_access(True)
82
+ except NameError:
83
+ if not async_client:
84
+ init_access()
85
+ else:
86
+ init_access(True)
87
+ pass
88
+ return _redis_access if async_client else _redis_access_async
@@ -0,0 +1,113 @@
1
+ from typing import List
2
+ from os import listdir
3
+ from os.path import isfile, join
4
+ from cgc.utils.config_utils import config_path
5
+ from operator import is_not
6
+ from functools import partial
7
+ from random import randrange
8
+
9
+ from cgc.utils.consts.env_consts import ENV_FILE_PATH
10
+ from cgc.utils.config_utils import read_from_cfg
11
+ from cgc.commands.auth import NoConfigFileFound
12
+
13
+
14
+ def require_confirm_loop(message: str):
15
+ while True:
16
+ answer = input(f"{message} (Y/N): ").lower()
17
+ if answer in ("y", "yes"):
18
+ break
19
+ if answer in ("n", "no"):
20
+ exit(0)
21
+
22
+
23
+ def quick_sort(collection: list) -> list:
24
+ """A pure Python implementation of quick sort algorithm
25
+
26
+ :param collection: a mutable collection of comparable items
27
+ :return: the same collection ordered by ascending
28
+
29
+ Examples:
30
+ >>> quick_sort([0, 5, 3, 2, 2])
31
+ [0, 2, 2, 3, 5]
32
+ >>> quick_sort([])
33
+ []
34
+ >>> quick_sort([-2, 5, 0, -45])
35
+ [-45, -2, 0, 5]
36
+ """
37
+ if len(collection) < 2:
38
+ return collection
39
+ pivot_index = randrange(len(collection)) # Use random element as pivot
40
+ pivot = collection[pivot_index]
41
+ greater: list[int] = [] # All elements greater than pivot
42
+ lesser: list[int] = [] # All elements less than or equal to pivot
43
+
44
+ for element in collection[:pivot_index]:
45
+ (greater if element > pivot else lesser).append(element)
46
+
47
+ for element in collection[pivot_index + 1 :]:
48
+ (greater if element > pivot else lesser).append(element)
49
+
50
+ return [*quick_sort(lesser), pivot, *quick_sort(greater)]
51
+
52
+
53
+ def set_environment_data(variable: str, value: str):
54
+ """Set variable to .env file
55
+
56
+ :return: new value
57
+ :rtype: str
58
+ """
59
+ f = open(file=ENV_FILE_PATH, mode="r")
60
+ replaced_content = f.read()
61
+ replaced_content = replaced_content.splitlines()
62
+ f.close()
63
+ for i, line in enumerate(replaced_content):
64
+ splitted = line.split(" ")
65
+ if splitted[0] == variable.upper():
66
+ replaced_content[i] = line.replace(splitted[2], value)
67
+ with open(file=ENV_FILE_PATH, mode="w") as f:
68
+ f.write("\n".join(replaced_content))
69
+ break
70
+ else:
71
+ with open(file=ENV_FILE_PATH, mode="a") as f:
72
+ f.write(f"\n{variable.upper()} = {value}")
73
+
74
+ return value
75
+
76
+
77
+ def find_first_available_config_name() -> str:
78
+ increment = 2
79
+ while True:
80
+ filename = f"{increment}.json"
81
+ try:
82
+ read_from_cfg(None, filename)
83
+ increment += 1
84
+ except NoConfigFileFound:
85
+ break
86
+ return filename
87
+
88
+
89
+ def check_if_config_exist(filename: str) -> bool:
90
+ try:
91
+ read_from_cfg(None, filename)
92
+ except NoConfigFileFound:
93
+ return False
94
+ return True
95
+
96
+
97
+ def list_all_config_files() -> List[str]:
98
+ only_files = [f for f in listdir(config_path) if isfile(join(config_path, f))]
99
+
100
+ def only_json_file(filename: str):
101
+ return filename if filename.endswith(".json") else None
102
+
103
+ def cgc_config_file(filename: str):
104
+ filename_prefix = filename.split(".")[0]
105
+ try:
106
+ int(filename_prefix)
107
+ return filename
108
+ except ValueError:
109
+ if filename_prefix == "cfg":
110
+ return filename
111
+
112
+ json_files = list(filter(partial(is_not, None), map(only_json_file, only_files)))
113
+ return list(filter(partial(is_not, None), map(cgc_config_file, json_files)))
@@ -3,11 +3,9 @@ import os
3
3
  import sys
4
4
  import click
5
5
 
6
- from cgc.commands.auth import NoNamespaceInConfig
6
+ from cgc.commands.auth import NoNamespaceInConfig, NoConfigFileFound
7
7
  from cgc.utils.message_utils import prepare_error_message
8
- from cgc.utils.consts.env_consts import (
9
- CONFIG_FILE_NAME,
10
- )
8
+ from cgc.utils.consts.env_consts import get_config_file_name
11
9
 
12
10
 
13
11
  def get_config_path():
@@ -32,7 +30,6 @@ def get_config_path():
32
30
 
33
31
 
34
32
  config_path = get_config_path()
35
- user_config_file = os.path.join(config_path, CONFIG_FILE_NAME)
36
33
 
37
34
 
38
35
  def save_to_config(**kwargs):
@@ -44,6 +41,7 @@ def save_to_config(**kwargs):
44
41
  :type kwargs: dict
45
42
  """
46
43
  read_cfg = {}
44
+ user_config_file = os.path.join(config_path, get_config_file_name())
47
45
  if not os.path.isdir(config_path):
48
46
  os.makedirs(config_path)
49
47
  try:
@@ -57,7 +55,7 @@ def save_to_config(**kwargs):
57
55
  json.dump(final_cfg, f)
58
56
 
59
57
 
60
- def read_from_cfg(key: str):
58
+ def read_from_cfg(key: str, filename=None):
61
59
  """Function to read a single value from config
62
60
 
63
61
  :param key: key name to read the value from config
@@ -65,15 +63,18 @@ def read_from_cfg(key: str):
65
63
  :return: value for the provided key
66
64
  :rtype: _type_
67
65
  """
66
+ if filename is None:
67
+ filename_with_path = os.path.join(config_path, get_config_file_name())
68
+ else:
69
+ filename_with_path = os.path.join(config_path, filename)
68
70
  try:
69
- f = open(user_config_file, "r+", encoding="UTF-8")
70
- read_cfg = json.load(f)
71
- return read_cfg[key]
71
+ with open(filename_with_path, "r+", encoding="UTF-8") as f:
72
+ read_cfg = json.load(f)
73
+ if key is None:
74
+ return read_cfg
75
+ return read_cfg[key]
72
76
  except FileNotFoundError:
73
- if key == "namespace":
74
- raise NoNamespaceInConfig()
75
- print("No config file found. Please use cgc register first.")
76
- sys.exit()
77
+ raise NoConfigFileFound()
77
78
  except KeyError:
78
79
  if key == "namespace":
79
80
  raise NoNamespaceInConfig()
@@ -36,8 +36,11 @@ else:
36
36
 
37
37
  API_PORT = os.getenv("API_PORT")
38
38
  API_URL = f"{__prefix}://{API_HOST}:{API_PORT}"
39
- CONFIG_FILE_NAME = os.getenv("CONFIG_FILE_NAME")
40
39
  TMP_DIR = os.getenv("TMP_DIR")
41
40
  RELEASE = int(os.getenv("RELEASE"))
42
41
  MAJOR_VERSION = int(os.getenv("MAJOR_VERSION"))
43
42
  MINOR_VERSION = int(os.getenv("MINOR_VERSION"))
43
+
44
+
45
+ def get_config_file_name():
46
+ return os.getenv("CONFIG_FILE_NAME")
@@ -1,3 +1,6 @@
1
+ CONFIG_FILE_NOT_FOUND = (
2
+ "Config file not found. Please register with CGC or verify user context."
3
+ )
1
4
  UNKNOWN_ERROR = "An unknown error occurred. Please try again or contact support at support@comtegra.pl."
2
5
  TIMEOUT_ERROR = (
3
6
  "Connection timed out. Try again or contact support at support@comtegra.pl"
@@ -12,6 +15,6 @@ OUTDATED_MINOR = "There is a new release available, consider updating the applic
12
15
  OUTDATED_MAJOR = (
13
16
  "You are using outdated version of cgcsdk, please update to the latest version."
14
17
  )
15
- UNAUTHORIZED_ERROR = "Unauthorized. Please check your API key and secret key or contact support at support@comtegra.pl"
18
+ UNAUTHORIZED_ERROR = "Unauthorized. Credentials provided are invalid. Contact support at support@comtegra.pl"
16
19
  DISABLED_ERROR = "Account has been disabled. If you require assistance, please contact support at support@comtegra.pl"
17
20
  ENDPOINT_DISABLED = "Endpoint is currently disabled, please try again later or contact support for additional information at support@comtegra.pl"
@@ -1,9 +1,12 @@
1
+ import click
2
+
1
3
  from colorama import Fore, Style
2
4
  from functools import wraps
3
5
 
4
6
  from cgc.commands.exceptions import ResponseException
5
7
  from cgc.telemetry.basic import increment_metric
6
- from cgc.utils.consts.message_consts import UNKNOWN_ERROR
8
+ from cgc.utils.consts.message_consts import UNKNOWN_ERROR, CONFIG_FILE_NOT_FOUND
9
+ from cgc.commands.auth import NoConfigFileFound
7
10
 
8
11
 
9
12
  def prepare_error_message(message: str) -> str:
@@ -46,6 +49,9 @@ def key_error_decorator_for_helpers(func):
46
49
  def wrapper(*args, **kwargs):
47
50
  try:
48
51
  return func(*args, **kwargs)
52
+ except NoConfigFileFound as err:
53
+ click.echo(prepare_warning_message(CONFIG_FILE_NOT_FOUND))
54
+ exit(1)
49
55
  except (TypeError, KeyError, IndexError) as err:
50
56
  print(args, "\n", kwargs)
51
57
  increment_metric("client.parser", True)
@@ -1,6 +1,7 @@
1
1
  from cgc.utils.config_utils import read_from_cfg
2
2
  from cgc.utils.consts.env_consts import API_URL, CGC_SECRET
3
3
  from cgc.commands.auth import auth_utils
4
+ from cgc.utils.message_utils import key_error_decorator_for_helpers
4
5
 
5
6
 
6
7
  def load_user_api_keys():
@@ -12,6 +13,7 @@ def load_user_api_keys():
12
13
  return read_from_cfg("api_key"), read_from_cfg("api_secret")
13
14
 
14
15
 
16
+ @key_error_decorator_for_helpers
15
17
  def get_api_url_and_prepare_headers():
16
18
  """Loads API_URL and user api keys into single function. Ment to be used as single point of truth for all andpoints except register - due to different Content-Type header
17
19
 
@@ -53,6 +55,7 @@ def get_url_and_headers_jwt_token():
53
55
  return url, headers
54
56
 
55
57
 
58
+ @key_error_decorator_for_helpers
56
59
  def prepare_headers_api_key():
57
60
  """Prepares headers for create API key request.
58
61
 
@@ -26,7 +26,7 @@ class EndpointTypes(Enum):
26
26
 
27
27
  def _process_endpoint_kwargs(**kwargs):
28
28
  if not "timeout" in kwargs.keys():
29
- kwargs["timeout"] = 10
29
+ kwargs["timeout"] = 30
30
30
  return kwargs
31
31
 
32
32
 
@@ -4,7 +4,7 @@ import pprint
4
4
  import requests
5
5
  import json
6
6
  from tabulate import tabulate
7
- from cgc.commands.auth import NoNamespaceInConfig
7
+ from cgc.commands.auth import NoNamespaceInConfig, NoConfigFileFound
8
8
  from cgc.utils.message_utils import prepare_error_message
9
9
  from cgc.utils.consts.message_consts import (
10
10
  UNKNOWN_ERROR,
@@ -38,6 +38,10 @@ def retrieve_and_validate_response_send_metric(
38
38
  metric = f"{get_namespace()}.{metric}"
39
39
  except NoNamespaceInConfig:
40
40
  metric = f"unknown-namespace.{metric}"
41
+ except NoConfigFileFound:
42
+ print("No config file found. Please use:")
43
+ print("cgc register")
44
+ metric = f"bad-client.{metric}"
41
45
 
42
46
  if response.status_code == 200:
43
47
  increment_metric(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cgcsdk
3
- Version: 0.8.5
3
+ Version: 0.8.7
4
4
  Summary: Comtegra GPU Cloud REST API client
5
5
  Author: Comtegra AI Team
6
6
  Author-email: ai@comtegra.pl
@@ -12,6 +12,7 @@ cgc/server.crt
12
12
  cgc/commands/__init__.py
13
13
  cgc/commands/cgc_cmd.py
14
14
  cgc/commands/cgc_cmd_responses.py
15
+ cgc/commands/cgc_helpers.py
15
16
  cgc/commands/exceptions.py
16
17
  cgc/commands/auth/__init__.py
17
18
  cgc/commands/auth/auth_cmd.py
@@ -1,27 +0,0 @@
1
- import shutil
2
- import os
3
-
4
- from cgc.commands.auth import auth_utils
5
- from cgc.utils.consts.env_consts import TMP_DIR
6
- from cgc.utils.config_utils import get_config_path, save_to_config, user_config_file
7
- from cgc.utils.message_utils import key_error_decorator_for_helpers
8
-
9
-
10
- @key_error_decorator_for_helpers
11
- def auth_register_response(response, user_id, priv_key_bytes) -> str:
12
- TMP_DIR_PATH = os.path.join(get_config_path(), TMP_DIR)
13
- unzip_dir = auth_utils.save_and_unzip_file(response)
14
- aes_key, password = auth_utils.get_aes_key_and_password(unzip_dir, priv_key_bytes)
15
-
16
- save_to_config(user_id=user_id, password=password, aes_key=aes_key)
17
- auth_utils.auth_create_api_key_with_save()
18
- shutil.rmtree(TMP_DIR_PATH)
19
- return f"Register successful! You can now use the CLI. Saved data to: {user_config_file}\n\
20
- Consider backup this file. It stores data with which you can access CGC platform."
21
-
22
-
23
- @key_error_decorator_for_helpers
24
- def login_successful_response():
25
- return f"Successfully logged in, created new API key pair.\n\
26
- Saved data to: {user_config_file}.\n\
27
- Consider backup this file. It stores data with which you can access CGC platform."
@@ -1,64 +0,0 @@
1
- import click
2
- import json
3
-
4
- from cgc.commands.cgc_cmd_responses import cgc_status_response
5
- from cgc.commands.resource.resource_cmd import resource_delete
6
- from cgc.utils.requests_helper import call_api, EndpointTypes
7
- from cgc.utils.click_group import CustomCommand
8
- from cgc.utils.prepare_headers import get_api_url_and_prepare_headers
9
- from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
10
- from cgc.telemetry.basic import telemetry_permission_set
11
- from cgc.commands.compute.compute_responses import compute_logs_response
12
-
13
-
14
- @click.command("rm", cls=CustomCommand)
15
- @click.argument("name", type=click.STRING)
16
- def cgc_rm(name: str):
17
- """
18
- Delete an app in user namespace
19
- """
20
- resource_delete(name)
21
-
22
-
23
- @click.command("events", cls=CustomCommand)
24
- @click.argument("app_name", type=click.STRING)
25
- def resource_events(app_name: str):
26
- """Get events of given app"""
27
- api_url, headers = get_api_url_and_prepare_headers()
28
- url = f"{api_url}/v1/api/resource/get_pod_events"
29
- metric = "resource.events"
30
- __payload = {"name": app_name}
31
- __res = call_api(
32
- request=EndpointTypes.get,
33
- url=url,
34
- headers=headers,
35
- data=json.dumps(__payload),
36
- )
37
- click.echo(
38
- compute_logs_response(retrieve_and_validate_response_send_metric(__res, metric))
39
- )
40
-
41
-
42
- @click.command("status", cls=CustomCommand)
43
- def cgc_status():
44
- """Lists available and used resources"""
45
- api_url, headers = get_api_url_and_prepare_headers()
46
- url = f"{api_url}/v1/api/resource/status"
47
- metric = "resource.status"
48
- __res = call_api(
49
- request=EndpointTypes.get,
50
- url=url,
51
- headers=headers,
52
- )
53
- click.echo(
54
- cgc_status_response(retrieve_and_validate_response_send_metric(__res, metric))
55
- )
56
-
57
-
58
- @click.command("telemetry", cls=CustomCommand)
59
- def sending_telemetry_permission():
60
- """Changing permission for sending telemetry"""
61
-
62
- click.echo(
63
- f"Sending telemetry is now {'enabled' if telemetry_permission_set() else 'disabled'}"
64
- )
@@ -1,51 +0,0 @@
1
- from redis import Redis, ConnectionError
2
-
3
-
4
- class RedisConnector:
5
- def __init__(
6
- self, host: str, password: str = None, decode_responses: bool = False
7
- ) -> None:
8
- self._host = host
9
- assert type(host) is str
10
- "host must be a str containing redis app name"
11
- self._password = password
12
- self._decode_responses = decode_responses
13
- self.connect()
14
-
15
- def connect(self):
16
- while True:
17
- try:
18
- self._redis_client = Redis(
19
- host=self._host,
20
- port=6379,
21
- password=self._password,
22
- decode_responses=self._decode_responses,
23
- )
24
- print(f"Connected to Redis: {self._host}")
25
- break
26
- except (ConnectionError,) as e:
27
- print(f"Redis connection error: {e}")
28
- print(f"retrying to connect...")
29
-
30
- def get_redis_client(self):
31
- return self._redis_client
32
-
33
-
34
- def get_redis_access(
35
- app_name: str, password: str, decode_responses: bool = False, restart: bool = False
36
- ):
37
- global _redis_access
38
-
39
- def init_access():
40
- global _redis_access
41
- _redis_access = RedisConnector(
42
- host=app_name, password=password, decode_responses=decode_responses
43
- )
44
-
45
- try:
46
- if not isinstance(_redis_access, RedisConnector) or restart:
47
- init_access()
48
- except NameError:
49
- init_access()
50
- pass
51
- return _redis_access
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes