utils_devops 0.1.160__py3-none-any.whl → 0.1.166__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
  """
@@ -2304,14 +2352,14 @@ def playwright_test_compose(
2304
2352
  new_version = _update_env_version(env_file=env_file, source=version_source, logger=logger)
2305
2353
  if new_version:
2306
2354
  logger.info(f"{STICKERS['success']} Version updated to {new_version}")
2307
- envs.set_system_env(key='STATIC_CAPTCHA', value='true')
2308
2355
  else:
2309
2356
  logger.info(f"{STICKERS['info']} Version update skipped (dry run)")
2310
2357
  logger.info("")
2311
2358
 
2312
2359
  # STEP 2: Environment
2313
2360
  if not dry_run:
2314
- envs.set_system_env(key='STATIC_CAPTCHA', value='true')
2361
+ envs.dotenv_set_key(env_file,"STATIC_CAPTCHA","true")
2362
+
2315
2363
 
2316
2364
  # STEP 3: Application stack
2317
2365
  logger.info(f"{STICKERS['info']} STEP 3: Starting application stack")
@@ -2348,6 +2396,7 @@ def playwright_test_compose(
2348
2396
  logger.info(f"{STICKERS['info']} STEP 4: Starting Playwright runner")
2349
2397
  if not dry_run:
2350
2398
  logger.info(f"{STICKERS['info']} Project: {project_name_playwright}")
2399
+ resolve_conflicts(playwright_compose_file,remove_conflicting_containers=True)
2351
2400
  up_success = compose_up(
2352
2401
  playwright_compose_file,
2353
2402
  services=playwright_services,
@@ -2367,7 +2416,8 @@ def playwright_test_compose(
2367
2416
  # STEP 5: Log capture
2368
2417
  pw_services = []
2369
2418
  logs_acc = {}
2370
-
2419
+ if ui :
2420
+ capture_logs = False
2371
2421
  if not dry_run and capture_logs:
2372
2422
  logger.info(f"{STICKERS['logs']} STEP 5: Capturing logs")
2373
2423
 
@@ -2419,54 +2469,66 @@ def playwright_test_compose(
2419
2469
 
2420
2470
  # Check if all services have finished
2421
2471
  ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
2422
- running_count = 0
2423
-
2424
- for container in ps:
2425
- service_name = container.service or ''
2426
- if service_name in pw_services:
2427
- 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()
2428
2479
  if 'running' in status or 'up' in status:
2429
- running_count += 1
2430
-
2431
- if running_count == 0:
2432
- finished = True
2433
- logger.info(f"{STICKERS['complete']} All services completed")
2434
- logger.info("")
2435
- else:
2480
+ running.append(c)
2481
+ elif 'exited' in status:
2482
+ exited.append(c)
2483
+
2484
+ if running:
2436
2485
  time.sleep(health_interval)
2486
+ else:
2487
+ finished = True
2488
+
2437
2489
 
2438
2490
  result['logs_captured'] = logs_acc
2439
2491
 
2440
2492
  # STEP 6: Analysis
2441
2493
  logger.info(f"{STICKERS['analysis']} STEP 6: Analyzing results")
2442
-
2494
+
2443
2495
  if not dry_run:
2444
- # Get exit codes
2445
- ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
2446
- exit_codes = {}
2447
-
2448
- for container in ps:
2449
- service_name = container.service or ''
2450
- if service_name in pw_services:
2451
- exit_code = getattr(container, 'exit_code', None) or 1
2452
- exit_codes[service_name] = exit_code
2453
-
2454
- # Check overall success
2455
- all_success = all(code == 0 for code in exit_codes.values())
2456
-
2457
- if all_success:
2458
- result['success'] = True
2459
- logger.info(f"{STICKERS['success']} " + "="*50)
2460
- logger.info(f"{STICKERS['success']} TESTS PASSED")
2461
- logger.info(f"{STICKERS['success']} Test ID: {test_id}")
2462
- 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)
2463
2528
  else:
2464
- result['success'] = False
2465
- result['error'] = 'Playwright tests failed'
2466
- logger.error(f"{STICKERS['error']} " + "="*50)
2467
- logger.error(f"{STICKERS['error']} TESTS FAILED")
2468
- logger.error(f"{STICKERS['error']} Exit codes: {exit_codes}")
2469
- logger.error(f"{STICKERS['error']} " + "="*50)
2529
+ result['success'] = True
2530
+ logger.info(f"{STICKERS['info']} Ui conteiner is Up")
2531
+
2470
2532
  else:
2471
2533
  result['success'] = True
2472
2534
  logger.info(f"{STICKERS['info']} Analysis skipped (dry run)")
@@ -2477,6 +2539,8 @@ def playwright_test_compose(
2477
2539
  logger.info("")
2478
2540
 
2479
2541
  # Cleanup
2542
+ if ui :
2543
+ keep_compose_up = True
2480
2544
  if not keep_compose_up and not dry_run:
2481
2545
  logger.info(f"{STICKERS['cleanup']} Cleaning up")
2482
2546
  logger.info(f"{STICKERS['info']} Stopping Playwright stack...")
@@ -4628,7 +4692,7 @@ def resolve_conflicts(
4628
4692
  remove_conflicting_containers: bool = False,
4629
4693
  remove_conflicting_networks: bool = False,
4630
4694
  remove_conflicting_volumes: bool = False,
4631
- force: bool = False,
4695
+ force: bool = True,
4632
4696
  project_name: Optional[str] = None,
4633
4697
  env_file: Optional[str] = None
4634
4698
  ) -> Dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: utils_devops
3
- Version: 0.1.160
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
@@ -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=ow9xa_NA7fIf44vSiTnHRhVBgMXK2m98OoM3syRyEqs,195176
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.160.dist-info/METADATA,sha256=1rf8mMVgKcP4yP4fd__okdptSNhKa5Lir6Z8OQIzQbk,1903
23
- utils_devops-0.1.160.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
24
- utils_devops-0.1.160.dist-info/entry_points.txt,sha256=ei3B6ZL5yu6dOq-U1r8wsBdkXeg63RAyV7m8_ADaE6k,53
25
- utils_devops-0.1.160.dist-info/RECORD,,
22
+ utils_devops-0.1.166.dist-info/METADATA,sha256=zkvREOE-NO-qC12A1VoMvJNisJ-rq7MGWZzVsrrKU7k,1903
23
+ utils_devops-0.1.166.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
24
+ utils_devops-0.1.166.dist-info/entry_points.txt,sha256=ei3B6ZL5yu6dOq-U1r8wsBdkXeg63RAyV7m8_ADaE6k,53
25
+ utils_devops-0.1.166.dist-info/RECORD,,