runbooks 1.1.3__py3-none-any.whl → 1.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/WEIGHT_CONFIG_README.md +1 -1
  8. runbooks/cfat/assessment/compliance.py +8 -8
  9. runbooks/cfat/assessment/runner.py +1 -0
  10. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  11. runbooks/cfat/models.py +6 -2
  12. runbooks/cfat/tests/__init__.py +6 -1
  13. runbooks/cli/__init__.py +13 -0
  14. runbooks/cli/commands/cfat.py +274 -0
  15. runbooks/cli/commands/finops.py +1164 -0
  16. runbooks/cli/commands/inventory.py +379 -0
  17. runbooks/cli/commands/operate.py +239 -0
  18. runbooks/cli/commands/security.py +248 -0
  19. runbooks/cli/commands/validation.py +825 -0
  20. runbooks/cli/commands/vpc.py +310 -0
  21. runbooks/cli/registry.py +107 -0
  22. runbooks/cloudops/__init__.py +23 -30
  23. runbooks/cloudops/base.py +96 -107
  24. runbooks/cloudops/cost_optimizer.py +549 -547
  25. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  26. runbooks/cloudops/interfaces.py +226 -227
  27. runbooks/cloudops/lifecycle_manager.py +5 -4
  28. runbooks/cloudops/mcp_cost_validation.py +252 -235
  29. runbooks/cloudops/models.py +78 -53
  30. runbooks/cloudops/monitoring_automation.py +5 -4
  31. runbooks/cloudops/notebook_framework.py +179 -215
  32. runbooks/cloudops/security_enforcer.py +125 -159
  33. runbooks/common/accuracy_validator.py +11 -0
  34. runbooks/common/aws_pricing.py +349 -326
  35. runbooks/common/aws_pricing_api.py +211 -212
  36. runbooks/common/aws_profile_manager.py +341 -0
  37. runbooks/common/aws_utils.py +75 -80
  38. runbooks/common/business_logic.py +127 -105
  39. runbooks/common/cli_decorators.py +36 -60
  40. runbooks/common/comprehensive_cost_explorer_integration.py +456 -464
  41. runbooks/common/cross_account_manager.py +198 -205
  42. runbooks/common/date_utils.py +27 -39
  43. runbooks/common/decorators.py +235 -0
  44. runbooks/common/dry_run_examples.py +173 -208
  45. runbooks/common/dry_run_framework.py +157 -155
  46. runbooks/common/enhanced_exception_handler.py +15 -4
  47. runbooks/common/enhanced_logging_example.py +50 -64
  48. runbooks/common/enhanced_logging_integration_example.py +65 -37
  49. runbooks/common/env_utils.py +16 -16
  50. runbooks/common/error_handling.py +40 -38
  51. runbooks/common/lazy_loader.py +41 -23
  52. runbooks/common/logging_integration_helper.py +79 -86
  53. runbooks/common/mcp_cost_explorer_integration.py +478 -495
  54. runbooks/common/mcp_integration.py +63 -74
  55. runbooks/common/memory_optimization.py +140 -118
  56. runbooks/common/module_cli_base.py +37 -58
  57. runbooks/common/organizations_client.py +176 -194
  58. runbooks/common/patterns.py +204 -0
  59. runbooks/common/performance_monitoring.py +67 -71
  60. runbooks/common/performance_optimization_engine.py +283 -274
  61. runbooks/common/profile_utils.py +248 -39
  62. runbooks/common/rich_utils.py +643 -92
  63. runbooks/common/sre_performance_suite.py +177 -186
  64. runbooks/enterprise/__init__.py +1 -1
  65. runbooks/enterprise/logging.py +144 -106
  66. runbooks/enterprise/security.py +187 -204
  67. runbooks/enterprise/validation.py +43 -56
  68. runbooks/finops/__init__.py +29 -33
  69. runbooks/finops/account_resolver.py +1 -1
  70. runbooks/finops/advanced_optimization_engine.py +980 -0
  71. runbooks/finops/automation_core.py +268 -231
  72. runbooks/finops/business_case_config.py +184 -179
  73. runbooks/finops/cli.py +660 -139
  74. runbooks/finops/commvault_ec2_analysis.py +157 -164
  75. runbooks/finops/compute_cost_optimizer.py +336 -320
  76. runbooks/finops/config.py +20 -20
  77. runbooks/finops/cost_optimizer.py +488 -622
  78. runbooks/finops/cost_processor.py +332 -214
  79. runbooks/finops/dashboard_runner.py +1006 -172
  80. runbooks/finops/ebs_cost_optimizer.py +991 -657
  81. runbooks/finops/elastic_ip_optimizer.py +317 -257
  82. runbooks/finops/enhanced_mcp_integration.py +340 -0
  83. runbooks/finops/enhanced_progress.py +40 -37
  84. runbooks/finops/enhanced_trend_visualization.py +3 -2
  85. runbooks/finops/enterprise_wrappers.py +230 -292
  86. runbooks/finops/executive_export.py +203 -160
  87. runbooks/finops/helpers.py +130 -288
  88. runbooks/finops/iam_guidance.py +1 -1
  89. runbooks/finops/infrastructure/__init__.py +80 -0
  90. runbooks/finops/infrastructure/commands.py +506 -0
  91. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  92. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  93. runbooks/finops/markdown_exporter.py +338 -175
  94. runbooks/finops/mcp_validator.py +1952 -0
  95. runbooks/finops/nat_gateway_optimizer.py +1513 -482
  96. runbooks/finops/network_cost_optimizer.py +657 -587
  97. runbooks/finops/notebook_utils.py +226 -188
  98. runbooks/finops/optimization_engine.py +1136 -0
  99. runbooks/finops/optimizer.py +25 -29
  100. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  101. runbooks/finops/reservation_optimizer.py +427 -363
  102. runbooks/finops/scenario_cli_integration.py +77 -78
  103. runbooks/finops/scenarios.py +1278 -439
  104. runbooks/finops/schemas.py +218 -182
  105. runbooks/finops/snapshot_manager.py +2289 -0
  106. runbooks/finops/tests/test_finops_dashboard.py +3 -3
  107. runbooks/finops/tests/test_reference_images_validation.py +2 -2
  108. runbooks/finops/tests/test_single_account_features.py +17 -17
  109. runbooks/finops/tests/validate_test_suite.py +1 -1
  110. runbooks/finops/types.py +3 -3
  111. runbooks/finops/validation_framework.py +263 -269
  112. runbooks/finops/vpc_cleanup_exporter.py +191 -146
  113. runbooks/finops/vpc_cleanup_optimizer.py +593 -575
  114. runbooks/finops/workspaces_analyzer.py +171 -182
  115. runbooks/hitl/enhanced_workflow_engine.py +1 -1
  116. runbooks/integration/__init__.py +89 -0
  117. runbooks/integration/mcp_integration.py +1920 -0
  118. runbooks/inventory/CLAUDE.md +816 -0
  119. runbooks/inventory/README.md +3 -3
  120. runbooks/inventory/Tests/common_test_data.py +30 -30
  121. runbooks/inventory/__init__.py +2 -2
  122. runbooks/inventory/cloud_foundations_integration.py +144 -149
  123. runbooks/inventory/collectors/aws_comprehensive.py +28 -11
  124. runbooks/inventory/collectors/aws_networking.py +111 -101
  125. runbooks/inventory/collectors/base.py +4 -0
  126. runbooks/inventory/core/collector.py +495 -313
  127. runbooks/inventory/discovery.md +2 -2
  128. runbooks/inventory/drift_detection_cli.py +69 -96
  129. runbooks/inventory/find_ec2_security_groups.py +1 -1
  130. runbooks/inventory/inventory_mcp_cli.py +48 -46
  131. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  132. runbooks/inventory/mcp_inventory_validator.py +549 -465
  133. runbooks/inventory/mcp_vpc_validator.py +359 -442
  134. runbooks/inventory/organizations_discovery.py +56 -52
  135. runbooks/inventory/rich_inventory_display.py +33 -32
  136. runbooks/inventory/unified_validation_engine.py +278 -251
  137. runbooks/inventory/vpc_analyzer.py +733 -696
  138. runbooks/inventory/vpc_architecture_validator.py +293 -348
  139. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  140. runbooks/inventory/vpc_flow_analyzer.py +3 -3
  141. runbooks/main.py +152 -9147
  142. runbooks/main_final.py +91 -60
  143. runbooks/main_minimal.py +22 -10
  144. runbooks/main_optimized.py +131 -100
  145. runbooks/main_ultra_minimal.py +7 -2
  146. runbooks/mcp/__init__.py +36 -0
  147. runbooks/mcp/integration.py +679 -0
  148. runbooks/metrics/dora_metrics_engine.py +2 -2
  149. runbooks/monitoring/performance_monitor.py +9 -4
  150. runbooks/operate/dynamodb_operations.py +3 -1
  151. runbooks/operate/ec2_operations.py +145 -137
  152. runbooks/operate/iam_operations.py +146 -152
  153. runbooks/operate/mcp_integration.py +1 -1
  154. runbooks/operate/networking_cost_heatmap.py +33 -10
  155. runbooks/operate/privatelink_operations.py +1 -1
  156. runbooks/operate/rds_operations.py +223 -254
  157. runbooks/operate/s3_operations.py +107 -118
  158. runbooks/operate/vpc_endpoints.py +1 -1
  159. runbooks/operate/vpc_operations.py +648 -618
  160. runbooks/remediation/base.py +1 -1
  161. runbooks/remediation/commons.py +10 -7
  162. runbooks/remediation/commvault_ec2_analysis.py +71 -67
  163. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  164. runbooks/remediation/multi_account.py +24 -21
  165. runbooks/remediation/rds_snapshot_list.py +91 -65
  166. runbooks/remediation/remediation_cli.py +92 -146
  167. runbooks/remediation/universal_account_discovery.py +83 -79
  168. runbooks/remediation/workspaces_list.py +49 -44
  169. runbooks/security/__init__.py +19 -0
  170. runbooks/security/assessment_runner.py +1150 -0
  171. runbooks/security/baseline_checker.py +812 -0
  172. runbooks/security/cloudops_automation_security_validator.py +509 -535
  173. runbooks/security/compliance_automation_engine.py +17 -17
  174. runbooks/security/config/__init__.py +2 -2
  175. runbooks/security/config/compliance_config.py +50 -50
  176. runbooks/security/config_template_generator.py +63 -76
  177. runbooks/security/enterprise_security_framework.py +1 -1
  178. runbooks/security/executive_security_dashboard.py +519 -508
  179. runbooks/security/integration_test_enterprise_security.py +5 -3
  180. runbooks/security/multi_account_security_controls.py +959 -1210
  181. runbooks/security/real_time_security_monitor.py +422 -444
  182. runbooks/security/run_script.py +1 -1
  183. runbooks/security/security_baseline_tester.py +1 -1
  184. runbooks/security/security_cli.py +143 -112
  185. runbooks/security/test_2way_validation.py +439 -0
  186. runbooks/security/two_way_validation_framework.py +852 -0
  187. runbooks/sre/mcp_reliability_engine.py +6 -6
  188. runbooks/sre/production_monitoring_framework.py +167 -177
  189. runbooks/tdd/__init__.py +15 -0
  190. runbooks/tdd/cli.py +1071 -0
  191. runbooks/utils/__init__.py +14 -17
  192. runbooks/utils/logger.py +7 -2
  193. runbooks/utils/version_validator.py +51 -48
  194. runbooks/validation/__init__.py +6 -6
  195. runbooks/validation/cli.py +9 -3
  196. runbooks/validation/comprehensive_2way_validator.py +754 -708
  197. runbooks/validation/mcp_validator.py +906 -228
  198. runbooks/validation/terraform_citations_validator.py +104 -115
  199. runbooks/validation/terraform_drift_detector.py +447 -451
  200. runbooks/vpc/README.md +617 -0
  201. runbooks/vpc/__init__.py +8 -1
  202. runbooks/vpc/analyzer.py +577 -0
  203. runbooks/vpc/cleanup_wrapper.py +476 -413
  204. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  205. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  206. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  207. runbooks/vpc/config.py +92 -97
  208. runbooks/vpc/cost_engine.py +411 -148
  209. runbooks/vpc/cost_explorer_integration.py +553 -0
  210. runbooks/vpc/cross_account_session.py +101 -106
  211. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  212. runbooks/vpc/eni_gate_validator.py +961 -0
  213. runbooks/vpc/heatmap_engine.py +190 -162
  214. runbooks/vpc/mcp_no_eni_validator.py +681 -640
  215. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  216. runbooks/vpc/networking_wrapper.py +15 -8
  217. runbooks/vpc/pdca_remediation_planner.py +528 -0
  218. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  219. runbooks/vpc/runbooks_adapter.py +1167 -241
  220. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  221. runbooks/vpc/test_data_loader.py +358 -0
  222. runbooks/vpc/tests/conftest.py +314 -4
  223. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  224. runbooks/vpc/tests/test_cost_engine.py +0 -2
  225. runbooks/vpc/topology_generator.py +326 -0
  226. runbooks/vpc/unified_scenarios.py +1302 -1129
  227. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  228. runbooks-1.1.5.dist-info/METADATA +328 -0
  229. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/RECORD +233 -200
  230. runbooks/finops/README.md +0 -414
  231. runbooks/finops/accuracy_cross_validator.py +0 -647
  232. runbooks/finops/business_cases.py +0 -950
  233. runbooks/finops/dashboard_router.py +0 -922
  234. runbooks/finops/ebs_optimizer.py +0 -956
  235. runbooks/finops/embedded_mcp_validator.py +0 -1629
  236. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  237. runbooks/finops/finops_dashboard.py +0 -584
  238. runbooks/finops/finops_scenarios.py +0 -1218
  239. runbooks/finops/legacy_migration.py +0 -730
  240. runbooks/finops/multi_dashboard.py +0 -1519
  241. runbooks/finops/single_dashboard.py +0 -1113
  242. runbooks/finops/unlimited_scenarios.py +0 -393
  243. runbooks-1.1.3.dist-info/METADATA +0 -799
  244. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
  245. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  246. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  247. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -31,8 +31,14 @@ from botocore.exceptions import ClientError
