kubetorch 0.2.5__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.
- kubetorch/__init__.py +59 -0
- kubetorch/cli.py +1939 -0
- kubetorch/cli_utils.py +967 -0
- kubetorch/config.py +453 -0
- kubetorch/constants.py +18 -0
- kubetorch/docs/Makefile +18 -0
- kubetorch/docs/__init__.py +0 -0
- kubetorch/docs/_ext/json_globaltoc.py +42 -0
- kubetorch/docs/api/cli.rst +10 -0
- kubetorch/docs/api/python/app.rst +21 -0
- kubetorch/docs/api/python/cls.rst +19 -0
- kubetorch/docs/api/python/compute.rst +25 -0
- kubetorch/docs/api/python/config.rst +11 -0
- kubetorch/docs/api/python/fn.rst +19 -0
- kubetorch/docs/api/python/image.rst +14 -0
- kubetorch/docs/api/python/secret.rst +18 -0
- kubetorch/docs/api/python/volumes.rst +13 -0
- kubetorch/docs/api/python.rst +101 -0
- kubetorch/docs/conf.py +69 -0
- kubetorch/docs/index.rst +20 -0
- kubetorch/docs/requirements.txt +5 -0
- kubetorch/globals.py +269 -0
- kubetorch/logger.py +59 -0
- kubetorch/resources/__init__.py +0 -0
- kubetorch/resources/callables/__init__.py +0 -0
- kubetorch/resources/callables/cls/__init__.py +0 -0
- kubetorch/resources/callables/cls/cls.py +159 -0
- kubetorch/resources/callables/fn/__init__.py +0 -0
- kubetorch/resources/callables/fn/fn.py +140 -0
- kubetorch/resources/callables/module.py +1315 -0
- kubetorch/resources/callables/utils.py +203 -0
- kubetorch/resources/compute/__init__.py +0 -0
- kubetorch/resources/compute/app.py +253 -0
- kubetorch/resources/compute/compute.py +2414 -0
- kubetorch/resources/compute/decorators.py +137 -0
- kubetorch/resources/compute/utils.py +1026 -0
- kubetorch/resources/compute/websocket.py +135 -0
- kubetorch/resources/images/__init__.py +1 -0
- kubetorch/resources/images/image.py +412 -0
- kubetorch/resources/images/images.py +64 -0
- kubetorch/resources/secrets/__init__.py +2 -0
- kubetorch/resources/secrets/kubernetes_secrets_client.py +377 -0
- kubetorch/resources/secrets/provider_secrets/__init__.py +0 -0
- kubetorch/resources/secrets/provider_secrets/anthropic_secret.py +12 -0
- kubetorch/resources/secrets/provider_secrets/aws_secret.py +16 -0
- kubetorch/resources/secrets/provider_secrets/azure_secret.py +14 -0
- kubetorch/resources/secrets/provider_secrets/cohere_secret.py +12 -0
- kubetorch/resources/secrets/provider_secrets/gcp_secret.py +16 -0
- kubetorch/resources/secrets/provider_secrets/github_secret.py +13 -0
- kubetorch/resources/secrets/provider_secrets/huggingface_secret.py +20 -0
- kubetorch/resources/secrets/provider_secrets/kubeconfig_secret.py +12 -0
- kubetorch/resources/secrets/provider_secrets/lambda_secret.py +13 -0
- kubetorch/resources/secrets/provider_secrets/langchain_secret.py +12 -0
- kubetorch/resources/secrets/provider_secrets/openai_secret.py +11 -0
- kubetorch/resources/secrets/provider_secrets/pinecone_secret.py +12 -0
- kubetorch/resources/secrets/provider_secrets/providers.py +92 -0
- kubetorch/resources/secrets/provider_secrets/ssh_secret.py +12 -0
- kubetorch/resources/secrets/provider_secrets/wandb_secret.py +11 -0
- kubetorch/resources/secrets/secret.py +224 -0
- kubetorch/resources/secrets/secret_factory.py +64 -0
- kubetorch/resources/secrets/utils.py +222 -0
- kubetorch/resources/volumes/__init__.py +0 -0
- kubetorch/resources/volumes/volume.py +340 -0
- kubetorch/servers/__init__.py +0 -0
- kubetorch/servers/http/__init__.py +0 -0
- kubetorch/servers/http/distributed_utils.py +2968 -0
- kubetorch/servers/http/http_client.py +802 -0
- kubetorch/servers/http/http_server.py +1622 -0
- kubetorch/servers/http/server_metrics.py +255 -0
- kubetorch/servers/http/utils.py +722 -0
- kubetorch/serving/__init__.py +0 -0
- kubetorch/serving/autoscaling.py +153 -0
- kubetorch/serving/base_service_manager.py +344 -0
- kubetorch/serving/constants.py +77 -0
- kubetorch/serving/deployment_service_manager.py +431 -0
- kubetorch/serving/knative_service_manager.py +487 -0
- kubetorch/serving/raycluster_service_manager.py +526 -0
- kubetorch/serving/service_manager.py +18 -0
- kubetorch/serving/templates/deployment_template.yaml +17 -0
- kubetorch/serving/templates/knative_service_template.yaml +19 -0
- kubetorch/serving/templates/kt_setup_template.sh.j2 +91 -0
- kubetorch/serving/templates/pod_template.yaml +198 -0
- kubetorch/serving/templates/raycluster_service_template.yaml +42 -0
- kubetorch/serving/templates/raycluster_template.yaml +35 -0
- kubetorch/serving/templates/service_template.yaml +21 -0
- kubetorch/serving/templates/workerset_template.yaml +36 -0
- kubetorch/serving/utils.py +344 -0
- kubetorch/utils.py +263 -0
- kubetorch-0.2.5.dist-info/METADATA +75 -0
- kubetorch-0.2.5.dist-info/RECORD +92 -0
- kubetorch-0.2.5.dist-info/WHEEL +4 -0
- kubetorch-0.2.5.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
from kubernetes import client, config
|
|
8
|
+
from kubernetes.client import V1SecretList
|
|
9
|
+
from kubernetes.client.rest import ApiException
|
|
10
|
+
|
|
11
|
+
import kubetorch
|
|
12
|
+
from kubetorch import globals
|
|
13
|
+
from kubetorch.constants import DEFAULT_KUBECONFIG_PATH
|
|
14
|
+
from kubetorch.logger import get_logger
|
|
15
|
+
from kubetorch.resources.secrets import Secret
|
|
16
|
+
from kubetorch.resources.secrets.utils import get_k8s_identity_name
|
|
17
|
+
from kubetorch.servers.http.utils import is_running_in_kubernetes
|
|
18
|
+
from kubetorch.serving.constants import KT_USER_IDENTIFIER_LABEL, KT_USERNAME_LABEL
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class KubernetesSecretsClient:
|
|
24
|
+
def __init__(self, namespace: str = None, kubeconfig_path: str = None):
|
|
25
|
+
self._kubeconfig_path = kubeconfig_path
|
|
26
|
+
self.namespace = namespace or globals.config.namespace
|
|
27
|
+
|
|
28
|
+
# Load config
|
|
29
|
+
self.kt_config = globals.config
|
|
30
|
+
|
|
31
|
+
# Derive User ID from config context
|
|
32
|
+
self.user_id = get_k8s_identity_name()
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
config.load_incluster_config()
|
|
36
|
+
except config.ConfigException:
|
|
37
|
+
config.load_kube_config(config_file=self.kubeconfig_path)
|
|
38
|
+
self.api_client = client.CoreV1Api()
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def kubeconfig_path(self):
|
|
42
|
+
if not self._kubeconfig_path:
|
|
43
|
+
self._kubeconfig_path = os.getenv("KUBECONFIG") or DEFAULT_KUBECONFIG_PATH
|
|
44
|
+
return str(Path(self._kubeconfig_path).expanduser())
|
|
45
|
+
|
|
46
|
+
# -------------------------------------
|
|
47
|
+
# SECRETS APIS
|
|
48
|
+
# -------------------------------------
|
|
49
|
+
def load_secret(self, name: str) -> Optional[Secret]:
|
|
50
|
+
secret_dict = self._read_secret(name)
|
|
51
|
+
if not secret_dict:
|
|
52
|
+
return None
|
|
53
|
+
secret = Secret.from_config(secret_dict)
|
|
54
|
+
return secret
|
|
55
|
+
|
|
56
|
+
def delete_secret(self, name: str, console: "Console" = None) -> bool:
|
|
57
|
+
"""Delete secret with provided name for current user."""
|
|
58
|
+
name = name if self._read_secret(name=name) else self._format_secret_name(name)
|
|
59
|
+
return self._delete_secret(name=name, console=console)
|
|
60
|
+
|
|
61
|
+
def delete_all_secrets(self, username: Optional[str] = None) -> bool:
|
|
62
|
+
"""Delete all secrets for current user."""
|
|
63
|
+
return self._delete_all_secrets_for_user(username=username)
|
|
64
|
+
|
|
65
|
+
def convert_to_secret_objects(self, secrets: List[Union[str, Secret]]) -> List[Secret]:
|
|
66
|
+
"""
|
|
67
|
+
Converts a list of strings and Secrets into Secret objects without uploading.
|
|
68
|
+
"""
|
|
69
|
+
from kubetorch.resources.secrets.secret_factory import secret as secret_factory
|
|
70
|
+
|
|
71
|
+
secret_objects = []
|
|
72
|
+
for secret_or_string in secrets:
|
|
73
|
+
# Create a provider secret if only the name is provided
|
|
74
|
+
secret = (
|
|
75
|
+
secret_factory(provider=secret_or_string) if isinstance(secret_or_string, str) else secret_or_string
|
|
76
|
+
)
|
|
77
|
+
secret_objects.append(secret)
|
|
78
|
+
|
|
79
|
+
return secret_objects
|
|
80
|
+
|
|
81
|
+
def upload_secrets_list(self, secrets: List[Union[str, Secret]]) -> List[Secret]:
|
|
82
|
+
"""Uploads secrets to Kubernetes Secrets to be used in knative yaml."""
|
|
83
|
+
if is_running_in_kubernetes():
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
# Convert to Secret objects first
|
|
87
|
+
secret_objects = self.convert_to_secret_objects(secrets)
|
|
88
|
+
|
|
89
|
+
synced_secrets = []
|
|
90
|
+
for secret in secret_objects:
|
|
91
|
+
success = self.create_or_update_secret(secret=secret)
|
|
92
|
+
if success:
|
|
93
|
+
synced_secrets.append(secret)
|
|
94
|
+
|
|
95
|
+
return synced_secrets
|
|
96
|
+
|
|
97
|
+
def extract_envs_and_volumes_from_secrets(
|
|
98
|
+
self,
|
|
99
|
+
secrets: List[Secret] = None,
|
|
100
|
+
) -> Tuple[list, list]:
|
|
101
|
+
if not secrets:
|
|
102
|
+
return [], []
|
|
103
|
+
|
|
104
|
+
env_vars = []
|
|
105
|
+
volumes = []
|
|
106
|
+
for secret in secrets:
|
|
107
|
+
secret_name = self._format_secret_name(secret.name)
|
|
108
|
+
if secret.env_vars:
|
|
109
|
+
env_vars.append(
|
|
110
|
+
{
|
|
111
|
+
"env_vars": secret.env_vars,
|
|
112
|
+
"secret_name": secret_name,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
if secret.path:
|
|
116
|
+
path = secret.path.replace("~", "/root")
|
|
117
|
+
if not secret.filenames:
|
|
118
|
+
# Reformat path to only include directory
|
|
119
|
+
path = os.path.dirname(path)
|
|
120
|
+
volumes.append(
|
|
121
|
+
{
|
|
122
|
+
"name": f"secrets-{secret.name}",
|
|
123
|
+
"secret_name": secret_name,
|
|
124
|
+
"path": path,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
return env_vars, volumes
|
|
128
|
+
|
|
129
|
+
def _format_secret_name(self, name: str) -> str:
|
|
130
|
+
"""Appends user ID to name to ensure uniqueness."""
|
|
131
|
+
user = self.kt_config.username or "global"
|
|
132
|
+
if user in name:
|
|
133
|
+
return name
|
|
134
|
+
return f"kt.secret.{user}.{name}"
|
|
135
|
+
|
|
136
|
+
def _read_secret(self, name: str) -> Optional[dict]:
|
|
137
|
+
secret_name = name
|
|
138
|
+
try:
|
|
139
|
+
secret = self.api_client.read_namespaced_secret(name, self.namespace)
|
|
140
|
+
except ApiException as e:
|
|
141
|
+
if e.status == 404: # secret does not exist, try to load with formatted k8 name
|
|
142
|
+
secret_name = self._format_secret_name(name)
|
|
143
|
+
try:
|
|
144
|
+
secret = self.api_client.read_namespaced_secret(secret_name, self.namespace)
|
|
145
|
+
except ApiException as e:
|
|
146
|
+
if e.status == 404:
|
|
147
|
+
logger.info(
|
|
148
|
+
f"Secret {secret_name} not found in namespace {self.namespace}",
|
|
149
|
+
)
|
|
150
|
+
return None
|
|
151
|
+
else:
|
|
152
|
+
logger.error(
|
|
153
|
+
f"Failed to read secret {name} from Kubernetes: {str(e)}",
|
|
154
|
+
)
|
|
155
|
+
return None
|
|
156
|
+
else:
|
|
157
|
+
logger.error(
|
|
158
|
+
f"Failed to read secret {name} from Kubernetes: {str(e)}",
|
|
159
|
+
)
|
|
160
|
+
return None
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.error(
|
|
163
|
+
f"Unexpected error occurred while reading secret {secret_name} from Kubernetes: {str(e)}",
|
|
164
|
+
)
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
override = (
|
|
168
|
+
secret.metadata.annotations.get("kubetorch.com/override", "False") if secret.metadata.annotations else None
|
|
169
|
+
)
|
|
170
|
+
path = (
|
|
171
|
+
secret.metadata.annotations.get("kubetorch.com/secret-path", None) if secret.metadata.annotations else None
|
|
172
|
+
)
|
|
173
|
+
filenames = (
|
|
174
|
+
secret.metadata.annotations.get("kubetorch.com/secret-filenames", None)
|
|
175
|
+
if secret.metadata.annotations
|
|
176
|
+
else None
|
|
177
|
+
)
|
|
178
|
+
filenames = json.loads(filenames) if filenames else filenames
|
|
179
|
+
|
|
180
|
+
secret_config = {
|
|
181
|
+
"name": secret_name,
|
|
182
|
+
"namespace": self.namespace,
|
|
183
|
+
"override": override,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if path:
|
|
187
|
+
secret_config["path"] = path
|
|
188
|
+
secret_config["filenames"] = filenames
|
|
189
|
+
return secret_config
|
|
190
|
+
|
|
191
|
+
decoded_values = {k: base64.b64decode(v).decode("utf-8") for k, v in secret.data.items()}
|
|
192
|
+
secret_config["values"] = decoded_values
|
|
193
|
+
|
|
194
|
+
mount_type = secret.metadata.labels.get("kubetorch.com/mount-type", None)
|
|
195
|
+
if mount_type == "env":
|
|
196
|
+
secret_config["env_vars"] = list(decoded_values.keys())
|
|
197
|
+
|
|
198
|
+
return secret_config
|
|
199
|
+
|
|
200
|
+
def _build_secret_body(self, secret: Secret):
|
|
201
|
+
secret_name = self._format_secret_name(secret.name)
|
|
202
|
+
provider = secret.provider
|
|
203
|
+
mount_type = "volume" if secret.path else "env"
|
|
204
|
+
encoded_data = {k: base64.b64encode(v.encode()).decode() for k, v in secret.values.items()}
|
|
205
|
+
labels = {
|
|
206
|
+
"kubetorch.com/mount-type": mount_type,
|
|
207
|
+
"kubetorch.com/secret-name": secret.name,
|
|
208
|
+
}
|
|
209
|
+
if self.user_id:
|
|
210
|
+
labels[KT_USER_IDENTIFIER_LABEL] = self.user_id
|
|
211
|
+
if self.kt_config.username:
|
|
212
|
+
labels[KT_USERNAME_LABEL] = self.kt_config.username
|
|
213
|
+
if provider:
|
|
214
|
+
labels["kubetorch.com/provider"] = provider
|
|
215
|
+
annotations = {"kubetorch.com/override": str(secret.override)}
|
|
216
|
+
if secret.path:
|
|
217
|
+
annotations["kubetorch.com/secret-path"] = secret.path
|
|
218
|
+
annotations["kubetorch.com/secret-filenames"] = json.dumps(secret.filenames)
|
|
219
|
+
|
|
220
|
+
metadata = client.V1ObjectMeta(
|
|
221
|
+
name=secret_name,
|
|
222
|
+
namespace=self.namespace,
|
|
223
|
+
labels=labels,
|
|
224
|
+
annotations=annotations,
|
|
225
|
+
)
|
|
226
|
+
secret_body = client.V1Secret(metadata=metadata, data=encoded_data, type="Opaque") # default secret type
|
|
227
|
+
|
|
228
|
+
return secret_body
|
|
229
|
+
|
|
230
|
+
def create_secret(self, secret: Secret, console: "Console" = None):
|
|
231
|
+
secret_name = self._format_secret_name(secret.name)
|
|
232
|
+
secret_body = self._build_secret_body(secret=secret)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
self.api_client.create_namespaced_secret(self.namespace, secret_body)
|
|
236
|
+
if console:
|
|
237
|
+
console.print("[bold green]✔ Secret created successfully[/bold green]")
|
|
238
|
+
console.print(f" Name: [cyan]{secret.name}[/cyan]")
|
|
239
|
+
console.print(f" Namespace: [cyan]{self.namespace}[/cyan]")
|
|
240
|
+
else:
|
|
241
|
+
logger.info(f"Created new Kubernetes secret {secret.name}")
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
except ApiException as e:
|
|
245
|
+
if console:
|
|
246
|
+
if e.status == 409:
|
|
247
|
+
msg = f"[yellow]Secret '{secret.name}' already exists in namespace {self.namespace}, skipping creation[/yellow]"
|
|
248
|
+
else:
|
|
249
|
+
msg = f"[red]Failed to create Kubernetes secret {secret_name}: {str(e)}[/red]"
|
|
250
|
+
console.print(msg)
|
|
251
|
+
return False
|
|
252
|
+
else:
|
|
253
|
+
raise e
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
if console:
|
|
257
|
+
msg = f"Unexpected error occurred while creating new Kubernetes secret {secret.name}: {str(e)}"
|
|
258
|
+
console.print(f"[red]{msg}[/red]")
|
|
259
|
+
return False
|
|
260
|
+
else:
|
|
261
|
+
raise e
|
|
262
|
+
|
|
263
|
+
def _get_existing_secret(self, secret: Secret):
|
|
264
|
+
try:
|
|
265
|
+
return self.api_client.read_namespaced_secret(secret.name, self.namespace)
|
|
266
|
+
except ApiException as e:
|
|
267
|
+
if e.status == 404: # try to load secret with kubernetes formatted name
|
|
268
|
+
formatted_name = self._format_secret_name(secret.name)
|
|
269
|
+
try:
|
|
270
|
+
return self.api_client.read_namespaced_secret(formatted_name, self.namespace)
|
|
271
|
+
except ApiException:
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
def update_secret(self, secret: Secret, console: "Console" = None):
|
|
275
|
+
existing_secret = self._get_existing_secret(secret)
|
|
276
|
+
if existing_secret is None:
|
|
277
|
+
if console:
|
|
278
|
+
console.print(f"[red]Failed to update secret {secret.name}: secret does not exist[/red]")
|
|
279
|
+
return False
|
|
280
|
+
else:
|
|
281
|
+
raise kubetorch.SecretNotFound(secret_name=secret.name, namespace=self.namespace)
|
|
282
|
+
|
|
283
|
+
if not secret.override:
|
|
284
|
+
decoded_values = {k: base64.b64decode(v).decode("utf-8") for k, v in existing_secret.data.items()}
|
|
285
|
+
if not decoded_values == secret.values:
|
|
286
|
+
msg = f"Secret {secret.name} exists with different values and `secret.override` not set to True."
|
|
287
|
+
if console:
|
|
288
|
+
console.print(f"[yellow]{msg}[/yellow]")
|
|
289
|
+
return False
|
|
290
|
+
else:
|
|
291
|
+
raise ValueError(msg)
|
|
292
|
+
else:
|
|
293
|
+
msg = f"Secret {secret.name} already exists with the same values."
|
|
294
|
+
console.print(f"[bold green]{msg}[/bold green]") if console else logger.info(msg)
|
|
295
|
+
return True
|
|
296
|
+
|
|
297
|
+
secret_name = self._format_secret_name(secret.name)
|
|
298
|
+
secret_body = self._build_secret_body(secret=secret)
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
self.api_client.replace_namespaced_secret(secret_name, self.namespace, secret_body)
|
|
302
|
+
if console:
|
|
303
|
+
console.print("[bold green]✔ Secret updated successfully[/bold green]")
|
|
304
|
+
console.print(f" Name: [cyan]{secret.name}[/cyan]")
|
|
305
|
+
console.print(f" Namespace: [cyan]{self.namespace}[/cyan]")
|
|
306
|
+
else:
|
|
307
|
+
logger.info(f"Updated existing Kubernetes secret {secret_name}")
|
|
308
|
+
return True
|
|
309
|
+
|
|
310
|
+
except Exception as e:
|
|
311
|
+
if console:
|
|
312
|
+
console.print(f"[red]Failed to update secret {secret.name}: {str(e)}[/red]")
|
|
313
|
+
return False
|
|
314
|
+
raise e
|
|
315
|
+
|
|
316
|
+
def create_or_update_secret(self, secret: Secret, console: "Console" = None):
|
|
317
|
+
try:
|
|
318
|
+
return self.update_secret(secret, console)
|
|
319
|
+
except kubetorch.SecretNotFound:
|
|
320
|
+
# if secret not found, try to create the secret.
|
|
321
|
+
return self.create_secret(secret, console)
|
|
322
|
+
|
|
323
|
+
def _delete_secret(self, name: str, console: "Console" = None):
|
|
324
|
+
name = self._format_secret_name(name)
|
|
325
|
+
try:
|
|
326
|
+
self.api_client.delete_namespaced_secret(name, self.namespace)
|
|
327
|
+
if console:
|
|
328
|
+
console.print(f"✓ Deleted secret [blue]{name}[/blue]")
|
|
329
|
+
else:
|
|
330
|
+
logger.info(f"Deleted Kubernetes secret: {name}")
|
|
331
|
+
return True
|
|
332
|
+
|
|
333
|
+
except ApiException as e:
|
|
334
|
+
msg = f"Failed to delete Kubernetes secret: {name}: {str(e)}"
|
|
335
|
+
console.print(msg) if console else logger.error(msg)
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
except Exception as e:
|
|
339
|
+
msg = f"Unexpected error occurred while deleting Kubernetes secret {name}: {str(e)}"
|
|
340
|
+
console.print(msg) if console else logger.error(msg)
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
def _delete_all_secrets_for_user(self, username: Optional[str] = None):
|
|
344
|
+
username = username or self.kt_config.username
|
|
345
|
+
label_selector = f"{KT_USERNAME_LABEL}={username}"
|
|
346
|
+
try:
|
|
347
|
+
secrets: V1SecretList = self.api_client.list_namespaced_secret(
|
|
348
|
+
namespace=self.namespace, label_selector=label_selector
|
|
349
|
+
)
|
|
350
|
+
deleted_all = True
|
|
351
|
+
|
|
352
|
+
for secret in secrets.items:
|
|
353
|
+
secret_name = secret.metadata.name
|
|
354
|
+
try:
|
|
355
|
+
self.api_client.delete_namespaced_secret(secret_name, self.namespace)
|
|
356
|
+
logger.info(
|
|
357
|
+
f"Deleted Kubernetes secret {secret_name} for user {username}",
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
except ApiException as e:
|
|
361
|
+
logger.warning(
|
|
362
|
+
f"Failed to delete specific Kubernetes secret {secret_name} for user {username}: {str(e)}",
|
|
363
|
+
)
|
|
364
|
+
deleted_all = False
|
|
365
|
+
|
|
366
|
+
return deleted_all
|
|
367
|
+
|
|
368
|
+
except ApiException as e:
|
|
369
|
+
logger.error(
|
|
370
|
+
f"Failed to list Kubernetes secrets: {str(e)}",
|
|
371
|
+
)
|
|
372
|
+
return False
|
|
373
|
+
|
|
374
|
+
except Exception as e:
|
|
375
|
+
logger.error(
|
|
376
|
+
f"Unexpected error occurred while listing Kubernetes secrets: {str(e)}",
|
|
377
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AnthropicSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create an AnthropicSecret, please use the factory method :func:`secret`
|
|
8
|
+
with ``provider="anthropic"``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
_PROVIDER = "anthropic"
|
|
12
|
+
_DEFAULT_ENV_VARS = {"api_key": "ANTHROPIC_API_KEY"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AWSSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create an AWSSecret, please use the factory method :func:`secret` with ``provider="aws"``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
_PROVIDER = "aws"
|
|
11
|
+
_DEFAULT_PATH = "~/.aws"
|
|
12
|
+
_DEFAULT_FILENAMES = ["config", "credentials"]
|
|
13
|
+
_DEFAULT_ENV_VARS = {
|
|
14
|
+
"access_key": "AWS_ACCESS_KEY_ID",
|
|
15
|
+
"secret_key": "AWS_SECRET_ACCESS_KEY",
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AzureSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create an AzureSecret, please use the factory method :func:`secret` with ``provider="azure"``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# values format: {"subscription_id": subscription_id}
|
|
11
|
+
_PROVIDER = "azure"
|
|
12
|
+
_DEFAULT_PATH = "~/.azure"
|
|
13
|
+
_DEFAULT_FILENAMES = ["clouds.config"]
|
|
14
|
+
_DEFAULT_ENV_VARS = {"subscription_id": "AZURE_SUBSCRIPTION_ID"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CohereSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create an CohereSecret, please use the factory method :func:`secret`
|
|
8
|
+
with ``provider="cohere"``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
_PROVIDER = "cohere"
|
|
12
|
+
_DEFAULT_ENV_VARS = {"api_key": "COHERE_API_KEY"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GCPSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create a GCPSecret, please use the factory method :func:`secret` with ``provider="gcp"``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
_PROVIDER = "gcp"
|
|
11
|
+
_DEFAULT_PATH = "~/.config/gcloud"
|
|
12
|
+
_DEFAULT_FILENAMES = ["application_default_credentials.json"]
|
|
13
|
+
_DEFAULT_ENV_VARS = {
|
|
14
|
+
"client_id": "CLIENT_ID",
|
|
15
|
+
"client_secret": "CLIENT_SECRET",
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GitHubSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create a GitHubSecret, please use the factory method :func:`secret` with ``provider="github"``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# values format: {"oauth_token": oath_token}
|
|
11
|
+
_PROVIDER = "github"
|
|
12
|
+
_DEFAULT_PATH = "~/.config/gh"
|
|
13
|
+
_DEFAULT_FILENAMES = ["hosts.yml"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HuggingFaceSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create a HuggingFaceSecret, please use the factory method :func:`secret` with
|
|
8
|
+
``provider="huggingface"``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# values format: {"token": hf_token}
|
|
12
|
+
_PROVIDER = "huggingface"
|
|
13
|
+
_DEFAULT_PATH = "~/.cache/huggingface"
|
|
14
|
+
_DEFAULT_FILENAMES = ["token"]
|
|
15
|
+
_DEFAULT_ENV_VARS = ["HF_TOKEN"]
|
|
16
|
+
|
|
17
|
+
# Ensure secrets can be used as environment variables
|
|
18
|
+
_MAP_FILENAMES_TO_ENV_VARS = {
|
|
19
|
+
"token": "HF_TOKEN",
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class KubeConfigSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create a KubeConfigSecret, please use the factory method :func:`secret` with ``provider=="kubernetes"``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
_PROVIDER = "kubernetes"
|
|
11
|
+
_DEFAULT_PATH = "~/.kube"
|
|
12
|
+
_DEFAULT_FILENAMES = ["config"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LambdaSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create a LambdaSecret, please use the factory method :func:`secret` with ``provider="lambda"``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# values format: {"api_key": api_key}
|
|
11
|
+
_DEFAULT_PATH = "~/.lambda_cloud"
|
|
12
|
+
_DEFAULT_FILENAMES = ["lambda_keys"]
|
|
13
|
+
_PROVIDER = "lambda"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LangChainSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create an LangChainSecret, please use the factory method :func:`secret`
|
|
8
|
+
with ``provider="langchain"``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
_PROVIDER = "langchain"
|
|
12
|
+
_DEFAULT_ENV_VARS = {"api_key": "LANGCHAIN_API_KEY"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class OpenAISecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create an OpenAISecret, please use the factory method :func:`secret` with ``provider="openai"``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
_PROVIDER = "openai"
|
|
11
|
+
_DEFAULT_ENV_VARS = {"api_key": "OPENAI_API_KEY"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PineconeSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create an PineconeSecret, please use the factory method :func:`secret`
|
|
8
|
+
with ``provider="pinecone"``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
_PROVIDER = "pinecone"
|
|
12
|
+
_DEFAULT_ENV_VARS = {"api_key": "PINECONE_API_KEY"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from .anthropic_secret import AnthropicSecret
|
|
4
|
+
from .aws_secret import AWSSecret
|
|
5
|
+
from .azure_secret import AzureSecret
|
|
6
|
+
from .cohere_secret import CohereSecret
|
|
7
|
+
from .gcp_secret import GCPSecret
|
|
8
|
+
from .github_secret import GitHubSecret
|
|
9
|
+
from .huggingface_secret import HuggingFaceSecret
|
|
10
|
+
from .kubeconfig_secret import KubeConfigSecret
|
|
11
|
+
from .lambda_secret import LambdaSecret
|
|
12
|
+
from .langchain_secret import LangChainSecret
|
|
13
|
+
from .openai_secret import OpenAISecret
|
|
14
|
+
from .pinecone_secret import PineconeSecret
|
|
15
|
+
from .ssh_secret import SSHSecret
|
|
16
|
+
from .wandb_secret import WandBSecret
|
|
17
|
+
|
|
18
|
+
_str_to_provider_class = {
|
|
19
|
+
# File and/or Env secrets
|
|
20
|
+
"aws": AWSSecret,
|
|
21
|
+
"azure": AzureSecret,
|
|
22
|
+
"gcp": GCPSecret,
|
|
23
|
+
"github": GitHubSecret,
|
|
24
|
+
"huggingface": HuggingFaceSecret,
|
|
25
|
+
"kubernetes": KubeConfigSecret,
|
|
26
|
+
"lambda": LambdaSecret,
|
|
27
|
+
# SSH secrets
|
|
28
|
+
"ssh": SSHSecret,
|
|
29
|
+
# API key secrets
|
|
30
|
+
"anthropic": AnthropicSecret,
|
|
31
|
+
"cohere": CohereSecret,
|
|
32
|
+
"langchain": LangChainSecret,
|
|
33
|
+
"openai": OpenAISecret,
|
|
34
|
+
"pinecone": PineconeSecret,
|
|
35
|
+
"wandb": WandBSecret,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_path_to_provider_class = {
|
|
39
|
+
"~/.aws": AWSSecret,
|
|
40
|
+
"~/.azure": AzureSecret,
|
|
41
|
+
"~/.config/gcloud": GCPSecret,
|
|
42
|
+
"~/.config/gh": GitHubSecret,
|
|
43
|
+
"~/.cache/huggingface": HuggingFaceSecret,
|
|
44
|
+
"~/.kube": KubeConfigSecret,
|
|
45
|
+
"~/.lambda_cloud": LambdaSecret,
|
|
46
|
+
"~/.ssh": SSHSecret,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_secret_to_env_vars = {
|
|
50
|
+
"aws": {"access_key": "AWS_ACCESS_KEY_ID", "secret_key": "AWS_SECRET_ACCESS_KEY"},
|
|
51
|
+
"azure": {"subscription_id": "AZURE_SUBSCRIPTION_ID"},
|
|
52
|
+
"gcp": {"client_id": "CLIENT_ID", "client_secret": "CLIENT_SECRET"},
|
|
53
|
+
"github": {},
|
|
54
|
+
"huggingface": ["HF_TOKEN"],
|
|
55
|
+
"anthropic": {"api_key": "ANTHROPIC_API_KEY"},
|
|
56
|
+
"cohere": {"api_key": "COHERE_API_KEY"},
|
|
57
|
+
"langchain": {"api_key": "LANGCHAIN_API_KEY"},
|
|
58
|
+
"openai": {"api_key": "OPENAI_API_KEY"},
|
|
59
|
+
"pinecone": {"api_key": "PINECONE_API_KEY"},
|
|
60
|
+
"wandb": {"api_key": "WANDB_API_KEY"},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _check_if_provider_secret(provider_str: str = None, provider_env_vars: dict = None):
|
|
65
|
+
import os
|
|
66
|
+
|
|
67
|
+
# user passed provider name or path to secrets values
|
|
68
|
+
if provider_str:
|
|
69
|
+
if provider_str in _str_to_provider_class.keys():
|
|
70
|
+
return _str_to_provider_class[provider_str]
|
|
71
|
+
|
|
72
|
+
full_default_paths_to_provider = {
|
|
73
|
+
os.path.abspath(path): secret for path, secret in _path_to_provider_class.items()
|
|
74
|
+
}
|
|
75
|
+
provided_full_path = os.path.abspath(provider_str)
|
|
76
|
+
return full_default_paths_to_provider.get(provided_full_path, None)
|
|
77
|
+
|
|
78
|
+
# user passed secrets keys to env vars mapping
|
|
79
|
+
elif provider_env_vars:
|
|
80
|
+
for provider_name, default_provider_env_vars in _secret_to_env_vars.items():
|
|
81
|
+
if provider_env_vars == default_provider_env_vars:
|
|
82
|
+
return _str_to_provider_class[provider_name]
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _get_provider_class(provider_info: Union[str, dict]):
|
|
87
|
+
provider_secret = (
|
|
88
|
+
_check_if_provider_secret(provider_str=provider_info)
|
|
89
|
+
if isinstance(provider_info, str)
|
|
90
|
+
else _check_if_provider_secret(provider_env_vars=provider_info)
|
|
91
|
+
)
|
|
92
|
+
return provider_secret
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .. import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SSHSecret(Secret):
|
|
5
|
+
"""
|
|
6
|
+
.. note::
|
|
7
|
+
To create a SSHSecret, please use the factory method :func:`secret` with ``provider="ssh"``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
_DEFAULT_PATH = "~/.ssh"
|
|
11
|
+
_DEFAULT_FILENAMES = ["id_rsa"]
|
|
12
|
+
_PROVIDER = "ssh"
|