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.
- runbooks/__init__.py +15 -6
- runbooks/cfat/__init__.py +3 -1
- runbooks/cloudops/__init__.py +3 -1
- runbooks/common/aws_utils.py +367 -0
- runbooks/common/enhanced_logging_example.py +239 -0
- runbooks/common/enhanced_logging_integration_example.py +257 -0
- runbooks/common/logging_integration_helper.py +344 -0
- runbooks/common/profile_utils.py +8 -6
- runbooks/common/rich_utils.py +347 -3
- runbooks/enterprise/logging.py +400 -38
- runbooks/finops/README.md +262 -406
- runbooks/finops/__init__.py +44 -1
- runbooks/finops/accuracy_cross_validator.py +12 -3
- runbooks/finops/business_cases.py +552 -0
- runbooks/finops/commvault_ec2_analysis.py +415 -0
- runbooks/finops/cost_processor.py +718 -42
- runbooks/finops/dashboard_router.py +44 -22
- runbooks/finops/dashboard_runner.py +302 -39
- runbooks/finops/embedded_mcp_validator.py +358 -48
- runbooks/finops/finops_scenarios.py +1122 -0
- runbooks/finops/helpers.py +182 -0
- runbooks/finops/multi_dashboard.py +30 -15
- runbooks/finops/scenarios.py +789 -0
- runbooks/finops/single_dashboard.py +386 -58
- runbooks/finops/types.py +29 -4
- runbooks/inventory/__init__.py +2 -1
- runbooks/main.py +522 -29
- runbooks/operate/__init__.py +3 -1
- runbooks/remediation/__init__.py +3 -1
- runbooks/remediation/commons.py +55 -16
- runbooks/remediation/commvault_ec2_analysis.py +259 -0
- runbooks/remediation/rds_snapshot_list.py +267 -102
- runbooks/remediation/workspaces_list.py +182 -31
- runbooks/security/__init__.py +3 -1
- runbooks/sre/__init__.py +2 -1
- runbooks/utils/__init__.py +81 -6
- runbooks/utils/version_validator.py +241 -0
- runbooks/vpc/__init__.py +2 -1
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/METADATA +98 -60
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/RECORD +44 -39
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/entry_points.txt +1 -0
- runbooks/inventory/cloudtrail.md +0 -727
- runbooks/inventory/discovery.md +0 -81
- runbooks/remediation/CLAUDE.md +0 -100
- runbooks/remediation/DOME9.md +0 -218
- runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +0 -506
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/WHEEL +0 -0
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/top_level.txt +0 -0
runbooks/operate/__init__.py
CHANGED
@@ -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
|
runbooks/remediation/__init__.py
CHANGED
@@ -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
|
runbooks/remediation/commons.py
CHANGED
@@ -151,24 +151,63 @@ def get_method_details(client, rest_api_id, resource_id, http_method):
|
|
151
151
|
raise
|
152
152
|
|
153
153
|
|
154
|
-
|
155
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|