runbooks 1.1.3__py3-none-any.whl → 1.1.5__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 (247) 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/WEIGHT_CONFIG_README.md +1 -1
  8. runbooks/cfat/assessment/compliance.py +8 -8
  9. runbooks/cfat/assessment/runner.py +1 -0
  10. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  11. runbooks/cfat/models.py +6 -2
  12. runbooks/cfat/tests/__init__.py +6 -1
  13. runbooks/cli/__init__.py +13 -0
  14. runbooks/cli/commands/cfat.py +274 -0
  15. runbooks/cli/commands/finops.py +1164 -0
  16. runbooks/cli/commands/inventory.py +379 -0
  17. runbooks/cli/commands/operate.py +239 -0
  18. runbooks/cli/commands/security.py +248 -0
  19. runbooks/cli/commands/validation.py +825 -0
  20. runbooks/cli/commands/vpc.py +310 -0
  21. runbooks/cli/registry.py +107 -0
  22. runbooks/cloudops/__init__.py +23 -30
  23. runbooks/cloudops/base.py +96 -107
  24. runbooks/cloudops/cost_optimizer.py +549 -547
  25. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  26. runbooks/cloudops/interfaces.py +226 -227
  27. runbooks/cloudops/lifecycle_manager.py +5 -4
  28. runbooks/cloudops/mcp_cost_validation.py +252 -235
  29. runbooks/cloudops/models.py +78 -53
  30. runbooks/cloudops/monitoring_automation.py +5 -4
  31. runbooks/cloudops/notebook_framework.py +179 -215
  32. runbooks/cloudops/security_enforcer.py +125 -159
  33. runbooks/common/accuracy_validator.py +11 -0
  34. runbooks/common/aws_pricing.py +349 -326
  35. runbooks/common/aws_pricing_api.py +211 -212
  36. runbooks/common/aws_profile_manager.py +341 -0
  37. runbooks/common/aws_utils.py +75 -80
  38. runbooks/common/business_logic.py +127 -105
  39. runbooks/common/cli_decorators.py +36 -60
  40. runbooks/common/comprehensive_cost_explorer_integration.py +456 -464
  41. runbooks/common/cross_account_manager.py +198 -205
  42. runbooks/common/date_utils.py +27 -39
  43. runbooks/common/decorators.py +235 -0
  44. runbooks/common/dry_run_examples.py +173 -208
  45. runbooks/common/dry_run_framework.py +157 -155
  46. runbooks/common/enhanced_exception_handler.py +15 -4
  47. runbooks/common/enhanced_logging_example.py +50 -64
  48. runbooks/common/enhanced_logging_integration_example.py +65 -37
  49. runbooks/common/env_utils.py +16 -16
  50. runbooks/common/error_handling.py +40 -38
  51. runbooks/common/lazy_loader.py +41 -23
  52. runbooks/common/logging_integration_helper.py +79 -86
  53. runbooks/common/mcp_cost_explorer_integration.py +478 -495
  54. runbooks/common/mcp_integration.py +63 -74
  55. runbooks/common/memory_optimization.py +140 -118
  56. runbooks/common/module_cli_base.py +37 -58
  57. runbooks/common/organizations_client.py +176 -194
  58. runbooks/common/patterns.py +204 -0
  59. runbooks/common/performance_monitoring.py +67 -71
  60. runbooks/common/performance_optimization_engine.py +283 -274
  61. runbooks/common/profile_utils.py +248 -39
  62. runbooks/common/rich_utils.py +643 -92
  63. runbooks/common/sre_performance_suite.py +177 -186
  64. runbooks/enterprise/__init__.py +1 -1
  65. runbooks/enterprise/logging.py +144 -106
  66. runbooks/enterprise/security.py +187 -204
  67. runbooks/enterprise/validation.py +43 -56
  68. runbooks/finops/__init__.py +29 -33
  69. runbooks/finops/account_resolver.py +1 -1
  70. runbooks/finops/advanced_optimization_engine.py +980 -0
  71. runbooks/finops/automation_core.py +268 -231
  72. runbooks/finops/business_case_config.py +184 -179
  73. runbooks/finops/cli.py +660 -139
  74. runbooks/finops/commvault_ec2_analysis.py +157 -164
  75. runbooks/finops/compute_cost_optimizer.py +336 -320
  76. runbooks/finops/config.py +20 -20
  77. runbooks/finops/cost_optimizer.py +488 -622
  78. runbooks/finops/cost_processor.py +332 -214
  79. runbooks/finops/dashboard_runner.py +1006 -172
  80. runbooks/finops/ebs_cost_optimizer.py +991 -657
  81. runbooks/finops/elastic_ip_optimizer.py +317 -257
  82. runbooks/finops/enhanced_mcp_integration.py +340 -0
  83. runbooks/finops/enhanced_progress.py +40 -37
  84. runbooks/finops/enhanced_trend_visualization.py +3 -2
  85. runbooks/finops/enterprise_wrappers.py +230 -292
  86. runbooks/finops/executive_export.py +203 -160
  87. runbooks/finops/helpers.py +130 -288
  88. runbooks/finops/iam_guidance.py +1 -1
  89. runbooks/finops/infrastructure/__init__.py +80 -0
  90. runbooks/finops/infrastructure/commands.py +506 -0
  91. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  92. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  93. runbooks/finops/markdown_exporter.py +338 -175
  94. runbooks/finops/mcp_validator.py +1952 -0
  95. runbooks/finops/nat_gateway_optimizer.py +1513 -482
  96. runbooks/finops/network_cost_optimizer.py +657 -587
  97. runbooks/finops/notebook_utils.py +226 -188
  98. runbooks/finops/optimization_engine.py +1136 -0
  99. runbooks/finops/optimizer.py +25 -29
  100. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  101. runbooks/finops/reservation_optimizer.py +427 -363
  102. runbooks/finops/scenario_cli_integration.py +77 -78
  103. runbooks/finops/scenarios.py +1278 -439
  104. runbooks/finops/schemas.py +218 -182
  105. runbooks/finops/snapshot_manager.py +2289 -0
  106. runbooks/finops/tests/test_finops_dashboard.py +3 -3
  107. runbooks/finops/tests/test_reference_images_validation.py +2 -2
  108. runbooks/finops/tests/test_single_account_features.py +17 -17
  109. runbooks/finops/tests/validate_test_suite.py +1 -1
  110. runbooks/finops/types.py +3 -3
  111. runbooks/finops/validation_framework.py +263 -269
  112. runbooks/finops/vpc_cleanup_exporter.py +191 -146
  113. runbooks/finops/vpc_cleanup_optimizer.py +593 -575
  114. runbooks/finops/workspaces_analyzer.py +171 -182
  115. runbooks/hitl/enhanced_workflow_engine.py +1 -1
  116. runbooks/integration/__init__.py +89 -0
  117. runbooks/integration/mcp_integration.py +1920 -0
  118. runbooks/inventory/CLAUDE.md +816 -0
  119. runbooks/inventory/README.md +3 -3
  120. runbooks/inventory/Tests/common_test_data.py +30 -30
  121. runbooks/inventory/__init__.py +2 -2
  122. runbooks/inventory/cloud_foundations_integration.py +144 -149
  123. runbooks/inventory/collectors/aws_comprehensive.py +28 -11
  124. runbooks/inventory/collectors/aws_networking.py +111 -101
  125. runbooks/inventory/collectors/base.py +4 -0
  126. runbooks/inventory/core/collector.py +495 -313
  127. runbooks/inventory/discovery.md +2 -2
  128. runbooks/inventory/drift_detection_cli.py +69 -96
  129. runbooks/inventory/find_ec2_security_groups.py +1 -1
  130. runbooks/inventory/inventory_mcp_cli.py +48 -46
  131. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  132. runbooks/inventory/mcp_inventory_validator.py +549 -465
  133. runbooks/inventory/mcp_vpc_validator.py +359 -442
  134. runbooks/inventory/organizations_discovery.py +56 -52
  135. runbooks/inventory/rich_inventory_display.py +33 -32
  136. runbooks/inventory/unified_validation_engine.py +278 -251
  137. runbooks/inventory/vpc_analyzer.py +733 -696
  138. runbooks/inventory/vpc_architecture_validator.py +293 -348
  139. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  140. runbooks/inventory/vpc_flow_analyzer.py +3 -3
  141. runbooks/main.py +152 -9147
  142. runbooks/main_final.py +91 -60
  143. runbooks/main_minimal.py +22 -10
  144. runbooks/main_optimized.py +131 -100
  145. runbooks/main_ultra_minimal.py +7 -2
  146. runbooks/mcp/__init__.py +36 -0
  147. runbooks/mcp/integration.py +679 -0
  148. runbooks/metrics/dora_metrics_engine.py +2 -2
  149. runbooks/monitoring/performance_monitor.py +9 -4
  150. runbooks/operate/dynamodb_operations.py +3 -1
  151. runbooks/operate/ec2_operations.py +145 -137
  152. runbooks/operate/iam_operations.py +146 -152
  153. runbooks/operate/mcp_integration.py +1 -1
  154. runbooks/operate/networking_cost_heatmap.py +33 -10
  155. runbooks/operate/privatelink_operations.py +1 -1
  156. runbooks/operate/rds_operations.py +223 -254
  157. runbooks/operate/s3_operations.py +107 -118
  158. runbooks/operate/vpc_endpoints.py +1 -1
  159. runbooks/operate/vpc_operations.py +648 -618
  160. runbooks/remediation/base.py +1 -1
  161. runbooks/remediation/commons.py +10 -7
  162. runbooks/remediation/commvault_ec2_analysis.py +71 -67
  163. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  164. runbooks/remediation/multi_account.py +24 -21
  165. runbooks/remediation/rds_snapshot_list.py +91 -65
  166. runbooks/remediation/remediation_cli.py +92 -146
  167. runbooks/remediation/universal_account_discovery.py +83 -79
  168. runbooks/remediation/workspaces_list.py +49 -44
  169. runbooks/security/__init__.py +19 -0
  170. runbooks/security/assessment_runner.py +1150 -0
  171. runbooks/security/baseline_checker.py +812 -0
  172. runbooks/security/cloudops_automation_security_validator.py +509 -535
  173. runbooks/security/compliance_automation_engine.py +17 -17
  174. runbooks/security/config/__init__.py +2 -2
  175. runbooks/security/config/compliance_config.py +50 -50
  176. runbooks/security/config_template_generator.py +63 -76
  177. runbooks/security/enterprise_security_framework.py +1 -1
  178. runbooks/security/executive_security_dashboard.py +519 -508
  179. runbooks/security/integration_test_enterprise_security.py +5 -3
  180. runbooks/security/multi_account_security_controls.py +959 -1210
  181. runbooks/security/real_time_security_monitor.py +422 -444
  182. runbooks/security/run_script.py +1 -1
  183. runbooks/security/security_baseline_tester.py +1 -1
  184. runbooks/security/security_cli.py +143 -112
  185. runbooks/security/test_2way_validation.py +439 -0
  186. runbooks/security/two_way_validation_framework.py +852 -0
  187. runbooks/sre/mcp_reliability_engine.py +6 -6
  188. runbooks/sre/production_monitoring_framework.py +167 -177
  189. runbooks/tdd/__init__.py +15 -0
  190. runbooks/tdd/cli.py +1071 -0
  191. runbooks/utils/__init__.py +14 -17
  192. runbooks/utils/logger.py +7 -2
  193. runbooks/utils/version_validator.py +51 -48
  194. runbooks/validation/__init__.py +6 -6
  195. runbooks/validation/cli.py +9 -3
  196. runbooks/validation/comprehensive_2way_validator.py +754 -708
  197. runbooks/validation/mcp_validator.py +906 -228
  198. runbooks/validation/terraform_citations_validator.py +104 -115
  199. runbooks/validation/terraform_drift_detector.py +447 -451
  200. runbooks/vpc/README.md +617 -0
  201. runbooks/vpc/__init__.py +8 -1
  202. runbooks/vpc/analyzer.py +577 -0
  203. runbooks/vpc/cleanup_wrapper.py +476 -413
  204. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  205. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  206. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  207. runbooks/vpc/config.py +92 -97
  208. runbooks/vpc/cost_engine.py +411 -148
  209. runbooks/vpc/cost_explorer_integration.py +553 -0
  210. runbooks/vpc/cross_account_session.py +101 -106
  211. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  212. runbooks/vpc/eni_gate_validator.py +961 -0
  213. runbooks/vpc/heatmap_engine.py +190 -162
  214. runbooks/vpc/mcp_no_eni_validator.py +681 -640
  215. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  216. runbooks/vpc/networking_wrapper.py +15 -8
  217. runbooks/vpc/pdca_remediation_planner.py +528 -0
  218. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  219. runbooks/vpc/runbooks_adapter.py +1167 -241
  220. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  221. runbooks/vpc/test_data_loader.py +358 -0
  222. runbooks/vpc/tests/conftest.py +314 -4
  223. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  224. runbooks/vpc/tests/test_cost_engine.py +0 -2
  225. runbooks/vpc/topology_generator.py +326 -0
  226. runbooks/vpc/unified_scenarios.py +1302 -1129
  227. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  228. runbooks-1.1.5.dist-info/METADATA +328 -0
  229. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/RECORD +233 -200
  230. runbooks/finops/README.md +0 -414
  231. runbooks/finops/accuracy_cross_validator.py +0 -647
  232. runbooks/finops/business_cases.py +0 -950
  233. runbooks/finops/dashboard_router.py +0 -922
  234. runbooks/finops/ebs_optimizer.py +0 -956
  235. runbooks/finops/embedded_mcp_validator.py +0 -1629
  236. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  237. runbooks/finops/finops_dashboard.py +0 -584
  238. runbooks/finops/finops_scenarios.py +0 -1218
  239. runbooks/finops/legacy_migration.py +0 -730
  240. runbooks/finops/multi_dashboard.py +0 -1519
  241. runbooks/finops/single_dashboard.py +0 -1113
  242. runbooks/finops/unlimited_scenarios.py +0 -393
  243. runbooks-1.1.3.dist-info/METADATA +0 -799
  244. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
  245. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  246. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  247. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -23,8 +23,16 @@ import click
