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/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 Optional, Any
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,