helper-cli 0.1.16__py3-none-any.whl → 0.1.21__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.
helper/commands/docker.py CHANGED
@@ -1,21 +1,31 @@
1
- import click
2
- import subprocess
1
+ """
2
+ Docker management commands for the helper CLI.
3
+
4
+ This module provides commands to manage Docker containers and images,
5
+ including listing, running, and removing containers and images.
6
+ """
7
+
3
8
  import json
4
- import re
5
9
  import logging
10
+ import subprocess
6
11
  import sys
7
- from typing import Dict, List, Tuple, Optional, Any
12
+ from typing import Dict, List
13
+
14
+ import click
15
+ from helper import __version__
8
16
 
9
17
  # Configure logging
10
18
  logging.basicConfig(
11
19
  level=logging.WARNING,
12
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
13
- stream=sys.stderr
20
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
21
+ stream=sys.stderr,
14
22
  )
15
- logger = logging.getLogger('docker-helper')
23
+ logger = logging.getLogger("docker-helper")
24
+
16
25
 
17
26
  class Verbosity:
18
27
  """Handle verbosity levels for logging."""
28
+
19
29
  def __init__(self, verbosity: int = 0):
20
30
  self.verbosity = verbosity
21
31
  self.set_level()
@@ -50,68 +60,97 @@ class Verbosity:
50
60
  """Log error message regardless of verbosity."""
51
61
  logger.error(msg, *args, **kwargs)
52
62
 
63
+
53
64
  def get_container_ports(container_id: str, verbosity: Verbosity) -> List[Dict]:
54
- """Get exposed ports and IPs for a container."""
55
- verbosity.debug(f"Getting ports for container {container_id}")
65
+ """Get exposed ports and IPs for a container.
66
+
67
+ Args:
68
+ container_id: The ID of the container to inspect
69
+ verbosity: Verbosity level for logging
70
+
71
+ Returns:
72
+ List of dictionaries containing port mappings
73
+ """
74
+ verbosity.debug("Getting ports for container %s", container_id)
56
75
  try:
57
76
  result = subprocess.run(
58
- ['docker', 'inspect', '--format',
59
- '{{range $p, $conf := .NetworkSettings.Ports}}' # noqa: E501
60
- '{{range $h, $hosts := $conf}}'
61
- '{{$p}}|{{$hosts.HostIp}}|{{$hosts.HostPort}};'
62
- '{{end}}{{end}}',
63
- container_id],
77
+ [
78
+ "docker",
79
+ "inspect",
80
+ "--format",
81
+ "{{range $p, $conf := .NetworkSettings.Ports}}" # noqa: E501
82
+ "{{range $h, $hosts := $conf}}"
83
+ "{{$p}}|{{$hosts.HostIp}}|{{$hosts.HostPort}};"
84
+ "{{end}}{{end}}",
85
+ container_id,
86
+ ],
64
87
  capture_output=True,
65
- text=True
88
+ text=True,
89
+ check=True,
66
90
  )
67
91
 
68
- if result.returncode != 0:
69
- verbosity.error(f"Failed to get container info: {result.stderr}")
70
- return []
71
-
72
92
  ports = []
73
93
  raw_output = result.stdout.strip()
74
- verbosity.debug(f"Raw port mappings: {raw_output}")
94
+ verbosity.debug("Raw port mappings: %s", raw_output)
75
95
 
