utils_devops 0.1.161__py3-none-any.whl → 0.1.167__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.
@@ -82,6 +82,7 @@ class ContainerInfo:
82
82
  status: str
83
83
  service: Optional[str] = None
84
84
  ports: Dict[str, Any] = field(default_factory=dict)
85
+ exit_code: Optional[int] = None
85
86
 
86
87
  # Core Docker operations
87
88
  def get_docker_client():
@@ -615,36 +616,82 @@ def compose_restart(compose_file: str, remove_volumes: bool = False,
615
616
 
616
617
  return None
617
618
 
619
+ import json
620
+ import re
621
+ from typing import List, Optional
622
+
623
+ _EXITED_RE = re.compile(r'Exited\s*\((\d+)\)', re.I)
624
+
618
625
  def compose_ps(compose_file: str, project_name: Optional[str] = None, env_file: Optional[str] = None) -> List[ContainerInfo]:
619
- """List compose services status using detected compose command"""
620
- result = run_compose_command(compose_file, ["ps" ,"-a", "--format", "json"], project_name , env_file)
621
-
622
- containers = []
623
- try:
624
- data = json.loads(result.stdout)
625
- for item in data if isinstance(data, list) else [data]:
626
+ """List compose services status using detected compose command. Returns ContainerInfo items
627
+ and attempts to populate `exit_code` when available (JSON ExitCode or parsed from Status)."""
628
+ result = run_compose_command(compose_file, ["ps", "-a", "--format", "json"], project_name, env_file)
629
+ logger.info(f"DEBUG: compose_ps stdout: {result.stdout}") # Use logger instead of print if available
630
+ containers: List[ContainerInfo] = []
631
+ lines = result.stdout.splitlines()
632
+ items = []
633
+ for line in lines:
634
+ if line.strip():
635
+ try:
636
+ items.append(json.loads(line))
637
+ except json.JSONDecodeError:
638
+ pass # ignore bad lines
639
+ if items:
640
+ for item in items:
641
+ state_str = item.get('State') or ''
642
+ full_status = item.get('Status') or state_str or ''
643
+ exit_code = item.get('ExitCode') or item.get('Exit')
644
+ if exit_code is not None:
645
+ try:
646
+ exit_code = int(exit_code)
647
+ except ValueError:
648
+ exit_code = None
649
+ if exit_code is None:
650
+ m = _EXITED_RE.search(full_status)
651
+ if m:
652
+ try:
653
+ exit_code = int(m.group(1))
654
+ except ValueError:
655
+ exit_code = None
626
656
  containers.append(ContainerInfo(
627
- id=item.get('ID', ''),
628
- name=item.get('Name', ''),
629
- image=item.get('Image', ''),
630
- status=item.get('State', ''),
631
- service=item.get('Service', '')
657
+ id=item.get('ID', '') or item.get('Id', ''),
658
+ name=item.get('Name', '') or item.get('Names', ''),
659
+ image=item.get('Image', '') or item.get('ImageName', ''),
660
+ status=state_str,
661
+ service=item.get('Service', '') or item.get('Labels', {}).get('com.docker.compose.service', ''),
662
+ exit_code=exit_code
632
663
  ))
633
- except json.JSONDecodeError:
634
- # Fallback to text parsing
635
- lines = result.stdout.strip().splitlines()[1:] # Skip header
636
- for line in lines:
637
- parts = line.split()
638
- if len(parts) >= 4:
639
- containers.append(ContainerInfo(
640
- id=parts[0],
641
- name=parts[1],
642
- image=parts[2],
643
- status=' '.join(parts[3:])
644
- ))
645
-
664
+ else:
665
+ # Fallback to text parsing (if not JSON format)
666
+ if len(lines) > 0 and 'NAME' in lines[0].upper(): # Check for header
667
+ for line in lines[1:]: # skip header
668
+ parts = re.split(r'\s{2,}', line.strip())
669
+ if len(parts) >= 6:
670
+ name = parts[0]
671
+ image = parts[1]
672
+ command = parts[2]
673
+ service = parts[3]
674
+ created = parts[4]
675
+ status = parts[5]
676
+ ports = ' '.join(parts[6:]) if len(parts) > 6 else ''
677
+ exit_code = None
678
+ m = _EXITED_RE.search(status)
679
+ if m:
680
+ try:
681
+ exit_code = int(m.group(1))
682
+ except ValueError:
683
+ exit_code = None
684
+ containers.append(ContainerInfo(
685
+ id='', # ID not in text
686
+ name=name,
687
+ image=image,
688
+ status=status,
689
+ service=service,
690
+ exit_code=exit_code
691
+ ))
646
692
  return containers
647
693
 
694
+
648
695
  # Add a function to check compose version
649
696
  def get_compose_version() -> Dict[str, str]:
650
697
  """Get detected compose command and version"""
@@ -2222,6 +2269,7 @@ def playwright_test_compose(
2222
2269
  keep_compose_up: bool = False,
2223
2270
  dry_run: bool = False,
2224
2271
  project_name: Optional[str] = None,
2272
+ ui: Optional[bool] = False,
2225
2273
  logger: Optional[logger] = None,
2226
2274
  ) -> Dict[str, Any]:
2227
2275
  """
@@ -2368,7 +2416,8 @@ def playwright_test_compose(
2368
2416
  # STEP 5: Log capture
2369
2417
  pw_services = []
2370
2418
  logs_acc = {}
2371
-
2419
+ if ui :
2420
+ capture_logs = False
2372
2421
  if not dry_run and capture_logs:
2373
2422
  logger.info(f"{STICKERS['logs']} STEP 5: Capturing logs")
2374
2423
 
@@ -2420,54 +2469,66 @@ def playwright_test_compose(
2420
2469
 
2421
2470
  # Check if all services have finished
2422
2471
  ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
2423
- running_count = 0
2424
-
2425
- for container in ps:
2426
- service_name = container.service or ''
2427
- if service_name in pw_services:
2428
- status = (container.status or '').lower()
2472
+
2473
+ running = []
2474
+ exited = []
2475
+
2476
+ for c in ps:
2477
+ if c.service in pw_services:
2478
+ status = (c.status or '').lower()
2429
2479
  if 'running' in status or 'up' in status:
2430
- running_count += 1
2431
-
2432
- if running_count == 0:
2433
- finished = True
2434
- logger.info(f"{STICKERS['complete']} All services completed")
2435
- logger.info("")
2436
- else:
2480
+ running.append(c)
2481
+ elif 'exited' in status:
2482
+ exited.append(c)
2483
+
2484
+ if running:
2437
2485
  time.sleep(health_interval)
2486
+ else:
2487
+ finished = True
2488
+
2438
2489
 
2439
2490
  result['logs_captured'] = logs_acc
2440
2491
 
2441
2492
  # STEP 6: Analysis
2442
2493
  logger.info(f"{STICKERS['analysis']} STEP 6: Analyzing results")
2443
-
2494
+
2444
2495
  if not dry_run:
2445
- # Get exit codes
2446
- ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
2447
- exit_codes = {}
2448
-
2449
- for container in ps:
2450
- service_name = container.service or ''
2451
- if service_name in pw_services:
2452
- exit_code = getattr(container, 'exit_code', None) or 1
2453
- exit_codes[service_name] = exit_code
2454
-
2455
- # Check overall success
2456
- all_success = all(code == 0 for code in exit_codes.values())
2457
-
2458
- if all_success:
2459
- result['success'] = True
2460
- logger.info(f"{STICKERS['success']} " + "="*50)
2461
- logger.info(f"{STICKERS['success']} TESTS PASSED")
2462
- logger.info(f"{STICKERS['success']} Test ID: {test_id}")
2463
- logger.info(f"{STICKERS['success']} " + "="*50)
2496
+ if not ui :
2497
+ # Get exit codes
2498
+ ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
2499
+ exit_codes = {}
2500
+
2501
+ for container in ps:
2502
+ service_name = container.service or ''
2503
+ if service_name in pw_services:
2504
+ exit_code = container.exit_code if container.exit_code is not None else 1 # Default to 1 if unknown
2505
+ exit_codes[service_name] = exit_code
2506
+
2507
+ if not exit_codes:
2508
+ logger.error("No Playwright containers found during analysis — assuming failure")
2509
+ exit_codes = {pw_services[0]: 1} if pw_services else {'unknown': 1}
2510
+ result['error'] = 'No containers found for analysis - likely exited with error'
2511
+
2512
+ # Check overall success
2513
+ all_success = all(code == 0 for code in exit_codes.values())
2514
+
2515
+ if all_success:
2516
+ result['success'] = True
2517
+ logger.info(f"{STICKERS['success']} " + "="*50)
2518
+ logger.info(f"{STICKERS['success']} TESTS PASSED")
2519
+ logger.info(f"{STICKERS['success']} Test ID: {test_id}")
2520
+ logger.info(f"{STICKERS['success']} " + "="*50)
2521
+ else:
2522
+ result['success'] = False
2523
+ result['error'] = 'Playwright tests failed' if 'error' not in result else result['error']
2524
+ logger.error(f"{STICKERS['error']} " + "="*50)
2525
+ logger.error(f"{STICKERS['error']} TESTS FAILED")
2526
+ logger.error(f"{STICKERS['error']} Exit codes: {exit_codes}")
2527
+ logger.error(f"{STICKERS['error']} " + "="*50)
2464
2528
  else:
2465
- result['success'] = False
2466
- result['error'] = 'Playwright tests failed'
2467
- logger.error(f"{STICKERS['error']} " + "="*50)
2468
- logger.error(f"{STICKERS['error']} TESTS FAILED")
2469
- logger.error(f"{STICKERS['error']} Exit codes: {exit_codes}")
2470
- logger.error(f"{STICKERS['error']} " + "="*50)
2529
+ result['success'] = True
2530
+ logger.info(f"{STICKERS['info']} Ui conteiner is Up")
2531
+
2471
2532
  else:
2472
2533
  result['success'] = True
2473
2534
  logger.info(f"{STICKERS['info']} Analysis skipped (dry run)")
@@ -2478,6 +2539,8 @@ def playwright_test_compose(
2478
2539
  logger.info("")
2479
2540
 
2480
2541
  # Cleanup
2542
+ if ui :
2543
+ keep_compose_up = True
2481
2544
  if not keep_compose_up and not dry_run:
2482
2545
  logger.info(f"{STICKERS['cleanup']} Cleaning up")
2483
2546
  logger.info(f"{STICKERS['info']} Stopping Playwright stack...")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: utils_devops
3
- Version: 0.1.161
3
+ Version: 0.1.167
4
4
  Summary: Lightweight DevOps utilities for automation scripts: config editing (YAML/JSON/INI/.env), templating, diffing, and CLI tools
5
5
  License: MIT
6
6
  Keywords: devops,automation,nginx,cli,jinja2,yaml,config,diff,templating,logging,docker,compose,file-ops
@@ -9,7 +9,7 @@ utils_devops/core/strings.py,sha256=8s0GSjcyTKwLjJjsJ_XfOJxPtyb549icDlU9SUxSvHI,
9
9
  utils_devops/core/systems.py,sha256=wNbEFUAvbMPdqWN-iXvTzvj5iE9xaWfjZYYvD0EZAH0,47577
10
10
  utils_devops/extras/__init__.py,sha256=ZXHeVLHO3_qiW9AY-UQ_YA9cQzmkLGv54a2UbyvtlM0,3571
11
11
  utils_devops/extras/aws_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- utils_devops/extras/docker_ops.py,sha256=1ZyK3rUJXd9mI48wXZykrkiNFmQfxzk2i40swqWoN0s,195192
12
+ utils_devops/extras/docker_ops.py,sha256=VGH3SWufYEEr4459Xl_tgJ-rXNu62lFj-hSbtQua39I,197991
13
13
  utils_devops/extras/git_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  utils_devops/extras/interaction_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  utils_devops/extras/metrics_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -19,7 +19,7 @@ utils_devops/extras/notification_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
19
19
  utils_devops/extras/performance_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  utils_devops/extras/ssh_ops.py,sha256=hTOYzyWmnZWzOLeZbCoZRLxSJiBmr0QgS_87qks-CYk,76305
21
21
  utils_devops/extras/vault_ops.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- utils_devops-0.1.161.dist-info/METADATA,sha256=K8OwtBHqkMBqahNYxrRqfZooJOsor_o6jbE9um0Jd9c,1903
23
- utils_devops-0.1.161.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
24
- utils_devops-0.1.161.dist-info/entry_points.txt,sha256=ei3B6ZL5yu6dOq-U1r8wsBdkXeg63RAyV7m8_ADaE6k,53
25
- utils_devops-0.1.161.dist-info/RECORD,,
22
+ utils_devops-0.1.167.dist-info/METADATA,sha256=wDLqYUzvaS-2ZxOyvkSJEkokdUTawXNDxM2ytS2h1-s,1903
23
+ utils_devops-0.1.167.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
24
+ utils_devops-0.1.167.dist-info/entry_points.txt,sha256=ei3B6ZL5yu6dOq-U1r8wsBdkXeg63RAyV7m8_ADaE6k,53
25
+ utils_devops-0.1.167.dist-info/RECORD,,