utils_devops 0.1.140__tar.gz → 0.1.142__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.140 → utils_devops-0.1.142}/PKG-INFO +1 -1
  2. {utils_devops-0.1.140 → utils_devops-0.1.142}/pyproject.toml +1 -1
  3. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/docker_ops.py +77 -30
  4. {utils_devops-0.1.140 → utils_devops-0.1.142}/README.md +0 -0
  5. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/__init__.py +0 -0
  6. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/core/__init__.py +0 -0
  7. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/core/datetimes.py +0 -0
  8. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/core/envs.py +0 -0
  9. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/core/files.py +0 -0
  10. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/core/logs.py +0 -0
  11. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/core/script_helpers.py +0 -0
  12. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/core/strings.py +0 -0
  13. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/core/systems.py +0 -0
  14. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/__init__.py +0 -0
  15. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/aws_ops.py +0 -0
  16. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/git_ops.py +0 -0
  17. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/interaction_ops.py +0 -0
  18. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/metrics_ops.py +0 -0
  19. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/network_ops.py +0 -0
  20. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/nginx_ops.py +0 -0
  21. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/notification_ops.py +0 -0
  22. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/performance_ops.py +0 -0
  23. {utils_devops-0.1.140 → utils_devops-0.1.142}/src/utils_devops/extras/ssh_ops.py +0 -0
  24. {utils_devops-0.1.140 → utils_devops-0.1.142}/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.140
