runbooks 1.1.4__py3-none-any.whl → 1.1.6__py3-none-any.whl

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 (273) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +135 -91
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +17 -12
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +99 -79
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +315 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/aws_decorators.py +2 -3
  113. runbooks/inventory/check_cloudtrail_compliance.py +2 -4
  114. runbooks/inventory/check_controltower_readiness.py +152 -151
  115. runbooks/inventory/check_landingzone_readiness.py +85 -84
  116. runbooks/inventory/cloud_foundations_integration.py +144 -149
  117. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  118. runbooks/inventory/collectors/aws_networking.py +109 -99
  119. runbooks/inventory/collectors/base.py +4 -0
  120. runbooks/inventory/core/collector.py +495 -313
  121. runbooks/inventory/core/formatter.py +11 -0
  122. runbooks/inventory/draw_org_structure.py +8 -9
  123. runbooks/inventory/drift_detection_cli.py +69 -96
  124. runbooks/inventory/ec2_vpc_utils.py +2 -2
  125. runbooks/inventory/find_cfn_drift_detection.py +5 -7
  126. runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
  127. runbooks/inventory/find_cfn_stackset_drift.py +5 -6
  128. runbooks/inventory/find_ec2_security_groups.py +48 -42
  129. runbooks/inventory/find_landingzone_versions.py +4 -6
  130. runbooks/inventory/find_vpc_flow_logs.py +7 -9
  131. runbooks/inventory/inventory_mcp_cli.py +48 -46
  132. runbooks/inventory/inventory_modules.py +103 -91
  133. runbooks/inventory/list_cfn_stacks.py +9 -10
  134. runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
  135. runbooks/inventory/list_cfn_stackset_operations.py +79 -57
  136. runbooks/inventory/list_cfn_stacksets.py +8 -10
  137. runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
  138. runbooks/inventory/list_ds_directories.py +65 -53
  139. runbooks/inventory/list_ec2_availability_zones.py +2 -4
  140. runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
  141. runbooks/inventory/list_ec2_instances.py +23 -28
  142. runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
  143. runbooks/inventory/list_elbs_load_balancers.py +22 -20
  144. runbooks/inventory/list_enis_network_interfaces.py +26 -33
  145. runbooks/inventory/list_guardduty_detectors.py +2 -4
  146. runbooks/inventory/list_iam_policies.py +2 -4
  147. runbooks/inventory/list_iam_roles.py +5 -7
  148. runbooks/inventory/list_iam_saml_providers.py +4 -6
  149. runbooks/inventory/list_lambda_functions.py +38 -38
  150. runbooks/inventory/list_org_accounts.py +6 -8
  151. runbooks/inventory/list_org_accounts_users.py +55 -44
  152. runbooks/inventory/list_rds_db_instances.py +31 -33
  153. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  154. runbooks/inventory/list_route53_hosted_zones.py +3 -5
  155. runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
  156. runbooks/inventory/list_sns_topics.py +2 -4
  157. runbooks/inventory/list_ssm_parameters.py +4 -7
  158. runbooks/inventory/list_vpc_subnets.py +2 -4
  159. runbooks/inventory/list_vpcs.py +7 -10
  160. runbooks/inventory/mcp_inventory_validator.py +554 -468
  161. runbooks/inventory/mcp_vpc_validator.py +359 -442
  162. runbooks/inventory/organizations_discovery.py +63 -55
  163. runbooks/inventory/recover_cfn_stack_ids.py +7 -8
  164. runbooks/inventory/requirements.txt +0 -1
  165. runbooks/inventory/rich_inventory_display.py +35 -34
  166. runbooks/inventory/run_on_multi_accounts.py +3 -5
  167. runbooks/inventory/unified_validation_engine.py +281 -253
  168. runbooks/inventory/verify_ec2_security_groups.py +1 -1
  169. runbooks/inventory/vpc_analyzer.py +735 -697
  170. runbooks/inventory/vpc_architecture_validator.py +293 -348
  171. runbooks/inventory/vpc_dependency_analyzer.py +384 -380
  172. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  173. runbooks/main.py +49 -34
  174. runbooks/main_final.py +91 -60
  175. runbooks/main_minimal.py +22 -10
  176. runbooks/main_optimized.py +131 -100
  177. runbooks/main_ultra_minimal.py +7 -2
  178. runbooks/mcp/__init__.py +36 -0
  179. runbooks/mcp/integration.py +679 -0
  180. runbooks/monitoring/performance_monitor.py +9 -4
  181. runbooks/operate/dynamodb_operations.py +3 -1
  182. runbooks/operate/ec2_operations.py +145 -137
  183. runbooks/operate/iam_operations.py +146 -152
  184. runbooks/operate/networking_cost_heatmap.py +29 -8
  185. runbooks/operate/rds_operations.py +223 -254
  186. runbooks/operate/s3_operations.py +107 -118
  187. runbooks/operate/vpc_operations.py +646 -616
  188. runbooks/remediation/base.py +1 -1
  189. runbooks/remediation/commons.py +10 -7
  190. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  191. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  192. runbooks/remediation/multi_account.py +24 -21
  193. runbooks/remediation/rds_snapshot_list.py +86 -60
  194. runbooks/remediation/remediation_cli.py +92 -146
  195. runbooks/remediation/universal_account_discovery.py +83 -79
  196. runbooks/remediation/workspaces_list.py +46 -41
  197. runbooks/security/__init__.py +19 -0
  198. runbooks/security/assessment_runner.py +1150 -0
  199. runbooks/security/baseline_checker.py +812 -0
  200. runbooks/security/cloudops_automation_security_validator.py +509 -535
  201. runbooks/security/compliance_automation_engine.py +17 -17
  202. runbooks/security/config/__init__.py +2 -2
  203. runbooks/security/config/compliance_config.py +50 -50
  204. runbooks/security/config_template_generator.py +63 -76
  205. runbooks/security/enterprise_security_framework.py +1 -1
  206. runbooks/security/executive_security_dashboard.py +519 -508
  207. runbooks/security/multi_account_security_controls.py +959 -1210
  208. runbooks/security/real_time_security_monitor.py +422 -444
  209. runbooks/security/security_baseline_tester.py +1 -1
  210. runbooks/security/security_cli.py +143 -112
  211. runbooks/security/test_2way_validation.py +439 -0
  212. runbooks/security/two_way_validation_framework.py +852 -0
  213. runbooks/sre/production_monitoring_framework.py +167 -177
  214. runbooks/tdd/__init__.py +15 -0
  215. runbooks/tdd/cli.py +1071 -0
  216. runbooks/utils/__init__.py +14 -17
  217. runbooks/utils/logger.py +7 -2
  218. runbooks/utils/version_validator.py +50 -47
  219. runbooks/validation/__init__.py +6 -6
  220. runbooks/validation/cli.py +9 -3
  221. runbooks/validation/comprehensive_2way_validator.py +745 -704
  222. runbooks/validation/mcp_validator.py +906 -228
  223. runbooks/validation/terraform_citations_validator.py +104 -115
  224. runbooks/validation/terraform_drift_detector.py +461 -454
  225. runbooks/vpc/README.md +617 -0
  226. runbooks/vpc/__init__.py +8 -1
  227. runbooks/vpc/analyzer.py +577 -0
  228. runbooks/vpc/cleanup_wrapper.py +476 -413
  229. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  230. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  231. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  232. runbooks/vpc/config.py +92 -97
  233. runbooks/vpc/cost_engine.py +411 -148
  234. runbooks/vpc/cost_explorer_integration.py +553 -0
  235. runbooks/vpc/cross_account_session.py +101 -106
  236. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  237. runbooks/vpc/eni_gate_validator.py +961 -0
  238. runbooks/vpc/heatmap_engine.py +185 -160
  239. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  240. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  241. runbooks/vpc/networking_wrapper.py +15 -8
  242. runbooks/vpc/pdca_remediation_planner.py +528 -0
  243. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  244. runbooks/vpc/runbooks_adapter.py +1167 -241
  245. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  246. runbooks/vpc/test_data_loader.py +358 -0
  247. runbooks/vpc/tests/conftest.py +314 -4
  248. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  249. runbooks/vpc/tests/test_cost_engine.py +0 -2
  250. runbooks/vpc/topology_generator.py +326 -0
  251. runbooks/vpc/unified_scenarios.py +1297 -1124
  252. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  253. runbooks-1.1.6.dist-info/METADATA +327 -0
  254. runbooks-1.1.6.dist-info/RECORD +489 -0
  255. runbooks/finops/README.md +0 -414
  256. runbooks/finops/accuracy_cross_validator.py +0 -647
  257. runbooks/finops/business_cases.py +0 -950
  258. runbooks/finops/dashboard_router.py +0 -922
  259. runbooks/finops/ebs_optimizer.py +0 -973
  260. runbooks/finops/embedded_mcp_validator.py +0 -1629
  261. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  262. runbooks/finops/finops_dashboard.py +0 -584
  263. runbooks/finops/finops_scenarios.py +0 -1218
  264. runbooks/finops/legacy_migration.py +0 -730
  265. runbooks/finops/multi_dashboard.py +0 -1519
  266. runbooks/finops/single_dashboard.py +0 -1113
  267. runbooks/finops/unlimited_scenarios.py +0 -393
  268. runbooks-1.1.4.dist-info/METADATA +0 -800
  269. runbooks-1.1.4.dist-info/RECORD +0 -468
  270. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
  271. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
  272. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
  273. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -392,7 +392,7 @@ class BaseRemediation(ABC):