76
- if raw_output:
77
- for mapping in raw_output.split(';'):
78
- if not mapping:
79
- continue
80
- try:
81
- container_port, host_ip, host_port = mapping.split('|')
82
- verbosity.debug(f"Processing mapping: container={container_port}, host_ip={host_ip}, host_port={host_port}")
83
-
84
- if container_port and host_port:
85
- port_info = {
86
- 'container_port': container_port.split('/')[0], # Remove /tcp or /udp
87
- 'host_ip': host_ip if host_ip not in ('0.0.0.0', '') else 'localhost',
88
- 'host_port': host_port
89
- }
90
- verbosity.info(f"Added port mapping: {port_info}")
91
- ports.append(port_info)
92
- else:
93
- verbosity.debug(f"Skipping incomplete mapping: {mapping}")
94
- except ValueError as e:
95
- verbosity.warning(f"Failed to parse mapping '{mapping}': {e}")
96
- continue
97
- verbosity.debug(f"Final port mappings: {ports}")
96
+ if not raw_output:
97
+ return ports
98
+
99
+ for mapping in raw_output.split(";"):
100
+ if not mapping:
101
+ continue
102
+ try:
103
+ container_port, host_ip, host_port = mapping.split("|")
104
+ verbosity.debug(
105
+ "Processing mapping: container=%s, host_ip=%s, host_port=%s",
106
+ container_port,
107
+ host_ip,
108
+ host_port,
109
+ )
110
+
111
+ if container_port and host_port:
112
+ port_info = {
113
+ "container_port": container_port.split("/")[
114
+ 0
115
+ ], # Remove /tcp or /udp
116
+ "host_ip": (
117
+ host_ip if host_ip not in ("0.0.0.0", "") else "localhost"
118
+ ),
119
+ "host_port": host_port,
120
+ }
121
+ verbosity.info("Added port mapping: %s", port_info)
122
+ ports.append(port_info)
123
+ else:
124
+ verbosity.debug("Skipping incomplete mapping: %s", mapping)
125
+ except ValueError as e:
126
+ verbosity.warning("Failed to parse mapping '%s': %s", mapping, e)
127
+
128
+ verbosity.debug("Final port mappings: %s", ports)
98
129
  return ports
99
- except Exception as e:
100
- verbosity.error(f"Unexpected error in get_container_ports: {str(e)}", exc_info=verbosity.verbosity >= 3)
101
- return []
130
+
131
+ except subprocess.CalledProcessError as e:
132
+ verbosity.error("Failed to get container info: %s", e.stderr)
133
+ except Exception as e: # pylint: disable=broad-except
134
+ verbosity.error(
135
+ "Unexpected error in get_container_ports: %s",
136
+ str(e),
137
+ exc_info=verbosity.verbosity >= 3,
138
+ )
139
+ return []
140
+
102
141
 
103
142
  def check_docker(verbosity: Verbosity) -> bool:
104
143
  """Check if Docker is installed and running."""
105
144
  verbosity.info("Checking if Docker is installed and running...")
106
145
  try:
107
- result = subprocess.run(['docker', 'info'],
108
- capture_output=True,
109
- text=True)
146
+ result = subprocess.run(["docker", "info"], capture_output=True, text=True)
110
147
 
111
148
  verbosity.debug(f"Docker info command output:\n{result.stdout}")
112
149
 
113
150
  if result.returncode != 0:
114
- verbosity.error(f"Docker is not running or not accessible. Error: {result.stderr}")
151
+ verbosity.error(
152
+ f"Docker is not running or not accessible. Error: {result.stderr}"
153
+ )
115
154
  return False
116
155
 
117
156
  verbosity.info("Docker is running and accessible")
@@ -121,79 +160,119 @@ def check_docker(verbosity: Verbosity) -> bool:
121
160
  verbosity.error("Docker command not found. Is Docker installed?")
122
161
  return False
123
162
  except Exception as e:
124
- verbosity.error(f"Unexpected error checking Docker: {str(e)}", exc_info=verbosity.verbosity >= 3)
163
+ verbosity.error(
164
+ f"Unexpected error checking Docker: {str(e)}",
165
+ exc_info=verbosity.verbosity >= 3,
166
+ )
125
167
  return False
126
168
 
127
- def format_output(output, output_format='table'):
169
+
170
+ def format_output(output, output_format="table"):
128
171
  """Format command output based on the specified format."""
129
- if output_format == 'json':
172
+ if output_format == "json":
130
173
  try:
131
174
  return json.dumps(json.loads(output), indent=2)
132
175
  except json.JSONDecodeError:
133
176
  return output
134
177
  return output
135
178
 
179
+
136
180
  def get_verbosity(ctx: click.Context) -> Verbosity:
137
181
  """Get verbosity level from context."""
138
182
  # Count the number of 'v's in the --verbose flag
139
- verbose = ctx.params.get('verbose', 0)
183
+ verbose = ctx.params.get("verbose", 0)
140
184
  verbosity = Verbosity(verbosity=verbose)
141
185
  verbosity.info(f"Verbosity level set to {verbose}")
142
186
  return verbosity
143
187
 
188
+
144
189
  @click.group()
145
- @click.option('-v', '--verbose', count=True, help='Increase verbosity (can be used multiple times)')
190
+ @click.option(
191
+ "-v",
192
+ "--verbose",
193
+ count=True,
194
+ help="Increase verbosity (can be used multiple times)",
195
+ )
146
196
  @click.pass_context
