cgcsdk 1.0.6__py3-none-any.whl → 1.0.9__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 (45) hide show
  1. cgc/.env +1 -1
  2. cgc/CHANGELOG.md +30 -0
  3. cgc/cgc.py +7 -1
  4. cgc/commands/auth/__init__.py +2 -2
  5. cgc/commands/auth/auth_cmd.py +14 -2
  6. cgc/commands/auth/auth_responses.py +9 -2
  7. cgc/commands/auth/auth_utils.py +6 -5
  8. cgc/commands/cgc_cmd.py +28 -3
  9. cgc/commands/cgc_cmd_responses.py +8 -0
  10. cgc/commands/cgc_models.py +9 -0
  11. cgc/commands/compute/compute_models.py +3 -23
  12. cgc/commands/compute/compute_utills.py +16 -6
  13. cgc/commands/db/db_cmd.py +7 -1
  14. cgc/commands/db/db_models.py +37 -0
  15. cgc/commands/exceptions.py +8 -0
  16. cgc/commands/jobs/__init__.py +10 -0
  17. cgc/commands/jobs/job_utils.py +180 -0
  18. cgc/commands/jobs/jobs_cmd.py +238 -0
  19. cgc/commands/jobs/jobs_responses.py +52 -0
  20. cgc/commands/keys/__init__.py +5 -0
  21. cgc/commands/keys/keys_cmd.py +176 -0
  22. cgc/commands/keys/keys_models.py +16 -0
  23. cgc/commands/keys/keys_responses.py +47 -0
  24. cgc/commands/keys/keys_utils.py +79 -0
  25. cgc/commands/resource/resource_cmd.py +2 -1
  26. cgc/sdk/__init__.py +1 -2
  27. cgc/sdk/job.py +147 -0
  28. cgc/sdk/resource.py +1 -0
  29. cgc/utils/__init__.py +8 -0
  30. cgc/utils/config_utils.py +5 -1
  31. cgc/utils/consts/env_consts.py +1 -1
  32. cgc/utils/custom_exceptions.py +8 -1
  33. cgc/utils/message_utils.py +1 -1
  34. cgc/utils/prepare_headers.py +22 -13
  35. cgc/utils/requests_helper.py +1 -3
  36. cgcsdk-1.0.9.dist-info/METADATA +66 -0
  37. {cgcsdk-1.0.6.dist-info → cgcsdk-1.0.9.dist-info}/RECORD +41 -32
  38. cgc/sdk/handlers.py +0 -24
  39. cgc/sdk/mongodb.py +0 -204
  40. cgc/sdk/redis.py +0 -91
  41. cgcsdk-1.0.6.dist-info/METADATA +0 -39
  42. {cgcsdk-1.0.6.dist-info → cgcsdk-1.0.9.dist-info}/LICENSE +0 -0
  43. {cgcsdk-1.0.6.dist-info → cgcsdk-1.0.9.dist-info}/WHEEL +0 -0
  44. {cgcsdk-1.0.6.dist-info → cgcsdk-1.0.9.dist-info}/entry_points.txt +0 -0
  45. {cgcsdk-1.0.6.dist-info → cgcsdk-1.0.9.dist-info}/top_level.txt +0 -0
cgc/.env CHANGED
@@ -6,4 +6,4 @@ CONFIG_FILE_NAME = cfg.json
6
6
  TMP_DIR = .tmp
7
7
  RELEASE = 1
8
8
  MAJOR_VERSION = 0
9
- MINOR_VERSION = 6
9
+ MINOR_VERSION = 9
cgc/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.0.9
4
+
5
+ Release on April 23, 2024
6
+
7
+ * updated cgc to work with cgc-server 1.1.0
8
+ * updated registration process
9
+ * updated available apps, so it reflects changes in cgc-server
10
+ * hotfix: listening jobs, which are not Running
11
+
12
+ ## 1.0.8
13
+
14
+ Release on April 11, 2024
15
+
16
+ * added new command group: cgc keys
17
+ * ssh create - create new ssh key, for ssh access
18
+ * ssh list - list all ssh keys
19
+ * ssh delete - delete ssh key
20
+ * ssh update - update ssh key
21
+
22
+ ## 1.0.7
23
+
24
+ Release on April 04, 2024
25
+
26
+ * speed of resource creation with volumes / shared memory, has been improved
27
+ * added jobs creation: cgc job create
28
+ * added jobs list: cgc job list
29
+ * added jobs delete: cgc job delete
30
+ * added logs (STDOUT, STDERR) reader for spawned applications: cgc logs
31
+ * added SDK for logs & jobs
32
+
3
33
  ## 1.0.6
