apolo-app-types 25.4.3__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.
- apolo_app_types/__init__.py +159 -0
- apolo_app_types/app_types.py +70 -0
- apolo_app_types/cli.py +50 -0
- apolo_app_types/clients/__init__.py +0 -0
- apolo_app_types/clients/kube.py +106 -0
- apolo_app_types/helm/__init__.py +0 -0
- apolo_app_types/helm/apps/__init__.py +14 -0
- apolo_app_types/helm/apps/base.py +32 -0
- apolo_app_types/helm/apps/common.py +264 -0
- apolo_app_types/helm/apps/custom_deployment.py +133 -0
- apolo_app_types/helm/apps/dockerhub.py +42 -0
- apolo_app_types/helm/apps/fooocus.py +119 -0
- apolo_app_types/helm/apps/ingress.py +90 -0
- apolo_app_types/helm/apps/llm.py +185 -0
- apolo_app_types/helm/apps/postgres.py +231 -0
- apolo_app_types/helm/apps/spark_job.py +191 -0
- apolo_app_types/helm/apps/stable_diffusion.py +88 -0
- apolo_app_types/helm/apps/weaviate.py +133 -0
- apolo_app_types/helm/utils/__init__.py +0 -0
- apolo_app_types/helm/utils/credentials.py +28 -0
- apolo_app_types/helm/utils/deep_merging.py +24 -0
- apolo_app_types/helm/utils/images.py +51 -0
- apolo_app_types/helm/utils/storage.py +13 -0
- apolo_app_types/inputs/__init__.py +0 -0
- apolo_app_types/inputs/args.py +100 -0
- apolo_app_types/outputs/__init__.py +0 -0
- apolo_app_types/outputs/custom_deployment.py +42 -0
- apolo_app_types/outputs/dockerhub.py +29 -0
- apolo_app_types/outputs/huggingface_cache.py +22 -0
- apolo_app_types/outputs/llm.py +71 -0
- apolo_app_types/outputs/postgres.py +64 -0
- apolo_app_types/outputs/spark_job.py +11 -0
- apolo_app_types/outputs/stable_diffusion.py +45 -0
- apolo_app_types/outputs/update_outputs.py +76 -0
- apolo_app_types/outputs/utils/__init__.py +0 -0
- apolo_app_types/outputs/utils/ingress.py +37 -0
- apolo_app_types/outputs/utils/parsing.py +18 -0
- apolo_app_types/outputs/weaviate.py +94 -0
- apolo_app_types/protocols/__init__.py +0 -0
- apolo_app_types/protocols/apolo_deploy.py +11 -0
- apolo_app_types/protocols/common/__init__.py +69 -0
- apolo_app_types/protocols/common/abc_.py +55 -0
- apolo_app_types/protocols/common/auth.py +28 -0
- apolo_app_types/protocols/common/autoscaling.py +24 -0
- apolo_app_types/protocols/common/buckets.py +128 -0
- apolo_app_types/protocols/common/containers.py +20 -0
- apolo_app_types/protocols/common/hugging_face.py +49 -0
- apolo_app_types/protocols/common/ingress.py +36 -0
- apolo_app_types/protocols/common/k8s.py +56 -0
- apolo_app_types/protocols/common/networking.py +41 -0
- apolo_app_types/protocols/common/postgres.py +33 -0
- apolo_app_types/protocols/common/preset.py +19 -0
- apolo_app_types/protocols/common/py.typed +0 -0
- apolo_app_types/protocols/common/redis.py +9 -0
- apolo_app_types/protocols/common/schema_extra.py +20 -0
- apolo_app_types/protocols/common/secrets_.py +45 -0
- apolo_app_types/protocols/common/storage.py +125 -0
- apolo_app_types/protocols/common/validator.py +26 -0
- apolo_app_types/protocols/custom_deployment.py +39 -0
- apolo_app_types/protocols/dify.py +44 -0
- apolo_app_types/protocols/dockerhub.py +61 -0
- apolo_app_types/protocols/fooocus.py +54 -0
- apolo_app_types/protocols/huggingface_cache.py +9 -0
- apolo_app_types/protocols/jupyter.py +19 -0
- apolo_app_types/protocols/llm.py +130 -0
- apolo_app_types/protocols/mlflow.py +16 -0
- apolo_app_types/protocols/postgres.py +125 -0
- apolo_app_types/protocols/private_gpt.py +29 -0
- apolo_app_types/protocols/py.typed +0 -0
- apolo_app_types/protocols/pycharm.py +10 -0
- apolo_app_types/protocols/shell.py +10 -0
- apolo_app_types/protocols/spark_job.py +189 -0
- apolo_app_types/protocols/stable_diffusion.py +81 -0
- apolo_app_types/protocols/text_embeddings.py +24 -0
- apolo_app_types/protocols/vscode.py +10 -0
- apolo_app_types/protocols/weaviate.py +75 -0
- apolo_app_types/py.typed +0 -0
- apolo_app_types-25.4.3.dist-info/METADATA +33 -0
- apolo_app_types-25.4.3.dist-info/RECORD +81 -0
- apolo_app_types-25.4.3.dist-info/WHEEL +4 -0
- apolo_app_types-25.4.3.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from apolo_app_types.protocols.apolo_deploy import ApoloDeployInputs, ApoloDeployOutputs
|
|
2
|
+
from apolo_app_types.protocols.common import (
|
|
3
|
+
ApoloFilesMount,
|
|
4
|
+
ApoloSecret,
|
|
5
|
+
AppInputs,
|
|
6
|
+
AppInputsDeployer,
|
|
7
|
+
AppOutputs,
|
|
8
|
+
AppOutputsDeployer,
|
|
9
|
+
BasicAuth,
|
|
10
|
+
Bucket,
|
|
11
|
+
Env,
|
|
12
|
+
HuggingFaceModel,
|
|
13
|
+
OptionalStrOrSecret,
|
|
14
|
+
StrOrSecret,
|
|
15
|
+
)
|
|
16
|
+
from apolo_app_types.protocols.custom_deployment import (
|
|
17
|
+
Container,
|
|
18
|
+
ContainerImage,
|
|
19
|
+
CustomDeploymentInputs,
|
|
20
|
+
CustomDeploymentOutputs,
|
|
21
|
+
Service,
|
|
22
|
+
)
|
|
23
|
+
from apolo_app_types.protocols.dify import (
|
|
24
|
+
DifyApi,
|
|
25
|
+
DifyInputs,
|
|
26
|
+
DifyOutputs,
|
|
27
|
+
DifyProxy,
|
|
28
|
+
DifyWeb,
|
|
29
|
+
DifyWorker,
|
|
30
|
+
)
|
|
31
|
+
from apolo_app_types.protocols.dockerhub import (
|
|
32
|
+
DockerConfigModel,
|
|
33
|
+
DockerHubInputs,
|
|
34
|
+
DockerHubOutputs,
|
|
35
|
+
)
|
|
36
|
+
from apolo_app_types.protocols.fooocus import (
|
|
37
|
+
FooocusAppInputs,
|
|
38
|
+
FooocusInputs,
|
|
39
|
+
FooocusOutputs,
|
|
40
|
+
)
|
|
41
|
+
from apolo_app_types.protocols.huggingface_cache import (
|
|
42
|
+
HuggingFaceCacheInputs,
|
|
43
|
+
HuggingFaceCacheOutputs,
|
|
44
|
+
)
|
|
45
|
+
from apolo_app_types.protocols.jupyter import (
|
|
46
|
+
JupyterInputs,
|
|
47
|
+
JupyterOutputs,
|
|
48
|
+
JupyterTypes,
|
|
49
|
+
)
|
|
50
|
+
from apolo_app_types.protocols.llm import (
|
|
51
|
+
LLMInputs,
|
|
52
|
+
OpenAICompatibleAPI,
|
|
53
|
+
OpenAICompatibleChatAPI,
|
|
54
|
+
OpenAICompatibleCompletionsAPI,
|
|
55
|
+
OpenAICompatibleEmbeddingsAPI,
|
|
56
|
+
VLLMOutputs,
|
|
57
|
+
VLLMOutputsV2,
|
|
58
|
+
)
|
|
59
|
+
from apolo_app_types.protocols.mlflow import MLFlowInputs, MLFlowOutputs
|
|
60
|
+
from apolo_app_types.protocols.postgres import (
|
|
61
|
+
CrunchyPostgresOutputs,
|
|
62
|
+
CrunchyPostgresUserCredentials,
|
|
63
|
+
PGBouncer,
|
|
64
|
+
PostgresConfig,
|
|
65
|
+
PostgresInputs,
|
|
66
|
+
PostgresOutputs,
|
|
67
|
+
)
|
|
68
|
+
from apolo_app_types.protocols.private_gpt import PrivateGPTInputs, PrivateGPTOutputs
|
|
69
|
+
from apolo_app_types.protocols.pycharm import PycharmInputs, PycharmOutputs
|
|
70
|
+
from apolo_app_types.protocols.shell import ShellInputs, ShellOutputs
|
|
71
|
+
from apolo_app_types.protocols.spark_job import SparkJobInputs, SparkJobOutputs
|
|
72
|
+
from apolo_app_types.protocols.stable_diffusion import (
|
|
73
|
+
SDModel,
|
|
74
|
+
SDOutputs,
|
|
75
|
+
StableDiffusionInputs,
|
|
76
|
+
StableDiffusionOutputs,
|
|
77
|
+
TextToImgAPI,
|
|
78
|
+
)
|
|
79
|
+
from apolo_app_types.protocols.text_embeddings import (
|
|
80
|
+
TextEmbeddingsInferenceInputs,
|
|
81
|
+
TextEmbeddingsInferenceOutputs,
|
|
82
|
+
)
|
|
83
|
+
from apolo_app_types.protocols.vscode import VSCodeInputs, VSCodeOutputs
|
|
84
|
+
from apolo_app_types.protocols.weaviate import WeaviateInputs, WeaviateOutputs
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"AppInputs",
|
|
89
|
+
"AppOutputs",
|
|
90
|
+
"DifyApi",
|
|
91
|
+
"DifyInputs",
|
|
92
|
+
"DifyOutputs",
|
|
93
|
+
"DifyProxy",
|
|
94
|
+
"DifyWeb",
|
|
95
|
+
"DifyWorker",
|
|
96
|
+
"PycharmInputs",
|
|
97
|
+
"PycharmOutputs",
|
|
98
|
+
"VSCodeInputs",
|
|
99
|
+
"VSCodeOutputs",
|
|
100
|
+
"FooocusInputs",
|
|
101
|
+
"FooocusOutputs",
|
|
102
|
+
"JupyterInputs",
|
|
103
|
+
"JupyterOutputs",
|
|
104
|
+
"LLMInputs",
|
|
105
|
+
"VLLMOutputs",
|
|
106
|
+
"MLFlowInputs",
|
|
107
|
+
"MLFlowOutputs",
|
|
108
|
+
"PostgresInputs",
|
|
109
|
+
"CrunchyPostgresOutputs",
|
|
110
|
+
"ShellInputs",
|
|
111
|
+
"ShellOutputs",
|
|
112
|
+
"SDOutputs",
|
|
113
|
+
"StableDiffusionInputs",
|
|
114
|
+
"TextEmbeddingsInferenceInputs",
|
|
115
|
+
"TextEmbeddingsInferenceOutputs",
|
|
116
|
+
"WeaviateInputs",
|
|
117
|
+
"WeaviateOutputs",
|
|
118
|
+
"OpenAICompatibleEmbeddingsAPI",
|
|
119
|
+
"OpenAICompatibleAPI",
|
|
120
|
+
"OpenAICompatibleChatAPI",
|
|
121
|
+
"OpenAICompatibleCompletionsAPI",
|
|
122
|
+
"TextToImgAPI",
|
|
123
|
+
"CrunchyPostgresUserCredentials",
|
|
124
|
+
"SDModel",
|
|
125
|
+
"BasicAuth",
|
|
126
|
+
"ApoloDeployInputs",
|
|
127
|
+
"ApoloDeployOutputs",
|
|
128
|
+
"JupyterTypes",
|
|
129
|
+
"PrivateGPTInputs",
|
|
130
|
+
"PrivateGPTOutputs",
|
|
131
|
+
"ApoloSecret",
|
|
132
|
+
"StrOrSecret",
|
|
133
|
+
"OptionalStrOrSecret",
|
|
134
|
+
"StableDiffusionOutputs",
|
|
135
|
+
"HuggingFaceModel",
|
|
136
|
+
"VLLMOutputsV2",
|
|
137
|
+
"AppInputsDeployer",
|
|
138
|
+
"PGBouncer",
|
|
139
|
+
"PostgresConfig",
|
|
140
|
+
"DockerHubInputs",
|
|
141
|
+
"DockerHubOutputs",
|
|
142
|
+
"DockerConfigModel",
|
|
143
|
+
"ApoloFilesMount",
|
|
144
|
+
"HuggingFaceCacheInputs",
|
|
145
|
+
"HuggingFaceCacheOutputs",
|
|
146
|
+
"CustomDeploymentInputs",
|
|
147
|
+
"CustomDeploymentOutputs",
|
|
148
|
+
"ContainerImage",
|
|
149
|
+
"Container",
|
|
150
|
+
"Service",
|
|
151
|
+
"Env",
|
|
152
|
+
"AppOutputsDeployer",
|
|
153
|
+
"PostgresOutputs",
|
|
154
|
+
"SparkJobInputs",
|
|
155
|
+
"SparkJobOutputs",
|
|
156
|
+
"FooocusAppInputs",
|
|
157
|
+
"ApoloFilesMount",
|
|
158
|
+
"Bucket",
|
|
159
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
|
|
3
|
+
from apolo_app_types import StableDiffusionInputs
|
|
4
|
+
from apolo_app_types.protocols.common import AppInputs
|
|
5
|
+
from apolo_app_types.protocols.dockerhub import DockerHubInputs
|
|
6
|
+
from apolo_app_types.protocols.huggingface_cache import (
|
|
7
|
+
HuggingFaceCacheInputs,
|
|
8
|
+
)
|
|
9
|
+
from apolo_app_types.protocols.llm import LLMInputs
|
|
10
|
+
from apolo_app_types.protocols.spark_job import SparkJobInputs
|
|
11
|
+
from apolo_app_types.protocols.weaviate import WeaviateInputs
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AppType(enum.StrEnum):
|
|
15
|
+
PostgreSQL = "postgresql"
|
|
16
|
+
TextEmbeddingsInference = "text-embeddings-inference"
|
|
17
|
+
LLMInference = "llm-inference"
|
|
18
|
+
PrivateGPT = "private-gpt"
|
|
19
|
+
Dify = "dify"
|
|
20
|
+
StableDiffusion = "stable-diffusion"
|
|
21
|
+
Weaviate = "weaviate"
|
|
22
|
+
Fooocus = "fooocus"
|
|
23
|
+
Jupyter = "jupyter"
|
|
24
|
+
VSCode = "vscode"
|
|
25
|
+
Pycharm = "pycharm"
|
|
26
|
+
MLFlow = "mlflow"
|
|
27
|
+
Shell = "shell"
|
|
28
|
+
ApoloDeploy = "apolo-deploy"
|
|
29
|
+
DockerHub = "dockerhub"
|
|
30
|
+
HuggingFaceCache = "huggingface-cache"
|
|
31
|
+
CustomDeployment = "custom-deployment"
|
|
32
|
+
SparkJob = "spark-job"
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
return str(self)
|
|
36
|
+
|
|
37
|
+
def deploys_as_job(self) -> bool:
|
|
38
|
+
return self in {
|
|
39
|
+
AppType.PrivateGPT,
|
|
40
|
+
AppType.Fooocus,
|
|
41
|
+
AppType.Jupyter,
|
|
42
|
+
AppType.VSCode,
|
|
43
|
+
AppType.Pycharm,
|
|
44
|
+
AppType.MLFlow,
|
|
45
|
+
AppType.Shell,
|
|
46
|
+
AppType.ApoloDeploy,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def is_appless(self) -> bool:
|
|
50
|
+
return self in {
|
|
51
|
+
AppType.HuggingFaceCache,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_app_inputs(cls, inputs: AppInputs) -> "AppType":
|
|
56
|
+
if isinstance(inputs, LLMInputs):
|
|
57
|
+
return AppType.LLMInference
|
|
58
|
+
if isinstance(inputs, WeaviateInputs):
|
|
59
|
+
return AppType.Weaviate
|
|
60
|
+
if isinstance(inputs, StableDiffusionInputs):
|
|
61
|
+
return AppType.StableDiffusion
|
|
62
|
+
if isinstance(inputs, DockerHubInputs):
|
|
63
|
+
return AppType.DockerHub
|
|
64
|
+
if isinstance(inputs, HuggingFaceCacheInputs):
|
|
65
|
+
return AppType.HuggingFaceCache
|
|
66
|
+
if isinstance(inputs, SparkJobInputs):
|
|
67
|
+
return AppType.SparkJob
|
|
68
|
+
|
|
69
|
+
error_message = f"Unsupported input type: {type(inputs).__name__}"
|
|
70
|
+
raise ValueError(error_message)
|
apolo_app_types/cli.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from apolo_app_types.outputs.update_outputs import update_app_outputs
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
|
|
13
|
+
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
level=log_level,
|
|
16
|
+
format="%(asctime)s - %(levelname)s - %(message)s",
|
|
17
|
+
handlers=[logging.StreamHandler(sys.stdout)],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group()
|
|
24
|
+
def cli() -> None:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@cli.command("update-outputs", context_settings={"ignore_unknown_options": True})
|
|
29
|
+
@click.argument("helm_outputs_json", type=str)
|
|
30
|
+
def update_outputs(
|
|
31
|
+
helm_outputs_json: str,
|
|
32
|
+
) -> None:
|
|
33
|
+
try:
|
|
34
|
+
logger.info("Helm input: %s", helm_outputs_json)
|
|
35
|
+
helm_outputs_dict = json.loads(helm_outputs_json)
|
|
36
|
+
logger.info("Helm outputs: %s", helm_outputs_dict)
|
|
37
|
+
result = asyncio.run(update_app_outputs(helm_outputs_dict))
|
|
38
|
+
if not result:
|
|
39
|
+
m = "Failed to run update_app_outputs"
|
|
40
|
+
raise Exception(m)
|
|
41
|
+
except json.JSONDecodeError as e:
|
|
42
|
+
logger.error("Failed to parse JSON input: %s", e)
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.error("An error occurred: %s", e)
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
cli()
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import typing
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from kubernetes import client, config # type: ignore
|
|
6
|
+
from kubernetes.client.rest import ApiException # type: ignore
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
SERVICE_ACC_NAMESPACE_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_current_namespace() -> str:
|
|
15
|
+
"""
|
|
16
|
+
Retrieve the current namespace from the Kubernetes service account namespace file.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
with Path.open(Path(SERVICE_ACC_NAMESPACE_FILE)) as f:
|
|
20
|
+
return f.read().strip()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
logger.error("Namespace file not found. Are you running in a Kubernetes pod?")
|
|
23
|
+
raise
|
|
24
|
+
except Exception as e:
|
|
25
|
+
err_msg = f"Error while reading namespace file: {e}"
|
|
26
|
+
logger.error(err_msg)
|
|
27
|
+
raise
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def get_ingresses_as_dict(label_selectors: str) -> dict[str, typing.Any]:
|
|
31
|
+
try:
|
|
32
|
+
config.load_incluster_config()
|
|
33
|
+
|
|
34
|
+
networking_v1 = client.NetworkingV1Api()
|
|
35
|
+
namespace = get_current_namespace()
|
|
36
|
+
ingresses = networking_v1.list_namespaced_ingress(
|
|
37
|
+
namespace=namespace, label_selector=label_selectors
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return client.ApiClient().sanitize_for_serialization(ingresses)
|
|
41
|
+
|
|
42
|
+
except ApiException as e:
|
|
43
|
+
err_msg = (
|
|
44
|
+
f"Exception when calling NetworkingV1Api->list_namespaced_ingress: {e}"
|
|
45
|
+
)
|
|
46
|
+
logger.error(err_msg)
|
|
47
|
+
raise e
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def get_services_by_label(label_selectors: str) -> dict[str, typing.Any]:
|
|
51
|
+
try:
|
|
52
|
+
config.load_incluster_config()
|
|
53
|
+
|
|
54
|
+
v1 = client.CoreV1Api()
|
|
55
|
+
namespace = get_current_namespace()
|
|
56
|
+
services = v1.list_namespaced_service(
|
|
57
|
+
namespace=namespace, label_selector=label_selectors
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return client.ApiClient().sanitize_for_serialization(services)
|
|
61
|
+
|
|
62
|
+
except ApiException as e:
|
|
63
|
+
err_msg = f"Exception when calling CoreV1Api->list_namespaced_service: {e}"
|
|
64
|
+
logger.error(err_msg)
|
|
65
|
+
raise e
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def get_services(match_labels: dict[str, str]) -> list[dict[str, typing.Any]]:
|
|
69
|
+
label_selectors = ",".join(f"{k}={v}" for k, v in match_labels.items())
|
|
70
|
+
get_svc_stdout = await get_services_by_label(label_selectors)
|
|
71
|
+
|
|
72
|
+
return get_svc_stdout["items"]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def get_service_host_port(match_labels: dict[str, str]) -> tuple[str, str]:
|
|
76
|
+
services = await get_services(match_labels)
|
|
77
|
+
if not services:
|
|
78
|
+
msg = f"Service with labels {match_labels} not found"
|
|
79
|
+
raise Exception(msg)
|
|
80
|
+
if len(services) > 1:
|
|
81
|
+
msg = f"Multiple services with labels {match_labels} found"
|
|
82
|
+
logger.warning(msg)
|
|
83
|
+
|
|
84
|
+
service = services[0]
|
|
85
|
+
host = f"{service['metadata']['name']}.{service['metadata']['namespace']}"
|
|
86
|
+
post = str(service["spec"]["ports"][0]["port"])
|
|
87
|
+
|
|
88
|
+
return host, post
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def get_secret(label: str) -> typing.Any:
|
|
92
|
+
try:
|
|
93
|
+
config.load_incluster_config()
|
|
94
|
+
|
|
95
|
+
v1 = client.CoreV1Api()
|
|
96
|
+
namespace = get_current_namespace()
|
|
97
|
+
|
|
98
|
+
return v1.list_namespaced_secret(
|
|
99
|
+
namespace=namespace,
|
|
100
|
+
label_selector=label,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
except ApiException as e:
|
|
104
|
+
err_msg = f"Exception when calling CoreV1Api->read_namespaced_secret: {e}"
|
|
105
|
+
logger.error(err_msg)
|
|
106
|
+
raise e
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .base import BaseChartValueProcessor
|
|
2
|
+
from .common import parse_chart_values_simple
|
|
3
|
+
from .custom_deployment import CustomDeploymentChartValueProcessor
|
|
4
|
+
from .llm import LLMChartValueProcessor
|
|
5
|
+
from .stable_diffusion import StableDiffusionChartValueProcessor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"BaseChartValueProcessor",
|
|
10
|
+
"LLMChartValueProcessor",
|
|
11
|
+
"StableDiffusionChartValueProcessor",
|
|
12
|
+
"parse_chart_values_simple",
|
|
13
|
+
"CustomDeploymentChartValueProcessor",
|
|
14
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import logging
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
import apolo_sdk
|
|
6
|
+
|
|
7
|
+
from apolo_app_types import AppInputs
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger()
|
|
11
|
+
|
|
12
|
+
AppInputT = t.TypeVar("AppInputT", bound="AppInputs")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseChartValueProcessor(abc.ABC, t.Generic[AppInputT]):
|
|
16
|
+
def __init__(self, client: apolo_sdk.Client):
|
|
17
|
+
self.client = client
|
|
18
|
+
|
|
19
|
+
async def gen_extra_helm_args(self, *_: t.Any) -> list[str]:
|
|
20
|
+
return ["--timeout", "15m", "--dependency-update"]
|
|
21
|
+
|
|
22
|
+
@abc.abstractmethod
|
|
23
|
+
async def gen_extra_values(
|
|
24
|
+
self,
|
|
25
|
+
input_: AppInputT,
|
|
26
|
+
app_name: str,
|
|
27
|
+
namespace: str,
|
|
28
|
+
app_secrets_name: str,
|
|
29
|
+
*args: t.Any,
|
|
30
|
+
**kwargs: t.Any,
|
|
31
|
+
) -> dict[str, t.Any]:
|
|
32
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import typing as t
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
|
|
9
|
+
import apolo_sdk
|
|
10
|
+
import yaml
|
|
11
|
+
from apolo_sdk import Preset
|
|
12
|
+
|
|
13
|
+
from apolo_app_types.helm.apps.ingress import get_ingress_values
|
|
14
|
+
from apolo_app_types.protocols.common import (
|
|
15
|
+
ApoloFilesMount,
|
|
16
|
+
Ingress,
|
|
17
|
+
Preset as PresetType,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
APOLO_STORAGE_ANNOTATION = "platform.apolo.us/inject-storage"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_preset(client: apolo_sdk.Client, preset_name: str) -> apolo_sdk.Preset:
|
|
27
|
+
if os.environ.get("ENV") == "local":
|
|
28
|
+
return Preset(
|
|
29
|
+
credits_per_hour=Decimal(1.0),
|
|
30
|
+
cpu=1,
|
|
31
|
+
memory=1024,
|
|
32
|
+
resource_pool_names=("default",),
|
|
33
|
+
available_resource_pool_names=("default",),
|
|
34
|
+
)
|
|
35
|
+
preset = client.config.presets.get(preset_name)
|
|
36
|
+
if not preset:
|
|
37
|
+
msg = f"Preset {preset_name} not exist in cluster {client.config.cluster_name}"
|
|
38
|
+
raise Exception(msg)
|
|
39
|
+
return preset
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def preset_to_resources(preset: apolo_sdk.Preset) -> dict[str, t.Any]:
|
|
43
|
+
requests = {
|
|
44
|
+
"cpu": f"{preset.cpu * 1000}m",
|
|
45
|
+
"memory": f"{preset.memory_mb}M",
|
|
46
|
+
}
|
|
47
|
+
if preset.nvidia_gpu:
|
|
48
|
+
requests["nvidia.com/gpu"] = str(preset.nvidia_gpu)
|
|
49
|
+
if preset.amd_gpu:
|
|
50
|
+
requests["amd.com/gpu"] = str(preset.amd_gpu)
|
|
51
|
+
|
|
52
|
+
return {"requests": requests, "limits": requests.copy()}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_component_values(preset: Preset, preset_name: str) -> dict[str, t.Any]:
|
|
56
|
+
return {
|
|
57
|
+
"labels": {
|
|
58
|
+
"platform.apolo.us/component": "app",
|
|
59
|
+
"platform.apolo.us/preset": preset_name,
|
|
60
|
+
},
|
|
61
|
+
"resources": preset_to_resources(preset),
|
|
62
|
+
"tolerations": preset_to_tolerations(preset),
|
|
63
|
+
"affinity": preset_to_affinity(preset),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _get_match_expressions(pool_names: list[str]) -> list[dict[str, t.Any]]:
|
|
68
|
+
return [
|
|
69
|
+
{
|
|
70
|
+
"key": "platform.neuromation.io/nodepool",
|
|
71
|
+
"operator": "In",
|
|
72
|
+
"values": pool_names,
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def preset_to_affinity(preset: apolo_sdk.Preset) -> dict[str, t.Any]:
|
|
78
|
+
affinity = {}
|
|
79
|
+
if preset.available_resource_pool_names:
|
|
80
|
+
affinity["nodeAffinity"] = {
|
|
81
|
+
"requiredDuringSchedulingIgnoredDuringExecution": {
|
|
82
|
+
"nodeSelectorTerms": [
|
|
83
|
+
{
|
|
84
|
+
"matchExpressions": _get_match_expressions(
|
|
85
|
+
list(preset.available_resource_pool_names)
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return affinity
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def preset_to_tolerations(preset: apolo_sdk.Preset) -> list[dict[str, t.Any]]:
|
|
95
|
+
tolerations: list[dict[str, t.Any]] = [
|
|
96
|
+
{
|
|
97
|
+
"effect": "NoSchedule",
|
|
98
|
+
"key": "platform.neuromation.io/job",
|
|
99
|
+
"operator": "Exists",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"effect": "NoExecute",
|
|
103
|
+
"key": "node.kubernetes.io/not-ready",
|
|
104
|
+
"operator": "Exists",
|
|
105
|
+
"tolerationSeconds": 300,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"effect": "NoExecute",
|
|
109
|
+
"key": "node.kubernetes.io/unreachable",
|
|
110
|
+
"operator": "Exists",
|
|
111
|
+
"tolerationSeconds": 300,
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
if preset.amd_gpu:
|
|
115
|
+
tolerations.append(
|
|
116
|
+
{"effect": "NoSchedule", "key": "amd.com/gpu", "operator": "Exists"}
|
|
117
|
+
)
|
|
118
|
+
if preset.nvidia_gpu:
|
|
119
|
+
tolerations.append(
|
|
120
|
+
{"effect": "NoSchedule", "key": "nvidia.com/gpu", "operator": "Exists"}
|
|
121
|
+
)
|
|
122
|
+
return tolerations
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def parse_chart_values_simple(helm_args: list[str]) -> dict[str, t.Any]:
|
|
126
|
+
chart_values = {}
|
|
127
|
+
set_re = re.compile(r"--set[\s+,=](.+?)(?= --set|\s|$)")
|
|
128
|
+
|
|
129
|
+
for match in set_re.finditer(" ".join(helm_args)):
|
|
130
|
+
keyvalue = match.group(1).strip()
|
|
131
|
+
key, value = keyvalue.split("=", 1)
|
|
132
|
+
chart_values[key] = value
|
|
133
|
+
return chart_values
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# TODO: hack - should define specific input models for each helm app
|
|
137
|
+
def get_extra_env_vars_from_job() -> tuple[dict[str, t.Any], list[str]]:
|
|
138
|
+
"""
|
|
139
|
+
Get extra env vars from job environment.
|
|
140
|
+
Currently, only HUGGING_FACE_HUB_TOKEN is supported.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Tuple[dict[str, t.Any], list[str]]: Tuple of extra env vars and secret strings.
|
|
144
|
+
"""
|
|
145
|
+
extra_env_vars = {}
|
|
146
|
+
secret_strings = []
|
|
147
|
+
hf_token = os.getenv("HUGGING_FACE_HUB_TOKEN")
|
|
148
|
+
if hf_token:
|
|
149
|
+
extra_env_vars["HUGGING_FACE_HUB_TOKEN"] = hf_token
|
|
150
|
+
secret_strings.append(hf_token)
|
|
151
|
+
return extra_env_vars, secret_strings
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def set_value_from_dot_notation(
|
|
155
|
+
data: dict[str, t.Any], key: str, value: t.Any
|
|
156
|
+
) -> dict[str, t.Any]:
|
|
157
|
+
"""
|
|
158
|
+
Set value in nested dict using dot notation.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
data (dict[str, t.Any]): Nested dict.
|
|
162
|
+
key (str): Dot notation key.
|
|
163
|
+
value (t.Any): Value to set.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
dict[str, t.Any]: Updated nested dict.
|
|
167
|
+
"""
|
|
168
|
+
data_ref = data
|
|
169
|
+
keys = key.split(".")
|
|
170
|
+
for k in keys[:-1]:
|
|
171
|
+
data = data.setdefault(k, {})
|
|
172
|
+
data[keys[-1]] = value
|
|
173
|
+
return data_ref
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def sanitize_dict_string(
|
|
177
|
+
values: dict[str, t.Any],
|
|
178
|
+
secrets: t.Sequence[str] | None = None,
|
|
179
|
+
keys: t.Sequence[str] | None = None,
|
|
180
|
+
) -> str:
|
|
181
|
+
keys = keys or []
|
|
182
|
+
secrets = secrets or []
|
|
183
|
+
values = deepcopy(values)
|
|
184
|
+
for key in keys:
|
|
185
|
+
values = set_value_from_dot_notation(values, key, "****")
|
|
186
|
+
dict_str = yaml.dump(values)
|
|
187
|
+
if len(secrets) > 0:
|
|
188
|
+
sec_re = re.compile(f"({'|'.join(secrets)})")
|
|
189
|
+
return sec_re.sub("****", dict_str)
|
|
190
|
+
return dict_str
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def gen_apolo_storage_integration_annotations(
|
|
194
|
+
files_mouts: t.Sequence[ApoloFilesMount],
|
|
195
|
+
) -> list[dict[str, str]]:
|
|
196
|
+
storage_mount_annotations = []
|
|
197
|
+
for storage_mount in files_mouts:
|
|
198
|
+
storage_mount_annotations.append(
|
|
199
|
+
{
|
|
200
|
+
"storage_uri": storage_mount.storage_uri.path,
|
|
201
|
+
"mount_path": storage_mount.mount_path.path,
|
|
202
|
+
"mount_mode": storage_mount.mode.mode.value,
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
return storage_mount_annotations
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def gen_apolo_storage_integration_labels(
|
|
209
|
+
*,
|
|
210
|
+
inject_storage: bool = False,
|
|
211
|
+
) -> dict[str, str]:
|
|
212
|
+
if inject_storage:
|
|
213
|
+
return {APOLO_STORAGE_ANNOTATION: "true"}
|
|
214
|
+
return {}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def append_apolo_storage_integration_annotations(
|
|
218
|
+
current_annotations: dict[str, t.Any], files_mounts: t.Sequence[ApoloFilesMount]
|
|
219
|
+
) -> dict[str, str]:
|
|
220
|
+
"""
|
|
221
|
+
Returns a new dict with the storage annotations appended to the current annotations.
|
|
222
|
+
"""
|
|
223
|
+
cur_annot = deepcopy(current_annotations)
|
|
224
|
+
cur_apolo_annotation_str: str | None = cur_annot.get(APOLO_STORAGE_ANNOTATION)
|
|
225
|
+
if cur_apolo_annotation_str:
|
|
226
|
+
current_list = json.loads(cur_apolo_annotation_str)
|
|
227
|
+
else:
|
|
228
|
+
current_list = []
|
|
229
|
+
|
|
230
|
+
new_annotations = gen_apolo_storage_integration_annotations(files_mounts)
|
|
231
|
+
current_list.extend(new_annotations)
|
|
232
|
+
cur_annot[APOLO_STORAGE_ANNOTATION] = json.dumps(current_list)
|
|
233
|
+
return cur_annot
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def gen_extra_values(
|
|
237
|
+
apolo_client: apolo_sdk.Client,
|
|
238
|
+
preset_type: PresetType,
|
|
239
|
+
ingress: Ingress | None = None,
|
|
240
|
+
namespace: str | None = None,
|
|
241
|
+
) -> dict[str, t.Any]:
|
|
242
|
+
preset_name = preset_type.name
|
|
243
|
+
if not preset_name:
|
|
244
|
+
logger.warning("No preset_name found in helm args.")
|
|
245
|
+
return {}
|
|
246
|
+
|
|
247
|
+
preset = get_preset(apolo_client, preset_name)
|
|
248
|
+
tolerations_vals = preset_to_tolerations(preset)
|
|
249
|
+
affinity_vals = preset_to_affinity(preset)
|
|
250
|
+
resources_vals = preset_to_resources(preset)
|
|
251
|
+
ingress_vals: dict[str, t.Any] = {}
|
|
252
|
+
if ingress:
|
|
253
|
+
if not namespace:
|
|
254
|
+
exception_msg = "Namespace is required when ingress is provided."
|
|
255
|
+
raise ValueError(exception_msg)
|
|
256
|
+
ingress_vals = await get_ingress_values(apolo_client, ingress, namespace)
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
"preset_name": preset_name,
|
|
260
|
+
"resources": resources_vals,
|
|
261
|
+
"tolerations": tolerations_vals,
|
|
262
|
+
"affinity": affinity_vals,
|
|
263
|
+
**ingress_vals,
|
|
264
|
+
}
|