pactown 0.1.4__py3-none-any.whl → 0.1.47__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.
- pactown/__init__.py +178 -4
- pactown/cli.py +539 -37
- pactown/config.py +12 -11
- pactown/deploy/__init__.py +17 -3
- pactown/deploy/base.py +35 -33
- pactown/deploy/compose.py +59 -58
- pactown/deploy/docker.py +40 -41
- pactown/deploy/kubernetes.py +43 -42
- pactown/deploy/podman.py +55 -56
- pactown/deploy/quadlet.py +1021 -0
- pactown/deploy/quadlet_api.py +533 -0
- pactown/deploy/quadlet_shell.py +557 -0
- pactown/events.py +1066 -0
- pactown/fast_start.py +514 -0
- pactown/generator.py +31 -30
- pactown/llm.py +450 -0
- pactown/markpact_blocks.py +50 -0
- pactown/network.py +59 -38
- pactown/orchestrator.py +90 -93
- pactown/parallel.py +40 -40
- pactown/platform.py +146 -0
- pactown/registry/__init__.py +1 -1
- pactown/registry/client.py +45 -46
- pactown/registry/models.py +25 -25
- pactown/registry/server.py +24 -24
- pactown/resolver.py +30 -30
- pactown/runner_api.py +458 -0
- pactown/sandbox_manager.py +480 -79
- pactown/security.py +682 -0
- pactown/service_runner.py +1201 -0
- pactown/user_isolation.py +458 -0
- {pactown-0.1.4.dist-info → pactown-0.1.47.dist-info}/METADATA +65 -9
- pactown-0.1.47.dist-info/RECORD +36 -0
- pactown-0.1.47.dist-info/entry_points.txt +5 -0
- pactown-0.1.4.dist-info/RECORD +0 -24
- pactown-0.1.4.dist-info/entry_points.txt +0 -3
- {pactown-0.1.4.dist-info → pactown-0.1.47.dist-info}/WHEEL +0 -0
- {pactown-0.1.4.dist-info → pactown-0.1.47.dist-info}/licenses/LICENSE +0 -0
pactown/deploy/podman.py
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import subprocess
|
|
6
5
|
import json
|
|
6
|
+
import subprocess
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
from .base import (
|
|
11
11
|
DeploymentBackend,
|
|
12
|
-
DeploymentConfig,
|
|
13
12
|
DeploymentResult,
|
|
14
13
|
RuntimeType,
|
|
15
14
|
)
|
|
@@ -18,10 +17,10 @@ from .base import (
|
|
|
18
17
|
class PodmanBackend(DeploymentBackend):
|
|
19
18
|
"""
|
|
20
19
|
Podman container runtime backend.
|
|
21
|
-
|
|
20
|
+
|
|
22
21
|
Podman is a daemonless, rootless container engine that is compatible
|
|
23
22
|
with Docker but provides better security for production environments.
|
|
24
|
-
|
|
23
|
+
|
|
25
24
|
Key advantages:
|
|
26
25
|
- Rootless by default (no root daemon)
|
|
27
26
|
- No daemon = no single point of failure
|
|
@@ -29,11 +28,11 @@ class PodmanBackend(DeploymentBackend):
|
|
|
29
28
|
- Systemd integration for service management
|
|
30
29
|
- Pod support (like Kubernetes pods)
|
|
31
30
|
"""
|
|
32
|
-
|
|
31
|
+
|
|
33
32
|
@property
|
|
34
33
|
def runtime_type(self) -> RuntimeType:
|
|
35
34
|
return RuntimeType.PODMAN
|
|
36
|
-
|
|
35
|
+
|
|
37
36
|
def is_available(self) -> bool:
|
|
38
37
|
"""Check if Podman is available."""
|
|
39
38
|
try:
|
|
@@ -46,7 +45,7 @@ class PodmanBackend(DeploymentBackend):
|
|
|
46
45
|
return result.returncode == 0
|
|
47
46
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
48
47
|
return False
|
|
49
|
-
|
|
48
|
+
|
|
50
49
|
def build_image(
|
|
51
50
|
self,
|
|
52
51
|
service_name: str,
|
|
@@ -60,23 +59,23 @@ class PodmanBackend(DeploymentBackend):
|
|
|
60
59
|
image_name = f"{image_name}:{tag}"
|
|
61
60
|
else:
|
|
62
61
|
image_name = f"{image_name}:latest"
|
|
63
|
-
|
|
62
|
+
|
|
64
63
|
cmd = [
|
|
65
64
|
"podman", "build",
|
|
66
65
|
"-t", image_name,
|
|
67
66
|
"-f", str(dockerfile_path),
|
|
68
67
|
]
|
|
69
|
-
|
|
68
|
+
|
|
70
69
|
# Add labels
|
|
71
70
|
for key, value in self.config.labels.items():
|
|
72
71
|
cmd.extend(["--label", f"{key}={value}"])
|
|
73
|
-
|
|
72
|
+
|
|
74
73
|
# Security options for build
|
|
75
74
|
if self.config.rootless:
|
|
76
75
|
cmd.extend(["--userns", "keep-id"])
|
|
77
|
-
|
|
76
|
+
|
|
78
77
|
cmd.append(str(context_path))
|
|
79
|
-
|
|
78
|
+
|
|
80
79
|
try:
|
|
81
80
|
result = subprocess.run(
|
|
82
81
|
cmd,
|
|
@@ -84,7 +83,7 @@ class PodmanBackend(DeploymentBackend):
|
|
|
84
83
|
text=True,
|
|
85
84
|
timeout=300,
|
|
86
85
|
)
|
|
87
|
-
|
|
86
|
+
|
|
88
87
|
if result.returncode == 0:
|
|
89
88
|
return DeploymentResult(
|
|
90
89
|
success=True,
|
|
@@ -106,7 +105,7 @@ class PodmanBackend(DeploymentBackend):
|
|
|
106
105
|
runtime=self.runtime_type,
|
|
107
106
|
error="Build timed out",
|
|
108
107
|
)
|
|
109
|
-
|
|
108
|
+
|
|
110
109
|
def push_image(
|
|
111
110
|
self,
|
|
112
111
|
image_name: str,
|
|
@@ -120,7 +119,7 @@ class PodmanBackend(DeploymentBackend):
|
|
|
120
119
|
["podman", "tag", image_name, target],
|
|
121
120
|
capture_output=True,
|
|
122
121
|
)
|
|
123
|
-
|
|
122
|
+
|
|
124
123
|
try:
|
|
125
124
|
result = subprocess.run(
|
|
126
125
|
["podman", "push", target],
|
|
@@ -128,7 +127,7 @@ class PodmanBackend(DeploymentBackend):
|
|
|
128
127
|
text=True,
|
|
129
128
|
timeout=300,
|
|
130
129
|
)
|
|
131
|
-
|
|
130
|
+
|
|
132
131
|
return DeploymentResult(
|
|
133
132
|
success=result.returncode == 0,
|
|
134
133
|
service_name=image_name.split("/")[-1].split(":")[0],
|
|
@@ -143,7 +142,7 @@ class PodmanBackend(DeploymentBackend):
|
|
|
143
142
|
runtime=self.runtime_type,
|
|
144
143
|
error="Push timed out",
|
|
145
144
|
)
|
|
146
|
-
|
|
145
|
+
|
|
147
146
|
def deploy(
|
|
148
147
|
self,
|
|
149
148
|
service_name: str,
|
|
@@ -154,57 +153,57 @@ class PodmanBackend(DeploymentBackend):
|
|
|
154
153
|
) -> DeploymentResult:
|
|
155
154
|
"""Deploy a container with Podman."""
|
|
156
155
|
container_name = f"{self.config.namespace}-{service_name}"
|
|
157
|
-
|
|
156
|
+
|
|
158
157
|
# Stop existing container if running
|
|
159
158
|
subprocess.run(
|
|
160
159
|
["podman", "rm", "-f", container_name],
|
|
161
160
|
capture_output=True,
|
|
162
161
|
)
|
|
163
|
-
|
|
162
|
+
|
|
164
163
|
cmd = [
|
|
165
164
|
"podman", "run",
|
|
166
165
|
"-d",
|
|
167
166
|
"--name", container_name,
|
|
168
167
|
"--network", self.config.network_name,
|
|
169
168
|
]
|
|
170
|
-
|
|
169
|
+
|
|
171
170
|
# Rootless mode with user namespace
|
|
172
171
|
if self.config.rootless:
|
|
173
172
|
cmd.extend(["--userns", "keep-id"])
|
|
174
|
-
|
|
173
|
+
|
|
175
174
|
# Port mapping
|
|
176
175
|
if self.config.expose_ports:
|
|
177
176
|
cmd.extend(["-p", f"{port}:{port}"])
|
|
178
|
-
|
|
177
|
+
|
|
179
178
|
# Environment variables
|
|
180
179
|
for key, value in env.items():
|
|
181
180
|
cmd.extend(["-e", f"{key}={value}"])
|
|
182
|
-
|
|
181
|
+
|
|
183
182
|
# Resource limits
|
|
184
183
|
if self.config.memory_limit:
|
|
185
184
|
cmd.extend(["--memory", self.config.memory_limit])
|
|
186
185
|
if self.config.cpu_limit:
|
|
187
186
|
cmd.extend(["--cpus", self.config.cpu_limit])
|
|
188
|
-
|
|
187
|
+
|
|
189
188
|
# Security options
|
|
190
189
|
if self.config.read_only_fs:
|
|
191
190
|
cmd.append("--read-only")
|
|
192
191
|
cmd.extend(["--tmpfs", "/tmp:rw,noexec,nosuid"])
|
|
193
|
-
|
|
192
|
+
|
|
194
193
|
if self.config.no_new_privileges:
|
|
195
194
|
cmd.append("--security-opt=no-new-privileges:true")
|
|
196
|
-
|
|
195
|
+
|
|
197
196
|
# SELinux context for production
|
|
198
197
|
cmd.extend(["--security-opt", "label=type:container_runtime_t"])
|
|
199
|
-
|
|
198
|
+
|
|
200
199
|
if self.config.drop_capabilities:
|
|
201
200
|
for cap in self.config.drop_capabilities:
|
|
202
201
|
cmd.extend(["--cap-drop", cap])
|
|
203
|
-
|
|
202
|
+
|
|
204
203
|
if self.config.add_capabilities:
|
|
205
204
|
for cap in self.config.add_capabilities:
|
|
206
205
|
cmd.extend(["--cap-add", cap])
|
|
207
|
-
|
|
206
|
+
|
|
208
207
|
# Health check
|
|
209
208
|
if health_check:
|
|
210
209
|
cmd.extend([
|
|
@@ -213,34 +212,34 @@ class PodmanBackend(DeploymentBackend):
|
|
|
213
212
|
"--health-timeout", self.config.health_check_timeout,
|
|
214
213
|
"--health-retries", str(self.config.health_check_retries),
|
|
215
214
|
])
|
|
216
|
-
|
|
215
|
+
|
|
217
216
|
# Labels
|
|
218
217
|
for key, value in self.config.labels.items():
|
|
219
218
|
cmd.extend(["--label", f"{key}={value}"])
|
|
220
|
-
|
|
219
|
+
|
|
221
220
|
# Systemd integration label
|
|
222
221
|
cmd.extend(["--label", "io.containers.autoupdate=registry"])
|
|
223
|
-
|
|
222
|
+
|
|
224
223
|
cmd.append(image_name)
|
|
225
|
-
|
|
224
|
+
|
|
226
225
|
try:
|
|
227
226
|
# Ensure network exists
|
|
228
227
|
subprocess.run(
|
|
229
228
|
["podman", "network", "create", self.config.network_name],
|
|
230
229
|
capture_output=True,
|
|
231
230
|
)
|
|
232
|
-
|
|
231
|
+
|
|
233
232
|
result = subprocess.run(
|
|
234
233
|
cmd,
|
|
235
234
|
capture_output=True,
|
|
236
235
|
text=True,
|
|
237
236
|
timeout=60,
|
|
238
237
|
)
|
|
239
|
-
|
|
238
|
+
|
|
240
239
|
if result.returncode == 0:
|
|
241
240
|
container_id = result.stdout.strip()[:12]
|
|
242
241
|
endpoint = f"http://{container_name}:{port}" if self.config.use_internal_dns else f"http://localhost:{port}"
|
|
243
|
-
|
|
242
|
+
|
|
244
243
|
return DeploymentResult(
|
|
245
244
|
success=True,
|
|
246
245
|
service_name=service_name,
|
|
@@ -263,54 +262,54 @@ class PodmanBackend(DeploymentBackend):
|
|
|
263
262
|
runtime=self.runtime_type,
|
|
264
263
|
error="Deploy timed out",
|
|
265
264
|
)
|
|
266
|
-
|
|
265
|
+
|
|
267
266
|
def stop(self, service_name: str) -> DeploymentResult:
|
|
268
267
|
"""Stop a container."""
|
|
269
268
|
container_name = f"{self.config.namespace}-{service_name}"
|
|
270
|
-
|
|
269
|
+
|
|
271
270
|
result = subprocess.run(
|
|
272
271
|
["podman", "stop", container_name],
|
|
273
272
|
capture_output=True,
|
|
274
273
|
text=True,
|
|
275
274
|
)
|
|
276
|
-
|
|
275
|
+
|
|
277
276
|
subprocess.run(
|
|
278
277
|
["podman", "rm", container_name],
|
|
279
278
|
capture_output=True,
|
|
280
279
|
)
|
|
281
|
-
|
|
280
|
+
|
|
282
281
|
return DeploymentResult(
|
|
283
282
|
success=result.returncode == 0,
|
|
284
283
|
service_name=service_name,
|
|
285
284
|
runtime=self.runtime_type,
|
|
286
285
|
error=result.stderr if result.returncode != 0 else None,
|
|
287
286
|
)
|
|
288
|
-
|
|
287
|
+
|
|
289
288
|
def logs(self, service_name: str, tail: int = 100) -> str:
|
|
290
289
|
"""Get container logs."""
|
|
291
290
|
container_name = f"{self.config.namespace}-{service_name}"
|
|
292
|
-
|
|
291
|
+
|
|
293
292
|
result = subprocess.run(
|
|
294
293
|
["podman", "logs", "--tail", str(tail), container_name],
|
|
295
294
|
capture_output=True,
|
|
296
295
|
text=True,
|
|
297
296
|
)
|
|
298
|
-
|
|
297
|
+
|
|
299
298
|
return result.stdout + result.stderr
|
|
300
|
-
|
|
299
|
+
|
|
301
300
|
def status(self, service_name: str) -> dict[str, Any]:
|
|
302
301
|
"""Get container status."""
|
|
303
302
|
container_name = f"{self.config.namespace}-{service_name}"
|
|
304
|
-
|
|
303
|
+
|
|
305
304
|
result = subprocess.run(
|
|
306
305
|
["podman", "inspect", container_name],
|
|
307
306
|
capture_output=True,
|
|
308
307
|
text=True,
|
|
309
308
|
)
|
|
310
|
-
|
|
309
|
+
|
|
311
310
|
if result.returncode != 0:
|
|
312
311
|
return {"running": False, "error": "Container not found"}
|
|
313
|
-
|
|
312
|
+
|
|
314
313
|
try:
|
|
315
314
|
data = json.loads(result.stdout)[0]
|
|
316
315
|
return {
|
|
@@ -323,7 +322,7 @@ class PodmanBackend(DeploymentBackend):
|
|
|
323
322
|
}
|
|
324
323
|
except (json.JSONDecodeError, KeyError, IndexError):
|
|
325
324
|
return {"running": False, "error": "Failed to parse status"}
|
|
326
|
-
|
|
325
|
+
|
|
327
326
|
def generate_systemd_unit(
|
|
328
327
|
self,
|
|
329
328
|
service_name: str,
|
|
@@ -331,12 +330,12 @@ class PodmanBackend(DeploymentBackend):
|
|
|
331
330
|
) -> str:
|
|
332
331
|
"""
|
|
333
332
|
Generate systemd unit file for production deployment.
|
|
334
|
-
|
|
333
|
+
|
|
335
334
|
This allows the container to be managed as a system service
|
|
336
335
|
with automatic restart, logging, and dependency management.
|
|
337
336
|
"""
|
|
338
337
|
container_name = container_name or f"{self.config.namespace}-{service_name}"
|
|
339
|
-
|
|
338
|
+
|
|
340
339
|
return f"""[Unit]
|
|
341
340
|
Description=Pactown {service_name} container
|
|
342
341
|
After=network-online.target
|
|
@@ -365,7 +364,7 @@ PrivateDevices=true
|
|
|
365
364
|
[Install]
|
|
366
365
|
WantedBy=multi-user.target
|
|
367
366
|
"""
|
|
368
|
-
|
|
367
|
+
|
|
369
368
|
def create_pod(
|
|
370
369
|
self,
|
|
371
370
|
pod_name: str,
|
|
@@ -374,7 +373,7 @@ WantedBy=multi-user.target
|
|
|
374
373
|
) -> DeploymentResult:
|
|
375
374
|
"""
|
|
376
375
|
Create a Podman pod (similar to Kubernetes pod).
|
|
377
|
-
|
|
376
|
+
|
|
378
377
|
All containers in a pod share the same network namespace,
|
|
379
378
|
making inter-service communication via localhost possible.
|
|
380
379
|
"""
|
|
@@ -382,16 +381,16 @@ WantedBy=multi-user.target
|
|
|
382
381
|
"podman", "pod", "create",
|
|
383
382
|
"--name", pod_name,
|
|
384
383
|
]
|
|
385
|
-
|
|
384
|
+
|
|
386
385
|
for port in ports:
|
|
387
386
|
cmd.extend(["-p", f"{port}:{port}"])
|
|
388
|
-
|
|
387
|
+
|
|
389
388
|
result = subprocess.run(
|
|
390
389
|
cmd,
|
|
391
390
|
capture_output=True,
|
|
392
391
|
text=True,
|
|
393
392
|
)
|
|
394
|
-
|
|
393
|
+
|
|
395
394
|
return DeploymentResult(
|
|
396
395
|
success=result.returncode == 0,
|
|
397
396
|
service_name=pod_name,
|