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,457 @@
|
|
|
1
|
+
"""Docker configuration management for BlackAnt SDK."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
from blackant.utils.logging import get_logger
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import docker
|
|
11
|
+
import docker.tls
|
|
12
|
+
except ImportError:
|
|
13
|
+
docker = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DockerDaemonConfig:
|
|
18
|
+
"""Remote Docker daemon configuration.
|
|
19
|
+
|
|
20
|
+
Supports both nginx proxy (recommended) and direct TLS connection
|
|
21
|
+
to remote Docker daemon as per BlackAnt architecture specification.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Remote Docker daemon host
|
|
25
|
+
remote_host: str = field(default_factory=lambda: os.getenv(
|
|
26
|
+
"DOCKER_DAEMON_HOST",
|
|
27
|
+
"localhost"
|
|
28
|
+
))
|
|
29
|
+
|
|
30
|
+
remote_port: int = field(default_factory=lambda: int(os.getenv(
|
|
31
|
+
"DOCKER_DAEMON_PORT",
|
|
32
|
+
"80"
|
|
33
|
+
)))
|
|
34
|
+
|
|
35
|
+
# Use nginx proxy (recommended for JWT token authentication)
|
|
36
|
+
use_nginx_proxy: bool = field(default_factory=lambda: os.getenv(
|
|
37
|
+
"DOCKER_USE_NGINX_PROXY",
|
|
38
|
+
"true"
|
|
39
|
+
).lower() == "true")
|
|
40
|
+
|
|
41
|
+
# Nginx proxy URL (when use_nginx_proxy=True)
|
|
42
|
+
nginx_proxy_url: str = field(default_factory=lambda: os.getenv(
|
|
43
|
+
"DOCKER_NGINX_PROXY_URL",
|
|
44
|
+
"http://localhost:80/api/docker"
|
|
45
|
+
))
|
|
46
|
+
|
|
47
|
+
# TLS configuration (when use_nginx_proxy=False)
|
|
48
|
+
use_tls: bool = field(default_factory=lambda: os.getenv(
|
|
49
|
+
"DOCKER_USE_TLS",
|
|
50
|
+
"false"
|
|
51
|
+
).lower() == "true")
|
|
52
|
+
|
|
53
|
+
ca_cert: Optional[str] = field(default_factory=lambda: os.getenv("DOCKER_CA_CERT"))
|
|
54
|
+
client_cert: Optional[str] = field(default_factory=lambda: os.getenv("DOCKER_CLIENT_CERT"))
|
|
55
|
+
client_key: Optional[str] = field(default_factory=lambda: os.getenv("DOCKER_CLIENT_KEY"))
|
|
56
|
+
|
|
57
|
+
# Connection timeout
|
|
58
|
+
timeout: int = field(default_factory=lambda: int(os.getenv(
|
|
59
|
+
"DOCKER_DAEMON_TIMEOUT",
|
|
60
|
+
"600"
|
|
61
|
+
)))
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def base_url(self) -> str:
|
|
65
|
+
"""Get Docker daemon base URL.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: Docker daemon URL (nginx proxy or direct connection)
|
|
69
|
+
"""
|
|
70
|
+
if self.use_nginx_proxy:
|
|
71
|
+
return self.nginx_proxy_url
|
|
72
|
+
|
|
73
|
+
protocol = "https" if self.use_tls else "http"
|
|
74
|
+
return f"{protocol}://{self.remote_host}:{self.remote_port}"
|
|
75
|
+
|
|
76
|
+
def get_tls_config(self) -> Optional['docker.tls.TLSConfig']:
|
|
77
|
+
"""Get TLS configuration for Docker client.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
TLSConfig or None if TLS not used or nginx proxy is enabled
|
|
81
|
+
"""
|
|
82
|
+
if docker is None:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
if not self.use_tls or self.use_nginx_proxy:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
if not all([self.ca_cert, self.client_cert, self.client_key]):
|
|
89
|
+
raise ValueError("TLS enabled but certificate paths not configured")
|
|
90
|
+
|
|
91
|
+
return docker.tls.TLSConfig(
|
|
92
|
+
client_cert=(self.client_cert, self.client_key),
|
|
93
|
+
ca_cert=self.ca_cert,
|
|
94
|
+
verify=True
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class DockerRegistryConfig:
|
|
100
|
+
"""Docker registry configuration data class."""
|
|
101
|
+
|
|
102
|
+
url: str = "env.blackant.app"
|
|
103
|
+
namespace: str = "science_module"
|
|
104
|
+
username: Optional[str] = None
|
|
105
|
+
password: Optional[str] = None
|
|
106
|
+
|
|
107
|
+
def __post_init__(self):
|
|
108
|
+
"""Post-initialization to load from environment if not provided."""
|
|
109
|
+
if not self.username:
|
|
110
|
+
self.username = os.getenv("DOCKER_REGISTRY_USER")
|
|
111
|
+
if not self.password:
|
|
112
|
+
self.password = os.getenv("DOCKER_REGISTRY_PASS")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class SdkServicesConfig:
|
|
117
|
+
"""SDK Services configuration for HTTP-based operations.
|
|
118
|
+
|
|
119
|
+
When enabled, registry push and role assignment operations are
|
|
120
|
+
delegated to sdk-services backend, keeping credentials server-side.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
# SDK Services URL
|
|
124
|
+
url: str = field(default_factory=lambda: os.getenv(
|
|
125
|
+
"SDK_SERVICES_URL",
|
|
126
|
+
"" # Empty = disabled, use direct Docker/Keycloak access
|
|
127
|
+
))
|
|
128
|
+
|
|
129
|
+
# Use SDK Services for registry push (instead of direct Docker push)
|
|
130
|
+
use_for_registry: bool = field(default_factory=lambda: os.getenv(
|
|
131
|
+
"SDK_SERVICES_REGISTRY",
|
|
132
|
+
"false"
|
|
133
|
+
).lower() == "true")
|
|
134
|
+
|
|
135
|
+
# Use SDK Services for role assignment (instead of direct Keycloak access)
|
|
136
|
+
use_for_roles: bool = field(default_factory=lambda: os.getenv(
|
|
137
|
+
"SDK_SERVICES_ROLES",
|
|
138
|
+
"false"
|
|
139
|
+
).lower() == "true")
|
|
140
|
+
|
|
141
|
+
# Request timeout in seconds
|
|
142
|
+
timeout: int = field(default_factory=lambda: int(os.getenv(
|
|
143
|
+
"SDK_SERVICES_TIMEOUT",
|
|
144
|
+
"600"
|
|
145
|
+
)))
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def enabled(self) -> bool:
|
|
149
|
+
"""Check if SDK Services is configured and enabled."""
|
|
150
|
+
return bool(self.url) and (self.use_for_registry or self.use_for_roles)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def registry_push_url(self) -> str:
|
|
154
|
+
"""Get the registry push endpoint URL."""
|
|
155
|
+
return f"{self.url}/api/sdk_services/registry/push" if self.url else ""
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def roles_assign_url(self) -> str:
|
|
159
|
+
"""Get the role assignment endpoint URL."""
|
|
160
|
+
return f"{self.url}/api/sdk_services/roles/assign" if self.url else ""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class DockerBuildConfig:
|
|
165
|
+
"""Docker build configuration data class."""
|
|
166
|
+
|
|
167
|
+
timeout: int = 600 # seconds
|
|
168
|
+
build_args: Dict[str, str] = None
|
|
169
|
+
labels: Dict[str, str] = None
|
|
170
|
+
pull: bool = True # Pull base image updates
|
|
171
|
+
rm: bool = True # Remove intermediate containers
|
|
172
|
+
forcerm: bool = True # Always remove intermediate containers
|
|
173
|
+
push_retries: int = 3
|
|
174
|
+
push_timeout: int = 300 # seconds
|
|
175
|
+
|
|
176
|
+
def __post_init__(self):
|
|
177
|
+
"""Initialize default values for mutable fields."""
|
|
178
|
+
if self.build_args is None:
|
|
179
|
+
self.build_args = {
|
|
180
|
+
"BLACKANT_VERSION": "1.0.0",
|
|
181
|
+
"BUILD_PLATFORM": "linux/amd64"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if self.labels is None:
|
|
185
|
+
self.labels = {
|
|
186
|
+
"blackant.sdk.version": "1.0.0",
|
|
187
|
+
"blackant.build.automated": "true"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class DockerConfig:
|
|
192
|
+
"""Central Docker configuration manager for BlackAnt SDK."""
|
|
193
|
+
|
|
194
|
+
def __init__(self):
|
|
195
|
+
"""Initialize Docker configuration."""
|
|
196
|
+
self.logger = get_logger("docker-config")
|
|
197
|
+
self._daemon_config = None
|
|
198
|
+
self._registry_config = None
|
|
199
|
+
self._build_config = None
|
|
200
|
+
self._sdk_services_config = None
|
|
201
|
+
|
|
202
|
+
# Load configuration on init
|
|
203
|
+
self._load_config()
|
|
204
|
+
|
|
205
|
+
def _load_config(self) -> None:
|
|
206
|
+
"""Load configuration from environment and defaults."""
|
|
207
|
+
self.logger.debug("Loading Docker configuration")
|
|
208
|
+
|
|
209
|
+
# Remote Docker daemon configuration
|
|
210
|
+
self._daemon_config = DockerDaemonConfig()
|
|
211
|
+
|
|
212
|
+
# Registry configuration
|
|
213
|
+
self._registry_config = DockerRegistryConfig(
|
|
214
|
+
url=os.getenv("DOCKER_REGISTRY_URL", "env.blackant.app"),
|
|
215
|
+
namespace=os.getenv("DOCKER_REGISTRY_NAMESPACE", "science_module"),
|
|
216
|
+
username=os.getenv("DOCKER_REGISTRY_USER"),
|
|
217
|
+
password=os.getenv("DOCKER_REGISTRY_PASS")
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Build configuration
|
|
221
|
+
self._build_config = DockerBuildConfig(
|
|
222
|
+
timeout=int(os.getenv("DOCKER_BUILD_TIMEOUT", "600")),
|
|
223
|
+
push_retries=int(os.getenv("DOCKER_PUSH_RETRIES", "3")),
|
|
224
|
+
push_timeout=int(os.getenv("DOCKER_PUSH_TIMEOUT", "300"))
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# SDK Services configuration
|
|
228
|
+
self._sdk_services_config = SdkServicesConfig()
|
|
229
|
+
|
|
230
|
+
self.logger.info(f"Docker config loaded: daemon={self._daemon_config.base_url}, registry={self._registry_config.url}")
|
|
231
|
+
|
|
232
|
+
if self._sdk_services_config.enabled:
|
|
233
|
+
self.logger.info(f"SDK Services enabled: {self._sdk_services_config.url}")
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def daemon(self) -> DockerDaemonConfig:
|
|
237
|
+
"""Get Docker daemon configuration."""
|
|
238
|
+
return self._daemon_config
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def registry(self) -> DockerRegistryConfig:
|
|
242
|
+
"""Get registry configuration."""
|
|
243
|
+
return self._registry_config
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def build(self) -> DockerBuildConfig:
|
|
247
|
+
"""Get build configuration."""
|
|
248
|
+
return self._build_config
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def sdk_services(self) -> SdkServicesConfig:
|
|
252
|
+
"""Get SDK Services configuration."""
|
|
253
|
+
return self._sdk_services_config
|
|
254
|
+
|
|
255
|
+
def get_full_image_name(self, service_name: str, tag: str = "latest") -> str:
|
|
256
|
+
"""Generate full Docker image name with registry and namespace.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
service_name: Service name
|
|
260
|
+
tag: Image tag
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
str: Full image name (e.g., "env.blackant.app/systemdevelopers/service:tag")
|
|
264
|
+
"""
|
|
265
|
+
return f"{self.registry.url}/{self.registry.namespace}/{service_name}:{tag}"
|
|
266
|
+
|
|
267
|
+
def get_registry_credentials(self) -> Dict[str, Optional[str]]:
|
|
268
|
+
"""Get registry credentials for Docker login.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
dict: Registry credentials or empty dict if not configured
|
|
272
|
+
"""
|
|
273
|
+
if self.registry.username and self.registry.password:
|
|
274
|
+
return {
|
|
275
|
+
"username": self.registry.username,
|
|
276
|
+
"password": self.registry.password,
|
|
277
|
+
"registry": self.registry.url
|
|
278
|
+
}
|
|
279
|
+
return {}
|
|
280
|
+
|
|
281
|
+
def get_build_args(self, service_name: str, additional_args: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
282
|
+
"""Get build arguments for Docker build.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
service_name: Service name to add to build args
|
|
286
|
+
additional_args: Additional build arguments to merge
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
dict: Combined build arguments
|
|
290
|
+
"""
|
|
291
|
+
args = {
|
|
292
|
+
**self.build.build_args,
|
|
293
|
+
"SERVICE_NAME": service_name
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if additional_args:
|
|
297
|
+
args.update(additional_args)
|
|
298
|
+
|
|
299
|
+
return args
|
|
300
|
+
|
|
301
|
+
def get_build_labels(self, service_name: str, additional_labels: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
302
|
+
"""Get build labels for Docker build.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
service_name: Service name to add to labels
|
|
306
|
+
additional_labels: Additional labels to merge
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
dict: Combined build labels
|
|
310
|
+
"""
|
|
311
|
+
from datetime import datetime
|
|
312
|
+
|
|
313
|
+
labels = {
|
|
314
|
+
**self.build.labels,
|
|
315
|
+
"blackant.service.name": service_name,
|
|
316
|
+
"blackant.build.date": datetime.now().isoformat(),
|
|
317
|
+
"blackant.registry": self.registry.url
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if additional_labels:
|
|
321
|
+
labels.update(additional_labels)
|
|
322
|
+
|
|
323
|
+
return labels
|
|
324
|
+
|
|
325
|
+
def validate_config(self) -> bool:
|
|
326
|
+
"""Validate Docker configuration.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
bool: True if configuration is valid
|
|
330
|
+
|
|
331
|
+
Raises:
|
|
332
|
+
ValueError: If configuration is invalid
|
|
333
|
+
"""
|
|
334
|
+
# Registry validation
|
|
335
|
+
if not self.registry.url:
|
|
336
|
+
raise ValueError("Docker registry URL cannot be empty")
|
|
337
|
+
|
|
338
|
+
if not self.registry.namespace:
|
|
339
|
+
raise ValueError("Docker registry namespace cannot be empty")
|
|
340
|
+
|
|
341
|
+
# Build validation
|
|
342
|
+
if self.build.timeout <= 0:
|
|
343
|
+
raise ValueError("Docker build timeout must be positive")
|
|
344
|
+
|
|
345
|
+
if self.build.push_retries < 0:
|
|
346
|
+
raise ValueError("Docker push retries cannot be negative")
|
|
347
|
+
|
|
348
|
+
if self.build.push_timeout <= 0:
|
|
349
|
+
raise ValueError("Docker push timeout must be positive")
|
|
350
|
+
|
|
351
|
+
self.logger.debug("Docker configuration validation passed")
|
|
352
|
+
return True
|
|
353
|
+
|
|
354
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
355
|
+
"""Convert configuration to dictionary.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
dict: Configuration as dictionary (without sensitive data)
|
|
359
|
+
"""
|
|
360
|
+
return {
|
|
361
|
+
"daemon": {
|
|
362
|
+
"base_url": self.daemon.base_url,
|
|
363
|
+
"use_nginx_proxy": self.daemon.use_nginx_proxy,
|
|
364
|
+
"use_tls": self.daemon.use_tls,
|
|
365
|
+
"timeout": self.daemon.timeout
|
|
366
|
+
},
|
|
367
|
+
"registry": {
|
|
368
|
+
"url": self.registry.url,
|
|
369
|
+
"namespace": self.registry.namespace,
|
|
370
|
+
"has_credentials": bool(self.registry.username and self.registry.password)
|
|
371
|
+
},
|
|
372
|
+
"build": {
|
|
373
|
+
"timeout": self.build.timeout,
|
|
374
|
+
"push_retries": self.build.push_retries,
|
|
375
|
+
"push_timeout": self.build.push_timeout,
|
|
376
|
+
"pull": self.build.pull,
|
|
377
|
+
"rm": self.build.rm,
|
|
378
|
+
"forcerm": self.build.forcerm
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
def __str__(self) -> str:
|
|
383
|
+
"""String representation of configuration."""
|
|
384
|
+
return f"DockerConfig(daemon={self.daemon.base_url}, registry={self.registry.url}/{self.registry.namespace})"
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# Global configuration instance - singleton pattern használata
|
|
388
|
+
_docker_config_instance = None
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def get_docker_config() -> DockerConfig:
|
|
392
|
+
"""Get global Docker configuration instance.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
DockerConfig: Global configuration instance
|
|
396
|
+
"""
|
|
397
|
+
global _docker_config_instance
|
|
398
|
+
|
|
399
|
+
if _docker_config_instance is None:
|
|
400
|
+
_docker_config_instance = DockerConfig()
|
|
401
|
+
_docker_config_instance.validate_config()
|
|
402
|
+
|
|
403
|
+
return _docker_config_instance
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def reload_docker_config() -> DockerConfig:
|
|
407
|
+
"""Reload Docker configuration from environment.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
DockerConfig: Newly loaded configuration instance
|
|
411
|
+
"""
|
|
412
|
+
global _docker_config_instance
|
|
413
|
+
_docker_config_instance = None
|
|
414
|
+
return get_docker_config()
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
# Environment variable documentation
|
|
418
|
+
"""
|
|
419
|
+
Environment Variables for Docker Configuration:
|
|
420
|
+
|
|
421
|
+
=== Remote Docker Daemon Configuration ===
|
|
422
|
+
DOCKER_USE_NGINX_PROXY: Use nginx proxy for Docker daemon access (default: true)
|
|
423
|
+
DOCKER_NGINX_PROXY_URL: Nginx proxy URL (default: http://localhost)
|
|
424
|
+
DOCKER_DAEMON_HOST: Remote Docker daemon host (default: localhost)
|
|
425
|
+
DOCKER_DAEMON_PORT: Remote Docker daemon port (default: 80)
|
|
426
|
+
DOCKER_USE_TLS: Use TLS for direct Docker connection (default: false)
|
|
427
|
+
DOCKER_CA_CERT: Path to CA certificate for TLS
|
|
428
|
+
DOCKER_CLIENT_CERT: Path to client certificate for TLS
|
|
429
|
+
DOCKER_CLIENT_KEY: Path to client key for TLS
|
|
430
|
+
DOCKER_DAEMON_TIMEOUT: Docker daemon timeout in seconds (default: 600)
|
|
431
|
+
|
|
432
|
+
=== Docker Registry Configuration ===
|
|
433
|
+
DOCKER_REGISTRY_URL: Docker registry URL (default: env.blackant.app)
|
|
434
|
+
DOCKER_REGISTRY_NAMESPACE: Registry namespace (default: systemdevelopers)
|
|
435
|
+
DOCKER_REGISTRY_USER: Registry username for authentication
|
|
436
|
+
DOCKER_REGISTRY_PASS: Registry password for authentication
|
|
437
|
+
|
|
438
|
+
=== Build Configuration ===
|
|
439
|
+
DOCKER_BUILD_TIMEOUT: Build timeout in seconds (default: 600)
|
|
440
|
+
DOCKER_PUSH_RETRIES: Number of push retries (default: 3)
|
|
441
|
+
DOCKER_PUSH_TIMEOUT: Push timeout in seconds (default: 300)
|
|
442
|
+
|
|
443
|
+
Example - Nginx Proxy (Recommended):
|
|
444
|
+
export DOCKER_USE_NGINX_PROXY=true
|
|
445
|
+
export DOCKER_NGINX_PROXY_URL="http://localhost/api/docker"
|
|
446
|
+
export DOCKER_REGISTRY_URL="env.blackant.app"
|
|
447
|
+
export DOCKER_REGISTRY_NAMESPACE="systemdevelopers"
|
|
448
|
+
|
|
449
|
+
Example - Direct TLS Connection:
|
|
450
|
+
export DOCKER_USE_NGINX_PROXY=false
|
|
451
|
+
export DOCKER_DAEMON_HOST="docker.blackant.app"
|
|
452
|
+
export DOCKER_DAEMON_PORT="2376"
|
|
453
|
+
export DOCKER_USE_TLS=true
|
|
454
|
+
export DOCKER_CA_CERT="/etc/blackant/certs/ca.pem"
|
|
455
|
+
export DOCKER_CLIENT_CERT="/etc/blackant/certs/client-cert.pem"
|
|
456
|
+
export DOCKER_CLIENT_KEY="/etc/blackant/certs/client-key.pem"
|
|
457
|
+
"""
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Keycloak Admin Configuration
|
|
2
|
+
|
|
3
|
+
Configuration for Keycloak service account used for role management.
|
|
4
|
+
|
|
5
|
+
Author: Balázs Milán (milan.balazs@uni-obuda.hu)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class KeycloakAdminConfig:
|
|
15
|
+
"""Configuration for Keycloak Admin API.
|
|
16
|
+
|
|
17
|
+
Service account credentials for role management operations.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
server_url: Keycloak server URL (e.g., "https://keycloak.company.com/auth")
|
|
21
|
+
realm_name: Realm name (e.g., "blackant")
|
|
22
|
+
client_id: Service account client ID
|
|
23
|
+
client_secret: Service account client secret
|
|
24
|
+
verify_ssl: Whether to verify SSL certificates
|
|
25
|
+
timeout: Request timeout in seconds
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
server_url: str
|
|
29
|
+
realm_name: str
|
|
30
|
+
client_id: str
|
|
31
|
+
client_secret: str
|
|
32
|
+
verify_ssl: bool = True
|
|
33
|
+
timeout: int = 30
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_env(cls) -> 'KeycloakAdminConfig':
|
|
37
|
+
"""Create configuration from environment variables.
|
|
38
|
+
|
|
39
|
+
Required environment variables:
|
|
40
|
+
- KEYCLOAK_SERVER_URL: Keycloak server URL
|
|
41
|
+
- KEYCLOAK_REALM: Realm name
|
|
42
|
+
- KEYCLOAK_ADMIN_CLIENT_ID: Service account client ID
|
|
43
|
+
- KEYCLOAK_ADMIN_CLIENT_SECRET: Service account client secret
|
|
44
|
+
|
|
45
|
+
Optional:
|
|
46
|
+
- KEYCLOAK_VERIFY_SSL: "true" or "false" (default: true)
|
|
47
|
+
- KEYCLOAK_TIMEOUT: Timeout in seconds (default: 30)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
KeycloakAdminConfig instance
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If required environment variables are missing
|
|
54
|
+
"""
|
|
55
|
+
server_url = os.getenv('KEYCLOAK_SERVER_URL')
|
|
56
|
+
realm_name = os.getenv('KEYCLOAK_REALM')
|
|
57
|
+
client_id = os.getenv('KEYCLOAK_ADMIN_CLIENT_ID')
|
|
58
|
+
client_secret = os.getenv('KEYCLOAK_ADMIN_CLIENT_SECRET')
|
|
59
|
+
|
|
60
|
+
# Validate required vars
|
|
61
|
+
missing = []
|
|
62
|
+
if not server_url:
|
|
63
|
+
missing.append('KEYCLOAK_SERVER_URL')
|
|
64
|
+
if not realm_name:
|
|
65
|
+
missing.append('KEYCLOAK_REALM')
|
|
66
|
+
if not client_id:
|
|
67
|
+
missing.append('KEYCLOAK_ADMIN_CLIENT_ID')
|
|
68
|
+
if not client_secret:
|
|
69
|
+
missing.append('KEYCLOAK_ADMIN_CLIENT_SECRET')
|
|
70
|
+
|
|
71
|
+
if missing:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Missing required environment variables: {', '.join(missing)}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Optional vars
|
|
77
|
+
verify_ssl = os.getenv('KEYCLOAK_VERIFY_SSL', 'true').lower() == 'true'
|
|
78
|
+
timeout = int(os.getenv('KEYCLOAK_TIMEOUT', '30'))
|
|
79
|
+
|
|
80
|
+
return cls(
|
|
81
|
+
server_url=server_url,
|
|
82
|
+
realm_name=realm_name,
|
|
83
|
+
client_id=client_id,
|
|
84
|
+
client_secret=client_secret,
|
|
85
|
+
verify_ssl=verify_ssl,
|
|
86
|
+
timeout=timeout
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def validate(self) -> None:
|
|
90
|
+
"""Validate configuration values.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If configuration is invalid
|
|
94
|
+
"""
|
|
95
|
+
if not self.server_url.startswith(('http://', 'https://')):
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Invalid server URL: {self.server_url} "
|
|
98
|
+
"(must start with http:// or https://)"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if len(self.client_secret) < 16:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"Client secret must be at least 16 characters long"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if self.timeout < 1 or self.timeout > 300:
|
|
107
|
+
raise ValueError("Timeout must be between 1 and 300 seconds")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# src/blackant/docker/__init__.py
|
|
2
|
+
"""Docker integration module for BlackAnt SDK."""
|
|
3
|
+
|
|
4
|
+
from .client import BlackAntDockerClient
|
|
5
|
+
from .dao import (
|
|
6
|
+
DockerDAO, DockerConnectionError, ImageConfig, ResourceConfig, ServiceConfig
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"BlackAntDockerClient", "DockerDAO", "DockerConnectionError",
|
|
11
|
+
"ImageConfig", "ResourceConfig", "ServiceConfig"
|
|
12
|
+
]
|