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.
- {utils_devops-0.1.138 → utils_devops-0.1.140}/PKG-INFO +1 -1
- {utils_devops-0.1.138 → utils_devops-0.1.140}/pyproject.toml +1 -1
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/docker_ops.py +81 -62
- {utils_devops-0.1.138 → utils_devops-0.1.140}/README.md +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/__init__.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/__init__.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/datetimes.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/envs.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/files.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/logs.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/script_helpers.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/strings.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/core/systems.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/__init__.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/aws_ops.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/git_ops.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/interaction_ops.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/metrics_ops.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/network_ops.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/nginx_ops.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/notification_ops.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/performance_ops.py +0 -0
- {utils_devops-0.1.138 → utils_devops-0.1.140}/src/utils_devops/extras/ssh_ops.py +0 -0
- {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.
|
|
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.
|
|
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
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
#
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
# Log
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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
|
|
817
|
-
|
|
818
|
-
logger.info(f"🎉 ALL SERVICES HEALTHY! Completed in {
|
|
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
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
|
|
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
|
|
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
|