container-manager-mcp 1.0.2__py3-none-any.whl → 1.0.4__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.
@@ -8,10 +8,12 @@ from abc import ABC, abstractmethod
8
8
  from typing import List, Dict, Optional, Any
9
9
  import argparse
10
10
  import json
11
+ import shutil
11
12
  import subprocess
12
13
  from datetime import datetime
13
14
  import dateutil.parser
14
15
  import platform
16
+ import traceback
15
17
 
16
18
  try:
17
19
  import docker
@@ -40,7 +42,7 @@ class ContainerManagerBase(ABC):
40
42
  log_file = os.path.join(script_dir, "container_manager.log")
41
43
  logging.basicConfig(
42
44
  filename=log_file,
43
- level=logging.INFO,
45
+ level=logging.DEBUG, # Changed to DEBUG for more detailed logging
44
46
  format="%(asctime)s - %(levelname)s - %(message)s",
45
47
  )
46
48
  self.logger = logging.getLogger(__name__)
@@ -57,7 +59,10 @@ class ContainerManagerBase(ABC):
57
59
  if result:
58
60
  self.logger.info(f"Result: {result}")
59
61
  if error:
60
- self.logger.error(f"Error: {str(error)}")
62
+ self.logger.error(
63
+ f"Error in {action}: {type(error).__name__}: {str(error)}"
64
+ )
65
+ self.logger.error(f"Traceback: {traceback.format_exc()}")
61
66
 
62
67
  def _format_size(self, size_bytes: int) -> str:
63
68
  """Helper to format bytes to human-readable (e.g., 1.23GB)."""
@@ -105,6 +110,10 @@ class ContainerManagerBase(ABC):
105
110
  def remove_image(self, image: str, force: bool = False) -> Dict:
106
111
  pass
107
112
 
113
+ @abstractmethod
114
+ def prune_images(self, force: bool = False, all: bool = False) -> Dict:
115
+ pass
116
+
108
117
  @abstractmethod
109
118
  def list_containers(self, all: bool = False) -> List[Dict]:
110
119
  pass
@@ -130,6 +139,10 @@ class ContainerManagerBase(ABC):
130
139
  def remove_container(self, container_id: str, force: bool = False) -> Dict:
131
140
  pass
132
141
 
142
+ @abstractmethod
143
+ def prune_containers(self) -> Dict:
144
+ pass
145
+
133
146
  @abstractmethod
134
147
  def get_container_logs(self, container_id: str, tail: str = "all") -> str:
135
148
  pass
@@ -152,6 +165,10 @@ class ContainerManagerBase(ABC):
152
165
  def remove_volume(self, name: str, force: bool = False) -> Dict:
153
166
  pass
154
167
 
168
+ @abstractmethod
169
+ def prune_volumes(self, force: bool = False, all: bool = False) -> Dict:
170
+ pass
171
+
155
172
  @abstractmethod
156
173
  def list_networks(self) -> List[Dict]:
157
174
  pass
@@ -164,6 +181,14 @@ class ContainerManagerBase(ABC):
164
181
  def remove_network(self, network_id: str) -> Dict:
165
182
  pass
166
183
 
184
+ @abstractmethod
185
+ def prune_networks(self) -> Dict:
186
+ pass
187
+
188
+ @abstractmethod
189
+ def prune_system(self, force: bool = False, all: bool = False) -> Dict:
190
+ pass
191
+
167
192
  @abstractmethod
