runbooks 0.9.8__py3-none-any.whl → 1.0.0__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 +1 -1
- runbooks/cfat/cloud_foundations_assessment.py +626 -0
- runbooks/cloudops/cost_optimizer.py +95 -33
- runbooks/common/aws_pricing.py +388 -0
- runbooks/common/aws_pricing_api.py +205 -0
- runbooks/common/aws_utils.py +2 -2
- runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
- runbooks/common/cross_account_manager.py +606 -0
- runbooks/common/enhanced_exception_handler.py +4 -0
- runbooks/common/env_utils.py +96 -0
- runbooks/common/mcp_integration.py +49 -2
- runbooks/common/organizations_client.py +579 -0
- runbooks/common/profile_utils.py +96 -2
- runbooks/common/rich_utils.py +3 -0
- runbooks/finops/cost_optimizer.py +2 -1
- runbooks/finops/elastic_ip_optimizer.py +13 -9
- runbooks/finops/embedded_mcp_validator.py +31 -0
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/markdown_exporter.py +441 -0
- runbooks/finops/nat_gateway_optimizer.py +57 -20
- runbooks/finops/optimizer.py +2 -0
- runbooks/finops/single_dashboard.py +2 -2
- runbooks/finops/vpc_cleanup_exporter.py +330 -0
- runbooks/finops/vpc_cleanup_optimizer.py +895 -40
- runbooks/inventory/__init__.py +10 -1
- runbooks/inventory/cloud_foundations_integration.py +409 -0
- runbooks/inventory/core/collector.py +1148 -88
- runbooks/inventory/discovery.md +389 -0
- runbooks/inventory/drift_detection_cli.py +327 -0
- runbooks/inventory/inventory_mcp_cli.py +171 -0
- runbooks/inventory/inventory_modules.py +4 -7
- runbooks/inventory/mcp_inventory_validator.py +2149 -0
- runbooks/inventory/mcp_vpc_validator.py +23 -6
- runbooks/inventory/organizations_discovery.py +91 -1
- runbooks/inventory/rich_inventory_display.py +129 -1
- runbooks/inventory/unified_validation_engine.py +1292 -0
- runbooks/inventory/verify_ec2_security_groups.py +3 -1
- runbooks/inventory/vpc_analyzer.py +825 -7
- runbooks/inventory/vpc_flow_analyzer.py +36 -42
- runbooks/main.py +969 -42
- runbooks/monitoring/performance_monitor.py +11 -7
- runbooks/operate/dynamodb_operations.py +6 -5
- runbooks/operate/ec2_operations.py +3 -2
- runbooks/operate/networking_cost_heatmap.py +4 -3
- runbooks/operate/s3_operations.py +13 -12
- runbooks/operate/vpc_operations.py +50 -2
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commvault_ec2_analysis.py +6 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
- runbooks/remediation/rds_snapshot_list.py +5 -3
- runbooks/validation/__init__.py +21 -1
- runbooks/validation/comprehensive_2way_validator.py +1996 -0
- runbooks/validation/mcp_validator.py +904 -94
- runbooks/validation/terraform_citations_validator.py +363 -0
- runbooks/validation/terraform_drift_detector.py +1098 -0
- runbooks/vpc/cleanup_wrapper.py +231 -10
- runbooks/vpc/config.py +310 -62
- runbooks/vpc/cross_account_session.py +308 -0
- runbooks/vpc/heatmap_engine.py +96 -29
- runbooks/vpc/manager_interface.py +9 -9
- runbooks/vpc/mcp_no_eni_validator.py +1551 -0
- runbooks/vpc/networking_wrapper.py +14 -8
- runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/vpc/runbooks.security.report_generator.log +0 -0
- runbooks/vpc/runbooks.security.run_script.log +0 -0
- runbooks/vpc/runbooks.security.security_export.log +0 -0
- runbooks/vpc/tests/test_cost_engine.py +1 -1
- runbooks/vpc/unified_scenarios.py +3269 -0
- runbooks/vpc/vpc_cleanup_integration.py +516 -82
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/METADATA +94 -52
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/RECORD +75 -51
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/WHEEL +0 -0
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,330 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
VPC Cleanup Exporter Module - Enterprise VPC Cleanup Result Export
|
4
|
+
|
5
|
+
This module provides export functionality for VPC cleanup analysis results,
|
6
|
+
leveraging the existing markdown_exporter infrastructure with VPC-specific formatting.
|
7
|
+
|
8
|
+
Author: CloudOps Runbooks Team
|
9
|
+
Version: 0.9.9
|
10
|
+
"""
|
11
|
+
|
12
|
+
import csv
|
13
|
+
import json
|
14
|
+
import os
|
15
|
+
from datetime import datetime
|
16
|
+
from typing import Any, Dict, List
|
17
|
+
|
18
|
+
from .markdown_exporter import MarkdownExporter
|
19
|
+
|
20
|
+
|
21
|
+
def _format_tags_for_display(tags_dict: Dict[str, str]) -> str:
|
22
|
+
"""Format tags for display with priority order, emphasizing ownership tags."""
|
23
|
+
if not tags_dict:
|
24
|
+
return "No tags"
|
25
|
+
|
26
|
+
# Enhanced priority keys with focus on ownership and approvals
|
27
|
+
priority_keys = ['Name', 'Owner', 'BusinessOwner', 'TechnicalOwner', 'Team', 'Contact',
|
28
|
+
'Environment', 'Project', 'CostCenter', 'CreatedBy', 'ManagedBy']
|
29
|
+
relevant_tags = []
|
30
|
+
|
31
|
+
for key in priority_keys:
|
32
|
+
if key in tags_dict and tags_dict[key]:
|
33
|
+
relevant_tags.append(f"{key}:{tags_dict[key]}")
|
34
|
+
|
35
|
+
# Add CloudFormation/Terraform tags for IaC detection
|
36
|
+
iac_keys = ['aws:cloudformation:stack-name', 'terraform:module', 'cdktf:stack', 'pulumi:project']
|
37
|
+
for key in iac_keys:
|
38
|
+
if key in tags_dict and tags_dict[key] and len(relevant_tags) < 6:
|
39
|
+
relevant_tags.append(f"IaC:{tags_dict[key]}")
|
40
|
+
|
41
|
+
# Add other important tags
|
42
|
+
for key, value in tags_dict.items():
|
43
|
+
if key not in priority_keys + iac_keys and value and len(relevant_tags) < 5:
|
44
|
+
relevant_tags.append(f"{key}:{value}")
|
45
|
+
|
46
|
+
return "; ".join(relevant_tags) if relevant_tags else f"({len(tags_dict)} tags)"
|
47
|
+
|
48
|
+
|
49
|
+
def export_vpc_cleanup_results(vpc_result: Any, export_formats: List[str], output_dir: str = "./") -> Dict[str, str]:
|
50
|
+
"""
|
51
|
+
Export VPC cleanup results in multiple formats.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
vpc_result: VPC cleanup analysis result object
|
55
|
+
export_formats: List of formats to export (markdown, csv, json, pdf)
|
56
|
+
output_dir: Directory to save exported files
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
Dict mapping format to exported filename
|
60
|
+
"""
|
61
|
+
results = {}
|
62
|
+
|
63
|
+
# Extract VPC candidates from result - use correct attribute name
|
64
|
+
vpc_candidates = getattr(vpc_result, 'cleanup_candidates', [])
|
65
|
+
if not vpc_candidates:
|
66
|
+
# Fallback to other possible attribute names
|
67
|
+
vpc_candidates = getattr(vpc_result, 'vpc_candidates', [])
|
68
|
+
|
69
|
+
if 'markdown' in export_formats:
|
70
|
+
try:
|
71
|
+
exporter = MarkdownExporter()
|
72
|
+
markdown_filename = exporter.export_vpc_analysis_to_file(
|
73
|
+
vpc_candidates,
|
74
|
+
filename="vpc-cleanup-candidates.md",
|
75
|
+
output_dir=output_dir
|
76
|
+
)
|
77
|
+
results['markdown'] = markdown_filename
|
78
|
+
except Exception as e:
|
79
|
+
print(f"Warning: Markdown export failed: {e}")
|
80
|
+
results['markdown'] = None
|
81
|
+
|
82
|
+
# Real implementations for other formats
|
83
|
+
if 'csv' in export_formats:
|
84
|
+
try:
|
85
|
+
csv_filename = _export_vpc_candidates_csv(vpc_candidates, output_dir)
|
86
|
+
results['csv'] = csv_filename
|
87
|
+
except Exception as e:
|
88
|
+
print(f"Warning: CSV export failed: {e}")
|
89
|
+
results['csv'] = None
|
90
|
+
|
91
|
+
if 'json' in export_formats:
|
92
|
+
try:
|
93
|
+
json_filename = _export_vpc_candidates_json(vpc_candidates, output_dir)
|
94
|
+
results['json'] = json_filename
|
95
|
+
except Exception as e:
|
96
|
+
print(f"Warning: JSON export failed: {e}")
|
97
|
+
results['json'] = None
|
98
|
+
|
99
|
+
if 'pdf' in export_formats:
|
100
|
+
try:
|
101
|
+
pdf_filename = _export_vpc_candidates_pdf(vpc_candidates, output_dir)
|
102
|
+
results['pdf'] = pdf_filename
|
103
|
+
except Exception as e:
|
104
|
+
print(f"Warning: PDF export failed: {e}")
|
105
|
+
results['pdf'] = None
|
106
|
+
|
107
|
+
return results
|
108
|
+
|
109
|
+
|
110
|
+
def _export_vpc_candidates_csv(vpc_candidates: List[Any], output_dir: str) -> str:
|
111
|
+
"""Export VPC candidates to CSV format with all 15 columns."""
|
112
|
+
filename = os.path.join(output_dir, "vpc-cleanup-candidates.csv")
|
113
|
+
|
114
|
+
# 15-column headers for comprehensive VPC analysis
|
115
|
+
headers = [
|
116
|
+
"Account_ID", "VPC_ID", "VPC_Name", "CIDR_Block", "Overlapping",
|
117
|
+
"Is_Default", "ENI_Count", "Tags", "Flow_Logs", "TGW/Peering",
|
118
|
+
"LBs_Present", "IaC", "Timeline", "Decision", "Owners/Approvals", "Notes"
|
119
|
+
]
|
120
|
+
|
121
|
+
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
|
122
|
+
writer = csv.writer(csvfile)
|
123
|
+
writer.writerow(headers)
|
124
|
+
|
125
|
+
for candidate in vpc_candidates:
|
126
|
+
# Extract data with enhanced tag and owner handling
|
127
|
+
tags_dict = getattr(candidate, 'tags', {}) or {}
|
128
|
+
|
129
|
+
# Use enhanced tag formatting function
|
130
|
+
tags_str = _format_tags_for_display(tags_dict)
|
131
|
+
|
132
|
+
load_balancers = getattr(candidate, 'load_balancers', []) or []
|
133
|
+
lbs_present = "Yes" if load_balancers else "No"
|
134
|
+
|
135
|
+
# Enhanced owner extraction from multiple sources
|
136
|
+
owners = getattr(candidate, 'owners_approvals', []) or []
|
137
|
+
|
138
|
+
# Extract owners from tags with enhanced logic
|
139
|
+
if not owners and tags_dict:
|
140
|
+
owner_keys = ['Owner', 'BusinessOwner', 'TechnicalOwner', 'Team', 'Contact', 'CreatedBy', 'ManagedBy']
|
141
|
+
for key in owner_keys:
|
142
|
+
if key in tags_dict and tags_dict[key]:
|
143
|
+
value = tags_dict[key]
|
144
|
+
if 'business' in key.lower() or 'manager' in value.lower():
|
145
|
+
owners.append(f"{value} (Business)")
|
146
|
+
elif 'technical' in key.lower() or 'engineer' in value.lower():
|
147
|
+
owners.append(f"{value} (Technical)")
|
148
|
+
elif 'team' in key.lower():
|
149
|
+
owners.append(f"{value} (Team)")
|
150
|
+
else:
|
151
|
+
owners.append(f"{value} ({key})")
|
152
|
+
|
153
|
+
# For default VPCs, add system indicator
|
154
|
+
is_default = getattr(candidate, 'is_default', False)
|
155
|
+
if is_default and not owners:
|
156
|
+
owners.append("System Default")
|
157
|
+
|
158
|
+
if owners:
|
159
|
+
owners_str = "; ".join(owners)
|
160
|
+
else:
|
161
|
+
# Enhanced fallback for CSV
|
162
|
+
if getattr(candidate, 'is_default', False):
|
163
|
+
owners_str = "System Default VPC"
|
164
|
+
elif getattr(candidate, 'iac_detected', False):
|
165
|
+
owners_str = "IaC Managed"
|
166
|
+
else:
|
167
|
+
owners_str = "No owner tags found"
|
168
|
+
|
169
|
+
row = [
|
170
|
+
getattr(candidate, 'account_id', 'Unknown'),
|
171
|
+
getattr(candidate, 'vpc_id', ''),
|
172
|
+
getattr(candidate, 'vpc_name', 'Unnamed'),
|
173
|
+
getattr(candidate, 'cidr_block', ''),
|
174
|
+
"No", # Overlapping analysis would need CIDR comparison
|
175
|
+
"Yes" if getattr(candidate, 'is_default', False) else "No",
|
176
|
+
getattr(candidate, 'dependency_analysis', {}).eni_count if hasattr(candidate, 'dependency_analysis') else 0,
|
177
|
+
tags_str,
|
178
|
+
"Yes" if getattr(candidate, 'flow_logs_enabled', False) else "No",
|
179
|
+
"No", # TGW/Peering analysis placeholder
|
180
|
+
lbs_present,
|
181
|
+
"Yes" if getattr(candidate, 'iac_detected', False) else "No",
|
182
|
+
"Unknown", # Timeline analysis placeholder
|
183
|
+
getattr(candidate, 'cleanup_recommendation', 'unknown'),
|
184
|
+
owners_str,
|
185
|
+
"Generated by CloudOps Runbooks VPC Module"
|
186
|
+
]
|
187
|
+
writer.writerow(row)
|
188
|
+
|
189
|
+
return filename
|
190
|
+
|
191
|
+
|
192
|
+
def _export_vpc_candidates_json(vpc_candidates: List[Any], output_dir: str) -> str:
|
193
|
+
"""Export VPC candidates to JSON format with full data structure."""
|
194
|
+
filename = os.path.join(output_dir, "vpc-cleanup-candidates.json")
|
195
|
+
|
196
|
+
# Convert candidates to serializable format
|
197
|
+
candidates_data = []
|
198
|
+
for candidate in vpc_candidates:
|
199
|
+
candidate_dict = {
|
200
|
+
"account_id": getattr(candidate, 'account_id', 'Unknown'),
|
201
|
+
"vpc_id": getattr(candidate, 'vpc_id', ''),
|
202
|
+
"vpc_name": getattr(candidate, 'vpc_name', 'Unnamed'),
|
203
|
+
"cidr_block": getattr(candidate, 'cidr_block', ''),
|
204
|
+
"region": getattr(candidate, 'region', 'unknown'),
|
205
|
+
"is_default": getattr(candidate, 'is_default', False),
|
206
|
+
"state": getattr(candidate, 'state', 'unknown'),
|
207
|
+
"tags": getattr(candidate, 'tags', {}) or {},
|
208
|
+
"tags_summary": _format_tags_for_display(getattr(candidate, 'tags', {}) or {}),
|
209
|
+
"flow_logs_enabled": getattr(candidate, 'flow_logs_enabled', False),
|
210
|
+
"load_balancers": getattr(candidate, 'load_balancers', []) or [],
|
211
|
+
"iac_detected": getattr(candidate, 'iac_detected', False),
|
212
|
+
"owners_approvals": getattr(candidate, 'owners_approvals', []) or [],
|
213
|
+
"cleanup_bucket": getattr(candidate, 'cleanup_bucket', 'unknown'),
|
214
|
+
"cleanup_recommendation": getattr(candidate, 'cleanup_recommendation', 'unknown'),
|
215
|
+
"risk_assessment": getattr(candidate, 'risk_assessment', 'unknown'),
|
216
|
+
"business_impact": getattr(candidate, 'business_impact', 'unknown')
|
217
|
+
}
|
218
|
+
|
219
|
+
# Add dependency analysis if available
|
220
|
+
if hasattr(candidate, 'dependency_analysis') and candidate.dependency_analysis:
|
221
|
+
dep_analysis = candidate.dependency_analysis
|
222
|
+
candidate_dict["dependency_analysis"] = {
|
223
|
+
"eni_count": getattr(dep_analysis, 'eni_count', 0),
|
224
|
+
"route_tables": getattr(dep_analysis, 'route_tables', []),
|
225
|
+
"security_groups": getattr(dep_analysis, 'security_groups', []),
|
226
|
+
"internet_gateways": getattr(dep_analysis, 'internet_gateways', []),
|
227
|
+
"nat_gateways": getattr(dep_analysis, 'nat_gateways', []),
|
228
|
+
"vpc_endpoints": getattr(dep_analysis, 'vpc_endpoints', []),
|
229
|
+
"peering_connections": getattr(dep_analysis, 'peering_connections', []),
|
230
|
+
"dependency_risk_level": getattr(dep_analysis, 'dependency_risk_level', 'unknown')
|
231
|
+
}
|
232
|
+
|
233
|
+
candidates_data.append(candidate_dict)
|
234
|
+
|
235
|
+
# Create export metadata
|
236
|
+
export_data = {
|
237
|
+
"metadata": {
|
238
|
+
"export_timestamp": datetime.now().isoformat(),
|
239
|
+
"total_candidates": len(candidates_data),
|
240
|
+
"generator": "CloudOps Runbooks VPC Module v0.9.9"
|
241
|
+
},
|
242
|
+
"vpc_candidates": candidates_data
|
243
|
+
}
|
244
|
+
|
245
|
+
with open(filename, 'w', encoding='utf-8') as jsonfile:
|
246
|
+
json.dump(export_data, jsonfile, indent=2, ensure_ascii=False)
|
247
|
+
|
248
|
+
return filename
|
249
|
+
|
250
|
+
|
251
|
+
def _export_vpc_candidates_pdf(vpc_candidates: List[Any], output_dir: str) -> str:
|
252
|
+
"""Export VPC candidates to PDF format for executive presentation."""
|
253
|
+
filename = os.path.join(output_dir, "vpc-cleanup-candidates.pdf")
|
254
|
+
|
255
|
+
try:
|
256
|
+
# Try to use reportlab for PDF generation
|
257
|
+
from reportlab.lib import colors
|
258
|
+
from reportlab.lib.pagesizes import letter, A4
|
259
|
+
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
|
260
|
+
from reportlab.lib.styles import getSampleStyleSheet
|
261
|
+
|
262
|
+
doc = SimpleDocTemplate(filename, pagesize=A4)
|
263
|
+
styles = getSampleStyleSheet()
|
264
|
+
story = []
|
265
|
+
|
266
|
+
# Title
|
267
|
+
title = Paragraph("VPC Cleanup Analysis Report", styles['Title'])
|
268
|
+
story.append(title)
|
269
|
+
story.append(Spacer(1, 20))
|
270
|
+
|
271
|
+
# Summary
|
272
|
+
summary_text = f"""
|
273
|
+
<b>Generated:</b> {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}<br/>
|
274
|
+
<b>Total VPC Candidates:</b> {len(vpc_candidates)}<br/>
|
275
|
+
<b>Analysis Source:</b> CloudOps Runbooks VPC Module v0.9.9
|
276
|
+
"""
|
277
|
+
summary = Paragraph(summary_text, styles['Normal'])
|
278
|
+
story.append(summary)
|
279
|
+
story.append(Spacer(1, 20))
|
280
|
+
|
281
|
+
# Create table data
|
282
|
+
table_data = [
|
283
|
+
["Account ID", "VPC ID", "VPC Name", "CIDR", "Default", "ENI Count", "Decision"]
|
284
|
+
]
|
285
|
+
|
286
|
+
for candidate in vpc_candidates:
|
287
|
+
row = [
|
288
|
+
str(getattr(candidate, 'account_id', 'Unknown'))[:15], # Truncate for PDF width
|
289
|
+
str(getattr(candidate, 'vpc_id', ''))[:20],
|
290
|
+
str(getattr(candidate, 'vpc_name', 'Unnamed'))[:15],
|
291
|
+
str(getattr(candidate, 'cidr_block', ''))[:15],
|
292
|
+
"Yes" if getattr(candidate, 'is_default', False) else "No",
|
293
|
+
str(getattr(candidate, 'dependency_analysis', {}).eni_count if hasattr(candidate, 'dependency_analysis') else 0),
|
294
|
+
str(getattr(candidate, 'cleanup_recommendation', 'unknown'))[:10]
|
295
|
+
]
|
296
|
+
table_data.append(row)
|
297
|
+
|
298
|
+
# Create table
|
299
|
+
table = Table(table_data)
|
300
|
+
table.setStyle(TableStyle([
|
301
|
+
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
302
|
+
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
303
|
+
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
304
|
+
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
305
|
+
('FONTSIZE', (0, 0), (-1, 0), 10),
|
306
|
+
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
307
|
+
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
308
|
+
('FONTSIZE', (0, 1), (-1, -1), 8),
|
309
|
+
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
310
|
+
]))
|
311
|
+
|
312
|
+
story.append(table)
|
313
|
+
doc.build(story)
|
314
|
+
|
315
|
+
except ImportError:
|
316
|
+
# Fallback: create a simple text-based PDF placeholder
|
317
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
318
|
+
f.write("VPC Cleanup Analysis Report (PDF)\n")
|
319
|
+
f.write("=" * 40 + "\n\n")
|
320
|
+
f.write(f"Generated: {datetime.now().isoformat()}\n")
|
321
|
+
f.write(f"Total VPC Candidates: {len(vpc_candidates)}\n\n")
|
322
|
+
|
323
|
+
for i, candidate in enumerate(vpc_candidates, 1):
|
324
|
+
f.write(f"{i}. VPC {getattr(candidate, 'vpc_id', 'Unknown')}\n")
|
325
|
+
f.write(f" Account: {getattr(candidate, 'account_id', 'Unknown')}\n")
|
326
|
+
f.write(f" CIDR: {getattr(candidate, 'cidr_block', 'Unknown')}\n")
|
327
|
+
f.write(f" ENI Count: {getattr(candidate, 'dependency_analysis', {}).eni_count if hasattr(candidate, 'dependency_analysis') else 0}\n")
|
328
|
+
f.write(f" Decision: {getattr(candidate, 'cleanup_recommendation', 'unknown')}\n\n")
|
329
|
+
|
330
|
+
return filename
|