utils_devops 0.1.138__tar.gz → 0.1.140__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.
Files changed (24) hide show
  1. {utils_devops-0.1.138 → utils_devops-0.1.140}/PKG-INFO +1 -1
  2. {utils_devops-0.1.138 → utils_devops-0.1.140}/pyproject.toml +1 -1
  3. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/docker_ops.py +81 -62
  4. {utils_devops-0.1.138 → utils_devops-0.1.140}/README.md +0 -0
  5. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/__init__.py +0 -0
  6. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/__init__.py +0 -0
  7. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/datetimes.py +0 -0
  8. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/envs.py +0 -0
  9. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/files.py +0 -0
  10. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/logs.py +0 -0
  11. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/script_helpers.py +0 -0
  12. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/strings.py +0 -0
  13. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/systems.py +0 -0
  14. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/__init__.py +0 -0
  15. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/aws_ops.py +0 -0
  16. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/git_ops.py +0 -0
  17. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/interaction_ops.py +0 -0
  18. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/metrics_ops.py +0 -0
  19. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/network_ops.py +0 -0
  20. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/nginx_ops.py +0 -0
  21. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/notification_ops.py +0 -0
  22. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/performance_ops.py +0 -0
  23. {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/ssh_ops.py +0 -0
  24. {utils_devops-0.1.138 → utils_devops-0.1.140}/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.138
3
+ Version: 0.1.140
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.138" # Bumped for new string features + diffing
3
+ version = "0.1.140" # 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"
@@ -767,75 +767,67 @@ def check_service_health(compose_file: str, service: str,
767
767
  logger.error(f"Error checking health for {service}: {e}")
768
768
  return {"healthy": False, "error": str(e)}
769
769
 
770
- def wait_for_healthy(compose_file: str, services: Optional[List[str]] = None,
771
- timeout: int = 300, interval: int = 5, logger: Optional[Logger] = None,env_file: Optional[str] = None) -> bool:
772
- """Wait for services to become healthy - ENHANCED LOGGING VERSION"""
773
- # Use parent function's logger if provided, otherwise get default
774
- logger = logger or DEFAULT_LOGGER
775
-
770
+ import time
771
+ from collections import defaultdict
772
+
773
+ def wait_for_healthy(
774
+ compose_file: str,
775
+ services: List[str],
776
+ timeout: int,
777
+ interval: int,
778
+ logger: logger,
779
+ env_file: Optional[str] = None
780
+ ) -> bool:
781
+ """Wait for services to become healthy with detailed status logging."""
776
782
  start_time = time.time()
777
- services_to_check = services or get_services_from_compose(compose_file , env_file)
783
+ attempt = 1
778
784
 
779
- logger.info(f"🩺 Starting health checks for {len(services_to_check)} services: {services_to_check}")
780
- logger.info(f"⏰ Timeout: {timeout}s, Check interval: {interval}s")
781
-
782
- attempt = 0
783
785
  while time.time() - start_time < timeout:
784
- attempt += 1
785
786
  elapsed = time.time() - start_time
786
- time_remaining = timeout - elapsed
787
-
788
- all_healthy = True
789
- unhealthy_services = []
790
- healthy_services = []
791
-
792
- # Check each service
793
- for service in services_to_check:
794
- health_result = check_service_health(compose_file, service)
795
- is_healthy = health_result.get('healthy', False)
796
-
797
- if is_healthy:
798
- healthy_services.append(service)
787
+ remaining = timeout - elapsed
788
+ logger.info(f"🔍 Health check attempt {attempt} ({elapsed:.1f}s elapsed, {remaining:.1f}s remaining):")
789
+
790
+ # Get detailed health status
791
+ detailed_health = _get_detailed_health_status(compose_file, services, logger)
792
+
793
+ # Group services by overall_status
794
+ status_groups = defaultdict(list)
795
+ for service, status in detailed_health.items():
796
+ overall = status['overall_status']
797
+ if overall == 'healthy':
798
+ status_groups[overall].append(service)
799
799
  else:
800
- all_healthy = False
801
- unhealthy_services.append({
802
- 'service': service,
803
- 'status': health_result.get('status', 'unknown'),
804
- 'error': health_result.get('error', 'No error details')
805
- })
806
-
807
- # Log detailed status for each attempt
808
- logger.info(f"🔍 Health check attempt {attempt} ({elapsed:.1f}s elapsed, {time_remaining:.1f}s remaining):")
809
- logger.info(f" ✅ Healthy ({len(healthy_services)}): {healthy_services}")
810
-
811
- if unhealthy_services:
812
- logger.info(f" Unhealthy ({len(unhealthy_services)}):")
813
- for us in unhealthy_services:
814
- logger.info(f" - {us['service']}: {us['status']} | Error: {us['error']}")
800
+ detail = f"{service}: {status['status']} (health: {status['health']}) | Error: {status['error']}"
801
+ status_groups[overall].append(detail)
802
+
803
+ # Define order and emojis
804
+ statuses = ['healthy', 'starting', 'restarting', 'unhealthy', 'not found']
805
+ emojis = {'healthy': '✅', 'starting': '🚀', 'restarting': '🔄', 'unhealthy': '❌', 'not found': '❓'}
806
+
807
+ # Log groups if they have items
808
+ for status in statuses:
809
+ group = status_groups[status]
810
+ if group:
811
+ emoji = emojis[status]
812
+ logger.info(f" {emoji} {status.capitalize()} ({len(group)}):")
813
+ if status == 'healthy':
814
+ logger.info(f" {sorted(group)}")
815
+ else:
816
+ for detail in sorted(group):
817
+ logger.info(f" - {detail}")
815
818
 
816
- if all_healthy:
817
- total_time = time.time() - start_time
818
- logger.info(f"🎉 ALL SERVICES HEALTHY! Completed in {total_time:.1f}s after {attempt} attempts")
819
+ # Check if all healthy
820
+ if len(status_groups['healthy']) == len(services):
821
+ logger.info(f"🎉 ALL SERVICES HEALTHY! Completed in {elapsed:.1f}s after {attempt} attempts")
819
822
  return True
820
823
 
821
- # Wait before next check
822
- if time_remaining > interval:
823
- logger.info(f"⏳ Waiting {interval}s before next health check...")
824
- dt_ops.time.sleep(interval)
825
- else:
826
- # Don't sleep if we're about to timeout
827
- break
828
-
829
- # Timeout reached
830
- elapsed = time.time() - start_time
831
- logger.error(f"⏰ HEALTH CHECK TIMEOUT after {elapsed:.1f}s and {attempt} attempts!")
832
- logger.error(f"💔 Final status - Healthy: {len(healthy_services)}, Unhealthy: {len(unhealthy_services)}")
833
-
834
- if unhealthy_services:
835
- logger.error("📋 Unhealthy services details:")
836
- for us in unhealthy_services:
837
- logger.error(f" - {us['service']}: {us['status']} | {us['error']}")
824
+ # Wait for next attempt
825
+ logger.info(f"⏳ Waiting {interval}s before next health check...")
826
+ time.sleep(interval)
827
+ attempt += 1
838
828
 
829
+ # Timeout
830
+ logger.error(f"❌ Health check timeout after {time.time() - start_time:.1f}s")
839
831
  return False
840
832
 
841
833
  def health_check_docker_compose(compose_file: str) -> bool:
@@ -1281,6 +1273,8 @@ def _check_missing_images(
1281
1273
 
1282
1274
  return missing_services
1283
1275
 
1276
+ from collections import defaultdict # Add this import at the top of your file
1277
+
1284
1278
  def _perform_health_checks(
1285
1279
  compose_file: str,
1286
1280
  services: List[str],
@@ -1312,7 +1306,32 @@ def _perform_health_checks(
1312
1306
  # Get detailed health status for all services
1313
1307
  detailed_health = _get_detailed_health_status(compose_file, services, logger)
1314
1308
 
1315
- healthy_count = sum(1 for status in detailed_health.values() if status.get('overall_status') == 'healthy')
1309
+ # Group services by overall_status for detailed logging
1310
+ status_groups = defaultdict(list)
1311
+ for service, status in detailed_health.items():
1312
+ overall = status['overall_status']
1313
+ if overall == 'healthy':
1314
+ status_groups[overall].append(service)
1315
+ else:
1316
+ detail = f"{service}: {status['status']} (health: {status['health']}) | Error: {status['error']}"
1317
+ status_groups[overall].append(detail)
1318
+
1319
+ # Log grouped statuses
1320
+ statuses = ['healthy', 'starting', 'restarting', 'unhealthy', 'not found']
1321
+ emojis = {'healthy': '✅', 'starting': '🚀', 'restarting': '🔄', 'unhealthy': '❌', 'not found': '❓'}
1322
+
1323
+ for status in statuses:
1324
+ group = status_groups[status]
1325
+ if group:
1326
+ emoji = emojis[status]
1327
+ logger.info(f"{emoji} {status.capitalize()} ({len(group)}):")
1328
+ if status == 'healthy':
1329
+ logger.info(f" {sorted(group)}")
1330
+ else:
1331
+ for detail in sorted(group):
1332
+ logger.info(f" - {detail}")
1333
+
1334
+ healthy_count = len(status_groups['healthy'])
1316
1335
  unhealthy_count = len(services) - healthy_count
1317
1336
 
1318
1337
  logger.info(f" Healthy: {healthy_count}/{len(services)}, Unhealthy: {unhealthy_count}")
@@ -1347,7 +1366,7 @@ def _get_detailed_health_status(
1347
1366
  'error': service_status.get('error', 'No error details'),
1348
1367
  'overall_status': overall_status
1349
1368
  }
1350
- logger.info(f" {service}: {overall_status}")
1369
+ # Removed: logger.info(f" {service}: {overall_status}") # Now handled grouped in _perform_health_checks
1351
1370
 
1352
1371
  except Exception as e:
1353
1372
  logger.error(f"Failed to get detailed health status: {e}")
File without changes