runbooks 0.9.9__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. runbooks/cfat/cloud_foundations_assessment.py +626 -0
  2. runbooks/cloudops/cost_optimizer.py +95 -33
  3. runbooks/common/aws_pricing.py +388 -0
  4. runbooks/common/aws_pricing_api.py +205 -0
  5. runbooks/common/aws_utils.py +2 -2
  6. runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
  7. runbooks/common/cross_account_manager.py +606 -0
  8. runbooks/common/enhanced_exception_handler.py +4 -0
  9. runbooks/common/env_utils.py +96 -0
  10. runbooks/common/mcp_integration.py +49 -2
  11. runbooks/common/organizations_client.py +579 -0
  12. runbooks/common/profile_utils.py +96 -2
  13. runbooks/finops/cost_optimizer.py +2 -1
  14. runbooks/finops/elastic_ip_optimizer.py +13 -9
  15. runbooks/finops/embedded_mcp_validator.py +31 -0
  16. runbooks/finops/enhanced_trend_visualization.py +3 -2
  17. runbooks/finops/markdown_exporter.py +217 -2
  18. runbooks/finops/nat_gateway_optimizer.py +57 -20
  19. runbooks/finops/vpc_cleanup_exporter.py +28 -26
  20. runbooks/finops/vpc_cleanup_optimizer.py +370 -16
  21. runbooks/inventory/__init__.py +10 -1
  22. runbooks/inventory/cloud_foundations_integration.py +409 -0
  23. runbooks/inventory/core/collector.py +1148 -88
  24. runbooks/inventory/discovery.md +389 -0
  25. runbooks/inventory/drift_detection_cli.py +327 -0
  26. runbooks/inventory/inventory_mcp_cli.py +171 -0
  27. runbooks/inventory/inventory_modules.py +4 -7
  28. runbooks/inventory/mcp_inventory_validator.py +2149 -0
  29. runbooks/inventory/mcp_vpc_validator.py +23 -6
  30. runbooks/inventory/organizations_discovery.py +91 -1
  31. runbooks/inventory/rich_inventory_display.py +129 -1
  32. runbooks/inventory/unified_validation_engine.py +1292 -0
  33. runbooks/inventory/verify_ec2_security_groups.py +3 -1
  34. runbooks/inventory/vpc_analyzer.py +825 -7
  35. runbooks/inventory/vpc_flow_analyzer.py +36 -42
  36. runbooks/main.py +654 -35
  37. runbooks/monitoring/performance_monitor.py +11 -7
  38. runbooks/operate/dynamodb_operations.py +6 -5
  39. runbooks/operate/ec2_operations.py +3 -2
  40. runbooks/operate/networking_cost_heatmap.py +4 -3
  41. runbooks/operate/s3_operations.py +13 -12
  42. runbooks/operate/vpc_operations.py +49 -1
  43. runbooks/remediation/base.py +1 -1
  44. runbooks/remediation/commvault_ec2_analysis.py +6 -1
  45. runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
  46. runbooks/remediation/rds_snapshot_list.py +5 -3
  47. runbooks/validation/__init__.py +21 -1
  48. runbooks/validation/comprehensive_2way_validator.py +1996 -0
  49. runbooks/validation/mcp_validator.py +904 -94
  50. runbooks/validation/terraform_citations_validator.py +363 -0
  51. runbooks/validation/terraform_drift_detector.py +1098 -0
  52. runbooks/vpc/cleanup_wrapper.py +231 -10
  53. runbooks/vpc/config.py +310 -62
  54. runbooks/vpc/cross_account_session.py +308 -0
  55. runbooks/vpc/heatmap_engine.py +96 -29
  56. runbooks/vpc/manager_interface.py +9 -9
  57. runbooks/vpc/mcp_no_eni_validator.py +1551 -0
  58. runbooks/vpc/networking_wrapper.py +14 -8
  59. runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
  60. runbooks/vpc/runbooks.security.report_generator.log +0 -0
  61. runbooks/vpc/runbooks.security.run_script.log +0 -0
  62. runbooks/vpc/runbooks.security.security_export.log +0 -0
  63. runbooks/vpc/tests/test_cost_engine.py +1 -1
  64. runbooks/vpc/unified_scenarios.py +73 -3
  65. runbooks/vpc/vpc_cleanup_integration.py +512 -78
  66. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/METADATA +94 -52
  67. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/RECORD +71 -49
  68. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/WHEEL +0 -0
  69. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/entry_points.txt +0 -0
  70. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/licenses/LICENSE +0 -0
  71. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,626 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CFAT Cloud Foundations Assessment Integration
4
+ Integrates Cloud Foundation Assessment Tool (CFAT) JavaScript engine with runbooks
5
+
6
+ This module provides dual-engine assessment capability:
7
+ - Python-based assessment (existing runbooks CFAT)
8
+ - JavaScript-based assessment (cloud-foundations-templates CFAT)
9
+ - Unified reporting with project management integration
10
+ - CloudShell compatibility for enterprise environments
11
+
12
+ Strategic Alignment:
13
+ - Enhances existing src/runbooks/cfat/ capabilities without duplication
14
+ - Maintains Rich CLI standards and enterprise quality
15
+ - Provides executive reporting with business value quantification
16
+ """
17
+
18
+ from typing import Dict, List, Optional, Any, Tuple
19
+ import json
20
+ import subprocess
21
+ import tempfile
22
+ import shutil
23
+ from pathlib import Path
24
+ from dataclasses import dataclass, field
25
+ import csv
26
+ import zipfile
27
+ from datetime import datetime
28
+
29
+ import boto3
30
+ from botocore.exceptions import ClientError
31
+
32
+ # Import runbooks enterprise standards
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
36
+ )
37
+ from runbooks.common.profile_utils import get_profile_for_operation
38
+
39
+ __version__ = "1.0.0"
40
+
41
+
42
+ @dataclass
43
+ class CFATFinding:
44
+ """
45
+ CFAT assessment finding compatible with both Python and JavaScript engines
46
+ """
47
+ check_id: str
48
+ category: str
49
+ severity: str # HIGH, MEDIUM, LOW
50
+ title: str
51
+ description: str
52
+ remediation: str
53
+ estimated_effort: str # HOURS, DAYS, WEEKS
54
+ compliance_frameworks: List[str] = field(default_factory=list)
55
+ resources_affected: List[str] = field(default_factory=list)
56
+
57
+ @classmethod
58
+ def from_javascript_result(cls, js_result: Dict[str, Any]) -> 'CFATFinding':
59
+ """Create finding from JavaScript CFAT result"""
60
+ 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', [])
70
+ )
71
+
72
+
73
+ @dataclass
74
+ class CFATAssessmentResult:
75
+ """
76
+ Comprehensive CFAT assessment result
77
+ """
78
+ assessment_id: str
79
+ timestamp: datetime
80
+ assessment_type: str # 'python', 'javascript', 'dual'
81
+ account_id: str
82
+ account_name: str
83
+ organization_id: Optional[str]
84
+ findings: List[CFATFinding] = field(default_factory=list)
85
+ summary_stats: Dict[str, int] = field(default_factory=dict)
86
+ execution_time: float = 0.0
87
+ artifacts_directory: Optional[str] = None
88
+
89
+ def __post_init__(self):
90
+ """Calculate summary statistics"""
91
+ if self.findings:
92
+ 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))
98
+ }
99
+
100
+
101
+ class CloudFoundationsCFATIntegration:
102
+ """
103
+ Cloud Foundations CFAT Integration
104
+
105
+ Provides dual-engine Cloud Foundation Assessment capability:
106
+ 1. JavaScript engine (cloud-foundations-templates/cfat/)
107
+ 2. Python engine integration (existing runbooks/cfat/)
108
+ 3. Unified reporting and export formats
109
+ 4. CloudShell execution compatibility
110
+
111
+ Key Features:
112
+ - Dual assessment engine execution
113
+ - Project management exports (Jira/Asana CSV)
114
+ - CloudShell compatibility mode
115
+ - Enterprise reporting with Rich CLI
116
+ - MCP validation integration
117
+ """
118
+
119
+ def __init__(self, profile: Optional[str] = None):
120
+ """Initialize CFAT integration with profile management"""
121
+ self.profile = get_profile_for_operation("management", profile)
122
+ self.session = boto3.Session(profile_name=self.profile)
123
+
124
+ # JavaScript engine configuration
125
+ self.js_engine_path = Path(__file__).parent / "cloud_foundations_js"
126
+ self.ensure_js_engine_available()
127
+
128
+ print_success(f"Initialized Cloud Foundations CFAT Integration with profile: {self.profile}")
129
+
130
+ def ensure_js_engine_available(self):
131
+ """Ensure JavaScript engine is available for execution"""
132
+ # In real implementation, this would extract/setup the JS engine
133
+ # For now, create placeholder structure
134
+ 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:
141
+ """
142
+ Run comprehensive CFAT assessment using available engines
143
+
144
+ Args:
145
+ cloudshell_mode: Enable CloudShell compatibility optimizations
146
+ include_python: Include Python-based assessment
147
+ include_javascript: Include JavaScript-based assessment
148
+ output_directory: Directory for assessment artifacts
149
+
150
+ Returns:
151
+ Comprehensive assessment result with findings from all engines
152
+ """
153
+ print_header("Cloud Foundations Assessment", __version__)
154
+
155
+ if not (include_python or include_javascript):
156
+ raise ValueError("At least one assessment engine must be enabled")
157
+
158
+ # Initialize assessment result
159
+ account_info = self._get_account_info()
160
+ assessment_result = CFATAssessmentResult(
161
+ assessment_id=f"cfat-{datetime.now().strftime('%Y%m%d-%H%M%S')}",
162
+ 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-')
168
+ )
169
+
170
+ start_time = datetime.now()
171
+
172
+ try:
173
+ with create_progress_bar() as progress:
174
+ total_tasks = (1 if include_python else 0) + (1 if include_javascript else 0) + 2
175
+ assessment_task = progress.add_task("Running comprehensive assessment...", total=total_tasks)
176
+
177
+ all_findings = []
178
+
179
+ # Run Python assessment if enabled
180
+ if include_python:
181
+ try:
182
+ python_findings = await self._run_python_assessment(cloudshell_mode)
183
+ all_findings.extend(python_findings)
184
+ print_success(f"Python assessment completed: {len(python_findings)} findings")
185
+ progress.advance(assessment_task)
186
+ except Exception as e:
187
+ print_warning(f"Python assessment failed: {e}")
188
+
189
+ # Run JavaScript assessment if enabled
190
+ if include_javascript:
191
+ try:
192
+ js_findings = await self._run_javascript_assessment(cloudshell_mode)
193
+ all_findings.extend(js_findings)
194
+ print_success(f"JavaScript assessment completed: {len(js_findings)} findings")
195
+ progress.advance(assessment_task)
196
+ except Exception as e:
197
+ print_warning(f"JavaScript assessment failed: {e}")
198
+
199
+ # Consolidate findings
200
+ assessment_result.findings = self._consolidate_findings(all_findings)
201
+ progress.advance(assessment_task)
202
+
203
+ # Generate artifacts
204
+ await self._generate_assessment_artifacts(assessment_result)
205
+ progress.advance(assessment_task)
206
+
207
+ 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
+
210
+ return assessment_result
211
+
212
+ except Exception as e:
213
+ print_error(f"Assessment execution failed: {e}")
214
+ raise
215
+
216
+ async def _run_python_assessment(self, cloudshell_mode: bool) -> List[CFATFinding]:
217
+ """
218
+ Run Python-based CFAT assessment
219
+ Integration with existing runbooks CFAT module
220
+ """
221
+ findings = []
222
+
223
+ try:
224
+ # Import existing CFAT functionality
225
+ from runbooks.cfat.cfat_runner import CFATRunner
226
+
227
+ cfat_runner = CFATRunner(profile=self.profile)
228
+ python_results = await cfat_runner.run_assessment()
229
+
230
+ # Convert Python results to standardized findings
231
+ for result in python_results.get('findings', []):
232
+ 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')
240
+ )
241
+ findings.append(finding)
242
+
243
+ except ImportError:
244
+ print_warning("Existing Python CFAT module not available, skipping Python assessment")
245
+ except Exception as e:
246
+ print_error(f"Python assessment execution failed: {e}")
247
+
248
+ return findings
249
+
250
+ async def _run_javascript_assessment(self, cloudshell_mode: bool) -> List[CFATFinding]:
251
+ """
252
+ Run JavaScript-based CFAT assessment
253
+ Based on cloud-foundations-templates/cfat/ engine
254
+ """
255
+ findings = []
256
+
257
+ try:
258
+ # Prepare JavaScript execution environment
259
+ js_command = self._prepare_javascript_command(cloudshell_mode)
260
+
261
+ # Execute JavaScript assessment
262
+ result = subprocess.run(
263
+ js_command,
264
+ capture_output=True,
265
+ text=True,
266
+ timeout=300, # 5 minute timeout
267
+ cwd=self.js_engine_path
268
+ )
269
+
270
+ if result.returncode == 0:
271
+ # Parse JavaScript assessment output
272
+ js_results = self._parse_javascript_output(result.stdout)
273
+
274
+ # Convert to standardized findings
275
+ for js_result in js_results:
276
+ finding = CFATFinding.from_javascript_result(js_result)
277
+ findings.append(finding)
278
+ else:
279
+ print_warning(f"JavaScript assessment returned non-zero exit code: {result.returncode}")
280
+ print_warning(f"stderr: {result.stderr}")
281
+
282
+ except subprocess.TimeoutExpired:
283
+ print_error("JavaScript assessment timed out after 5 minutes")
284
+ except Exception as e:
285
+ print_error(f"JavaScript assessment execution failed: {e}")
286
+
287
+ return findings
288
+
289
+ def _prepare_javascript_command(self, cloudshell_mode: bool) -> List[str]:
290
+ """
291
+ Prepare JavaScript execution command
292
+ Adapts for CloudShell or local execution
293
+ """
294
+ if cloudshell_mode:
295
+ # CloudShell optimized execution
296
+ return [
297
+ 'bash', '-c',
298
+ f'cd {self.js_engine_path} && AWS_PROFILE={self.profile} node app.js'
299
+ ]
300
+ else:
301
+ # Local execution
302
+ return [
303
+ 'node', 'app.js'
304
+ ]
305
+
306
+ def _parse_javascript_output(self, stdout: str) -> List[Dict[str, Any]]:
307
+ """Parse JavaScript assessment output to structured findings"""
308
+ # In real implementation, this would parse the actual JS CFAT output format
309
+ # For demonstration, return mock structure
310
+ return [
311
+ {
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'
319
+ }
320
+ ]
321
+
322
+ def _consolidate_findings(self, all_findings: List[CFATFinding]) -> List[CFATFinding]:
323
+ """
324
+ Consolidate findings from multiple engines, removing duplicates
325
+ """
326
+ # Simple deduplication by check_id for now
327
+ # In real implementation, would use more sophisticated matching
328
+ seen_checks = set()
329
+ consolidated = []
330
+
331
+ for finding in all_findings:
332
+ if finding.check_id not in seen_checks:
333
+ seen_checks.add(finding.check_id)
334
+ consolidated.append(finding)
335
+
336
+ return consolidated
337
+
338
+ async def _generate_assessment_artifacts(self, assessment: CFATAssessmentResult):
339
+ """
340
+ Generate comprehensive assessment artifacts
341
+ Based on cloud-foundations-templates export formats
342
+ """
343
+ artifacts_path = Path(assessment.artifacts_directory)
344
+ artifacts_path.mkdir(exist_ok=True)
345
+
346
+ # Generate detailed report
347
+ await self._generate_detailed_report(assessment, artifacts_path)
348
+
349
+ # Generate CSV exports
350
+ await self._generate_csv_exports(assessment, artifacts_path)
351
+
352
+ # Generate project management imports
353
+ await self._generate_project_management_exports(assessment, artifacts_path)
354
+
355
+ # Create assessment archive
356
+ await self._create_assessment_archive(assessment, artifacts_path)
357
+
358
+ async def _generate_detailed_report(self, assessment: CFATAssessmentResult, artifacts_path: Path):
359
+ """Generate detailed text report"""
360
+ report_file = artifacts_path / "cfat-detailed-report.txt"
361
+
362
+ with open(report_file, 'w') as f:
363
+ f.write("="*80 + "\n")
364
+ f.write("CLOUD FOUNDATIONS ASSESSMENT REPORT\n")
365
+ f.write("="*80 + "\n")
366
+ f.write(f"Assessment ID: {assessment.assessment_id}\n")
367
+ f.write(f"Timestamp: {assessment.timestamp}\n")
368
+ f.write(f"Account: {assessment.account_name} ({assessment.account_id})\n")
369
+ f.write(f"Assessment Type: {assessment.assessment_type}\n")
370
+ f.write(f"Execution Time: {assessment.execution_time:.1f}s\n")
371
+ f.write("\n")
372
+
373
+ f.write("EXECUTIVE SUMMARY\n")
374
+ f.write("-"*40 + "\n")
375
+ f.write(f"Total Findings: {assessment.summary_stats.get('total_findings', 0)}\n")
376
+ f.write(f"High Severity: {assessment.summary_stats.get('high_severity', 0)}\n")
377
+ f.write(f"Medium Severity: {assessment.summary_stats.get('medium_severity', 0)}\n")
378
+ f.write(f"Low Severity: {assessment.summary_stats.get('low_severity', 0)}\n")
379
+ f.write(f"Categories: {assessment.summary_stats.get('categories', 0)}\n")
380
+ f.write("\n")
381
+
382
+ f.write("DETAILED FINDINGS\n")
383
+ f.write("-"*40 + "\n")
384
+
385
+ for finding in assessment.findings:
386
+ f.write(f"Finding: {finding.title}\n")
387
+ f.write(f"Category: {finding.category}\n")
388
+ f.write(f"Severity: {finding.severity}\n")
389
+ f.write(f"Description: {finding.description}\n")
390
+ f.write(f"Remediation: {finding.remediation}\n")
391
+ f.write(f"Estimated Effort: {finding.estimated_effort}\n")
392
+ f.write("-"*40 + "\n")
393
+
394
+ async def _generate_csv_exports(self, assessment: CFATAssessmentResult, artifacts_path: Path):
395
+ """Generate CSV exports for analysis"""
396
+ 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']
400
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
401
+
402
+ writer.writeheader()
403
+ 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
+
414
+ async def _generate_project_management_exports(self, assessment: CFATAssessmentResult, artifacts_path: Path):
415
+ """
416
+ Generate project management import files
417
+ Based on cloud-foundations-templates export formats
418
+ """
419
+ # Jira import
420
+ 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']
423
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
424
+
425
+ writer.writeheader()
426
+ 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
+
436
+ # Asana import
437
+ asana_file = artifacts_path / "asana-import.csv"
438
+ with open(asana_file, 'w', newline='') as csvfile:
439
+ fieldnames = ['Name', 'Notes', 'Priority', 'Tags', 'Projects']
440
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
441
+
442
+ writer.writeheader()
443
+ 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
+
452
+ def _map_severity_to_jira_priority(self, severity: str) -> str:
453
+ """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
+
461
+ def _map_severity_to_asana_priority(self, severity: str) -> str:
462
+ """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
+
470
+ async def _create_assessment_archive(self, assessment: CFATAssessmentResult, artifacts_path: Path):
471
+ """Create zip archive of all assessment artifacts"""
472
+ 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
477
+ zipf.write(file_path, file_path.name)
478
+
479
+ print_success(f"Assessment archive created: {archive_file}")
480
+
481
+ def _get_account_info(self) -> Dict[str, str]:
482
+ """Get current account information"""
483
+ try:
484
+ sts = self.session.client('sts')
485
+ identity = sts.get_caller_identity()
486
+
487
+ account_id = identity['Account']
488
+
489
+ # Try to get account name from organizations
490
+ account_name = account_id # Default to account ID
491
+ org_id = None
492
+
493
+ try:
494
+ orgs = self.session.client('organizations')
495
+ account = orgs.describe_account(AccountId=account_id)
496
+ account_name = account['Account']['Name']
497
+
498
+ org_info = orgs.describe_organization()
499
+ org_id = org_info['Organization']['Id']
500
+ except ClientError:
501
+ 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
+
509
+ except ClientError as e:
510
+ print_error(f"Failed to get account information: {e}")
511
+ raise
512
+
513
+ def display_assessment_summary(self, assessment: CFATAssessmentResult):
514
+ """
515
+ Display assessment summary with Rich CLI formatting
516
+ """
517
+ print_header("Assessment Results Summary", __version__)
518
+
519
+ # 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.add_column("Metric", style="cyan")
526
+ summary_table.add_column("Value", justify="right", style="green")
527
+ summary_table.add_column("Impact", style="yellow")
528
+
529
+ impact_levels = {
530
+ 'total_findings': 'Requires attention',
531
+ 'high_severity': 'Immediate action',
532
+ 'medium_severity': 'Plan remediation',
533
+ 'low_severity': 'Schedule improvement'
534
+ }
535
+
536
+ 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
+
544
+ console.print(summary_table)
545
+
546
+ # Findings by category
547
+ if assessment.findings:
548
+ category_stats = {}
549
+ for finding in assessment.findings:
550
+ if finding.category not in category_stats:
551
+ category_stats[finding.category] = {'high': 0, 'medium': 0, 'low': 0}
552
+ category_stats[finding.category][finding.severity.lower()] += 1
553
+
554
+ category_table = create_table(title="Findings by Category")
555
+ category_table.add_column("Category", style="cyan")
556
+ category_table.add_column("High", justify="right", style="red")
557
+ category_table.add_column("Medium", justify="right", style="yellow")
558
+ category_table.add_column("Low", justify="right", style="green")
559
+
560
+ 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
+
568
+ console.print(category_table)
569
+
570
+ # Assessment details panel
571
+ details_text = f"""
572
+ Assessment Type: {assessment.assessment_type}
573
+ Account: {assessment.account_name} ({assessment.account_id})
574
+ Execution Time: {assessment.execution_time:.1f} seconds
575
+ Artifacts: {assessment.artifacts_directory}
576
+ """
577
+
578
+ details_panel = create_panel(
579
+ details_text,
580
+ title="Assessment Details",
581
+ style="blue"
582
+ )
583
+ console.print(details_panel)
584
+
585
+
586
+ async def main():
587
+ """
588
+ Demonstration of CFAT Cloud Foundations integration
589
+ """
590
+ 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
+
601
+ args = parser.parse_args()
602
+
603
+ try:
604
+ cfat_integration = CloudFoundationsCFATIntegration(profile=args.profile)
605
+
606
+ # Run comprehensive assessment
607
+ result = await cfat_integration.run_comprehensive_assessment(
608
+ cloudshell_mode=args.cloudshell,
609
+ include_python=not args.javascript_only,
610
+ include_javascript=not args.python_only,
611
+ output_directory=args.output_dir
612
+ )
613
+
614
+ # Display results
615
+ cfat_integration.display_assessment_summary(result)
616
+
617
+ print_success("CFAT Cloud Foundations integration demonstration completed")
618
+
619
+ except Exception as e:
620
+ print_error(f"CFAT integration demonstration failed: {e}")
621
+ raise
622
+
623
+
624
+ if __name__ == "__main__":
625
+ import asyncio
626
+ asyncio.run(main())