runbooks 0.9.2__py3-none-any.whl → 0.9.4__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 (46) hide show
  1. runbooks/__init__.py +15 -6
  2. runbooks/cfat/__init__.py +3 -1
  3. runbooks/cloudops/__init__.py +3 -1
  4. runbooks/common/aws_utils.py +367 -0
  5. runbooks/common/enhanced_logging_example.py +239 -0
  6. runbooks/common/enhanced_logging_integration_example.py +257 -0
  7. runbooks/common/logging_integration_helper.py +344 -0
  8. runbooks/common/profile_utils.py +8 -6
  9. runbooks/common/rich_utils.py +347 -3
  10. runbooks/enterprise/logging.py +400 -38
  11. runbooks/finops/README.md +262 -406
  12. runbooks/finops/__init__.py +2 -1
  13. runbooks/finops/accuracy_cross_validator.py +12 -3
  14. runbooks/finops/commvault_ec2_analysis.py +415 -0
  15. runbooks/finops/cost_processor.py +718 -42
  16. runbooks/finops/dashboard_router.py +44 -22
  17. runbooks/finops/dashboard_runner.py +302 -39
  18. runbooks/finops/embedded_mcp_validator.py +358 -48
  19. runbooks/finops/finops_scenarios.py +771 -0
  20. runbooks/finops/multi_dashboard.py +30 -15
  21. runbooks/finops/single_dashboard.py +386 -58
  22. runbooks/finops/types.py +29 -4
  23. runbooks/inventory/__init__.py +2 -1
  24. runbooks/main.py +522 -29
  25. runbooks/operate/__init__.py +3 -1
  26. runbooks/remediation/__init__.py +3 -1
  27. runbooks/remediation/commons.py +55 -16
  28. runbooks/remediation/commvault_ec2_analysis.py +259 -0
  29. runbooks/remediation/rds_snapshot_list.py +267 -102
  30. runbooks/remediation/workspaces_list.py +182 -31
  31. runbooks/security/__init__.py +3 -1
  32. runbooks/sre/__init__.py +2 -1
  33. runbooks/utils/__init__.py +81 -6
  34. runbooks/utils/version_validator.py +241 -0
  35. runbooks/vpc/__init__.py +2 -1
  36. {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/METADATA +98 -60
  37. {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/RECORD +41 -38
  38. {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/entry_points.txt +1 -0
  39. runbooks/inventory/cloudtrail.md +0 -727
  40. runbooks/inventory/discovery.md +0 -81
  41. runbooks/remediation/CLAUDE.md +0 -100
  42. runbooks/remediation/DOME9.md +0 -218
  43. runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +0 -506
  44. {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/WHEEL +0 -0
  45. {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/licenses/LICENSE +0 -0
  46. {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,8 @@ This module provides terminal-based AWS cost monitoring with features including:
12
12
  Integrated as a submodule of CloudOps Runbooks for enterprise FinOps automation.
13
13
  """
14
14
 
15
- __version__ = "0.7.8"
15
+ # Import centralized version from main runbooks package
16
+ from runbooks import __version__
16
17
 
17
18
  # Core components
18
19
  # AWS client utilities
@@ -379,14 +379,23 @@ class AccuracyCrossValidator:
379
379
  session = boto3.Session(profile_name=profile)
380
380
  ce_client = session.client("ce", region_name="us-east-1")
381
381
 
382
- # Get current month cost data
382
+ # Get current month cost data with September 1st fix
383
383
  end_date = datetime.now().date()
384
384
  start_date = end_date.replace(day=1)
385
+
386
+ # CRITICAL FIX: September 1st boundary handling (matches cost_processor.py)
387
+ if end_date.day == 1:
388
+ self.console.log(f"[yellow]⚠️ Cross-Validator: First day of month detected ({end_date.strftime('%B %d, %Y')}) - using partial period[/]")
389
+ # For AWS Cost Explorer, end date is exclusive, so add one day to include today
390
+ end_date = end_date + timedelta(days=1)
391
+ else:
392
+ # Normal case: include up to today (exclusive end date)
393
+ end_date = end_date + timedelta(days=1)
385
394
 
386
395
  response = ce_client.get_cost_and_usage(
387
396
  TimePeriod={"Start": start_date.isoformat(), "End": end_date.isoformat()},
388
397
  Granularity="MONTHLY",
389
- Metrics=["BlendedCost"],
398
+ Metrics=["UnblendedCost"],
390
399
  GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
391
400
  )
392
401
 
@@ -398,7 +407,7 @@ class AccuracyCrossValidator:
398
407
  for result in response["ResultsByTime"]:
399
408
  for group in result.get("Groups", []):
400
409
  service = group.get("Keys", ["Unknown"])[0]
401
- cost = float(group.get("Metrics", {}).get("BlendedCost", {}).get("Amount", 0))
410
+ cost = float(group.get("Metrics", {}).get("UnblendedCost", {}).get("Amount", 0))
402
411
  services[service] = cost
403
412
  total_cost += cost
404
413
 
@@ -0,0 +1,415 @@
1
+ """
2
+ AWSO-25: Commvault EC2 Investigation Framework
3
+
4
+ Strategic Achievement: Investigation methodology established for infrastructure optimization
5
+ Account Focus: 637423383469 (Commvault backups account)
6
+ Objective: Analyze EC2 instances for backup utilization and cost optimization potential
7
+
8
+ This module provides comprehensive EC2 utilization analysis specifically for Commvault
9
+ backup infrastructure to determine if instances are actively performing backups.
10
+
11
+ Strategic Alignment:
12
+ - "Do one thing and do it well": Focus on Commvault-specific EC2 analysis
13
+ - "Move Fast, But Not So Fast We Crash": Careful analysis before decommissioning
14
+ - Enterprise FAANG SDLC: Evidence-based investigation with audit trails
15
+ """
16
+
17
+ import logging
18
+ from datetime import datetime, timedelta, timezone
19
+ from typing import Dict, List, Optional, Tuple
20
+
21
+ import boto3
22
+ import click
23
+ from botocore.exceptions import ClientError
24
+
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
28
+ )
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class CommvaultEC2Analysis:
34
+ """
35
+ AWSO-25: Commvault EC2 Investigation Framework
36
+
37
+ Provides systematic analysis of EC2 instances in Commvault backup account
38
+ to determine utilization patterns and optimization opportunities.
39
+ """
40
+
41
+ def __init__(self, profile_name: Optional[str] = None, account_id: str = "637423383469"):
42
+ """Initialize Commvault EC2 analysis."""
43
+ self.profile_name = profile_name
44
+ self.account_id = account_id
45
+ self.session = boto3.Session(profile_name=profile_name) if profile_name else boto3.Session()
46
+
47
+ def analyze_commvault_instances(self, region: str = "us-east-1") -> Dict:
48
+ """
49
+ Analyze EC2 instances in Commvault account for utilization patterns.
50
+
51
+ Args:
52
+ region: AWS region to analyze (default: us-east-1)
53
+
54
+ Returns:
55
+ Dict containing analysis results with cost implications
56
+ """
57
+ print_header("AWSO-25: Commvault EC2 Investigation", f"Account: {self.account_id}")
58
+
59
+ try:
60
+ ec2_client = self.session.client('ec2', region_name=region)
61
+ cloudwatch_client = self.session.client('cloudwatch', region_name=region)
62
+
63
+ # Get all EC2 instances
64
+ response = ec2_client.describe_instances()
65
+ instances = []
66
+
67
+ for reservation in response['Reservations']:
68
+ for instance in reservation['Instances']:
69
+ if instance['State']['Name'] != 'terminated':
70
+ instances.append(instance)
71
+
72
+ if not instances:
73
+ print_warning(f"No active instances found in account {self.account_id}")
74
+ return {"instances": [], "total_cost": 0, "optimization_potential": 0}
75
+
76
+ print_info(f"Found {len(instances)} active instances for analysis")
77
+
78
+ # Analyze each instance
79
+ analysis_results = []
80
+ total_monthly_cost = 0
81
+
82
+ with create_progress_bar() as progress:
83
+ task = progress.add_task("Analyzing instances...", total=len(instances))
84
+
85
+ for instance in instances:
86
+ instance_analysis = self._analyze_single_instance(
87
+ instance, cloudwatch_client, region
88
+ )
89
+ analysis_results.append(instance_analysis)
90
+ total_monthly_cost += instance_analysis['estimated_monthly_cost']
91
+ progress.advance(task)
92
+
93
+ # Generate summary
94
+ optimization_potential = self._calculate_optimization_potential(analysis_results)
95
+
96
+ # Display results
97
+ self._display_analysis_results(analysis_results, total_monthly_cost, optimization_potential)
98
+
99
+ return {
100
+ "instances": analysis_results,
101
+ "total_monthly_cost": total_monthly_cost,
102
+ "optimization_potential": optimization_potential,
103
+ "account_id": self.account_id,
104
+ "analysis_timestamp": datetime.now().isoformat()
105
+ }
106
+
107
+ except ClientError as e:
108
+ print_error(f"AWS API Error: {e}")
109
+ raise
110
+ except Exception as e:
111
+ print_error(f"Analysis Error: {e}")
112
+ raise
113
+
114
+ def _analyze_single_instance(self, instance: Dict, cloudwatch_client, region: str) -> Dict:
115
+ """Analyze a single EC2 instance for utilization patterns."""
116
+ instance_id = instance['InstanceId']
117
+ instance_type = instance['InstanceType']
118
+
119
+ # Get CloudWatch metrics for last 30 days
120
+ end_time = datetime.now(timezone.utc)
121
+ start_time = end_time - timedelta(days=30)
122
+
123
+ try:
124
+ # CPU Utilization
125
+ cpu_response = cloudwatch_client.get_metric_statistics(
126
+ Namespace='AWS/EC2',
127
+ MetricName='CPUUtilization',
128
+ Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
129
+ StartTime=start_time,
130
+ EndTime=end_time,
131
+ Period=3600, # 1 hour intervals
132
+ Statistics=['Average']
133
+ )
134
+
135
+ # Network metrics for backup activity indication
136
+ network_in_response = cloudwatch_client.get_metric_statistics(
137
+ Namespace='AWS/EC2',
138
+ MetricName='NetworkIn',
139
+ Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
140
+ StartTime=start_time,
141
+ EndTime=end_time,
142
+ Period=3600,
143
+ Statistics=['Sum']
144
+ )
145
+
146
+ network_out_response = cloudwatch_client.get_metric_statistics(
147
+ Namespace='AWS/EC2',
148
+ MetricName='NetworkOut',
149
+ Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
150
+ StartTime=start_time,
151
+ EndTime=end_time,
152
+ Period=3600,
153
+ Statistics=['Sum']
154
+ )
155
+
156
+ except ClientError as e:
157
+ logger.warning(f"CloudWatch metrics unavailable for {instance_id}: {e}")
158
+ cpu_response = {'Datapoints': []}
159
+ network_in_response = {'Datapoints': []}
160
+ network_out_response = {'Datapoints': []}
161
+
162
+ # Calculate averages
163
+ avg_cpu = 0
164
+ if cpu_response['Datapoints']:
165
+ avg_cpu = sum(dp['Average'] for dp in cpu_response['Datapoints']) / len(cpu_response['Datapoints'])
166
+
167
+ total_network_in = sum(dp['Sum'] for dp in network_in_response['Datapoints']) if network_in_response['Datapoints'] else 0
168
+ total_network_out = sum(dp['Sum'] for dp in network_out_response['Datapoints']) if network_out_response['Datapoints'] else 0
169
+
170
+ # Estimate monthly cost (simplified pricing model)
171
+ estimated_monthly_cost = self._estimate_instance_cost(instance_type)
172
+
173
+ # Get instance tags
174
+ tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
175
+
176
+ # Backup activity assessment
177
+ backup_activity_score = self._assess_backup_activity(
178
+ avg_cpu, total_network_in, total_network_out, tags
179
+ )
180
+
181
+ return {
182
+ "instance_id": instance_id,
183
+ "instance_type": instance_type,
184
+ "state": instance['State']['Name'],
185
+ "launch_time": instance.get('LaunchTime', '').isoformat() if instance.get('LaunchTime') else '',
186
+ "avg_cpu_utilization": round(avg_cpu, 2),
187
+ "network_in_bytes": int(total_network_in),
188
+ "network_out_bytes": int(total_network_out),
189
+ "estimated_monthly_cost": estimated_monthly_cost,
190
+ "tags": tags,
191
+ "backup_activity_score": backup_activity_score,
192
+ "recommendation": self._generate_recommendation(backup_activity_score, avg_cpu)
193
+ }
194
+
195
+ def _estimate_instance_cost(self, instance_type: str) -> float:
196
+ """Estimate monthly cost for EC2 instance type."""
197
+ # Simplified pricing model - actual costs may vary
198
+ instance_pricing = {
199
+ 't2.micro': 8.76,
200
+ 't2.small': 17.52,
201
+ 't2.medium': 35.04,
202
+ 't2.large': 70.08,
203
+ 't3.micro': 7.59,
204
+ 't3.small': 15.18,
205
+ 't3.medium': 30.37,
206
+ 't3.large': 60.74,
207
+ 'm5.large': 70.08,
208
+ 'm5.xlarge': 140.16,
209
+ 'm5.2xlarge': 280.32,
210
+ 'c5.large': 62.93,
211
+ 'c5.xlarge': 125.87,
212
+ 'r5.large': 91.98,
213
+ 'r5.xlarge': 183.96
214
+ }
215
+
216
+ return instance_pricing.get(instance_type, 100.0) # Default estimate
217
+
218
+ def _assess_backup_activity(self, cpu: float, network_in: int, network_out: int, tags: Dict) -> str:
219
+ """Assess likelihood of backup activity based on metrics and tags."""
220
+ score_factors = []
221
+
222
+ # CPU utilization assessment
223
+ if cpu > 20:
224
+ score_factors.append("High CPU usage suggests active processes")
225
+ elif cpu < 5:
226
+ score_factors.append("Low CPU usage may indicate idle instance")
227
+
228
+ # Network activity assessment
229
+ total_network = network_in + network_out
230
+ if total_network > 10 * 1024**3: # 10 GB
231
+ score_factors.append("High network activity suggests data transfer")
232
+ elif total_network < 1 * 1024**3: # 1 GB
233
+ score_factors.append("Low network activity may indicate minimal backup activity")
234
+
235
+ # Tag analysis for Commvault indicators
236
+ commvault_indicators = ['commvault', 'backup', 'cv', 'media', 'agent']
237
+ for indicator in commvault_indicators:
238
+ for tag_value in tags.values():
239
+ if indicator.lower() in str(tag_value).lower():
240
+ score_factors.append(f"Tag indicates Commvault purpose: {tag_value}")
241
+ break
242
+
243
+ if len(score_factors) >= 2:
244
+ return "LIKELY_ACTIVE"
245
+ elif len(score_factors) == 1:
246
+ return "UNCERTAIN"
247
+ else:
248
+ return "LIKELY_IDLE"
249
+
250
+ def _generate_recommendation(self, activity_score: str, cpu: float) -> str:
251
+ """Generate optimization recommendation based on analysis."""
252
+ if activity_score == "LIKELY_IDLE" and cpu < 5:
253
+ return "CANDIDATE_FOR_DECOMMISSION"
254
+ elif activity_score == "UNCERTAIN":
255
+ return "REQUIRES_DEEPER_INVESTIGATION"
256
+ elif activity_score == "LIKELY_ACTIVE":
257
+ return "RETAIN_MONITOR_USAGE"
258
+ else:
259
+ return "MANUAL_REVIEW_REQUIRED"
260
+
261
+ def _calculate_optimization_potential(self, instances: List[Dict]) -> Dict:
262
+ """Calculate potential cost savings from optimization."""
263
+ decommission_candidates = [
264
+ i for i in instances
265
+ if i['recommendation'] == 'CANDIDATE_FOR_DECOMMISSION'
266
+ ]
267
+
268
+ investigation_required = [
269
+ i for i in instances
270
+ if i['recommendation'] == 'REQUIRES_DEEPER_INVESTIGATION'
271
+ ]
272
+
273
+ potential_monthly_savings = sum(i['estimated_monthly_cost'] for i in decommission_candidates)
274
+ potential_annual_savings = potential_monthly_savings * 12
275
+
276
+ return {
277
+ "decommission_candidates": len(decommission_candidates),
278
+ "investigation_required": len(investigation_required),
279
+ "potential_monthly_savings": potential_monthly_savings,
280
+ "potential_annual_savings": potential_annual_savings,
281
+ "confidence_level": "HIGH" if len(decommission_candidates) > 0 else "MEDIUM"
282
+ }
283
+
284
+ def _display_analysis_results(self, instances: List[Dict], total_cost: float, optimization: Dict):
285
+ """Display comprehensive analysis results."""
286
+ # Summary table
287
+ summary_table = create_table(
288
+ title="AWSO-25: Commvault EC2 Analysis Summary",
289
+ caption=f"Account: {self.account_id} | Analysis Date: {datetime.now().strftime('%Y-%m-%d')}"
290
+ )
291
+
292
+ summary_table.add_column("Metric", style="cyan", width=25)
293
+ summary_table.add_column("Value", style="green", justify="right", width=20)
294
+ summary_table.add_column("Impact", style="yellow", width=30)
295
+
296
+ summary_table.add_row(
297
+ "Total Instances",
298
+ str(len(instances)),
299
+ "Infrastructure scope"
300
+ )
301
+ summary_table.add_row(
302
+ "Monthly Cost",
303
+ format_cost(total_cost, period="monthly"),
304
+ "Current infrastructure cost"
305
+ )
306
+ summary_table.add_row(
307
+ "Decommission Candidates",
308
+ str(optimization['decommission_candidates']),
309
+ "Immediate optimization opportunities"
310
+ )
311
+ summary_table.add_row(
312
+ "Investigation Required",
313
+ str(optimization['investigation_required']),
314
+ "Further analysis needed"
315
+ )
316
+ summary_table.add_row(
317
+ "Potential Annual Savings",
318
+ format_cost(optimization['potential_annual_savings'], period="annual"),
319
+ f"Confidence: {optimization['confidence_level']}"
320
+ )
321
+
322
+ console.print(summary_table)
323
+
324
+ # Detailed instance analysis
325
+ if instances:
326
+ detail_table = create_table(
327
+ title="Detailed Instance Analysis",
328
+ caption="CPU and network utilization patterns with recommendations"
329
+ )
330
+
331
+ detail_table.add_column("Instance ID", style="cyan", width=18)
332
+ detail_table.add_column("Type", style="blue", width=12)
333
+ detail_table.add_column("Avg CPU %", style="yellow", justify="right", width=10)
334
+ detail_table.add_column("Network (GB)", style="magenta", justify="right", width=12)
335
+ detail_table.add_column("Monthly Cost", style="green", justify="right", width=12)
336
+ detail_table.add_column("Recommendation", style="red", width=20)
337
+
338
+ for instance in instances:
339
+ network_gb = (instance['network_in_bytes'] + instance['network_out_bytes']) / (1024**3)
340
+
341
+ recommendation_style = {
342
+ 'CANDIDATE_FOR_DECOMMISSION': '[red]DECOMMISSION[/red]',
343
+ 'REQUIRES_DEEPER_INVESTIGATION': '[yellow]INVESTIGATE[/yellow]',
344
+ 'RETAIN_MONITOR_USAGE': '[green]RETAIN[/green]',
345
+ 'MANUAL_REVIEW_REQUIRED': '[blue]MANUAL REVIEW[/blue]'
346
+ }.get(instance['recommendation'], instance['recommendation'])
347
+
348
+ detail_table.add_row(
349
+ instance['instance_id'],
350
+ instance['instance_type'],
351
+ f"{instance['avg_cpu_utilization']:.1f}%",
352
+ f"{network_gb:.1f}",
353
+ format_cost(instance['estimated_monthly_cost'], period="monthly"),
354
+ recommendation_style
355
+ )
356
+
357
+ console.print(detail_table)
358
+
359
+ # Business impact panel
360
+ if optimization['potential_annual_savings'] > 0:
361
+ impact_panel = create_panel(
362
+ f"[bold green]Business Impact Analysis[/bold green]\n\n"
363
+ f"💰 [yellow]Optimization Potential:[/yellow] {format_cost(optimization['potential_annual_savings'], period='annual')}\n"
364
+ f"📊 [yellow]Confidence Level:[/yellow] {optimization['confidence_level']}\n"
365
+ f"🎯 [yellow]Implementation Approach:[/yellow] Systematic decommissioning with validation\n"
366
+ f"⏱️ [yellow]Timeline:[/yellow] 3-4 weeks investigation + approval process\n\n"
367
+ f"[blue]Strategic Value:[/blue] Establish investigation methodology for infrastructure optimization\n"
368
+ f"[blue]Risk Assessment:[/blue] Medium risk - requires careful backup workflow validation",
369
+ title="AWSO-25: Commvault Investigation Framework Results"
370
+ )
371
+ console.print(impact_panel)
372
+
373
+ print_success(f"AWSO-25 analysis complete - {len(instances)} instances analyzed")
374
+
375
+
376
+ def analyze_commvault_ec2(profile: Optional[str] = None, account_id: str = "637423383469",
377
+ region: str = "us-east-1") -> Dict:
378
+ """
379
+ Business wrapper function for AWSO-25 Commvault EC2 investigation.
380
+
381
+ Args:
382
+ profile: AWS profile name
383
+ account_id: Target account (default: 637423383469)
384
+ region: AWS region (default: us-east-1)
385
+
386
+ Returns:
387
+ Dict containing comprehensive analysis results
388
+ """
389
+ analyzer = CommvaultEC2Analysis(profile_name=profile, account_id=account_id)
390
+ return analyzer.analyze_commvault_instances(region=region)
391
+
392
+
393
+ @click.command()
394
+ @click.option('--profile', help='AWS profile name')
395
+ @click.option('--account-id', default='637423383469', help='Commvault account ID')
396
+ @click.option('--region', default='us-east-1', help='AWS region')
397
+ @click.option('--output-file', help='Save results to file')
398
+ def main(profile, account_id, region, output_file):
399
+ """AWSO-25: Commvault EC2 Investigation Framework - CLI interface."""
400
+ try:
401
+ results = analyze_commvault_ec2(profile, account_id, region)
402
+
403
+ if output_file:
404
+ import json
405
+ with open(output_file, 'w') as f:
406
+ json.dump(results, f, indent=2, default=str)
407
+ print_success(f"Results saved to {output_file}")
408
+
409
+ except Exception as e:
410
+ print_error(f"Analysis failed: {e}")
411
+ raise click.Abort()
412
+
413
+
414
+ if __name__ == '__main__':
415
+ main()