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.
Files changed (70) hide show
  1. blackant/__init__.py +31 -0
  2. blackant/auth/__init__.py +10 -0
  3. blackant/auth/blackant_auth.py +518 -0
  4. blackant/auth/keycloak_manager.py +363 -0
  5. blackant/auth/request_id.py +52 -0
  6. blackant/auth/role_assignment.py +443 -0
  7. blackant/auth/tokens.py +57 -0
  8. blackant/client.py +400 -0
  9. blackant/config/__init__.py +0 -0
  10. blackant/config/docker_config.py +457 -0
  11. blackant/config/keycloak_admin_config.py +107 -0
  12. blackant/docker/__init__.py +12 -0
  13. blackant/docker/builder.py +616 -0
  14. blackant/docker/client.py +983 -0
  15. blackant/docker/dao.py +462 -0
  16. blackant/docker/registry.py +172 -0
  17. blackant/exceptions.py +111 -0
  18. blackant/http/__init__.py +8 -0
  19. blackant/http/client.py +125 -0
  20. blackant/patterns/__init__.py +1 -0
  21. blackant/patterns/singleton.py +20 -0
  22. blackant/services/__init__.py +10 -0
  23. blackant/services/dao.py +414 -0
  24. blackant/services/registry.py +635 -0
  25. blackant/utils/__init__.py +8 -0
  26. blackant/utils/initialization.py +32 -0
  27. blackant/utils/logging.py +337 -0
  28. blackant/utils/request_id.py +13 -0
  29. blackant/utils/store.py +50 -0
  30. blackant_sdk-1.0.2.dist-info/METADATA +117 -0
  31. blackant_sdk-1.0.2.dist-info/RECORD +70 -0
  32. blackant_sdk-1.0.2.dist-info/WHEEL +5 -0
  33. blackant_sdk-1.0.2.dist-info/top_level.txt +5 -0
  34. calculation/__init__.py +0 -0
  35. calculation/base.py +26 -0
  36. calculation/errors.py +2 -0
  37. calculation/impl/__init__.py +0 -0
  38. calculation/impl/my_calculation.py +144 -0
  39. calculation/impl/simple_calc.py +53 -0
  40. calculation/impl/test.py +1 -0
  41. calculation/impl/test_calc.py +36 -0
  42. calculation/loader.py +227 -0
  43. notifinations/__init__.py +8 -0
  44. notifinations/mail_sender.py +212 -0
  45. storage/__init__.py +0 -0
  46. storage/errors.py +10 -0
  47. storage/factory.py +26 -0
  48. storage/interface.py +19 -0
  49. storage/minio.py +106 -0
  50. task/__init__.py +0 -0
  51. task/dao.py +38 -0
  52. task/errors.py +10 -0
  53. task/log_adapter.py +11 -0
  54. task/parsers/__init__.py +0 -0
  55. task/parsers/base.py +13 -0
  56. task/parsers/callback.py +40 -0
  57. task/parsers/cmd_args.py +52 -0
  58. task/parsers/freetext.py +19 -0
  59. task/parsers/objects.py +50 -0
  60. task/parsers/request.py +56 -0
  61. task/resource.py +84 -0
  62. task/states/__init__.py +0 -0
  63. task/states/base.py +14 -0
  64. task/states/error.py +47 -0
  65. task/states/idle.py +12 -0
  66. task/states/ready.py +51 -0
  67. task/states/running.py +21 -0
  68. task/states/set_up.py +40 -0
  69. task/states/tear_down.py +29 -0
  70. 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,8 @@
1
+ """BlackAnt SDK utilities module.
2
+
3
+ Provides common utilities and base classes.
4
+ """
5
+
6
+ from .store import Store
7
+
8
+ __all__ = ["Store"]
@@ -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)