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,983 @@
|
|
|
1
|
+
"""BlackAnt Docker Client module.
|
|
2
|
+
|
|
3
|
+
High-level Docker client providing user-friendly API for Docker operations
|
|
4
|
+
through BlackAnt platform with automatic authentication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
from typing import Dict, Any, Optional, List
|
|
10
|
+
|
|
11
|
+
from ..auth.blackant_auth import BlackAntAuth
|
|
12
|
+
from ..auth.role_assignment import get_role_assignment_manager
|
|
13
|
+
from ..http.client import HTTPClient, HTTPConnectionError
|
|
14
|
+
from ..exceptions import BlackAntDockerError
|
|
15
|
+
from ..utils.logging import get_logger
|
|
16
|
+
from .builder import DockerImageBuilder
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BlackAntDockerClient:
|
|
20
|
+
"""Docker client with BlackAnt authentication integration.
|
|
21
|
+
|
|
22
|
+
Provides high-level Docker operations through BlackAnt's HTTP API
|
|
23
|
+
with automatic Bearer token authentication. All requests are routed
|
|
24
|
+
through Nginx proxy which validates tokens with Keycloak.
|
|
25
|
+
|
|
26
|
+
This client uses the existing HTTPClient with persistent sessions,
|
|
27
|
+
connection pooling, and request ID tracking for efficient and
|
|
28
|
+
traceable API communication.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
auth (BlackAntAuth): Authentication object with credentials.
|
|
32
|
+
base_url (str, optional): Base URL for BlackAnt API.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
>>> auth = BlackAntAuth(user="my_name", password="xxx")
|
|
36
|
+
>>> client = BlackAntDockerClient(auth=auth)
|
|
37
|
+
>>> print(client.version) # "18.9.3"
|
|
38
|
+
>>> containers = client.containers()
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def \
|
|
42
|
+
__init__(self, auth: BlackAntAuth, base_url: Optional[str] = None):
|
|
43
|
+
"""Initialize Docker client with authentication.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
auth: BlackAntAuth object with user credentials.
|
|
47
|
+
base_url: Optional base URL, defaults to environment variable.
|
|
48
|
+
"""
|
|
49
|
+
self.auth = auth
|
|
50
|
+
self.base_url = base_url or os.getenv(
|
|
51
|
+
"BLACKANT_BASE_URL",
|
|
52
|
+
"https://dev.blackant.app"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Initialize HTTPClient with persistent session and connection pooling
|
|
56
|
+
self.http_client = HTTPClient(
|
|
57
|
+
base_url=self.base_url,
|
|
58
|
+
auth_token_store=auth.token_store # Share token store with auth
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.logger = get_logger("docker.client")
|
|
62
|
+
|
|
63
|
+
# Initialize Docker image builder with auth for remote daemon
|
|
64
|
+
self.image_builder = DockerImageBuilder(auth=self.auth)
|
|
65
|
+
|
|
66
|
+
# Ensure authentication on initialization
|
|
67
|
+
self.auth.get_token()
|
|
68
|
+
self.logger.info("BlackAntDockerClient initialized")
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def version(self) -> str:
|
|
72
|
+
"""Get Docker daemon version.
|
|
73
|
+
|
|
74
|
+
Returns the Docker daemon version string through BlackAnt API.
|
|
75
|
+
This provides the Docker daemon version information through BlackAnt API.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
str: Docker version string (e.g., "18.9.3").
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
BlackAntDockerError: If version fetch fails.
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
response = self.http_client.send_request(
|
|
85
|
+
endpoint="/api/docker/version",
|
|
86
|
+
method="GET"
|
|
87
|
+
)
|
|
88
|
+
response.raise_for_status()
|
|
89
|
+
|
|
90
|
+
data = response.json()
|
|
91
|
+
version = data.get("version", data.get("Version", "unknown"))
|
|
92
|
+
|
|
93
|
+
self.logger.debug(f"Docker version: {version}")
|
|
94
|
+
return version
|
|
95
|
+
|
|
96
|
+
except HTTPConnectionError as error:
|
|
97
|
+
self.logger.error(f"Failed to fetch Docker version: {error}")
|
|
98
|
+
raise BlackAntDockerError(f"Could not fetch Docker version: {error}") from error
|
|
99
|
+
except Exception as error:
|
|
100
|
+
self.logger.error(f"Unexpected error fetching version: {error}")
|
|
101
|
+
raise BlackAntDockerError(f"Version fetch failed: {error}") from error
|
|
102
|
+
|
|
103
|
+
def containers(self, all_containers: bool = False) -> List[Dict[str, Any]]:
|
|
104
|
+
"""List Docker containers.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
all_containers: If True, show all containers. If False, only running.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
List of container dictionaries with details.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
BlackAntDockerError: If container list fails.
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
params = {"all": str(all_containers).lower()} if all_containers else {}
|
|
117
|
+
|
|
118
|
+
response = self.http_client.send_request(
|
|
119
|
+
endpoint="/api/docker/v1.24/containers/json",
|
|
120
|
+
method="GET",
|
|
121
|
+
json=params if params else None
|
|
122
|
+
)
|
|
123
|
+
response.raise_for_status()
|
|
124
|
+
|
|
125
|
+
containers = response.json()
|
|
126
|
+
self.logger.debug(f"Found {len(containers)} containers")
|
|
127
|
+
return containers
|
|
128
|
+
|
|
129
|
+
except HTTPConnectionError as error:
|
|
130
|
+
self.logger.error(f"Failed to list containers: {error}")
|
|
131
|
+
raise BlackAntDockerError(f"Could not list containers: {error}") from error
|
|
132
|
+
except Exception as error:
|
|
133
|
+
self.logger.error(f"Unexpected error listing containers: {error}")
|
|
134
|
+
raise BlackAntDockerError(f"Container list failed: {error}") from error
|
|
135
|
+
|
|
136
|
+
def images(self) -> List[Dict[str, Any]]:
|
|
137
|
+
"""List Docker images.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
List of image dictionaries with details.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
BlackAntDockerError: If image list fails.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
response = self.http_client.send_request(
|
|
147
|
+
endpoint="/api/docker/v1.24/images/json",
|
|
148
|
+
method="GET"
|
|
149
|
+
)
|
|
150
|
+
response.raise_for_status()
|
|
151
|
+
|
|
152
|
+
images = response.json()
|
|
153
|
+
self.logger.debug(f"Found {len(images)} images")
|
|
154
|
+
return images
|
|
155
|
+
|
|
156
|
+
except HTTPConnectionError as error:
|
|
157
|
+
self.logger.error(f"Failed to list images: {error}")
|
|
158
|
+
raise BlackAntDockerError(f"Could not list images: {error}") from error
|
|
159
|
+
except Exception as error:
|
|
160
|
+
self.logger.error(f"Unexpected error listing images: {error}")
|
|
161
|
+
raise BlackAntDockerError(f"Image list failed: {error}") from error
|
|
162
|
+
|
|
163
|
+
def services(self) -> List[Dict[str, Any]]:
|
|
164
|
+
"""List Docker services (Swarm mode).
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
List of service dictionaries with details.
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
BlackAntDockerError: If service list fails.
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
response = self.http_client.send_request(
|
|
174
|
+
endpoint="/api/docker/services",
|
|
175
|
+
method="GET"
|
|
176
|
+
)
|
|
177
|
+
response.raise_for_status()
|
|
178
|
+
|
|
179
|
+
services = response.json()
|
|
180
|
+
self.logger.debug(f"Found {len(services)} services")
|
|
181
|
+
return services
|
|
182
|
+
|
|
183
|
+
except HTTPConnectionError as error:
|
|
184
|
+
self.logger.error(f"Failed to list services: {error}")
|
|
185
|
+
raise BlackAntDockerError(f"Could not list services: {error}") from error
|
|
186
|
+
except Exception as error:
|
|
187
|
+
self.logger.error(f"Unexpected error listing services: {error}")
|
|
188
|
+
raise BlackAntDockerError(f"Service list failed: {error}") from error
|
|
189
|
+
|
|
190
|
+
def info(self) -> Dict[str, Any]:
|
|
191
|
+
"""Get Docker daemon system information.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Dictionary with Docker daemon info.
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
BlackAntDockerError: If info fetch fails.
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
response = self.http_client.send_request(
|
|
201
|
+
endpoint="/api/docker/info",
|
|
202
|
+
method="GET"
|
|
203
|
+
)
|
|
204
|
+
response.raise_for_status()
|
|
205
|
+
|
|
206
|
+
info = response.json()
|
|
207
|
+
self.logger.debug("Fetched Docker daemon info")
|
|
208
|
+
return info
|
|
209
|
+
|
|
210
|
+
except HTTPConnectionError as error:
|
|
211
|
+
self.logger.error(f"Failed to fetch Docker info: {error}")
|
|
212
|
+
raise BlackAntDockerError(f"Could not fetch Docker info: {error}") from error
|
|
213
|
+
except Exception as error:
|
|
214
|
+
self.logger.error(f"Unexpected error fetching info: {error}")
|
|
215
|
+
raise BlackAntDockerError(f"Info fetch failed: {error}") from error
|
|
216
|
+
|
|
217
|
+
def ping(self) -> bool:
|
|
218
|
+
"""Ping Docker daemon to check connectivity.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
bool: True if Docker daemon is accessible.
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
response = self.http_client.send_request(
|
|
225
|
+
endpoint="/api/docker/v1.24/_ping",
|
|
226
|
+
method="GET"
|
|
227
|
+
)
|
|
228
|
+
return response.status_code == 200
|
|
229
|
+
|
|
230
|
+
except (ConnectionError, TimeoutError, ValueError):
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
def build_image(self, dockerfile_path: str, tag: str, context_path: str = ".",
|
|
234
|
+
build_args: Optional[Dict[str, str]] = None,
|
|
235
|
+
stream_logs: bool = True) -> Dict[str, Any]:
|
|
236
|
+
"""Build Docker image from Dockerfile with streaming logs.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
dockerfile_path: Path to Dockerfile relative to context.
|
|
240
|
+
tag: Image tag (e.g., "myapp:latest").
|
|
241
|
+
context_path: Build context directory path.
|
|
242
|
+
build_args: Build arguments dict.
|
|
243
|
+
stream_logs: Whether to stream build logs.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Build result with logs and image ID.
|
|
247
|
+
|
|
248
|
+
Raises:
|
|
249
|
+
BlackAntDockerError: If build fails.
|
|
250
|
+
"""
|
|
251
|
+
try:
|
|
252
|
+
import tarfile
|
|
253
|
+
import io
|
|
254
|
+
import base64
|
|
255
|
+
|
|
256
|
+
# Create tar archive from context
|
|
257
|
+
tar_buffer = io.BytesIO()
|
|
258
|
+
with tarfile.open(fileobj=tar_buffer, mode='w:gz') as tar:
|
|
259
|
+
tar.add(context_path, arcname='.')
|
|
260
|
+
|
|
261
|
+
tar_data = base64.b64encode(tar_buffer.getvalue()).decode()
|
|
262
|
+
|
|
263
|
+
build_data = {
|
|
264
|
+
"dockerfile": dockerfile_path,
|
|
265
|
+
"t": tag,
|
|
266
|
+
"buildargs": build_args or {},
|
|
267
|
+
"context": tar_data
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
response = self.http_client.send_request(
|
|
271
|
+
endpoint="/api/docker/v1.24/build",
|
|
272
|
+
method="POST",
|
|
273
|
+
json=build_data,
|
|
274
|
+
req_timeout=300.0
|
|
275
|
+
)
|
|
276
|
+
response.raise_for_status()
|
|
277
|
+
|
|
278
|
+
result = {"logs": [], "image_id": None, "success": False}
|
|
279
|
+
|
|
280
|
+
if stream_logs:
|
|
281
|
+
# Parse streaming response
|
|
282
|
+
for line in response.iter_lines():
|
|
283
|
+
if line:
|
|
284
|
+
try:
|
|
285
|
+
log_entry = line.decode().strip()
|
|
286
|
+
result["logs"].append(log_entry)
|
|
287
|
+
self.logger.info(f"Build: {log_entry}")
|
|
288
|
+
except Exception:
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
build_response = response.json() if hasattr(response, 'json') else {}
|
|
292
|
+
result["image_id"] = build_response.get("Id", tag)
|
|
293
|
+
result["success"] = True
|
|
294
|
+
|
|
295
|
+
self.logger.info(f"Image built successfully: {tag}")
|
|
296
|
+
return result
|
|
297
|
+
|
|
298
|
+
except HTTPConnectionError as error:
|
|
299
|
+
self.logger.error(f"Failed to build image: {error}")
|
|
300
|
+
raise BlackAntDockerError(f"Could not build image: {error}") from error
|
|
301
|
+
except Exception as error:
|
|
302
|
+
self.logger.error(f"Unexpected error building image: {error}")
|
|
303
|
+
raise BlackAntDockerError(f"Image build failed: {error}") from error
|
|
304
|
+
|
|
305
|
+
def create_container(self, image: str, name: Optional[str] = None,
|
|
306
|
+
command: Optional[List[str]] = None,
|
|
307
|
+
environment: Optional[Dict[str, str]] = None,
|
|
308
|
+
ports: Optional[Dict[str, int]] = None,
|
|
309
|
+
volumes: Optional[Dict[str, str]] = None) -> str:
|
|
310
|
+
"""Create a new container.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
image: Docker image name/tag.
|
|
314
|
+
name: Container name (optional).
|
|
315
|
+
command: Command to run in container.
|
|
316
|
+
environment: Environment variables.
|
|
317
|
+
ports: Port mappings {container_port: host_port}.
|
|
318
|
+
volumes: Volume mappings {host_path: container_path}.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Container ID.
|
|
322
|
+
|
|
323
|
+
Raises:
|
|
324
|
+
BlackAntDockerError: If container creation fails.
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
create_data = {
|
|
328
|
+
"Image": image,
|
|
329
|
+
"Cmd": command or [],
|
|
330
|
+
"Env": [f"{k}={v}" for k, v in (environment or {}).items()],
|
|
331
|
+
"ExposedPorts": {f"{port}/tcp": {} for port in (ports or {}).keys()},
|
|
332
|
+
"HostConfig": {
|
|
333
|
+
"PortBindings": {
|
|
334
|
+
f"{container_port}/tcp": [{"HostPort": str(host_port)}]
|
|
335
|
+
for container_port, host_port in (ports or {}).items()
|
|
336
|
+
},
|
|
337
|
+
"Binds": [f"{host}:{container}" for host, container in (volumes or {}).items()]
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if name:
|
|
342
|
+
create_data["Name"] = name
|
|
343
|
+
|
|
344
|
+
response = self.http_client.send_request(
|
|
345
|
+
endpoint="/api/docker/v1.24/containers/create",
|
|
346
|
+
method="POST",
|
|
347
|
+
json=create_data
|
|
348
|
+
)
|
|
349
|
+
response.raise_for_status()
|
|
350
|
+
|
|
351
|
+
result = response.json()
|
|
352
|
+
container_id = result.get("Id")
|
|
353
|
+
|
|
354
|
+
self.logger.info(f"Container created: {container_id}")
|
|
355
|
+
return container_id
|
|
356
|
+
|
|
357
|
+
except HTTPConnectionError as error:
|
|
358
|
+
self.logger.error(f"Failed to create container: {error}")
|
|
359
|
+
raise BlackAntDockerError(f"Could not create container: {error}") from error
|
|
360
|
+
except Exception as error:
|
|
361
|
+
self.logger.error(f"Unexpected error creating container: {error}")
|
|
362
|
+
raise BlackAntDockerError(f"Container creation failed: {error}") from error
|
|
363
|
+
|
|
364
|
+
def start_container(self, container_id: str) -> bool:
|
|
365
|
+
"""Start a container.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
container_id: Container ID or name.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
True if started successfully.
|
|
372
|
+
|
|
373
|
+
Raises:
|
|
374
|
+
BlackAntDockerError: If start fails.
|
|
375
|
+
"""
|
|
376
|
+
try:
|
|
377
|
+
response = self.http_client.send_request(
|
|
378
|
+
endpoint=f"/api/docker/v1.24/containers/{container_id}/start",
|
|
379
|
+
method="POST"
|
|
380
|
+
)
|
|
381
|
+
response.raise_for_status()
|
|
382
|
+
|
|
383
|
+
self.logger.info(f"Container started: {container_id}")
|
|
384
|
+
return True
|
|
385
|
+
|
|
386
|
+
except HTTPConnectionError as error:
|
|
387
|
+
self.logger.error(f"Failed to start container: {error}")
|
|
388
|
+
raise BlackAntDockerError(f"Could not start container: {error}") from error
|
|
389
|
+
except Exception as error:
|
|
390
|
+
self.logger.error(f"Unexpected error starting container: {error}")
|
|
391
|
+
raise BlackAntDockerError(f"Container start failed: {error}") from error
|
|
392
|
+
|
|
393
|
+
def stop_container(self, container_id: str, timeout: int = 10) -> bool:
|
|
394
|
+
"""Stop a container.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
container_id: Container ID or name.
|
|
398
|
+
timeout: Seconds to wait before killing.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
True if stopped successfully.
|
|
402
|
+
|
|
403
|
+
Raises:
|
|
404
|
+
BlackAntDockerError: If stop fails.
|
|
405
|
+
"""
|
|
406
|
+
try:
|
|
407
|
+
response = self.http_client.send_request(
|
|
408
|
+
endpoint=f"/api/docker/v1.24/containers/{container_id}/stop?t={timeout}",
|
|
409
|
+
method="POST"
|
|
410
|
+
)
|
|
411
|
+
response.raise_for_status()
|
|
412
|
+
|
|
413
|
+
self.logger.info(f"Container stopped: {container_id}")
|
|
414
|
+
return True
|
|
415
|
+
|
|
416
|
+
except HTTPConnectionError as error:
|
|
417
|
+
self.logger.error(f"Failed to stop container: {error}")
|
|
418
|
+
raise BlackAntDockerError(f"Could not stop container: {error}") from error
|
|
419
|
+
except Exception as error:
|
|
420
|
+
self.logger.error(f"Unexpected error stopping container: {error}")
|
|
421
|
+
raise BlackAntDockerError(f"Container stop failed: {error}") from error
|
|
422
|
+
|
|
423
|
+
def remove_container(self, container_id: str, force: bool = False) -> bool:
|
|
424
|
+
"""Remove a container.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
container_id: Container ID or name.
|
|
428
|
+
force: Force removal of running container.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
True if removed successfully.
|
|
432
|
+
|
|
433
|
+
Raises:
|
|
434
|
+
BlackAntDockerError: If removal fails.
|
|
435
|
+
"""
|
|
436
|
+
try:
|
|
437
|
+
params = "?force=true" if force else ""
|
|
438
|
+
response = self.http_client.send_request(
|
|
439
|
+
endpoint=f"/api/docker/v1.24/containers/{container_id}{params}",
|
|
440
|
+
method="DELETE"
|
|
441
|
+
)
|
|
442
|
+
response.raise_for_status()
|
|
443
|
+
|
|
444
|
+
self.logger.info(f"Container removed: {container_id}")
|
|
445
|
+
return True
|
|
446
|
+
|
|
447
|
+
except HTTPConnectionError as error:
|
|
448
|
+
self.logger.error(f"Failed to remove container: {error}")
|
|
449
|
+
raise BlackAntDockerError(f"Could not remove container: {error}") from error
|
|
450
|
+
except Exception as error:
|
|
451
|
+
self.logger.error(f"Unexpected error removing container: {error}")
|
|
452
|
+
raise BlackAntDockerError(f"Container removal failed: {error}") from error
|
|
453
|
+
|
|
454
|
+
def pull_image(self, image: str, tag: str = "latest") -> Dict[str, Any]:
|
|
455
|
+
"""Pull Docker image from registry.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
image: Image name (e.g., "nginx").
|
|
459
|
+
tag: Image tag.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Pull result with status.
|
|
463
|
+
|
|
464
|
+
Raises:
|
|
465
|
+
BlackAntDockerError: If pull fails.
|
|
466
|
+
"""
|
|
467
|
+
try:
|
|
468
|
+
full_image = f"{image}:{tag}"
|
|
469
|
+
response = self.http_client.send_request(
|
|
470
|
+
endpoint=f"/api/docker/v1.24/images/create?fromImage={image}&tag={tag}",
|
|
471
|
+
method="POST",
|
|
472
|
+
req_timeout=300.0
|
|
473
|
+
)
|
|
474
|
+
response.raise_for_status()
|
|
475
|
+
|
|
476
|
+
self.logger.info(f"Image pulled: {full_image}")
|
|
477
|
+
return {"image": full_image, "success": True}
|
|
478
|
+
|
|
479
|
+
except HTTPConnectionError as error:
|
|
480
|
+
self.logger.error(f"Failed to pull image: {error}")
|
|
481
|
+
raise BlackAntDockerError(f"Could not pull image: {error}") from error
|
|
482
|
+
except Exception as error:
|
|
483
|
+
self.logger.error(f"Unexpected error pulling image: {error}")
|
|
484
|
+
raise BlackAntDockerError(f"Image pull failed: {error}") from error
|
|
485
|
+
|
|
486
|
+
def push_image(self, image: str, tag: str = "latest") -> Dict[str, Any]:
|
|
487
|
+
"""Push Docker image to registry.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
image: Image name.
|
|
491
|
+
tag: Image tag.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
Push result with status.
|
|
495
|
+
|
|
496
|
+
Raises:
|
|
497
|
+
BlackAntDockerError: If push fails.
|
|
498
|
+
"""
|
|
499
|
+
try:
|
|
500
|
+
full_image = f"{image}:{tag}"
|
|
501
|
+
response = self.http_client.send_request(
|
|
502
|
+
endpoint=f"/api/docker/v1.24/images/{full_image}/push",
|
|
503
|
+
method="POST",
|
|
504
|
+
req_timeout=300.0
|
|
505
|
+
)
|
|
506
|
+
response.raise_for_status()
|
|
507
|
+
|
|
508
|
+
self.logger.info(f"Image pushed: {full_image}")
|
|
509
|
+
return {"image": full_image, "success": True}
|
|
510
|
+
|
|
511
|
+
except HTTPConnectionError as error:
|
|
512
|
+
self.logger.error(f"Failed to push image: {error}")
|
|
513
|
+
raise BlackAntDockerError(f"Could not push image: {error}") from error
|
|
514
|
+
except Exception as error:
|
|
515
|
+
self.logger.error(f"Unexpected error pushing image: {error}")
|
|
516
|
+
raise BlackAntDockerError(f"Image push failed: {error}") from error
|
|
517
|
+
|
|
518
|
+
def inspect_image(self, image: str) -> Dict[str, Any]:
|
|
519
|
+
"""Inspect Docker image details.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
image: Image name/ID.
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
Image inspection data.
|
|
526
|
+
|
|
527
|
+
Raises:
|
|
528
|
+
BlackAntDockerError: If inspection fails.
|
|
529
|
+
"""
|
|
530
|
+
try:
|
|
531
|
+
response = self.http_client.send_request(
|
|
532
|
+
endpoint=f"/api/docker/v1.24/images/{image}/json",
|
|
533
|
+
method="GET"
|
|
534
|
+
)
|
|
535
|
+
response.raise_for_status()
|
|
536
|
+
|
|
537
|
+
image_data = response.json()
|
|
538
|
+
self.logger.debug(f"Image inspected: {image}")
|
|
539
|
+
return image_data
|
|
540
|
+
|
|
541
|
+
except HTTPConnectionError as error:
|
|
542
|
+
self.logger.error(f"Failed to inspect image: {error}")
|
|
543
|
+
raise BlackAntDockerError(f"Could not inspect image: {error}") from error
|
|
544
|
+
except Exception as error:
|
|
545
|
+
self.logger.error(f"Unexpected error inspecting image: {error}")
|
|
546
|
+
raise BlackAntDockerError(f"Image inspection failed: {error}") from error
|
|
547
|
+
|
|
548
|
+
def get_container_logs(self, container_id: str, follow: bool = False,
|
|
549
|
+
tail: int = 100) -> str:
|
|
550
|
+
"""Get container logs.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
container_id: Container ID or name.
|
|
554
|
+
follow: Follow log output.
|
|
555
|
+
tail: Number of lines from end of logs.
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
Container logs as string.
|
|
559
|
+
|
|
560
|
+
Raises:
|
|
561
|
+
BlackAntDockerError: If getting logs fails.
|
|
562
|
+
"""
|
|
563
|
+
try:
|
|
564
|
+
params = f"?stdout=true&stderr=true&tail={tail}"
|
|
565
|
+
if follow:
|
|
566
|
+
params += "&follow=true"
|
|
567
|
+
|
|
568
|
+
response = self.http_client.send_request(
|
|
569
|
+
endpoint=f"/api/docker/v1.24/containers/{container_id}/logs{params}",
|
|
570
|
+
method="GET"
|
|
571
|
+
)
|
|
572
|
+
response.raise_for_status()
|
|
573
|
+
|
|
574
|
+
logs = response.text
|
|
575
|
+
self.logger.debug(f"Retrieved logs for container: {container_id}")
|
|
576
|
+
return logs
|
|
577
|
+
|
|
578
|
+
except HTTPConnectionError as error:
|
|
579
|
+
self.logger.error(f"Failed to get container logs: {error}")
|
|
580
|
+
raise BlackAntDockerError(f"Could not get logs: {error}") from error
|
|
581
|
+
except Exception as error:
|
|
582
|
+
self.logger.error(f"Unexpected error getting logs: {error}")
|
|
583
|
+
raise BlackAntDockerError(f"Get logs failed: {error}") from error
|
|
584
|
+
|
|
585
|
+
def get_events(self, since: Optional[str] = None, until: Optional[str] = None,
|
|
586
|
+
filters: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
|
587
|
+
"""Get Docker events.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
since: Timestamp to start from.
|
|
591
|
+
until: Timestamp to end at.
|
|
592
|
+
filters: Event filters (type, container, etc.).
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
List of Docker events.
|
|
596
|
+
|
|
597
|
+
Raises:
|
|
598
|
+
BlackAntDockerError: If getting events fails.
|
|
599
|
+
"""
|
|
600
|
+
try:
|
|
601
|
+
params = []
|
|
602
|
+
if since:
|
|
603
|
+
params.append(f"since={since}")
|
|
604
|
+
if until:
|
|
605
|
+
params.append(f"until={until}")
|
|
606
|
+
if filters:
|
|
607
|
+
filters_json = json.dumps(filters)
|
|
608
|
+
params.append(f"filters={filters_json}")
|
|
609
|
+
|
|
610
|
+
query_string = "&".join(params)
|
|
611
|
+
endpoint = "/api/docker/v1.24/events"
|
|
612
|
+
if query_string:
|
|
613
|
+
endpoint += f"?{query_string}"
|
|
614
|
+
|
|
615
|
+
response = self.http_client.send_request(
|
|
616
|
+
endpoint=endpoint,
|
|
617
|
+
method="GET",
|
|
618
|
+
req_timeout=30.0
|
|
619
|
+
)
|
|
620
|
+
response.raise_for_status()
|
|
621
|
+
|
|
622
|
+
# Parse JSONL response (one JSON object per line)
|
|
623
|
+
events = []
|
|
624
|
+
for line in response.text.split('\n'):
|
|
625
|
+
if line.strip():
|
|
626
|
+
try:
|
|
627
|
+
event = json.loads(line)
|
|
628
|
+
events.append(event)
|
|
629
|
+
except json.JSONDecodeError:
|
|
630
|
+
pass
|
|
631
|
+
|
|
632
|
+
self.logger.debug(f"Retrieved {len(events)} Docker events")
|
|
633
|
+
return events
|
|
634
|
+
|
|
635
|
+
except HTTPConnectionError as error:
|
|
636
|
+
self.logger.error(f"Failed to get Docker events: {error}")
|
|
637
|
+
raise BlackAntDockerError(f"Could not get events: {error}") from error
|
|
638
|
+
except Exception as error:
|
|
639
|
+
self.logger.error(f"Unexpected error getting events: {error}")
|
|
640
|
+
raise BlackAntDockerError(f"Get events failed: {error}") from error
|
|
641
|
+
|
|
642
|
+
def get_system_df(self) -> Dict[str, Any]:
|
|
643
|
+
"""Get Docker system disk usage information.
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
System disk usage data.
|
|
647
|
+
|
|
648
|
+
Raises:
|
|
649
|
+
BlackAntDockerError: If getting disk usage fails.
|
|
650
|
+
"""
|
|
651
|
+
try:
|
|
652
|
+
response = self.http_client.send_request(
|
|
653
|
+
endpoint="/api/docker/v1.24/system/df",
|
|
654
|
+
method="GET"
|
|
655
|
+
)
|
|
656
|
+
response.raise_for_status()
|
|
657
|
+
|
|
658
|
+
df_data = response.json()
|
|
659
|
+
self.logger.debug("Retrieved Docker disk usage")
|
|
660
|
+
return df_data
|
|
661
|
+
|
|
662
|
+
except HTTPConnectionError as error:
|
|
663
|
+
self.logger.error(f"Failed to get disk usage: {error}")
|
|
664
|
+
raise BlackAntDockerError(f"Could not get disk usage: {error}") from error
|
|
665
|
+
except Exception as error:
|
|
666
|
+
self.logger.error(f"Unexpected error getting disk usage: {error}")
|
|
667
|
+
raise BlackAntDockerError(f"Get disk usage failed: {error}") from error
|
|
668
|
+
|
|
669
|
+
def inspect_container(self, container_id: str) -> Dict[str, Any]:
|
|
670
|
+
"""Inspect container details.
|
|
671
|
+
|
|
672
|
+
Args:
|
|
673
|
+
container_id: Container ID or name.
|
|
674
|
+
|
|
675
|
+
Returns:
|
|
676
|
+
Container inspection data.
|
|
677
|
+
|
|
678
|
+
Raises:
|
|
679
|
+
BlackAntDockerError: If inspection fails.
|
|
680
|
+
"""
|
|
681
|
+
try:
|
|
682
|
+
response = self.http_client.send_request(
|
|
683
|
+
endpoint=f"/api/docker/v1.24/containers/{container_id}/json",
|
|
684
|
+
method="GET"
|
|
685
|
+
)
|
|
686
|
+
response.raise_for_status()
|
|
687
|
+
|
|
688
|
+
container_data = response.json()
|
|
689
|
+
self.logger.debug(f"Container inspected: {container_id}")
|
|
690
|
+
return container_data
|
|
691
|
+
|
|
692
|
+
except HTTPConnectionError as error:
|
|
693
|
+
self.logger.error(f"Failed to inspect container: {error}")
|
|
694
|
+
raise BlackAntDockerError(f"Could not inspect container: {error}") from error
|
|
695
|
+
except Exception as error:
|
|
696
|
+
self.logger.error(f"Unexpected error inspecting container: {error}")
|
|
697
|
+
raise BlackAntDockerError(f"Container inspection failed: {error}") from error
|
|
698
|
+
|
|
699
|
+
def get_container_stats(self, container_id: str, stream: bool = False) -> Dict[str, Any]:
|
|
700
|
+
"""Get container resource usage statistics.
|
|
701
|
+
|
|
702
|
+
Args:
|
|
703
|
+
container_id: Container ID or name.
|
|
704
|
+
stream: Whether to stream stats continuously.
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
Container resource statistics.
|
|
708
|
+
|
|
709
|
+
Raises:
|
|
710
|
+
BlackAntDockerError: If getting stats fails.
|
|
711
|
+
"""
|
|
712
|
+
try:
|
|
713
|
+
params = "?stream=false"
|
|
714
|
+
if stream:
|
|
715
|
+
params = "?stream=true"
|
|
716
|
+
|
|
717
|
+
response = self.http_client.send_request(
|
|
718
|
+
endpoint=f"/api/docker/v1.24/containers/{container_id}/stats{params}",
|
|
719
|
+
method="GET"
|
|
720
|
+
)
|
|
721
|
+
response.raise_for_status()
|
|
722
|
+
|
|
723
|
+
if stream:
|
|
724
|
+
# Return iterator for streaming stats
|
|
725
|
+
def stats_iterator():
|
|
726
|
+
for line in response.iter_lines():
|
|
727
|
+
if line:
|
|
728
|
+
try:
|
|
729
|
+
yield json.loads(line.decode())
|
|
730
|
+
except json.JSONDecodeError:
|
|
731
|
+
pass
|
|
732
|
+
return stats_iterator()
|
|
733
|
+
else:
|
|
734
|
+
stats = response.json()
|
|
735
|
+
self.logger.debug(f"Retrieved stats for container: {container_id}")
|
|
736
|
+
return stats
|
|
737
|
+
|
|
738
|
+
except HTTPConnectionError as error:
|
|
739
|
+
self.logger.error(f"Failed to get container stats: {error}")
|
|
740
|
+
raise BlackAntDockerError(f"Could not get stats: {error}") from error
|
|
741
|
+
except Exception as error:
|
|
742
|
+
self.logger.error(f"Unexpected error getting stats: {error}")
|
|
743
|
+
raise BlackAntDockerError(f"Get stats failed: {error}") from error
|
|
744
|
+
|
|
745
|
+
def prune_containers(self, filters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
746
|
+
"""Remove unused containers.
|
|
747
|
+
|
|
748
|
+
Args:
|
|
749
|
+
filters: Prune filters.
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
Prune results with space reclaimed.
|
|
753
|
+
|
|
754
|
+
Raises:
|
|
755
|
+
BlackAntDockerError: If pruning fails.
|
|
756
|
+
"""
|
|
757
|
+
try:
|
|
758
|
+
prune_data = {}
|
|
759
|
+
if filters:
|
|
760
|
+
prune_data["filters"] = filters
|
|
761
|
+
|
|
762
|
+
response = self.http_client.send_request(
|
|
763
|
+
endpoint="/api/docker/v1.24/containers/prune",
|
|
764
|
+
method="POST",
|
|
765
|
+
json=prune_data if prune_data else None
|
|
766
|
+
)
|
|
767
|
+
response.raise_for_status()
|
|
768
|
+
|
|
769
|
+
prune_result = response.json()
|
|
770
|
+
space_reclaimed = prune_result.get('SpaceReclaimed', 0)
|
|
771
|
+
self.logger.info(f"Containers pruned: {space_reclaimed} bytes reclaimed")
|
|
772
|
+
return prune_result
|
|
773
|
+
|
|
774
|
+
except HTTPConnectionError as error:
|
|
775
|
+
self.logger.error(f"Failed to prune containers: {error}")
|
|
776
|
+
raise BlackAntDockerError(f"Could not prune containers: {error}") from error
|
|
777
|
+
except Exception as error:
|
|
778
|
+
self.logger.error(f"Unexpected error pruning containers: {error}")
|
|
779
|
+
raise BlackAntDockerError(f"Container prune failed: {error}") from error
|
|
780
|
+
|
|
781
|
+
def prune_images(self, dangling_only: bool = True) -> Dict[str, Any]:
|
|
782
|
+
"""Remove unused images.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
dangling_only: Only remove dangling images.
|
|
786
|
+
|
|
787
|
+
Returns:
|
|
788
|
+
Prune results with space reclaimed.
|
|
789
|
+
|
|
790
|
+
Raises:
|
|
791
|
+
BlackAntDockerError: If pruning fails.
|
|
792
|
+
"""
|
|
793
|
+
try:
|
|
794
|
+
filters = {}
|
|
795
|
+
if dangling_only:
|
|
796
|
+
filters["dangling"] = ["true"]
|
|
797
|
+
|
|
798
|
+
prune_data = {"filters": filters} if filters else {}
|
|
799
|
+
|
|
800
|
+
response = self.http_client.send_request(
|
|
801
|
+
endpoint="/api/docker/v1.24/images/prune",
|
|
802
|
+
method="POST",
|
|
803
|
+
json=prune_data if prune_data else None
|
|
804
|
+
)
|
|
805
|
+
response.raise_for_status()
|
|
806
|
+
|
|
807
|
+
prune_result = response.json()
|
|
808
|
+
space_reclaimed = prune_result.get('SpaceReclaimed', 0)
|
|
809
|
+
self.logger.info(f"Images pruned: {space_reclaimed} bytes reclaimed")
|
|
810
|
+
return prune_result
|
|
811
|
+
|
|
812
|
+
except HTTPConnectionError as error:
|
|
813
|
+
self.logger.error(f"Failed to prune images: {error}")
|
|
814
|
+
raise BlackAntDockerError(f"Could not prune images: {error}") from error
|
|
815
|
+
except Exception as error:
|
|
816
|
+
self.logger.error(f"Unexpected error pruning images: {error}")
|
|
817
|
+
raise BlackAntDockerError(f"Image prune failed: {error}") from error
|
|
818
|
+
|
|
819
|
+
def build_service(self,
|
|
820
|
+
service_name: str,
|
|
821
|
+
impl_path: str = "src/calculation/impl",
|
|
822
|
+
dockerfile_path: str = "Dockerfile",
|
|
823
|
+
tag: Optional[str] = None,
|
|
824
|
+
push: bool = True,
|
|
825
|
+
register_service: bool = True) -> Dict[str, Any]:
|
|
826
|
+
"""Build és deploy service Docker image.
|
|
827
|
+
|
|
828
|
+
Ez az 5 core metódus egyike! Build Docker image a user implementation-ből,
|
|
829
|
+
upload registry-be, és regisztrálja a ServiceManager-ben.
|
|
830
|
+
|
|
831
|
+
Args:
|
|
832
|
+
service_name: Service név (alphanumeric + hyphens/underscores)
|
|
833
|
+
impl_path: User implementation mappa útvonala
|
|
834
|
+
dockerfile_path: Dockerfile útvonala (repository root-ból)
|
|
835
|
+
tag: Verzió tag (auto-generált ha None: v1.0.0-{timestamp})
|
|
836
|
+
push: Feltöltés env.blackant.app registry-be
|
|
837
|
+
register_service: Automatikus service regisztráció ServiceManager-ben
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
dict: Build és deployment információk
|
|
841
|
+
{
|
|
842
|
+
"image_name": "env.blackant.app/systemdevelopers/service_name",
|
|
843
|
+
"tag": "v1.0.0-20250105-123456",
|
|
844
|
+
"full_image": "env.blackant.app/systemdevelopers/service_name:v1.0.0-20250105-123456",
|
|
845
|
+
"image_id": "sha256:...",
|
|
846
|
+
"size": 125000000,
|
|
847
|
+
"pushed": True,
|
|
848
|
+
"registry_url": "env.blackant.app",
|
|
849
|
+
"service_id": "uuid-from-servicemanager",
|
|
850
|
+
"service_registered": True
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
Raises:
|
|
854
|
+
BlackAntDockerError: Build, push vagy regisztrációs hiba esetén
|
|
855
|
+
|
|
856
|
+
Example:
|
|
857
|
+
>>> auth = BlackAntAuth(user="developer", password="xxx")
|
|
858
|
+
>>> client = BlackAntDockerClient(auth=auth)
|
|
859
|
+
>>> result = client.build_service(
|
|
860
|
+
... service_name="my-calculation",
|
|
861
|
+
... impl_path="src/calculation/impl",
|
|
862
|
+
... tag="v1.0.0"
|
|
863
|
+
... )
|
|
864
|
+
>>> print(result["full_image"])
|
|
865
|
+
env.blackant.app/systemdevelopers/my-calculation:v1.0.0
|
|
866
|
+
>>> print(result["service_id"])
|
|
867
|
+
abc-123-def-456
|
|
868
|
+
"""
|
|
869
|
+
|
|
870
|
+
self.logger.info(f"Building service: {service_name}")
|
|
871
|
+
|
|
872
|
+
try:
|
|
873
|
+
# 1. Build Docker image local-ban
|
|
874
|
+
build_result = self.image_builder.build_service(
|
|
875
|
+
service_name=service_name,
|
|
876
|
+
impl_path=impl_path,
|
|
877
|
+
dockerfile_path=dockerfile_path,
|
|
878
|
+
tag=tag,
|
|
879
|
+
push_to_registry=push
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
self.logger.info(f"Image build completed: {build_result['full_image']}")
|
|
883
|
+
|
|
884
|
+
# 2. Role assignment after successful push (Balázs requirement)
|
|
885
|
+
role_assigned = False
|
|
886
|
+
if build_result.get("pushed", False):
|
|
887
|
+
try:
|
|
888
|
+
# Get user token for role assignment
|
|
889
|
+
user_token = self.auth.get_token() if self.auth else None
|
|
890
|
+
if user_token:
|
|
891
|
+
role_manager = get_role_assignment_manager()
|
|
892
|
+
role_assigned = role_manager.assign_role_after_publish(
|
|
893
|
+
user_token=user_token,
|
|
894
|
+
service_name=service_name,
|
|
895
|
+
metadata={
|
|
896
|
+
"image": build_result.get("full_image"),
|
|
897
|
+
"registry_url": build_result.get("registry_url")
|
|
898
|
+
}
|
|
899
|
+
)
|
|
900
|
+
if role_assigned:
|
|
901
|
+
self.logger.info(f"Role 'science_module' assigned after push for service: {service_name}")
|
|
902
|
+
else:
|
|
903
|
+
self.logger.warning(f"Role assignment failed for service: {service_name}")
|
|
904
|
+
else:
|
|
905
|
+
self.logger.warning("No user token available for role assignment")
|
|
906
|
+
except Exception as role_error:
|
|
907
|
+
self.logger.warning(f"Role assignment error (non-fatal): {role_error}")
|
|
908
|
+
# Continue - role assignment failure shouldn't block the build
|
|
909
|
+
|
|
910
|
+
# 3. Service registration preparation
|
|
911
|
+
service_registered = False
|
|
912
|
+
service_id = None
|
|
913
|
+
|
|
914
|
+
# 4. Service registration in ServiceManager (if requested and push successful)
|
|
915
|
+
if register_service and build_result.get("pushed", False):
|
|
916
|
+
try:
|
|
917
|
+
service_config = {
|
|
918
|
+
"name": service_name,
|
|
919
|
+
"type": "calculation",
|
|
920
|
+
"image": {
|
|
921
|
+
"container": "systemdevelopers", # Registry namespace fix
|
|
922
|
+
"name": service_name,
|
|
923
|
+
"tag": build_result["tag"]
|
|
924
|
+
},
|
|
925
|
+
"environments": {
|
|
926
|
+
"BLACKANT_ENV": "production",
|
|
927
|
+
"LOG_LEVEL": "INFO",
|
|
928
|
+
"SERVICE_NAME": service_name
|
|
929
|
+
},
|
|
930
|
+
"networks": ["blackant_network"],
|
|
931
|
+
"parent": None,
|
|
932
|
+
"resource_config": {
|
|
933
|
+
"use_gpu": False,
|
|
934
|
+
"cpu_limit": 1.0,
|
|
935
|
+
"ram_limit": "512M",
|
|
936
|
+
"disk_limit": "1G",
|
|
937
|
+
"node_label": None
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
self.logger.debug(f"Registering service with config: {service_config}")
|
|
942
|
+
|
|
943
|
+
# ServiceManager API hívás
|
|
944
|
+
response = self.http_client.send_request(
|
|
945
|
+
endpoint="/api/v1/services",
|
|
946
|
+
method="POST",
|
|
947
|
+
json=service_config
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
if response.status_code == 200 or response.status_code == 201:
|
|
951
|
+
response_data = response.json()
|
|
952
|
+
service_id = response_data.get("uuid") or response_data.get("id")
|
|
953
|
+
service_registered = True
|
|
954
|
+
self.logger.info(f"Service registered successfully: {service_name} -> {service_id}")
|
|
955
|
+
else:
|
|
956
|
+
self.logger.warning(f"Service registration failed: {response.status_code} - {response.text}")
|
|
957
|
+
|
|
958
|
+
except Exception as reg_error:
|
|
959
|
+
self.logger.warning(f"Service registration failed: {reg_error}")
|
|
960
|
+
# Folytatjuk annak ellenére, hogy a regisztráció nem sikerült
|
|
961
|
+
|
|
962
|
+
# 5. Collect complete result
|
|
963
|
+
result = {
|
|
964
|
+
**build_result, # All build results (image_name, tag, etc.)
|
|
965
|
+
"service_id": service_id,
|
|
966
|
+
"service_registered": service_registered,
|
|
967
|
+
"role_assigned": role_assigned,
|
|
968
|
+
"build_success": True
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
# 6. Success log
|
|
972
|
+
self.logger.info(f"Build service completed: {service_name}")
|
|
973
|
+
if role_assigned:
|
|
974
|
+
self.logger.info(f"Role 'science_module' assigned to user")
|
|
975
|
+
if service_registered:
|
|
976
|
+
self.logger.info(f"Service registered with ID: {service_id}")
|
|
977
|
+
|
|
978
|
+
return result
|
|
979
|
+
|
|
980
|
+
except Exception as e:
|
|
981
|
+
error_msg = f"Build service failed for {service_name}: {e}"
|
|
982
|
+
self.logger.error(error_msg)
|
|
983
|
+
raise BlackAntDockerError(error_msg) from e
|