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.
Files changed (110) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/base.py +2 -2
  3. runbooks/cfat/__init__.py +8 -4
  4. runbooks/cfat/assessment/collectors.py +171 -14
  5. runbooks/cfat/assessment/compliance.py +546 -522
  6. runbooks/cfat/assessment/runner.py +122 -11
  7. runbooks/cfat/models.py +6 -2
  8. runbooks/common/logger.py +14 -0
  9. runbooks/common/rich_utils.py +451 -0
  10. runbooks/enterprise/__init__.py +68 -0
  11. runbooks/enterprise/error_handling.py +411 -0
  12. runbooks/enterprise/logging.py +439 -0
  13. runbooks/enterprise/multi_tenant.py +583 -0
  14. runbooks/finops/README.md +468 -241
  15. runbooks/finops/__init__.py +39 -3
  16. runbooks/finops/cli.py +31 -28
  17. runbooks/finops/cross_validation.py +375 -0
  18. runbooks/finops/dashboard_runner.py +384 -207
  19. runbooks/finops/enhanced_dashboard_runner.py +525 -0
  20. runbooks/finops/finops_dashboard.py +1892 -0
  21. runbooks/finops/helpers.py +176 -173
  22. runbooks/finops/optimizer.py +384 -383
  23. runbooks/finops/tests/__init__.py +19 -0
  24. runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
  25. runbooks/finops/tests/run_comprehensive_tests.py +421 -0
  26. runbooks/finops/tests/run_tests.py +305 -0
  27. runbooks/finops/tests/test_finops_dashboard.py +705 -0
  28. runbooks/finops/tests/test_integration.py +477 -0
  29. runbooks/finops/tests/test_performance.py +380 -0
  30. runbooks/finops/tests/test_performance_benchmarks.py +500 -0
  31. runbooks/finops/tests/test_reference_images_validation.py +867 -0
  32. runbooks/finops/tests/test_single_account_features.py +715 -0
  33. runbooks/finops/tests/validate_test_suite.py +220 -0
  34. runbooks/finops/types.py +1 -1
  35. runbooks/hitl/enhanced_workflow_engine.py +725 -0
  36. runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
  37. runbooks/inventory/collectors/aws_comprehensive.py +192 -185
  38. runbooks/inventory/collectors/enterprise_scale.py +281 -0
  39. runbooks/inventory/core/collector.py +172 -13
  40. runbooks/inventory/list_ec2_instances.py +18 -20
  41. runbooks/inventory/list_ssm_parameters.py +31 -3
  42. runbooks/inventory/organizations_discovery.py +1269 -0
  43. runbooks/inventory/rich_inventory_display.py +393 -0
  44. runbooks/inventory/run_on_multi_accounts.py +35 -19
  45. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  46. runbooks/inventory/runbooks.security.run_script.log +0 -0
  47. runbooks/inventory/vpc_flow_analyzer.py +1030 -0
  48. runbooks/main.py +2124 -174
  49. runbooks/metrics/dora_metrics_engine.py +599 -0
  50. runbooks/operate/__init__.py +2 -2
  51. runbooks/operate/base.py +122 -10
  52. runbooks/operate/deployment_framework.py +1032 -0
  53. runbooks/operate/deployment_validator.py +853 -0
  54. runbooks/operate/dynamodb_operations.py +10 -6
  55. runbooks/operate/ec2_operations.py +319 -11
  56. runbooks/operate/executive_dashboard.py +779 -0
  57. runbooks/operate/mcp_integration.py +750 -0
  58. runbooks/operate/nat_gateway_operations.py +1120 -0
  59. runbooks/operate/networking_cost_heatmap.py +685 -0
  60. runbooks/operate/privatelink_operations.py +940 -0
  61. runbooks/operate/s3_operations.py +10 -6
  62. runbooks/operate/vpc_endpoints.py +644 -0
  63. runbooks/operate/vpc_operations.py +1038 -0
  64. runbooks/remediation/__init__.py +2 -2
  65. runbooks/remediation/acm_remediation.py +1 -1
  66. runbooks/remediation/base.py +1 -1
  67. runbooks/remediation/cloudtrail_remediation.py +1 -1
  68. runbooks/remediation/cognito_remediation.py +1 -1
  69. runbooks/remediation/dynamodb_remediation.py +1 -1
  70. runbooks/remediation/ec2_remediation.py +1 -1
  71. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
  72. runbooks/remediation/kms_enable_key_rotation.py +1 -1
  73. runbooks/remediation/kms_remediation.py +1 -1
  74. runbooks/remediation/lambda_remediation.py +1 -1
  75. runbooks/remediation/multi_account.py +1 -1
  76. runbooks/remediation/rds_remediation.py +1 -1
  77. runbooks/remediation/s3_block_public_access.py +1 -1
  78. runbooks/remediation/s3_enable_access_logging.py +1 -1
  79. runbooks/remediation/s3_encryption.py +1 -1
  80. runbooks/remediation/s3_remediation.py +1 -1
  81. runbooks/remediation/vpc_remediation.py +475 -0
  82. runbooks/security/__init__.py +3 -1
  83. runbooks/security/compliance_automation.py +632 -0
  84. runbooks/security/report_generator.py +10 -0
  85. runbooks/security/run_script.py +31 -5
  86. runbooks/security/security_baseline_tester.py +169 -30
  87. runbooks/security/security_export.py +477 -0
  88. runbooks/validation/__init__.py +10 -0
  89. runbooks/validation/benchmark.py +484 -0
  90. runbooks/validation/cli.py +356 -0
  91. runbooks/validation/mcp_validator.py +768 -0
  92. runbooks/vpc/__init__.py +38 -0
  93. runbooks/vpc/config.py +212 -0
  94. runbooks/vpc/cost_engine.py +347 -0
  95. runbooks/vpc/heatmap_engine.py +605 -0
  96. runbooks/vpc/manager_interface.py +634 -0
  97. runbooks/vpc/networking_wrapper.py +1260 -0
  98. runbooks/vpc/rich_formatters.py +679 -0
  99. runbooks/vpc/tests/__init__.py +5 -0
  100. runbooks/vpc/tests/conftest.py +356 -0
  101. runbooks/vpc/tests/test_cli_integration.py +530 -0
  102. runbooks/vpc/tests/test_config.py +458 -0
  103. runbooks/vpc/tests/test_cost_engine.py +479 -0
  104. runbooks/vpc/tests/test_networking_wrapper.py +512 -0
  105. {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
  106. {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/RECORD +110 -52
  107. {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
  108. {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
  109. {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
  110. {runbooks-0.7.7.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -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, A4
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("🎯 AWS FinOps Dashboard - PDCA Enhanced Audit Report", styles["Title"]))
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('\n')
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 '\n'.join(truncated_lines)
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 = ((total_current_cost - total_previous_cost) / total_previous_cost * 100) if total_previous_cost > 0 else 0
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
- row["profile"],
457
- row["account_id"],
458
- f"${row['last_month']:,.2f}",
459
- f"${row['current_month']:,.2f}{cost_change_text}",
460
- services_data,
461
- budgets_data,
462
- ec2_data_summary,
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
- # Header styling
474
- ("BACKGROUND", (0, 0), (-1, 0), colors.navy),
475
- ("TEXTCOLOR", (0, 0), (-1, 0), colors.whitesmoke),
476
- ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
477
- ("FONTSIZE", (0, 0), (-1, 0), 7),
478
-
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
-
485
- # Grid and background
486
- ("GRID", (0, 0), (-1, -1), 0.5, colors.darkgrey),
487
- ("BACKGROUND", (0, 1), (-1, -1), colors.beige),
488
- ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
489
-
490
- # Column-specific styling
491
- ("ALIGN", (2, 0), (3, -1), "RIGHT"), # Cost columns right-aligned
492
- ("FONTNAME", (2, 1), (3, -1), "Helvetica-Bold"), # Bold cost values
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"AWS FinOps Dashboard - Page {page_idx + 1} of {len(paginated_tables)}",
500
+ f"CloudOps Runbooks FinOps - Page {page_idx + 1} of {len(paginated_tables)}",
502
501
  ParagraphStyle(
503
- name="PageHeader",
504
- parent=styles["Heading2"],
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.3 | "
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(f"[bright_green]✅ PDF generated successfully: {os.path.abspath(output_filename)} ({file_size:,} bytes)[/]")
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
- "phase": "PLAN",
657
- "priority": "HIGH",
658
- "category": "Compliance",
659
- "issue": f"Found {total_untagged} untagged resources across all accounts",
660
- "action": "Implement mandatory tagging strategy using AWS Config rules",
661
- "expected_outcome": "100% resource compliance within 30 days",
662
- "owner": "Cloud Governance Team"
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
- "phase": "PLAN",
668
- "priority": "MEDIUM",
669
- "category": "Cost Optimization",
670
- "issue": f"Found {total_unused_eips} unused Elastic IPs",
671
- "action": "Schedule monthly EIP cleanup automation",
672
- "expected_outcome": f"Save ~${total_unused_eips * 3.65:.2f}/month",
673
- "owner": "FinOps Team"
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
- "phase": "DO",
680
- "priority": "CRITICAL",
681
- "category": "Risk Management",
682
- "issue": f"{len(high_risk_accounts)} accounts have critical risk scores",
683
- "action": "Execute immediate remediation on high-risk accounts",
684
- "expected_outcome": "Reduce risk scores by 70% within 2 weeks",
685
- "owner": "Security Team"
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
- "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
-
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
- "phase": "ACT",
703
- "priority": "MEDIUM",
704
- "category": "Process Improvement",
705
- "issue": "Need continuous improvement framework",
706
- "action": "Establish monthly PDCA review cycles",
707
- "expected_outcome": "25% reduction in average risk score per quarter",
708
- "owner": "Cloud Center of Excellence"
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