147
197
  def docker(ctx, verbose):
148
- """Docker management commands."""
198
+ """Docker management commands (v{}).
199
+
200
+ This command provides various Docker management subcommands including:
201
+ - ps: List containers
202
+ - run: Run a command in a new container
203
+ - rm: Remove one or more containers
204
+ - rmi: Remove one or more images
205
+ - url: Show containers with their HTTP/HTTPS URLs
206
+
207
+ Use --help with any subcommand for more information.
208
+ """.format(__version__)
149
209
  ctx.ensure_object(dict)
150
210
 
151
211
  # Get verbosity from parent context if it exists, otherwise use the flag value
152
- parent_verbosity = ctx.obj.get('verbosity', 0) if hasattr(ctx, 'obj') else 0
212
+ parent_verbosity = ctx.obj.get("verbosity", 0) if hasattr(ctx, "obj") else 0
153
213
  verbosity_level = max(verbose, parent_verbosity)
154
214
 
155
215
  # Initialize verbosity
156
216
  verbosity = Verbosity(verbosity=verbosity_level)
157
- ctx.obj['verbosity'] = verbosity
158
-
159
- # Set up logging
160
- logger = logging.getLogger('docker-helper')
161
- if verbosity_level >= 3:
162
- logger.setLevel(logging.DEBUG)
163
- elif verbosity_level == 2:
164
- logger.setLevel(logging.INFO)
165
- elif verbosity_level == 1:
166
- logger.setLevel(logging.WARNING)
167
- else:
168
- logger.setLevel(logging.ERROR)
217
+ ctx.obj["verbosity"] = verbosity
218
+
219
+ # Configure logger with verbosity level
220
+ logger.setLevel(
221
+ logging.DEBUG
222
+ if verbosity_level >= 3
223
+ else (
224
+ logging.INFO
225
+ if verbosity_level == 2
226
+ else logging.WARNING if verbosity_level == 1 else logging.ERROR
227
+ )
228
+ )
169
229
 
170
- logger.debug(f"Docker command group initialized with verbosity level: {verbosity_level}")
230
+ logger.debug(
231
+ "Docker command group initialized with verbosity level: %s", verbosity_level
232
+ )
171
233
 
172
234
  verbosity.debug("Initializing Docker command group")
173
235
  if not check_docker(verbosity):
174
- click.echo("Error: Docker is not installed or not running. Please start Docker and try again.", err=True)
236
+ click.echo(
237
+ "Error: Docker is not installed or not running. Please start Docker and try again.",
238
+ err=True,
239
+ )
175
240
  ctx.exit(1)
176
241
 
177
- @docker.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
178
- @click.option('--all', '-a', is_flag=True, help='Show all containers (default shows just running)')
179
- @click.option('--all-containers', is_flag=True, help='Show all containers (default shows just running)')
180
- @click.option('--format', '-f', type=click.Choice(['table', 'json'], case_sensitive=False),
181
- default='table', help='Output format')
182
- @click.help_option('--help', '-h')
242
+
243
+ @docker.command(
244
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
245
+ )
246
+ @click.option(
247
+ "--all", "-a", is_flag=True, help="Show all containers (default shows just running)"
248
+ )
249
+ @click.option(
250
+ "--all-containers",
251
+ is_flag=True,
252
+ help="Show all containers (default shows just running)",
253
+ )
254
+ @click.option(
255
+ "--format",
256
+ "-f",
257
+ type=click.Choice(["table", "json"], case_sensitive=False),
258
+ default="table",
259
+ help="Output format",
260
+ )
261
+ @click.help_option("--help", "-h")
183
262
  @click.pass_context
184
- def ps(ctx, all, format):
263
+ def ps(ctx, all_containers, output_format): # pylint: disable=redefined-builtin
185
264
  """List containers."""
186
- verbosity = ctx.obj['verbosity']
187
- cmd = ['docker', 'ps']
188
- if all:
189
- cmd.append('-a')
265
+ verbosity = ctx.obj["verbosity"]
266
+ cmd = ["docker", "ps"]
267
+ if all_containers:
268
+ cmd.append("-a")
190
269
 
191
270
  try:
192
271
  verbosity.debug(f"Running command: {' '.join(cmd)}")
193
272
  result = subprocess.run(cmd, capture_output=True, text=True)
