utils_devops 0.1.161__tar.gz → 0.1.166__tar.gz
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.
- {utils_devops-0.1.161 → utils_devops-0.1.166}/PKG-INFO +1 -1
- {utils_devops-0.1.161 → utils_devops-0.1.166}/pyproject.toml +1 -1
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/docker_ops.py +128 -65
- {utils_devops-0.1.161 → utils_devops-0.1.166}/README.md +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/__init__.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/core/__init__.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/core/datetimes.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/core/envs.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/core/files.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/core/logs.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/core/script_helpers.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/core/strings.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/core/systems.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/__init__.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/aws_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/git_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/interaction_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/metrics_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/network_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/nginx_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/notification_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/performance_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/ssh_ops.py +0 -0
- {utils_devops-0.1.161 → utils_devops-0.1.166}/src/utils_devops/extras/vault_ops.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: utils_devops
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.166
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "utils_devops"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.166" # Bumped for new string features + diffing
|
|
4
4
|
description = "Lightweight DevOps utilities for automation scripts: config editing (YAML/JSON/INI/.env), templating, diffing, and CLI tools"
|
|
5
5
|
authors = ["Hamed Sheikhan <sh.sheikhan.m@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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=
|
|
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
|
-
|
|
634
|
-
# Fallback to text parsing
|
|
635
|
-
lines
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
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
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
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
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
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'] =
|
|
2466
|
-
|
|
2467
|
-
|
|
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...")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|