cgcsdk 1.0.7__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.
- cgc/.env +1 -1
- cgc/CHANGELOG.md +19 -0
- cgc/cgc.py +2 -0
- cgc/commands/auth/__init__.py +2 -2
- cgc/commands/auth/auth_cmd.py +14 -2
- cgc/commands/auth/auth_responses.py +9 -2
- cgc/commands/auth/auth_utils.py +6 -5
- cgc/commands/cgc_models.py +9 -0
- cgc/commands/compute/compute_models.py +1 -23
- cgc/commands/db/db_cmd.py +7 -1
- cgc/commands/db/db_models.py +37 -0
- cgc/commands/exceptions.py +8 -0
- cgc/commands/jobs/job_utils.py +36 -31
- cgc/commands/jobs/jobs_responses.py +1 -1
- cgc/commands/keys/__init__.py +5 -0
- cgc/commands/keys/keys_cmd.py +176 -0
- cgc/commands/keys/keys_models.py +16 -0
- cgc/commands/keys/keys_responses.py +47 -0
- cgc/commands/keys/keys_utils.py +79 -0
- cgc/commands/resource/resource_cmd.py +2 -1
- cgc/sdk/__init__.py +0 -2
- cgc/sdk/job.py +1 -1
- cgc/utils/__init__.py +8 -0
- cgc/utils/config_utils.py +5 -1
- cgc/utils/consts/env_consts.py +1 -1
- cgc/utils/custom_exceptions.py +3 -0
- cgc/utils/message_utils.py +1 -1
- cgc/utils/prepare_headers.py +22 -13
- cgc/utils/requests_helper.py +1 -3
- {cgcsdk-1.0.7.dist-info → cgcsdk-1.0.9.dist-info}/METADATA +3 -7
- {cgcsdk-1.0.7.dist-info → cgcsdk-1.0.9.dist-info}/RECORD +35 -31
- cgc/sdk/handlers.py +0 -24
- cgc/sdk/mongodb.py +0 -204
- cgc/sdk/redis.py +0 -91
- {cgcsdk-1.0.7.dist-info → cgcsdk-1.0.9.dist-info}/LICENSE +0 -0
- {cgcsdk-1.0.7.dist-info → cgcsdk-1.0.9.dist-info}/WHEEL +0 -0
- {cgcsdk-1.0.7.dist-info → cgcsdk-1.0.9.dist-info}/entry_points.txt +0 -0
- {cgcsdk-1.0.7.dist-info → cgcsdk-1.0.9.dist-info}/top_level.txt +0 -0
cgc/.env
CHANGED
cgc/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
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
|
+
|
3
22
|
## 1.0.7
|
4
23
|
|
5
24
|
Release on April 04, 2024
|
cgc/cgc.py
CHANGED
@@ -19,6 +19,7 @@ from cgc.commands.cgc_cmd import (
|
|
19
19
|
resource_events,
|
20
20
|
context_group,
|
21
21
|
)
|
22
|
+
from cgc.commands.keys.keys_cmd import keys_group
|
22
23
|
|
23
24
|
from cgc.utils.version_control import check_version, _get_version
|
24
25
|
from cgc.utils.click_group import CustomGroup
|
@@ -46,6 +47,7 @@ cli.add_command(cgc_status)
|
|
46
47
|
cli.add_command(sending_telemetry_permission)
|
47
48
|
cli.add_command(cgc_logs)
|
48
49
|
cli.add_command(job_group)
|
50
|
+
cli.add_command(keys_group)
|
49
51
|
|
50
52
|
if __name__ == "__main__" or __name__ == "cgc.cgc":
|
51
53
|
cli()
|
cgc/commands/auth/__init__.py
CHANGED
@@ -7,9 +7,9 @@ class AuthCommandException(ResponseException):
|
|
7
7
|
|
8
8
|
class NoNamespaceInConfig(AuthCommandException):
|
9
9
|
def __init__(self) -> None:
|
10
|
-
super().__init__(
|
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__(
|
15
|
+
super().__init__("Config does not exists.")
|
cgc/commands/auth/auth_cmd.py
CHANGED
@@ -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
|
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(
|
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(
|
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,
|
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)
|
cgc/commands/auth/auth_utils.py
CHANGED
@@ -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
|
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"{
|
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"{
|
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"{
|
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"{
|
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(
|
@@ -1,12 +1,4 @@
|
|
1
|
-
from
|
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
|
|
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.
|
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
|
+
)
|
cgc/commands/exceptions.py
CHANGED
cgc/commands/jobs/job_utils.py
CHANGED
@@ -106,35 +106,46 @@ def job_create_payload(
|
|
106
106
|
return payload
|
107
107
|
|
108
108
|
|
109
|
-
def get_job_list(
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
+
# ]
|
117
127
|
break
|
118
|
-
|
128
|
+
|
129
|
+
return list_of_json_job_data
|
119
130
|
|
120
131
|
|
121
|
-
def
|
132
|
+
def get_job_json_data(job_list: list):
|
122
133
|
"""Formats and returns list of jobs to print.
|
123
134
|
|
124
|
-
:param
|
125
|
-
:type
|
126
|
-
:return: formatted list of
|
135
|
+
:param job_list: list of jobs
|
136
|
+
:type job_list: list
|
137
|
+
:return: formatted list of jobs
|
127
138
|
:rtype: list
|
128
139
|
"""
|
129
140
|
output_data = []
|
130
141
|
|
131
|
-
for
|
142
|
+
for job in job_list:
|
132
143
|
try:
|
133
144
|
main_container_name = "custom-job"
|
134
145
|
try:
|
135
146
|
main_container = [
|
136
147
|
x
|
137
|
-
for x in
|
148
|
+
for x in job.get("containers", [])
|
138
149
|
if x.get("name") == main_container_name
|
139
150
|
][0]
|
140
151
|
except IndexError:
|
@@ -146,29 +157,23 @@ def get_job_pod_list(job_pod_list: list) -> list:
|
|
146
157
|
cpu = limits.get("cpu") if limits is not None else 0
|
147
158
|
ram = limits.get("memory") if limits is not None else "0Gi"
|
148
159
|
|
149
|
-
|
150
|
-
"name":
|
151
|
-
"status":
|
160
|
+
job_data = {
|
161
|
+
"name": job.get("labels", {}).get("app-name"),
|
162
|
+
"status": job.get("status", {}).get("phase", "Pending"),
|
152
163
|
"volumes_mounted": volumes_mounted,
|
153
164
|
"cpu": cpu,
|
154
165
|
"ram": ram,
|
155
|
-
"gpu-count":
|
156
|
-
"gpu-label":
|
166
|
+
"gpu-count": job.get("labels", {}).get("gpu-count", 0),
|
167
|
+
"gpu-label": job.get("labels", {}).get("gpu-label", "N/A"),
|
157
168
|
}
|
158
169
|
# getting rid of unwanted and used values
|
159
|
-
if "pod-template-hash" in
|
160
|
-
|
161
|
-
|
162
|
-
pod["labels"].pop("entity")
|
163
|
-
pod["labels"].pop("resource-type")
|
164
|
-
pod["labels"].pop("job-name")
|
165
|
-
pod["labels"].pop("controller-uid")
|
166
|
-
pod["labels"].pop("api-key-id", None)
|
167
|
-
pod["labels"].pop("user-id", None)
|
170
|
+
if "pod-template-hash" in job["labels"].keys():
|
171
|
+
job["labels"].pop("pod-template-hash")
|
172
|
+
job["labels"].pop("entity")
|
168
173
|
|
169
174
|
# appending the rest of labels
|
170
|
-
|
171
|
-
output_data.append(
|
175
|
+
job_data.update(job["labels"])
|
176
|
+
output_data.append(job_data)
|
172
177
|
except KeyError:
|
173
178
|
pass
|
174
179
|
|
@@ -32,7 +32,7 @@ def job_list_response(data: dict) -> list:
|
|
32
32
|
if not job_list:
|
33
33
|
raise NoJobsToList()
|
34
34
|
|
35
|
-
list_of_json_data = get_job_list(
|
35
|
+
list_of_json_data = get_job_list(job_list, job_pod_list)
|
36
36
|
table = fill_missing_values_in_a_response(list_of_json_data)
|
37
37
|
|
38
38
|
return tabulate_a_response(table)
|
@@ -0,0 +1,176 @@
|
|
1
|
+
import json
|
2
|
+
import click
|
3
|
+
from typing import Optional
|
4
|
+
from cgc.commands.keys.keys_responses import (
|
5
|
+
create_ssh_key_response,
|
6
|
+
delete_ssh_key_response,
|
7
|
+
list_ssh_keys_response,
|
8
|
+
update_ssh_key_response,
|
9
|
+
)
|
10
|
+
from cgc.commands.keys.keys_utils import create_ssh_key_payload, update_ssh_key_payload
|
11
|
+
from cgc.commands.keys.keys_models import SSHKeyTypes
|
12
|
+
from cgc.utils.prepare_headers import get_api_url_and_prepare_headers
|
13
|
+
from cgc.utils.response_utils import retrieve_and_validate_response_send_metric
|
14
|
+
from cgc.utils.click_group import CustomGroup, CustomCommand
|
15
|
+
from cgc.utils.requests_helper import call_api, EndpointTypes
|
16
|
+
|
17
|
+
|
18
|
+
@click.group(name="keys", cls=CustomGroup)
|
19
|
+
def keys_group():
|
20
|
+
"""
|
21
|
+
Management of keys.
|
22
|
+
"""
|
23
|
+
|
24
|
+
|
25
|
+
@keys_group.group(name="ssh", cls=CustomGroup)
|
26
|
+
def ssh_keys_group():
|
27
|
+
"""
|
28
|
+
Management of ssh keys.
|
29
|
+
"""
|
30
|
+
|
31
|
+
|
32
|
+
@ssh_keys_group.command("create", cls=CustomCommand)
|
33
|
+
@click.option(
|
34
|
+
"-pk",
|
35
|
+
"--pub-key",
|
36
|
+
"public_key",
|
37
|
+
type=click.STRING,
|
38
|
+
required=False,
|
39
|
+
help="Whole Public key string",
|
40
|
+
)
|
41
|
+
@click.option(
|
42
|
+
"-kt",
|
43
|
+
"--key-type",
|
44
|
+
"key_type",
|
45
|
+
type=click.Choice(SSHKeyTypes.get_list(), case_sensitive=False),
|
46
|
+
required=False,
|
47
|
+
# default="ssh-rsa",
|
48
|
+
help="Type of the key",
|
49
|
+
)
|
50
|
+
@click.option(
|
51
|
+
"-k",
|
52
|
+
"--key",
|
53
|
+
"key",
|
54
|
+
type=click.STRING,
|
55
|
+
required=False,
|
56
|
+
help="Public key",
|
57
|
+
)
|
58
|
+
@click.option(
|
59
|
+
"-c",
|
60
|
+
"--comment",
|
61
|
+
"comment",
|
62
|
+
type=click.STRING,
|
63
|
+
required=False,
|
64
|
+
default="",
|
65
|
+
help="Comment for the key",
|
66
|
+
)
|
67
|
+
def create_ssh_key(
|
68
|
+
public_key: Optional[str] = None,
|
69
|
+
key_type: Optional[str] = None,
|
70
|
+
key: Optional[str] = None,
|
71
|
+
comment: str = "",
|
72
|
+
):
|
73
|
+
"""Create a new SSH key"""
|
74
|
+
api_url, headers = get_api_url_and_prepare_headers()
|
75
|
+
url = f"{api_url}/v1/api/keys/ssh/create"
|
76
|
+
metric = "keys.create"
|
77
|
+
__payload = create_ssh_key_payload(public_key, key_type, key, comment)
|
78
|
+
__res = call_api(
|
79
|
+
request=EndpointTypes.post,
|
80
|
+
url=url,
|
81
|
+
headers=headers,
|
82
|
+
data=json.dumps(__payload).encode("utf-8"),
|
83
|
+
)
|
84
|
+
click.echo(
|
85
|
+
create_ssh_key_response(
|
86
|
+
retrieve_and_validate_response_send_metric(__res, metric)
|
87
|
+
)
|
88
|
+
)
|
89
|
+
|
90
|
+
|
91
|
+
@ssh_keys_group.command("update", cls=CustomCommand)
|
92
|
+
@click.argument("key_id", type=click.STRING)
|
93
|
+
@click.option(
|
94
|
+
"-kt",
|
95
|
+
"--key-type",
|
96
|
+
"key_type",
|
97
|
+
type=click.Choice(SSHKeyTypes.get_list(), case_sensitive=False),
|
98
|
+
# default="ssh-rsa",
|
99
|
+
required=False,
|
100
|
+
help="Type of the key",
|
101
|
+
)
|
102
|
+
@click.option(
|
103
|
+
"-k",
|
104
|
+
"--key",
|
105
|
+
"key",
|
106
|
+
type=click.STRING,
|
107
|
+
required=False,
|
108
|
+
help="Public key",
|
109
|
+
)
|
110
|
+
@click.option(
|
111
|
+
"-c",
|
112
|
+
"--comment",
|
113
|
+
"comment",
|
114
|
+
type=click.STRING,
|
115
|
+
required=False,
|
116
|
+
help="Comment for the key",
|
117
|
+
)
|
118
|
+
def update_ssh_key(
|
119
|
+
key_id: str,
|
120
|
+
key_type: Optional[str] = None,
|
121
|
+
key: Optional[str] = None,
|
122
|
+
comment: Optional[str] = None,
|
123
|
+
):
|
124
|
+
"""Update an existing SSH key"""
|
125
|
+
api_url, headers = get_api_url_and_prepare_headers()
|
126
|
+
url = f"{api_url}/v1/api/keys/ssh/update/{key_id}"
|
127
|
+
metric = "keys.update"
|
128
|
+
__payload = update_ssh_key_payload(key_type, key, comment)
|
129
|
+
__res = call_api(
|
130
|
+
request=EndpointTypes.post,
|
131
|
+
url=url,
|
132
|
+
headers=headers,
|
133
|
+
data=json.dumps(__payload).encode("utf-8"),
|
134
|
+
)
|
135
|
+
click.echo(
|
136
|
+
update_ssh_key_response(
|
137
|
+
retrieve_and_validate_response_send_metric(__res, metric)
|
138
|
+
)
|
139
|
+
)
|
140
|
+
|
141
|
+
|
142
|
+
@ssh_keys_group.command("delete", cls=CustomCommand)
|
143
|
+
@click.argument("key_id", type=click.STRING)
|
144
|
+
def delete_ssh_key(key_id: str):
|
145
|
+
"""Delete an SSH key"""
|
146
|
+
api_url, headers = get_api_url_and_prepare_headers()
|
147
|
+
url = f"{api_url}/v1/api/keys/ssh/delete/{key_id}"
|
148
|
+
metric = "keys.delete"
|
149
|
+
__res = call_api(
|
150
|
+
request=EndpointTypes.delete,
|
151
|
+
url=url,
|
152
|
+
headers=headers,
|
153
|
+
)
|
154
|
+
click.echo(
|
155
|
+
delete_ssh_key_response(
|
156
|
+
retrieve_and_validate_response_send_metric(__res, metric)
|
157
|
+
)
|
158
|
+
)
|
159
|
+
|
160
|
+
|
161
|
+
@ssh_keys_group.command("list", cls=CustomCommand)
|
162
|
+
def list_ssh_keys():
|
163
|
+
"""List all SSH keys"""
|
164
|
+
api_url, headers = get_api_url_and_prepare_headers()
|
165
|
+
url = f"{api_url}/v1/api/keys/ssh/list"
|
166
|
+
metric = "keys.list"
|
167
|
+
__res = call_api(
|
168
|
+
request=EndpointTypes.get,
|
169
|
+
url=url,
|
170
|
+
headers=headers,
|
171
|
+
)
|
172
|
+
click.echo(
|
173
|
+
list_ssh_keys_response(
|
174
|
+
retrieve_and_validate_response_send_metric(__res, metric)
|
175
|
+
)
|
176
|
+
)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from cgc.commands.cgc_models import CGCEntityList
|
2
|
+
|
3
|
+
|
4
|
+
class SSHKeyTypes(CGCEntityList):
|
5
|
+
"""List of supported SSH key types
|
6
|
+
|
7
|
+
:param Enum: name of SSH key type
|
8
|
+
:type Enum: str
|
9
|
+
"""
|
10
|
+
|
11
|
+
RSA = "ssh-rsa"
|
12
|
+
DSS = "ssh-dss"
|
13
|
+
ECDSA_P256 = "ecdsa-sha2-nistp256"
|
14
|
+
ECDSA_P384 = "ecdsa-sha2-nistp384"
|
15
|
+
ECDSA_P521 = "ecdsa-sha2-nistp521"
|
16
|
+
ED25519 = "ssh-ed25519"
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import click
|
2
|
+
from cgc.utils.message_utils import key_error_decorator_for_helpers
|
3
|
+
from cgc.commands.keys.keys_utils import get_user_ssh_keys
|
4
|
+
from cgc.utils.response_utils import (
|
5
|
+
fill_missing_values_in_a_response,
|
6
|
+
tabulate_a_response,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
@key_error_decorator_for_helpers
|
11
|
+
def create_ssh_key_response(data: dict) -> dict:
|
12
|
+
"""Create a response for creating a new SSH key"""
|
13
|
+
try:
|
14
|
+
key_id = data["details"]["key_id"]
|
15
|
+
except KeyError:
|
16
|
+
raise click.ClickException("Key creation failed!")
|
17
|
+
return f"Key created with ID: {key_id}"
|
18
|
+
|
19
|
+
|
20
|
+
@key_error_decorator_for_helpers
|
21
|
+
def delete_ssh_key_response(data: dict) -> dict:
|
22
|
+
"""Create a response for deleting an SSH key"""
|
23
|
+
try:
|
24
|
+
key_id = data["details"]["key_id"]
|
25
|
+
except KeyError:
|
26
|
+
raise click.ClickException("Key deletion failed!")
|
27
|
+
return f"Key with ID: {key_id} deleted."
|
28
|
+
|
29
|
+
|
30
|
+
@key_error_decorator_for_helpers
|
31
|
+
def list_ssh_keys_response(data: dict) -> dict:
|
32
|
+
"""Create a response for listing all SSH keys"""
|
33
|
+
list_of_json_data = get_user_ssh_keys(data["details"])
|
34
|
+
table = fill_missing_values_in_a_response(list_of_json_data)
|
35
|
+
|
36
|
+
return tabulate_a_response(table)
|
37
|
+
|
38
|
+
|
39
|
+
@key_error_decorator_for_helpers
|
40
|
+
def update_ssh_key_response(data: dict) -> dict:
|
41
|
+
"""Create a response for updating an SSH key"""
|
42
|
+
try:
|
43
|
+
key_id = data["details"]["key_id"]
|
44
|
+
except KeyError:
|
45
|
+
return "Key updated."
|
46
|
+
else:
|
47
|
+
return f"Key updated. New key ID: {key_id}."
|