194
273
 
195
274
  if result.returncode == 0:
196
- if format == 'json':
275
+ if output_format == "json":
197
276
  # Try to parse and pretty-print JSON output
198
277
  try:
199
278
  data = json.loads(result.stdout)
@@ -203,27 +282,42 @@ def ps(ctx, all, format):
203
282
  click.echo(result.stdout)
204
283
  else:
205
284
  # For table format, try to align columns
206
- lines = result.stdout.strip().split('\n')
285
+ lines = result.stdout.strip().split("\n")
207
286
  if len(lines) > 1:
208
287
  # Parse as JSON to handle special characters in values
209
288
  try:
210
289
  data = [json.loads(line) for line in lines[1:]]
211
290
  headers = data[0].keys()
212
- rows = [[item.get(header, '') for header in headers] for item in data]
291
+ rows = [
292
+ [item.get(header, "") for header in headers]
293
+ for item in data
294
+ ]
213
295
 
214
296
  # Calculate column widths
215
- col_widths = [max(len(str(header)),
216
- max((len(str(row[i])) for row in rows), default=0))
217
- for i, header in enumerate(headers)]
297
+ col_widths = [
298
+ max(
299
+ len(str(header)),
300
+ max((len(str(row[i])) for row in rows), default=0),
301
+ )
302
+ for i, header in enumerate(headers)
303
+ ]
218
304
 
219
305
  # Print header
220
- header_row = " ".join(header.ljust(width) for header, width in zip(headers, col_widths))
306
+ header_row = " ".join(
307
+ header.ljust(width)
308
+ for header, width in zip(headers, col_widths)
309
+ )
221
310
  click.echo(header_row)
222
311
  click.echo("-" * len(header_row))
223
312
 
224
313
  # Print rows
225
314
  for row in rows:
226
- click.echo(" ".join(str(cell).ljust(width) for cell, width in zip(row, col_widths)))
315
+ click.echo(
316
+ " ".join(
317
+ str(cell).ljust(width)
318
+ for cell, width in zip(row, col_widths)
319
+ )
320
+ )
227
321
  except Exception as e:
228
322
  verbosity.debug(f"Error formatting table: {str(e)}")
229
323
  # Fall back to raw output if processing fails
@@ -243,37 +337,47 @@ def ps(ctx, all, format):
243
337
  verbosity.error(error_msg)
244
338
  click.echo(error_msg, err=True)
245
339
 
246
- @docker.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
247
- @click.argument('image', required=False)
248
- @click.option('--name', help='Assign a name to the container')
249
- @click.option('--port', '-p', multiple=True, help='Publish a container\'s port(s) to the host')
250
- @click.option('--detach', '-d', is_flag=True, help='Run container in background and print container ID')
251
- @click.option('--env', '-e', multiple=True, help='Set environment variables')
252
- @click.option('--volume', '-v', multiple=True, help='Bind mount a volume')
340
+
341
+ @docker.command(
342
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
343
+ )
344
+ @click.argument("image", required=False)
345
+ @click.option("--name", help="Assign a name to the container")
346
+ @click.option(
347
+ "--port", "-p", multiple=True, help="Publish a container's port(s) to the host"
348
+ )
349
+ @click.option(
350
+ "--detach",
351
+ "-d",
352
+ is_flag=True,
353
+ help="Run container in background and print container ID",
354
+ )
355
+ @click.option("--env", "-e", multiple=True, help="Set environment variables")
356
+ @click.option("--volume", "-v", multiple=True, help="Bind mount a volume")
253
357
  @click.pass_context
254
358
  def run(ctx, image, name, port, detach, env, volume):
255
359
  """Run a command in a new container."""
256
- verbosity = ctx.obj['verbosity']
257
- cmd = ['docker', 'run']
360
+ verbosity = ctx.obj["verbosity"]
361
+ cmd = ["docker", "run"]
258
362
 
259
363
  if name:
260
- cmd.extend(['--name', name])
364
+ cmd.extend(["--name", name])
261
365
  verbosity.debug(f"Setting container name: {name}")
262
366
 
263
367
  for p in port:
264
- cmd.extend(['-p', p])
368
+ cmd.extend(["-p", p])
265
369
  verbosity.debug(f"Adding port mapping: {p}")
266
370
 
267
371
  if detach:
268
- cmd.append('-d')
372
+ cmd.append("-d")
269
373
  verbosity.debug("Running container in detached mode")