23
23
  from botocore.exceptions import ClientError
24
24
 
25
25
  from ..common.rich_utils import (
26
- console, print_header, print_success, print_error, print_warning, print_info,
27
- create_table, create_progress_bar, format_cost, create_panel
26
+ console,
27
+ create_panel,
28
+ create_progress_bar,
29
+ create_table,
30
+ format_cost,
31
+ print_error,
32
+ print_header,
33
+ print_info,
34
+ print_success,
35
+ print_warning,
28
36
  )
29
37
 
30
38
  logger = logging.getLogger(__name__)
@@ -33,11 +41,11 @@ logger = logging.getLogger(__name__)
33
41
  class CommvaultEC2Analysis:
34
42
  """
35
43
  FinOps-25: Commvault EC2 Investigation Framework
36
-
44
+
37
45
  Provides systematic analysis of EC2 instances in Commvault backup account
38
46
  to determine utilization patterns and optimization opportunities.
39
47
  """
40
-
48
+
41
49
  def __init__(self, profile_name: Optional[str] = None, account_id: Optional[str] = None):
42
50
  """
43
51
  Initialize Commvault EC2 analysis.
@@ -54,325 +62,308 @@ class CommvaultEC2Analysis:
54
62
  self.account_id = account_id
55
63
  else:
56
64
  try:
57
- self.account_id = self.session.client('sts').get_caller_identity()['Account']
65
+ self.account_id = self.session.client("sts").get_caller_identity()["Account"]
58
66
  except Exception as e:
59
67
  logger.warning(f"Could not resolve account ID from profile: {e}")
60
68
  self.account_id = "unknown"
61
-
69
+
62
70
  def analyze_commvault_instances(self, region: str = "us-east-1") -> Dict:
63
71
  """
