blackant-sdk 1.0.2__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.
- blackant/__init__.py +31 -0
- blackant/auth/__init__.py +10 -0
- blackant/auth/blackant_auth.py +518 -0
- blackant/auth/keycloak_manager.py +363 -0
- blackant/auth/request_id.py +52 -0
- blackant/auth/role_assignment.py +443 -0
- blackant/auth/tokens.py +57 -0
- blackant/client.py +400 -0
- blackant/config/__init__.py +0 -0
- blackant/config/docker_config.py +457 -0
- blackant/config/keycloak_admin_config.py +107 -0
- blackant/docker/__init__.py +12 -0
- blackant/docker/builder.py +616 -0
- blackant/docker/client.py +983 -0
- blackant/docker/dao.py +462 -0
- blackant/docker/registry.py +172 -0
- blackant/exceptions.py +111 -0
- blackant/http/__init__.py +8 -0
- blackant/http/client.py +125 -0
- blackant/patterns/__init__.py +1 -0
- blackant/patterns/singleton.py +20 -0
- blackant/services/__init__.py +10 -0
- blackant/services/dao.py +414 -0
- blackant/services/registry.py +635 -0
- blackant/utils/__init__.py +8 -0
- blackant/utils/initialization.py +32 -0
- blackant/utils/logging.py +337 -0
- blackant/utils/request_id.py +13 -0
- blackant/utils/store.py +50 -0
- blackant_sdk-1.0.2.dist-info/METADATA +117 -0
- blackant_sdk-1.0.2.dist-info/RECORD +70 -0
- blackant_sdk-1.0.2.dist-info/WHEEL +5 -0
- blackant_sdk-1.0.2.dist-info/top_level.txt +5 -0
- calculation/__init__.py +0 -0
- calculation/base.py +26 -0
- calculation/errors.py +2 -0
- calculation/impl/__init__.py +0 -0
- calculation/impl/my_calculation.py +144 -0
- calculation/impl/simple_calc.py +53 -0
- calculation/impl/test.py +1 -0
- calculation/impl/test_calc.py +36 -0
- calculation/loader.py +227 -0
- notifinations/__init__.py +8 -0
- notifinations/mail_sender.py +212 -0
- storage/__init__.py +0 -0
- storage/errors.py +10 -0
- storage/factory.py +26 -0
- storage/interface.py +19 -0
- storage/minio.py +106 -0
- task/__init__.py +0 -0
- task/dao.py +38 -0
- task/errors.py +10 -0
- task/log_adapter.py +11 -0
- task/parsers/__init__.py +0 -0
- task/parsers/base.py +13 -0
- task/parsers/callback.py +40 -0
- task/parsers/cmd_args.py +52 -0
- task/parsers/freetext.py +19 -0
- task/parsers/objects.py +50 -0
- task/parsers/request.py +56 -0
- task/resource.py +84 -0
- task/states/__init__.py +0 -0
- task/states/base.py +14 -0
- task/states/error.py +47 -0
- task/states/idle.py +12 -0
- task/states/ready.py +51 -0
- task/states/running.py +21 -0
- task/states/set_up.py +40 -0
- task/states/tear_down.py +29 -0
- task/task.py +358 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"""BlackAnt Service Registry module.
|
|
2
|
+
|
|
3
|
+
Provides service registration, discovery, and lifecycle management
|
|
4
|
+
through BlackAnt's service registry API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Dict, Any, Optional, List
|
|
9
|
+
|
|
10
|
+
from ..auth.blackant_auth import BlackAntAuth
|
|
11
|
+
from ..http.client import HTTPClient, HTTPConnectionError
|
|
12
|
+
from ..exceptions import BlackAntDockerError
|
|
13
|
+
from ..utils.logging import get_logger
|
|
14
|
+
from ..auth.role_assignment import get_role_assignment_manager
|
|
15
|
+
|
|
16
|
+
# Import ServiceManagerDAO for object-level Docker operations
|
|
17
|
+
try:
|
|
18
|
+
from .dao import ServiceManagerDAO
|
|
19
|
+
except ImportError:
|
|
20
|
+
ServiceManagerDAO = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ServiceRegistry:
|
|
24
|
+
"""Service registry for managing BlackAnt services.
|
|
25
|
+
|
|
26
|
+
Handles service registration, discovery, metadata management,
|
|
27
|
+
and lifecycle operations through BlackAnt's API.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
auth (BlackAntAuth): Authentication object with credentials.
|
|
31
|
+
base_url (str, optional): Base URL for BlackAnt API.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
>>> auth = BlackAntAuth(user="my_name", password="xxx")
|
|
35
|
+
>>> registry = ServiceRegistry(auth=auth)
|
|
36
|
+
>>> service_id = registry.register({
|
|
37
|
+
... "name": "my-service",
|
|
38
|
+
... "namespace": "default",
|
|
39
|
+
... "metadata": {"version": "1.0.0"}
|
|
40
|
+
... })
|
|
41
|
+
>>> services = registry.list(namespace="default")
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, auth: BlackAntAuth, base_url: Optional[str] = None):
|
|
45
|
+
"""Initialize Service registry with authentication.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
auth: BlackAntAuth object with user credentials.
|
|
49
|
+
base_url: Optional base URL, defaults to environment variable.
|
|
50
|
+
"""
|
|
51
|
+
self.auth = auth
|
|
52
|
+
self.base_url = base_url or os.getenv(
|
|
53
|
+
"BLACKANT_BASE_URL",
|
|
54
|
+
"https://dev.blackant.app"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Initialize HTTPClient with persistent session and connection pooling
|
|
58
|
+
self.http_client = HTTPClient(
|
|
59
|
+
base_url=self.base_url,
|
|
60
|
+
auth_token_store=auth.token_store
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self.logger = get_logger("services.registry")
|
|
64
|
+
|
|
65
|
+
# Initialize ServiceManagerDAO for object-level Docker operations
|
|
66
|
+
# This provides hybrid architecture: HTTP API (external) + Docker SDK (internal)
|
|
67
|
+
try:
|
|
68
|
+
self.service_dao = ServiceManagerDAO() if ServiceManagerDAO else None
|
|
69
|
+
if self.service_dao:
|
|
70
|
+
self.logger.info("ServiceManagerDAO initialized for object-level operations")
|
|
71
|
+
except Exception as dao_error:
|
|
72
|
+
self.logger.warning(f"ServiceManagerDAO initialization failed: {dao_error}")
|
|
73
|
+
self.service_dao = None
|
|
74
|
+
|
|
75
|
+
self.logger.info("ServiceRegistry initialized")
|
|
76
|
+
|
|
77
|
+
def register(self, service_data: Dict[str, Any]) -> str:
|
|
78
|
+
"""Register a new service.
|
|
79
|
+
|
|
80
|
+
MODIFIED: Added automatic role assignment after successful registration.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
service_data: Service registration data containing:
|
|
84
|
+
- name: Service name
|
|
85
|
+
- namespace: Service namespace
|
|
86
|
+
- metadata: Additional service metadata
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Service ID of the registered service.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
BlackAntDockerError: If service registration fails.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
# Get token from auth for authenticated request
|
|
96
|
+
token = self.auth.get_token() if self.auth else None
|
|
97
|
+
|
|
98
|
+
response = self.http_client.send_request(
|
|
99
|
+
endpoint="/api/services/services",
|
|
100
|
+
method="POST",
|
|
101
|
+
json=service_data,
|
|
102
|
+
token=token
|
|
103
|
+
)
|
|
104
|
+
response.raise_for_status()
|
|
105
|
+
|
|
106
|
+
result = response.json()
|
|
107
|
+
service_id = result.get("service_id", result.get("id"))
|
|
108
|
+
self.logger.info(f"Service registered: {service_id}")
|
|
109
|
+
|
|
110
|
+
# Post-publish hook - Automatic role assignment
|
|
111
|
+
self._assign_role_after_publish(service_data, service_id)
|
|
112
|
+
|
|
113
|
+
return service_id
|
|
114
|
+
|
|
115
|
+
except HTTPConnectionError as error:
|
|
116
|
+
self.logger.error(f"Failed to register service: {error}")
|
|
117
|
+
raise BlackAntDockerError(f"Could not register service: {error}") from error
|
|
118
|
+
except Exception as error:
|
|
119
|
+
self.logger.error(f"Unexpected error registering service: {error}")
|
|
120
|
+
raise BlackAntDockerError(f"Service registration failed: {error}") from error
|
|
121
|
+
|
|
122
|
+
def list(self, namespace: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
123
|
+
"""List all services, optionally filtered by namespace.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
namespace: Optional namespace filter.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List of service dictionaries.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
BlackAntDockerError: If listing services fails.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
endpoint = "/api/services/services"
|
|
136
|
+
if namespace:
|
|
137
|
+
endpoint += f"?namespace={namespace}"
|
|
138
|
+
|
|
139
|
+
# Get token from auth for authenticated request
|
|
140
|
+
token = self.auth.get_token() if self.auth else None
|
|
141
|
+
|
|
142
|
+
response = self.http_client.send_request(
|
|
143
|
+
endpoint=endpoint,
|
|
144
|
+
method="GET",
|
|
145
|
+
token=token
|
|
146
|
+
)
|
|
147
|
+
response.raise_for_status()
|
|
148
|
+
|
|
149
|
+
services = response.json()
|
|
150
|
+
self.logger.debug(f"Found {len(services)} services")
|
|
151
|
+
return services
|
|
152
|
+
|
|
153
|
+
except HTTPConnectionError as error:
|
|
154
|
+
self.logger.error(f"Failed to list services: {error}")
|
|
155
|
+
raise BlackAntDockerError(f"Could not list services: {error}") from error
|
|
156
|
+
except Exception as error:
|
|
157
|
+
self.logger.error(f"Unexpected error listing services: {error}")
|
|
158
|
+
raise BlackAntDockerError(f"Service list failed: {error}") from error
|
|
159
|
+
|
|
160
|
+
def get(self, service_id: str = None, service_uuid: str = None, service_name: str = None) -> Dict[str, Any]:
|
|
161
|
+
"""Get metadata for a specific service.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
service_id: Service ID to query.
|
|
165
|
+
service_uuid: Service UUID to query.
|
|
166
|
+
service_name: Service name to query.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Service metadata dictionary.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
BlackAntDockerError: If getting service fails.
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
endpoint = "/api/services/services"
|
|
176
|
+
params = []
|
|
177
|
+
if service_id:
|
|
178
|
+
params.append(f"id={service_id}")
|
|
179
|
+
if service_uuid:
|
|
180
|
+
params.append(f"uuid={service_uuid}")
|
|
181
|
+
if service_name:
|
|
182
|
+
params.append(f"name={service_name}")
|
|
183
|
+
|
|
184
|
+
if params:
|
|
185
|
+
endpoint += "?" + "&".join(params)
|
|
186
|
+
|
|
187
|
+
# Get token from auth for authenticated request
|
|
188
|
+
token = self.auth.get_token() if self.auth else None
|
|
189
|
+
|
|
190
|
+
response = self.http_client.send_request(
|
|
191
|
+
endpoint=endpoint,
|
|
192
|
+
method="GET",
|
|
193
|
+
token=token
|
|
194
|
+
)
|
|
195
|
+
response.raise_for_status()
|
|
196
|
+
|
|
197
|
+
service = response.json()
|
|
198
|
+
self.logger.debug(f"Retrieved service: {service_id}")
|
|
199
|
+
return service
|
|
200
|
+
|
|
201
|
+
except HTTPConnectionError as error:
|
|
202
|
+
self.logger.error(f"Failed to get service {service_id}: {error}")
|
|
203
|
+
raise BlackAntDockerError(f"Could not get service: {error}") from error
|
|
204
|
+
except Exception as error:
|
|
205
|
+
self.logger.error(f"Unexpected error getting service: {error}")
|
|
206
|
+
raise BlackAntDockerError(f"Get service failed: {error}") from error
|
|
207
|
+
|
|
208
|
+
def update(self, service_id: str = None, service_uuid: str = None, service_name: str = None, update_data: Dict[str, Any] = None) -> bool:
|
|
209
|
+
"""Update service metadata.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
service_id: Service ID to update.
|
|
213
|
+
service_uuid: Service UUID to update.
|
|
214
|
+
service_name: Service name to update.
|
|
215
|
+
update_data: Updated service data.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if update successful.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
BlackAntDockerError: If updating service fails.
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
# Note: Backend doesn't have PUT endpoint currently
|
|
225
|
+
# This would need to be implemented in backend
|
|
226
|
+
endpoint = "/api/services/services"
|
|
227
|
+
params = []
|
|
228
|
+
if service_id:
|
|
229
|
+
params.append(f"id={service_id}")
|
|
230
|
+
if service_uuid:
|
|
231
|
+
params.append(f"uuid={service_uuid}")
|
|
232
|
+
if service_name:
|
|
233
|
+
params.append(f"name={service_name}")
|
|
234
|
+
|
|
235
|
+
if params:
|
|
236
|
+
endpoint += "?" + "&".join(params)
|
|
237
|
+
else:
|
|
238
|
+
raise ValueError("One of service_id, service_uuid, or service_name must be provided")
|
|
239
|
+
|
|
240
|
+
# Get token from auth for authenticated request
|
|
241
|
+
token = self.auth.get_token() if self.auth else None
|
|
242
|
+
|
|
243
|
+
response = self.http_client.send_request(
|
|
244
|
+
endpoint=endpoint,
|
|
245
|
+
method="PUT",
|
|
246
|
+
json=update_data,
|
|
247
|
+
token=token
|
|
248
|
+
)
|
|
249
|
+
response.raise_for_status()
|
|
250
|
+
|
|
251
|
+
self.logger.info(f"Service updated: {service_id}")
|
|
252
|
+
return True
|
|
253
|
+
|
|
254
|
+
except HTTPConnectionError as error:
|
|
255
|
+
self.logger.error(f"Failed to update service {service_id}: {error}")
|
|
256
|
+
raise BlackAntDockerError(f"Could not update service: {error}") from error
|
|
257
|
+
except Exception as error:
|
|
258
|
+
self.logger.error(f"Unexpected error updating service: {error}")
|
|
259
|
+
raise BlackAntDockerError(f"Update service failed: {error}") from error
|
|
260
|
+
|
|
261
|
+
def delete(self, service_id: str = None, service_uuid: str = None, service_name: str = None) -> bool:
|
|
262
|
+
"""Delete a service.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
service_id: Service ID to delete.
|
|
266
|
+
service_uuid: Service UUID to delete.
|
|
267
|
+
service_name: Service name to delete.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
True if deletion successful.
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
BlackAntDockerError: If deleting service fails.
|
|
274
|
+
"""
|
|
275
|
+
try:
|
|
276
|
+
endpoint = "/api/services/services"
|
|
277
|
+
params = []
|
|
278
|
+
if service_id:
|
|
279
|
+
params.append(f"id={service_id}")
|
|
280
|
+
if service_uuid:
|
|
281
|
+
params.append(f"uuid={service_uuid}")
|
|
282
|
+
if service_name:
|
|
283
|
+
params.append(f"name={service_name}")
|
|
284
|
+
|
|
285
|
+
if params:
|
|
286
|
+
endpoint += "?" + "&".join(params)
|
|
287
|
+
else:
|
|
288
|
+
raise ValueError("One of service_id, service_uuid, or service_name must be provided")
|
|
289
|
+
|
|
290
|
+
# Get token from auth for authenticated request
|
|
291
|
+
token = self.auth.get_token() if self.auth else None
|
|
292
|
+
|
|
293
|
+
response = self.http_client.send_request(
|
|
294
|
+
endpoint=endpoint,
|
|
295
|
+
method="DELETE",
|
|
296
|
+
token=token
|
|
297
|
+
)
|
|
298
|
+
response.raise_for_status()
|
|
299
|
+
|
|
300
|
+
self.logger.info(f"Service deleted: {service_id}")
|
|
301
|
+
return True
|
|
302
|
+
|
|
303
|
+
except HTTPConnectionError as error:
|
|
304
|
+
self.logger.error(f"Failed to delete service {service_id}: {error}")
|
|
305
|
+
raise BlackAntDockerError(f"Could not delete service: {error}") from error
|
|
306
|
+
except Exception as error:
|
|
307
|
+
self.logger.error(f"Unexpected error deleting service: {error}")
|
|
308
|
+
raise BlackAntDockerError(f"Delete service failed: {error}") from error
|
|
309
|
+
|
|
310
|
+
def get_status(self, service_id: str = None, service_uuid: str = None, service_name: str = None) -> Dict[str, Any]:
|
|
311
|
+
"""Get status of a specific service.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
service_id: Service ID to check status.
|
|
315
|
+
service_uuid: Service UUID to check status.
|
|
316
|
+
service_name: Service name to check status.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Service status dictionary containing:
|
|
320
|
+
- status: Service status (running, stopped, error, etc.)
|
|
321
|
+
- health: Health check results if available
|
|
322
|
+
- metrics: Performance metrics if available
|
|
323
|
+
|
|
324
|
+
Raises:
|
|
325
|
+
BlackAntDockerError: If getting status fails.
|
|
326
|
+
"""
|
|
327
|
+
try:
|
|
328
|
+
# Note: Backend doesn't have status endpoint currently
|
|
329
|
+
# Using GET /api/services/services with parameters (same as get())
|
|
330
|
+
endpoint = "/api/services/services"
|
|
331
|
+
params = []
|
|
332
|
+
if service_id:
|
|
333
|
+
params.append(f"id={service_id}")
|
|
334
|
+
if service_uuid:
|
|
335
|
+
params.append(f"uuid={service_uuid}")
|
|
336
|
+
if service_name:
|
|
337
|
+
params.append(f"name={service_name}")
|
|
338
|
+
|
|
339
|
+
if params:
|
|
340
|
+
endpoint += "?" + "&".join(params)
|
|
341
|
+
else:
|
|
342
|
+
raise ValueError("One of service_id, service_uuid, or service_name must be provided")
|
|
343
|
+
|
|
344
|
+
# Get token from auth for authenticated request
|
|
345
|
+
token = self.auth.get_token() if self.auth else None
|
|
346
|
+
|
|
347
|
+
response = self.http_client.send_request(
|
|
348
|
+
endpoint=endpoint,
|
|
349
|
+
method="GET",
|
|
350
|
+
token=token
|
|
351
|
+
)
|
|
352
|
+
response.raise_for_status()
|
|
353
|
+
|
|
354
|
+
status = response.json()
|
|
355
|
+
self.logger.debug(f"Retrieved status for service: {service_id}")
|
|
356
|
+
return status
|
|
357
|
+
|
|
358
|
+
except HTTPConnectionError as error:
|
|
359
|
+
self.logger.error(f"Failed to get service status {service_id}: {error}")
|
|
360
|
+
raise BlackAntDockerError(f"Could not get service status: {error}") from error
|
|
361
|
+
except Exception as error:
|
|
362
|
+
self.logger.error(f"Unexpected error getting service status: {error}")
|
|
363
|
+
raise BlackAntDockerError(f"Get service status failed: {error}") from error
|
|
364
|
+
|
|
365
|
+
# =============================================================================
|
|
366
|
+
# OBJECT-LEVEL DOCKER OPERATIONS (Using ServiceManagerDAO)
|
|
367
|
+
# =============================================================================
|
|
368
|
+
|
|
369
|
+
def create_service_with_docker(self, service_config: Dict[str, Any]) -> Optional[str]:
|
|
370
|
+
"""Create Docker service using object-level operations.
|
|
371
|
+
|
|
372
|
+
This method uses the ServiceManagerDAO for direct Docker SDK operations
|
|
373
|
+
instead of HTTP API calls. Provides better performance and object-oriented
|
|
374
|
+
interface for internal BlackAnt server operations.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
service_config: Service configuration dictionary containing:
|
|
378
|
+
- name: Service name
|
|
379
|
+
- image: Docker image
|
|
380
|
+
- environments: Environment variables dict
|
|
381
|
+
- networks: Network list
|
|
382
|
+
- labels: Labels dict
|
|
383
|
+
- constraints: Placement constraints list
|
|
384
|
+
- replicas: Replica count (default: 1)
|
|
385
|
+
- cpu_limit: CPU limit in nanocpus
|
|
386
|
+
- memory_limit: Memory limit in bytes
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Service ID if successful, None if ServiceManagerDAO not available
|
|
390
|
+
|
|
391
|
+
Raises:
|
|
392
|
+
BlackAntDockerError: If service creation fails
|
|
393
|
+
|
|
394
|
+
Examples:
|
|
395
|
+
>>> registry = ServiceRegistry(auth)
|
|
396
|
+
>>> service_id = registry.create_service_with_docker({
|
|
397
|
+
... "name": "my-app",
|
|
398
|
+
... "image": "nginx:latest",
|
|
399
|
+
... "environments": {"ENV": "prod"},
|
|
400
|
+
... "replicas": 2
|
|
401
|
+
... })
|
|
402
|
+
"""
|
|
403
|
+
if not self.service_dao:
|
|
404
|
+
self.logger.warning("ServiceManagerDAO not available, cannot create service with Docker")
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
# Use ServiceManagerDAO for object-level Docker operations
|
|
409
|
+
docker_service = self.service_dao.create_service(service_config)
|
|
410
|
+
|
|
411
|
+
self.logger.info(f"Docker service created successfully: {docker_service.name} (ID: {docker_service.id})")
|
|
412
|
+
return docker_service.id
|
|
413
|
+
|
|
414
|
+
except Exception as error:
|
|
415
|
+
self.logger.error(f"Failed to create Docker service: {error}")
|
|
416
|
+
raise BlackAntDockerError(f"Docker service creation failed: {error}") from error
|
|
417
|
+
|
|
418
|
+
def get_docker_service(self, service_id: str):
|
|
419
|
+
"""Get Docker service object using ServiceManagerDAO.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
service_id: Service ID or name
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Docker service object or None if not found/DAO not available
|
|
426
|
+
|
|
427
|
+
Examples:
|
|
428
|
+
>>> service = registry.get_docker_service("my-service")
|
|
429
|
+
>>> if service:
|
|
430
|
+
... print(f"Service: {service.name}, Status: {service.attrs['Spec']}")
|
|
431
|
+
"""
|
|
432
|
+
if not self.service_dao:
|
|
433
|
+
self.logger.warning("ServiceManagerDAO not available")
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
return self.service_dao.get_service(service_id)
|
|
438
|
+
except Exception as error:
|
|
439
|
+
self.logger.error(f"Failed to get Docker service: {error}")
|
|
440
|
+
return None
|
|
441
|
+
|
|
442
|
+
def list_docker_services(self, filters: Optional[Dict[str, Any]] = None) -> List:
|
|
443
|
+
"""List Docker services using object-level operations.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
filters: Docker API filters (e.g., {'label': 'env=prod'})
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
List of Docker service objects, empty list if DAO not available
|
|
450
|
+
|
|
451
|
+
Examples:
|
|
452
|
+
>>> services = registry.list_docker_services({'label': 'env=prod'})
|
|
453
|
+
>>> for service in services:
|
|
454
|
+
... print(f"Service: {service.name}")
|
|
455
|
+
"""
|
|
456
|
+
if not self.service_dao:
|
|
457
|
+
self.logger.warning("ServiceManagerDAO not available")
|
|
458
|
+
return []
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
return self.service_dao.list_services(filters=filters)
|
|
462
|
+
except Exception as error:
|
|
463
|
+
self.logger.error(f"Failed to list Docker services: {error}")
|
|
464
|
+
return []
|
|
465
|
+
|
|
466
|
+
def remove_docker_service(self, service_id: str) -> bool:
|
|
467
|
+
"""Remove Docker service using ServiceManagerDAO.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
service_id: Service ID or name
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
True if removed successfully, False if not found/DAO not available
|
|
474
|
+
|
|
475
|
+
Examples:
|
|
476
|
+
>>> success = registry.remove_docker_service("my-service")
|
|
477
|
+
>>> if success:
|
|
478
|
+
... print("Service removed successfully")
|
|
479
|
+
"""
|
|
480
|
+
if not self.service_dao:
|
|
481
|
+
self.logger.warning("ServiceManagerDAO not available")
|
|
482
|
+
return False
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
return self.service_dao.remove_service(service_id)
|
|
486
|
+
except Exception as error:
|
|
487
|
+
self.logger.error(f"Failed to remove Docker service: {error}")
|
|
488
|
+
return False
|
|
489
|
+
|
|
490
|
+
def scale_docker_service(self, service_id: str, replicas: int) -> bool:
|
|
491
|
+
"""Scale Docker service using object-level operations.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
service_id: Service ID or name
|
|
495
|
+
replicas: Target replica count
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
True if scaled successfully, False if failed/DAO not available
|
|
499
|
+
|
|
500
|
+
Examples:
|
|
501
|
+
>>> success = registry.scale_docker_service("my-service", 3)
|
|
502
|
+
>>> if success:
|
|
503
|
+
... print("Service scaled to 3 replicas")
|
|
504
|
+
"""
|
|
505
|
+
if not self.service_dao:
|
|
506
|
+
self.logger.warning("ServiceManagerDAO not available")
|
|
507
|
+
return False
|
|
508
|
+
|
|
509
|
+
try:
|
|
510
|
+
service = self.service_dao.scale_service(service_id, replicas)
|
|
511
|
+
return service is not None
|
|
512
|
+
except Exception as error:
|
|
513
|
+
self.logger.error(f"Failed to scale Docker service: {error}")
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
def get_service_logs_docker(self, service_id: str, **kwargs) -> Optional[str]:
|
|
517
|
+
"""Get service logs using ServiceManagerDAO.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
service_id: Service ID or name
|
|
521
|
+
**kwargs: Log options (tail, since, follow, etc.)
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
Service logs as string, None if failed/DAO not available
|
|
525
|
+
|
|
526
|
+
Examples:
|
|
527
|
+
>>> logs = registry.get_service_logs_docker("my-service", tail=100)
|
|
528
|
+
>>> if logs:
|
|
529
|
+
... print(logs)
|
|
530
|
+
"""
|
|
531
|
+
if not self.service_dao:
|
|
532
|
+
self.logger.warning("ServiceManagerDAO not available")
|
|
533
|
+
return None
|
|
534
|
+
|
|
535
|
+
try:
|
|
536
|
+
return self.service_dao.get_service_logs(service_id, **kwargs)
|
|
537
|
+
except Exception as error:
|
|
538
|
+
self.logger.error(f"Failed to get service logs: {error}")
|
|
539
|
+
return None
|
|
540
|
+
|
|
541
|
+
def is_docker_service_running(self, service_id: str) -> bool:
|
|
542
|
+
"""Check if Docker service is running using ServiceManagerDAO.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
service_id: Service ID or name
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
True if service has running tasks, False otherwise
|
|
549
|
+
|
|
550
|
+
Examples:
|
|
551
|
+
>>> if registry.is_docker_service_running("my-service"):
|
|
552
|
+
... print("Service is running")
|
|
553
|
+
"""
|
|
554
|
+
if not self.service_dao:
|
|
555
|
+
return False
|
|
556
|
+
|
|
557
|
+
try:
|
|
558
|
+
return self.service_dao.is_service_running(service_id)
|
|
559
|
+
except Exception as error:
|
|
560
|
+
self.logger.error(f"Failed to check service status: {error}")
|
|
561
|
+
return False
|
|
562
|
+
|
|
563
|
+
@property
|
|
564
|
+
def has_docker_support(self) -> bool:
|
|
565
|
+
"""Check if object-level Docker operations are available.
|
|
566
|
+
|
|
567
|
+
Returns:
|
|
568
|
+
True if ServiceManagerDAO is available and operational
|
|
569
|
+
"""
|
|
570
|
+
return self.service_dao is not None
|
|
571
|
+
|
|
572
|
+
def _assign_role_after_publish(
|
|
573
|
+
self,
|
|
574
|
+
service_data: Dict[str, Any],
|
|
575
|
+
service_id: str
|
|
576
|
+
) -> None:
|
|
577
|
+
"""Post-publish hook: Assign science_module role to user.
|
|
578
|
+
|
|
579
|
+
This method is called after successful service registration.
|
|
580
|
+
Role assignment errors are logged but do not prevent service publication.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
service_data: Service registration data (contains user token)
|
|
584
|
+
service_id: ID of the registered service
|
|
585
|
+
"""
|
|
586
|
+
try:
|
|
587
|
+
# Check if automatic role assignment is enabled
|
|
588
|
+
auto_assign_enabled = os.getenv('BLACKANT_AUTO_ASSIGN_ENABLED', 'true').lower() == 'true'
|
|
589
|
+
if not auto_assign_enabled:
|
|
590
|
+
self.logger.debug("Automatic role assignment is disabled")
|
|
591
|
+
return
|
|
592
|
+
|
|
593
|
+
# Get user token from auth token store
|
|
594
|
+
user_token = self.auth.token_store.user_token if self.auth else None
|
|
595
|
+
|
|
596
|
+
if not user_token:
|
|
597
|
+
self.logger.warning(
|
|
598
|
+
"No user token available for role assignment, skipping"
|
|
599
|
+
)
|
|
600
|
+
return
|
|
601
|
+
|
|
602
|
+
# Get role assignment manager
|
|
603
|
+
role_manager = get_role_assignment_manager()
|
|
604
|
+
|
|
605
|
+
# Attempt role assignment (non-blocking)
|
|
606
|
+
service_name = service_data.get('name', 'unknown')
|
|
607
|
+
metadata = {
|
|
608
|
+
'service_id': service_id,
|
|
609
|
+
'image': service_data.get('image'),
|
|
610
|
+
'type': service_data.get('type'),
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
success = role_manager.assign_role_after_publish(
|
|
614
|
+
user_token=user_token,
|
|
615
|
+
service_name=service_name,
|
|
616
|
+
metadata=metadata
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
if success:
|
|
620
|
+
self.logger.info(
|
|
621
|
+
f"Role assignment successful for service '{service_name}' "
|
|
622
|
+
f"(service_id: {service_id})"
|
|
623
|
+
)
|
|
624
|
+
else:
|
|
625
|
+
self.logger.warning(
|
|
626
|
+
f"Role assignment failed for service '{service_name}', "
|
|
627
|
+
f"but service was published successfully"
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
except Exception as e:
|
|
631
|
+
# Catch all exceptions - role assignment must not prevent publication
|
|
632
|
+
self.logger.warning(
|
|
633
|
+
f"Role assignment error (service published successfully): {e}",
|
|
634
|
+
exc_info=True
|
|
635
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Initialization utilities for the application."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import logging.config
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def process_run_mode():
|
|
8
|
+
"""
|
|
9
|
+
Process the run mode from environment variables.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
tuple: (DEBUG_MODE, RELEASE_MODE) booleans
|
|
13
|
+
"""
|
|
14
|
+
run_mode = os.environ.get("RUN_MODE", "debug").lower()
|
|
15
|
+
debug_mode = run_mode == "debug"
|
|
16
|
+
release_mode = run_mode == "release"
|
|
17
|
+
return debug_mode, release_mode
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def initialize_logging(app, config_file_path="logging.ini"):
|
|
21
|
+
"""
|
|
22
|
+
Initialize logging configuration.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
app: Flask application instance
|
|
26
|
+
config_file_path: Path to the logging configuration file
|
|
27
|
+
"""
|
|
28
|
+
_ = app # Mark as intentionally unused
|
|
29
|
+
if os.path.exists(config_file_path):
|
|
30
|
+
logging.config.fileConfig(config_file_path)
|
|
31
|
+
else:
|
|
32
|
+
logging.basicConfig(level=logging.INFO)
|