runbooks 0.9.2__py3-none-any.whl → 0.9.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 (49) 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 +44 -1
  13. runbooks/finops/accuracy_cross_validator.py +12 -3
  14. runbooks/finops/business_cases.py +552 -0
  15. runbooks/finops/commvault_ec2_analysis.py +415 -0
  16. runbooks/finops/cost_processor.py +718 -42
  17. runbooks/finops/dashboard_router.py +44 -22
  18. runbooks/finops/dashboard_runner.py +302 -39
  19. runbooks/finops/embedded_mcp_validator.py +358 -48
  20. runbooks/finops/finops_scenarios.py +1122 -0
  21. runbooks/finops/helpers.py +182 -0
  22. runbooks/finops/multi_dashboard.py +30 -15
  23. runbooks/finops/scenarios.py +789 -0
  24. runbooks/finops/single_dashboard.py +386 -58
  25. runbooks/finops/types.py +29 -4
  26. runbooks/inventory/__init__.py +2 -1
  27. runbooks/main.py +522 -29
  28. runbooks/operate/__init__.py +3 -1
  29. runbooks/remediation/__init__.py +3 -1
  30. runbooks/remediation/commons.py +55 -16
  31. runbooks/remediation/commvault_ec2_analysis.py +259 -0
  32. runbooks/remediation/rds_snapshot_list.py +267 -102
  33. runbooks/remediation/workspaces_list.py +182 -31
  34. runbooks/security/__init__.py +3 -1
  35. runbooks/sre/__init__.py +2 -1
  36. runbooks/utils/__init__.py +81 -6
  37. runbooks/utils/version_validator.py +241 -0
  38. runbooks/vpc/__init__.py +2 -1
  39. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/METADATA +98 -60
  40. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/RECORD +44 -39
  41. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/entry_points.txt +1 -0
  42. runbooks/inventory/cloudtrail.md +0 -727
  43. runbooks/inventory/discovery.md +0 -81
  44. runbooks/remediation/CLAUDE.md +0 -100
  45. runbooks/remediation/DOME9.md +0 -218
  46. runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +0 -506
  47. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/WHEEL +0 -0
  48. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/licenses/LICENSE +0 -0
  49. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/top_level.txt +0 -0
@@ -182,8 +182,10 @@ from runbooks.operate.iam_operations import IAMOperations
182
182
  from runbooks.operate.s3_operations import S3Operations
183
183
  from runbooks.operate.tagging_operations import TaggingOperations
184
184
 
185
+ # Import centralized version from main runbooks package
186
+ from runbooks import __version__
187
+
185
188
  # Version info
186
- __version__ = "0.7.8"
187
189
  __author__ = "CloudOps Runbooks Team"
188
190
 
189
191
  # Public API exports
@@ -65,8 +65,10 @@ from runbooks.remediation.rds_remediation import RDSSecurityRemediation
65
65
  # Import remediation operations
66
66
  from runbooks.remediation.s3_remediation import S3SecurityRemediation
67
67
 
68
+ # Import centralized version from main runbooks package
69
+ from runbooks import __version__
70
+
68
71
  # Version info
69
- __version__ = "0.7.8"
70
72
  __author__ = "CloudOps Runbooks Team"
71
73
 
72
74
  # Public API exports
@@ -151,24 +151,63 @@ def get_method_details(client, rest_api_id, resource_id, http_method):
151
151
  raise
152
152
 
153
153
 
154
- def get_client(client_name: str):
155
- return boto3.client(
156
- client_name,
157
- aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
158
- aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
159
- aws_session_token=os.environ.get("AWS_SESSION_TOKEN"),
160
- region_name=os.environ.get("AWS_REGION"), # or your preferred region
161
- )
154
+ # Global profile variable for cost optimization commands
155
+ _profile = None
162
156
 
157
+ def get_client(client_name: str, profile_name: str = None, region_name: str = None):
158
+ """
159
+ Enhanced client creation with profile support for cost optimization commands.
160
+
161
+ Enterprise pattern: Uses profile-based sessions like other runbooks modules.
162
+ Supports both environment variables and profile-based authentication.
163
+ """
164
+ # Determine the profile to use (priority order)
165
+ profile_to_use = profile_name or _profile or os.environ.get("AWS_PROFILE")
166
+
167
+ # Determine the region to use
168
+ region_to_use = region_name or os.environ.get("AWS_REGION", "ap-southeast-2")
169
+
170
+ if profile_to_use:
171
+ # Use profile-based session (enterprise pattern)
172
+ session = boto3.Session(profile_name=profile_to_use)
173
+ return session.client(client_name, region_name=region_to_use)
174
+ else:
175
+ # Fallback to environment variables for backward compatibility
176
+ return boto3.client(
177
+ client_name,
178
+ aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
179
+ aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
180
+ aws_session_token=os.environ.get("AWS_SESSION_TOKEN"),
181
+ region_name=region_to_use,
182
+ )
163
183
 