270
374
 
271
375
  for e in env:
272
- cmd.extend(['-e', e])
376
+ cmd.extend(["-e", e])
273
377
  verbosity.debug(f"Setting environment variable: {e}")
274
378
 
275
379
  for v in volume:
276
- cmd.extend(['-v', v])
380
+ cmd.extend(["-v", v])
277
381
  verbosity.debug(f"Mounting volume: {v}")
278
382
 
279
383
  if image:
@@ -281,7 +385,7 @@ def run(ctx, image, name, port, detach, env, volume):
281
385
  verbosity.debug(f"Using image: {image}")
282
386
 
283
387
  # Add any remaining arguments
284
- if hasattr(ctx, 'args') and ctx.args:
388
+ if hasattr(ctx, "args") and ctx.args:
285
389
  cmd.extend(ctx.args)
286
390
  verbosity.debug(f"Additional arguments: {' '.join(ctx.args)}")
287
391
 
@@ -305,26 +409,39 @@ def run(ctx, image, name, port, detach, env, volume):
305
409
  click.echo(error_msg, err=True)
306
410
  ctx.exit(1)
307
411
 
308
- @docker.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
309
- @click.argument('containers', nargs=-1, required=False)
310
- @click.option('--force', '-f', is_flag=True, help='Force the removal of a running container (uses SIGKILL)')
311
- @click.option('--volumes', '-v', is_flag=True, help='Remove anonymous volumes associated with the container')
412
+
413
+ @docker.command(
414
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
415
+ )
416
+ @click.argument("containers", nargs=-1, required=False)
417
+ @click.option(
418
+ "--force",
419
+ "-f",
420
+ is_flag=True,
421
+ help="Force the removal of a running container (uses SIGKILL)",
422
+ )
423
+ @click.option(
424
+ "--volumes",
425
+ "-v",
426
+ is_flag=True,
427
+ help="Remove anonymous volumes associated with the container",
428
+ )
312
429
  @click.pass_context
313
430
  def rm(ctx, containers, force, volumes):
314
431
  """Remove one or more containers."""
315
- verbosity = ctx.obj['verbosity']
316
- cmd = ['docker', 'rm']
432
+ verbosity = ctx.obj["verbosity"]
433
+ cmd = ["docker", "rm"]
317
434
 
318
435
  if force:
319
- cmd.append('-f')
436
+ cmd.append("-f")
320
437
  verbosity.debug("Force removal enabled")
321
438
  if volumes:
322
- cmd.append('-v')
439
+ cmd.append("-v")
323
440
  verbosity.debug("Volume removal enabled")
324
441
 
325
442
  # Get containers from both the containers argument and any remaining args
326
443
  all_containers = list(containers)
327
- if hasattr(ctx, 'args') and ctx.args:
444
+ if hasattr(ctx, "args") and ctx.args:
328
445
  all_containers.extend(ctx.args)
329
446
 
330
447
  if not all_containers:
@@ -356,20 +473,32 @@ def rm(ctx, containers, force, volumes):
356
473
  click.echo(error_msg, err=True)
357
474
  ctx.exit(1)
358
475
 
359
- @docker.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
360
- @click.option('--show-all', '-a', is_flag=True, help='Show all containers (default shows just running)')
361
- @click.option('--http-only', '-h', is_flag=True, help='Show only containers with HTTP/HTTPS ports')
476
+
477
+ @docker.command(
478
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
479
+ )
480
+ @click.option(
481
+ "--show-all",
482
+ "-a",
483
+ is_flag=True,
484
+ help="Show all containers (default shows just running)",
485
+ )
486
+ @click.option(
487
+ "--http-only", "-h", is_flag=True, help="Show only containers with HTTP/HTTPS ports"
488
+ )
362
489
  @click.pass_context
363
490
  def url(ctx, show_all, http_only):
364
491
  """Show containers with their HTTP/HTTPS URLs."""
365
- verbosity = ctx.obj['verbosity']
366
- verbosity.info(f"Starting url command with show_all={show_all}, http_only={http_only}")
492
+ verbosity = ctx.obj["verbosity"]
493
+ verbosity.info(
494
+ f"Starting url command with show_all={show_all}, http_only={http_only}"
495
+ )
367
496
 
368
497
  try:
369
498
  # Get all containers