4
34
 
5
35
  Release on March 13, 2024
cgc/cgc.py CHANGED
@@ -4,6 +4,7 @@ from cgc.commands.compute.compute_cmd import compute_group
4
4
  from cgc.commands.billing.billing_cmd import billing_group
5
5
  from cgc.commands.db.db_cmd import db_group
6
6
  from cgc.commands.resource.resource_cmd import resource_group
7
+ from cgc.commands.jobs.jobs_cmd import job_group
7
8
  from cgc.commands.auth.auth_cmd import (
8
9
  api_keys_group,
9
10
  auth_register,
@@ -13,10 +14,13 @@ from cgc.commands.debug.debug_cmd import debug_group
13
14
  from cgc.commands.cgc_cmd import (
14
15
  cgc_rm,
15
16
  cgc_status,
17
+ cgc_logs,
16
18
  sending_telemetry_permission,
17
19
  resource_events,
18
20
  context_group,
19
21
  )
22
+ from cgc.commands.keys.keys_cmd import keys_group
23
+
20
24
  from cgc.utils.version_control import check_version, _get_version
21
25
  from cgc.utils.click_group import CustomGroup
22
26
 
@@ -41,7 +45,9 @@ cli.add_command(resource_group)
41
45
  cli.add_command(billing_group)
42
46
  cli.add_command(cgc_status)
43
47
  cli.add_command(sending_telemetry_permission)
44
-
48
+ cli.add_command(cgc_logs)
49
+ cli.add_command(job_group)
50
+ cli.add_command(keys_group)
45
51
 
46
52
  if __name__ == "__main__" or __name__ == "cgc.cgc":
47
53
  cli()
@@ -7,9 +7,9 @@ class AuthCommandException(ResponseException):
7
7
 
8
8
  class NoNamespaceInConfig(AuthCommandException):
9
9
  def __init__(self) -> None:
10
- super().__init__(f"Namespace not readable from config file.")
10
+ super().__init__("Namespace not readable from config file.")
11
11
 
12
12
 
13
13
  class NoConfigFileFound(AuthCommandException):
14
14
  def __init__(self) -> None:
15
- super().__init__(f"Config does not exists.")
15
+ super().__init__("Config does not exists.")
@@ -9,7 +9,11 @@ from cgc.commands.auth.auth_utils import (
9
9
  auth_delete_api_key,
10
10
  auth_list_api_keys,
11
11
  )
12
- from cgc.utils.prepare_headers import get_url_and_prepare_headers_register
12
+ from cgc.utils.prepare_headers import (
13
+ get_url_and_prepare_headers_register,
14
+ load_user_api_url,
15
+ load_user_cgc_secret,
16
+ )
13
17
  from cgc.utils.cryptography import rsa_crypto
14
18
  from cgc.utils.click_group import CustomCommand, CustomGroup
15
19
  from cgc.utils.requests_helper import call_api, EndpointTypes
@@ -20,6 +24,7 @@ from cgc.utils.response_utils import (
20
24
  )
21
25
  from cgc.utils import (
22
26
  check_if_config_exist,
27
+ require_answer_loop,
23
28
  require_confirm_loop,
24
29
  find_first_available_config_name,
25
30
  )
@@ -55,9 +60,14 @@ def auth_register(config_filename: str = "cfg.json"):
55
60
  require_confirm_loop("Do you want to add new context?")
56
61
  config_filename = find_first_available_config_name()
57
62
 
63
+ cgc_api_url = require_answer_loop("Enter CGC server address", load_user_api_url())
64
+ cgc_secret = require_answer_loop("Enter CGC secret", load_user_cgc_secret())
65
+
58
66
  user_id = input("User ID: ")
59
67
  access_key = input("Access key: ")
60
- url, headers = get_url_and_prepare_headers_register(user_id, access_key)
68
+ url, headers = get_url_and_prepare_headers_register(
69
+ user_id, access_key, cgc_api_url, cgc_secret
70
+ )
61
71
  metric = "auth.register"
62
72
  pub_key_bytes, priv_key_bytes = rsa_crypto.key_generate_pair()
63
73
  __payload = pub_key_bytes
@@ -74,6 +84,8 @@ def auth_register(config_filename: str = "cfg.json"):
74
84
  user_id,
75
85
  priv_key_bytes,
76
86
  config_filename,
87
+ cgc_api_url,
88
+ cgc_secret,
77
89
  )
