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.
Files changed (24) hide show
  1. {utils_devops-0.1.141 → utils_devops-0.1.143}/PKG-INFO +1 -1
  2. {utils_devops-0.1.141 → utils_devops-0.1.143}/pyproject.toml +1 -1
  3. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/docker_ops.py +74 -29
  4. {utils_devops-0.1.141 → utils_devops-0.1.143}/README.md +0 -0
  5. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/__init__.py +0 -0
  6. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/__init__.py +0 -0
  7. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/datetimes.py +0 -0
  8. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/envs.py +0 -0
  9. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/files.py +0 -0
  10. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/logs.py +0 -0
  11. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/script_helpers.py +0 -0
  12. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/strings.py +0 -0
  13. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/core/systems.py +0 -0
  14. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/__init__.py +0 -0
  15. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/aws_ops.py +0 -0
  16. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/git_ops.py +0 -0
  17. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/interaction_ops.py +0 -0
  18. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/metrics_ops.py +0 -0
  19. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/network_ops.py +0 -0
  20. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/nginx_ops.py +0 -0
  21. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/notification_ops.py +0 -0
  22. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/performance_ops.py +0 -0
  23. {utils_devops-0.1.141 → utils_devops-0.1.143}/src/utils_devops/extras/ssh_ops.py +0 -0
  24. {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.141
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.141" # Bumped for new string features + diffing
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
- detail = f"{service}: {status['status']} | Error: {status['error']}"
801
- status_groups[overall].append(detail)
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
- status = service_status.get('status', 'not found').lower()
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 status == 'restarting':
885
+ if state == 'restarting':
856
886
  return "restarting"
857
887
 
858
- if status in ['created', 'paused']: # Treat as starting or unhealthy based on context
888
+ if state in ['created', 'paused']:
859
889
  return "starting" if health == 'starting' else "unhealthy"
860
890
 
861
- if status != 'running':
862
- return "unhealthy" # exited, dead, etc.
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
- 'error': service_status.get('error', 'No error details'),
1409
+ 'state': service_status.get('state', 'unknown'), # Added
1410
+ 'error': error_msg,
1369
1411
  'overall_status': overall_status
1370
1412
  }
1371
- # Removed: logger.info(f" {service}: {overall_status}") # Now handled grouped in _perform_health_checks
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: {up_result.get('error', 'Unknown error')}")
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