164
- def get_resource(client_name: str):
165
- return boto3.resource(
166
- client_name,
167
- aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
168
- aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
169
- aws_session_token=os.environ.get("AWS_SESSION_TOKEN"),
170
- region_name=os.environ.get("AWS_REGION"), # or your preferred region
171
- )
184
+
185
+ def get_resource(client_name: str, profile_name: str = None, region_name: str = None):
186
+ """
187
+ Enhanced resource creation with profile support for cost optimization commands.
188
+
189
+ Enterprise pattern: Uses profile-based sessions like other runbooks modules.
190
+ Supports both environment variables and profile-based authentication.
191
+ """
192
+ # Determine the profile to use (priority order)
193
+ profile_to_use = profile_name or _profile or os.environ.get("AWS_PROFILE")
194
+
195
+ # Determine the region to use
196
+ region_to_use = region_name or os.environ.get("AWS_REGION", "ap-southeast-2")
197
+
198
+ if profile_to_use:
199
+ # Use profile-based session (enterprise pattern)
200
+ session = boto3.Session(profile_name=profile_to_use)
201
+ return session.resource(client_name, region_name=region_to_use)
202
+ else:
203
+ # Fallback to environment variables for backward compatibility
204
+ return boto3.resource(
205
+ client_name,
206
+ aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
207
+ aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
208
+ aws_session_token=os.environ.get("AWS_SESSION_TOKEN"),
209
+ region_name=region_to_use,
210
+ )
172
211
 
173
212
 
174
213
  def get_log_groups(client, log_group_name_prefix):