31
31
 
32
32
  # Import runbooks enterprise standards
33
33
  from runbooks.common.rich_utils import (
34
- console, print_header, print_success, print_warning, print_error,
35
- create_table, create_progress_bar, create_panel
34
+ console,
35
+ print_header,
36
+ print_success,
37
+ print_warning,
38
+ print_error,
39
+ create_table,
40
+ create_progress_bar,
41
+ create_panel,
36
42
  )
37
43
  from runbooks.common.profile_utils import get_profile_for_operation
38
44
 
@@ -44,6 +50,7 @@ class CFATFinding:
44
50
  """
45
51
  CFAT assessment finding compatible with both Python and JavaScript engines
46
52
  """
53
+
47
54
  check_id: str
48
55
  category: str
49
56
  severity: str # HIGH, MEDIUM, LOW
@@ -53,28 +60,29 @@ class CFATFinding:
53
60
  estimated_effort: str # HOURS, DAYS, WEEKS
54
61
  compliance_frameworks: List[str] = field(default_factory=list)
55
62
  resources_affected: List[str] = field(default_factory=list)
56
-
63
+
57
64
  @classmethod
58
- def from_javascript_result(cls, js_result: Dict[str, Any]) -> 'CFATFinding':
65
+ def from_javascript_result(cls, js_result: Dict[str, Any]) -> "CFATFinding":
59
66
  """Create finding from JavaScript CFAT result"""
