utils_devops 0.1.141__tar.gz → 0.1.143__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.141 → utils_devops-0.1.143}/PKG-INFO +1 -1
- {utils_devops-0.1.141 → utils_devops-0.1.143}/pyproject.toml +1 -1
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/docker_ops.py +74 -29
- {utils_devops-0.1.141 → utils_devops-0.1.143}/README.md +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/__init__.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/__init__.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/datetimes.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/envs.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/files.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/logs.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/script_helpers.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/strings.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/systems.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/__init__.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/aws_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/git_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/interaction_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/metrics_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/network_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/nginx_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/notification_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/performance_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/ssh_ops.py +0 -0
- {utils_devops-0.1.141 → utils_devops-0.1.143}/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.143
|
|
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.143" # 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"
|
|
@@ -545,7 +545,7 @@ def validate_compose_file(compose_file: str, env_file: Optional[str] = None) ->
|
|
|
545
545
|
# Compose operations
|
|
546
546
|
def compose_up(compose_file: str, services: Optional[List[str]] = None,
|
|
547
547
|
build: bool = False, pull: bool = False, detach: bool = True,
|
|
548
|
-
project_name: Optional[str] = None , env_file: Optional[str] = None) -> bool:
|
|
548
|
+
project_name: Optional[str] = None , env_file: Optional[str] = None , no_build: Optional[bool] = False ,no_pull: Optional[bool] = False) -> bool:
|
|
549
549
|
"""Start compose services with detailed progress logging"""
|
|
550
550
|
cmd = ["up"]
|
|
551
551
|
if detach:
|
|
@@ -556,6 +556,12 @@ def compose_up(compose_file: str, services: Optional[List[str]] = None,
|
|
|
556
556
|
if pull:
|
|
557
557
|
cmd.append("--pull")
|
|
558
558
|
logger.info("⬇️ Pulling latest images before starting...")
|
|
559
|
+
if no_build:
|
|
560
|
+
cmd.append("--no-build")
|
|
561
|
+
logger.info("🔨 Forbidden Building images before starting...")
|
|
562
|
+
if no_pull:
|
|
563
|
+
cmd.append("--pull never")
|
|
564
|
+
logger.info("⬇️ Forbidden Pulling latest images before starting...")
|
|
559
565
|
if services:
|
|
560
566
|
cmd.extend(services)
|
|
561
567
|
logger.info(f"🚀 Starting services: {services}")
|
|
@@ -767,9 +773,6 @@ def check_service_health(compose_file: str, service: str,
|
|
|
767
773
|
logger.error(f"Error checking health for {service}: {e}")
|
|
768
774
|
return {"healthy": False, "error": str(e)}
|
|
769
775
|
|
|
770
|
-
import time
|
|
771
|
-
from collections import defaultdict
|
|
772
|
-
|
|
773
776
|
def wait_for_healthy(
|
|
774
777
|
compose_file: str,
|
|
775
778
|
services: List[str],
|
|
@@ -797,8 +800,17 @@ def wait_for_healthy(
|
|
|
797
800
|
if overall == 'healthy':
|
|
798
801
|
status_groups[overall].append(service)
|
|
799
802
|
else:
|
|
800
|
-
|
|
801
|
-
|
|
803
|
+
# Customize message based on overall
|
|
804
|
+
msg = f"{service}: {status['status']} (health: {status['health']})"
|
|
805
|
+
if overall == 'starting':
|
|
806
|
+
msg += " | Starting..."
|
|
807
|
+
elif overall == 'restarting':
|
|
808
|
+
msg += " | Restarting..."
|
|
809
|
+
elif overall == 'unhealthy':
|
|
810
|
+
msg += f" | Error: {status['error']}"
|
|
811
|
+
elif overall == 'not found':
|
|
812
|
+
msg += f" | Not found: {status['error']}"
|
|
813
|
+
status_groups[overall].append(msg)
|
|
802
814
|
|
|
803
815
|
# Define order and emojis
|
|
804
816
|
statuses = ['healthy', 'starting', 'restarting', 'unhealthy', 'not found']
|
|
@@ -849,17 +861,35 @@ def _compute_overall_status(service_status: Dict[str, Any]) -> str:
|
|
|
849
861
|
if not service_status:
|
|
850
862
|
return "not found"
|
|
851
863
|
|
|
852
|
-
|
|
864
|
+
# Use 'state' for base status (e.g., "running", "restarting")
|
|
865
|
+
state = service_status.get('state', '').lower()
|
|
853
866
|
health = service_status.get('health', 'none').lower()
|
|
867
|
+
status = service_status.get('status', '').lower() # Fallback for parsing
|
|
868
|
+
|
|
869
|
+
# Fallback parsing if state is empty
|
|
870
|
+
if not state:
|
|
871
|
+
if ' (health: ' in status:
|
|
872
|
+
health = status.split('(health: ')[1].split(')')[0].lower()
|
|
873
|
+
elif '(healthy)' in status:
|
|
874
|
+
health = 'healthy'
|
|
875
|
+
base_status = status.split(' (')[0]
|
|
876
|
+
if base_status.startswith('up'):
|
|
877
|
+
state = 'running'
|
|
878
|
+
elif 'restarting' in base_status:
|
|
879
|
+
state = 'restarting'
|
|
880
|
+
elif 'created' in base_status or 'paused' in base_status:
|
|
881
|
+
state = base_status
|
|
882
|
+
else:
|
|
883
|
+
state = 'unknown'
|
|
854
884
|
|
|
855
|
-
if
|
|
885
|
+
if state == 'restarting':
|
|
856
886
|
return "restarting"
|
|
857
887
|
|
|
858
|
-
if
|
|
888
|
+
if state in ['created', 'paused']:
|
|
859
889
|
return "starting" if health == 'starting' else "unhealthy"
|
|
860
890
|
|
|
861
|
-
if
|
|
862
|
-
return "unhealthy"
|
|
891
|
+
if state != 'running':
|
|
892
|
+
return "unhealthy"
|
|
863
893
|
|
|
864
894
|
if health == 'starting':
|
|
865
895
|
return "starting"
|
|
@@ -880,11 +910,11 @@ def check_health_from_compose_ps(compose_file: str) -> Dict[str, Any]:
|
|
|
880
910
|
result = run_compose_command(compose_file, ["ps", "--format", "json"])
|
|
881
911
|
if result.rc != 0:
|
|
882
912
|
return {"healthy": False, "error": f"docker compose ps failed: {result.stderr}"}
|
|
883
|
-
|
|
913
|
+
|
|
884
914
|
# Parse JSON output - handle multiple JSON objects (one per line)
|
|
885
915
|
import json
|
|
886
916
|
containers_data = []
|
|
887
|
-
|
|
917
|
+
|
|
888
918
|
# Split by lines and parse each JSON object separately
|
|
889
919
|
for line in result.stdout.strip().split('\n'):
|
|
890
920
|
if line.strip(): # Skip empty lines
|
|
@@ -894,18 +924,19 @@ def check_health_from_compose_ps(compose_file: str) -> Dict[str, Any]:
|
|
|
894
924
|
except json.JSONDecodeError as e:
|
|
895
925
|
logger.error(f"Failed to parse JSON line: {line}, error: {e}")
|
|
896
926
|
continue
|
|
897
|
-
|
|
927
|
+
|
|
898
928
|
if not containers_data:
|
|
899
929
|
return {"healthy": False, "error": "No container data parsed from output"}
|
|
900
|
-
|
|
930
|
+
|
|
901
931
|
health_status = {}
|
|
902
932
|
all_healthy = True
|
|
903
|
-
|
|
933
|
+
|
|
904
934
|
for container in containers_data:
|
|
905
935
|
service = container.get('Service', '')
|
|
906
936
|
health = container.get('Health', '')
|
|
907
937
|
status = container.get('Status', '')
|
|
908
|
-
|
|
938
|
+
state = container.get('State', '') # Added: the base state like "running"
|
|
939
|
+
|
|
909
940
|
# Determine health status
|
|
910
941
|
is_healthy = False
|
|
911
942
|
if health:
|
|
@@ -913,22 +944,23 @@ def check_health_from_compose_ps(compose_file: str) -> Dict[str, Any]:
|
|
|
913
944
|
else:
|
|
914
945
|
# Fallback to status parsing
|
|
915
946
|
is_healthy = '(healthy)' in status.lower()
|
|
916
|
-
|
|
947
|
+
|
|
917
948
|
health_status[service] = {
|
|
918
949
|
'healthy': is_healthy,
|
|
919
950
|
'health': health,
|
|
920
|
-
'status': status
|
|
951
|
+
'status': status,
|
|
952
|
+
'state': state # Added
|
|
921
953
|
}
|
|
922
|
-
|
|
954
|
+
|
|
923
955
|
if not is_healthy:
|
|
924
956
|
all_healthy = False
|
|
925
|
-
|
|
957
|
+
|
|
926
958
|
return {
|
|
927
959
|
"healthy": all_healthy,
|
|
928
960
|
"services": health_status,
|
|
929
961
|
"details": f"{sum(1 for s in health_status.values() if s['healthy'])}/{len(health_status)} services healthy"
|
|
930
962
|
}
|
|
931
|
-
|
|
963
|
+
|
|
932
964
|
except Exception as e:
|
|
933
965
|
return {"healthy": False, "error": f"Health check failed: {e}"}
|
|
934
966
|
|
|
@@ -1352,24 +1384,34 @@ def _get_detailed_health_status(
|
|
|
1352
1384
|
) -> Dict[str, Dict[str, Any]]:
|
|
1353
1385
|
"""Get detailed health status for each service with overall_status."""
|
|
1354
1386
|
detailed_status = {}
|
|
1355
|
-
|
|
1387
|
+
|
|
1356
1388
|
try:
|
|
1357
1389
|
# Use the reliable JSON parsing method
|
|
1358
1390
|
health_result = check_health_from_compose_ps(compose_file)
|
|
1359
1391
|
all_services_status = health_result.get('services', {})
|
|
1360
|
-
|
|
1392
|
+
|
|
1361
1393
|
for service in services:
|
|
1362
1394
|
service_status = all_services_status.get(service, {})
|
|
1363
1395
|
overall_status = _compute_overall_status(service_status)
|
|
1396
|
+
error_msg = "No error details"
|
|
1397
|
+
if overall_status == "starting":
|
|
1398
|
+
error_msg = "Service is starting"
|
|
1399
|
+
elif overall_status == "restarting":
|
|
1400
|
+
error_msg = "Service is restarting"
|
|
1401
|
+
elif overall_status == "unhealthy":
|
|
1402
|
+
error_msg = f"Service is unhealthy: {service_status.get('status', 'unknown')}"
|
|
1403
|
+
elif overall_status == "not found":
|
|
1404
|
+
error_msg = "Service not found"
|
|
1364
1405
|
detailed_status[service] = {
|
|
1365
1406
|
'healthy': overall_status == 'healthy',
|
|
1366
1407
|
'health': service_status.get('health', 'unknown'),
|
|
1367
1408
|
'status': service_status.get('status', 'not found'),
|
|
1368
|
-
'
|
|
1409
|
+
'state': service_status.get('state', 'unknown'), # Added
|
|
1410
|
+
'error': error_msg,
|
|
1369
1411
|
'overall_status': overall_status
|
|
1370
1412
|
}
|
|
1371
|
-
# Removed
|
|
1372
|
-
|
|
1413
|
+
# Removed individual logging; handled in groups
|
|
1414
|
+
|
|
1373
1415
|
except Exception as e:
|
|
1374
1416
|
logger.error(f"Failed to get detailed health status: {e}")
|
|
1375
1417
|
# Fallback: mark all as unhealthy
|
|
@@ -1378,10 +1420,11 @@ def _get_detailed_health_status(
|
|
|
1378
1420
|
'healthy': False,
|
|
1379
1421
|
'health': 'unknown',
|
|
1380
1422
|
'status': 'check failed',
|
|
1423
|
+
'state': 'unknown',
|
|
1381
1424
|
'error': str(e),
|
|
1382
1425
|
'overall_status': 'unhealthy'
|
|
1383
1426
|
}
|
|
1384
|
-
|
|
1427
|
+
|
|
1385
1428
|
return detailed_status
|
|
1386
1429
|
|
|
1387
1430
|
|
|
@@ -2334,10 +2377,12 @@ def test_compose(
|
|
|
2334
2377
|
compose_file,
|
|
2335
2378
|
services=services,
|
|
2336
2379
|
project_name=project_name,
|
|
2380
|
+
no_build=no_build,
|
|
2381
|
+
no_pull=no_pull,
|
|
2337
2382
|
)
|
|
2338
2383
|
# Assuming compose_up returns a dict with 'success'; adjust if needed
|
|
2339
2384
|
if up_result is not True: # If it doesn't raise, check result
|
|
2340
|
-
raise DockerOpsError(f"Compose up failed:
|
|
2385
|
+
raise DockerOpsError(f"Compose up failed: 'Fial to docker compose up for Unknown error'")
|
|
2341
2386
|
except Exception as e:
|
|
2342
2387
|
result["error"] = str(e)
|
|
2343
2388
|
logger.error(f"❌ Compose up failed: {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
|