370
- cmd = ['docker', 'ps', '--format', '{{.ID}}|{{.Names}}|{{.Status}}|{{.Ports}}']
499
+ cmd = ["docker", "ps", "--format", "{{.ID}}|{{.Names}}|{{.Status}}|{{.Ports}}"]
371
500
  if show_all:
372
- cmd.append('-a')
501
+ cmd.append("-a")
373
502
 
374
503
  verbosity.debug(f"Running command: {' '.join(cmd)}")
375
504
  result = subprocess.run(cmd, capture_output=True, text=True)
@@ -385,7 +514,7 @@ def url(ctx, show_all, http_only):
385
514
  running_containers = []
386
515
  stopped_containers = []
387
516
 
388
- container_lines = result.stdout.strip().split('\n')
517
+ container_lines = result.stdout.strip().split("\n")
389
518
  verbosity.info(f"Found {len(container_lines)} container(s)")
390
519
 
391
520
  for line in container_lines:
@@ -395,16 +524,18 @@ def url(ctx, show_all, http_only):
395
524
 
396
525
  try:
397
526
  verbosity.debug(f"Processing container line: {line}")
398
- container_id, name, status, ports = line.split('|', 3)
399
- is_running = 'Up' in status
400
- verbosity.info(f"Container: ID={container_id[:12]}, Name={name}, Status={status}, Running={is_running}")
527
+ container_id, name, status, ports = line.split("|", 3)
528
+ is_running = "Up" in status
529
+ verbosity.info(
530
+ f"Container: ID={container_id[:12]}, Name={name}, Status={status}, Running={is_running}"
531
+ )
401
532
 
402
533
  # Get container details
403
534
  container_info = {
404
- 'id': container_id[:12], # Short ID
405
- 'name': name,
406
- 'status': status,
407
- 'urls': []
535
+ "id": container_id[:12], # Short ID
536
+ "name": name,
537
+ "status": status,
538
+ "urls": [],
408
539
  }
409
540
 
410
541
  # Get exposed ports and their mappings
@@ -412,59 +543,63 @@ def url(ctx, show_all, http_only):
412
543
  verbosity.debug(f"Found {len(port_mappings)} port mappings for {name}")
413
544
 
414
545
  for port in port_mappings:
415
- if not port.get('host_port') or not port.get('container_port'):
546
+ if not port.get("host_port") or not port.get("container_port"):
416
547
  verbosity.debug(f"Skipping incomplete port mapping: {port}")
417
548
  continue
418
549
 
419
550
  verbosity.debug(f"Checking port mapping: {port}")
420
- verbosity.debug(f"Container name: {name}, Port: {port['container_port']}")
551
+ verbosity.debug(
552
+ f"Container name: {name}, Port: {port['container_port']}"
553
+ )
421
554
 
422
555
  # Handle different port string formats (e.g., '8069/tcp', '0.0.0.0:8080->80/tcp')
423
- port_str = port['container_port']
556
+ port_str = port["container_port"]
424
557
 
425
558
  # Extract port number and protocol
426
559
  port_num = None
427
- protocol = 'tcp' # default protocol
560
+ protocol = "tcp" # default protocol
428
561
 
429
562
  # Handle format like '8069/tcp' or '80/http'
430
- if '/' in port_str:
431
- port_num, protocol = port_str.split('/', 1)
563
+ if "/" in port_str:
564
+ port_num, protocol = port_str.split("/", 1)
432
565
  # Handle format like '0.0.0.0:8080->80/tcp'
433
- elif '->' in port_str:
434
- _, port_mapping = port_str.split('->', 1)
435
- if '/' in port_mapping:
436
- port_num, protocol = port_mapping.split('/', 1)
566
+ elif "->" in port_str:
567
+ _, port_mapping = port_str.split("->", 1)
568
+ if "/" in port_mapping:
569
+ port_num, protocol = port_mapping.split("/", 1)
437
570
  else:
438
571
  port_num = port_mapping
439
572
  else:
440
573
  port_num = port_str
441
574
 
442
575
  # Clean up port number (remove any non-numeric characters)
443
- port_num = ''.join(c for c in port_num if c.isdigit())
576
+ port_num = "".join(c for c in port_num if c.isdigit())
444
577
 
445
578
  # Map all ports to HTTP URLs
446
579
  if port_num: # Process all ports regardless of protocol
447
- scheme = 'http'
580
+ scheme = "http"
448
581
 