78
90
  )
79
91
 
@@ -8,14 +8,21 @@ from cgc.utils.message_utils import key_error_decorator_for_helpers
8
8
 
9
9
 
10
10
  @key_error_decorator_for_helpers
11
- def auth_register_response(response, user_id, priv_key_bytes, config_filename) -> str:
11
+ def auth_register_response(
12
+ response, user_id, priv_key_bytes, config_filename, cgc_api_url, cgc_secret
13
+ ) -> str:
12
14
  TMP_DIR_PATH = os.path.join(get_config_path(), TMP_DIR)
13
15
  unzip_dir, namespace = auth_utils.save_and_unzip_file(response)
14
16
  aes_key, password = auth_utils.get_aes_key_and_password(unzip_dir, priv_key_bytes)
15
17
 
16
18
  os.environ["CONFIG_FILE_NAME"] = config_filename
17
19
  save_to_config(
18
- user_id=user_id, password=password, aes_key=aes_key, namespace=namespace
20
+ user_id=user_id,
21
+ password=password,
22
+ aes_key=aes_key,
23
+ namespace=namespace,
24
+ cgc_api_url=cgc_api_url,
25
+ cgc_secret=cgc_secret,
19
26
  )
20
27
  auth_utils.auth_create_api_key_with_save()
21
28
  shutil.rmtree(TMP_DIR_PATH)
@@ -10,7 +10,7 @@ from cgc.utils.config_utils import save_to_config
10
10
  from cgc.utils.config_utils import read_from_cfg
11
11
  from cgc.utils.cryptography import rsa_crypto
12
12
  from cgc.utils import prepare_headers
13
- from cgc.utils.consts.env_consts import API_URL, TMP_DIR
13
+ from cgc.utils.consts.env_consts import TMP_DIR
14
14
 
15
15
  from cgc.utils.requests_helper import call_api, EndpointTypes
16
16
  from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
@@ -29,9 +29,10 @@ def _get_jwt_from_server(user_id: str = None, password: str = None) -> str:
29
29
  if user_id is None or password is None:
30
30
  if user_id is not None or password is not None:
31
31
  raise ValueError("Both user_id and password must be provided")
32
+ if user_id is None and password is None:
32
33
  user_id = urllib.parse.quote(read_from_cfg("user_id"))
33
34
  password = urllib.parse.quote(read_from_cfg("password"))
