jac-scale 0.1.1__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/__init__.py +0 -0
- jac_scale/abstractions/config/app_config.jac +30 -0
- jac_scale/abstractions/config/base_config.jac +26 -0
- jac_scale/abstractions/database_provider.jac +51 -0
- jac_scale/abstractions/deployment_target.jac +64 -0
- jac_scale/abstractions/image_registry.jac +54 -0
- jac_scale/abstractions/logger.jac +20 -0
- jac_scale/abstractions/models/deployment_result.jac +27 -0
- jac_scale/abstractions/models/resource_status.jac +38 -0
- jac_scale/config_loader.jac +31 -0
- jac_scale/context.jac +14 -0
- jac_scale/factories/database_factory.jac +43 -0
- jac_scale/factories/deployment_factory.jac +43 -0
- jac_scale/factories/registry_factory.jac +32 -0
- jac_scale/factories/utility_factory.jac +34 -0
- jac_scale/impl/config_loader.impl.jac +131 -0
- jac_scale/impl/context.impl.jac +24 -0
- jac_scale/impl/memory_hierarchy.main.impl.jac +63 -0
- jac_scale/impl/memory_hierarchy.mongo.impl.jac +239 -0
- jac_scale/impl/memory_hierarchy.redis.impl.jac +186 -0
- jac_scale/impl/serve.impl.jac +1785 -0
- jac_scale/jserver/__init__.py +0 -0
- jac_scale/jserver/impl/jfast_api.impl.jac +731 -0
- jac_scale/jserver/impl/jserver.impl.jac +79 -0
- jac_scale/jserver/jfast_api.jac +162 -0
- jac_scale/jserver/jserver.jac +101 -0
- jac_scale/memory_hierarchy.jac +138 -0
- jac_scale/plugin.jac +218 -0
- jac_scale/plugin_config.jac +175 -0
- jac_scale/providers/database/kubernetes_mongo.jac +137 -0
- jac_scale/providers/database/kubernetes_redis.jac +110 -0
- jac_scale/providers/registry/dockerhub.jac +64 -0
- jac_scale/serve.jac +118 -0
- jac_scale/targets/kubernetes/kubernetes_config.jac +215 -0
- jac_scale/targets/kubernetes/kubernetes_target.jac +841 -0
- jac_scale/targets/kubernetes/utils/kubernetes_utils.impl.jac +519 -0
- jac_scale/targets/kubernetes/utils/kubernetes_utils.jac +85 -0
- jac_scale/tests/__init__.py +0 -0
- jac_scale/tests/conftest.py +29 -0
- jac_scale/tests/fixtures/test_api.jac +159 -0
- jac_scale/tests/fixtures/todo_app.jac +68 -0
- jac_scale/tests/test_abstractions.py +88 -0
- jac_scale/tests/test_deploy_k8s.py +265 -0
- jac_scale/tests/test_examples.py +484 -0
- jac_scale/tests/test_factories.py +149 -0
- jac_scale/tests/test_file_upload.py +444 -0
- jac_scale/tests/test_k8s_utils.py +156 -0
- jac_scale/tests/test_memory_hierarchy.py +247 -0
- jac_scale/tests/test_serve.py +1835 -0
- jac_scale/tests/test_sso.py +711 -0
- jac_scale/utilities/loggers/standard_logger.jac +40 -0
- jac_scale/utils.jac +16 -0
- jac_scale-0.1.1.dist-info/METADATA +658 -0
- jac_scale-0.1.1.dist-info/RECORD +57 -0
- jac_scale-0.1.1.dist-info/WHEEL +5 -0
- jac_scale-0.1.1.dist-info/entry_points.txt +3 -0
- jac_scale-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Plugin configuration hooks for jac-scale.
|
|
2
|
+
|
|
3
|
+
This module implements JacPluginConfig hooks to integrate with core's
|
|
4
|
+
configuration system, registering:
|
|
5
|
+
- Plugin metadata (name, version)
|
|
6
|
+
- Config schema for [plugins.scale] section
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import from typing { Any }
|
|
10
|
+
import from jaclang.pycore.runtime { hookimpl }
|
|
11
|
+
|
|
12
|
+
"""Plugin configuration hooks for jac-scale."""
|
|
13
|
+
class JacScalePluginConfig {
|
|
14
|
+
"""Return plugin metadata."""
|
|
15
|
+
@hookimpl
|
|
16
|
+
static def get_plugin_metadata -> dict[str, Any] {
|
|
17
|
+
return {
|
|
18
|
+
"name": "scale",
|
|
19
|
+
"version": "0.1.0",
|
|
20
|
+
"description": "Jac Scale - Deploy and scale Jac applications with Kubernetes"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
"""Return the plugin's configuration schema for [plugins.scale]."""
|
|
25
|
+
@hookimpl
|
|
26
|
+
static def get_config_schema -> dict[str, Any] {
|
|
27
|
+
return {
|
|
28
|
+
"section": "scale",
|
|
29
|
+
"options": {
|
|
30
|
+
"jwt": {
|
|
31
|
+
"type": "dict",
|
|
32
|
+
"default": {},
|
|
33
|
+
"description": "JWT authentication configuration",
|
|
34
|
+
"nested": {
|
|
35
|
+
"secret": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"default": "supersecretkey",
|
|
38
|
+
"description": "Secret key for JWT signing"
|
|
39
|
+
},
|
|
40
|
+
"algorithm": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"default": "HS256",
|
|
43
|
+
"description": "JWT encoding algorithm"
|
|
44
|
+
},
|
|
45
|
+
"exp_delta_days": {
|
|
46
|
+
"type": "int",
|
|
47
|
+
"default": 7,
|
|
48
|
+
"description": "JWT token expiration in days"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"sso": {
|
|
53
|
+
"type": "dict",
|
|
54
|
+
"default": {},
|
|
55
|
+
"description": "SSO authentication configuration",
|
|
56
|
+
"nested": {
|
|
57
|
+
"host": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"default": "http://localhost:8000/sso",
|
|
60
|
+
"description": "SSO callback host URL"
|
|
61
|
+
},
|
|
62
|
+
"google": {
|
|
63
|
+
"type": "dict",
|
|
64
|
+
"default": {},
|
|
65
|
+
"description": "Google OAuth configuration",
|
|
66
|
+
"nested": {
|
|
67
|
+
"client_id": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"default": "",
|
|
70
|
+
"description": "Google OAuth client ID"
|
|
71
|
+
},
|
|
72
|
+
"client_secret": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"default": "",
|
|
75
|
+
"description": "Google OAuth client secret"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"database": {
|
|
82
|
+
"type": "dict",
|
|
83
|
+
"default": {},
|
|
84
|
+
"description": "Database connection configuration",
|
|
85
|
+
"nested": {
|
|
86
|
+
"mongodb_uri": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"default": None,
|
|
89
|
+
"description": "MongoDB connection URI"
|
|
90
|
+
},
|
|
91
|
+
"redis_url": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"default": None,
|
|
94
|
+
"description": "Redis connection URL"
|
|
95
|
+
},
|
|
96
|
+
"shelf_db_path": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"default": ".jac/data/anchor_store.db",
|
|
99
|
+
"description": "File-based storage path for ShelfDB fallback"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"kubernetes": {
|
|
104
|
+
"type": "dict",
|
|
105
|
+
"default": {},
|
|
106
|
+
"description": "Kubernetes deployment configuration",
|
|
107
|
+
"nested": {
|
|
108
|
+
"app_name": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"default": "jaseci",
|
|
111
|
+
"description": "Application name for Kubernetes resources"
|
|
112
|
+
},
|
|
113
|
+
"docker_image_name": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"default": "",
|
|
116
|
+
"description": "Docker image name (defaults to {app_name}:latest)"
|
|
117
|
+
},
|
|
118
|
+
"docker_username": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"default": "",
|
|
121
|
+
"description": "DockerHub username for pushing images"
|
|
122
|
+
},
|
|
123
|
+
"docker_password": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"default": "",
|
|
126
|
+
"description": "DockerHub password/token for pushing images"
|
|
127
|
+
},
|
|
128
|
+
"namespace": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"default": "default",
|
|
131
|
+
"description": "Kubernetes namespace for deployment"
|
|
132
|
+
},
|
|
133
|
+
"container_port": {
|
|
134
|
+
"type": "int",
|
|
135
|
+
"default": 8000,
|
|
136
|
+
"description": "Container port for the application"
|
|
137
|
+
},
|
|
138
|
+
"node_port": {
|
|
139
|
+
"type": "int",
|
|
140
|
+
"default": 30001,
|
|
141
|
+
"description": "NodePort for external access"
|
|
142
|
+
},
|
|
143
|
+
"mongodb_enabled": {
|
|
144
|
+
"type": "bool",
|
|
145
|
+
"default": True,
|
|
146
|
+
"description": "Enable MongoDB deployment in Kubernetes"
|
|
147
|
+
},
|
|
148
|
+
"redis_enabled": {
|
|
149
|
+
"type": "bool",
|
|
150
|
+
"default": True,
|
|
151
|
+
"description": "Enable Redis deployment in Kubernetes"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"server": {
|
|
156
|
+
"type": "dict",
|
|
157
|
+
"default": {},
|
|
158
|
+
"description": "Server configuration",
|
|
159
|
+
"nested": {
|
|
160
|
+
"port": {
|
|
161
|
+
"type": "int",
|
|
162
|
+
"default": 8000,
|
|
163
|
+
"description": "Server port"
|
|
164
|
+
},
|
|
165
|
+
"host": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"default": "0.0.0.0",
|
|
168
|
+
"description": "Server host"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Kubernetes MongoDB provider implementation."""
|
|
2
|
+
import from typing { Any }
|
|
3
|
+
import from jac_scale.abstractions.database_provider { DatabaseProvider }
|
|
4
|
+
import from jac_scale.abstractions.deployment_target { DeploymentTarget }
|
|
5
|
+
|
|
6
|
+
"""MongoDB provider for Kubernetes deployments."""
|
|
7
|
+
class KubernetesMongoProvider(DatabaseProvider) {
|
|
8
|
+
has target: (DeploymentTarget | None) = None,
|
|
9
|
+
config: dict[str, Any] = {},
|
|
10
|
+
app_name: str = '',
|
|
11
|
+
connection_string: str = '';
|
|
12
|
+
|
|
13
|
+
def init(
|
|
14
|
+
self: KubernetesMongoProvider,
|
|
15
|
+
target: (DeploymentTarget | None) = None,
|
|
16
|
+
config: dict[str, Any] = {}
|
|
17
|
+
) -> None {
|
|
18
|
+
self.target = target;
|
|
19
|
+
self.config = config;
|
|
20
|
+
self.app_name = config.get('app_name', 'jaseci');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def deploy(
|
|
24
|
+
self: KubernetesMongoProvider, config: dict[str, Any] = {}
|
|
25
|
+
) -> dict[str, Any] {
|
|
26
|
+
mongodb_name = f"{self.app_name}-mongodb";
|
|
27
|
+
mongodb_port = 27017;
|
|
28
|
+
mongodb_service_name = f"{mongodb_name}-service";
|
|
29
|
+
mongodb_volume_name = f"{self.app_name}-mongo-data";
|
|
30
|
+
|
|
31
|
+
mongodb_statefulset = {
|
|
32
|
+
'apiVersion': 'apps/v1',
|
|
33
|
+
'kind': 'StatefulSet',
|
|
34
|
+
'metadata': {'name': mongodb_name},
|
|
35
|
+
'spec': {
|
|
36
|
+
'serviceName': mongodb_service_name,
|
|
37
|
+
'replicas': 1,
|
|
38
|
+
'selector': {'matchLabels': {'app': mongodb_name}},
|
|
39
|
+
'template': {
|
|
40
|
+
'metadata': {'labels': {'app': mongodb_name}},
|
|
41
|
+
'spec': {
|
|
42
|
+
'containers': [
|
|
43
|
+
{
|
|
44
|
+
'name': 'mongodb',
|
|
45
|
+
'image': 'mongo:6.0',
|
|
46
|
+
'ports': [{'containerPort': mongodb_port}],
|
|
47
|
+
'env': [
|
|
48
|
+
{
|
|
49
|
+
'name': 'MONGO_INITDB_ROOT_USERNAME',
|
|
50
|
+
'value': 'admin'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
'name': 'MONGO_INITDB_ROOT_PASSWORD',
|
|
54
|
+
'value': 'password'
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
'volumeMounts': [
|
|
58
|
+
{
|
|
59
|
+
'name': mongodb_volume_name,
|
|
60
|
+
'mountPath': '/data/db'
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
'volumeClaimTemplates': [
|
|
68
|
+
{
|
|
69
|
+
'metadata': {'name': mongodb_volume_name},
|
|
70
|
+
'spec': {
|
|
71
|
+
'accessModes': ['ReadWriteOnce'],
|
|
72
|
+
'resources': {'requests': {'storage': '1Gi'}}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
mongodb_service = {
|
|
80
|
+
'apiVersion': 'v1',
|
|
81
|
+
'kind': 'Service',
|
|
82
|
+
'metadata': {'name': mongodb_service_name},
|
|
83
|
+
'spec': {
|
|
84
|
+
'clusterIP': 'None',
|
|
85
|
+
'selector': {'app': mongodb_name},
|
|
86
|
+
'ports': [
|
|
87
|
+
{
|
|
88
|
+
'protocol': 'TCP',
|
|
89
|
+
'port': mongodb_port,
|
|
90
|
+
'targetPort': mongodb_port
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
self.connection_string = f"mongodb://admin:password@{mongodb_service_name}:{mongodb_port}";
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
'deployment': mongodb_statefulset,
|
|
100
|
+
'service': mongodb_service,
|
|
101
|
+
'connection_string': self.connection_string,
|
|
102
|
+
'service_name': mongodb_service_name
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def get_connection_string(self: KubernetesMongoProvider) -> str {
|
|
107
|
+
if not self.connection_string {
|
|
108
|
+
# Generate default connection string
|
|
109
|
+
service_name = f"{self.app_name}-mongodb-service";
|
|
110
|
+
self.connection_string = f"mongodb://admin:password@{service_name}:27017";
|
|
111
|
+
}
|
|
112
|
+
return self.connection_string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def is_available(self: KubernetesMongoProvider) -> bool {
|
|
116
|
+
# TODO: Implement actual check
|
|
117
|
+
return True;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def cleanup(self: KubernetesMongoProvider) -> None {
|
|
121
|
+
# Cleanup is handled by the deployment target
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override def get_init_container(
|
|
125
|
+
self: KubernetesMongoProvider, app_name: str, wait_image: str
|
|
126
|
+
) -> (dict[(str, Any)] | None) {
|
|
127
|
+
return {
|
|
128
|
+
'name': 'wait-for-mongodb',
|
|
129
|
+
'image': wait_image,
|
|
130
|
+
'command': [
|
|
131
|
+
'sh',
|
|
132
|
+
'-c',
|
|
133
|
+
f"until nc -z {app_name}-mongodb-service 27017; do echo waiting for mongodb; sleep 3; done"
|
|
134
|
+
]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Kubernetes Redis provider implementation."""
|
|
2
|
+
import from typing { Any }
|
|
3
|
+
import from jac_scale.abstractions.database_provider { DatabaseProvider }
|
|
4
|
+
import from jac_scale.abstractions.deployment_target { DeploymentTarget }
|
|
5
|
+
|
|
6
|
+
"""Redis provider for Kubernetes deployments."""
|
|
7
|
+
class KubernetesRedisProvider(DatabaseProvider) {
|
|
8
|
+
has target: (DeploymentTarget | None) = None,
|
|
9
|
+
config: dict[str, Any] = {},
|
|
10
|
+
app_name: str = '',
|
|
11
|
+
connection_string: str = '';
|
|
12
|
+
|
|
13
|
+
def init(
|
|
14
|
+
self: KubernetesRedisProvider,
|
|
15
|
+
target: (DeploymentTarget | None) = None,
|
|
16
|
+
config: dict[str, Any] = {}
|
|
17
|
+
) -> None {
|
|
18
|
+
self.target = target;
|
|
19
|
+
self.config = config;
|
|
20
|
+
self.app_name = config.get('app_name', 'jaseci');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def deploy(
|
|
24
|
+
self: KubernetesRedisProvider, config: dict[str, Any] = {}
|
|
25
|
+
) -> dict[str, Any] {
|
|
26
|
+
redis_name = f"{self.app_name}-redis";
|
|
27
|
+
redis_port = 6379;
|
|
28
|
+
redis_service_name = f"{redis_name}-service";
|
|
29
|
+
|
|
30
|
+
redis_deployment = {
|
|
31
|
+
'apiVersion': 'apps/v1',
|
|
32
|
+
'kind': 'Deployment',
|
|
33
|
+
'metadata': {'name': redis_name},
|
|
34
|
+
'spec': {
|
|
35
|
+
'replicas': 1,
|
|
36
|
+
'selector': {'matchLabels': {'app': redis_name}},
|
|
37
|
+
'template': {
|
|
38
|
+
'metadata': {'labels': {'app': redis_name}},
|
|
39
|
+
'spec': {
|
|
40
|
+
'containers': [
|
|
41
|
+
{
|
|
42
|
+
'name': 'redis',
|
|
43
|
+
'image': 'redis:7.2',
|
|
44
|
+
'ports': [{'containerPort': redis_port}],
|
|
45
|
+
'args': ['--save', '', '--appendonly', 'no'],
|
|
46
|
+
'resources': {
|
|
47
|
+
'requests': {'cpu': '100m', 'memory': '128Mi'},
|
|
48
|
+
'limits': {'cpu': '500m', 'memory': '256Mi'}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
redis_service = {
|
|
58
|
+
'apiVersion': 'v1',
|
|
59
|
+
'kind': 'Service',
|
|
60
|
+
'metadata': {'name': redis_service_name},
|
|
61
|
+
'spec': {
|
|
62
|
+
'selector': {'app': redis_name},
|
|
63
|
+
'ports': [
|
|
64
|
+
{'protocol': 'TCP', 'port': redis_port, 'targetPort': redis_port}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
self.connection_string = f"redis://{redis_service_name}:{redis_port}/0";
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
'deployment': redis_deployment,
|
|
73
|
+
'service': redis_service,
|
|
74
|
+
'connection_string': self.connection_string,
|
|
75
|
+
'service_name': redis_service_name
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def get_connection_string(self: KubernetesRedisProvider) -> str {
|
|
80
|
+
if not self.connection_string {
|
|
81
|
+
# Generate default connection string
|
|
82
|
+
service_name = f"{self.app_name}-redis-service";
|
|
83
|
+
self.connection_string = f"redis://{service_name}:6379/0";
|
|
84
|
+
}
|
|
85
|
+
return self.connection_string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
def is_available(self: KubernetesRedisProvider) -> bool {
|
|
89
|
+
# TODO: Implement actual check
|
|
90
|
+
return True;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def cleanup(self: KubernetesRedisProvider) -> None {
|
|
94
|
+
# Cleanup is handled by the deployment target
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override def get_init_container(
|
|
98
|
+
self: KubernetesRedisProvider, app_name: str, wait_image: str
|
|
99
|
+
) -> (dict[(str, Any)] | None) {
|
|
100
|
+
return {
|
|
101
|
+
'name': 'wait-for-redis',
|
|
102
|
+
'image': wait_image,
|
|
103
|
+
'command': [
|
|
104
|
+
'sh',
|
|
105
|
+
'-c',
|
|
106
|
+
f"until nc -z {app_name}-redis-service 6379; do echo waiting for redis; sleep 3; done"
|
|
107
|
+
]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""DockerHub registry implementation."""
|
|
2
|
+
import os;
|
|
3
|
+
import docker;
|
|
4
|
+
import from typing { Any }
|
|
5
|
+
import from docker.errors { APIError, BuildError }
|
|
6
|
+
import from jac_scale.abstractions.image_registry { ImageRegistry }
|
|
7
|
+
|
|
8
|
+
"""DockerHub image registry implementation."""
|
|
9
|
+
class DockerHubRegistry(ImageRegistry) {
|
|
10
|
+
has config: dict[str, Any] = {},
|
|
11
|
+
docker_username: str = '',
|
|
12
|
+
docker_password: str = '';
|
|
13
|
+
|
|
14
|
+
def init(self: DockerHubRegistry, config: dict[str, Any] = {}) -> None {
|
|
15
|
+
self.config = config;
|
|
16
|
+
self.docker_username = config.get('docker_username', '')
|
|
17
|
+
or os.getenv('DOCKER_USERNAME', 'juzailmlwork');
|
|
18
|
+
self.docker_password = config.get('docker_password', '')
|
|
19
|
+
or os.getenv('DOCKER_PASSWORD', '');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def build_image(
|
|
23
|
+
self: DockerHubRegistry, code_folder: str, image_name: (str | None) = None
|
|
24
|
+
) -> str {
|
|
25
|
+
if not image_name {
|
|
26
|
+
app_name = self.config.get('app_name', 'jaseci');
|
|
27
|
+
image_name = self.config.get('docker_image_name') or f"{app_name}:latest";
|
|
28
|
+
}
|
|
29
|
+
repository_name = f"{self.docker_username}/{image_name}";
|
|
30
|
+
|
|
31
|
+
# Build Docker image using Docker client
|
|
32
|
+
docker_client = docker.from_env();
|
|
33
|
+
try {
|
|
34
|
+
docker_client.images.build(
|
|
35
|
+
path=code_folder, tag=repository_name, dockerfile='Dockerfile', rm=True
|
|
36
|
+
);
|
|
37
|
+
} except BuildError as e {
|
|
38
|
+
raise RuntimeError(f"Failed to build Docker image: {e}") from e ;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return repository_name;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def push_image(self: DockerHubRegistry, image_name: str) -> None {
|
|
45
|
+
# Push Docker image to DockerHub
|
|
46
|
+
docker_client = docker.from_env();
|
|
47
|
+
try {
|
|
48
|
+
docker_client.login(
|
|
49
|
+
username=self.docker_username, password=self.docker_password
|
|
50
|
+
);
|
|
51
|
+
docker_client.images.push(image_name);
|
|
52
|
+
} except APIError as e {
|
|
53
|
+
raise RuntimeError(f"Failed to push Docker image: {e}") from e ;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def get_image_url(self: DockerHubRegistry, image_name: str) -> str {
|
|
58
|
+
# If image_name doesn't include username, add it
|
|
59
|
+
if '/' not in image_name {
|
|
60
|
+
return f"{self.docker_username}/{image_name}";
|
|
61
|
+
}
|
|
62
|
+
return image_name;
|
|
63
|
+
}
|
|
64
|
+
}
|
jac_scale/serve.jac
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import logging;
|
|
2
|
+
import mimetypes;
|
|
3
|
+
import from collections.abc { Callable }
|
|
4
|
+
import from datetime { UTC, datetime, timedelta }
|
|
5
|
+
import from pathlib { Path }
|
|
6
|
+
import from pydantic { BaseModel, Field }
|
|
7
|
+
import from typing { Any }
|
|
8
|
+
import jwt;
|
|
9
|
+
import from os { getenv }
|
|
10
|
+
import from fastapi.middleware.cors { CORSMiddleware }
|
|
11
|
+
import from fastapi.responses { HTMLResponse, JSONResponse, Response, RedirectResponse }
|
|
12
|
+
import from jac_scale.jserver.jfast_api { JFastApiServer }
|
|
13
|
+
import from jac_scale.jserver.jserver {
|
|
14
|
+
APIParameter,
|
|
15
|
+
HTTPMethod,
|
|
16
|
+
JEndPoint,
|
|
17
|
+
ParameterType
|
|
18
|
+
}
|
|
19
|
+
import from jaclang.pycore.runtime { JacRuntime as Jac }
|
|
20
|
+
import from jaclang.runtimelib.server { JacAPIServer as JServer }
|
|
21
|
+
import from jaclang.runtimelib.server { JsonValue }
|
|
22
|
+
import from jaclang.runtimelib.transport { TransportResponse, Meta, MessageType }
|
|
23
|
+
import from enum { StrEnum }
|
|
24
|
+
import from fastapi_sso.sso.google { GoogleSSO }
|
|
25
|
+
import from jac_scale.utils { generate_random_password }
|
|
26
|
+
import from jac_scale.config_loader { get_scale_config }
|
|
27
|
+
|
|
28
|
+
# Load configuration from jac.toml with env var overrides
|
|
29
|
+
glob _jwt_config = get_scale_config().get_jwt_config(),
|
|
30
|
+
_sso_config = get_scale_config().get_sso_config(),
|
|
31
|
+
JWT_SECRET = _jwt_config['secret'],
|
|
32
|
+
JWT_ALGORITHM = _jwt_config['algorithm'],
|
|
33
|
+
JWT_EXP_DELTA_DAYS = _jwt_config['exp_delta_days'],
|
|
34
|
+
SSO_HOST = _sso_config['host'],
|
|
35
|
+
logger = logging.getLogger(__name__);
|
|
36
|
+
|
|
37
|
+
enum Platforms ( StrEnum ) { GOOGLE = 'google' }
|
|
38
|
+
|
|
39
|
+
enum Operations ( StrEnum ) { LOGIN = 'login', REGISTER = 'register' }
|
|
40
|
+
|
|
41
|
+
obj JacAPIServer(JServer) {
|
|
42
|
+
# HMR (Hot Module Replacement) support fields
|
|
43
|
+
has _hmr_pending: bool = False,
|
|
44
|
+
_hot_reloader: Any | None = None;
|
|
45
|
+
|
|
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
|
+
def postinit -> None;
|
|
50
|
+
def login(username: str, password: str) -> TransportResponse;
|
|
51
|
+
def register_login_endpoint -> None;
|
|
52
|
+
def update_username(
|
|
53
|
+
current_username: str, new_username: str, Authorization: (str | None) = None
|
|
54
|
+
) -> TransportResponse;
|
|
55
|
+
|
|
56
|
+
def update_password(
|
|
57
|
+
username: str,
|
|
58
|
+
current_password: str,
|
|
59
|
+
new_password: str,
|
|
60
|
+
Authorization: (str | None) = None
|
|
61
|
+
) -> TransportResponse;
|
|
62
|
+
|
|
63
|
+
def register_update_username_endpoint -> None;
|
|
64
|
+
def register_update_password_endpoint -> None;
|
|
65
|
+
def create_user(username: str, password: str) -> TransportResponse;
|
|
66
|
+
def refresh_token(token: (str | None) = None) -> TransportResponse;
|
|
67
|
+
def register_create_user_endpoint -> None;
|
|
68
|
+
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
|
+
def register_sso_endpoints -> None;
|
|
79
|
+
def create_walker_callback(
|
|
80
|
+
walker_name: str, has_node_param: bool = False
|
|
81
|
+
) -> Callable[..., TransportResponse];
|
|
82
|
+
|
|
83
|
+
def create_walker_parameters(
|
|
84
|
+
walker_name: str, invoke_on_root: bool
|
|
85
|
+
) -> list[APIParameter];
|
|
86
|
+
|
|
87
|
+
def register_walkers_endpoints -> None;
|
|
88
|
+
def create_function_callback(func_name: str) -> Callable[..., TransportResponse];
|
|
89
|
+
def create_function_parameters(func_name: str) -> list[APIParameter];
|
|
90
|
+
def register_functions_endpoints -> None;
|
|
91
|
+
def render_page_callback -> Callable[..., HTMLResponse];
|
|
92
|
+
def render_base_route_callback(app_name: str) -> Callable[..., HTMLResponse];
|
|
93
|
+
def register_page_endpoint -> None;
|
|
94
|
+
def serve_client_js_callback -> Callable[..., Response];
|
|
95
|
+
def register_client_js_endpoint -> None;
|
|
96
|
+
def register_static_file_endpoint -> None;
|
|
97
|
+
def serve_static_file(file_path: str) -> Response;
|
|
98
|
+
def register_root_asset_endpoint -> None;
|
|
99
|
+
def serve_root_asset(file_path: str) -> Response;
|
|
100
|
+
def _configure_openapi_security -> None;
|
|
101
|
+
def start(dev: bool = False) -> None;
|
|
102
|
+
# HMR (Hot Module Replacement) dynamic routing methods
|
|
103
|
+
def enable_hmr(hot_reloader: Any) -> None;
|
|
104
|
+
def register_dynamic_walker_endpoint -> None;
|
|
105
|
+
def register_dynamic_function_endpoint -> None;
|
|
106
|
+
def register_dynamic_introspection_endpoints -> None;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
class UpdateUsernameRequest(BaseModel) {
|
|
110
|
+
has current_username: str = Field(..., description='Current username'),
|
|
111
|
+
new_username: str = Field(..., description='New username');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class UpdatePasswordRequest(BaseModel) {
|
|
115
|
+
has username: str = Field(..., description='Username'),
|
|
116
|
+
current_password: str = Field(..., description='Current password'),
|
|
117
|
+
new_password: str = Field(..., description='New password');
|
|
118
|
+
}
|