3
+ Version: 0.1.142
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.140" # Bumped for new string features + diffing
3
+ version = "0.1.142" # 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("--no-pull")
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']} (health: {status['health']}) | 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']
@@ -816,8 +828,10 @@ def wait_for_healthy(
816
828
  for detail in sorted(group):
817
829
  logger.info(f" - {detail}")
818
830
 
819
- # Check if all healthy
820
- if len(status_groups['healthy']) == len(services):
831
+ # Check if all healthy using the 'healthy' bool
832
+ all_healthy = all(status.get('healthy', False) for status in detailed_health.values())
833
+
834
+ if all_healthy:
821
835
  logger.info(f"🎉 ALL SERVICES HEALTHY! Completed in {elapsed:.1f}s after {attempt} attempts")
822
836
  return True
823
837
 
@@ -847,17 +861,35 @@ def _compute_overall_status(service_status: Dict[str, Any]) -> str:
847
861
  if not service_status:
848
862
  return "not found"
849
863
 
850
- 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()
851
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'
852
884
 
853
- if status == 'restarting':
885
+ if state == 'restarting':
854
886
  return "restarting"
855
887
 
856
- if status in ['created', 'paused']: # Treat as starting or unhealthy based on context
888
+ if state in ['created', 'paused']:
857
889
  return "starting" if health == 'starting' else "unhealthy"
858
890
 
859
- if status != 'running':
860
- return "unhealthy" # exited, dead, etc.
891
+ if state != 'running':
892
+ return "unhealthy"
861
893
 
862
894
  if health == 'starting':
863
895
  return "starting"
@@ -878,11 +910,11 @@ def check_health_from_compose_ps(compose_file: str) -> Dict[str, Any]:
878
910
  result = run_compose_command(compose_file, ["ps", "--format", "json"])
879
911
  if result.rc != 0:
880
912
  return {"healthy": False, "error": f"docker compose ps failed: {result.stderr}"}
881
-
913
+
882
914
  # Parse JSON output - handle multiple JSON objects (one per line)
883
915
  import json
884
916
  containers_data = []
885
-
917
+
886
918
  # Split by lines and parse each JSON object separately
887
919
  for line in result.stdout.strip().split('\n'):
888
920
  if line.strip(): # Skip empty lines
@@ -892,18 +924,19 @@ def check_health_from_compose_ps(compose_file: str) -> Dict[str, Any]:
892
924
  except json.JSONDecodeError as e:
893
925
  logger.error(f"Failed to parse JSON line: {line}, error: {e}")
894
926
  continue
895
-
927
+
896
928
  if not containers_data:
897
929
  return {"healthy": False, "error": "No container data parsed from output"}
898
-
930
+
899
931
  health_status = {}
900
932
  all_healthy = True
901
-
933
+
902
934
  for container in containers_data:
903
935
  service = container.get('Service', '')
904
936
  health = container.get('Health', '')
905
937
  status = container.get('Status', '')
906
-
938
+ state = container.get('State', '') # Added: the base state like "running"
939
+
907
940
  # Determine health status
908
941
  is_healthy = False
909
942
  if health:
@@ -911,22 +944,23 @@ def check_health_from_compose_ps(compose_file: str) -> Dict[str, Any]:
911
944
  else:
912
945
  # Fallback to status parsing
913
946
  is_healthy = '(healthy)' in status.lower()
914
-
947
+
915
948
  health_status[service] = {
916
949
  'healthy': is_healthy,
917
950
  'health': health,
918
- 'status': status
951
+ 'status': status,
952
+ 'state': state # Added
919
953
  }
920
-
954
+
921
955
  if not is_healthy:
922
956
  all_healthy = False
923
-
957
+
924
958
  return {
925
959
  "healthy": all_healthy,
926
960
  "services": health_status,
927
961
  "details": f"{sum(1 for s in health_status.values() if s['healthy'])}/{len(health_status)} services healthy"
928
962
  }
929
-
963
+
930
964
  except Exception as e:
931
965
  return {"healthy": False, "error": f"Health check failed: {e}"}
932
966
 
@@ -1350,24 +1384,34 @@ def _get_detailed_health_status(
1350
1384
  ) -> Dict[str, Dict[str, Any]]:
1351
1385
  """Get detailed health status for each service with overall_status."""
1352
1386
  detailed_status = {}
1353
-
1387
+
1354
1388
  try:
1355
1389
  # Use the reliable JSON parsing method
1356
1390
  health_result = check_health_from_compose_ps(compose_file)
1357
1391
  all_services_status = health_result.get('services', {})
1358
-
1392
+
1359
1393
  for service in services:
1360
1394
  service_status = all_services_status.get(service, {})
1361
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"
1362
1405
  detailed_status[service] = {
1363
1406
  'healthy': overall_status == 'healthy',
1364
1407
  'health': service_status.get('health', 'unknown'),
1365
1408
  'status': service_status.get('status', 'not found'),
1366
- 'error': service_status.get('error', 'No error details'),
1409
+ 'state': service_status.get('state', 'unknown'), # Added
1410
+ 'error': error_msg,
1367
1411
  'overall_status': overall_status
1368
1412
  }
1369
- # Removed: logger.info(f" {service}: {overall_status}") # Now handled grouped in _perform_health_checks
1370
-
1413
+ # Removed individual logging; handled in groups
1414
+
1371
1415
  except Exception as e:
1372
1416
  logger.error(f"Failed to get detailed health status: {e}")
1373
1417
  # Fallback: mark all as unhealthy
@@ -1376,10 +1420,11 @@ def _get_detailed_health_status(
1376
1420
  'healthy': False,
1377
1421
  'health': 'unknown',
1378
1422
  'status': 'check failed',
1423
+ 'state': 'unknown',
1379
1424
  'error': str(e),
1380
1425
  'overall_status': 'unhealthy'
1381
1426
  }
1382
-
1427
+
1383
1428
  return detailed_status
1384
1429
 
1385
1430
 
@@ -2332,6 +2377,8 @@ def test_compose(
2332
2377
  compose_file,
2333
2378
  services=services,
2334
2379
  project_name=project_name,
2380
+ no_build=no_build,
2381
+ no_pull=no_pull,
2335
2382
  )
2336
2383
  # Assuming compose_up returns a dict with 'success'; adjust if needed
2337
2384
  if up_result is not True: # If it doesn't raise, check result
File without changes