64
72
  Analyze EC2 instances in Commvault account for utilization patterns.
65
-
73
+
66
74
  Args:
67
75
  region: AWS region to analyze (default: us-east-1)
68
-
76
+
69
77
  Returns:
70
78
  Dict containing analysis results with cost implications
71
79
  """
72
80
  print_header("FinOps-25: Commvault EC2 Investigation", f"Account: {self.account_id}")
73
-
81
+
74
82
  try:
75
- ec2_client = self.session.client('ec2', region_name=region)
76
- cloudwatch_client = self.session.client('cloudwatch', region_name=region)
77
-
83
+ ec2_client = self.session.client("ec2", region_name=region)
84
+ cloudwatch_client = self.session.client("cloudwatch", region_name=region)
85
+
78
86
  # Get all EC2 instances
79
87
  response = ec2_client.describe_instances()
80
88
  instances = []
81
-
82
- for reservation in response['Reservations']:
83
- for instance in reservation['Instances']:
84
- if instance['State']['Name'] != 'terminated':
89
+
90
+ for reservation in response["Reservations"]:
91
+ for instance in reservation["Instances"]:
92
+ if instance["State"]["Name"] != "terminated":
85
93
  instances.append(instance)
86
-
94
+
87
95
  if not instances:
88
96
  print_warning(f"No active instances found in account {self.account_id}")
89
97
  return {"instances": [], "total_cost": 0, "optimization_potential": 0}
90
-
98
+
91
99
  print_info(f"Found {len(instances)} active instances for analysis")
92
-
100
+
93
101
  # Analyze each instance
94
102
  analysis_results = []
95
103
  total_monthly_cost = 0
96
-
104
+
97
105
  with create_progress_bar() as progress:
98
106
  task = progress.add_task("Analyzing instances...", total=len(instances))
99
-
107
+
100
108
  for instance in instances:
101
- instance_analysis = self._analyze_single_instance(
102
- instance, cloudwatch_client, region
103
- )
109
+ instance_analysis = self._analyze_single_instance(instance, cloudwatch_client, region)
104
110
  analysis_results.append(instance_analysis)
105
- total_monthly_cost += instance_analysis['estimated_monthly_cost']
111
+ total_monthly_cost += instance_analysis["estimated_monthly_cost"]
106
112
  progress.advance(task)
107
-
113
+
108
114
  # Generate summary
109
115
  optimization_potential = self._calculate_optimization_potential(analysis_results)
110
-
116
+
111
117
  # Display results
112
118
  self._display_analysis_results(analysis_results, total_monthly_cost, optimization_potential)
113
-
119
+
114
120
  return {
115
121
  "instances": analysis_results,
116
122
  "total_monthly_cost": total_monthly_cost,
117
123
  "optimization_potential": optimization_potential,
118
124
  "account_id": self.account_id,
119
- "analysis_timestamp": datetime.now().isoformat()
125
+ "analysis_timestamp": datetime.now().isoformat(),
120
126
  }
121
-
127
+
122
128
  except ClientError as e:
123
129
  print_error(f"AWS API Error: {e}")
124
130
  raise
125
131
  except Exception as e:
126
132
  print_error(f"Analysis Error: {e}")
127
133
  raise
128
-
134
+
129
135
  def _analyze_single_instance(self, instance: Dict, cloudwatch_client, region: str) -> Dict:
130
136
  """Analyze a single EC2 instance for utilization patterns."""
131
- instance_id = instance['InstanceId']
132
- instance_type = instance['InstanceType']
133
-
137
+ instance_id = instance["InstanceId"]
138
+ instance_type = instance["InstanceType"]
139
+
134
140
  # Get CloudWatch metrics for last 30 days
135
141
  end_time = datetime.now(timezone.utc)
136
142
  start_time = end_time - timedelta(days=30)
137
-
143
+
138
144
  try:
139
145
  # CPU Utilization
140
146
  cpu_response = cloudwatch_client.get_metric_statistics(
141
- Namespace='AWS/EC2',
142
- MetricName='CPUUtilization',
143
- Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
147
+ Namespace="AWS/EC2",
148
+ MetricName="CPUUtilization",
149
+ Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
144
150
  StartTime=start_time,
145
151
  EndTime=end_time,
146
152
  Period=3600, # 1 hour intervals
147
- Statistics=['Average']
153
+ Statistics=["Average"],
148
154
  )
149
-
155
+
150
156
  # Network metrics for backup activity indication
151
157
  network_in_response = cloudwatch_client.get_metric_statistics(
152
- Namespace='AWS/EC2',
153
- MetricName='NetworkIn',
154
- Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
158
+ Namespace="AWS/EC2",
159
+ MetricName="NetworkIn",
160
+ Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
155
161
  StartTime=start_time,
156
162
  EndTime=end_time,
157
163
  Period=3600,
158
- Statistics=['Sum']
164
+ Statistics=["Sum"],
159
165
  )
160
-
166
+
161
167
  network_out_response = cloudwatch_client.get_metric_statistics(
162
- Namespace='AWS/EC2',
163
- MetricName='NetworkOut',
164
- Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
168
+ Namespace="AWS/EC2",
169
+ MetricName="NetworkOut",
170
+ Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
165
171
  StartTime=start_time,
166
172
  EndTime=end_time,
167
173
  Period=3600,
168
- Statistics=['Sum']
174
+ Statistics=["Sum"],
169
175
  )
170
-
176
+
171
177
  except ClientError as e:
172
178
  logger.warning(f"CloudWatch metrics unavailable for {instance_id}: {e}")
173
- cpu_response = {'Datapoints': []}
174
- network_in_response = {'Datapoints': []}
175
- network_out_response = {'Datapoints': []}
176
-
179
+ cpu_response = {"Datapoints": []}
180
+ network_in_response = {"Datapoints": []}
181
+ network_out_response = {"Datapoints": []}
182
+
177
183
  # Calculate averages
178
184
  avg_cpu = 0
179
- if cpu_response['Datapoints']:
180
- avg_cpu = sum(dp['Average'] for dp in cpu_response['Datapoints']) / len(cpu_response['Datapoints'])
181
-
182
- total_network_in = sum(dp['Sum'] for dp in network_in_response['Datapoints']) if network_in_response['Datapoints'] else 0
183
- total_network_out = sum(dp['Sum'] for dp in network_out_response['Datapoints']) if network_out_response['Datapoints'] else 0
184
-
185
+ if cpu_response["Datapoints"]:
186
+ avg_cpu = sum(dp["Average"] for dp in cpu_response["Datapoints"]) / len(cpu_response["Datapoints"])
187
+
188
+ total_network_in = (
189
+ sum(dp["Sum"] for dp in network_in_response["Datapoints"]) if network_in_response["Datapoints"] else 0
190
+ )
191
+ total_network_out = (
192
+ sum(dp["Sum"] for dp in network_out_response["Datapoints"]) if network_out_response["Datapoints"] else 0
193
+ )
194
+
185
195
  # Estimate monthly cost (simplified pricing model)
186
196
  estimated_monthly_cost = self._estimate_instance_cost(instance_type)
187
-
197
+
188
198
  # Get instance tags
189
- tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
190
-
199
+ tags = {tag["Key"]: tag["Value"] for tag in instance.get("Tags", [])}
200
+
191
201
  # Backup activity assessment
192
- backup_activity_score = self._assess_backup_activity(
193
- avg_cpu, total_network_in, total_network_out, tags
194
- )
195
-
202
+ backup_activity_score = self._assess_backup_activity(avg_cpu, total_network_in, total_network_out, tags)
203
+
196
204
  return {
197
205
  "instance_id": instance_id,
198
206
  "instance_type": instance_type,
199
- "state": instance['State']['Name'],
200
- "launch_time": instance.get('LaunchTime', '').isoformat() if instance.get('LaunchTime') else '',
207
+ "state": instance["State"]["Name"],
208
+ "launch_time": instance.get("LaunchTime", "").isoformat() if instance.get("LaunchTime") else "",
201
209
  "avg_cpu_utilization": round(avg_cpu, 2),
202
210
  "network_in_bytes": int(total_network_in),
203
211
  "network_out_bytes": int(total_network_out),
204
212
  "estimated_monthly_cost": estimated_monthly_cost,
205
213
  "tags": tags,
206
214
  "backup_activity_score": backup_activity_score,
207
- "recommendation": self._generate_recommendation(backup_activity_score, avg_cpu)
215
+ "recommendation": self._generate_recommendation(backup_activity_score, avg_cpu),
208
216
  }
209
-
217
+
210
218
  def _estimate_instance_cost(self, instance_type: str) -> float:
211
219
  """Estimate monthly cost for EC2 instance type."""
212
220
  # Simplified pricing model - actual costs may vary
213
221
  instance_pricing = {
214
- 't2.micro': 8.76,
215
- 't2.small': 17.52,
216
- 't2.medium': 35.04,
217
- 't2.large': 70.08,
218
- 't3.micro': 7.59,
219
- 't3.small': 15.18,
220
- 't3.medium': 30.37,
221
- 't3.large': 60.74,
222
- 'm5.large': 70.08,
223
- 'm5.xlarge': 140.16,
224
- 'm5.2xlarge': 280.32,
225
- 'c5.large': 62.93,
226
- 'c5.xlarge': 125.87,
227
- 'r5.large': 91.98,
228
- 'r5.xlarge': 183.96
222
+ "t2.micro": 8.76,
223
+ "t2.small": 17.52,
224
+ "t2.medium": 35.04,
225
+ "t2.large": 70.08,
226
+ "t3.micro": 7.59,
227
+ "t3.small": 15.18,
228
+ "t3.medium": 30.37,
229
+ "t3.large": 60.74,
230
+ "m5.large": 70.08,
231
+ "m5.xlarge": 140.16,
232
+ "m5.2xlarge": 280.32,
233
+ "c5.large": 62.93,
234
+ "c5.xlarge": 125.87,
235
+ "r5.large": 91.98,
236
+ "r5.xlarge": 183.96,
229
237
  }
230
-
238
+
231
239
  return instance_pricing.get(instance_type, 100.0) # Default estimate
232
-
240
+
233
241
  def _assess_backup_activity(self, cpu: float, network_in: int, network_out: int, tags: Dict) -> str:
234
242
  """Assess likelihood of backup activity based on metrics and tags."""
235
243
  score_factors = []
236
-
244
+
237
245
  # CPU utilization assessment
238
246
  if cpu > 20:
239
247
  score_factors.append("High CPU usage suggests active processes")
240
248
  elif cpu < 5:
241
249
  score_factors.append("Low CPU usage may indicate idle instance")
242
-
250
+
243
251
  # Network activity assessment
244
252
  total_network = network_in + network_out
245
253
  if total_network > 10 * 1024**3: # 10 GB
246
254
  score_factors.append("High network activity suggests data transfer")
247
255
  elif total_network < 1 * 1024**3: # 1 GB
248
256
  score_factors.append("Low network activity may indicate minimal backup activity")
249
-
257
+
250
258
  # Tag analysis for Commvault indicators
251
- commvault_indicators = ['commvault', 'backup', 'cv', 'media', 'agent']
259
+ commvault_indicators = ["commvault", "backup", "cv", "media", "agent"]
252
260
  for indicator in commvault_indicators:
253
261
  for tag_value in tags.values():
254
262
  if indicator.lower() in str(tag_value).lower():
255
263
  score_factors.append(f"Tag indicates Commvault purpose: {tag_value}")
256
264
  break
257
-
265
+
258
266
  if len(score_factors) >= 2:
259
267
  return "LIKELY_ACTIVE"
260
268
  elif len(score_factors) == 1:
261
269
  return "UNCERTAIN"
262
270
  else:
263
271
  return "LIKELY_IDLE"
264
-
272
+
265
273
  def _generate_recommendation(self, activity_score: str, cpu: float) -> str:
266
274
  """Generate optimization recommendation based on analysis."""
267
275
  if activity_score == "LIKELY_IDLE" and cpu < 5:
268
276
  return "CANDIDATE_FOR_DECOMMISSION"
269
277
  elif activity_score == "UNCERTAIN":
270
- return "REQUIRES_DEEPER_INVESTIGATION"
278
+ return "REQUIRES_DEEPER_INVESTIGATION"
271
279
  elif activity_score == "LIKELY_ACTIVE":
272
280
  return "RETAIN_MONITOR_USAGE"
273
281
  else:
274
282
  return "MANUAL_REVIEW_REQUIRED"
275
-
283
+
276
284
  def _calculate_optimization_potential(self, instances: List[Dict]) -> Dict:
277
285
  """Calculate potential cost savings from optimization."""
278
- decommission_candidates = [
279
- i for i in instances
280
- if i['recommendation'] == 'CANDIDATE_FOR_DECOMMISSION'
281
- ]
282
-
283
- investigation_required = [
284
- i for i in instances
285
- if i['recommendation'] == 'REQUIRES_DEEPER_INVESTIGATION'
286
- ]
287
-
288
- potential_monthly_savings = sum(i['estimated_monthly_cost'] for i in decommission_candidates)
286
+ decommission_candidates = [i for i in instances if i["recommendation"] == "CANDIDATE_FOR_DECOMMISSION"]
287
+
288
+ investigation_required = [i for i in instances if i["recommendation"] == "REQUIRES_DEEPER_INVESTIGATION"]
289
+
290
+ potential_monthly_savings = sum(i["estimated_monthly_cost"] for i in decommission_candidates)
289
291
  potential_annual_savings = potential_monthly_savings * 12
290
-
292
+
291
293
  return {
292
294
  "decommission_candidates": len(decommission_candidates),
293
295
  "investigation_required": len(investigation_required),
294
296
  "potential_monthly_savings": potential_monthly_savings,
295
297
  "potential_annual_savings": potential_annual_savings,
296
- "confidence_level": "HIGH" if len(decommission_candidates) > 0 else "MEDIUM"
298
+ "confidence_level": "HIGH" if len(decommission_candidates) > 0 else "MEDIUM",
297
299
  }
298
-
300
+
299
301
  def _display_analysis_results(self, instances: List[Dict], total_cost: float, optimization: Dict):
300
302
  """Display comprehensive analysis results."""
301
303
  # Summary table
302
304
  summary_table = create_table(
303
305
  title="FinOps-25: Commvault EC2 Analysis Summary",
304
- caption=f"Account: {self.account_id} | Analysis Date: {datetime.now().strftime('%Y-%m-%d')}"
306
+ caption=f"Account: {self.account_id} | Analysis Date: {datetime.now().strftime('%Y-%m-%d')}",
305
307
  )
306
-
308
+
307
309
  summary_table.add_column("Metric", style="cyan", width=25)
308
310
  summary_table.add_column("Value", style="green", justify="right", width=20)
309
311
  summary_table.add_column("Impact", style="yellow", width=30)
310
-
311
- summary_table.add_row(
312
- "Total Instances",
313
- str(len(instances)),
314
- "Infrastructure scope"
315
- )
316
- summary_table.add_row(
317
- "Monthly Cost",
318
- format_cost(total_cost, period="monthly"),
319
- "Current infrastructure cost"
320
- )
312
+
313
+ summary_table.add_row("Total Instances", str(len(instances)), "Infrastructure scope")
314
+ summary_table.add_row("Monthly Cost", format_cost(total_cost, period="monthly"), "Current infrastructure cost")
321
315
  summary_table.add_row(
322
316
  "Decommission Candidates",
323
- str(optimization['decommission_candidates']),
324
- "Immediate optimization opportunities"
317
+ str(optimization["decommission_candidates"]),
318
+ "Immediate optimization opportunities",
325
319
  )
326
320
  summary_table.add_row(
327
- "Investigation Required",
328
- str(optimization['investigation_required']),
329
- "Further analysis needed"
321
+ "Investigation Required", str(optimization["investigation_required"]), "Further analysis needed"
330
322
  )
331
323
  summary_table.add_row(
332
324
  "Potential Annual Savings",
333
- format_cost(optimization['potential_annual_savings'], period="annual"),
334
- f"Confidence: {optimization['confidence_level']}"
325
+ format_cost(optimization["potential_annual_savings"], period="annual"),
326
+ f"Confidence: {optimization['confidence_level']}",
335
327
  )
336
-
328
+
337
329
  console.print(summary_table)
338
-
330
+
339
331
  # Detailed instance analysis
340
332
  if instances:
341
333
  detail_table = create_table(
342
- title="Detailed Instance Analysis",
343
- caption="CPU and network utilization patterns with recommendations"
334
+ title="Detailed Instance Analysis", caption="CPU and network utilization patterns with recommendations"
344
335
  )
345
-
336
+
346
337
  detail_table.add_column("Instance ID", style="cyan", width=18)
347
338
  detail_table.add_column("Type", style="blue", width=12)
348
339
  detail_table.add_column("Avg CPU %", style="yellow", justify="right", width=10)
349
340
  detail_table.add_column("Network (GB)", style="magenta", justify="right", width=12)
350
341
  detail_table.add_column("Monthly Cost", style="green", justify="right", width=12)
351
342
  detail_table.add_column("Recommendation", style="red", width=20)
352
-
343
+
353
344
  for instance in instances:
354
- network_gb = (instance['network_in_bytes'] + instance['network_out_bytes']) / (1024**3)
355
-
345
+ network_gb = (instance["network_in_bytes"] + instance["network_out_bytes"]) / (1024**3)
346
+
356
347
  recommendation_style = {
357
- 'CANDIDATE_FOR_DECOMMISSION': '[red]DECOMMISSION[/red]',
358
- 'REQUIRES_DEEPER_INVESTIGATION': '[yellow]INVESTIGATE[/yellow]',
359
- 'RETAIN_MONITOR_USAGE': '[green]RETAIN[/green]',
360
- 'MANUAL_REVIEW_REQUIRED': '[blue]MANUAL REVIEW[/blue]'
361
- }.get(instance['recommendation'], instance['recommendation'])
362
-
348
+ "CANDIDATE_FOR_DECOMMISSION": "[red]DECOMMISSION[/red]",
349
+ "REQUIRES_DEEPER_INVESTIGATION": "[yellow]INVESTIGATE[/yellow]",
350
+ "RETAIN_MONITOR_USAGE": "[green]RETAIN[/green]",
351
+ "MANUAL_REVIEW_REQUIRED": "[blue]MANUAL REVIEW[/blue]",
352
+ }.get(instance["recommendation"], instance["recommendation"])
353
+
363
354
  detail_table.add_row(
364
- instance['instance_id'],
365
- instance['instance_type'],
355
+ instance["instance_id"],
356
+ instance["instance_type"],
366
357
  f"{instance['avg_cpu_utilization']:.1f}%",
367
358
  f"{network_gb:.1f}",
368
- format_cost(instance['estimated_monthly_cost'], period="monthly"),
369
- recommendation_style
359
+ format_cost(instance["estimated_monthly_cost"], period="monthly"),
360
+ recommendation_style,
370
361
  )
371
-
362
+
372
363
  console.print(detail_table)
373
-
364
+
374
365
  # Business impact panel
375
- if optimization['potential_annual_savings'] > 0:
366
+ if optimization["potential_annual_savings"] > 0:
376
367
  impact_panel = create_panel(
377
368
  f"[bold green]Business Impact Analysis[/bold green]\n\n"
378
369
  f"💰 [yellow]Optimization Potential:[/yellow] {format_cost(optimization['potential_annual_savings'], period='annual')}\n"
@@ -381,15 +372,16 @@ class CommvaultEC2Analysis:
381
372
  f"⏱️ [yellow]Timeline:[/yellow] 3-4 weeks investigation + approval process\n\n"
382
373
  f"[blue]Strategic Value:[/blue] Establish investigation methodology for infrastructure optimization\n"
383
374
  f"[blue]Risk Assessment:[/blue] Medium risk - requires careful backup workflow validation",
384
- title="FinOps-25: Commvault Investigation Framework Results"
375
+ title="FinOps-25: Commvault Investigation Framework Results",
385
376
  )
386
377
  console.print(impact_panel)
387
-
378
+
388
379
  print_success(f"FinOps-25 analysis complete - {len(instances)} instances analyzed")
389
380
 
390
381
 
391
- def analyze_commvault_ec2(profile: Optional[str] = None, account_id: Optional[str] = None,
392
- region: str = "us-east-1") -> Dict:
382
+ def analyze_commvault_ec2(
383
+ profile: Optional[str] = None, account_id: Optional[str] = None, region: str = "us-east-1"
384
+ ) -> Dict:
393
385
  """