449
582
  # Handle IPv6 addresses (add brackets if needed)
450
- host = port['host_ip']
451
- if ':' in host and not host.startswith('['):
452
- host = f'[{host}]'
453
- verbosity.info(f"Skipping non-HTTP port: {port['container_port']}")
583
+ host = port["host_ip"]
584
+ if ":" in host and not host.startswith("["):
585
+ host = f"[{host}]"
586
+ verbosity.info(
587
+ f"Skipping non-HTTP port: {port['container_port']}"
588
+ )
454
589
  continue
455
590
 
456
591
  url = f"{scheme}://{host}:{port['host_port']}"
457
592
 
458
- container_info['urls'].append({
459
- 'url': url,
460
- 'port': port_num,
461
- 'protocol': protocol
462
- })
463
- verbosity.info(f"Added URL for {name}: {url} (port {port_num}/{protocol})")
593
+ container_info["urls"].append(
594
+ {"url": url, "port": port_num, "protocol": protocol}
595
+ )
596
+ verbosity.info(
597
+ f"Added URL for {name}: {url} (port {port_num}/{protocol})"
598
+ )
464
599
  verbosity.info(f"Added URL for {name}: {url} (port {port_num})")
465
600
 
466
601
  # If http_only is True and no HTTP URLs, skip this container
467
- if http_only and not container_info['urls']:
602
+ if http_only and not container_info["urls"]:
468
603
  continue
469
604
 
470
605
  if is_running:
@@ -478,69 +613,95 @@ def url(ctx, show_all, http_only):
478
613
 
479
614
  # Display running containers
480
615
  if running_containers:
481
- click.secho("\n🚀 Running Containers:", fg='green', bold=True)
616
+ click.secho("\n🚀 Running Containers:", fg="green", bold=True)
482
617
  for container in running_containers:
483
618
  verbosity.debug(f"Displaying running container: {container['name']}")
484
- click.echo(f"\n{click.style('●', fg='green')} {click.style(container['name'], bold=True)} ({container['id']})")
619
+ click.echo(
620
+ f"\n{click.style('●', fg='green')} {click.style(container['name'], bold=True)} ({container['id']})"
621
+ )
485
622
 
486
- if container['urls']:
487
- verbosity.info(f"Found {len(container['urls'])} URLs for {container['name']}")
488
- for url_info in container['urls']:
623
+ if container["urls"]:
624
+ verbosity.info(
625
+ f"Found {len(container['urls'])} URLs for {container['name']}"
626
+ )
627
+ for url_info in container["urls"]:
489
628
  verbosity.debug(f"Displaying URL: {url_info['url']}")
490
- click.echo(f" {click.style('→', fg='blue')} {click.style(url_info['url'], fg='blue', underline=True)}")
629
+ click.echo(
630
+ f" {click.style('→', fg='blue')} {click.style(url_info['url'], fg='blue', underline=True)}"
631
+ )
491
632
  else:
492
633
  verbosity.debug(f"No URLs found for {container['name']}")
493
634
 
494
635
  # Display stopped containers
495
636
  if stopped_containers and (show_all or not http_only):
496
- click.secho("\n⏸️ Stopped Containers:", fg='yellow', bold=True)
637
+ click.secho("\n⏸️ Stopped Containers:", fg="yellow", bold=True)
497
638
  for container in stopped_containers:
498
639
  verbosity.debug(f"Displaying stopped container: {container['name']}")
499
- click.echo(f"\n{click.style('●', fg='yellow')} {click.style(container['name'], dim=True)} ({container['id']})")
640
+ click.echo(
641
+ f"\n{click.style('●', fg='yellow')} {click.style(container['name'], dim=True)} ({container['id']})"
642
+ )
500
643
 
501
- if container['urls']:
502
- verbosity.info(f"Found {len(container['urls'])} URLs for stopped container {container['name']}")
503
- for url_info in container['urls']:
504
- verbosity.debug(f"Displaying URL for stopped container: {url_info['url']}")
505
- click.echo(f" {click.style('→', fg='blue')} {click.style(url_info['url'], fg='blue', underline=True, dim=True)}")
644
+ if container["urls"]:
645
+ verbosity.info(
646
+ f"Found {len(container['urls'])} URLs for stopped container {container['name']}"
647
+ )
648
+ for url_info in container["urls"]:
649
+ verbosity.debug(
650
+ f"Displaying URL for stopped container: {url_info['url']}"
651
+ )
652
+ click.echo(
653
+ f" {click.style('→', fg='blue')} {click.style(url_info['url'], fg='blue', underline=True, dim=True)}"
654
+ )
506
655
  else:
