jac-scale 0.1.1__py3-none-any.whl → 0.1.4__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.
- jac_scale/abstractions/config/app_config.jac +5 -2
- jac_scale/config_loader.jac +2 -1
- jac_scale/context.jac +2 -1
- jac_scale/factories/storage_factory.jac +75 -0
- jac_scale/google_sso_provider.jac +85 -0
- jac_scale/impl/config_loader.impl.jac +28 -3
- jac_scale/impl/context.impl.jac +1 -0
- jac_scale/impl/serve.impl.jac +749 -266
- jac_scale/impl/user_manager.impl.jac +349 -0
- jac_scale/impl/webhook.impl.jac +212 -0
- jac_scale/jserver/impl/jfast_api.impl.jac +4 -0
- jac_scale/memory_hierarchy.jac +3 -1
- jac_scale/plugin.jac +46 -3
- jac_scale/plugin_config.jac +28 -1
- jac_scale/serve.jac +33 -16
- jac_scale/sso_provider.jac +72 -0
- jac_scale/targets/kubernetes/kubernetes_config.jac +9 -15
- jac_scale/targets/kubernetes/kubernetes_target.jac +174 -15
- jac_scale/tests/fixtures/scale-feats/components/Button.cl.jac +32 -0
- jac_scale/tests/fixtures/scale-feats/main.jac +147 -0
- jac_scale/tests/fixtures/test_api.jac +89 -0
- jac_scale/tests/fixtures/test_restspec.jac +88 -0
- jac_scale/tests/test_deploy_k8s.py +2 -1
- jac_scale/tests/test_examples.py +180 -5
- jac_scale/tests/test_hooks.py +39 -0
- jac_scale/tests/test_restspec.py +289 -0
- jac_scale/tests/test_serve.py +411 -4
- jac_scale/tests/test_sso.py +273 -284
- jac_scale/tests/test_storage.py +274 -0
- jac_scale/user_manager.jac +49 -0
- jac_scale/webhook.jac +93 -0
- {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/METADATA +11 -4
- {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/RECORD +36 -23
- {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/WHEEL +1 -1
- {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/entry_points.txt +0 -0
- {jac_scale-0.1.1.dist-info → jac_scale-0.1.4.dist-info}/top_level.txt +0 -0
jac_scale/plugin_config.jac
CHANGED
|
@@ -34,7 +34,7 @@ class JacScalePluginConfig {
|
|
|
34
34
|
"nested": {
|
|
35
35
|
"secret": {
|
|
36
36
|
"type": "string",
|
|
37
|
-
"default": "
|
|
37
|
+
"default": "supersecretkey_for_testing_only!",
|
|
38
38
|
"description": "Secret key for JWT signing"
|
|
39
39
|
},
|
|
40
40
|
"algorithm": {
|
|
@@ -149,6 +149,33 @@ class JacScalePluginConfig {
|
|
|
149
149
|
"type": "bool",
|
|
150
150
|
"default": True,
|
|
151
151
|
"description": "Enable Redis deployment in Kubernetes"
|
|
152
|
+
},
|
|
153
|
+
"plugin_versions": {
|
|
154
|
+
"type": "dict",
|
|
155
|
+
"default": {},
|
|
156
|
+
"description": "Package versions for PyPI installation (default mode). Use 'latest' or specific version.",
|
|
157
|
+
"nested": {
|
|
158
|
+
"jaclang": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"default": "latest",
|
|
161
|
+
"description": "jaclang package version"
|
|
162
|
+
},
|
|
163
|
+
"jac_scale": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"default": "latest",
|
|
166
|
+
"description": "jac-scale package version"
|
|
167
|
+
},
|
|
168
|
+
"jac_client": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"default": "latest",
|
|
171
|
+
"description": "jac-client package version"
|
|
172
|
+
},
|
|
173
|
+
"jac_byllm": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"default": "latest",
|
|
176
|
+
"description": "jac-byllm package version (use 'none' to skip)"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
152
179
|
}
|
|
153
180
|
}
|
|
154
181
|
},
|
jac_scale/serve.jac
CHANGED
|
@@ -6,7 +6,9 @@ import from pathlib { Path }
|
|
|
6
6
|
import from pydantic { BaseModel, Field }
|
|
7
7
|
import from typing { Any }
|
|
8
8
|
import jwt;
|
|
9
|
+
import json;
|
|
9
10
|
import from os { getenv }
|
|
11
|
+
import from fastapi { Request }
|
|
10
12
|
import from fastapi.middleware.cors { CORSMiddleware }
|
|
11
13
|
import from fastapi.responses { HTMLResponse, JSONResponse, Response, RedirectResponse }
|
|
12
14
|
import from jac_scale.jserver.jfast_api { JFastApiServer }
|
|
@@ -24,6 +26,10 @@ import from enum { StrEnum }
|
|
|
24
26
|
import from fastapi_sso.sso.google { GoogleSSO }
|
|
25
27
|
import from jac_scale.utils { generate_random_password }
|
|
26
28
|
import from jac_scale.config_loader { get_scale_config }
|
|
29
|
+
import from jac_scale.webhook { ApiKeyManager, WebhookUtils }
|
|
30
|
+
import from typing { AsyncGenerator }
|
|
31
|
+
import from inspect { isgenerator }
|
|
32
|
+
import from fastapi.responses { StreamingResponse }
|
|
27
33
|
|
|
28
34
|
# Load configuration from jac.toml with env var overrides
|
|
29
35
|
glob _jwt_config = get_scale_config().get_jwt_config(),
|
|
@@ -38,14 +44,14 @@ enum Platforms ( StrEnum ) { GOOGLE = 'google' }
|
|
|
38
44
|
|
|
39
45
|
enum Operations ( StrEnum ) { LOGIN = 'login', REGISTER = 'register' }
|
|
40
46
|
|
|
47
|
+
enum TransportType ( StrEnum ) { HTTP = 'http', WEBHOOK = 'webhook' }
|
|
48
|
+
|
|
41
49
|
obj JacAPIServer(JServer) {
|
|
42
50
|
# HMR (Hot Module Replacement) support fields
|
|
43
51
|
has _hmr_pending: bool = False,
|
|
44
|
-
_hot_reloader: Any | None = None
|
|
52
|
+
_hot_reloader: Any | None = None,
|
|
53
|
+
_api_key_manager: ApiKeyManager | None = None;
|
|
45
54
|
|
|
46
|
-
static def create_jwt_token(username: str) -> str;
|
|
47
|
-
static def validate_jwt_token(token: str) -> (str | None);
|
|
48
|
-
static def refresh_jwt_token(token: str) -> (str | None);
|
|
49
55
|
def postinit -> None;
|
|
50
56
|
def login(username: str, password: str) -> TransportResponse;
|
|
51
57
|
def register_login_endpoint -> None;
|
|
@@ -66,27 +72,21 @@ obj JacAPIServer(JServer) {
|
|
|
66
72
|
def refresh_token(token: (str | None) = None) -> TransportResponse;
|
|
67
73
|
def register_create_user_endpoint -> None;
|
|
68
74
|
def register_refresh_token_endpoint -> None;
|
|
69
|
-
def get_sso(platform: str, operation: str) -> (GoogleSSO | None);
|
|
70
|
-
async def sso_initiate(
|
|
71
|
-
platform: str, operation: str
|
|
72
|
-
) -> (Response | TransportResponse);
|
|
73
|
-
|
|
74
|
-
async def sso_callback(
|
|
75
|
-
request: Request, platform: str, operation: str
|
|
76
|
-
) -> TransportResponse;
|
|
77
|
-
|
|
78
75
|
def register_sso_endpoints -> None;
|
|
79
76
|
def create_walker_callback(
|
|
80
77
|
walker_name: str, has_node_param: bool = False
|
|
81
78
|
) -> Callable[..., TransportResponse];
|
|
82
79
|
|
|
83
80
|
def create_walker_parameters(
|
|
84
|
-
walker_name: str, invoke_on_root: bool
|
|
81
|
+
walker_name: str, invoke_on_root: bool, method: HTTPMethod = HTTPMethod.POST
|
|
85
82
|
) -> list[APIParameter];
|
|
86
83
|
|
|
87
84
|
def register_walkers_endpoints -> None;
|
|
88
85
|
def create_function_callback(func_name: str) -> Callable[..., TransportResponse];
|
|
89
|
-
def create_function_parameters(
|
|
86
|
+
def create_function_parameters(
|
|
87
|
+
func_name: str, method: HTTPMethod = HTTPMethod.POST
|
|
88
|
+
) -> list[APIParameter];
|
|
89
|
+
|
|
90
90
|
def register_functions_endpoints -> None;
|
|
91
91
|
def render_page_callback -> Callable[..., HTMLResponse];
|
|
92
92
|
def render_base_route_callback(app_name: str) -> Callable[..., HTMLResponse];
|
|
@@ -98,12 +98,29 @@ obj JacAPIServer(JServer) {
|
|
|
98
98
|
def register_root_asset_endpoint -> None;
|
|
99
99
|
def serve_root_asset(file_path: str) -> Response;
|
|
100
100
|
def _configure_openapi_security -> None;
|
|
101
|
-
def start(dev: bool = False) -> None;
|
|
101
|
+
def start(dev: bool = False, no_client: bool = False) -> None;
|
|
102
102
|
# HMR (Hot Module Replacement) dynamic routing methods
|
|
103
103
|
def enable_hmr(hot_reloader: Any) -> None;
|
|
104
104
|
def register_dynamic_walker_endpoint -> None;
|
|
105
105
|
def register_dynamic_function_endpoint -> None;
|
|
106
106
|
def register_dynamic_introspection_endpoints -> None;
|
|
107
|
+
# Webhook support methods
|
|
108
|
+
def get_api_key_manager -> ApiKeyManager;
|
|
109
|
+
def create_api_key(
|
|
110
|
+
name: str, expiry_days: int | None = None, Authorization: str | None = None
|
|
111
|
+
) -> TransportResponse;
|
|
112
|
+
|
|
113
|
+
def list_api_keys(Authorization: str | None = None) -> TransportResponse;
|
|
114
|
+
def revoke_api_key(
|
|
115
|
+
api_key_id: str, Authorization: str | None = None
|
|
116
|
+
) -> TransportResponse;
|
|
117
|
+
|
|
118
|
+
def register_api_key_endpoints -> None;
|
|
119
|
+
def register_webhook_endpoints -> None;
|
|
120
|
+
def create_webhook_callback(walker_name: str) -> Callable[..., TransportResponse];
|
|
121
|
+
def create_webhook_parameters(walker_name: str) -> list[APIParameter];
|
|
122
|
+
def register_dynamic_webhook_endpoint -> None;
|
|
123
|
+
def get_transport_type_for_walker(walker_name: str) -> str;
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
class UpdateUsernameRequest(BaseModel) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""SSO Provider Abstraction for jac-scale.
|
|
2
|
+
|
|
3
|
+
This module defines the abstract interface for SSO providers, enabling
|
|
4
|
+
easy addition of new authentication vendors (Google, Microsoft, GitHub, SAML, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import from typing { Any }
|
|
8
|
+
import from fastapi { Request, Response }
|
|
9
|
+
import from dataclasses { dataclass }
|
|
10
|
+
|
|
11
|
+
"""Standardized user information from SSO providers."""
|
|
12
|
+
@dataclass
|
|
13
|
+
class SSOUserInfo {
|
|
14
|
+
has email: str,
|
|
15
|
+
external_id: str,
|
|
16
|
+
platform: str,
|
|
17
|
+
display_name: (str | None) = None;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
"""Abstract base class for SSO providers.
|
|
21
|
+
|
|
22
|
+
All SSO provider implementations must inherit from this class and implement
|
|
23
|
+
the required methods. This abstraction enables:
|
|
24
|
+
|
|
25
|
+
1. Consistent interface across all SSO vendors
|
|
26
|
+
2. Easy addition of new providers (just implement this interface)
|
|
27
|
+
3. Testability through mock implementations
|
|
28
|
+
4. Standardized user information format
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
To add a new SSO provider (e.g., Microsoft):
|
|
32
|
+
|
|
33
|
+
```jac
|
|
34
|
+
obj MicrosoftSSOProvider(SSOProvider) {
|
|
35
|
+
# Implement the three required methods
|
|
36
|
+
async def initiate_auth(operation: str) -> Response;
|
|
37
|
+
async def handle_callback(request: Request) -> SSOUserInfo;
|
|
38
|
+
def get_platform_name() -> str;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
"""
|
|
42
|
+
obj SSOProvider {
|
|
43
|
+
"""Initialize SSO authentication flow.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
operation: The operation type ('login' or 'register')
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Response object (typically a redirect to the SSO provider's auth page)
|
|
50
|
+
"""
|
|
51
|
+
async def initiate_auth(operation: str) -> Response;
|
|
52
|
+
|
|
53
|
+
"""Handle the OAuth callback from the SSO provider.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
request: The FastAPI request object containing the OAuth callback data
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
SSOUserInfo: Standardized user information from the provider
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
Exception: If authentication fails or user info cannot be retrieved
|
|
63
|
+
"""
|
|
64
|
+
async def handle_callback(request: Request) -> SSOUserInfo;
|
|
65
|
+
|
|
66
|
+
"""Get the platform identifier for this provider.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
str: Platform name (e.g., 'google', 'microsoft', 'github')
|
|
70
|
+
"""
|
|
71
|
+
def get_platform_name -> str;
|
|
72
|
+
}
|
|
@@ -34,12 +34,12 @@ class KubernetesConfig(BaseConfig) {
|
|
|
34
34
|
app_mount_path: str = '/app',
|
|
35
35
|
code_mount_path: str = '/code',
|
|
36
36
|
workspace_path: str = '/code/workspace',
|
|
37
|
-
# Runtime environment (defaults to official Jaseci repo)
|
|
38
37
|
jaseci_repo_url: str = 'https://github.com/jaseci-labs/jaseci.git',
|
|
39
38
|
jaseci_branch: str = 'main',
|
|
40
39
|
jaseci_commit: (str | None) = None,
|
|
41
40
|
install_jaseci: bool = True,
|
|
42
|
-
additional_packages: list[str] = []
|
|
41
|
+
additional_packages: list[str] = [],
|
|
42
|
+
plugin_versions: dict[str, str] = {};
|
|
43
43
|
|
|
44
44
|
def init(
|
|
45
45
|
self: KubernetesConfig,
|
|
@@ -75,12 +75,8 @@ class KubernetesConfig(BaseConfig) {
|
|
|
75
75
|
jaseci_branch: str = 'main',
|
|
76
76
|
jaseci_commit: (str | None) = None,
|
|
77
77
|
install_jaseci: bool = True,
|
|
78
|
-
additional_packages: list[str] = []
|
|
79
|
-
|
|
80
|
-
# Storage configuration
|
|
81
|
-
# Timing configuration
|
|
82
|
-
# Paths
|
|
83
|
-
# Runtime environment
|
|
78
|
+
additional_packages: list[str] = [],
|
|
79
|
+
plugin_versions: dict[str, str] = {}
|
|
84
80
|
) -> None {
|
|
85
81
|
self.app_name = app_name;
|
|
86
82
|
self.namespace = namespace;
|
|
@@ -121,6 +117,7 @@ class KubernetesConfig(BaseConfig) {
|
|
|
121
117
|
self.jaseci_commit = jaseci_commit;
|
|
122
118
|
self.install_jaseci = install_jaseci;
|
|
123
119
|
self.additional_packages = additional_packages;
|
|
120
|
+
self.plugin_versions = plugin_versions;
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
override def to_dict(self: KubernetesConfig) -> dict[str, Any] {
|
|
@@ -162,7 +159,8 @@ class KubernetesConfig(BaseConfig) {
|
|
|
162
159
|
'jaseci_branch': self.jaseci_branch,
|
|
163
160
|
'jaseci_commit': self.jaseci_commit,
|
|
164
161
|
'install_jaseci': self.install_jaseci,
|
|
165
|
-
'additional_packages': self.additional_packages
|
|
162
|
+
'additional_packages': self.additional_packages,
|
|
163
|
+
'plugin_versions': self.plugin_versions
|
|
166
164
|
}
|
|
167
165
|
);
|
|
168
166
|
return base;
|
|
@@ -204,12 +202,8 @@ class KubernetesConfig(BaseConfig) {
|
|
|
204
202
|
jaseci_branch=config.get('jaseci_branch', 'main'),
|
|
205
203
|
jaseci_commit=config.get('jaseci_commit'),
|
|
206
204
|
install_jaseci=config.get('install_jaseci', True),
|
|
207
|
-
additional_packages=config.get('additional_packages', [])
|
|
208
|
-
|
|
209
|
-
# Storage configuration
|
|
210
|
-
# Timing configuration
|
|
211
|
-
# Paths
|
|
212
|
-
# Runtime environment
|
|
205
|
+
additional_packages=config.get('additional_packages', []),
|
|
206
|
+
plugin_versions=config.get('plugin_versions', {})
|
|
213
207
|
);
|
|
214
208
|
}
|
|
215
209
|
}
|
|
@@ -174,10 +174,7 @@ class KubernetesTarget(DeploymentTarget) {
|
|
|
174
174
|
return service;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
"""Build the bash command for setting up Jaseci runtime environment.
|
|
178
|
-
|
|
179
|
-
This replaces the hard-coded command with a configurable version.
|
|
180
|
-
"""
|
|
177
|
+
"""Build the bash command for setting up Jaseci runtime environment."""
|
|
181
178
|
def _build_runtime_setup_command(
|
|
182
179
|
self: KubernetesTarget, app_config: AppConfig
|
|
183
180
|
) -> list[str] {
|
|
@@ -188,36 +185,61 @@ class KubernetesTarget(DeploymentTarget) {
|
|
|
188
185
|
commands.append('export DEBIAN_FRONTEND=noninteractive');
|
|
189
186
|
commands.append('apt-get update');
|
|
190
187
|
|
|
191
|
-
# Install base packages
|
|
192
|
-
base_packages = ['git', '
|
|
188
|
+
# Install base packages (curl and unzip needed for Bun installation)
|
|
189
|
+
base_packages = ['git', 'curl', 'unzip'];
|
|
193
190
|
if config.additional_packages {
|
|
194
191
|
base_packages.extend(config.additional_packages);
|
|
195
192
|
}
|
|
196
193
|
commands.append(f"apt-get install -y {' '.join(base_packages)}");
|
|
197
194
|
|
|
198
|
-
#
|
|
199
|
-
|
|
195
|
+
# Install Bun (required for jac-client frontend builds)
|
|
196
|
+
commands.append('curl -fsSL https://bun.sh/install | bash');
|
|
197
|
+
commands.append('export BUN_INSTALL="$HOME/.bun"');
|
|
198
|
+
commands.append('export PATH="$BUN_INSTALL/bin:$PATH"');
|
|
199
|
+
|
|
200
|
+
if app_config.experimental {
|
|
200
201
|
commands.append('rm -rf jaseci');
|
|
201
|
-
# Clone repository
|
|
202
202
|
clone_cmd = f"git clone --branch {config.jaseci_branch} --single-branch {config.jaseci_repo_url}";
|
|
203
203
|
commands.append(clone_cmd);
|
|
204
204
|
commands.append('cd ./jaseci');
|
|
205
|
-
# Checkout specific commit if provided
|
|
206
205
|
if config.jaseci_commit {
|
|
207
206
|
commands.append(f"git checkout {config.jaseci_commit}");
|
|
208
207
|
}
|
|
209
|
-
# Update submodules
|
|
210
208
|
commands.append('git submodule update --init --recursive');
|
|
211
|
-
# Setup virtual environment
|
|
212
|
-
commands.append('python -m venv venv');
|
|
213
|
-
commands.append('source venv/bin/activate');
|
|
214
|
-
# Install Jaseci components
|
|
215
209
|
commands.append('pip install pluggy');
|
|
216
210
|
commands.append('pip install -e ./jac');
|
|
217
211
|
commands.append('pip install -e ./jac-scale');
|
|
218
212
|
commands.append('pip install -e ./jac-client');
|
|
219
213
|
commands.append('pip install -e ./jac-byllm');
|
|
220
214
|
commands.append('cd ..');
|
|
215
|
+
} else {
|
|
216
|
+
packages = config.plugin_versions or {};
|
|
217
|
+
jaclang_v = packages.get('jaclang', 'latest');
|
|
218
|
+
scale_v = packages.get('jac_scale', 'latest');
|
|
219
|
+
client_v = packages.get('jac_client', 'latest');
|
|
220
|
+
byllm_v = packages.get('jac_byllm', 'latest');
|
|
221
|
+
if jaclang_v == 'latest' {
|
|
222
|
+
commands.append('pip install jaclang');
|
|
223
|
+
} else {
|
|
224
|
+
commands.append(f'pip install jaclang=={jaclang_v}');
|
|
225
|
+
}
|
|
226
|
+
if scale_v == 'latest' {
|
|
227
|
+
commands.append('pip install jac-scale');
|
|
228
|
+
} else {
|
|
229
|
+
commands.append(f'pip install jac-scale=={scale_v}');
|
|
230
|
+
}
|
|
231
|
+
if client_v == 'latest' {
|
|
232
|
+
commands.append('pip install jac-client');
|
|
233
|
+
} else {
|
|
234
|
+
commands.append(f'pip install jac-client=={client_v}');
|
|
235
|
+
}
|
|
236
|
+
if byllm_v and byllm_v != 'none' {
|
|
237
|
+
if byllm_v == 'latest' {
|
|
238
|
+
commands.append('pip install byllm');
|
|
239
|
+
} else {
|
|
240
|
+
commands.append(f'pip install byllm=={byllm_v}');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
# Change to app directory (project is already copied there via volume mount)
|
|
@@ -641,6 +663,135 @@ class KubernetesTarget(DeploymentTarget) {
|
|
|
641
663
|
);
|
|
642
664
|
}
|
|
643
665
|
|
|
666
|
+
"""Wait for resources to be completely deleted."""
|
|
667
|
+
def _wait_for_deletion(
|
|
668
|
+
self: KubernetesTarget,
|
|
669
|
+
app_name: str,
|
|
670
|
+
namespace: str,
|
|
671
|
+
apps_v1: Any,
|
|
672
|
+
core_v1: Any,
|
|
673
|
+
max_wait: int = 60,
|
|
674
|
+
poll_interval: float = 1.0
|
|
675
|
+
) -> None {
|
|
676
|
+
elapsed = 0.0;
|
|
677
|
+
while elapsed < max_wait {
|
|
678
|
+
resources_exist = False;
|
|
679
|
+
|
|
680
|
+
# Check deployment
|
|
681
|
+
try {
|
|
682
|
+
apps_v1.read_namespaced_deployment(name=app_name, namespace=namespace);
|
|
683
|
+
resources_exist = True;
|
|
684
|
+
} except ApiException as e {
|
|
685
|
+
if e.status != 404 {
|
|
686
|
+
raise ;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
# Check service
|
|
691
|
+
try {
|
|
692
|
+
core_v1.read_namespaced_service(
|
|
693
|
+
name=f"{app_name}-service", namespace=namespace
|
|
694
|
+
);
|
|
695
|
+
resources_exist = True;
|
|
696
|
+
} except ApiException as e {
|
|
697
|
+
if e.status != 404 {
|
|
698
|
+
raise ;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
# Check MongoDB resources if enabled
|
|
703
|
+
if self.k8s_config.mongodb_enabled {
|
|
704
|
+
mongodb_name = f"{app_name}-mongodb";
|
|
705
|
+
try {
|
|
706
|
+
apps_v1.read_namespaced_stateful_set(
|
|
707
|
+
name=mongodb_name, namespace=namespace
|
|
708
|
+
);
|
|
709
|
+
resources_exist = True;
|
|
710
|
+
} except ApiException as e {
|
|
711
|
+
if e.status != 404 {
|
|
712
|
+
raise ;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
core_v1.read_namespaced_service(
|
|
717
|
+
name=f"{mongodb_name}-service", namespace=namespace
|
|
718
|
+
);
|
|
719
|
+
resources_exist = True;
|
|
720
|
+
} except ApiException as e {
|
|
721
|
+
if e.status != 404 {
|
|
722
|
+
raise ;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
# Check Redis resources if enabled
|
|
728
|
+
if self.k8s_config.redis_enabled {
|
|
729
|
+
redis_name = f"{app_name}-redis";
|
|
730
|
+
try {
|
|
731
|
+
apps_v1.read_namespaced_deployment(
|
|
732
|
+
name=redis_name, namespace=namespace
|
|
733
|
+
);
|
|
734
|
+
resources_exist = True;
|
|
735
|
+
} except ApiException as e {
|
|
736
|
+
if e.status != 404 {
|
|
737
|
+
raise ;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
try {
|
|
741
|
+
core_v1.read_namespaced_service(
|
|
742
|
+
name=f"{redis_name}-service", namespace=namespace
|
|
743
|
+
);
|
|
744
|
+
resources_exist = True;
|
|
745
|
+
} except ApiException as e {
|
|
746
|
+
if e.status != 404 {
|
|
747
|
+
raise ;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
# Check PVCs
|
|
753
|
+
try {
|
|
754
|
+
pvcs = core_v1.list_namespaced_persistent_volume_claim(namespace);
|
|
755
|
+
for pvc in pvcs.items {
|
|
756
|
+
if pvc.metadata.name.startswith(app_name) {
|
|
757
|
+
resources_exist = True;
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
} except Exception { }
|
|
762
|
+
|
|
763
|
+
# Check code sync pod
|
|
764
|
+
try {
|
|
765
|
+
core_v1.read_namespaced_pod(
|
|
766
|
+
name=f"{app_name}-code-sync", namespace=namespace
|
|
767
|
+
);
|
|
768
|
+
resources_exist = True;
|
|
769
|
+
} except ApiException as e {
|
|
770
|
+
if e.status != 404 {
|
|
771
|
+
raise ;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
# If no resources exist, deletion is complete
|
|
776
|
+
if not resources_exist {
|
|
777
|
+
if self.logger {
|
|
778
|
+
self.logger.info(
|
|
779
|
+
f"All resources for '{app_name}' have been deleted"
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
time.sleep(poll_interval);
|
|
785
|
+
elapsed = elapsed + poll_interval;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if self.logger {
|
|
789
|
+
self.logger.warn(
|
|
790
|
+
f"Timeout waiting for resources to be deleted after {max_wait} seconds"
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
644
795
|
"""Destroy all enabled databases."""
|
|
645
796
|
def _destroy_databases(
|
|
646
797
|
self: KubernetesTarget,
|
|
@@ -727,6 +878,14 @@ class KubernetesTarget(DeploymentTarget) {
|
|
|
727
878
|
}
|
|
728
879
|
}
|
|
729
880
|
|
|
881
|
+
# Wait for all resources to be completely deleted
|
|
882
|
+
if self.logger {
|
|
883
|
+
self.logger.info(
|
|
884
|
+
f"Waiting for all resources to be deleted for '{app_name}'..."
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
self._wait_for_deletion(app_name, namespace, apps_v1, core_v1);
|
|
888
|
+
|
|
730
889
|
if self.logger {
|
|
731
890
|
self.logger.info(f"Application '{app_name}' destroyed successfully");
|
|
732
891
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Button component for the Jac client application."""
|
|
2
|
+
|
|
3
|
+
def:pub Button(label: str, onClick: any, variant: str = "primary", disabled: bool = False) -> any {
|
|
4
|
+
base_styles = {
|
|
5
|
+
"padding": "0.75rem 1.5rem",
|
|
6
|
+
"fontSize": "1rem",
|
|
7
|
+
"fontWeight": "600",
|
|
8
|
+
"borderRadius": "0.5rem",
|
|
9
|
+
"border": "none",
|
|
10
|
+
"cursor": "not-allowed" if disabled else "pointer",
|
|
11
|
+
"transition": "all 0.2s ease"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
variant_styles = {
|
|
15
|
+
"primary": {
|
|
16
|
+
"backgroundColor": "#9ca3af" if disabled else "#3b82f6",
|
|
17
|
+
"color": "#ffffff"
|
|
18
|
+
},
|
|
19
|
+
"secondary": {
|
|
20
|
+
"backgroundColor": "#e5e7eb" if disabled else "#6b7280",
|
|
21
|
+
"color": "#ffffff"
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return <button
|
|
26
|
+
style={{**base_styles, **variant_styles[variant]}}
|
|
27
|
+
onClick={onClick}
|
|
28
|
+
disabled={disabled}
|
|
29
|
+
>
|
|
30
|
+
{label}
|
|
31
|
+
</button>;
|
|
32
|
+
}
|