60
67
  return cls(
61
- check_id=js_result.get('checkId', 'unknown'),
62
- category=js_result.get('category', 'general'),
63
- severity=js_result.get('severity', 'MEDIUM'),
64
- title=js_result.get('title', ''),
65
- description=js_result.get('description', ''),
66
- remediation=js_result.get('remediation', ''),
67
- estimated_effort=js_result.get('estimatedEffort', 'UNKNOWN'),
68
- compliance_frameworks=js_result.get('complianceFrameworks', []),
69
- resources_affected=js_result.get('resourcesAffected', [])
68
+ check_id=js_result.get("checkId", "unknown"),
69
+ category=js_result.get("category", "general"),
70
+ severity=js_result.get("severity", "MEDIUM"),
71
+ title=js_result.get("title", ""),
72
+ description=js_result.get("description", ""),
73
+ remediation=js_result.get("remediation", ""),
74
+ estimated_effort=js_result.get("estimatedEffort", "UNKNOWN"),
75
+ compliance_frameworks=js_result.get("complianceFrameworks", []),
76
+ resources_affected=js_result.get("resourcesAffected", []),
70
77
  )
71
78
 
72
79
 
73
- @dataclass
80
+ @dataclass
74
81
  class CFATAssessmentResult:
75
82
  """
76
83
  Comprehensive CFAT assessment result
77
84
  """
85
+
78
86
  assessment_id: str
79
87
  timestamp: datetime
80
88
  assessment_type: str # 'python', 'javascript', 'dual'
@@ -85,29 +93,29 @@ class CFATAssessmentResult:
85
93
  summary_stats: Dict[str, int] = field(default_factory=dict)
86
94
  execution_time: float = 0.0
87
95
  artifacts_directory: Optional[str] = None
88
-
96
+
89
97
  def __post_init__(self):
90
98
  """Calculate summary statistics"""
91
99
  if self.findings:
92
100
  self.summary_stats = {
93
- 'total_findings': len(self.findings),
94
- 'high_severity': len([f for f in self.findings if f.severity == 'HIGH']),
95
- 'medium_severity': len([f for f in self.findings if f.severity == 'MEDIUM']),
96
- 'low_severity': len([f for f in self.findings if f.severity == 'LOW']),
97
- 'categories': len(set(f.category for f in self.findings))
101
+ "total_findings": len(self.findings),
102
+ "high_severity": len([f for f in self.findings if f.severity == "HIGH"]),
103
+ "medium_severity": len([f for f in self.findings if f.severity == "MEDIUM"]),
104
+ "low_severity": len([f for f in self.findings if f.severity == "LOW"]),
105
+ "categories": len(set(f.category for f in self.findings)),
98
106
  }
99
107
 
100
108
 
101
109
  class CloudFoundationsCFATIntegration:
102
110
  """
103
111
  Cloud Foundations CFAT Integration
104
-
112
+
105
113
  Provides dual-engine Cloud Foundation Assessment capability:
106
114
  1. JavaScript engine (cloud-foundations-templates/cfat/)
107
115
  2. Python engine integration (existing runbooks/cfat/)
108
116
  3. Unified reporting and export formats
109
117
  4. CloudShell execution compatibility
110
-
118
+
111
119
  Key Features:
112
120
  - Dual assessment engine execution
113
121
  - Project management exports (Jira/Asana CSV)
@@ -115,67 +123,73 @@ class CloudFoundationsCFATIntegration:
115
123
  - Enterprise reporting with Rich CLI
116
124
  - MCP validation integration
117
125
  """
118
-
126
+
119
127
  def __init__(self, profile: Optional[str] = None):
120
128
  """Initialize CFAT integration with profile management"""
121
129
  self.profile = get_profile_for_operation("management", profile)
122
130
  self.session = boto3.Session(profile_name=self.profile)
123
-
131
+
124
132
  # JavaScript engine configuration
125
133
  self.js_engine_path = Path(__file__).parent / "cloud_foundations_js"
126
134
  self.ensure_js_engine_available()
127
-
135
+
128
136
  print_success(f"Initialized Cloud Foundations CFAT Integration with profile: {self.profile}")
129
-
137
+
130
138
  def ensure_js_engine_available(self):
131
139
  """Ensure JavaScript engine is available for execution"""
132
140
  # In real implementation, this would extract/setup the JS engine
133
141
  # For now, create placeholder structure
134
142
  self.js_engine_path.mkdir(exist_ok=True)
135
-
136
- async def run_comprehensive_assessment(self,
137
- cloudshell_mode: bool = False,
138
- include_python: bool = True,
139
- include_javascript: bool = True,
140
- output_directory: Optional[str] = None) -> CFATAssessmentResult:
143
+
144
+ async def run_comprehensive_assessment(
145
+ self,
146
+ cloudshell_mode: bool = False,
147
+ include_python: bool = True,
148
+ include_javascript: bool = True,
149
+ output_directory: Optional[str] = None,
150
+ ) -> CFATAssessmentResult:
141
151
  """
142
152
  Run comprehensive CFAT assessment using available engines
143
-
153
+
144
154
  Args:
145
155
  cloudshell_mode: Enable CloudShell compatibility optimizations
146
156
  include_python: Include Python-based assessment
147
- include_javascript: Include JavaScript-based assessment
157
+ include_javascript: Include JavaScript-based assessment
148
158
  output_directory: Directory for assessment artifacts
149
-
159
+
150
160
  Returns:
151
161
  Comprehensive assessment result with findings from all engines
152
162
  """
153
163
  print_header("Cloud Foundations Assessment", __version__)
154
-
164
+
155
165
  if not (include_python or include_javascript):
156
166
  raise ValueError("At least one assessment engine must be enabled")
157
-
167
+
158
168
  # Initialize assessment result
159
169
  account_info = self._get_account_info()
160
170
  assessment_result = CFATAssessmentResult(
161
171
  assessment_id=f"cfat-{datetime.now().strftime('%Y%m%d-%H%M%S')}",
162
172
  timestamp=datetime.now(),
163
- assessment_type='dual' if (include_python and include_javascript) else 'python' if include_python else 'javascript',
164
- account_id=account_info['account_id'],
165
- account_name=account_info['account_name'],
166
- organization_id=account_info.get('organization_id'),
167
- artifacts_directory=output_directory or tempfile.mkdtemp(prefix='cfat-assessment-')
173
+ assessment_type="dual"
174
+ if (include_python and include_javascript)
175
+ else "python"
176
+ if include_python
177
+ else "javascript",
178
+ account_id=account_info["account_id"],
179
+ account_name=account_info["account_name"],
180
+ organization_id=account_info.get("organization_id"),
181
+ artifacts_directory=output_directory or tempfile.mkdtemp(prefix="cfat-assessment-"),
168
182
  )
169
-
183
+
170
184
  start_time = datetime.now()
171
-
185
+
172
186
  try:
173
187
  with create_progress_bar() as progress:
174
188
  total_tasks = (1 if include_python else 0) + (1 if include_javascript else 0) + 2
175
189
  assessment_task = progress.add_task("Running comprehensive assessment...", total=total_tasks)
176
-
190
+
177
191
  all_findings = []
178
-
192
+
179
193
  # Run Python assessment if enabled
180
194
  if include_python:
181
195
  try:
@@ -185,7 +199,7 @@ class CloudFoundationsCFATIntegration:
185
199
  progress.advance(assessment_task)
186
200
  except Exception as e:
187
201
  print_warning(f"Python assessment failed: {e}")
188
-
202
+
189
203
  # Run JavaScript assessment if enabled
190
204
  if include_javascript:
191
205
  try:
@@ -195,82 +209,84 @@ class CloudFoundationsCFATIntegration:
195
209
  progress.advance(assessment_task)
196
210
  except Exception as e:
197
211
  print_warning(f"JavaScript assessment failed: {e}")
198
-
212
+
199
213
  # Consolidate findings
200
214
  assessment_result.findings = self._consolidate_findings(all_findings)
201
215
  progress.advance(assessment_task)
202
-
216
+
203
217
  # Generate artifacts
204
218
  await self._generate_assessment_artifacts(assessment_result)
205
219
  progress.advance(assessment_task)
206
-
220
+
207
221
  assessment_result.execution_time = (datetime.now() - start_time).total_seconds()
208
- print_success(f"Assessment completed in {assessment_result.execution_time:.1f}s with {len(assessment_result.findings)} findings")
209
-
222
+ print_success(
223
+ f"Assessment completed in {assessment_result.execution_time:.1f}s with {len(assessment_result.findings)} findings"
224
+ )
225
+
210
226
  return assessment_result
211
-
227
+
212
228
  except Exception as e:
213
229
  print_error(f"Assessment execution failed: {e}")
214
230
  raise
215
-
231
+
216
232
  async def _run_python_assessment(self, cloudshell_mode: bool) -> List[CFATFinding]:
217
233
  """
218
234
  Run Python-based CFAT assessment
219
235
  Integration with existing runbooks CFAT module
220
236
  """
221
237
  findings = []
222
-
238
+
223
239
  try:
224
240
  # Import existing CFAT functionality
225
241
  from runbooks.cfat.cfat_runner import CFATRunner
226
-
242
+
227
243
  cfat_runner = CFATRunner(profile=self.profile)
228
244
  python_results = await cfat_runner.run_assessment()
229
-
245
+
230
246
  # Convert Python results to standardized findings
231
- for result in python_results.get('findings', []):
247
+ for result in python_results.get("findings", []):
232
248
  finding = CFATFinding(
233
- check_id=result.get('check_id', 'python-check'),
234
- category=result.get('category', 'governance'),
235
- severity=result.get('severity', 'MEDIUM'),
236
- title=result.get('title', ''),
237
- description=result.get('description', ''),
238
- remediation=result.get('remediation', ''),
239
- estimated_effort=result.get('effort', 'UNKNOWN')
249
+ check_id=result.get("check_id", "python-check"),
250
+ category=result.get("category", "governance"),
251
+ severity=result.get("severity", "MEDIUM"),
252
+ title=result.get("title", ""),
253
+ description=result.get("description", ""),
254
+ remediation=result.get("remediation", ""),
255
+ estimated_effort=result.get("effort", "UNKNOWN"),
240
256
  )
241
257
  findings.append(finding)
242
-
258
+
243
259
  except ImportError:
244
260
  print_warning("Existing Python CFAT module not available, skipping Python assessment")
245
261
  except Exception as e:
246
262
  print_error(f"Python assessment execution failed: {e}")
247
-
263
+
248
264
  return findings
249
-
265
+
250
266
  async def _run_javascript_assessment(self, cloudshell_mode: bool) -> List[CFATFinding]:
251
267
  """
252
268
  Run JavaScript-based CFAT assessment
253
269
  Based on cloud-foundations-templates/cfat/ engine
254
270
  """
255
271
  findings = []
256
-
272
+
257
273
  try:
258
274
  # Prepare JavaScript execution environment
259
275
  js_command = self._prepare_javascript_command(cloudshell_mode)
260
-
276
+
261
277
  # Execute JavaScript assessment
262
278
  result = subprocess.run(
263
279
  js_command,
264
280
  capture_output=True,
265
281
  text=True,
266
282
  timeout=300, # 5 minute timeout
267
- cwd=self.js_engine_path
283
+ cwd=self.js_engine_path,
268
284
  )
269
-
285
+
270
286
  if result.returncode == 0:
271
287
  # Parse JavaScript assessment output
272
288
  js_results = self._parse_javascript_output(result.stdout)
273
-
289
+
274
290
  # Convert to standardized findings
275
291
  for js_result in js_results:
276
292
  finding = CFATFinding.from_javascript_result(js_result)
@@ -278,14 +294,14 @@ class CloudFoundationsCFATIntegration:
278
294
  else:
279
295
  print_warning(f"JavaScript assessment returned non-zero exit code: {result.returncode}")
280
296
  print_warning(f"stderr: {result.stderr}")
281
-
297
+
282
298
  except subprocess.TimeoutExpired:
283
299
  print_error("JavaScript assessment timed out after 5 minutes")
284
300
  except Exception as e:
285
301
  print_error(f"JavaScript assessment execution failed: {e}")
286
-
302
+
287
303
  return findings
288
-
304
+
289
305
  def _prepare_javascript_command(self, cloudshell_mode: bool) -> List[str]:
290
306
  """
291
307
  Prepare JavaScript execution command
@@ -293,32 +309,27 @@ class CloudFoundationsCFATIntegration:
293
309
  """
294
310
  if cloudshell_mode:
295
311
  # CloudShell optimized execution
296
- return [
297
- 'bash', '-c',
298
- f'cd {self.js_engine_path} && AWS_PROFILE={self.profile} node app.js'
299
- ]
312
+ return ["bash", "-c", f"cd {self.js_engine_path} && AWS_PROFILE={self.profile} node app.js"]
300
313
  else:
301
314
  # Local execution
302
- return [
303
- 'node', 'app.js'
304
- ]
305
-
315
+ return ["node", "app.js"]
316
+
306
317
  def _parse_javascript_output(self, stdout: str) -> List[Dict[str, Any]]:
307
318
  """Parse JavaScript assessment output to structured findings"""
308
319
  # In real implementation, this would parse the actual JS CFAT output format
309
320
  # For demonstration, return mock structure
310
321
  return [
311
322
  {
312
- 'checkId': 'js-org-001',
313
- 'category': 'organization',
314
- 'severity': 'HIGH',
315
- 'title': 'Organization Structure Assessment',
316
- 'description': 'Organization structure needs improvement',
317
- 'remediation': 'Implement proper OU structure',
318
- 'estimatedEffort': 'DAYS'
323
+ "checkId": "js-org-001",
324
+ "category": "organization",
325
+ "severity": "HIGH",
326
+ "title": "Organization Structure Assessment",
327
+ "description": "Organization structure needs improvement",
328
+ "remediation": "Implement proper OU structure",
329
+ "estimatedEffort": "DAYS",
319
330
  }
320
331
  ]
321
-
332
+
322
333
  def _consolidate_findings(self, all_findings: List[CFATFinding]) -> List[CFATFinding]:
323
334
  """
324
335
  Consolidate findings from multiple engines, removing duplicates
@@ -327,14 +338,14 @@ class CloudFoundationsCFATIntegration:
327
338
  # In real implementation, would use more sophisticated matching
328
339
  seen_checks = set()
329
340
  consolidated = []
330
-
341
+
331
342
  for finding in all_findings:
332
343
  if finding.check_id not in seen_checks:
333
344
  seen_checks.add(finding.check_id)
334
345
  consolidated.append(finding)
335
-
346
+
336
347
  return consolidated
337
-
348
+
338
349
  async def _generate_assessment_artifacts(self, assessment: CFATAssessmentResult):
339
350
  """
340
351
  Generate comprehensive assessment artifacts
@@ -342,46 +353,46 @@ class CloudFoundationsCFATIntegration:
342
353
  """
343
354
  artifacts_path = Path(assessment.artifacts_directory)
344
355
  artifacts_path.mkdir(exist_ok=True)
345
-
356
+
346
357
  # Generate detailed report
347
358
  await self._generate_detailed_report(assessment, artifacts_path)
348
-
359
+
349
360
  # Generate CSV exports
350
361
  await self._generate_csv_exports(assessment, artifacts_path)
351
-
362
+
352
363
  # Generate project management imports
353
364
  await self._generate_project_management_exports(assessment, artifacts_path)
354
-
365
+
355
366
  # Create assessment archive
356
367
  await self._create_assessment_archive(assessment, artifacts_path)
357
-
368
+
358
369
  async def _generate_detailed_report(self, assessment: CFATAssessmentResult, artifacts_path: Path):
359
370
  """Generate detailed text report"""
360
371
  report_file = artifacts_path / "cfat-detailed-report.txt"
361
-
362
- with open(report_file, 'w') as f:
363
- f.write("="*80 + "\n")
372
+
373
+ with open(report_file, "w") as f:
374
+ f.write("=" * 80 + "\n")
364
375
  f.write("CLOUD FOUNDATIONS ASSESSMENT REPORT\n")
365
- f.write("="*80 + "\n")
376
+ f.write("=" * 80 + "\n")
366
377
  f.write(f"Assessment ID: {assessment.assessment_id}\n")
367
378
  f.write(f"Timestamp: {assessment.timestamp}\n")
368
379
  f.write(f"Account: {assessment.account_name} ({assessment.account_id})\n")
369
380
  f.write(f"Assessment Type: {assessment.assessment_type}\n")
370
381
  f.write(f"Execution Time: {assessment.execution_time:.1f}s\n")
371
382
  f.write("\n")
372
-
383
+
373
384
  f.write("EXECUTIVE SUMMARY\n")
374
- f.write("-"*40 + "\n")
385
+ f.write("-" * 40 + "\n")
375
386
  f.write(f"Total Findings: {assessment.summary_stats.get('total_findings', 0)}\n")
376
387
  f.write(f"High Severity: {assessment.summary_stats.get('high_severity', 0)}\n")
377
388
  f.write(f"Medium Severity: {assessment.summary_stats.get('medium_severity', 0)}\n")
378
389
  f.write(f"Low Severity: {assessment.summary_stats.get('low_severity', 0)}\n")
379
390
  f.write(f"Categories: {assessment.summary_stats.get('categories', 0)}\n")
380
391
  f.write("\n")
381
-
392
+
382
393
  f.write("DETAILED FINDINGS\n")
383
- f.write("-"*40 + "\n")
384
-
394
+ f.write("-" * 40 + "\n")
395
+
385
396
  for finding in assessment.findings:
386
397
  f.write(f"Finding: {finding.title}\n")
387
398
  f.write(f"Category: {finding.category}\n")
@@ -389,28 +400,30 @@ class CloudFoundationsCFATIntegration:
389
400
  f.write(f"Description: {finding.description}\n")
390
401
  f.write(f"Remediation: {finding.remediation}\n")
391
402
  f.write(f"Estimated Effort: {finding.estimated_effort}\n")
392
- f.write("-"*40 + "\n")
393
-
403
+ f.write("-" * 40 + "\n")
404
+
394
405
  async def _generate_csv_exports(self, assessment: CFATAssessmentResult, artifacts_path: Path):
395
406
  """Generate CSV exports for analysis"""
396
407
  csv_file = artifacts_path / "cfat-findings.csv"
397
-
398
- with open(csv_file, 'w', newline='') as csvfile:
399
- fieldnames = ['check_id', 'category', 'severity', 'title', 'description', 'remediation', 'estimated_effort']
408
+
409
+ with open(csv_file, "w", newline="") as csvfile:
410
+ fieldnames = ["check_id", "category", "severity", "title", "description", "remediation", "estimated_effort"]
400
411
  writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
401
-
412
+
402
413
  writer.writeheader()
403
414
  for finding in assessment.findings:
404
- writer.writerow({
405
- 'check_id': finding.check_id,
406
- 'category': finding.category,
407
- 'severity': finding.severity,
408
- 'title': finding.title,
409
- 'description': finding.description,
410
- 'remediation': finding.remediation,
411
- 'estimated_effort': finding.estimated_effort
412
- })
413
-
415
+ writer.writerow(
416
+ {
417
+ "check_id": finding.check_id,
418
+ "category": finding.category,
419
+ "severity": finding.severity,
420
+ "title": finding.title,
421
+ "description": finding.description,
422
+ "remediation": finding.remediation,
423
+ "estimated_effort": finding.estimated_effort,
424
+ }
425
+ )
426
+
414
427
  async def _generate_project_management_exports(self, assessment: CFATAssessmentResult, artifacts_path: Path):
415
428
  """
416
429
  Generate project management import files
@@ -418,155 +431,135 @@ class CloudFoundationsCFATIntegration:
418
431
  """
419
432
  # Jira import
420
433
  jira_file = artifacts_path / "jira-import.csv"
421
- with open(jira_file, 'w', newline='') as csvfile:
422
- fieldnames = ['Summary', 'Issue Type', 'Priority', 'Description', 'Labels', 'Epic Link']
434
+ with open(jira_file, "w", newline="") as csvfile:
435
+ fieldnames = ["Summary", "Issue Type", "Priority", "Description", "Labels", "Epic Link"]
423
436
  writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
424
-
437
+
425
438
  writer.writeheader()
426
439
  for finding in assessment.findings:
427
- writer.writerow({
428
- 'Summary': finding.title,
429
- 'Issue Type': 'Task',
430
- 'Priority': self._map_severity_to_jira_priority(finding.severity),
431
- 'Description': f"{finding.description}\n\nRemediation: {finding.remediation}",
432
- 'Labels': f"cfat,{finding.category},{finding.severity.lower()}",
433
- 'Epic Link': 'Cloud Foundations Assessment'
434
- })
435
-
440
+ writer.writerow(
441
+ {
442
+ "Summary": finding.title,
443
+ "Issue Type": "Task",
444
+ "Priority": self._map_severity_to_jira_priority(finding.severity),
445
+ "Description": f"{finding.description}\n\nRemediation: {finding.remediation}",
446
+ "Labels": f"cfat,{finding.category},{finding.severity.lower()}",
447
+ "Epic Link": "Cloud Foundations Assessment",
448
+ }
449
+ )
450
+
436
451
  # Asana import
437
452
  asana_file = artifacts_path / "asana-import.csv"
438
- with open(asana_file, 'w', newline='') as csvfile:
439
- fieldnames = ['Name', 'Notes', 'Priority', 'Tags', 'Projects']
453
+ with open(asana_file, "w", newline="") as csvfile:
454
+ fieldnames = ["Name", "Notes", "Priority", "Tags", "Projects"]
440
455
  writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
441
-
456
+
442
457
  writer.writeheader()
443
458
  for finding in assessment.findings:
444
- writer.writerow({
445
- 'Name': finding.title,
446
- 'Notes': f"{finding.description}\n\nRemediation: {finding.remediation}",
447
- 'Priority': self._map_severity_to_asana_priority(finding.severity),
448
- 'Tags': f"cfat,{finding.category}",
449
- 'Projects': 'Cloud Foundations Assessment'
450
- })
451
-
459
+ writer.writerow(
460
+ {
461
+ "Name": finding.title,
462
+ "Notes": f"{finding.description}\n\nRemediation: {finding.remediation}",
463
+ "Priority": self._map_severity_to_asana_priority(finding.severity),
464
+ "Tags": f"cfat,{finding.category}",
465
+ "Projects": "Cloud Foundations Assessment",
466
+ }
467
+ )
468
+
452
469
  def _map_severity_to_jira_priority(self, severity: str) -> str:
453
470
  """Map CFAT severity to Jira priority"""
454
- mapping = {
455
- 'HIGH': 'High',
456
- 'MEDIUM': 'Medium',
457
- 'LOW': 'Low'
458
- }
459
- return mapping.get(severity, 'Medium')
460
-
471
+ mapping = {"HIGH": "High", "MEDIUM": "Medium", "LOW": "Low"}
472
+ return mapping.get(severity, "Medium")
473
+
461
474
  def _map_severity_to_asana_priority(self, severity: str) -> str:
462
475
  """Map CFAT severity to Asana priority"""
463
- mapping = {
464
- 'HIGH': 'High',
465
- 'MEDIUM': 'Medium',
466
- 'LOW': 'Low'
467
- }
468
- return mapping.get(severity, 'Medium')
469
-
476
+ mapping = {"HIGH": "High", "MEDIUM": "Medium", "LOW": "Low"}
477
+ return mapping.get(severity, "Medium")
478
+
470
479
  async def _create_assessment_archive(self, assessment: CFATAssessmentResult, artifacts_path: Path):
471
480
  """Create zip archive of all assessment artifacts"""
472
481
  archive_file = artifacts_path / "assessment.zip"
473
-
474
- with zipfile.ZipFile(archive_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
475
- for file_path in artifacts_path.glob('*'):
476
- if file_path.name != 'assessment.zip': # Don't include the zip itself
482
+
483
+ with zipfile.ZipFile(archive_file, "w", zipfile.ZIP_DEFLATED) as zipf:
484
+ for file_path in artifacts_path.glob("*"):
485
+ if file_path.name != "assessment.zip": # Don't include the zip itself
477
486
  zipf.write(file_path, file_path.name)
478
-
487
+
479
488
  print_success(f"Assessment archive created: {archive_file}")
480
-
489
+
481
490
  def _get_account_info(self) -> Dict[str, str]:
482
491
  """Get current account information"""
483
492
  try:
484
- sts = self.session.client('sts')
493
+ sts = self.session.client("sts")
485
494
  identity = sts.get_caller_identity()
486
-
487
- account_id = identity['Account']
488
-
495
+
496
+ account_id = identity["Account"]
497
+
489
498
  # Try to get account name from organizations
490
499
  account_name = account_id # Default to account ID
491
500
  org_id = None
492
-
501
+
493
502
  try:
494
- orgs = self.session.client('organizations')
503
+ orgs = self.session.client("organizations")
495
504
  account = orgs.describe_account(AccountId=account_id)
496
- account_name = account['Account']['Name']
497
-
505
+ account_name = account["Account"]["Name"]
506
+
498
507
  org_info = orgs.describe_organization()
499
- org_id = org_info['Organization']['Id']
508
+ org_id = org_info["Organization"]["Id"]
500
509
  except ClientError:
501
510
  pass # Not in organization or no permission
502
-
503
- return {
504
- 'account_id': account_id,
505
- 'account_name': account_name,
506
- 'organization_id': org_id
507
- }
508
-
511
+
512
+ return {"account_id": account_id, "account_name": account_name, "organization_id": org_id}
513
+
509
514
  except ClientError as e:
510
515
  print_error(f"Failed to get account information: {e}")
511
516
  raise
512
-
517
+
513
518
  def display_assessment_summary(self, assessment: CFATAssessmentResult):
514
519
  """
515
520
  Display assessment summary with Rich CLI formatting
516
521
  """
517
522
  print_header("Assessment Results Summary", __version__)
518
-
523
+
519
524
  # Executive summary table
520
- summary_table = create_table(
521
- title="Executive Summary",
522
- caption=f"Assessment ID: {assessment.assessment_id}"
523
- )
524
-
525
+ summary_table = create_table(title="Executive Summary", caption=f"Assessment ID: {assessment.assessment_id}")
526
+
525
527
  summary_table.add_column("Metric", style="cyan")
526
528
  summary_table.add_column("Value", justify="right", style="green")
527
529
  summary_table.add_column("Impact", style="yellow")
528
-
530
+
529
531
  impact_levels = {
530
- 'total_findings': 'Requires attention',
531
- 'high_severity': 'Immediate action',
532
- 'medium_severity': 'Plan remediation',
533
- 'low_severity': 'Schedule improvement'
532
+ "total_findings": "Requires attention",
533
+ "high_severity": "Immediate action",
534
+ "medium_severity": "Plan remediation",
535
+ "low_severity": "Schedule improvement",
534
536
  }
535
-
537
+
536
538
  for metric, value in assessment.summary_stats.items():
537
- impact = impact_levels.get(metric, 'Review needed')
538
- summary_table.add_row(
539
- metric.replace('_', ' ').title(),
540
- str(value),
541
- impact
542
- )
543
-
539
+ impact = impact_levels.get(metric, "Review needed")
540
+ summary_table.add_row(metric.replace("_", " ").title(), str(value), impact)
541
+
544
542
  console.print(summary_table)
545
-
543
+
546
544
  # Findings by category
547
545
  if assessment.findings:
548
546
  category_stats = {}
549
547
  for finding in assessment.findings:
550
548
  if finding.category not in category_stats:
551
- category_stats[finding.category] = {'high': 0, 'medium': 0, 'low': 0}
549
+ category_stats[finding.category] = {"high": 0, "medium": 0, "low": 0}
552
550
  category_stats[finding.category][finding.severity.lower()] += 1
553
-
551
+
554
552
  category_table = create_table(title="Findings by Category")
555
553
  category_table.add_column("Category", style="cyan")
556
554
  category_table.add_column("High", justify="right", style="red")
557
555
  category_table.add_column("Medium", justify="right", style="yellow")
558
556
  category_table.add_column("Low", justify="right", style="green")
559
-
557
+
560
558
  for category, stats in category_stats.items():
561
- category_table.add_row(
562
- category.title(),
563
- str(stats['high']),
564
- str(stats['medium']),
565
- str(stats['low'])
566
- )
567
-
559
+ category_table.add_row(category.title(), str(stats["high"]), str(stats["medium"]), str(stats["low"]))
560
+
568
561
  console.print(category_table)
569
-
562
+
570
563
  # Assessment details panel
571
564
  details_text = f"""
572
565
  Assessment Type: {assessment.assessment_type}
@@ -574,12 +567,8 @@ Account: {assessment.account_name} ({assessment.account_id})
574
567
  Execution Time: {assessment.execution_time:.1f} seconds
575
568
  Artifacts: {assessment.artifacts_directory}
576
569
  """
577
-
578
- details_panel = create_panel(
579
- details_text,
580
- title="Assessment Details",
581
- style="blue"
582
- )
570
+
571
+ details_panel = create_panel(details_text, title="Assessment Details", style="blue")
583
572
  console.print(details_panel)
584
573
 
585
574
 
@@ -588,34 +577,32 @@ async def main():
588
577
  Demonstration of CFAT Cloud Foundations integration
589
578
  """
590
579
  import argparse
591
-
592
- parser = argparse.ArgumentParser(
593
- description="CFAT Cloud Foundations Integration - Dual Engine Assessment"
594
- )
595
- parser.add_argument('--profile', help='AWS profile to use')
596
- parser.add_argument('--cloudshell', action='store_true', help='Enable CloudShell compatibility mode')
597
- parser.add_argument('--python-only', action='store_true', help='Run Python assessment only')
598
- parser.add_argument('--javascript-only', action='store_true', help='Run JavaScript assessment only')
599
- parser.add_argument('--output-dir', help='Output directory for artifacts')
600
-
580
+
581
+ parser = argparse.ArgumentParser(description="CFAT Cloud Foundations Integration - Dual Engine Assessment")
582
+ parser.add_argument("--profile", help="AWS profile to use")
583
+ parser.add_argument("--cloudshell", action="store_true", help="Enable CloudShell compatibility mode")
584
+ parser.add_argument("--python-only", action="store_true", help="Run Python assessment only")
585
+ parser.add_argument("--javascript-only", action="store_true", help="Run JavaScript assessment only")
586
+ parser.add_argument("--output-dir", help="Output directory for artifacts")
587
+
601
588
  args = parser.parse_args()
602
-
589
+
603
590
  try:
604
591
  cfat_integration = CloudFoundationsCFATIntegration(profile=args.profile)
605
-
592
+
606
593
  # Run comprehensive assessment
607
594
  result = await cfat_integration.run_comprehensive_assessment(
608
595
  cloudshell_mode=args.cloudshell,
609
596
  include_python=not args.javascript_only,
610
597
  include_javascript=not args.python_only,
611
- output_directory=args.output_dir
598
+ output_directory=args.output_dir,
612
599
  )
613
-
600
+
614
601
  # Display results
615
602
  cfat_integration.display_assessment_summary(result)
616
-
603
+
617
604
  print_success("CFAT Cloud Foundations integration demonstration completed")
618
-
605
+
619
606
  except Exception as e:
620
607
  print_error(f"CFAT integration demonstration failed: {e}")
621
608
  raise
@@ -623,4 +610,5 @@ async def main():
623
610
 
624
611
  if __name__ == "__main__":
625
612
  import asyncio
626
- asyncio.run(main())
613
+
614
+ asyncio.run(main())