392
392
  if self._session is None:
393
393
  try:
394
394
  # Use management profile for remediation operations requiring cross-account access
395
- self._session = create_management_session(profile=self.profile)
395
+ self._session = create_management_session(profile_name=self.profile)
396
396
  except Exception as e:
397
397
  logger.warning(f"Failed to create session with profile {self.profile}: {e}")
398
398
  self._session = create_management_session() # Use default profile
@@ -154,19 +154,20 @@ def get_method_details(client, rest_api_id, resource_id, http_method):
154
154
  # Global profile variable for cost optimization commands
155
155
  _profile = None
156
156
 
157
+
157
158
  def get_client(client_name: str, profile_name: str = None, region_name: str = None):
158
159
  """
159
160
  Enhanced client creation with profile support for cost optimization commands.
160
-
161
+
161
162
  Enterprise pattern: Uses profile-based sessions like other runbooks modules.
162
163
  Supports both environment variables and profile-based authentication.
163
164
  """
164
165
  # Determine the profile to use (priority order)
165
166
  profile_to_use = profile_name or _profile or os.environ.get("AWS_PROFILE")
166
-
167
+
167
168
  # Determine the region to use
168
169
  region_to_use = region_name or os.environ.get("AWS_REGION", "us-east-1")
169
-
170
+
170
171
  if profile_to_use:
171
172
  # Use profile-based session (enterprise pattern)
172
173
  session = boto3.Session(profile_name=profile_to_use)
@@ -185,16 +186,16 @@ def get_client(client_name: str, profile_name: str = None, region_name: str = No
185
186
  def get_resource(client_name: str, profile_name: str = None, region_name: str = None):
186
187
  """
187
188
  Enhanced resource creation with profile support for cost optimization commands.
188
-
189
+
189
190
  Enterprise pattern: Uses profile-based sessions like other runbooks modules.
190
191
  Supports both environment variables and profile-based authentication.
191
192
  """
192
193
  # Determine the profile to use (priority order)
193
194
  profile_to_use = profile_name or _profile or os.environ.get("AWS_PROFILE")
194
-
195
+
195
196
  # Determine the region to use
196
197
  region_to_use = region_name or os.environ.get("AWS_REGION", "us-east-1")
197
-
198
+
198
199
  if profile_to_use:
199
200
  # Use profile-based session (enterprise pattern)
200
201
  session = boto3.Session(profile_name=profile_to_use)
@@ -372,7 +373,9 @@ def get_price(service_code, region_name, instance_type):
372
373
  @LRU_cache(maxsize=32)
373
374
  def get_product_pricing(instance_type, region_name, service_code):
374
375
  # Pricing API available only in selected regions
375
- pricing = botocore.session.get_session().create_client("pricing", region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
376
+ pricing = botocore.session.get_session().create_client(
377
+ "pricing", region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1")
378
+ )
376
379
  response = pricing.get_products(
377
380
  ServiceCode=service_code,
378
381
  Filters=[
@@ -15,8 +15,14 @@ from botocore.exceptions import ClientError
15
15
 
16
16
  from .commons import display_aws_account_info, get_client, write_to_csv
17
17
  from ..common.rich_utils import (
18
- console, print_header, print_success, print_error, print_warning,
19
- create_table, create_progress_bar, format_cost
18
+ console,
19
+ print_header,
20
+ print_success,
21
+ print_error,
22
+ print_warning,
23
+ create_table,
24
+ create_progress_bar,
25
+ format_cost,
20
26
  )
21
27
  from ..common.profile_utils import create_operational_session
22
28
 
@@ -26,41 +32,41 @@ logger = logging.getLogger(__name__)
26
32
  def _calculate_instance_monthly_cost(instance_type: str, region: str = "us-east-1") -> float:
27
33
  """
28
34
  Calculate monthly cost for EC2 instance type using dynamic AWS Pricing API.
29
-
35
+
30
36
  ENTERPRISE COMPLIANCE: Uses AWS Pricing API to eliminate hardcoded pricing violations.
31
-
37
+
32
38
  Args:
33
39
  instance_type: EC2 instance type (e.g., 't3.micro', 'm5.large')
34
40
  region: AWS region for pricing lookup
35
-
41
+
36
42
  Returns:
37
43
  float: Monthly cost in USD from AWS Pricing API
38
44
  """
39
45
  from ..common.aws_pricing import get_ec2_monthly_cost
40
46
  from ..common.rich_utils import console
41
-
47
+
42
48
  try:
43
49
  # Use dynamic AWS pricing - NO hardcoded values allowed
44
50
  monthly_cost = get_ec2_monthly_cost(instance_type, region)
45
51
  logger.debug(f"Dynamic pricing for {instance_type}: ${monthly_cost:.2f}/month")
46
52
  return monthly_cost
47
-
53
+
48
54
  except Exception as e:
49
55
  console.print(f"[red]⚠ ENTERPRISE WARNING: Cannot get dynamic pricing for {instance_type}: {e}[/red]")
50
56
  console.print(f"[yellow]Falling back to AWS pricing pattern calculation...[/yellow]")
51
-
57
+
52
58
  # Use AWS pricing engine's fallback calculation (which uses documented AWS patterns)
53
59
  from ..common.aws_pricing import get_aws_pricing_engine
54
-
60
+
55
61
  try:
56
62
  pricing_engine = get_aws_pricing_engine(enable_fallback=True)
57
63
  result = pricing_engine.get_ec2_instance_pricing(instance_type, region)
58
64
  logger.warning(f"Using fallback pricing for {instance_type}: ${result.monthly_cost:.2f}/month")
59
65
  return result.monthly_cost
60
-
66
+
61
67
  except Exception as fallback_error:
62
68
  logger.error(f"All pricing methods failed for {instance_type}: {fallback_error}")
63
-
69
+
64
70
  # Complete failure - cannot proceed without violating enterprise standards
65
71
  raise RuntimeError(
66
72
  f"ENTERPRISE VIOLATION: Cannot get dynamic pricing for {instance_type} "
@@ -70,27 +76,25 @@ def _calculate_instance_monthly_cost(instance_type: str, region: str = "us-east-
70
76
  ) from e
71
77
 
72
78
 
73
-
74
-
75
79
  def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
76
80
  """
77
81
  Calculate potential cost impact for EC2 instances.
78
-
82
+
79
83
  JIRA FinOps-25: Focuses on Commvault backup account utilization analysis
80
84
  """
81
85
  total_instances = len(instances_data)
82
86
  running_instances = [i for i in instances_data if i.get("State", {}).get("Name") == "running"]
83
87
  idle_instances = [i for i in instances_data if i.get("CpuUtilization", 0) < 5.0] # <5% CPU
84
-
88
+
85
89
  # Dynamic cost estimation using environment configuration
86
90
  estimated_monthly_cost = 0.0
87
91
  for instance in running_instances:
88
92
  instance_type = instance.get("InstanceType", "t3.micro")
89
-
93
+
90
94
  # Dynamic cost calculation based on instance type
91
95
  # Using AWS pricing calculation: hours * daily_hours * monthly_days
92
96
  estimated_monthly_cost += _calculate_instance_monthly_cost(instance_type)
93
-
97
+
94
98
  return {
95
99
  "total_instances": total_instances,
96
100
  "running_instances": len(running_instances),
@@ -103,30 +107,32 @@ def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
103
107
 
104
108
  @click.command()
105
109
  @click.option("--output-file", default="./tmp/commvault_ec2_investigation.csv", help="Output CSV file path")
106
- @click.option("--account", help="Commvault backup account ID (JIRA FinOps-25). If not specified, uses current AWS account.")
110
+ @click.option(
111
+ "--account", help="Commvault backup account ID (JIRA FinOps-25). If not specified, uses current AWS account."
112
+ )
107
113
  @click.option("--investigate-utilization", is_flag=True, help="Investigate EC2 utilization patterns")
108
114
  @click.option("--days", default=7, help="Number of days to analyze for utilization metrics")
109
115
  @click.option("--dry-run", is_flag=True, default=True, help="Preview analysis without execution")
110
116
  def investigate_commvault_ec2(output_file, account, investigate_utilization, days, dry_run):
111
117
  """
112
118
  FinOps-25: Commvault EC2 investigation for cost optimization.
113
-
119
+
114
120
  Challenge: Determine if EC2 instances are actively used for backups or idle
115
121
  """
116
122
  print_header("JIRA FinOps-25: Commvault EC2 Investigation", "latest version")
117
-
123
+
118
124
  # Auto-detect account if not specified
119
125
  if not account:
120
126
  try:
121
127
  session = create_operational_session()
122
- sts = session.client('sts')
128
+ sts = session.client("sts")
123
129
  identity = sts.get_caller_identity()
124
- account = identity['Account']
130
+ account = identity["Account"]
125
131
  console.print(f"[dim cyan]Auto-detected account: {account}[/dim cyan]")
126
132
  except Exception as e:
127
133
  console.print(f"[red]Error detecting account: {e}[/red]")
128
134
  raise click.ClickException("Could not determine AWS account. Please specify --account parameter.")
129
-
135
+
130
136
  account_info = display_aws_account_info()
131
137
  console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
132
138
 
@@ -137,10 +143,10 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
137
143
  # Get all EC2 instances
138
144
  console.print("[yellow]Collecting EC2 instance data...[/yellow]")
139
145
  response = ec2_client.describe_instances()
140
-
146
+
141
147
  instances_data = []
142
148
  all_instances = []
143
-
149
+
144
150
  # Flatten instance data
145
151
  for reservation in response.get("Reservations", []):
146
152
  all_instances.extend(reservation.get("Instances", []))
@@ -157,8 +163,7 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
157
163
 
158
164
  with create_progress_bar() as progress:
159
165
  task_id = progress.add_task(
160
- f"Investigating {len(all_instances)} EC2 instances...",
161
- total=len(all_instances)
166
+ f"Investigating {len(all_instances)} EC2 instances...", total=len(all_instances)
162
167
  )
163
168
 
164
169
  for instance in all_instances:
@@ -166,7 +171,7 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
166
171
  instance_type = instance.get("InstanceType", "unknown")
167
172
  state = instance.get("State", {}).get("Name", "unknown")
168
173
  launch_time = instance.get("LaunchTime")
169
-
174
+
170
175
  # Get CPU utilization metrics
171
176
  cpu_utilization = 0.0
172
177
  if investigate_utilization and state == "running":
@@ -180,18 +185,18 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
180
185
  Period=3600, # 1 hour intervals
181
186
  Statistics=["Average"],
182
187
  )
183
-
188
+
184
189
  if cpu_response.get("Datapoints"):
185
- cpu_utilization = sum(
186
- dp["Average"] for dp in cpu_response["Datapoints"]
187
- ) / len(cpu_response["Datapoints"])
188
-
190
+ cpu_utilization = sum(dp["Average"] for dp in cpu_response["Datapoints"]) / len(
191
+ cpu_response["Datapoints"]
192
+ )
193
+
189
194
  except ClientError as e:
190
195
  logger.warning(f"Could not get CPU metrics for {instance_id}: {e}")
191
196
 
192
197
  # Get tags for context
193
198
  tags = {tag["Key"]: tag["Value"] for tag in instance.get("Tags", [])}
194
-
199
+
195
200
  instance_data = {
196
201
  "InstanceId": instance_id,
197
202
  "InstanceType": instance_type,
@@ -203,7 +208,7 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
203
208
  "Purpose": tags.get("Purpose", tags.get("Application", "Unknown")),
204
209
  "IsLowUtilization": cpu_utilization < 5.0 if investigate_utilization else False,
205
210
  }
206
-
211
+
207
212
  instances_data.append(instance_data)
208
213
  progress.advance(task_id)
209
214
 
@@ -213,47 +218,44 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
213
218
 
214
219
  # Calculate cost impact analysis
215
220
  cost_analysis = calculate_ec2_cost_impact(instances_data)
216
-
221
+
217
222
  # Create summary table
218
223
  print_header("Commvault EC2 Investigation Summary")
219
-
224
+
220
225
  summary_table = create_table(
221
226
  title="EC2 Cost Impact Analysis - JIRA FinOps-25",
222
227
  columns=[
223
228
  {"header": "Metric", "style": "cyan"},
224
229
  {"header": "Count", "style": "green bold"},
225
230
  {"header": "Monthly Cost", "style": "red"},
226
- {"header": "Annual Cost", "style": "red bold"}
227
- ]
231
+ {"header": "Annual Cost", "style": "red bold"},
232
+ ],
228
233
  )
229
234
 
230
235
  summary_table.add_row(
231
- "Total EC2 Instances",
232
- str(cost_analysis["total_instances"]),
233
- "Mixed Types",
234
- "Mixed Types"
236
+ "Total EC2 Instances", str(cost_analysis["total_instances"]), "Mixed Types", "Mixed Types"
235
237
  )
236
-
238
+
237
239
  summary_table.add_row(
238
240
  "Running Instances",
239
241
  str(cost_analysis["running_instances"]),
240
242
  format_cost(cost_analysis["estimated_monthly_cost"]),
241
- format_cost(cost_analysis["estimated_annual_cost"])
243
+ format_cost(cost_analysis["estimated_annual_cost"]),
242
244
  )
243
-
245
+
244
246
  if investigate_utilization:
245
247
  summary_table.add_row(
246
248
  "Low Utilization (<5% CPU)",
247
249
  str(cost_analysis["idle_instances"]),
248
250
  "Investigation Required",
249
- "Investigation Required"
251
+ "Investigation Required",
250
252
  )
251
-
253
+
252
254
  summary_table.add_row(
253
255
  "🎯 Potential Savings (if idle)",
254
256
  f"{cost_analysis['idle_instances']} instances",
255
257
  format_cost(cost_analysis["potential_savings_if_idle"] / 12),
256
- format_cost(cost_analysis["potential_savings_if_idle"])
258
+ format_cost(cost_analysis["potential_savings_if_idle"]),
257
259
  )
258
260
 
259
261
  console.print(summary_table)
@@ -261,10 +263,10 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
261
263
  # Investigation recommendations
262
264
  if investigate_utilization:
263
265
  low_util_instances = [i for i in instances_data if i["IsLowUtilization"]]
264
-
266
+
265
267
  if low_util_instances:
266
268
  print_warning(f"🔍 Found {len(low_util_instances)} instances with low utilization:")
267
-
269
+
268
270
  # Create detailed investigation table
269
271
  investigation_table = create_table(
270
272
  title="Low Utilization Instances (Investigation Required)",
@@ -274,33 +276,35 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
274
276
  {"header": "CPU %", "style": "yellow"},
275
277
  {"header": "Name", "style": "green"},
276
278
  {"header": "Purpose", "style": "magenta"},
277
- {"header": "State", "style": "red"}
278
- ]
279
+ {"header": "State", "style": "red"},
280
+ ],
279
281
  )
280
-
282
+
281
283
  for instance in low_util_instances[:10]: # Show first 10
282
284
  investigation_table.add_row(
283
- instance['InstanceId'],
284
- instance['InstanceType'],
285
+ instance["InstanceId"],
286
+ instance["InstanceType"],
285
287
  f"{instance['CpuUtilization']:.1f}%",
286
- instance['Name'],
287
- instance['Purpose'],
288
- instance['State']
288
+ instance["Name"],
289
+ instance["Purpose"],
290
+ instance["State"],
289
291
  )
290
-
292
+
291
293
  console.print(investigation_table)
292
-
294
+
293
295
  if len(low_util_instances) > 10:
294
- console.print(f"[dim]... and {len(low_util_instances) - 10} more instances requiring investigation[/dim]")
295
-
296
+ console.print(
297
+ f"[dim]... and {len(low_util_instances) - 10} more instances requiring investigation[/dim]"
298
+ )
299
+
296
300
  # Manual verification checklist
297
301
  print_header("Manual Verification Checklist")
298
302
  console.print("[yellow]For each low-utilization instance, verify:[/yellow]")
299
303
  console.print("[dim]1. Is this instance actively used for Commvault backup operations?[/dim]")
300
- console.print("[dim]2. Are there scheduled backup jobs running on this instance?[/dim]")
304
+ console.print("[dim]2. Are there scheduled backup jobs running on this instance?[/dim]")
301
305
  console.print("[dim]3. Can this instance be right-sized or terminated safely?[/dim]")
302
306
  console.print("[dim]4. Are there any dependencies or integrations that require this instance?[/dim]")
303
-
307
+
304
308
  else:
305
309
  print_success("✓ No low-utilization instances detected")
306
310
 
@@ -310,4 +314,4 @@ def investigate_commvault_ec2(output_file, account, investigate_utilization, day
310
314
 
311
315
  except Exception as e:
312
316
  logger.error(f"Failed to investigate Commvault EC2: {e}")
313
- raise
317
+ raise
@@ -307,6 +307,7 @@ def detect_and_delete_volumes(dry_run: bool, max_age_days: int, output_file: Opt
307
307
 
308
308
  # Real-time EBS cost from AWS Pricing API - NO hardcoded defaults
309
309
  from runbooks.common.aws_pricing_api import pricing_api
310
+
310
311
  if volume_type == "gp3":
311
312
  cost_per_gb = pricing_api.get_ebs_gp3_cost_per_gb(region_name)
312
313
  else:
@@ -71,7 +71,10 @@ from loguru import logger
71
71
  from runbooks.inventory.models.account import AWSAccount
72
72
  from runbooks.remediation.base import BaseRemediation, RemediationContext, RemediationResult, RemediationStatus
73
73
  from runbooks.common.profile_utils import create_management_session
74
- from runbooks.remediation.universal_account_discovery import UniversalAccountDiscovery, AWSAccount as UniversalAWSAccount
74
+ from runbooks.remediation.universal_account_discovery import (
75
+ UniversalAccountDiscovery,
76
+ AWSAccount as UniversalAWSAccount,
77
+ )
75
78
 
76
79
 
77
80
  class MultiAccountRemediator:
@@ -572,16 +575,16 @@ class MultiAccountRemediator:
572
575
  def get_accounts_from_environment(profile: Optional[str] = None) -> Optional[List[AWSAccount]]:
573
576
  """
574
577
  Get AWS accounts using universal account discovery system.
575
-
578
+
576
579
  Uses enhanced discovery with support for:
577
580
  - Environment variables (REMEDIATION_TARGET_ACCOUNTS)
578
581
  - Configuration files (REMEDIATION_ACCOUNT_CONFIG)
579
582
  - AWS Organizations API (automatic discovery)
580
583
  - Current account fallback (single account mode)
581
-
584
+
582
585
  Args:
583
586
  profile: AWS profile to use for discovery
584
-
587
+
585
588
  Returns:
586
589
  List of AWSAccount objects or None if not configured
587
590
  """
@@ -589,22 +592,22 @@ def get_accounts_from_environment(profile: Optional[str] = None) -> Optional[Lis
589
592
  # Use universal account discovery system
590
593
  discovery = UniversalAccountDiscovery(profile=profile)
591
594
  universal_accounts = discovery.discover_target_accounts()
592
-
595
+
593
596
  if not universal_accounts:
594
597
  return None
595
-
598
+
596
599
  # Convert to legacy AWSAccount format for compatibility
597
600
  legacy_accounts = []
598
601
  for universal_account in universal_accounts:
599
602
  legacy_account = AWSAccount(
600
603
  universal_account.account_id,
601
- universal_account.account_name or f"account-{universal_account.account_id}"
604
+ universal_account.account_name or f"account-{universal_account.account_id}",
602
605
  )
603
606
  legacy_accounts.append(legacy_account)
604
-
607
+
605
608
  logger.info(f"Using {len(legacy_accounts)} accounts discovered via universal discovery system")
606
609
  return legacy_accounts
607
-
610
+
608
611
  except Exception as e:
609
612
  logger.warning(f"Failed to discover accounts using universal discovery: {e}")
610
613
  return None
@@ -613,16 +616,16 @@ def get_accounts_from_environment(profile: Optional[str] = None) -> Optional[Lis
613
616
  def discover_organization_accounts(profile: Optional[str] = None) -> List[AWSAccount]:
614
617
  """
615
618
  Discover AWS accounts using universal discovery system.
616
-
619
+
617
620
  Enhanced to use the universal account discovery system which provides:
618
621
  - Organizations API discovery (if available)
619
622
  - Environment variable fallback
620
623
  - Configuration file support
621
624
  - Current account fallback
622
-
625
+
623
626
  Args:
624
627
  profile: AWS profile for discovery (universal profile management)
625
-
628
+
626
629
  Returns:
627
630
  List of discovered AWSAccount objects
628
631
  """
@@ -630,25 +633,25 @@ def discover_organization_accounts(profile: Optional[str] = None) -> List[AWSAcc
630
633
  # Use universal account discovery system for Organizations discovery
631
634
  discovery = UniversalAccountDiscovery(profile=profile)
632
635
  universal_accounts = discovery._get_accounts_from_organizations()
633
-
636
+
634
637
  if not universal_accounts:
635
638
  # Fallback to other discovery methods
636
639
  logger.info("Organizations API not available, trying other discovery methods...")
637
640
  universal_accounts = discovery.discover_target_accounts()
638
-
641
+
639
642
  # Convert to legacy AWSAccount format for compatibility
640
643
  legacy_accounts = []
641
644
  for universal_account in universal_accounts:
642
645
  if universal_account.status == "ACTIVE":
643
646
  legacy_account = AWSAccount(
644
647
  universal_account.account_id,
645
- universal_account.account_name or f"org-account-{universal_account.account_id}"
648
+ universal_account.account_name or f"org-account-{universal_account.account_id}",
646
649
  )
647
650
  legacy_accounts.append(legacy_account)
648
-
651
+
649
652
  logger.info(f"Discovered {len(legacy_accounts)} active AWS accounts via universal discovery")
650
653
  return legacy_accounts
651
-
654
+
652
655
  except Exception as e:
653
656
  logger.warning(f"Failed to discover organization accounts: {e}")
654
657
  # Universal discovery handles all fallback scenarios
@@ -658,20 +661,20 @@ def discover_organization_accounts(profile: Optional[str] = None) -> List[AWSAcc
658
661
  def _determine_account_environment(account_name: str) -> str:
659
662
  """
660
663
  Determine account environment based on account name patterns.
661
-
664
+
662
665
  Args:
663
666
  account_name: AWS account name
664
-
667
+
665
668
  Returns:
666
669
  Environment classification
667
670
  """
668
671
  name_lower = account_name.lower()
669
-
672
+
670
673
  # Common environment patterns
671
674
  if any(env in name_lower for env in ["prod", "production"]):
672
675
  return "production"
673
676
  elif any(env in name_lower for env in ["staging", "stage", "uat"]):
674
- return "staging"
677
+ return "staging"
675
678
  elif any(env in name_lower for env in ["dev", "development"]):
676
679
  return "development"
677
680
  elif any(env in name_lower for env in ["test", "testing"]):