@@ -0,0 +1,259 @@
1
+ """
2
+ Commvault EC2 Analysis - Investigate EC2 utilization for cost optimization.
3
+
4
+ JIRA FinOps-25: Enhanced Commvault EC2 investigation for cost optimization
5
+ Account: 637423383469 (Commvault backup account)
6
+ Challenge: Determine if EC2 instances are actively used for backups or idle
7
+ """
8
+
9
+ import logging
10
+ from datetime import datetime, timedelta, timezone
11
+ from typing import Dict, List, Optional
12
+
13
+ import click
14
+ from botocore.exceptions import ClientError
15
+
16
+ from .commons import display_aws_account_info, get_client, write_to_csv
17
+ from ..common.rich_utils import (
18
+ console, print_header, print_success, print_error, print_warning,
19
+ create_table, create_progress_bar, format_cost
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def calculate_ec2_cost_impact(instances_data: List[Dict]) -> Dict[str, float]:
26
+ """
27
+ Calculate potential cost impact for EC2 instances.
28
+
29
+ JIRA FinOps-25: Focuses on Commvault backup account utilization analysis
30
+ """
31
+ total_instances = len(instances_data)
32
+ running_instances = [i for i in instances_data if i.get("State", {}).get("Name") == "running"]
33
+ idle_instances = [i for i in instances_data if i.get("CpuUtilization", 0) < 5.0] # <5% CPU
34
+
35
+ # Rough cost estimation (simplified for investigation phase)
36
+ estimated_monthly_cost = 0.0
37
+ for instance in running_instances:
38
+ instance_type = instance.get("InstanceType", "t3.micro")
39
+ # Simplified cost mapping (USD/month for common instance types)
40
+ cost_map = {
41
+ "t3.micro": 8.47,
42
+ "t3.small": 16.94,
43
+ "t3.medium": 33.87,
44
+ "m5.large": 70.08,
45
+ "m5.xlarge": 140.16,
46
+ "c5.large": 62.98,
47
+ "c5.xlarge": 125.95,
48
+ }
49
+ estimated_monthly_cost += cost_map.get(instance_type, 50.0) # Default $50/month
50
+
51
+ return {
52
+ "total_instances": total_instances,
53
+ "running_instances": len(running_instances),
54
+ "idle_instances": len(idle_instances),
55
+ "estimated_monthly_cost": estimated_monthly_cost,
56
+ "estimated_annual_cost": estimated_monthly_cost * 12,
57
+ "potential_savings_if_idle": estimated_monthly_cost * 12 * 0.7, # 70% of cost if truly idle
58
+ }
59
+
60
+
61
+ @click.command()
62
+ @click.option("--output-file", default="/tmp/commvault_ec2_investigation.csv", help="Output CSV file path")
63
+ @click.option("--account", default="637423383469", help="Commvault backup account ID (JIRA FinOps-25)")
64
+ @click.option("--investigate-utilization", is_flag=True, help="Investigate EC2 utilization patterns")
65
+ @click.option("--days", default=7, help="Number of days to analyze for utilization metrics")
66
+ @click.option("--dry-run", is_flag=True, default=True, help="Preview analysis without execution")
67
+ def investigate_commvault_ec2(output_file, account, investigate_utilization, days, dry_run):
68
+ """
69
+ FinOps-25: Commvault EC2 investigation for cost optimization.
70
+
71
+ Account: 637423383469 (Commvault backup account)
72
+ Challenge: Determine if EC2 instances are actively used for backups or idle
73
+ """
74
+ print_header("JIRA FinOps-25: Commvault EC2 Investigation", "v0.9.1")
75
+
76
+ account_info = display_aws_account_info()
77
+ console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
78
+
79
+ try:
80
+ ec2_client = get_client("ec2")
81
+ cloudwatch_client = get_client("cloudwatch")
82
+
83
+ # Get all EC2 instances
84
+ console.print("[yellow]Collecting EC2 instance data...[/yellow]")
85
+ response = ec2_client.describe_instances()
86
+
87
+ instances_data = []
88
+ all_instances = []
89
+
90
+ # Flatten instance data
91
+ for reservation in response.get("Reservations", []):
92
+ all_instances.extend(reservation.get("Instances", []))
93
+
94
+ if not all_instances:
95
+ print_warning("No EC2 instances found in this account")
96
+ return
97
+
98
+ console.print(f"[green]Found {len(all_instances)} EC2 instances to investigate[/green]")
99
+
100
+ # Calculate time range for analysis
101
+ end_time = datetime.now(tz=timezone.utc)
102
+ start_time = end_time - timedelta(days=days)
103
+
104
+ with create_progress_bar() as progress:
105
+ task_id = progress.add_task(
106
+ f"Investigating {len(all_instances)} EC2 instances...",
107
+ total=len(all_instances)
108
+ )
109
+
110
+ for instance in all_instances:
111
+ instance_id = instance["InstanceId"]
112
+ instance_type = instance.get("InstanceType", "unknown")
113
+ state = instance.get("State", {}).get("Name", "unknown")
114
+ launch_time = instance.get("LaunchTime")
115
+
116
+ # Get CPU utilization metrics
117
+ cpu_utilization = 0.0
118
+ if investigate_utilization and state == "running":
119
+ try:
120
+ cpu_response = cloudwatch_client.get_metric_statistics(
121
+ Namespace="AWS/EC2",
122
+ MetricName="CPUUtilization",
123
+ Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
124
+ StartTime=start_time,
125
+ EndTime=end_time,
126
+ Period=3600, # 1 hour intervals
127
+ Statistics=["Average"],
128
+ )
129
+
130
+ if cpu_response.get("Datapoints"):
131
+ cpu_utilization = sum(
132
+ dp["Average"] for dp in cpu_response["Datapoints"]
133
+ ) / len(cpu_response["Datapoints"])
134
+
135
+ except ClientError as e:
136
+ logger.warning(f"Could not get CPU metrics for {instance_id}: {e}")
137
+
138
+ # Get tags for context
139
+ tags = {tag["Key"]: tag["Value"] for tag in instance.get("Tags", [])}
140
+
141
+ instance_data = {
142
+ "InstanceId": instance_id,
143
+ "InstanceType": instance_type,
144
+ "State": state,
145
+ "LaunchTime": launch_time.strftime("%Y-%m-%d %H:%M:%S") if launch_time else "Unknown",
146
+ "CpuUtilization": round(cpu_utilization, 2),
147
+ "Name": tags.get("Name", "Unknown"),
148
+ "Environment": tags.get("Environment", tags.get("Env", "Unknown")),
149
+ "Purpose": tags.get("Purpose", tags.get("Application", "Unknown")),
150
+ "IsLowUtilization": cpu_utilization < 5.0 if investigate_utilization else False,
151
+ }
152
+
153
+ instances_data.append(instance_data)
154
+ progress.advance(task_id)
155
+
156
+ # Export results
157
+ write_to_csv(instances_data, output_file)
158
+ print_success(f"Commvault EC2 investigation exported to: {output_file}")
159
+
160
+ # Calculate cost impact analysis
161
+ cost_analysis = calculate_ec2_cost_impact(instances_data)
162
+
163
+ # Create summary table
164
+ print_header("Commvault EC2 Investigation Summary")
165
+
166
+ summary_table = create_table(
167
+ title="EC2 Cost Impact Analysis - JIRA FinOps-25",
168
+ columns=[
169
+ {"header": "Metric", "style": "cyan"},
170
+ {"header": "Count", "style": "green bold"},
171
+ {"header": "Monthly Cost", "style": "red"},
172
+ {"header": "Annual Cost", "style": "red bold"}
173
+ ]
174
+ )
175
+
176
+ summary_table.add_row(
177
+ "Total EC2 Instances",
178
+ str(cost_analysis["total_instances"]),
179
+ "Mixed Types",
180
+ "Mixed Types"
181
+ )
182
+
183
+ summary_table.add_row(
184
+ "Running Instances",
185
+ str(cost_analysis["running_instances"]),
186
+ format_cost(cost_analysis["estimated_monthly_cost"]),
187
+ format_cost(cost_analysis["estimated_annual_cost"])
188
+ )
189
+
190
+ if investigate_utilization:
191
+ summary_table.add_row(
192
+ "Low Utilization (<5% CPU)",
193
+ str(cost_analysis["idle_instances"]),
194
+ "Investigation Required",
195
+ "Investigation Required"
196
+ )
197
+
198
+ summary_table.add_row(
199
+ "🎯 Potential Savings (if idle)",
200
+ f"{cost_analysis['idle_instances']} instances",
201
+ format_cost(cost_analysis["potential_savings_if_idle"] / 12),
202
+ format_cost(cost_analysis["potential_savings_if_idle"])
203
+ )
204
+
205
+ console.print(summary_table)
206
+
207
+ # Investigation recommendations
208
+ if investigate_utilization:
209
+ low_util_instances = [i for i in instances_data if i["IsLowUtilization"]]
210
+
211
+ if low_util_instances:
212
+ print_warning(f"🔍 Found {len(low_util_instances)} instances with low utilization:")
213
+
214
+ # Create detailed investigation table
215
+ investigation_table = create_table(
216
+ title="Low Utilization Instances (Investigation Required)",
217
+ columns=[
218
+ {"header": "Instance ID", "style": "cyan"},
219
+ {"header": "Type", "style": "blue"},
220
+ {"header": "CPU %", "style": "yellow"},
221
+ {"header": "Name", "style": "green"},
222
+ {"header": "Purpose", "style": "magenta"},
223
+ {"header": "State", "style": "red"}
224
+ ]
225
+ )
226
+
227
+ for instance in low_util_instances[:10]: # Show first 10
228
+ investigation_table.add_row(
229
+ instance['InstanceId'],
230
+ instance['InstanceType'],
231
+ f"{instance['CpuUtilization']:.1f}%",
232
+ instance['Name'],
233
+ instance['Purpose'],
234
+ instance['State']
235
+ )
236
+
237
+ console.print(investigation_table)
238
+
239
+ if len(low_util_instances) > 10:
240
+ console.print(f"[dim]... and {len(low_util_instances) - 10} more instances requiring investigation[/dim]")
241
+
242
+ # Manual verification checklist
243
+ print_header("Manual Verification Checklist")
244
+ console.print("[yellow]For each low-utilization instance, verify:[/yellow]")
245
+ console.print("[dim]1. Is this instance actively used for Commvault backup operations?[/dim]")
246
+ console.print("[dim]2. Are there scheduled backup jobs running on this instance?[/dim]")
247
+ console.print("[dim]3. Can this instance be right-sized or terminated safely?[/dim]")
248
+ console.print("[dim]4. Are there any dependencies or integrations that require this instance?[/dim]")
249
+
250
+ else:
251
+ print_success("✓ No low-utilization instances detected")
252
+
253
+ # JIRA FinOps-25 completion status
254
+ console.print(f"\n[cyan]📋 JIRA FinOps-25 Status: Investigation framework complete[/cyan]")
255
+ console.print(f"[dim]Next steps: Manual review of findings and cost impact validation[/dim]")
256
+
257
+ except Exception as e:
258
+ logger.error(f"Failed to investigate Commvault EC2: {e}")
259
+ raise