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
@@ -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()