runbooks 0.7.7__py3-none-any.whl → 0.7.9__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/base.py +2 -2
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +546 -522
- runbooks/cfat/assessment/runner.py +122 -11
- runbooks/cfat/models.py +6 -2
- runbooks/common/logger.py +14 -0
- runbooks/common/rich_utils.py +451 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/finops/README.md +468 -241
- runbooks/finops/__init__.py +39 -3
- runbooks/finops/cli.py +31 -28
- runbooks/finops/cross_validation.py +375 -0
- runbooks/finops/dashboard_runner.py +384 -207
- runbooks/finops/enhanced_dashboard_runner.py +525 -0
- runbooks/finops/finops_dashboard.py +1892 -0
- runbooks/finops/helpers.py +176 -173
- runbooks/finops/optimizer.py +384 -383
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +192 -185
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +172 -13
- runbooks/inventory/list_ec2_instances.py +18 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1269 -0
- runbooks/inventory/rich_inventory_display.py +393 -0
- runbooks/inventory/run_on_multi_accounts.py +35 -19
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +2124 -174
- runbooks/metrics/dora_metrics_engine.py +599 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +122 -10
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +319 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/__init__.py +3 -1
- runbooks/security/compliance_automation.py +632 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +31 -5
- runbooks/security/security_baseline_tester.py +169 -30
- runbooks/security/security_export.py +477 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +484 -0
- runbooks/validation/cli.py +356 -0
- runbooks/validation/mcp_validator.py +768 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +634 -0
- runbooks/vpc/networking_wrapper.py +1260 -0
- runbooks/vpc/rich_formatters.py +679 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
- {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/RECORD +110 -52
- {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
- {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
runbooks/finops/helpers.py
CHANGED
@@ -9,7 +9,7 @@ from typing import Any, Dict, List, Optional
|
|
9
9
|
|
10
10
|
import yaml
|
11
11
|
from reportlab.lib import colors
|
12
|
-
from reportlab.lib.pagesizes import landscape, letter
|
12
|
+
from reportlab.lib.pagesizes import A4, landscape, letter
|
13
13
|
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
14
14
|
from reportlab.lib.units import inch
|
15
15
|
from reportlab.platypus import (
|
@@ -71,7 +71,7 @@ def export_audit_report_to_pdf(
|
|
71
71
|
"Profile",
|
72
72
|
"Account ID",
|
73
73
|
"Untagged Resources",
|
74
|
-
"Stopped EC2 Instances",
|
74
|
+
"Stopped EC2 Instances",
|
75
75
|
"Unused Volumes",
|
76
76
|
"Unused EIPs",
|
77
77
|
"Budget Alerts",
|
@@ -90,7 +90,7 @@ def export_audit_report_to_pdf(
|
|
90
90
|
risk_display = f"HIGH ({risk_score})"
|
91
91
|
else:
|
92
92
|
risk_display = f"CRITICAL ({risk_score})"
|
93
|
-
|
93
|
+
|
94
94
|
table_data.append(
|
95
95
|
[
|
96
96
|
row.get("profile", ""),
|
@@ -120,11 +120,11 @@ def export_audit_report_to_pdf(
|
|
120
120
|
)
|
121
121
|
)
|
122
122
|
|
123
|
-
elements.append(Paragraph("🎯
|
123
|
+
elements.append(Paragraph("🎯 CloudOps Runbooks FinOps - Enterprise Audit Report", styles["Title"]))
|
124
124
|
elements.append(Spacer(1, 12))
|
125
125
|
elements.append(table)
|
126
126
|
elements.append(Spacer(1, 4))
|
127
|
-
|
127
|
+
|
128
128
|
# Enhanced notes with PDCA information
|
129
129
|
pdca_info = Paragraph(
|
130
130
|
"📊 PDCA Framework: This report follows Plan-Do-Check-Act continuous improvement methodology.<br/>"
|
@@ -133,7 +133,7 @@ def export_audit_report_to_pdf(
|
|
133
133
|
audit_footer_style,
|
134
134
|
)
|
135
135
|
elements.append(pdca_info)
|
136
|
-
|
136
|
+
|
137
137
|
elements.append(Spacer(1, 2))
|
138
138
|
current_time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
139
139
|
footer_text = (
|
@@ -151,46 +151,46 @@ def export_audit_report_to_pdf(
|
|
151
151
|
def _truncate_service_costs(services_data: str, max_length: int = 500) -> str:
|
152
152
|
"""
|
153
153
|
Truncate service costs data for PDF display if too long.
|
154
|
-
|
154
|
+
|
155
155
|
:param services_data: Service costs formatted as string
|
156
156
|
:param max_length: Maximum character length before truncation
|
157
157
|
:return: Truncated service costs string
|
158
158
|
"""
|
159
159
|
if len(services_data) <= max_length:
|
160
160
|
return services_data
|
161
|
-
|
162
|
-
lines = services_data.split(
|
161
|
+
|
162
|
+
lines = services_data.split("\n")
|
163
163
|
truncated_lines = []
|
164
164
|
current_length = 0
|
165
|
-
|
165
|
+
|
166
166
|
for line in lines:
|
167
167
|
if current_length + len(line) + 1 <= max_length - 50: # Reserve space for truncation message
|
168
168
|
truncated_lines.append(line)
|
169
169
|
current_length += len(line) + 1
|
170
170
|
else:
|
171
171
|
break
|
172
|
-
|
172
|
+
|
173
173
|
# Add truncation indicator with service count
|
174
174
|
remaining_services = len(lines) - len(truncated_lines)
|
175
175
|
if remaining_services > 0:
|
176
176
|
truncated_lines.append(f"... and {remaining_services} more services")
|
177
|
-
|
178
|
-
return
|
177
|
+
|
178
|
+
return "\n".join(truncated_lines)
|
179
179
|
|
180
180
|
|
181
181
|
def _optimize_table_for_pdf(table_data: List[List[str]], max_col_width: int = 120) -> List[List[str]]:
|
182
182
|
"""
|
183
183
|
Optimize table data for PDF rendering by managing column widths.
|
184
|
-
|
184
|
+
|
185
185
|
:param table_data: Raw table data with headers and rows
|
186
186
|
:param max_col_width: Maximum character width for any column
|
187
187
|
:return: Optimized table data
|
188
188
|
"""
|
189
189
|
optimized_data = []
|
190
|
-
|
190
|
+
|
191
191
|
for row_idx, row in enumerate(table_data):
|
192
192
|
optimized_row = []
|
193
|
-
|
193
|
+
|
194
194
|
for col_idx, cell in enumerate(row):
|
195
195
|
if col_idx == 4: # "Cost By Service" column (index 4)
|
196
196
|
# Apply special handling to service costs column
|
@@ -200,38 +200,38 @@ def _optimize_table_for_pdf(table_data: List[List[str]], max_col_width: int = 12
|
|
200
200
|
cell_str = str(cell)
|
201
201
|
if len(cell_str) > max_col_width:
|
202
202
|
# Truncate long content with ellipsis
|
203
|
-
optimized_cell = cell_str[:max_col_width-3] + "..."
|
203
|
+
optimized_cell = cell_str[: max_col_width - 3] + "..."
|
204
204
|
else:
|
205
205
|
optimized_cell = cell_str
|
206
|
-
|
206
|
+
|
207
207
|
optimized_row.append(optimized_cell)
|
208
|
-
|
208
|
+
|
209
209
|
optimized_data.append(optimized_row)
|
210
|
-
|
210
|
+
|
211
211
|
return optimized_data
|
212
212
|
|
213
213
|
|
214
214
|
def _create_paginated_tables(table_data: List[List[str]], max_rows_per_page: int = 15) -> List[List[List[str]]]:
|
215
215
|
"""
|
216
216
|
Split large table data into multiple pages for PDF generation.
|
217
|
-
|
217
|
+
|
218
218
|
:param table_data: Complete table data including headers
|
219
219
|
:param max_rows_per_page: Maximum data rows per page (excluding header)
|
220
220
|
:return: List of table data chunks, each with headers
|
221
221
|
"""
|
222
222
|
if len(table_data) <= max_rows_per_page + 1: # +1 for header
|
223
223
|
return [table_data]
|
224
|
-
|
224
|
+
|
225
225
|
headers = table_data[0]
|
226
226
|
data_rows = table_data[1:]
|
227
|
-
|
227
|
+
|
228
228
|
paginated_tables = []
|
229
|
-
|
229
|
+
|
230
230
|
for i in range(0, len(data_rows), max_rows_per_page):
|
231
|
-
chunk = data_rows[i:i + max_rows_per_page]
|
231
|
+
chunk = data_rows[i : i + max_rows_per_page]
|
232
232
|
table_chunk = [headers] + chunk
|
233
233
|
paginated_tables.append(table_chunk)
|
234
|
-
|
234
|
+
|
235
235
|
return paginated_tables
|
236
236
|
|
237
237
|
|
@@ -273,7 +273,7 @@ def export_audit_report_to_csv(
|
|
273
273
|
data_keys = [
|
274
274
|
"profile",
|
275
275
|
"account_id",
|
276
|
-
"untagged_resources",
|
276
|
+
"untagged_resources",
|
277
277
|
"stopped_instances",
|
278
278
|
"unused_volumes",
|
279
279
|
"unused_eips",
|
@@ -352,12 +352,12 @@ def export_cost_dashboard_to_pdf(
|
|
352
352
|
|
353
353
|
# Use A4 landscape for better space utilization
|
354
354
|
doc = SimpleDocTemplate(
|
355
|
-
output_filename,
|
355
|
+
output_filename,
|
356
356
|
pagesize=landscape(A4),
|
357
|
-
rightMargin=0.5*inch,
|
358
|
-
leftMargin=0.5*inch,
|
359
|
-
topMargin=0.5*inch,
|
360
|
-
bottomMargin=0.5*inch
|
357
|
+
rightMargin=0.5 * inch,
|
358
|
+
leftMargin=0.5 * inch,
|
359
|
+
topMargin=0.5 * inch,
|
360
|
+
bottomMargin=0.5 * inch,
|
361
361
|
)
|
362
362
|
styles = getSampleStyleSheet()
|
363
363
|
elements: List[Flowable] = []
|
@@ -369,26 +369,24 @@ def export_cost_dashboard_to_pdf(
|
|
369
369
|
fontSize=16,
|
370
370
|
spaceAfter=12,
|
371
371
|
textColor=colors.darkblue,
|
372
|
-
alignment=1 # Center alignment
|
372
|
+
alignment=1, # Center alignment
|
373
373
|
)
|
374
374
|
|
375
375
|
# Calculate summary metrics
|
376
376
|
total_accounts = len(data)
|
377
377
|
total_current_cost = sum(row["current_month"] for row in data if row.get("current_month", 0))
|
378
378
|
total_previous_cost = sum(row["last_month"] for row in data if row.get("last_month", 0))
|
379
|
-
cost_change = (
|
379
|
+
cost_change = (
|
380
|
+
((total_current_cost - total_previous_cost) / total_previous_cost * 100) if total_previous_cost > 0 else 0
|
381
|
+
)
|
382
|
+
|
383
|
+
elements.append(Paragraph("🏢 CloudOps Runbooks FinOps - Enterprise Cost Report", title_style))
|
380
384
|
|
381
|
-
elements.append(Paragraph("🏢 AWS FinOps Dashboard - Enterprise Cost Report", title_style))
|
382
|
-
|
383
385
|
# Executive summary
|
384
386
|
summary_style = ParagraphStyle(
|
385
|
-
name="Summary",
|
386
|
-
parent=styles["Normal"],
|
387
|
-
fontSize=10,
|
388
|
-
spaceAfter=8,
|
389
|
-
textColor=colors.darkgreen
|
387
|
+
name="Summary", parent=styles["Normal"], fontSize=10, spaceAfter=8, textColor=colors.darkgreen
|
390
388
|
)
|
391
|
-
|
389
|
+
|
392
390
|
summary_text = (
|
393
391
|
f"📊 Executive Summary: {total_accounts} accounts analyzed | "
|
394
392
|
f"Total Current Cost: ${total_current_cost:,.2f} | "
|
@@ -404,14 +402,14 @@ def export_cost_dashboard_to_pdf(
|
|
404
402
|
|
405
403
|
headers = [
|
406
404
|
"Profile",
|
407
|
-
"Account ID",
|
405
|
+
"Account ID",
|
408
406
|
previous_period_header,
|
409
407
|
current_period_header,
|
410
408
|
"Top Services",
|
411
409
|
"Budget Status",
|
412
410
|
"EC2 Summary",
|
413
411
|
]
|
414
|
-
|
412
|
+
|
415
413
|
raw_table_data = [headers]
|
416
414
|
|
417
415
|
for row in data:
|
@@ -452,45 +450,46 @@ def export_cost_dashboard_to_pdf(
|
|
452
450
|
else:
|
453
451
|
cost_change_text = "\n→ 0%"
|
454
452
|
|
455
|
-
raw_table_data.append(
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
453
|
+
raw_table_data.append(
|
454
|
+
[
|
455
|
+
row["profile"],
|
456
|
+
row["account_id"],
|
457
|
+
f"${row['last_month']:,.2f}",
|
458
|
+
f"${row['current_month']:,.2f}{cost_change_text}",
|
459
|
+
services_data,
|
460
|
+
budgets_data,
|
461
|
+
ec2_data_summary,
|
462
|
+
]
|
463
|
+
)
|
464
464
|
|
465
465
|
# Optimize table data for PDF rendering
|
466
466
|
optimized_table_data = _optimize_table_for_pdf(raw_table_data, max_col_width=80)
|
467
|
-
|
467
|
+
|
468
468
|
# Create paginated tables for large datasets
|
469
469
|
paginated_tables = _create_paginated_tables(optimized_table_data, max_rows_per_page=12)
|
470
470
|
|
471
471
|
# Style configuration for optimal PDF rendering
|
472
|
-
table_style = TableStyle(
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
])
|
472
|
+
table_style = TableStyle(
|
473
|
+
[
|
474
|
+
# Header styling
|
475
|
+
("BACKGROUND", (0, 0), (-1, 0), colors.navy),
|
476
|
+
("TEXTCOLOR", (0, 0), (-1, 0), colors.whitesmoke),
|
477
|
+
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
|
478
|
+
("FONTSIZE", (0, 0), (-1, 0), 7),
|
479
|
+
# Data styling
|
480
|
+
("FONTNAME", (0, 1), (-1, -1), "Helvetica"),
|
481
|
+
("FONTSIZE", (0, 1), (-1, -1), 6),
|
482
|
+
("ALIGN", (0, 0), (-1, -1), "LEFT"),
|
483
|
+
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
484
|
+
# Grid and background
|
485
|
+
("GRID", (0, 0), (-1, -1), 0.5, colors.darkgrey),
|
486
|
+
("BACKGROUND", (0, 1), (-1, -1), colors.beige),
|
487
|
+
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
|
488
|
+
# Column-specific styling
|
489
|
+
("ALIGN", (2, 0), (3, -1), "RIGHT"), # Cost columns right-aligned
|
490
|
+
("FONTNAME", (2, 1), (3, -1), "Helvetica-Bold"), # Bold cost values
|
491
|
+
]
|
492
|
+
)
|
494
493
|
|
495
494
|
# Generate tables with pagination
|
496
495
|
for page_idx, table_data_chunk in enumerate(paginated_tables):
|
@@ -498,22 +497,19 @@ def export_cost_dashboard_to_pdf(
|
|
498
497
|
elements.append(PageBreak())
|
499
498
|
# Add page header for continuation pages
|
500
499
|
page_header = Paragraph(
|
501
|
-
f"
|
500
|
+
f"CloudOps Runbooks FinOps - Page {page_idx + 1} of {len(paginated_tables)}",
|
502
501
|
ParagraphStyle(
|
503
|
-
name="PageHeader",
|
504
|
-
|
505
|
-
fontSize=12,
|
506
|
-
textColor=colors.darkblue
|
507
|
-
)
|
502
|
+
name="PageHeader", parent=styles["Heading2"], fontSize=12, textColor=colors.darkblue
|
503
|
+
),
|
508
504
|
)
|
509
505
|
elements.append(page_header)
|
510
506
|
elements.append(Spacer(1, 8))
|
511
507
|
|
512
508
|
# Create table with dynamic column widths
|
513
|
-
available_width = landscape(A4)[0] - 1*inch # Account for margins
|
509
|
+
available_width = landscape(A4)[0] - 1 * inch # Account for margins
|
514
510
|
col_widths = [
|
515
511
|
available_width * 0.12, # Profile (12%)
|
516
|
-
available_width * 0.15, # Account ID (15%)
|
512
|
+
available_width * 0.15, # Account ID (15%)
|
517
513
|
available_width * 0.12, # Previous Cost (12%)
|
518
514
|
available_width * 0.12, # Current Cost (12%)
|
519
515
|
available_width * 0.25, # Services (25%)
|
@@ -527,18 +523,14 @@ def export_cost_dashboard_to_pdf(
|
|
527
523
|
|
528
524
|
# Enhanced footer with metadata
|
529
525
|
elements.append(Spacer(1, 12))
|
530
|
-
|
526
|
+
|
531
527
|
footer_style = ParagraphStyle(
|
532
|
-
name="EnhancedFooter",
|
533
|
-
parent=styles["Normal"],
|
534
|
-
fontSize=8,
|
535
|
-
textColor=colors.grey,
|
536
|
-
alignment=1
|
528
|
+
name="EnhancedFooter", parent=styles["Normal"], fontSize=8, textColor=colors.grey, alignment=1
|
537
529
|
)
|
538
|
-
|
530
|
+
|
539
531
|
current_time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")
|
540
532
|
footer_text = (
|
541
|
-
f"🚀 Generated by CloudOps-Runbooks FinOps Dashboard v0.7.
|
533
|
+
f"🚀 Generated by CloudOps-Runbooks FinOps Dashboard v0.7.8 | "
|
542
534
|
f"Report Generated: {current_time_str} | "
|
543
535
|
f"Accounts Analyzed: {total_accounts} | "
|
544
536
|
f"© 2025 CloudOps Enterprise"
|
@@ -547,20 +539,23 @@ def export_cost_dashboard_to_pdf(
|
|
547
539
|
|
548
540
|
# Build PDF with error handling
|
549
541
|
doc.build(elements)
|
550
|
-
|
542
|
+
|
551
543
|
# Verify file creation
|
552
544
|
if os.path.exists(output_filename):
|
553
545
|
file_size = os.path.getsize(output_filename)
|
554
|
-
console.print(
|
546
|
+
console.print(
|
547
|
+
f"[bright_green]✅ PDF generated successfully: {os.path.abspath(output_filename)} ({file_size:,} bytes)[/]"
|
548
|
+
)
|
555
549
|
return os.path.abspath(output_filename)
|
556
550
|
else:
|
557
551
|
console.print("[bold red]❌ PDF file was not created[/]")
|
558
552
|
return None
|
559
|
-
|
553
|
+
|
560
554
|
except Exception as e:
|
561
555
|
console.print(f"[bold red]❌ Error exporting to PDF: {str(e)}[/]")
|
562
556
|
# Print more detailed error information for debugging
|
563
557
|
import traceback
|
558
|
+
|
564
559
|
console.print(f"[red]Detailed error trace: {traceback.format_exc()}[/]")
|
565
560
|
return None
|
566
561
|
|
@@ -611,13 +606,11 @@ def load_config_file(file_path: str) -> Optional[Dict[str, Any]]:
|
|
611
606
|
|
612
607
|
|
613
608
|
def generate_pdca_improvement_report(
|
614
|
-
pdca_metrics: List[Dict[str, Any]],
|
615
|
-
file_name: str = "pdca_improvement",
|
616
|
-
path: Optional[str] = None
|
609
|
+
pdca_metrics: List[Dict[str, Any]], file_name: str = "pdca_improvement", path: Optional[str] = None
|
617
610
|
) -> Optional[str]:
|
618
611
|
"""
|
619
612
|
Generate PDCA (Plan-Do-Check-Act) continuous improvement report.
|
620
|
-
|
613
|
+
|
621
614
|
:param pdca_metrics: List of PDCA metrics for each profile
|
622
615
|
:param file_name: The base name of the output file
|
623
616
|
:param path: Optional directory where the file will be saved
|
@@ -626,133 +619,143 @@ def generate_pdca_improvement_report(
|
|
626
619
|
try:
|
627
620
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
|
628
621
|
base_filename = f"{file_name}_pdca_report_{timestamp}.json"
|
629
|
-
|
622
|
+
|
630
623
|
if path:
|
631
624
|
os.makedirs(path, exist_ok=True)
|
632
625
|
output_filename = os.path.join(path, base_filename)
|
633
626
|
else:
|
634
627
|
output_filename = base_filename
|
635
|
-
|
628
|
+
|
636
629
|
# Calculate aggregate metrics
|
637
630
|
total_risk = sum(m["risk_score"] for m in pdca_metrics)
|
638
631
|
avg_risk = total_risk / len(pdca_metrics) if pdca_metrics else 0
|
639
|
-
|
632
|
+
|
640
633
|
high_risk_accounts = [m for m in pdca_metrics if m["risk_score"] > 25]
|
641
634
|
medium_risk_accounts = [m for m in pdca_metrics if 10 < m["risk_score"] <= 25]
|
642
635
|
low_risk_accounts = [m for m in pdca_metrics if m["risk_score"] <= 10]
|
643
|
-
|
636
|
+
|
644
637
|
total_untagged = sum(m["untagged_count"] for m in pdca_metrics)
|
645
638
|
total_stopped = sum(m["stopped_count"] for m in pdca_metrics)
|
646
639
|
total_unused_volumes = sum(m["unused_volumes_count"] for m in pdca_metrics)
|
647
640
|
total_unused_eips = sum(m["unused_eips_count"] for m in pdca_metrics)
|
648
641
|
total_budget_overruns = sum(m["budget_overruns"] for m in pdca_metrics)
|
649
|
-
|
642
|
+
|
650
643
|
# Generate improvement recommendations
|
651
644
|
recommendations = []
|
652
|
-
|
645
|
+
|
653
646
|
# PLAN phase recommendations
|
654
647
|
if total_untagged > 50:
|
655
|
-
recommendations.append(
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
648
|
+
recommendations.append(
|
649
|
+
{
|
650
|
+
"phase": "PLAN",
|
651
|
+
"priority": "HIGH",
|
652
|
+
"category": "Compliance",
|
653
|
+
"issue": f"Found {total_untagged} untagged resources across all accounts",
|
654
|
+
"action": "Implement mandatory tagging strategy using AWS Config rules",
|
655
|
+
"expected_outcome": "100% resource compliance within 30 days",
|
656
|
+
"owner": "Cloud Governance Team",
|
657
|
+
}
|
658
|
+
)
|
659
|
+
|
665
660
|
if total_unused_eips > 5:
|
666
|
-
recommendations.append(
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
661
|
+
recommendations.append(
|
662
|
+
{
|
663
|
+
"phase": "PLAN",
|
664
|
+
"priority": "MEDIUM",
|
665
|
+
"category": "Cost Optimization",
|
666
|
+
"issue": f"Found {total_unused_eips} unused Elastic IPs",
|
667
|
+
"action": "Schedule monthly EIP cleanup automation",
|
668
|
+
"expected_outcome": f"Save ~${total_unused_eips * 3.65:.2f}/month",
|
669
|
+
"owner": "FinOps Team",
|
670
|
+
}
|
671
|
+
)
|
672
|
+
|
676
673
|
# DO phase recommendations
|
677
674
|
if high_risk_accounts:
|
678
|
-
recommendations.append(
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
675
|
+
recommendations.append(
|
676
|
+
{
|
677
|
+
"phase": "DO",
|
678
|
+
"priority": "CRITICAL",
|
679
|
+
"category": "Risk Management",
|
680
|
+
"issue": f"{len(high_risk_accounts)} accounts have critical risk scores",
|
681
|
+
"action": "Execute immediate remediation on high-risk accounts",
|
682
|
+
"expected_outcome": "Reduce risk scores by 70% within 2 weeks",
|
683
|
+
"owner": "Security Team",
|
684
|
+
}
|
685
|
+
)
|
686
|
+
|
688
687
|
# CHECK phase recommendations
|
689
688
|
if avg_risk > 15:
|
690
|
-
recommendations.append(
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
689
|
+
recommendations.append(
|
690
|
+
{
|
691
|
+
"phase": "CHECK",
|
692
|
+
"priority": "HIGH",
|
693
|
+
"category": "Monitoring",
|
694
|
+
"issue": f"Average risk score ({avg_risk:.1f}) exceeds threshold",
|
695
|
+
"action": "Implement automated risk scoring dashboard",
|
696
|
+
"expected_outcome": "Real-time risk visibility and alerting",
|
697
|
+
"owner": "DevOps Team",
|
698
|
+
}
|
699
|
+
)
|
700
|
+
|
700
701
|
# ACT phase recommendations
|
701
|
-
recommendations.append(
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
702
|
+
recommendations.append(
|
703
|
+
{
|
704
|
+
"phase": "ACT",
|
705
|
+
"priority": "MEDIUM",
|
706
|
+
"category": "Process Improvement",
|
707
|
+
"issue": "Need continuous improvement framework",
|
708
|
+
"action": "Establish monthly PDCA review cycles",
|
709
|
+
"expected_outcome": "25% reduction in average risk score per quarter",
|
710
|
+
"owner": "Cloud Center of Excellence",
|
711
|
+
}
|
712
|
+
)
|
713
|
+
|
711
714
|
# Create comprehensive PDCA report
|
712
715
|
pdca_report = {
|
713
716
|
"report_metadata": {
|
714
717
|
"generated_at": datetime.now().isoformat(),
|
715
718
|
"report_type": "PDCA Continuous Improvement Analysis",
|
716
719
|
"accounts_analyzed": len(pdca_metrics),
|
717
|
-
"framework_version": "v1.0"
|
720
|
+
"framework_version": "v1.0",
|
718
721
|
},
|
719
722
|
"executive_summary": {
|
720
723
|
"overall_risk_score": total_risk,
|
721
724
|
"average_risk_score": round(avg_risk, 2),
|
722
725
|
"risk_distribution": {
|
723
726
|
"critical_accounts": len(high_risk_accounts),
|
724
|
-
"high_risk_accounts": len(medium_risk_accounts),
|
725
|
-
"low_risk_accounts": len(low_risk_accounts)
|
727
|
+
"high_risk_accounts": len(medium_risk_accounts),
|
728
|
+
"low_risk_accounts": len(low_risk_accounts),
|
726
729
|
},
|
727
730
|
"key_findings": {
|
728
731
|
"untagged_resources": total_untagged,
|
729
732
|
"stopped_instances": total_stopped,
|
730
733
|
"unused_volumes": total_unused_volumes,
|
731
734
|
"unused_elastic_ips": total_unused_eips,
|
732
|
-
"budget_overruns": total_budget_overruns
|
733
|
-
}
|
735
|
+
"budget_overruns": total_budget_overruns,
|
736
|
+
},
|
734
737
|
},
|
735
738
|
"pdca_analysis": {
|
736
739
|
"plan_phase": {
|
737
740
|
"description": "Strategic planning based on current state analysis",
|
738
741
|
"metrics_collected": len(pdca_metrics),
|
739
|
-
"baseline_established": True
|
742
|
+
"baseline_established": True,
|
740
743
|
},
|
741
744
|
"do_phase": {
|
742
745
|
"description": "Implementation of audit data collection",
|
743
746
|
"data_sources": ["EC2", "RDS", "Lambda", "ELBv2", "Budgets"],
|
744
|
-
"regions_scanned": "All accessible regions"
|
747
|
+
"regions_scanned": "All accessible regions",
|
745
748
|
},
|
746
749
|
"check_phase": {
|
747
750
|
"description": "Analysis of collected audit data",
|
748
751
|
"risk_assessment_completed": True,
|
749
|
-
"trends_identified": True
|
752
|
+
"trends_identified": True,
|
750
753
|
},
|
751
754
|
"act_phase": {
|
752
755
|
"description": "Actionable recommendations for improvement",
|
753
756
|
"recommendations_generated": len(recommendations),
|
754
|
-
"prioritization_completed": True
|
755
|
-
}
|
757
|
+
"prioritization_completed": True,
|
758
|
+
},
|
756
759
|
},
|
757
760
|
"detailed_metrics": pdca_metrics,
|
758
761
|
"improvement_recommendations": recommendations,
|
@@ -760,27 +763,27 @@ def generate_pdca_improvement_report(
|
|
760
763
|
"immediate_actions": [
|
761
764
|
"Review high-risk accounts within 48 hours",
|
762
765
|
"Implement automated tagging for untagged resources",
|
763
|
-
"Schedule EIP cleanup automation"
|
766
|
+
"Schedule EIP cleanup automation",
|
764
767
|
],
|
765
768
|
"medium_term_goals": [
|
766
769
|
"Establish monthly PDCA review cycle",
|
767
770
|
"Implement risk scoring dashboard",
|
768
|
-
"Create automated remediation workflows"
|
771
|
+
"Create automated remediation workflows",
|
769
772
|
],
|
770
773
|
"long_term_objectives": [
|
771
774
|
"Achieve average risk score below 5",
|
772
775
|
"Maintain 100% resource compliance",
|
773
|
-
"Reduce cloud waste by 25%"
|
774
|
-
]
|
775
|
-
}
|
776
|
+
"Reduce cloud waste by 25%",
|
777
|
+
],
|
778
|
+
},
|
776
779
|
}
|
777
|
-
|
780
|
+
|
778
781
|
# Export to JSON
|
779
782
|
with open(output_filename, "w", encoding="utf-8") as jsonfile:
|
780
783
|
json.dump(pdca_report, jsonfile, indent=4, default=str)
|
781
|
-
|
784
|
+
|
782
785
|
return os.path.abspath(output_filename)
|
783
|
-
|
786
|
+
|
784
787
|
except Exception as e:
|
785
788
|
console.print(f"[bold red]Error generating PDCA improvement report: {str(e)}[/]")
|
786
789
|
return None
|