394
386
  Business wrapper function for FinOps-25 Commvault EC2 investigation.
395
387
 
@@ -406,26 +398,27 @@ def analyze_commvault_ec2(profile: Optional[str] = None, account_id: Optional[st
406
398
 
407
399
 
408
400
  @click.command()
409
- @click.option('--profile', help='AWS profile name')
410
- @click.option('--account-id', help='Target account ID (defaults to profile account)')
411
- @click.option('--region', default='us-east-1', help='AWS region')
412
- @click.option('--output-file', help='Save results to file')
401
+ @click.option("--profile", help="AWS profile name")
402
+ @click.option("--account-id", help="Target account ID (defaults to profile account)")
403
+ @click.option("--region", default="us-east-1", help="AWS region")
404
+ @click.option("--output-file", help="Save results to file")
413
405
  def main(profile, account_id, region, output_file):
414
406
  """FinOps-25: Commvault EC2 Investigation Framework - CLI interface."""
415
407
  try:
416
408
  # If account_id not provided, it will be auto-resolved from profile
417
409
  results = analyze_commvault_ec2(profile, account_id, region)
418
-
410
+
419
411
  if output_file:
420
412
  import json
421
- with open(output_file, 'w') as f:
413
+
414
+ with open(output_file, "w") as f:
422
415
  json.dump(results, f, indent=2, default=str)
423
416
  print_success(f"Results saved to {output_file}")
424
-
417
+
425
418
  except Exception as e:
426
419
  print_error(f"Analysis failed: {e}")
427
420
  raise click.Abort()
428
421
 
429
422
 
430
- if __name__ == '__main__':
431
- main()
423
+ if __name__ == "__main__":
424
+ main()