34
- url = f"{API_URL}/v1/api/user/create/token"
35
+ url = f"{prepare_headers.load_user_api_url()}/v1/api/user/create/token"
35
36
  headers = {
36
37
  "accept": "application/json",
37
38
  "Content-Type": "application/x-www-form-urlencoded",
@@ -54,7 +55,7 @@ def get_jwt(user_id: str = None, password: str = None) -> str:
54
55
 
55
56
  def auth_create_api_key_with_save(user_id: str = None, password: str = None):
56
57
  """Function to create API key and API secret for user and save it to config file."""
57
- url = f"{API_URL}/v1/api/user/create/api-key"
58
+ url = f"{prepare_headers.load_user_api_url()}/v1/api/user/create/api-key"
58
59
  headers = prepare_headers.prepare_headers_api_key(user_id, password)
59
60
  metric = "auth.api-key"
60
61
  __res = call_api(
@@ -74,7 +75,7 @@ def auth_create_api_key_with_save(user_id: str = None, password: str = None):
74
75
 
75
76
  def auth_delete_api_key(api_key: str, user_id: str = None, password: str = None):
76
77
  """Function to delete API key."""
77
- url = f"{API_URL}/v1/api/user/delete/api-key?api_key={api_key}"
78
+ url = f"{prepare_headers.load_user_api_url()}/v1/api/user/delete/api-key?api_key={api_key}"
78
79
  headers = prepare_headers.prepare_headers_api_key(user_id, password)
79
80
  metric = "auth.api-key"
80
81
  __res = call_api(
@@ -89,7 +90,7 @@ def auth_delete_api_key(api_key: str, user_id: str = None, password: str = None)
89
90
 
90
91
  def auth_list_api_keys(user_id: str = None, password: str = None):
91
92
  """Function to list API keys."""
92
- url = f"{API_URL}/v1/api/user/list/api-key"
93
+ url = f"{prepare_headers.load_user_api_url()}/v1/api/user/list/api-key"
93
94
  headers = prepare_headers.prepare_headers_api_key(user_id, password)
94
95
  metric = "auth.api-key"
95
96
  __res = call_api(
cgc/commands/cgc_cmd.py CHANGED
@@ -1,7 +1,10 @@
1
1
  import click
2
2
  import json
3
3
 
4
- from cgc.commands.cgc_cmd_responses import cgc_status_response
4
+ from cgc.commands.cgc_cmd_responses import (
5
+ cgc_logs_response,
6
+ cgc_status_response,
7
+ )
5
8
  from cgc.commands.resource.resource_cmd import resource_delete
6
9
  from cgc.utils.requests_helper import call_api, EndpointTypes
7
10
  from cgc.utils.click_group import CustomCommand, CustomGroup
@@ -9,7 +12,7 @@ from cgc.utils.prepare_headers import get_api_url_and_prepare_headers
9
12
  from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
10
13
  from cgc.telemetry.basic import telemetry_permission_set
11
14
  from cgc.commands.compute.compute_responses import compute_logs_response
12
- from cgc.commands.auth.auth_cmd import auth_register
15
+ # from cgc.commands.auth.auth_cmd import auth_register
13
16
  from cgc.utils import set_environment_data, check_if_config_exist, list_all_config_files
14
17
  from cgc.commands.cgc_helpers import table_of_user_context_files
15
18
  from cgc.utils.config_utils import config_path
@@ -37,7 +40,7 @@ def resource_events(app_name: str):
37
40
  request=EndpointTypes.get,
38
41
  url=url,
39
42
  headers=headers,
40
- data=json.dumps(__payload).encode('utf-8'),
43
+ data=json.dumps(__payload).encode("utf-8"),
41
44
  )
42
45
  click.echo(
43
46
  compute_logs_response(retrieve_and_validate_response_send_metric(__res, metric))
@@ -122,3 +125,25 @@ def folder_of_contexts():
122
125
  def get_env_path():
123
126
  """Displays current environment file path"""
124
127
  click.echo(f"Current environment file path: {ENV_FILE_PATH}")
128
+
129
+
130
+ @click.command("logs", cls=CustomCommand)
131
+ @click.argument("app_name", type=click.STRING)
132
+ def cgc_logs(app_name):
133
+ """Displays logs of a given app"""
134
+
135
+ if not app_name:
136
+ raise click.ClickException("Please provide a non-empty name")
137
+
138
+ api_url, headers = get_api_url_and_prepare_headers()
139
+ url = f"{api_url}/v1/api/resource/logs/{app_name}"
140
+ metric = "logs.get"
141
+ __res = call_api(
142
+ request=EndpointTypes.get,
143
+ url=url,
144
+ headers=headers,
145
+ )
146
+
147
+ click.echo(
148
+ cgc_logs_response(retrieve_and_validate_response_send_metric(__res, metric))
149
+ )
@@ -72,3 +72,11 @@ def cgc_status_response(data: dict):
72
72
  ),
73
73
  headers=list_headers,
74
74
  )
75
+
76
+
77
+ def cgc_logs_response(data: dict):
78
+ return "\n".join(
79
+ "==> %s/%s <==\n%s" % (pod, cont, log)
80
+ for pod, containers in data["details"]["logs"].items()
81
+ for cont, log in containers.items()
82
+ )
@@ -0,0 +1,9 @@
1
+ from enum import Enum
2
+
3
+
4
+ class CGCEntityList(Enum):
5
+ """Base class for other lists"""
6
+
7
+ @classmethod
8
+ def get_list(cls) -> list[str]:
9
+ return [el.value for el in cls]
@@ -1,12 +1,4 @@
1
- from enum import Enum
2
-
3
-
4
- class CGCEntityList(Enum):
5
- """Base class for other lists"""
6
-
7
- @classmethod
8
- def get_list(cls) -> list[str]:
9
- return [el.value for el in cls]
1
+ from cgc.commands.cgc_models import CGCEntityList
10
2
 
11
3
 
12
4
  class ComputesList(CGCEntityList):
@@ -29,20 +21,6 @@ class ComputesList(CGCEntityList):
29
21
  CUSTOM = "custom"
30
22
 
31
23
 
32
- class DatabasesList(CGCEntityList):
33
- """List of templates in cgc-server
34
-
35
- :param Enum: name of template
36
- :type Enum: str
37
- """
38
-
39
- MONGODB = "mongodb"
40
- POSTGRESQL = "postgresql"
41
- REDIS = "redis"
42
- WEAVIATE = "weaviate"
43
- # MINIO = "minio"
44
-
45
-
46
24
  class GPUsList(CGCEntityList):
47
25
  """List of templates in cgc-server
48
26
 
@@ -54,3 +32,5 @@ class GPUsList(CGCEntityList):
54
32
  A100 = "A100"
55
33
  A5000 = "A5000"
56
34
  H100 = "H100"
35
+ P40 = "P40"
36
+ P100 = "P100"
@@ -1,6 +1,6 @@
1
- from ast import main
2
1
  import cgc.utils.consts.env_consts as env_consts
3
2
 
3
+
4
4
  def list_get_mounted_volumes_paths(volume_list: list) -> str:
5
5
  """Formats and returns list of PVC volumes mounted to an app.
6
6
 
@@ -20,6 +20,7 @@ def list_get_mounted_volumes_paths(volume_list: list) -> str:
20
20
  )
21
21
  return volumes_mounted
22
22
 
23
+
23
24
  def list_get_mounted_volumes(volume_list: list) -> str:
24
25
  """Formats and returns list of PVC volumes mounted to an app.
25
26
 
@@ -39,16 +40,21 @@ def list_get_mounted_volumes(volume_list: list) -> str:
39
40
  )
40
41
  return volumes_mounted
41
42
 
42
- def get_app_mounts(pod_list:list) -> list:
43
+
44
+ def get_app_mounts(pod_list: list) -> list:
43
45
  output_data = []
44
-
46
+
45
47
  for pod in pod_list:
46
48
  try:
47
- main_container_name = pod["labels"]["entity"]
49
+ main_container_name = pod["labels"]["entity"]
48
50
  try:
49
- main_container = [x for x in pod["containers"] if x["name"] == main_container_name][0]
51
+ main_container = [
52
+ x for x in pod["containers"] if x["name"] == main_container_name
53
+ ][0]
50
54
  except IndexError:
51
- raise Exception("Parser was unable to find main container in server output in container list")
55
+ raise Exception(
56
+ "Parser was unable to find main container in server output in container list"
57
+ )
52
58
  volumes_mounted = list_get_mounted_volumes(main_container["mounts"])
53
59
  volumes_paths = list_get_mounted_volumes_paths(main_container["mounts"])
54
60
  pod_data = {
@@ -63,6 +69,7 @@ def get_app_mounts(pod_list:list) -> list:
63
69
  pass
64
70
  return output_data
65
71
 
72
+
66
73
  def get_app_list(pod_list: list, detailed: bool) -> list:
67
74
  """Formats and returns list of apps to print.
68
75
 
@@ -110,6 +117,9 @@ def get_app_list(pod_list: list, detailed: bool) -> list:
110
117
  pod["labels"]["url"] = pod["labels"]["pod_url"]
111
118
  pod["labels"].pop("app-token")
112
119
  pod["labels"].pop("pod_url")
120
+ pod["labels"].pop("resource-type")
121
+ pod["labels"].pop("api-key-id", None)
122
+ pod["labels"].pop("user-id", None)
113
123
 
114
124
  # appending the rest of labels
115
125
  pod_data.update(pod["labels"])
cgc/commands/db/db_cmd.py CHANGED
@@ -2,9 +2,10 @@ import json
2
2
  import click
3
3
 
4
4
  from cgc.commands.compute.compute_responses import compute_list_response
5
- from cgc.commands.compute.compute_models import DatabasesList
5
+ from cgc.commands.db.db_models import DatabasesList
6
6
  from cgc.commands.compute.compute_responses import compute_create_response
7
7
  from cgc.commands.compute.compute_utills import compute_create_payload
8
+ from cgc.commands.exceptions import DatabaseCreationException
8
9
  from cgc.commands.resource.resource_cmd import resource_delete
9
10
  from cgc.utils.prepare_headers import get_api_url_and_prepare_headers
10
11
  from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
@@ -66,6 +67,11 @@ def db_create(
66
67
  :param name: name of app
67
68
  :type name: str
68
69
  """
70
+ try:
71
+ DatabasesList.verify(entity)
72
+ except DatabaseCreationException as e:
73
+ click.echo(e, err=True, color="yellow")
74
+ return
69
75
  api_url, headers = get_api_url_and_prepare_headers()
70
76
  url = f"{api_url}/v1/api/resource/create"
71
77
  metric = "db.create"
@@ -0,0 +1,37 @@
1
+ from cgc.commands.cgc_models import CGCEntityList
2
+ from cgc.commands.exceptions import DatabaseCreationException
3
+
4
+
5
+ class DatabasesList(CGCEntityList):
6
+ """List of templates in cgc-server
7
+
8
+ :param Enum: name of template
9
+ :type Enum: str
10
+ """
11
+
12
+ POSTGRESQL = "postgresql"
13
+ WEAVIATE = "weaviate"
14
+ # MINIO = "minio"
15
+ MONGODB = "mongodb"
16
+ REDIS = "redis"
17
+
18
+ @staticmethod
19
+ def verify(entity: str) -> str:
20
+ if entity == "mongodb":
21
+ raise DatabaseCreationException(
22
+ """
23
+ Due to license agreement we can not serve MongoDB as a single click app.
24
+ If you like to use it you can spawn one as custom image.
25
+
26
+ cgc compute create custom -n name -c 4 -m 8 --image mongo
27
+ """
28
+ )
29
+ elif entity == "redis":
30
+ raise DatabaseCreationException(
31
+ """
32
+ Due to license agreement we can not serve Redis as a single click app.
33
+ If you like to use it you can spawn one as custom image.
34
+
35
+ cgc compute create custom -n name -c 4 -m 8 --image redis
36
+ """
37
+ )
@@ -1,2 +1,10 @@
1
1
  class ResponseException(Exception):
2
2
  pass
3
+
4
+
5
+ class InternalException(Exception):
6
+ pass
7
+
8
+
9
+ class DatabaseCreationException(InternalException):
10
+ pass
@@ -0,0 +1,10 @@
1
+ from cgc.commands.exceptions import ResponseException
2
+
3
+
4
+ class JobCommandException(ResponseException):
5
+ pass
6
+
7
+
8
+ class NoJobsToList(JobCommandException):
9
+ def __init__(self) -> None:
10
+ super().__init__("No jobs to list.")
@@ -0,0 +1,180 @@
1
+ from typing import Optional
2
+ from cgc.commands.compute.compute_utills import list_get_mounted_volumes
3
+ import cgc.utils.consts.env_consts as env_consts
4
+
5
+
6
+ def job_delete_payload(name):
7
+ """
8
+ Create payload for job delete.
9
+ """
10
+ payload = {
11
+ "name": name,
12
+ }
13
+ return payload
14
+
15
+
16
+ def job_create_payload(
17
+ name,
18
+ cpu,
19
+ memory,
20
+ volumes: list,
21
+ volume_full_path: str,
22
+ resource_data: list = [],
23
+ config_maps_data: list = [],
24
+ gpu: int = 0,
25
+ gpu_type: str = None,
26
+ shm_size: int = 0,
27
+ image_name: str = "",
28
+ startup_command: str = "",
29
+ repository_secret: str = "",
30
+ ttl_seconds_after_finished: Optional[int] = None,
31
+ ):
32
+ """
33
+ Create payload for app creation.
34
+ """
35
+ extra_payload = {}
36
+ if shm_size is not None and shm_size != 0:
37
+ extra_payload["shared_memory"] = shm_size
38
+
39
+ if ttl_seconds_after_finished is not None:
40
+ extra_payload["ttl_seconds_after_finished"] = ttl_seconds_after_finished
41
+
42
+ payload = {
43
+ "resource_data": {
44
+ "name": name,
45
+ "cpu": cpu,
46
+ "gpu": gpu,
47
+ "memory": memory,
48
+ "gpu_type": gpu_type,
49
+ "full_mount_path": volume_full_path,
50
+ **extra_payload,
51
+ }
52
+ }
53
+ try:
54
+ if len(volumes) != 0:
55
+ if not volume_full_path:
56
+ payload["resource_data"]["pv_volume"] = volumes
57
+ elif volume_full_path and len(volumes) != 1:
58
+ raise Exception(
59
+ "Volume full path can only be used with a single volume"
60
+ )
61
+ else:
62
+ payload["resource_data"]["pv_volume"] = volumes
63
+ except TypeError:
64
+ pass
65
+ try:
66
+ resource_data_dict = {"resource_data": {}}
67
+ if len(resource_data) != 0:
68
+ for resource in resource_data:
69
+ try:
70
+ key, value = resource.split("=")
71
+ resource_data_dict["resource_data"][key] = value
72
+ except ValueError:
73
+ raise Exception(
74
+ "Invalid resource data format. Use key=value format"
75
+ )
76
+ if image_name:
77
+ resource_data_dict["resource_data"]["custom_image"] = image_name
78
+ if startup_command:
79
+ resource_data_dict["resource_data"]["custom_command"] = startup_command
80
+ if repository_secret:
81
+ resource_data_dict["resource_data"][
82
+ "image_pull_secret_name"
83
+ ] = repository_secret
84
+ if resource_data_dict["resource_data"] != {}:
85
+ payload["template_specific_data"] = resource_data_dict
86
+ except TypeError:
87
+ pass
88
+ try:
89
+ if len(config_maps_data) != 0:
90
+ config_maps_data_dict = {}
91
+ for config_map in config_maps_data:
92
+ try:
93
+ key, value = config_map.split(
94
+ "="
95
+ ) # where key is name of config map and value is data
96
+ config_maps_data_dict[key] = (
97
+ value # value is dict, ex.: {"key": "value"}
98
+ )
99
+ except ValueError:
100
+ raise Exception(
101
+ "Invalid config map data format. Use key=value format"
102
+ )
103
+ payload["config_maps_data"] = config_maps_data_dict
104
+ except TypeError:
105
+ pass
106
+ return payload
107
+
108
+
109
+ def get_job_list(job_list: list, job_pod_list: list):
110
+ list_of_json_job_data = get_job_json_data(job_list)
111
+
112
+ for i, job_data in enumerate(job_list):
113
+ list_of_json_job_data[i]["name"] = job_data.get("name", "")
114
+ list_of_json_job_data[i]["ttl_seconds_after_finished"] = job_data.get(
115
+ "ttl_seconds_after_finished", "N/A"
116
+ )
117
+ for job in list_of_json_job_data:
118
+ for job_pod in job_pod_list:
119
+ if job_pod.get("labels", {}).get("app-name") == job.get("name"):
120
+ job["status"] = job_pod.get("status", "Pending")
121
+ # job["status_reason"] = [
122
+ # x.get("reason", "N/A") for x in job_pod.get("status_reasons", [])
123
+ # ]
124
+ # job["status_message"] = [
125
+ # x.get("message", "N/A") for x in job_pod.get("status_reasons", [])
126
+ # ]
127
+ break
128
+
129
+ return list_of_json_job_data
130
+
131
+
132
+ def get_job_json_data(job_list: list):
133
+ """Formats and returns list of jobs to print.
134
+
135
+ :param job_list: list of jobs
136
+ :type job_list: list
137
+ :return: formatted list of jobs
138
+ :rtype: list
139
+ """
140
+ output_data = []
141
+
142
+ for job in job_list:
143
+ try:
144
+ main_container_name = "custom-job"
145
+ try:
146
+ main_container = [
147
+ x
148
+ for x in job.get("containers", [])
149
+ if x.get("name") == main_container_name
150
+ ][0]
151
+ except IndexError:
152
+ raise Exception(
153
+ "Parser was unable to find main container in server output in container list"
154
+ )
155
+ volumes_mounted = list_get_mounted_volumes(main_container.get("mounts", []))
156
+ limits = main_container.get("resources", {}).get("limits")
157
+ cpu = limits.get("cpu") if limits is not None else 0
158
+ ram = limits.get("memory") if limits is not None else "0Gi"
159
+
160
+ job_data = {
161
+ "name": job.get("labels", {}).get("app-name"),
162
+ "status": job.get("status", {}).get("phase", "Pending"),
163
+ "volumes_mounted": volumes_mounted,
164
+ "cpu": cpu,
165
+ "ram": ram,
166
+ "gpu-count": job.get("labels", {}).get("gpu-count", 0),
167
+ "gpu-label": job.get("labels", {}).get("gpu-label", "N/A"),
168
+ }
169
+ # getting rid of unwanted and used values
170
+ if "pod-template-hash" in job["labels"].keys():
171
+ job["labels"].pop("pod-template-hash")
172
+ job["labels"].pop("entity")
173
+
174
+ # appending the rest of labels
175
+ job_data.update(job["labels"])
176
+ output_data.append(job_data)
177
+ except KeyError:
178
+ pass
179
+
180
+ return output_data