168
193
  def compose_up(
169
194
  self, compose_file: str, detach: bool = True, build: bool = False
@@ -225,6 +250,78 @@ class DockerManager(ContainerManagerBase):
225
250
  self.logger.error(f"Failed to connect to Docker daemon: {str(e)}")
226
251
  raise RuntimeError(f"Failed to connect to Docker: {str(e)}")
227
252
 
253
+ def prune_system(self, force: bool = False, all: bool = False) -> Dict:
254
+ params = {"force": force, "all": all}
255
+ try:
256
+ filters = {"until": None} if all else {}
257
+ result = self.client.system.prune(filters=filters, volumes=all)
258
+ if result is None:
259
+ result = {
260
+ "SpaceReclaimed": 0,
261
+ "ImagesDeleted": [],
262
+ "ContainersDeleted": [],
263
+ "VolumesDeleted": [],
264
+ "NetworksDeleted": [],
265
+ }
266
+ self.logger.debug(f"Raw prune_system result: {result}")
267
+ pruned = {
268
+ "space_reclaimed": self._format_size(result.get("SpaceReclaimed", 0)),
269
+ "images_removed": (
270
+ [img["Id"][7:19] for img in result.get("ImagesDeleted", [])]
271
+ ),
272
+ "containers_removed": (
273
+ [c["Id"][7:19] for c in result.get("ContainersDeleted", [])]
274
+ ),
275
+ "volumes_removed": (
276
+ [v["Name"] for v in result.get("VolumesDeleted", [])]
277
+ ),
278
+ "networks_removed": (
279
+ [n["Id"][7:19] for n in result.get("NetworksDeleted", [])]
280
+ ),
281
+ }
282
+ self.log_action("prune_system", params, pruned)
283
+ return pruned
284
+ except Exception as e:
285
+ self.log_action("prune_system", params, error=e)
286
+ raise RuntimeError(f"Failed to prune system: {str(e)}")
287
+
288
+ # Other DockerManager methods remain unchanged (omitted for brevity)
289
+ def get_version(self) -> Dict:
290
+ params = {}
291
+ try:
292
+ version = self.client.version()
293
+ result = {
294
+ "version": version.get("Version", "unknown"),
295
+ "api_version": version.get("ApiVersion", "unknown"),
296
+ "os": version.get("Os", "unknown"),
297
+ "arch": version.get("Arch", "unknown"),
298
+ "build_time": version.get("BuildTime", "unknown"),
299
+ }
300
+ self.log_action("get_version", params, result)
301
+ return result
302
+ except Exception as e:
303
+ self.log_action("get_version", params, error=e)
304
+ raise RuntimeError(f"Failed to get version: {str(e)}")
305
+
306
+ def get_info(self) -> Dict:
307
+ params = {}
308
+ try:
309
+ info = self.client.info()
310
+ result = {
311
+ "containers_total": info.get("Containers", 0),
312
+ "containers_running": info.get("ContainersRunning", 0),
313
+ "images": info.get("Images", 0),
314
+ "driver": info.get("Driver", "unknown"),
315
+ "platform": f"{info.get('OperatingSystem', 'unknown')} {info.get('Architecture', 'unknown')}",
316
+ "memory_total": self._format_size(info.get("MemTotal", 0)),
317
+ "swap_total": self._format_size(info.get("SwapTotal", 0)),
318
+ }
319
+ self.log_action("get_info", params, result)
320
+ return result
321
+ except Exception as e:
322
+ self.log_action("get_info", params, error=e)
323
+ raise RuntimeError(f"Failed to get info: {str(e)}")
324
+
228
325
  def list_images(self) -> List[Dict]:
229
326
  params = {}
230
327
  try:
@@ -294,6 +391,59 @@ class DockerManager(ContainerManagerBase):
294
391
  self.log_action("pull_image", params, error=e)
295
392
  raise RuntimeError(f"Failed to pull image: {str(e)}")
296
393
 
394
+ def remove_image(self, image: str, force: bool = False) -> Dict:
395
+ params = {"image": image, "force": force}
396
+ try:
397
+ self.client.images.remove(image, force=force)
398
+ result = {"removed": image}
399
+ self.log_action("remove_image", params, result)
400
+ return result
401
+ except Exception as e:
402
+ self.log_action("remove_image", params, error=e)
403
+ raise RuntimeError(f"Failed to remove image: {str(e)}")
404
+
405
+ def prune_images(self, force: bool = False, all: bool = False) -> Dict:
406
+ params = {"force": force, "all": all}
407
+ try:
408
+ if all:
409
+ # Manually remove all unused images
410
+ images = self.client.images.list(all=True)
411
+ removed = []
412
+ for img in images:
413
+ try:
414
+ for tag in img.attrs.get("RepoTags", []):
415
+ self.client.images.remove(tag, force=force)
416
+ removed.append(img.attrs["Id"][7:19])
417
+ except Exception as e:
418
+ self.logger.info(
419
+ f"Info: Failed to remove image {img.attrs.get('Id', 'unknown')}: {e}"
420
+ )
421
+ continue
422
+ result = {
423
+ "images_removed": removed,
424
+ "space_reclaimed": "N/A (all images)",
425
+ }
426
+ else:
427
+ filters = {"dangling": True} if not all else {}
428
+ result = self.client.images.prune(filters=filters)
429
+ if result is None:
430
+ result = {"SpaceReclaimed": 0, "ImagesDeleted": []}
431
+ self.logger.debug(f"Raw prune_images result: {result}")
432
+ pruned = {
433
+ "space_reclaimed": self._format_size(
434
+ result.get("SpaceReclaimed", 0)
435
+ ),
436
+ "images_removed": (
437
+ [img["Id"][7:19] for img in result.get("ImagesDeleted", [])]
438
+ ),
439
+ }
440
+ result = pruned
441
+ self.log_action("prune_images", params, result)
442
+ return result
443
+ except Exception as e:
444
+ self.log_action("prune_images", params, error=e)
445
+ raise RuntimeError(f"Failed to prune images: {str(e)}")
446
+
297
447
  def list_containers(self, all: bool = False) -> List[Dict]:
298
448
  params = {"all": all}
299
449
  try:
@@ -384,98 +534,6 @@ class DockerManager(ContainerManagerBase):
384
534
  self.log_action("run_container", params, error=e)
385
535
  raise RuntimeError(f"Failed to run container: {str(e)}")
386
536
 
387
- def list_networks(self) -> List[Dict]:
388
- params = {}
389
- try:
390
- networks = self.client.networks.list()
391
- result = []
392
- for net in networks:
393
- attrs = net.attrs
394
- containers = len(attrs.get("Containers", {}))
395
- created = attrs.get("Created", None)
396
- created_str = self._parse_timestamp(created)
397
- simplified = {
398
- "id": attrs.get("Id", "unknown")[7:19],
399
- "name": attrs.get("Name", "unknown"),
400
- "driver": attrs.get("Driver", "unknown"),
401
- "scope": attrs.get("Scope", "unknown"),
402
- "containers": containers,
403
- "created": created_str,
404
- }
405
- result.append(simplified)
406
- self.log_action("list_networks", params, result)
407
- return result
408
- except Exception as e:
409
- self.log_action("list_networks", params, error=e)
410
- raise RuntimeError(f"Failed to list networks: {str(e)}")
411
-
412
- def create_network(self, name: str, driver: str = "bridge") -> Dict:
413
- params = {"name": name, "driver": driver}
414
- try:
415
- network = self.client.networks.create(name, driver=driver)
416
- attrs = network.attrs
417
- created = attrs.get("Created", None)
418
- created_str = self._parse_timestamp(created)
419
- result = {
420
- "id": attrs.get("Id", "unknown")[7:19],
421
- "name": attrs.get("Name", name),
422
- "driver": attrs.get("Driver", driver),
423
- "scope": attrs.get("Scope", "unknown"),
424
- "created": created_str,
425
- }
426
- self.log_action("create_network", params, result)
427
- return result
428
- except Exception as e:
429
- self.log_action("create_network", params, error=e)
430
- raise RuntimeError(f"Failed to create network: {str(e)}")
431
-
432
- def get_version(self) -> Dict:
433
- params = {}
434
- try:
435
- version = self.client.version()
436
- result = {
437
- "version": version.get("Version", "unknown"),
438
- "api_version": version.get("ApiVersion", "unknown"),
439
- "os": version.get("Os", "unknown"),
440
- "arch": version.get("Arch", "unknown"),
441
- "build_time": version.get("BuildTime", "unknown"),
442
- }
443
- self.log_action("get_version", params, result)
444
- return result
445
- except Exception as e:
446
- self.log_action("get_version", params, error=e)
447
- raise RuntimeError(f"Failed to get version: {str(e)}")
448
-
449
- def get_info(self) -> Dict:
450
- params = {}
451
- try:
452
- info = self.client.info()
453
- result = {
454
- "containers_total": info.get("Containers", 0),
455
- "containers_running": info.get("ContainersRunning", 0),
456
- "images": info.get("Images", 0),
457
- "driver": info.get("Driver", "unknown"),
458
- "platform": f"{info.get('OperatingSystem', 'unknown')} {info.get('Architecture', 'unknown')}",
459
- "memory_total": self._format_size(info.get("MemTotal", 0)),
460
- "swap_total": self._format_size(info.get("SwapTotal", 0)),
461
- }
462
- self.log_action("get_info", params, result)
463
- return result
464
- except Exception as e:
465
- self.log_action("get_info", params, error=e)
466
- raise RuntimeError(f"Failed to get info: {str(e)}")
467
-
468
- def remove_image(self, image: str, force: bool = False) -> Dict:
469
- params = {"image": image, "force": force}
470
- try:
471
- self.client.images.remove(image, force=force)
472
- result = {"removed": image}
473
- self.log_action("remove_image", params, result)
474
- return result
475
- except Exception as e:
476
- self.log_action("remove_image", params, error=e)
477
- raise RuntimeError(f"Failed to remove image: {str(e)}")
478
-
479
537
  def stop_container(self, container_id: str, timeout: int = 10) -> Dict:
480
538
  params = {"container_id": container_id, "timeout": timeout}
481
539
  try:
@@ -500,6 +558,34 @@ class DockerManager(ContainerManagerBase):
500
558
  self.log_action("remove_container", params, error=e)
501
559
  raise RuntimeError(f"Failed to remove container: {str(e)}")
502
560
 
561
+ def prune_containers(self) -> Dict:
562
+ params = {}
563
+ try:
564
+ result = self.client.containers.prune()
565
+ self.logger.debug(f"Raw prune_containers result: {result}")
566
+ if result is None:
567
+ result = {"SpaceReclaimed": 0, "ContainersDeleted": []}
568
+ pruned = {
569
+ "space_reclaimed": self._format_size(result.get("SpaceReclaimed", 0)),
570
+ "containers_removed": (
571
+ [c["Id"][7:19] for c in result.get("ContainersDeleted", [])]
572
+ ),
573
+ }
574
+ self.log_action("prune_containers", params, pruned)
575
+ return pruned
576
+ except TypeError as e:
577
+ self.logger.error(f"TypeError in prune_containers: {str(e)}")
578
+ self.logger.error(f"Traceback: {traceback.format_exc()}")
579
+ self.log_action("prune_containers", params, error=e)
580
+ raise RuntimeError(f"Failed to prune containers: {str(e)}")
581
+ except Exception as e:
582
+ self.logger.error(
583
+ f"Unexpected exception in prune_containers: {type(e).__name__}: {str(e)}"
584
+ )
585
+ self.logger.error(f"Traceback: {traceback.format_exc()}")
586
+ self.log_action("prune_containers", params, error=e)
587
+ raise RuntimeError(f"Failed to prune containers: {str(e)}")
588
+
503
589
  def get_container_logs(self, container_id: str, tail: str = "all") -> str:
504
590
  params = {"container_id": container_id, "tail": tail}
505
591
  try:
@@ -581,6 +667,90 @@ class DockerManager(ContainerManagerBase):
581
667
  self.log_action("remove_volume", params, error=e)
582
668
  raise RuntimeError(f"Failed to remove volume: {str(e)}")
583
669
 
670
+ def prune_volumes(self, force: bool = False, all: bool = False) -> Dict:
671
+ params = {"force": force, "all": all}
672
+ try:
673
+ if all:
674
+ volumes = self.client.volumes.list(all=True)
675
+ removed = []
676
+ for v in volumes:
677
+ try:
678
+ v.remove(force=force)
679
+ removed.append(v.attrs["Name"])
680
+ except Exception as e:
681
+ self.logger.info(
682
+ f"Info: Failed to remove volume {v.attrs.get('Name', 'unknown')}: {e}"
683
+ )
684
+ continue
685
+ result = {
686
+ "volumes_removed": removed,
687
+ "space_reclaimed": "N/A (all volumes)",
688
+ }
689
+ else:
690
+ result = self.client.volumes.prune()
691
+ if result is None:
692
+ result = {"SpaceReclaimed": 0, "VolumesDeleted": []}
693
+ self.logger.debug(f"Raw prune_volumes result: {result}")
694
+ pruned = {
695
+ "space_reclaimed": self._format_size(
696
+ result.get("SpaceReclaimed", 0)
697
+ ),
698
+ "volumes_removed": (
699
+ [v["Name"] for v in result.get("VolumesDeleted", [])]
700
+ ),
701
+ }
702
+ result = pruned
703
+ self.log_action("prune_volumes", params, result)
704
+ return result
705
+ except Exception as e:
706
+ self.log_action("prune_volumes", params, error=e)
707
+ raise RuntimeError(f"Failed to prune volumes: {str(e)}")
708
+
709
+ def list_networks(self) -> List[Dict]:
710
+ params = {}
711
+ try:
712
+ networks = self.client.networks.list()
713
+ result = []
714
+ for net in networks:
715
+ attrs = net.attrs
716
+ containers = len(attrs.get("Containers", {}))
717
+ created = attrs.get("Created", None)
718
+ created_str = self._parse_timestamp(created)
719
+ simplified = {
720
+ "id": attrs.get("Id", "unknown")[7:19],
721
+ "name": attrs.get("Name", "unknown"),
722
+ "driver": attrs.get("Driver", "unknown"),
723
+ "scope": attrs.get("Scope", "unknown"),
724
+ "containers": containers,
725
+ "created": created_str,
726
+ }
727
+ result.append(simplified)
728
+ self.log_action("list_networks", params, result)
729
+ return result
730
+ except Exception as e:
731
+ self.log_action("list_networks", params, error=e)
732
+ raise RuntimeError(f"Failed to list networks: {str(e)}")
733
+
734
+ def create_network(self, name: str, driver: str = "bridge") -> Dict:
735
+ params = {"name": name, "driver": driver}
736
+ try:
737
+ network = self.client.networks.create(name, driver=driver)
738
+ attrs = network.attrs
739
+ created = attrs.get("Created", None)
740
+ created_str = self._parse_timestamp(created)
741
+ result = {
742
+ "id": attrs.get("Id", "unknown")[7:19],
743
+ "name": attrs.get("Name", name),
744
+ "driver": attrs.get("Driver", driver),
745
+ "scope": attrs.get("Scope", "unknown"),
746
+ "created": created_str,
747
+ }
748
+ self.log_action("create_network", params, result)
749
+ return result
750
+ except Exception as e:
751
+ self.log_action("create_network", params, error=e)
752
+ raise RuntimeError(f"Failed to create network: {str(e)}")
753
+
584
754
  def remove_network(self, network_id: str) -> Dict:
585
755
  params = {"network_id": network_id}
586
756
  try:
@@ -593,6 +763,25 @@ class DockerManager(ContainerManagerBase):
593
763
  self.log_action("remove_network", params, error=e)
594
764
  raise RuntimeError(f"Failed to remove network: {str(e)}")
595
765
 
766
+ def prune_networks(self) -> Dict:
767
+ params = {}
768
+ try:
769
+ result = self.client.networks.prune()
770
+ if result is None:
771
+ result = {"SpaceReclaimed": 0, "NetworksDeleted": []}
772
+ self.logger.debug(f"Raw prune_networks result: {result}")
773
+ pruned = {
774
+ "space_reclaimed": self._format_size(result.get("SpaceReclaimed", 0)),
775
+ "networks_removed": (
776
+ [n["Id"][7:19] for n in result.get("NetworksDeleted", [])]
777
+ ),
778
+ }
779
+ self.log_action("prune_networks", params, pruned)
780
+ return pruned
781
+ except Exception as e:
782
+ self.log_action("prune_networks", params, error=e)
783
+ raise RuntimeError(f"Failed to prune networks: {str(e)}")
784
+
596
785
  def compose_up(
597
786
  self, compose_file: str, detach: bool = True, build: bool = False
598
787
  ) -> str:
@@ -818,17 +1007,14 @@ class DockerManager(ContainerManagerBase):
818
1007
  class PodmanManager(ContainerManagerBase):
819
1008
  def __init__(self, silent: bool = False, log_file: Optional[str] = None):
820
1009
  super().__init__(silent, log_file)
821
-
822
1010
  if PodmanClient is None:
823
1011
  raise ImportError("Please install podman-py: pip install podman")
824
-
825
1012
  base_url = self._autodetect_podman_url()
826
1013
  if base_url is None:
827
1014
  self.logger.error(
828
1015
  "No valid Podman socket found after trying all known locations"
829
1016
  )
830
1017
  raise RuntimeError("Failed to connect to Podman: No valid socket found")
831
-
832
1018
  try:
833
1019
  self.client = PodmanClient(base_url=base_url)
834
1020
  self.logger.info(f"Connected to Podman with base_url: {base_url}")
@@ -863,7 +1049,6 @@ class PodmanManager(ContainerManagerBase):
863
1049
  """Attempt to connect to Podman with the given base_url."""
864
1050
  try:
865
1051
  client = PodmanClient(base_url=base_url)
866
- # Test connection
867
1052
  client.version()
868
1053
  return client
869
1054
  except PodmanError as e:
@@ -872,41 +1057,31 @@ class PodmanManager(ContainerManagerBase):
872
1057
 
873
1058
  def _autodetect_podman_url(self) -> Optional[str]:
874
1059
  """Autodetect the appropriate Podman socket URL based on platform."""
875
- # Check for environment variable override
876
1060
  base_url = os.environ.get("PODMAN_BASE_URL")
877
1061
  if base_url:
878
1062
  self.logger.info(f"Using PODMAN_BASE_URL from environment: {base_url}")
879
1063
  return base_url
880
-
881
1064
  system = platform.system()
882
1065
  is_wsl = self._is_wsl()
883
-
884
- # Define socket candidates based on platform
885
1066
  socket_candidates = []
886
1067
  if system == "Windows" and not is_wsl:
887
- # Windows with Podman machine
888
1068
  if self._is_podman_machine_running():
889
1069
  socket_candidates.append("npipe:////./pipe/docker_engine")
890
- # Fallback to WSL2 distro sockets if running in a mixed setup
891
1070
  socket_candidates.extend(
892
1071
  [
893
- "unix:///mnt/wsl/podman-sockets/podman-machine-default/podman-user.sock", # Rootless
894
- "unix:///mnt/wsl/podman-sockets/podman-machine-default/podman-root.sock", # Rootful
1072
+ "unix:///mnt/wsl/podman-sockets/podman-machine-default/podman-user.sock",
1073
+ "unix:///mnt/wsl/podman-sockets/podman-machine-default/podman-root.sock",
895
1074
  ]
896
1075
  )
897
1076
  elif system == "Linux" or is_wsl:
898
- # Linux or WSL2 distro: prioritize rootless, then rootful
899
1077
  uid = os.getuid()
900
1078
  socket_candidates.extend(
901
1079
  [
902
- f"unix:///run/user/{uid}/podman/podman.sock", # Rootless
903
- "unix:///run/podman/podman.sock", # Rootful
1080
+ f"unix:///run/user/{uid}/podman/podman.sock",
1081
+ "unix:///run/podman/podman.sock",
904
1082
  ]
905
1083
  )
906
-
907
- # Try each socket candidate
908
1084
  for url in socket_candidates:
909
- # For Unix sockets, check if the file exists (on Linux/WSL2)
910
1085
  if url.startswith("unix://") and (system == "Linux" or is_wsl):
911
1086
  socket_path = url.replace("unix://", "")
912
1087
  if not os.path.exists(socket_path):
@@ -915,9 +1090,207 @@ class PodmanManager(ContainerManagerBase):
915
1090
  client = self._try_connect(url)
916
1091
  if client:
917
1092
  return url
918
-
919
1093
  return None
920
1094
 
1095
+ def prune_images(self, force: bool = False, all: bool = False) -> Dict:
1096
+ params = {"force": force, "all": all}
1097
+ try:
1098
+ if all:
1099
+ # Manually remove all unused images
1100
+ images = self.client.images.list(all=True)
1101
+ removed = []
1102
+ for img in images:
1103
+ try:
1104
+ for tag in img.attrs.get("Names", []):
1105
+ self.client.images.remove(tag, force=force)
1106
+ removed.append(img.attrs["Id"][7:19])
1107
+ except Exception as e:
1108
+ self.logger.info(
1109
+ f"Info: Failed to remove image {img.attrs.get('Id', 'unknown')}: {e}"
1110
+ )
1111
+ continue
1112
+ result = {
1113
+ "images_removed": removed,
1114
+ "space_reclaimed": "N/A (all images)",
1115
+ }
1116
+ else:
1117
+ filters = {"dangling": True} if not all else {}
1118
+ result = self.client.images.prune(filters=filters)
1119
+ if result is None:
1120
+ result = {"SpaceReclaimed": 0, "ImagesRemoved": []}
1121
+ self.logger.debug(f"Raw prune_images result: {result}")
1122
+ pruned = {
1123
+ "space_reclaimed": self._format_size(
1124
+ result.get("SpaceReclaimed", 0)
1125
+ ),
1126
+ "images_removed": (
1127
+ [img["Id"][7:19] for img in result.get("ImagesRemoved", [])]
1128
+ or [img["Id"][7:19] for img in result.get("ImagesDeleted", [])]
1129
+ ),
1130
+ }
1131
+ result = pruned
1132
+ self.log_action("prune_images", params, result)
1133
+ return result
1134
+ except Exception as e:
1135
+ self.log_action("prune_images", params, error=e)
1136
+ raise RuntimeError(f"Failed to prune images: {str(e)}")
1137
+
1138
+ def prune_containers(self) -> Dict:
1139
+ params = {}
1140
+ try:
1141
+ result = self.client.containers.prune()
1142
+ self.logger.debug(f"Raw prune_containers result: {result}")
1143
+ if result is None:
1144
+ result = {"SpaceReclaimed": 0, "ContainersDeleted": []}
1145
+ pruned = {
1146
+ "space_reclaimed": self._format_size(result.get("SpaceReclaimed", 0)),
1147
+ "containers_removed": (
1148
+ [c["Id"][7:19] for c in result.get("ContainersDeleted", [])]
1149
+ or [c["Id"][7:19] for c in result.get("ContainersRemoved", [])]
1150
+ ),
1151
+ }
1152
+ self.log_action("prune_containers", params, pruned)
1153
+ return pruned
1154
+ except PodmanError as e:
1155
+ self.logger.error(f"PodmanError in prune_containers: {str(e)}")
1156
+ self.logger.error(f"Traceback: {traceback.format_exc()}")
1157
+ self.log_action("prune_containers", params, error=e)
1158
+ raise RuntimeError(f"Failed to prune containers: {str(e)}")
1159
+ except Exception as e:
1160
+ self.logger.error(
1161
+ f"Unexpected exception in prune_containers: {type(e).__name__}: {str(e)}"
1162
+ )
1163
+ self.logger.error(f"Traceback: {traceback.format_exc()}")
1164
+ self.log_action("prune_containers", params, error=e)
1165
+ raise RuntimeError(f"Failed to prune containers: {str(e)}")
1166
+
1167
+ def prune_volumes(self, force: bool = False, all: bool = False) -> Dict:
1168
+ params = {"force": force, "all": all}
1169
+ try:
1170
+ if all:
1171
+ volumes = self.client.volumes.list(all=True)
1172
+ removed = []
1173
+ for v in volumes:
1174
+ try:
1175
+ v.remove(force=force)
1176
+ removed.append(v.attrs["Name"])
1177
+ except Exception as e:
1178
+ self.logger.info(
1179
+ f"Info: Failed to remove volume {v.attrs.get('Name', 'unknown')}: {e}"
1180
+ )
1181
+ continue
1182
+ result = {
1183
+ "volumes_removed": removed,
1184
+ "space_reclaimed": "N/A (all volumes)",
1185
+ }
1186
+ else:
1187
+ result = self.client.volumes.prune()
1188
+ if result is None:
1189
+ result = {"SpaceReclaimed": 0, "VolumesRemoved": []}
1190
+ self.logger.debug(f"Raw prune_volumes result: {result}")
1191
+ pruned = {
1192
+ "space_reclaimed": self._format_size(
1193
+ result.get("SpaceReclaimed", 0)
1194
+ ),
1195
+ "volumes_removed": (
1196
+ [v["Name"] for v in result.get("VolumesRemoved", [])]
1197
+ or [v["Name"] for v in result.get("VolumesDeleted", [])]
1198
+ ),
1199
+ }
1200
+ result = pruned
1201
+ self.log_action("prune_volumes", params, result)
1202
+ return result
1203
+ except Exception as e:
1204
+ self.log_action("prune_volumes", params, error=e)
1205
+ raise RuntimeError(f"Failed to prune volumes: {str(e)}")
1206
+
1207
+ def prune_networks(self) -> Dict:
1208
+ params = {}
1209
+ try:
1210
+ result = self.client.networks.prune()
1211
+ if result is None:
1212
+ result = {"SpaceReclaimed": 0, "NetworksRemoved": []}
1213
+ self.logger.debug(f"Raw prune_networks result: {result}")
1214
+ pruned = {
1215
+ "space_reclaimed": self._format_size(result.get("SpaceReclaimed", 0)),
1216
+ "networks_removed": (
1217
+ [n["Id"][7:19] for n in result.get("NetworksRemoved", [])]
1218
+ or [n["Id"][7:19] for n in result.get("NetworksDeleted", [])]
1219
+ ),
1220
+ }
1221
+ self.log_action("prune_networks", params, pruned)
1222
+ return pruned
1223
+ except Exception as e:
1224
+ self.log_action("prune_networks", params, error=e)
1225
+ raise RuntimeError(f"Failed to prune networks: {str(e)}")
1226
+
1227
+ def prune_system(self, force: bool = False, all: bool = False) -> Dict:
1228
+ params = {"force": force, "all": all}
1229
+ try:
1230
+ cmd = (
1231
+ ["podman", "system", "prune", "--force"]
1232
+ if force
1233
+ else ["podman", "system", "prune"]
1234
+ )
1235
+ if all:
1236
+ cmd.append("--all")
1237
+ if all: # Include volumes if all=True
1238
+ cmd.append("--volumes")
1239
+ result = subprocess.run(cmd, capture_output=True, text=True)
1240
+ if result.returncode != 0:
1241
+ raise RuntimeError(result.stderr)
1242
+ self.logger.debug(f"Raw prune_system result: {result.stdout}")
1243
+ pruned = {
1244
+ "output": result.stdout.strip(),
1245
+ "space_reclaimed": "Check output",
1246
+ "images_removed": [], # Podman CLI doesn't provide detailed breakdown
1247
+ "containers_removed": [],
1248
+ "volumes_removed": [],
1249
+ "networks_removed": [],
1250
+ }
1251
+ self.log_action("prune_system", params, pruned)
1252
+ return pruned
1253
+ except Exception as e:
1254
+ self.log_action("prune_system", params, error=e)
1255
+ raise RuntimeError(f"Failed to prune system: {str(e)}")
1256
+
1257
+ def get_version(self) -> Dict:
1258
+ params = {}
1259
+ try:
1260
+ version = self.client.version()
1261
+ result = {
1262
+ "version": version.get("Version", "unknown"),
1263
+ "api_version": version.get("APIVersion", "unknown"),
1264
+ "os": version.get("Os", "unknown"),
1265
+ "arch": version.get("Arch", "unknown"),
1266
+ "build_time": version.get("BuildTime", "unknown"),
1267
+ }
1268
+ self.log_action("get_version", params, result)
1269
+ return result
1270
+ except Exception as e:
1271
+ self.log_action("get_version", params, error=e)
1272
+ raise RuntimeError(f"Failed to get version: {str(e)}")
1273
+
1274
+ def get_info(self) -> Dict:
1275
+ params = {}
1276
+ try:
1277
+ info = self.client.info()
1278
+ host = info.get("host", {})
1279
+ result = {
1280
+ "containers_total": info.get("store", {}).get("containers", 0),
1281
+ "containers_running": host.get("runningContainers", 0),
1282
+ "images": info.get("store", {}).get("images", 0),
1283
+ "driver": host.get("graphDriverName", "unknown"),
1284
+ "platform": f"{host.get('os', 'unknown')} {host.get('arch', 'unknown')}",
1285
+ "memory_total": self._format_size(host.get("memTotal", 0)),
1286
+ "swap_total": self._format_size(host.get("swapTotal", 0)),
1287
+ }
1288
+ self.log_action("get_info", params, result)
1289
+ return result
1290
+ except Exception as e:
1291
+ self.log_action("get_info", params, error=e)
1292
+ raise RuntimeError(f"Failed to get info: {str(e)}")
1293
+
921
1294
  def list_images(self) -> List[Dict]:
922
1295
  params = {}
923
1296
  try:
@@ -983,6 +1356,17 @@ class PodmanManager(ContainerManagerBase):
983
1356
  self.log_action("pull_image", params, error=e)
984
1357
  raise RuntimeError(f"Failed to pull image: {str(e)}")
985
1358
 
1359
+ def remove_image(self, image: str, force: bool = False) -> Dict:
1360
+ params = {"image": image, "force": force}
1361
+ try:
1362
+ self.client.images.remove(image, force=force)
1363
+ result = {"removed": image}
1364
+ self.log_action("remove_image", params, result)
1365
+ return result
1366
+ except Exception as e:
1367
+ self.log_action("remove_image", params, error=e)
1368
+ raise RuntimeError(f"Failed to remove image: {str(e)}")
1369
+
986
1370
  def list_containers(self, all: bool = False) -> List[Dict]:
987
1371
  params = {"all": all}
988
1372
  try:
@@ -1069,99 +1453,6 @@ class PodmanManager(ContainerManagerBase):
1069
1453
  self.log_action("run_container", params, error=e)
1070
1454
  raise RuntimeError(f"Failed to run container: {str(e)}")
1071
1455
 
1072
- def list_networks(self) -> List[Dict]:
1073
- params = {}
1074
- try:
1075
- networks = self.client.networks.list()
1076
- result = []
1077
- for net in networks:
1078
- attrs = net.attrs
1079
- containers = len(attrs.get("Containers", {}))
1080
- created = attrs.get("Created", None)
1081
- created_str = self._parse_timestamp(created)
1082
- simplified = {
1083
- "id": attrs.get("Id", "unknown")[7:19],
1084
- "name": attrs.get("Name", "unknown"),
1085
- "driver": attrs.get("Driver", "unknown"),
1086
- "scope": attrs.get("Scope", "unknown"),
1087
- "containers": containers,
1088
- "created": created_str,
1089
- }
1090
- result.append(simplified)
1091
- self.log_action("list_networks", params, result)
1092
- return result
1093
- except Exception as e:
1094
- self.log_action("list_networks", params, error=e)
1095
- raise RuntimeError(f"Failed to list networks: {str(e)}")
1096
-
1097
- def create_network(self, name: str, driver: str = "bridge") -> Dict:
1098
- params = {"name": name, "driver": driver}
1099
- try:
1100
- network = self.client.networks.create(name, driver=driver)
1101
- attrs = network.attrs
1102
- created = attrs.get("Created", None)
1103
- created_str = self._parse_timestamp(created)
1104
- result = {
1105
- "id": attrs.get("Id", "unknown")[7:19],
1106
- "name": attrs.get("Name", name),
1107
- "driver": attrs.get("Driver", driver),
1108
- "scope": attrs.get("Scope", "unknown"),
1109
- "created": created_str,
1110
- }
1111
- self.log_action("create_network", params, result)
1112
- return result
1113
- except Exception as e:
1114
- self.log_action("create_network", params, error=e)
1115
- raise RuntimeError(f"Failed to create network: {str(e)}")
1116
-
1117
- def get_version(self) -> Dict:
1118
- params = {}
1119
- try:
1120
- version = self.client.version()
1121
- result = {
1122
- "version": version.get("Version", "unknown"),
1123
- "api_version": version.get("APIVersion", "unknown"),
1124
- "os": version.get("Os", "unknown"),
1125
- "arch": version.get("Arch", "unknown"),
1126
- "build_time": version.get("BuildTime", "unknown"),
1127
- }
1128
- self.log_action("get_version", params, result)
1129
- return result
1130
- except Exception as e:
1131
- self.log_action("get_version", params, error=e)
1132
- raise RuntimeError(f"Failed to get version: {str(e)}")
1133
-
1134
- def get_info(self) -> Dict:
1135
- params = {}
1136
- try:
1137
- info = self.client.info()
1138
- host = info.get("host", {})
1139
- result = {
1140
- "containers_total": info.get("store", {}).get("containers", 0),
1141
- "containers_running": host.get("runningContainers", 0),
1142
- "images": info.get("store", {}).get("images", 0),
1143
- "driver": host.get("graphDriverName", "unknown"),
1144
- "platform": f"{host.get('os', 'unknown')} {host.get('arch', 'unknown')}",
1145
- "memory_total": self._format_size(host.get("memTotal", 0)),
1146
- "swap_total": self._format_size(host.get("swapTotal", 0)),
1147
- }
1148
- self.log_action("get_info", params, result)
1149
- return result
1150
- except Exception as e:
1151
- self.log_action("get_info", params, error=e)
1152
- raise RuntimeError(f"Failed to get info: {str(e)}")
1153
-
1154
- def remove_image(self, image: str, force: bool = False) -> Dict:
1155
- params = {"image": image, "force": force}
1156
- try:
1157
- self.client.images.remove(image, force=force)
1158
- result = {"removed": image}
1159
- self.log_action("remove_image", params, result)
1160
- return result
1161
- except Exception as e:
1162
- self.log_action("remove_image", params, error=e)
1163
- raise RuntimeError(f"Failed to remove image: {str(e)}")
1164
-
1165
1456
  def stop_container(self, container_id: str, timeout: int = 10) -> Dict:
1166
1457
  params = {"container_id": container_id, "timeout": timeout}
1167
1458
  try:
@@ -1267,6 +1558,51 @@ class PodmanManager(ContainerManagerBase):
1267
1558
  self.log_action("remove_volume", params, error=e)
1268
1559
  raise RuntimeError(f"Failed to remove volume: {str(e)}")
1269
1560
 
1561
+ def list_networks(self) -> List[Dict]:
1562
+ params = {}
1563
+ try:
1564
+ networks = self.client.networks.list()
1565
+ result = []
1566
+ for net in networks:
1567
+ attrs = net.attrs
1568
+ containers = len(attrs.get("Containers", {}))
1569
+ created = attrs.get("Created", None)
1570
+ created_str = self._parse_timestamp(created)
1571
+ simplified = {
1572
+ "id": attrs.get("Id", "unknown")[7:19],
1573
+ "name": attrs.get("Name", "unknown"),
1574
+ "driver": attrs.get("Driver", "unknown"),
1575
+ "scope": attrs.get("Scope", "unknown"),
1576
+ "containers": containers,
1577
+ "created": created_str,
1578
+ }
1579
+ result.append(simplified)
1580
+ self.log_action("list_networks", params, result)
1581
+ return result
1582
+ except Exception as e:
1583
+ self.log_action("list_networks", params, error=e)
1584
+ raise RuntimeError(f"Failed to list networks: {str(e)}")
1585
+
1586
+ def create_network(self, name: str, driver: str = "bridge") -> Dict:
1587
+ params = {"name": name, "driver": driver}
1588
+ try:
1589
+ network = self.client.networks.create(name, driver=driver)
1590
+ attrs = network.attrs
1591
+ created = attrs.get("Created", None)
1592
+ created_str = self._parse_timestamp(created)
1593
+ result = {
1594
+ "id": attrs.get("Id", "unknown")[7:19],
1595
+ "name": attrs.get("Name", name),
1596
+ "driver": attrs.get("Driver", driver),
1597
+ "scope": attrs.get("Scope", "unknown"),
1598
+ "created": created_str,
1599
+ }
1600
+ self.log_action("create_network", params, result)
1601
+ return result
1602
+ except Exception as e:
1603
+ self.log_action("create_network", params, error=e)
1604
+ raise RuntimeError(f"Failed to create network: {str(e)}")
1605
+
1270
1606
  def remove_network(self, network_id: str) -> Dict:
1271
1607
  params = {"network_id": network_id}
1272
1608
  try:
@@ -1365,21 +1701,24 @@ class PodmanManager(ContainerManagerBase):
1365
1701
  raise NotImplementedError("Swarm not supported in Podman")
1366
1702
 
1367
1703
 
1704
+ def is_app_installed(app_name: str = "docker") -> bool:
1705
+ return shutil.which(app_name.lower()) is not None
1706
+
1707
+
1368
1708
  def create_manager(
1369
1709
  manager_type: Optional[str] = None, silent: bool = False, log_file: str = None
1370
1710
  ) -> ContainerManagerBase:
1371
1711
  if manager_type is None:
1372
- manager_type = os.environ.get("CONTAINER_MANAGER_TYPE")
1712
+ manager_type = os.environ.get("CONTAINER_MANAGER_TYPE", None)
1373
1713
  if manager_type is None:
1374
- # Autodetect
1375
- if PodmanClient is not None:
1714
+ if is_app_installed("podman"):
1376
1715
  try:
1377
1716
  test_client = PodmanClient()
1378
1717
  test_client.close()
1379
1718
  manager_type = "podman"
1380
1719
  except Exception:
1381
1720
  pass
1382
- if manager_type is None and docker is not None:
1721
+ if is_app_installed("docker"):
1383
1722
  try:
1384
1723
  test_client = docker.from_env()
1385
1724
  test_client.close()
@@ -1418,6 +1757,8 @@ Actions:
1418
1757
  --platform <plat> [ Platform, e.g., linux/amd64 ]
1419
1758
  --remove-image <image> [ Remove image ]
1420
1759
  --force [ Force removal (global for remove actions) ]
1760
+ --prune-images [ Prune unused images ]
1761
+ --all [ Prune all unused images ]
1421
1762
  --list-containers [ List containers ]
1422
1763
  --all [ Show all containers ]
1423
1764
  --run-container <image> [ Run container ]
@@ -1431,6 +1772,7 @@ Actions:
1431
1772
  --timeout <sec> [ Timeout, default 10 ]
1432
1773
  --remove-container <id>[ Remove container ]
1433
1774
  --force [ Force ]
1775
+ --prune-containers [ Prune stopped containers ]
1434
1776
  --get-container-logs <id> [ Get logs ]
1435
1777
  --tail <tail> [ Tail lines, default all ]
1436
1778
  --exec-in-container <id> [ Exec command ]
@@ -1440,10 +1782,15 @@ Actions:
1440
1782
  --create-volume <name> [ Create volume ]
1441
1783
  --remove-volume <name> [ Remove volume ]
1442
1784
  --force [ Force ]
1785
+ --prune-volumes [ Prune unused volumes ]
1786
+ --all [ Remove all volumes (dangerous) ]
1443
1787
  --list-networks [ List networks ]
1444
1788
  --create-network <name>[ Create network ]
1445
1789
  --driver <driver> [ Driver, default bridge ]
1446
1790
  --remove-network <id> [ Remove network ]
1791
+ --prune-networks [ Prune unused networks ]
1792
+ --prune-system [ Prune system resources ]
1793
+ --all [ Prune all unused (including volumes, build cache) ]
1447
1794
  --compose-up <file> [ Compose up ]
1448
1795
  --build [ Build images ]
1449
1796
  --detach [ Detach mode, default true ]
@@ -1470,7 +1817,7 @@ container_manager.py --manager docker --pull-image nginx --tag latest --list-con
1470
1817
  )
1471
1818
 
1472
1819
 
1473
- def container_manager(argv):
1820
+ def container_manager():
1474
1821
  parser = argparse.ArgumentParser(
1475
1822
  description="Container Manager: A tool to manage containers with Docker, Podman, and Docker Swarm!"
1476
1823
  )
@@ -1492,7 +1839,7 @@ def container_manager(argv):
1492
1839
  parser.add_argument(
1493
1840
  "--remove-image", type=str, default=None, help="Image to remove"
1494
1841
  )
1495
- parser.add_argument("--force", action="store_true", help="Force removal")
1842
+ parser.add_argument("--prune-images", action="store_true", help="Prune images")
1496
1843
  parser.add_argument(
1497
1844
  "--list-containers", action="store_true", help="List containers"
1498
1845
  )
@@ -1513,6 +1860,9 @@ def container_manager(argv):
1513
1860
  parser.add_argument(
1514
1861
  "--remove-container", type=str, default=None, help="Container to remove"
1515
1862
  )
1863
+ parser.add_argument(
1864
+ "--prune-containers", action="store_true", help="Prune containers"
1865
+ )
1516
1866
  parser.add_argument(
1517
1867
  "--get-container-logs", type=str, default=None, help="Container logs"
1518
1868
  )
@@ -1529,6 +1879,7 @@ def container_manager(argv):
1529
1879
  parser.add_argument(
1530
1880
  "--remove-volume", type=str, default=None, help="Volume to remove"
1531
1881
  )
1882
+ parser.add_argument("--prune-volumes", action="store_true", help="Prune volumes")
1532
1883
  parser.add_argument("--list-networks", action="store_true", help="List networks")
1533
1884
  parser.add_argument(
1534
1885
  "--create-network", type=str, default=None, help="Network to create"
@@ -1537,6 +1888,8 @@ def container_manager(argv):
1537
1888
  parser.add_argument(
1538
1889
  "--remove-network", type=str, default=None, help="Network to remove"
1539
1890
  )
1891
+ parser.add_argument("--prune-networks", action="store_true", help="Prune networks")
1892
+ parser.add_argument("--prune-system", action="store_true", help="Prune system")
1540
1893
  parser.add_argument("--compose-up", type=str, default=None, help="Compose file up")
1541
1894
  parser.add_argument("--build", action="store_true", help="Build images")
1542
1895
  parser.add_argument(
@@ -1566,9 +1919,10 @@ def container_manager(argv):
1566
1919
  parser.add_argument(
1567
1920
  "--remove-service", type=str, default=None, help="Service to remove"
1568
1921
  )
1922
+ parser.add_argument("--force", action="store_true", help="Force removal")
1569
1923
  parser.add_argument("-h", "--help", action="store_true", help="Show help")
1570
1924
 
1571
- args = parser.parse_args(argv)
1925
+ args = parser.parse_args()
1572
1926
 
1573
1927
  if args.help:
1574
1928
  usage()
@@ -1583,9 +1937,11 @@ def container_manager(argv):
1583
1937
  platform = args.platform
1584
1938
  remove_image = args.remove_image is not None
1585
1939
  remove_image_str = args.remove_image
1940
+ prune_images = args.prune_images
1941
+ prune_images_all = args.all if prune_images else False
1586
1942
  force = args.force
1587
1943
  list_containers = args.list_containers
1588
- all_containers = args.all
1944
+ all_containers = args.all if list_containers else False
1589
1945
  run_container = args.run_container is not None
1590
1946
  run_image = args.run_container
1591
1947
  name = args.name
@@ -1599,6 +1955,7 @@ def container_manager(argv):
1599
1955
  timeout = args.timeout
1600
1956
  remove_container = args.remove_container is not None
1601
1957
  remove_container_id = args.remove_container
1958
+ prune_containers = args.prune_containers
1602
1959
  get_container_logs = args.get_container_logs is not None
1603
1960
  container_logs_id = args.get_container_logs
1604
1961
  tail = args.tail
@@ -1611,12 +1968,17 @@ def container_manager(argv):
1611
1968
  create_volume_name = args.create_volume
1612
1969
  remove_volume = args.remove_volume is not None
1613
1970
  remove_volume_name = args.remove_volume
1971
+ prune_volumes = args.prune_volumes
1972
+ prune_volumes_all = args.all if prune_volumes else False
1614
1973
  list_networks = args.list_networks
1615
1974
  create_network = args.create_network is not None
1616
1975
  create_network_name = args.create_network
1617
1976
  driver = args.driver
1618
1977
  remove_network = args.remove_network is not None
1619
1978
  remove_network_id = args.remove_network
1979
+ prune_networks = args.prune_networks
1980
+ prune_system = args.prune_system
1981
+ prune_system_all = args.all if prune_system else False
1620
1982
  compose_up = args.compose_up is not None
1621
1983
  compose_up_file = args.compose_up
1622
1984
  compose_build = args.build
@@ -1665,6 +2027,9 @@ def container_manager(argv):
1665
2027
  raise ValueError("Image required for remove-image")
1666
2028
  print(json.dumps(manager.remove_image(remove_image_str, force), indent=2))
1667
2029
 
2030
+ if prune_images:
2031
+ print(json.dumps(manager.prune_images(force, prune_images_all), indent=2))
2032
+
1668
2033
  if list_containers:
1669
2034
  print(json.dumps(manager.list_containers(all_containers), indent=2))
1670
2035
 
@@ -1710,6 +2075,9 @@ def container_manager(argv):
1710
2075
  json.dumps(manager.remove_container(remove_container_id, force), indent=2)
1711
2076
  )
1712
2077
 
2078
+ if prune_containers:
2079
+ print(json.dumps(manager.prune_containers(), indent=2))
2080
+
1713
2081
  if get_container_logs:
1714
2082
  if not container_logs_id:
1715
2083
  raise ValueError("Container ID required for get-container-logs")
@@ -1739,6 +2107,9 @@ def container_manager(argv):
1739
2107
  raise ValueError("Name required for remove-volume")
1740
2108
  print(json.dumps(manager.remove_volume(remove_volume_name, force), indent=2))
1741
2109
 
2110
+ if prune_volumes:
2111
+ print(json.dumps(manager.prune_volumes(force, prune_volumes_all), indent=2))
2112
+
1742
2113
  if list_networks:
1743
2114
  print(json.dumps(manager.list_networks(), indent=2))
1744
2115
 
@@ -1752,6 +2123,12 @@ def container_manager(argv):
1752
2123
  raise ValueError("ID required for remove-network")
1753
2124
  print(json.dumps(manager.remove_network(remove_network_id), indent=2))
1754
2125
 
2126
+ if prune_networks:
2127
+ print(json.dumps(manager.prune_networks(), indent=2))
2128
+
2129
+ if prune_system:
2130
+ print(json.dumps(manager.prune_system(force, prune_system_all), indent=2))
2131
+
1755
2132
  if compose_up:
1756
2133
  if not compose_up_file:
1757
2134
  raise ValueError("File required for compose-up")
@@ -1819,4 +2196,4 @@ if __name__ == "__main__":
1819
2196
  if len(sys.argv) < 2:
1820
2197
  usage()
1821
2198
  sys.exit(2)
1822
- container_manager(sys.argv[1:])
2199
+ container_manager()