507
- verbosity.debug(f"No URLs found for stopped container {container['name']}")
656
+ verbosity.debug(
657
+ f"No URLs found for stopped container {container['name']}"
658
+ )
508
659
 
509
660
  if not running_containers and not stopped_containers:
510
661
  msg = "No containers found."
511
662
  verbosity.info(msg)
512
663
  click.echo(msg)
513
664
  else:
514
- verbosity.info(f"Displayed {len(running_containers)} running and {len(stopped_containers)} stopped containers")
665
+ verbosity.info(
666
+ f"Displayed {len(running_containers)} running and {len(stopped_containers)} stopped containers"
667
+ )
515
668
 
516
669
  except Exception as e:
517
670
  error_msg = f"Error in url command: {str(e)}"
518
671
  verbosity.error(error_msg, exc_info=verbosity.verbosity >= 3)
519
672
  click.echo(error_msg, err=True)
520
673
 
521
- @docker.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
522
- @click.argument('image', required=False)
523
- @click.option('--all-tags', '-a', is_flag=True, help='Remove all versions of the image with the given name')
524
- @click.option('--force', '-f', is_flag=True, help='Force removal of the image')
525
- @click.option('--no-prune', is_flag=True, help='Do not delete untagged parents')
674
+
675
+ @docker.command(
676
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
677
+ )
678
+ @click.argument("image", required=False)
679
+ @click.option(
680
+ "--all-tags",
681
+ "-a",
682
+ is_flag=True,
683
+ help="Remove all versions of the image with the given name",
684
+ )
685
+ @click.option("--force", "-f", is_flag=True, help="Force removal of the image")
686
+ @click.option("--no-prune", is_flag=True, help="Do not delete untagged parents")
526
687
  @click.pass_context
527
688
  def rmi(ctx, image, all_tags, force, no_prune):
528
689
  """Remove one or more images."""
529
- verbosity = ctx.obj['verbosity']
530
- cmd = ['docker', 'rmi']
690
+ verbosity = ctx.obj["verbosity"]
691
+ cmd = ["docker", "rmi"]
531
692
 
532
693
  if force:
533
- cmd.append('-f')
694
+ cmd.append("-f")
534
695
  verbosity.debug("Force removal enabled")
535
696
  if no_prune:
536
- cmd.append('--no-prune')
697
+ cmd.append("--no-prune")
537
698
  verbosity.debug("Pruning of untagged parents disabled")
538
699
 
539
700
  # Get images from both the image argument and any remaining args
540
701
  images = []
541
702
  if image:
542
703
  images.append(image)
543
- if hasattr(ctx, 'args') and ctx.args:
704
+ if hasattr(ctx, "args") and ctx.args:
544
705
  images.extend(ctx.args)
545
706
 
546
707
  if not images and not all_tags:
@@ -562,20 +723,23 @@ def rmi(ctx, image, all_tags, force, no_prune):
562
723
  verbosity.debug(f"Finding all tags for image: {img}")
563
724
  try:
564
725
  result = subprocess.run(
565
- ['docker', 'images', '--format', '{{.Repository}}:{{.Tag}}', img],
726
+ ["docker", "images", "--format", "{{.Repository}}:{{.Tag}}", img],
566
727
  capture_output=True,
567
- text=True
728
+ text=True,
568
729
  )
569
730
 
570
731
  if result.returncode == 0 and result.stdout.strip():
571
- tags = [line for line in result.stdout.split('\n') if line]
732
+ tags = [line for line in result.stdout.split("\n") if line]
572
733
  verbosity.debug(f"Found {len(tags)} tags for {img}")
573
734
  all_tags_to_remove.extend(tags)
574
735
  else:
575
736
  verbosity.warning(f"No images found matching '{img}'")
576
737
 
577
738
  except Exception as e:
578
- verbosity.error(f"Error finding tags for {img}: {str(e)}", exc_info=verbosity.verbosity >= 3)
739
+ verbosity.error(
740
+ f"Error finding tags for {img}: {str(e)}",
741
+ exc_info=verbosity.verbosity >= 3,
742
+ )
579
743
  continue
580
744
 
581
745
  if not all_tags_to_remove: