container-manager-mcp 0.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.
@@ -0,0 +1,1381 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ import sys
5
+ import logging
6
+ import os
7
+ from abc import ABC, abstractmethod
8
+ from typing import List, Dict, Optional, Any
9
+ import getopt
10
+ import json
11
+ import subprocess
12
+
13
+ try:
14
+ import docker
15
+ from docker.errors import DockerException
16
+ except ImportError:
17
+ docker = None
18
+ DockerException = Exception
19
+
20
+ try:
21
+ from podman import PodmanClient
22
+ from podman.errors import PodmanError
23
+ except ImportError:
24
+ PodmanClient = None
25
+ PodmanError = Exception
26
+
27
+
28
+ class ContainerManagerBase(ABC):
29
+ def __init__(self, silent: bool = False, log_file: str = None):
30
+ self.silent = silent
31
+ self.setup_logging(log_file)
32
+
33
+ def setup_logging(self, log_file: str):
34
+ if not log_file:
35
+ script_dir = os.path.dirname(os.path.abspath(__file__))
36
+ log_file = os.path.join(script_dir, "container_manager.log")
37
+ logging.basicConfig(
38
+ filename=log_file,
39
+ level=logging.INFO,
40
+ format="%(asctime)s - %(levelname)s - %(message)s",
41
+ )
42
+ self.logger = logging.getLogger(__name__)
43
+ self.logger.info(f"Logging initialized to {log_file}")
44
+
45
+ def log_action(
46
+ self,
47
+ action: str,
48
+ params: Dict = None,
49
+ result: Any = None,
50
+ error: Exception = None,
51
+ ):
52
+ self.logger.info(f"Performing action: {action} with params: {params}")
53
+ if result:
54
+ self.logger.info(f"Result: {result}")
55
+ if error:
56
+ self.logger.error(f"Error: {str(error)}")
57
+
58
+ @abstractmethod
59
+ def get_version(self) -> Dict:
60
+ pass
61
+
62
+ @abstractmethod
63
+ def get_info(self) -> Dict:
64
+ pass
65
+
66
+ @abstractmethod
67
+ def list_images(self) -> List[Dict]:
68
+ pass
69
+
70
+ @abstractmethod
71
+ def pull_image(
72
+ self, image: str, tag: str = "latest", platform: Optional[str] = None
73
+ ) -> Dict:
74
+ pass
75
+
76
+ @abstractmethod
77
+ def remove_image(self, image: str, force: bool = False) -> Dict:
78
+ pass
79
+
80
+ @abstractmethod
81
+ def list_containers(self, all: bool = False) -> List[Dict]:
82
+ pass
83
+
84
+ @abstractmethod
85
+ def run_container(
86
+ self,
87
+ image: str,
88
+ name: Optional[str] = None,
89
+ command: Optional[str] = None,
90
+ detach: bool = False,
91
+ ports: Optional[Dict[str, str]] = None,
92
+ volumes: Optional[Dict[str, Dict]] = None,
93
+ environment: Optional[Dict[str, str]] = None,
94
+ ) -> Dict:
95
+ pass
96
+
97
+ @abstractmethod
98
+ def stop_container(self, container_id: str, timeout: int = 10) -> Dict:
99
+ pass
100
+
101
+ @abstractmethod
102
+ def remove_container(self, container_id: str, force: bool = False) -> Dict:
103
+ pass
104
+
105
+ @abstractmethod
106
+ def get_container_logs(self, container_id: str, tail: str = "all") -> str:
107
+ pass
108
+
109
+ @abstractmethod
110
+ def exec_in_container(
111
+ self, container_id: str, command: List[str], detach: bool = False
112
+ ) -> Dict:
113
+ pass
114
+
115
+ @abstractmethod
116
+ def list_volumes(self) -> Dict:
117
+ pass
118
+
119
+ @abstractmethod
120
+ def create_volume(self, name: str) -> Dict:
121
+ pass
122
+
123
+ @abstractmethod
124
+ def remove_volume(self, name: str, force: bool = False) -> Dict:
125
+ pass
126
+
127
+ @abstractmethod
128
+ def list_networks(self) -> List[Dict]:
129
+ pass
130
+
131
+ @abstractmethod
132
+ def create_network(self, name: str, driver: str = "bridge") -> Dict:
133
+ pass
134
+
135
+ @abstractmethod
136
+ def remove_network(self, network_id: str) -> Dict:
137
+ pass
138
+
139
+ # Compose methods
140
+ @abstractmethod
141
+ def compose_up(
142
+ self, compose_file: str, detach: bool = True, build: bool = False
143
+ ) -> str:
144
+ pass
145
+
146
+ @abstractmethod
147
+ def compose_down(self, compose_file: str) -> str:
148
+ pass
149
+
150
+ @abstractmethod
151
+ def compose_ps(self, compose_file: str) -> str:
152
+ pass
153
+
154
+ @abstractmethod
155
+ def compose_logs(self, compose_file: str, service: Optional[str] = None) -> str:
156
+ pass
157
+
158
+ # Swarm methods (to be implemented only in DockerManager)
159
+ def init_swarm(self, advertise_addr: Optional[str] = None) -> Dict:
160
+ raise NotImplementedError("Swarm not supported")
161
+
162
+ def leave_swarm(self, force: bool = False) -> Dict:
163
+ raise NotImplementedError("Swarm not supported")
164
+
165
+ def list_nodes(self) -> List[Dict]:
166
+ raise NotImplementedError("Swarm not supported")
167
+
168
+ def list_services(self) -> List[Dict]:
169
+ raise NotImplementedError("Swarm not supported")
170
+
171
+ def create_service(
172
+ self,
173
+ name: str,
174
+ image: str,
175
+ replicas: int = 1,
176
+ ports: Optional[Dict[str, str]] = None,
177
+ mounts: Optional[List[str]] = None,
178
+ ) -> Dict:
179
+ raise NotImplementedError("Swarm not supported")
180
+
181
+ def remove_service(self, service_id: str) -> Dict:
182
+ raise NotImplementedError("Swarm not supported")
183
+
184
+
185
+ class DockerManager(ContainerManagerBase):
186
+ def __init__(self, silent: bool = False, log_file: str = None):
187
+ super().__init__(silent, log_file)
188
+ if docker is None:
189
+ raise ImportError("Please install docker-py: pip install docker")
190
+ try:
191
+ self.client = docker.from_env()
192
+ except DockerException as e:
193
+ self.logger.error(f"Failed to connect to Docker daemon: {str(e)}")
194
+ raise RuntimeError(f"Failed to connect to Docker: {str(e)}")
195
+
196
+ def get_version(self) -> Dict:
197
+ params = {}
198
+ try:
199
+ result = self.client.version()
200
+ self.log_action("get_version", params, result)
201
+ return result
202
+ except Exception as e:
203
+ self.log_action("get_version", params, error=e)
204
+ raise RuntimeError(f"Failed to get version: {str(e)}")
205
+
206
+ def get_info(self) -> Dict:
207
+ params = {}
208
+ try:
209
+ result = self.client.info()
210
+ self.log_action("get_info", params, result)
211
+ return result
212
+ except Exception as e:
213
+ self.log_action("get_info", params, error=e)
214
+ raise RuntimeError(f"Failed to get info: {str(e)}")
215
+
216
+ def list_images(self) -> List[Dict]:
217
+ params = {}
218
+ try:
219
+ images = self.client.images.list()
220
+ result = [img.attrs for img in images]
221
+ self.log_action("list_images", params, result)
222
+ return result
223
+ except Exception as e:
224
+ self.log_action("list_images", params, error=e)
225
+ raise RuntimeError(f"Failed to list images: {str(e)}")
226
+
227
+ def pull_image(
228
+ self, image: str, tag: str = "latest", platform: Optional[str] = None
229
+ ) -> Dict:
230
+ params = {"image": image, "tag": tag, "platform": platform}
231
+ try:
232
+ img = self.client.images.pull(f"{image}:{tag}", platform=platform)
233
+ result = img.attrs
234
+ self.log_action("pull_image", params, result)
235
+ return result
236
+ except Exception as e:
237
+ self.log_action("pull_image", params, error=e)
238
+ raise RuntimeError(f"Failed to pull image: {str(e)}")
239
+
240
+ def remove_image(self, image: str, force: bool = False) -> Dict:
241
+ params = {"image": image, "force": force}
242
+ try:
243
+ self.client.images.remove(image, force=force)
244
+ result = {"removed": image}
245
+ self.log_action("remove_image", params, result)
246
+ return result
247
+ except Exception as e:
248
+ self.log_action("remove_image", params, error=e)
249
+ raise RuntimeError(f"Failed to remove image: {str(e)}")
250
+
251
+ def list_containers(self, all: bool = False) -> List[Dict]:
252
+ params = {"all": all}
253
+ try:
254
+ containers = self.client.containers.list(all=all)
255
+ result = [c.attrs for c in containers]
256
+ self.log_action("list_containers", params, result)
257
+ return result
258
+ except Exception as e:
259
+ self.log_action("list_containers", params, error=e)
260
+ raise RuntimeError(f"Failed to list containers: {str(e)}")
261
+
262
+ def run_container(
263
+ self,
264
+ image: str,
265
+ name: Optional[str] = None,
266
+ command: Optional[str] = None,
267
+ detach: bool = False,
268
+ ports: Optional[Dict[str, str]] = None,
269
+ volumes: Optional[Dict[str, Dict]] = None,
270
+ environment: Optional[Dict[str, str]] = None,
271
+ ) -> Dict:
272
+ params = {
273
+ "image": image,
274
+ "name": name,
275
+ "command": command,
276
+ "detach": detach,
277
+ "ports": ports,
278
+ "volumes": volumes,
279
+ "environment": environment,
280
+ }
281
+ try:
282
+ container = self.client.containers.run(
283
+ image,
284
+ command=command,
285
+ name=name,
286
+ detach=detach,
287
+ ports=ports,
288
+ volumes=volumes,
289
+ environment=environment,
290
+ )
291
+ result = (
292
+ container.attrs if detach else {"output": container.decode("utf-8")}
293
+ )
294
+ self.log_action("run_container", params, result)
295
+ return result
296
+ except Exception as e:
297
+ self.log_action("run_container", params, error=e)
298
+ raise RuntimeError(f"Failed to run container: {str(e)}")
299
+
300
+ def stop_container(self, container_id: str, timeout: int = 10) -> Dict:
301
+ params = {"container_id": container_id, "timeout": timeout}
302
+ try:
303
+ container = self.client.containers.get(container_id)
304
+ container.stop(timeout=timeout)
305
+ result = {"stopped": container_id}
306
+ self.log_action("stop_container", params, result)
307
+ return result
308
+ except Exception as e:
309
+ self.log_action("stop_container", params, error=e)
310
+ raise RuntimeError(f"Failed to stop container: {str(e)}")
311
+
312
+ def remove_container(self, container_id: str, force: bool = False) -> Dict:
313
+ params = {"container_id": container_id, "force": force}
314
+ try:
315
+ container = self.client.containers.get(container_id)
316
+ container.remove(force=force)
317
+ result = {"removed": container_id}
318
+ self.log_action("remove_container", params, result)
319
+ return result
320
+ except Exception as e:
321
+ self.log_action("remove_container", params, error=e)
322
+ raise RuntimeError(f"Failed to remove container: {str(e)}")
323
+
324
+ def get_container_logs(self, container_id: str, tail: str = "all") -> str:
325
+ params = {"container_id": container_id, "tail": tail}
326
+ try:
327
+ container = self.client.containers.get(container_id)
328
+ logs = container.logs(tail=tail).decode("utf-8")
329
+ self.log_action("get_container_logs", params, logs)
330
+ return logs
331
+ except Exception as e:
332
+ self.log_action("get_container_logs", params, error=e)
333
+ raise RuntimeError(f"Failed to get container logs: {str(e)}")
334
+
335
+ def exec_in_container(
336
+ self, container_id: str, command: List[str], detach: bool = False
337
+ ) -> Dict:
338
+ params = {"container_id": container_id, "command": command, "detach": detach}
339
+ try:
340
+ container = self.client.containers.get(container_id)
341
+ exit_code, output = container.exec_run(command, detach=detach)
342
+ result = {
343
+ "exit_code": exit_code,
344
+ "output": output.decode("utf-8") if output else None,
345
+ }
346
+ self.log_action("exec_in_container", params, result)
347
+ return result
348
+ except Exception as e:
349
+ self.log_action("exec_in_container", params, error=e)
350
+ raise RuntimeError(f"Failed to exec in container: {str(e)}")
351
+
352
+ def list_volumes(self) -> Dict:
353
+ params = {}
354
+ try:
355
+ volumes = self.client.volumes.list()
356
+ result = {"volumes": [v.attrs for v in volumes]}
357
+ self.log_action("list_volumes", params, result)
358
+ return result
359
+ except Exception as e:
360
+ self.log_action("list_volumes", params, error=e)
361
+ raise RuntimeError(f"Failed to list volumes: {str(e)}")
362
+
363
+ def create_volume(self, name: str) -> Dict:
364
+ params = {"name": name}
365
+ try:
366
+ volume = self.client.volumes.create(name=name)
367
+ result = volume.attrs
368
+ self.log_action("create_volume", params, result)
369
+ return result
370
+ except Exception as e:
371
+ self.log_action("create_volume", params, error=e)
372
+ raise RuntimeError(f"Failed to create volume: {str(e)}")
373
+
374
+ def remove_volume(self, name: str, force: bool = False) -> Dict:
375
+ params = {"name": name, "force": force}
376
+ try:
377
+ volume = self.client.volumes.get(name)
378
+ volume.remove(force=force)
379
+ result = {"removed": name}
380
+ self.log_action("remove_volume", params, result)
381
+ return result
382
+ except Exception as e:
383
+ self.log_action("remove_volume", params, error=e)
384
+ raise RuntimeError(f"Failed to remove volume: {str(e)}")
385
+
386
+ def list_networks(self) -> List[Dict]:
387
+ params = {}
388
+ try:
389
+ networks = self.client.networks.list()
390
+ result = [net.attrs for net in networks]
391
+ self.log_action("list_networks", params, result)
392
+ return result
393
+ except Exception as e:
394
+ self.log_action("list_networks", params, error=e)
395
+ raise RuntimeError(f"Failed to list networks: {str(e)}")
396
+
397
+ def create_network(self, name: str, driver: str = "bridge") -> Dict:
398
+ params = {"name": name, "driver": driver}
399
+ try:
400
+ network = self.client.networks.create(name, driver=driver)
401
+ result = network.attrs
402
+ self.log_action("create_network", params, result)
403
+ return result
404
+ except Exception as e:
405
+ self.log_action("create_network", params, error=e)
406
+ raise RuntimeError(f"Failed to create network: {str(e)}")
407
+
408
+ def remove_network(self, network_id: str) -> Dict:
409
+ params = {"network_id": network_id}
410
+ try:
411
+ network = self.client.networks.get(network_id)
412
+ network.remove()
413
+ result = {"removed": network_id}
414
+ self.log_action("remove_network", params, result)
415
+ return result
416
+ except Exception as e:
417
+ self.log_action("remove_network", params, error=e)
418
+ raise RuntimeError(f"Failed to remove network: {str(e)}")
419
+
420
+ def compose_up(
421
+ self, compose_file: str, detach: bool = True, build: bool = False
422
+ ) -> str:
423
+ params = {"compose_file": compose_file, "detach": detach, "build": build}
424
+ try:
425
+ cmd = ["docker", "compose", "-f", compose_file, "up"]
426
+ if build:
427
+ cmd.append("--build")
428
+ if detach:
429
+ cmd.append("-d")
430
+ result = subprocess.run(cmd, capture_output=True, text=True)
431
+ if result.returncode != 0:
432
+ raise RuntimeError(result.stderr)
433
+ self.log_action("compose_up", params, result.stdout)
434
+ return result.stdout
435
+ except Exception as e:
436
+ self.log_action("compose_up", params, error=e)
437
+ raise RuntimeError(f"Failed to compose up: {str(e)}")
438
+
439
+ def compose_down(self, compose_file: str) -> str:
440
+ params = {"compose_file": compose_file}
441
+ try:
442
+ cmd = ["docker", "compose", "-f", compose_file, "down"]
443
+ result = subprocess.run(cmd, capture_output=True, text=True)
444
+ if result.returncode != 0:
445
+ raise RuntimeError(result.stderr)
446
+ self.log_action("compose_down", params, result.stdout)
447
+ return result.stdout
448
+ except Exception as e:
449
+ self.log_action("compose_down", params, error=e)
450
+ raise RuntimeError(f"Failed to compose down: {str(e)}")
451
+
452
+ def compose_ps(self, compose_file: str) -> str:
453
+ params = {"compose_file": compose_file}
454
+ try:
455
+ cmd = ["docker", "compose", "-f", compose_file, "ps"]
456
+ result = subprocess.run(cmd, capture_output=True, text=True)
457
+ if result.returncode != 0:
458
+ raise RuntimeError(result.stderr)
459
+ self.log_action("compose_ps", params, result.stdout)
460
+ return result.stdout
461
+ except Exception as e:
462
+ self.log_action("compose_ps", params, error=e)
463
+ raise RuntimeError(f"Failed to compose ps: {str(e)}")
464
+
465
+ def compose_logs(self, compose_file: str, service: Optional[str] = None) -> str:
466
+ params = {"compose_file": compose_file, "service": service}
467
+ try:
468
+ cmd = ["docker", "compose", "-f", compose_file, "logs"]
469
+ if service:
470
+ cmd.append(service)
471
+ result = subprocess.run(cmd, capture_output=True, text=True)
472
+ if result.returncode != 0:
473
+ raise RuntimeError(result.stderr)
474
+ self.log_action("compose_logs", params, result.stdout)
475
+ return result.stdout
476
+ except Exception as e:
477
+ self.log_action("compose_logs", params, error=e)
478
+ raise RuntimeError(f"Failed to compose logs: {str(e)}")
479
+
480
+ def init_swarm(self, advertise_addr: Optional[str] = None) -> Dict:
481
+ params = {"advertise_addr": advertise_addr}
482
+ try:
483
+ swarm_id = self.client.swarm.init(advertise_addr=advertise_addr)
484
+ result = {"swarm_id": swarm_id}
485
+ self.log_action("init_swarm", params, result)
486
+ return result
487
+ except Exception as e:
488
+ self.log_action("init_swarm", params, error=e)
489
+ raise RuntimeError(f"Failed to init swarm: {str(e)}")
490
+
491
+ def leave_swarm(self, force: bool = False) -> Dict:
492
+ params = {"force": force}
493
+ try:
494
+ self.client.swarm.leave(force=force)
495
+ result = {"left": True}
496
+ self.log_action("leave_swarm", params, result)
497
+ return result
498
+ except Exception as e:
499
+ self.log_action("leave_swarm", params, error=e)
500
+ raise RuntimeError(f"Failed to leave swarm: {str(e)}")
501
+
502
+ def list_nodes(self) -> List[Dict]:
503
+ params = {}
504
+ try:
505
+ nodes = self.client.nodes.list()
506
+ result = [node.attrs for node in nodes]
507
+ self.log_action("list_nodes", params, result)
508
+ return result
509
+ except Exception as e:
510
+ self.log_action("list_nodes", params, error=e)
511
+ raise RuntimeError(f"Failed to list nodes: {str(e)}")
512
+
513
+ def list_services(self) -> List[Dict]:
514
+ params = {}
515
+ try:
516
+ services = self.client.services.list()
517
+ result = [service.attrs for service in services]
518
+ self.log_action("list_services", params, result)
519
+ return result
520
+ except Exception as e:
521
+ self.log_action("list_services", params, error=e)
522
+ raise RuntimeError(f"Failed to list services: {str(e)}")
523
+
524
+ def create_service(
525
+ self,
526
+ name: str,
527
+ image: str,
528
+ replicas: int = 1,
529
+ ports: Optional[Dict[str, str]] = None,
530
+ mounts: Optional[List[str]] = None,
531
+ ) -> Dict:
532
+ params = {
533
+ "name": name,
534
+ "image": image,
535
+ "replicas": replicas,
536
+ "ports": ports,
537
+ "mounts": mounts,
538
+ }
539
+ try:
540
+ mode = {"mode": "replicated", "replicas": replicas}
541
+ target_ports = [docker.types.EndpointSpec(ports=ports)] if ports else None
542
+ service = self.client.services.create(
543
+ image,
544
+ name=name,
545
+ mode=mode,
546
+ mounts=mounts,
547
+ endpoint_spec=target_ports[0] if target_ports else None,
548
+ )
549
+ result = service.attrs
550
+ self.log_action("create_service", params, result)
551
+ return result
552
+ except Exception as e:
553
+ self.log_action("create_service", params, error=e)
554
+ raise RuntimeError(f"Failed to create service: {str(e)}")
555
+
556
+ def remove_service(self, service_id: str) -> Dict:
557
+ params = {"service_id": service_id}
558
+ try:
559
+ service = self.client.services.get(service_id)
560
+ service.remove()
561
+ result = {"removed": service_id}
562
+ self.log_action("remove_service", params, result)
563
+ return result
564
+ except Exception as e:
565
+ self.log_action("remove_service", params, error=e)
566
+ raise RuntimeError(f"Failed to remove service: {str(e)}")
567
+
568
+
569
+ class PodmanManager(ContainerManagerBase):
570
+ def __init__(self, silent: bool = False, log_file: str = None):
571
+ super().__init__(silent, log_file)
572
+ if PodmanClient is None:
573
+ raise ImportError("Please install podman-py: pip install podman")
574
+ try:
575
+ self.client = PodmanClient()
576
+ except PodmanError as e:
577
+ self.logger.error(f"Failed to connect to Podman daemon: {str(e)}")
578
+ raise RuntimeError(f"Failed to connect to Podman: {str(e)}")
579
+
580
+ def get_version(self) -> Dict:
581
+ params = {}
582
+ try:
583
+ result = self.client.version()
584
+ self.log_action("get_version", params, result)
585
+ return result
586
+ except Exception as e:
587
+ self.log_action("get_version", params, error=e)
588
+ raise RuntimeError(f"Failed to get version: {str(e)}")
589
+
590
+ def get_info(self) -> Dict:
591
+ params = {}
592
+ try:
593
+ result = self.client.info()
594
+ self.log_action("get_info", params, result)
595
+ return result
596
+ except Exception as e:
597
+ self.log_action("get_info", params, error=e)
598
+ raise RuntimeError(f"Failed to get info: {str(e)}")
599
+
600
+ def list_images(self) -> List[Dict]:
601
+ params = {}
602
+ try:
603
+ images = self.client.images.list()
604
+ result = [img.attrs for img in images]
605
+ self.log_action("list_images", params, result)
606
+ return result
607
+ except Exception as e:
608
+ self.log_action("list_images", params, error=e)
609
+ raise RuntimeError(f"Failed to list images: {str(e)}")
610
+
611
+ def pull_image(
612
+ self, image: str, tag: str = "latest", platform: Optional[str] = None
613
+ ) -> Dict:
614
+ params = {"image": image, "tag": tag, "platform": platform}
615
+ try:
616
+ img = self.client.images.pull(f"{image}:{tag}", platform=platform)
617
+ result = img[0].attrs if isinstance(img, list) else img.attrs
618
+ self.log_action("pull_image", params, result)
619
+ return result
620
+ except Exception as e:
621
+ self.log_action("pull_image", params, error=e)
622
+ raise RuntimeError(f"Failed to pull image: {str(e)}")
623
+
624
+ def remove_image(self, image: str, force: bool = False) -> Dict:
625
+ params = {"image": image, "force": force}
626
+ try:
627
+ self.client.images.remove(image, force=force)
628
+ result = {"removed": image}
629
+ self.log_action("remove_image", params, result)
630
+ return result
631
+ except Exception as e:
632
+ self.log_action("remove_image", params, error=e)
633
+ raise RuntimeError(f"Failed to remove image: {str(e)}")
634
+
635
+ def list_containers(self, all: bool = False) -> List[Dict]:
636
+ params = {"all": all}
637
+ try:
638
+ containers = self.client.containers.list(all=all)
639
+ result = [c.attrs for c in containers]
640
+ self.log_action("list_containers", params, result)
641
+ return result
642
+ except Exception as e:
643
+ self.log_action("list_containers", params, error=e)
644
+ raise RuntimeError(f"Failed to list containers: {str(e)}")
645
+
646
+ def run_container(
647
+ self,
648
+ image: str,
649
+ name: Optional[str] = None,
650
+ command: Optional[str] = None,
651
+ detach: bool = False,
652
+ ports: Optional[Dict[str, str]] = None,
653
+ volumes: Optional[Dict[str, Dict]] = None,
654
+ environment: Optional[Dict[str, str]] = None,
655
+ ) -> Dict:
656
+ params = {
657
+ "image": image,
658
+ "name": name,
659
+ "command": command,
660
+ "detach": detach,
661
+ "ports": ports,
662
+ "volumes": volumes,
663
+ "environment": environment,
664
+ }
665
+ try:
666
+ container = self.client.containers.run(
667
+ image,
668
+ command=command,
669
+ name=name,
670
+ detach=detach,
671
+ ports=ports,
672
+ volumes=volumes,
673
+ environment=environment,
674
+ )
675
+ result = (
676
+ container.attrs if detach else {"output": container.decode("utf-8")}
677
+ )
678
+ self.log_action("run_container", params, result)
679
+ return result
680
+ except Exception as e:
681
+ self.log_action("run_container", params, error=e)
682
+ raise RuntimeError(f"Failed to run container: {str(e)}")
683
+
684
+ def stop_container(self, container_id: str, timeout: int = 10) -> Dict:
685
+ params = {"container_id": container_id, "timeout": timeout}
686
+ try:
687
+ container = self.client.containers.get(container_id)
688
+ container.stop(timeout=timeout)
689
+ result = {"stopped": container_id}
690
+ self.log_action("stop_container", params, result)
691
+ return result
692
+ except Exception as e:
693
+ self.log_action("stop_container", params, error=e)
694
+ raise RuntimeError(f"Failed to stop container: {str(e)}")
695
+
696
+ def remove_container(self, container_id: str, force: bool = False) -> Dict:
697
+ params = {"container_id": container_id, "force": force}
698
+ try:
699
+ container = self.client.containers.get(container_id)
700
+ container.remove(force=force)
701
+ result = {"removed": container_id}
702
+ self.log_action("remove_container", params, result)
703
+ return result
704
+ except Exception as e:
705
+ self.log_action("remove_container", params, error=e)
706
+ raise RuntimeError(f"Failed to remove container: {str(e)}")
707
+
708
+ def get_container_logs(self, container_id: str, tail: str = "all") -> str:
709
+ params = {"container_id": container_id, "tail": tail}
710
+ try:
711
+ container = self.client.containers.get(container_id)
712
+ logs = container.logs(tail=tail).decode("utf-8")
713
+ self.log_action("get_container_logs", params, logs)
714
+ return logs
715
+ except Exception as e:
716
+ self.log_action("get_container_logs", params, error=e)
717
+ raise RuntimeError(f"Failed to get container logs: {str(e)}")
718
+
719
+ def exec_in_container(
720
+ self, container_id: str, command: List[str], detach: bool = False
721
+ ) -> Dict:
722
+ params = {"container_id": container_id, "command": command, "detach": detach}
723
+ try:
724
+ container = self.client.containers.get(container_id)
725
+ exit_code, output = container.exec_run(command, detach=detach)
726
+ result = {
727
+ "exit_code": exit_code,
728
+ "output": output.decode("utf-8") if output else None,
729
+ }
730
+ self.log_action("exec_in_container", params, result)
731
+ return result
732
+ except Exception as e:
733
+ self.log_action("exec_in_container", params, error=e)
734
+ raise RuntimeError(f"Failed to exec in container: {str(e)}")
735
+
736
+ def list_volumes(self) -> Dict:
737
+ params = {}
738
+ try:
739
+ volumes = self.client.volumes.list()
740
+ result = {"volumes": [v.attrs for v in volumes]}
741
+ self.log_action("list_volumes", params, result)
742
+ return result
743
+ except Exception as e:
744
+ self.log_action("list_volumes", params, error=e)
745
+ raise RuntimeError(f"Failed to list volumes: {str(e)}")
746
+
747
+ def create_volume(self, name: str) -> Dict:
748
+ params = {"name": name}
749
+ try:
750
+ volume = self.client.volumes.create(name=name)
751
+ result = volume.attrs
752
+ self.log_action("create_volume", params, result)
753
+ return result
754
+ except Exception as e:
755
+ self.log_action("create_volume", params, error=e)
756
+ raise RuntimeError(f"Failed to create volume: {str(e)}")
757
+
758
+ def remove_volume(self, name: str, force: bool = False) -> Dict:
759
+ params = {"name": name, "force": force}
760
+ try:
761
+ volume = self.client.volumes.get(name)
762
+ volume.remove(force=force)
763
+ result = {"removed": name}
764
+ self.log_action("remove_volume", params, result)
765
+ return result
766
+ except Exception as e:
767
+ self.log_action("remove_volume", params, error=e)
768
+ raise RuntimeError(f"Failed to remove volume: {str(e)}")
769
+
770
+ def list_networks(self) -> List[Dict]:
771
+ params = {}
772
+ try:
773
+ networks = self.client.networks.list()
774
+ result = [net.attrs for net in networks]
775
+ self.log_action("list_networks", params, result)
776
+ return result
777
+ except Exception as e:
778
+ self.log_action("list_networks", params, error=e)
779
+ raise RuntimeError(f"Failed to list networks: {str(e)}")
780
+
781
+ def create_network(self, name: str, driver: str = "bridge") -> Dict:
782
+ params = {"name": name, "driver": driver}
783
+ try:
784
+ network = self.client.networks.create(name, driver=driver)
785
+ result = network.attrs
786
+ self.log_action("create_network", params, result)
787
+ return result
788
+ except Exception as e:
789
+ self.log_action("create_network", params, error=e)
790
+ raise RuntimeError(f"Failed to create network: {str(e)}")
791
+
792
+ def remove_network(self, network_id: str) -> Dict:
793
+ params = {"network_id": network_id}
794
+ try:
795
+ network = self.client.networks.get(network_id)
796
+ network.remove()
797
+ result = {"removed": network_id}
798
+ self.log_action("remove_network", params, result)
799
+ return result
800
+ except Exception as e:
801
+ self.log_action("remove_network", params, error=e)
802
+ raise RuntimeError(f"Failed to remove network: {str(e)}")
803
+
804
+ def compose_up(
805
+ self, compose_file: str, detach: bool = True, build: bool = False
806
+ ) -> str:
807
+ params = {"compose_file": compose_file, "detach": detach, "build": build}
808
+ try:
809
+ cmd = ["podman-compose", "-f", compose_file, "up"]
810
+ if build:
811
+ cmd.append("--build")
812
+ if detach:
813
+ cmd.append("-d")
814
+ result = subprocess.run(cmd, capture_output=True, text=True)
815
+ if result.returncode != 0:
816
+ raise RuntimeError(result.stderr)
817
+ self.log_action("compose_up", params, result.stdout)
818
+ return result.stdout
819
+ except Exception as e:
820
+ self.log_action("compose_up", params, error=e)
821
+ raise RuntimeError(f"Failed to compose up: {str(e)}")
822
+
823
+ def compose_down(self, compose_file: str) -> str:
824
+ params = {"compose_file": compose_file}
825
+ try:
826
+ cmd = ["podman-compose", "-f", compose_file, "down"]
827
+ result = subprocess.run(cmd, capture_output=True, text=True)
828
+ if result.returncode != 0:
829
+ raise RuntimeError(result.stderr)
830
+ self.log_action("compose_down", params, result.stdout)
831
+ return result.stdout
832
+ except Exception as e:
833
+ self.log_action("compose_down", params, error=e)
834
+ raise RuntimeError(f"Failed to compose down: {str(e)}")
835
+
836
+ def compose_ps(self, compose_file: str) -> str:
837
+ params = {"compose_file": compose_file}
838
+ try:
839
+ cmd = ["podman-compose", "-f", compose_file, "ps"]
840
+ result = subprocess.run(cmd, capture_output=True, text=True)
841
+ if result.returncode != 0:
842
+ raise RuntimeError(result.stderr)
843
+ self.log_action("compose_ps", params, result.stdout)
844
+ return result.stdout
845
+ except Exception as e:
846
+ self.log_action("compose_ps", params, error=e)
847
+ raise RuntimeError(f"Failed to compose ps: {str(e)}")
848
+
849
+ def compose_logs(self, compose_file: str, service: Optional[str] = None) -> str:
850
+ params = {"compose_file": compose_file, "service": service}
851
+ try:
852
+ cmd = ["podman-compose", "-f", compose_file, "logs"]
853
+ if service:
854
+ cmd.append(service)
855
+ result = subprocess.run(cmd, capture_output=True, text=True)
856
+ if result.returncode != 0:
857
+ raise RuntimeError(result.stderr)
858
+ self.log_action("compose_logs", params, result.stdout)
859
+ return result.stdout
860
+ except Exception as e:
861
+ self.log_action("compose_logs", params, error=e)
862
+ raise RuntimeError(f"Failed to compose logs: {str(e)}")
863
+
864
+
865
+ def create_manager(
866
+ manager_type: str, silent: bool = False, log_file: str = None
867
+ ) -> ContainerManagerBase:
868
+ if manager_type.lower() == "docker" or manager_type.lower() == "swarm":
869
+ return DockerManager(silent=silent, log_file=log_file)
870
+ elif manager_type.lower() == "podman":
871
+ return PodmanManager(silent=silent, log_file=log_file)
872
+ else:
873
+ raise ValueError(f"Unsupported container manager type: {manager_type}")
874
+
875
+
876
+ def usage():
877
+ print(
878
+ """
879
+ Container Manager: A tool to manage containers with Docker, Podman, and Docker Swarm!
880
+
881
+ Usage:
882
+ -h | --help [ See usage for script ]
883
+ -s | --silent [ Suppress output ]
884
+ -m | --manager <type> [ docker, podman, swarm; default: docker ]
885
+ --log-file <path> [ Log to specified file (default: container_manager.log in script dir) ]
886
+
887
+ Actions:
888
+ --get-version [ Get version info ]
889
+ --get-info [ Get system info ]
890
+ --list-images [ List images ]
891
+ --pull-image <image> [ Pull image, e.g., nginx ]
892
+ --tag <tag> [ Tag, default: latest ]
893
+ --platform <plat> [ Platform, e.g., linux/amd64 ]
894
+ --remove-image <image> [ Remove image ]
895
+ --force [ Force removal (global for remove actions) ]
896
+ --list-containers [ List containers ]
897
+ --all [ Show all containers ]
898
+ --run-container <image> [ Run container ]
899
+ --name <name> [ Container name ]
900
+ --command <cmd> [ Command to run ]
901
+ --detach [ Detach mode ]
902
+ --ports <ports> [ Ports, comma-separated host:container, e.g., 8080:80,8443:443 ]
903
+ --volumes <vols> [ Volumes, comma-separated host:container:mode, mode default rw ]
904
+ --environment <env> [ Env vars, comma-separated KEY=val ]
905
+ --stop-container <id> [ Stop container ]
906
+ --timeout <sec> [ Timeout, default 10 ]
907
+ --remove-container <id>[ Remove container ]
908
+ --force [ Force ]
909
+ --get-container-logs <id> [ Get logs ]
910
+ --tail <tail> [ Tail lines, default all ]
911
+ --exec-in-container <id> [ Exec command ]
912
+ --exec-command <cmd> [ Command, space-separated "ls -l /" ]
913
+ --exec-detach [ Detach exec ]
914
+ --list-volumes [ List volumes ]
915
+ --create-volume <name> [ Create volume ]
916
+ --remove-volume <name> [ Remove volume ]
917
+ --force [ Force ]
918
+ --list-networks [ List networks ]
919
+ --create-network <name>[ Create network ]
920
+ --driver <driver> [ Driver, default bridge ]
921
+ --remove-network <id> [ Remove network ]
922
+ --compose-up <file> [ Compose up ]
923
+ --build [ Build images ]
924
+ --detach [ Detach mode, default true ]
925
+ --compose-down <file> [ Compose down ]
926
+ --compose-ps <file> [ Compose ps ]
927
+ --compose-logs <file> [ Compose logs ]
928
+ --service <service> [ Specific service ]
929
+ --init-swarm [ Init swarm ]
930
+ --advertise-addr <addr> [ Advertise address ]
931
+ --leave-swarm [ Leave swarm ]
932
+ --force [ Force ]
933
+ --list-nodes [ List swarm nodes ]
934
+ --list-services [ List swarm services ]
935
+ --create-service <name>[ Create service ]
936
+ --image <image> [ Image for service ]
937
+ --replicas <n> [ Replicas, default 1 ]
938
+ --ports <ports> [ Ports, same as run-container ]
939
+ --mounts <mounts> [ Mounts, comma-separated source:target:mode ]
940
+ --remove-service <id> [ Remove service ]
941
+
942
+ Example:
943
+ container_manager.py --manager docker --pull-image nginx --tag latest --list-containers --all --log-file /path/to/log.log
944
+ """
945
+ )
946
+
947
+
948
+ def main(argv):
949
+ get_version = False
950
+ get_info = False
951
+ list_images = False
952
+ pull_image = False
953
+ pull_image_str = None
954
+ tag = "latest"
955
+ platform = None
956
+ remove_image = False
957
+ remove_image_str = None
958
+ force = False
959
+ list_containers = False
960
+ all_containers = False
961
+ run_container = False
962
+ run_image = None
963
+ name = None
964
+ command = None
965
+ detach = False
966
+ ports_str = None
967
+ volumes_str = None
968
+ environment_str = None
969
+ stop_container = False
970
+ stop_container_id = None
971
+ timeout = 10
972
+ remove_container = False
973
+ remove_container_id = None
974
+ get_container_logs = False
975
+ container_logs_id = None
976
+ tail = "all"
977
+ exec_in_container = False
978
+ exec_container_id = None
979
+ exec_command = None
980
+ exec_detach = False
981
+ list_volumes = False
982
+ create_volume = False
983
+ create_volume_name = None
984
+ remove_volume = False
985
+ remove_volume_name = None
986
+ list_networks = False
987
+ create_network = False
988
+ create_network_name = None
989
+ driver = "bridge"
990
+ remove_network = False
991
+ remove_network_id = None
992
+ compose_up = False
993
+ compose_up_file = None
994
+ compose_build = False
995
+ compose_detach = True
996
+ compose_down = False
997
+ compose_down_file = None
998
+ compose_ps = False
999
+ compose_ps_file = None
1000
+ compose_logs = False
1001
+ compose_logs_file = None
1002
+ compose_service = None
1003
+ init_swarm = False
1004
+ advertise_addr = None
1005
+ leave_swarm = False
1006
+ list_nodes = False
1007
+ list_services = False
1008
+ create_service = False
1009
+ create_service_name = None
1010
+ service_image = None
1011
+ replicas = 1
1012
+ mounts_str = None
1013
+ remove_service = False
1014
+ remove_service_id = None
1015
+ manager_type = "docker"
1016
+ silent = False
1017
+ log_file = None
1018
+
1019
+ try:
1020
+ opts, _ = getopt.getopt(
1021
+ argv,
1022
+ "hsm:",
1023
+ [
1024
+ "help",
1025
+ "silent",
1026
+ "manager=",
1027
+ "log-file=",
1028
+ "get-version",
1029
+ "get-info",
1030
+ "list-images",
1031
+ "pull-image=",
1032
+ "tag=",
1033
+ "platform=",
1034
+ "remove-image=",
1035
+ "force",
1036
+ "list-containers",
1037
+ "all",
1038
+ "run-container=",
1039
+ "name=",
1040
+ "command=",
1041
+ "detach",
1042
+ "ports=",
1043
+ "volumes=",
1044
+ "environment=",
1045
+ "stop-container=",
1046
+ "timeout=",
1047
+ "remove-container=",
1048
+ "get-container-logs=",
1049
+ "tail=",
1050
+ "exec-in-container=",
1051
+ "exec-command=",
1052
+ "exec-detach",
1053
+ "list-volumes",
1054
+ "create-volume=",
1055
+ "remove-volume=",
1056
+ "list-networks",
1057
+ "create-network=",
1058
+ "driver=",
1059
+ "remove-network=",
1060
+ "compose-up=",
1061
+ "build",
1062
+ "compose-down=",
1063
+ "compose-ps=",
1064
+ "compose-logs=",
1065
+ "service=",
1066
+ "init-swarm",
1067
+ "advertise-addr=",
1068
+ "leave-swarm",
1069
+ "list-nodes",
1070
+ "list-services",
1071
+ "create-service=",
1072
+ "image=",
1073
+ "replicas=",
1074
+ "mounts=",
1075
+ "remove-service=",
1076
+ ],
1077
+ )
1078
+ except getopt.GetoptError:
1079
+ usage()
1080
+ sys.exit(2)
1081
+
1082
+ for opt, arg in opts:
1083
+ if opt in ("-h", "--help"):
1084
+ usage()
1085
+ sys.exit()
1086
+ elif opt in ("-s", "--silent"):
1087
+ silent = True
1088
+ elif opt in ("-m", "--manager"):
1089
+ manager_type = arg
1090
+ elif opt == "--log-file":
1091
+ log_file = arg
1092
+ elif opt == "--get-version":
1093
+ get_version = True
1094
+ elif opt == "--get-info":
1095
+ get_info = True
1096
+ elif opt == "--list-images":
1097
+ list_images = True
1098
+ elif opt == "--pull-image":
1099
+ pull_image = True
1100
+ pull_image_str = arg
1101
+ elif opt == "--tag":
1102
+ tag = arg
1103
+ elif opt == "--platform":
1104
+ platform = arg
1105
+ elif opt == "--remove-image":
1106
+ remove_image = True
1107
+ remove_image_str = arg
1108
+ elif opt == "--force":
1109
+ force = True
1110
+ elif opt == "--list-containers":
1111
+ list_containers = True
1112
+ elif opt == "--all":
1113
+ all_containers = True
1114
+ elif opt == "--run-container":
1115
+ run_container = True
1116
+ run_image = arg
1117
+ elif opt == "--name":
1118
+ name = arg
1119
+ elif opt == "--command":
1120
+ command = arg
1121
+ elif opt == "--detach":
1122
+ detach = True
1123
+ elif opt == "--ports":
1124
+ ports_str = arg
1125
+ elif opt == "--volumes":
1126
+ volumes_str = arg
1127
+ elif opt == "--environment":
1128
+ environment_str = arg
1129
+ elif opt == "--stop-container":
1130
+ stop_container = True
1131
+ stop_container_id = arg
1132
+ elif opt == "--timeout":
1133
+ timeout = int(arg)
1134
+ elif opt == "--remove-container":
1135
+ remove_container = True
1136
+ remove_container_id = arg
1137
+ elif opt == "--get-container-logs":
1138
+ get_container_logs = True
1139
+ container_logs_id = arg
1140
+ elif opt == "--tail":
1141
+ tail = arg
1142
+ elif opt == "--exec-in-container":
1143
+ exec_in_container = True
1144
+ exec_container_id = arg
1145
+ elif opt == "--exec-command":
1146
+ exec_command = arg
1147
+ elif opt == "--exec-detach":
1148
+ exec_detach = True
1149
+ elif opt == "--list-volumes":
1150
+ list_volumes = True
1151
+ elif opt == "--create-volume":
1152
+ create_volume = True
1153
+ create_volume_name = arg
1154
+ elif opt == "--remove-volume":
1155
+ remove_volume = True
1156
+ remove_volume_name = arg
1157
+ elif opt == "--list-networks":
1158
+ list_networks = True
1159
+ elif opt == "--create-network":
1160
+ create_network = True
1161
+ create_network_name = arg
1162
+ elif opt == "--driver":
1163
+ driver = arg
1164
+ elif opt == "--remove-network":
1165
+ remove_network = True
1166
+ remove_network_id = arg
1167
+ elif opt == "--compose-up":
1168
+ compose_up = True
1169
+ compose_up_file = arg
1170
+ elif opt == "--build":
1171
+ compose_build = True
1172
+ elif opt == "--compose-down":
1173
+ compose_down = True
1174
+ compose_down_file = arg
1175
+ elif opt == "--compose-ps":
1176
+ compose_ps = True
1177
+ compose_ps_file = arg
1178
+ elif opt == "--compose-logs":
1179
+ compose_logs = True
1180
+ compose_logs_file = arg
1181
+ elif opt == "--service":
1182
+ compose_service = arg
1183
+ elif opt == "--init-swarm":
1184
+ init_swarm = True
1185
+ elif opt == "--advertise-addr":
1186
+ advertise_addr = arg
1187
+ elif opt == "--leave-swarm":
1188
+ leave_swarm = True
1189
+ elif opt == "--list-nodes":
1190
+ list_nodes = True
1191
+ elif opt == "--list-services":
1192
+ list_services = True
1193
+ elif opt == "--create-service":
1194
+ create_service = True
1195
+ create_service_name = arg
1196
+ elif opt == "--image":
1197
+ service_image = arg
1198
+ elif opt == "--replicas":
1199
+ replicas = int(arg)
1200
+ elif opt == "--mounts":
1201
+ mounts_str = arg
1202
+ elif opt == "--remove-service":
1203
+ remove_service = True
1204
+ remove_service_id = arg
1205
+
1206
+ manager = create_manager(manager_type, silent, log_file)
1207
+
1208
+ if get_version:
1209
+ print(json.dumps(manager.get_version(), indent=2))
1210
+
1211
+ if get_info:
1212
+ print(json.dumps(manager.get_info(), indent=2))
1213
+
1214
+ if list_images:
1215
+ print(json.dumps(manager.list_images(), indent=2))
1216
+
1217
+ if pull_image:
1218
+ if not pull_image_str:
1219
+ raise ValueError("Image required for pull-image")
1220
+ print(json.dumps(manager.pull_image(pull_image_str, tag, platform), indent=2))
1221
+
1222
+ if remove_image:
1223
+ if not remove_image_str:
1224
+ raise ValueError("Image required for remove-image")
1225
+ print(json.dumps(manager.remove_image(remove_image_str, force), indent=2))
1226
+
1227
+ if list_containers:
1228
+ print(json.dumps(manager.list_containers(all_containers), indent=2))
1229
+
1230
+ if run_container:
1231
+ if not run_image:
1232
+ raise ValueError("Image required for run-container")
1233
+ ports = None
1234
+ if ports_str:
1235
+ ports = {}
1236
+ for p in ports_str.split(","):
1237
+ host, cont = p.split(":")
1238
+ ports[cont + "/tcp"] = host
1239
+ volumes = None
1240
+ if volumes_str:
1241
+ volumes = {}
1242
+ for v in volumes_str.split(","):
1243
+ parts = v.split(":")
1244
+ host = parts[0]
1245
+ cont = parts[1]
1246
+ mode = parts[2] if len(parts) > 2 else "rw"
1247
+ volumes[host] = {"bind": cont, "mode": mode}
1248
+ env = None
1249
+ if environment_str:
1250
+ env = dict(e.split("=") for e in environment_str.split(","))
1251
+ print(
1252
+ json.dumps(
1253
+ manager.run_container(
1254
+ run_image, name, command, detach, ports, volumes, env
1255
+ ),
1256
+ indent=2,
1257
+ )
1258
+ )
1259
+
1260
+ if stop_container:
1261
+ if not stop_container_id:
1262
+ raise ValueError("Container ID required for stop-container")
1263
+ print(json.dumps(manager.stop_container(stop_container_id, timeout), indent=2))
1264
+
1265
+ if remove_container:
1266
+ if not remove_container_id:
1267
+ raise ValueError("Container ID required for remove-container")
1268
+ print(
1269
+ json.dumps(manager.remove_container(remove_container_id, force), indent=2)
1270
+ )
1271
+
1272
+ if get_container_logs:
1273
+ if not container_logs_id:
1274
+ raise ValueError("Container ID required for get-container-logs")
1275
+ print(manager.get_container_logs(container_logs_id, tail))
1276
+
1277
+ if exec_in_container:
1278
+ if not exec_container_id:
1279
+ raise ValueError("Container ID required for exec-in-container")
1280
+ cmd_list = exec_command.split() if exec_command else []
1281
+ print(
1282
+ json.dumps(
1283
+ manager.exec_in_container(exec_container_id, cmd_list, exec_detach),
1284
+ indent=2,
1285
+ )
1286
+ )
1287
+
1288
+ if list_volumes:
1289
+ print(json.dumps(manager.list_volumes(), indent=2))
1290
+
1291
+ if create_volume:
1292
+ if not create_volume_name:
1293
+ raise ValueError("Name required for create-volume")
1294
+ print(json.dumps(manager.create_volume(create_volume_name), indent=2))
1295
+
1296
+ if remove_volume:
1297
+ if not remove_volume_name:
1298
+ raise ValueError("Name required for remove-volume")
1299
+ print(json.dumps(manager.remove_volume(remove_volume_name, force), indent=2))
1300
+
1301
+ if list_networks:
1302
+ print(json.dumps(manager.list_networks(), indent=2))
1303
+
1304
+ if create_network:
1305
+ if not create_network_name:
1306
+ raise ValueError("Name required for create-network")
1307
+ print(json.dumps(manager.create_network(create_network_name, driver), indent=2))
1308
+
1309
+ if remove_network:
1310
+ if not remove_network_id:
1311
+ raise ValueError("ID required for remove-network")
1312
+ print(json.dumps(manager.remove_network(remove_network_id), indent=2))
1313
+
1314
+ if compose_up:
1315
+ if not compose_up_file:
1316
+ raise ValueError("File required for compose-up")
1317
+ print(manager.compose_up(compose_up_file, compose_detach, compose_build))
1318
+
1319
+ if compose_down:
1320
+ if not compose_down_file:
1321
+ raise ValueError("File required for compose-down")
1322
+ print(manager.compose_down(compose_down_file))
1323
+
1324
+ if compose_ps:
1325
+ if not compose_ps_file:
1326
+ raise ValueError("File required for compose-ps")
1327
+ print(manager.compose_ps(compose_ps_file))
1328
+
1329
+ if compose_logs:
1330
+ if not compose_logs_file:
1331
+ raise ValueError("File required for compose-logs")
1332
+ print(manager.compose_logs(compose_logs_file, compose_service))
1333
+
1334
+ if init_swarm:
1335
+ print(json.dumps(manager.init_swarm(advertise_addr), indent=2))
1336
+
1337
+ if leave_swarm:
1338
+ print(json.dumps(manager.leave_swarm(force), indent=2))
1339
+
1340
+ if list_nodes:
1341
+ print(json.dumps(manager.list_nodes(), indent=2))
1342
+
1343
+ if list_services:
1344
+ print(json.dumps(manager.list_services(), indent=2))
1345
+
1346
+ if create_service:
1347
+ if not create_service_name:
1348
+ raise ValueError("Name required for create-service")
1349
+ if not service_image:
1350
+ raise ValueError("Image required for create-service")
1351
+ ports = None
1352
+ if ports_str:
1353
+ ports = {}
1354
+ for p in ports_str.split(","):
1355
+ host, cont = p.split(":")
1356
+ ports[cont + "/tcp"] = host
1357
+ mounts = None
1358
+ if mounts_str:
1359
+ mounts = mounts_str.split(",")
1360
+ print(
1361
+ json.dumps(
1362
+ manager.create_service(
1363
+ create_service_name, service_image, replicas, ports, mounts
1364
+ ),
1365
+ indent=2,
1366
+ )
1367
+ )
1368
+
1369
+ if remove_service:
1370
+ if not remove_service_id:
1371
+ raise ValueError("ID required for remove-service")
1372
+ print(json.dumps(manager.remove_service(remove_service_id), indent=2))
1373
+
1374
+ print("Done!")
1375
+
1376
+
1377
+ if __name__ == "__main__":
1378
+ if len(sys.argv) < 2:
1379
+ usage()
1380
+ sys.exit(2)
1381
+ main(sys.argv[1:])