runbooks 0.7.7__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runbooks/__init__.py +1 -1
- runbooks/base.py +2 -2
- runbooks/cfat/README.md +12 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +546 -522
- runbooks/cfat/assessment/runner.py +129 -10
- runbooks/cfat/models.py +6 -2
- runbooks/common/__init__.py +152 -0
- runbooks/common/accuracy_validator.py +1039 -0
- runbooks/common/context_logger.py +440 -0
- runbooks/common/cross_module_integration.py +594 -0
- runbooks/common/enhanced_exception_handler.py +1108 -0
- runbooks/common/enterprise_audit_integration.py +634 -0
- runbooks/common/logger.py +14 -0
- runbooks/common/mcp_integration.py +539 -0
- runbooks/common/performance_monitor.py +387 -0
- runbooks/common/profile_utils.py +216 -0
- runbooks/common/rich_utils.py +622 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/feedback/user_feedback_collector.py +440 -0
- runbooks/finops/README.md +129 -14
- runbooks/finops/__init__.py +22 -3
- runbooks/finops/account_resolver.py +279 -0
- runbooks/finops/accuracy_cross_validator.py +638 -0
- runbooks/finops/aws_client.py +721 -36
- runbooks/finops/budget_integration.py +313 -0
- runbooks/finops/cli.py +90 -33
- runbooks/finops/cost_processor.py +211 -37
- runbooks/finops/dashboard_router.py +900 -0
- runbooks/finops/dashboard_runner.py +1334 -399
- runbooks/finops/embedded_mcp_validator.py +288 -0
- runbooks/finops/enhanced_dashboard_runner.py +526 -0
- runbooks/finops/enhanced_progress.py +327 -0
- runbooks/finops/enhanced_trend_visualization.py +423 -0
- runbooks/finops/finops_dashboard.py +41 -0
- runbooks/finops/helpers.py +639 -323
- runbooks/finops/iam_guidance.py +400 -0
- runbooks/finops/markdown_exporter.py +466 -0
- runbooks/finops/multi_dashboard.py +1502 -0
- runbooks/finops/optimizer.py +396 -395
- runbooks/finops/profile_processor.py +2 -2
- runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/finops/runbooks.security.report_generator.log +0 -0
- runbooks/finops/runbooks.security.run_script.log +0 -0
- runbooks/finops/runbooks.security.security_export.log +0 -0
- runbooks/finops/service_mapping.py +195 -0
- runbooks/finops/single_dashboard.py +710 -0
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/README.md +12 -1
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +192 -185
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +299 -12
- runbooks/inventory/list_ec2_instances.py +21 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1315 -0
- runbooks/inventory/rich_inventory_display.py +360 -0
- runbooks/inventory/run_on_multi_accounts.py +32 -16
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +4171 -1615
- runbooks/metrics/dora_metrics_engine.py +1293 -0
- runbooks/monitoring/performance_monitor.py +433 -0
- runbooks/operate/README.md +394 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +291 -11
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +321 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/README.md +489 -13
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/commons.py +8 -4
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +506 -0
- runbooks/security/README.md +12 -1
- runbooks/security/__init__.py +166 -33
- runbooks/security/compliance_automation.py +634 -0
- runbooks/security/compliance_automation_engine.py +1021 -0
- runbooks/security/enterprise_security_framework.py +931 -0
- runbooks/security/enterprise_security_policies.json +293 -0
- runbooks/security/integration_test_enterprise_security.py +879 -0
- runbooks/security/module_security_integrator.py +641 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +27 -5
- runbooks/security/security_baseline_tester.py +153 -27
- runbooks/security/security_export.py +456 -0
- runbooks/sre/README.md +472 -0
- runbooks/sre/__init__.py +33 -0
- runbooks/sre/mcp_reliability_engine.py +1049 -0
- runbooks/sre/performance_optimization_engine.py +1032 -0
- runbooks/sre/reliability_monitoring_framework.py +1011 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +489 -0
- runbooks/validation/cli.py +368 -0
- runbooks/validation/mcp_validator.py +797 -0
- runbooks/vpc/README.md +478 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +649 -0
- runbooks/vpc/networking_wrapper.py +1289 -0
- runbooks/vpc/rich_formatters.py +693 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/METADATA +175 -65
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/RECORD +157 -60
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/entry_points.txt +1 -1
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/WHEEL +0 -0
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/top_level.txt +0 -0
runbooks/inventory/README.md
CHANGED
@@ -1,4 +1,15 @@
|
|
1
|
-
# AWS
|
1
|
+
# AWS Multi-Account Discovery & Inventory (CLI)
|
2
|
+
|
3
|
+
The AWS Multi-Account Discovery module is an enterprise-grade command-line tool for comprehensive AWS resource discovery across 50+ services. Built with the Rich library for beautiful terminal output, it provides multi-threaded inventory collection with enterprise-grade error handling and reporting.
|
4
|
+
|
5
|
+
## 📈 *inventory-runbooks*.md Enterprise Rollout
|
6
|
+
|
7
|
+
Following proven **99/100 manager score** success patterns established in FinOps:
|
8
|
+
|
9
|
+
### **Rollout Strategy**: Progressive *-runbooks*.md standardization
|
10
|
+
- **Phase 2**: Inventory rollout with *inventory-runbooks*.md patterns ✅
|
11
|
+
- **Current Success Rate**: 37/46 scripts (80.4%) ✅
|
12
|
+
- **Integration**: Complete multi-account discovery framework
|
2
13
|
|
3
14
|
## ✅ **Current Success Rate (v0.6.1): 37/46 scripts (80.4%)**
|
4
15
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
🔧 AGENT ASSIGNMENTS FOR SCALE & OPTIMIZE IMPLEMENTATION
|
2
|
+
|
3
|
+
Agent 0 (Management): Orchestrate 200+ account scaling with performance validation
|
4
|
+
Agent 1 (Development): Implement enhanced concurrent processing and MCP extensions
|
5
|
+
Agent 2 (Architecture): Design scalability patterns and advanced MCP integration
|
6
|
+
Agent 3 (Testing): Validate performance targets with enterprise load testing
|
7
|
+
Agent 4 (FinOps): Enhanced Cost Explorer MCP for multi-tenant optimization
|
8
|
+
Agent 5 (Security): Enterprise compliance frameworks and security patterns
|
9
|
+
|
10
|
+
Phase 1: Enhanced Concurrent Processing (Target: 200+ accounts)
|
11
|
+
Phase 2: Advanced MCP Server Integration
|
12
|
+
Phase 3: Enterprise Integration Patterns
|
@@ -1,13 +1,15 @@
|
|
1
1
|
"""
|
2
|
-
Comprehensive AWS Resource Collector for
|
2
|
+
Comprehensive AWS Resource Collector for Multi-Account Organizations
|
3
3
|
Sprint 1: Discovery & Assessment - Enhanced for parallel processing
|
4
|
+
Supports any organization size: 10, 60, 200+ accounts dynamically
|
4
5
|
"""
|
5
6
|
|
6
7
|
import asyncio
|
7
8
|
import json
|
8
9
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
9
|
-
from typing import Dict, List, Any, Optional
|
10
10
|
from datetime import datetime
|
11
|
+
from typing import Any, Dict, List, Optional
|
12
|
+
|
11
13
|
import boto3
|
12
14
|
from botocore.exceptions import ClientError, NoCredentialsError
|
13
15
|
|
@@ -19,206 +21,214 @@ class ComprehensiveCollector(BaseResourceCollector):
|
|
19
21
|
Collect all AWS resources across multi-account organization with parallel processing.
|
20
22
|
Optimized for Sprint 1 discovery goals.
|
21
23
|
"""
|
22
|
-
|
24
|
+
|
23
25
|
def __init__(self, profile: str = None, parallel_workers: int = 10):
|
24
26
|
"""Initialize comprehensive collector with parallel processing."""
|
25
27
|
super().__init__(profile)
|
26
28
|
self.parallel_workers = parallel_workers
|
27
29
|
self.discovered_resources = {}
|
28
30
|
self.discovery_metrics = {
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
"start_time": datetime.now(),
|
32
|
+
"accounts_scanned": 0,
|
33
|
+
"total_resources": 0,
|
34
|
+
"services_discovered": set(),
|
33
35
|
}
|
34
|
-
|
36
|
+
|
35
37
|
def collect_all_services(self, accounts: List[str] = None) -> Dict[str, Any]:
|
36
38
|
"""
|
37
39
|
Collect resources from all critical AWS services across accounts.
|
38
|
-
|
40
|
+
|
39
41
|
Args:
|
40
42
|
accounts: List of account IDs to scan (None for all)
|
41
|
-
|
43
|
+
|
42
44
|
Returns:
|
43
45
|
Comprehensive inventory with visualization data
|
44
46
|
"""
|
45
47
|
services = [
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
"ec2",
|
49
|
+
"s3",
|
50
|
+
"rds",
|
51
|
+
"lambda",
|
52
|
+
"dynamodb",
|
53
|
+
"cloudformation",
|
54
|
+
"iam",
|
55
|
+
"vpc",
|
56
|
+
"elb",
|
57
|
+
"route53",
|
58
|
+
"ecs",
|
59
|
+
"eks",
|
60
|
+
"elasticache",
|
61
|
+
"cloudwatch",
|
62
|
+
"sns",
|
49
63
|
]
|
50
|
-
|
64
|
+
|
51
65
|
if not accounts:
|
52
66
|
accounts = self._discover_all_accounts()
|
53
|
-
|
67
|
+
|
54
68
|
results = {
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
69
|
+
"metadata": {
|
70
|
+
"scan_date": datetime.now().isoformat(),
|
71
|
+
"accounts_total": len(accounts),
|
72
|
+
"services_scanned": services,
|
73
|
+
"profile_used": self.profile or "default",
|
60
74
|
},
|
61
|
-
|
62
|
-
|
75
|
+
"resources": {},
|
76
|
+
"summary": {},
|
63
77
|
}
|
64
|
-
|
78
|
+
|
65
79
|
# Parallel collection across accounts and services
|
66
80
|
with ThreadPoolExecutor(max_workers=self.parallel_workers) as executor:
|
67
81
|
futures = []
|
68
|
-
|
82
|
+
|
69
83
|
for account_id in accounts:
|
70
84
|
for service in services:
|
71
|
-
future = executor.submit(
|
72
|
-
self._collect_service_resources,
|
73
|
-
account_id,
|
74
|
-
service
|
75
|
-
)
|
85
|
+
future = executor.submit(self._collect_service_resources, account_id, service)
|
76
86
|
futures.append((future, account_id, service))
|
77
|
-
|
87
|
+
|
78
88
|
# Process results as they complete
|
79
89
|
for future, account_id, service in futures:
|
80
90
|
try:
|
81
91
|
service_resources = future.result(timeout=30)
|
82
92
|
if service_resources:
|
83
|
-
if account_id not in results[
|
84
|
-
results[
|
85
|
-
results[
|
86
|
-
self.discovery_metrics[
|
93
|
+
if account_id not in results["resources"]:
|
94
|
+
results["resources"][account_id] = {}
|
95
|
+
results["resources"][account_id][service] = service_resources
|
96
|
+
self.discovery_metrics["services_discovered"].add(service)
|
87
97
|
except Exception as e:
|
88
98
|
print(f"Error collecting {service} from {account_id}: {e}")
|
89
|
-
|
99
|
+
|
90
100
|
# Generate summary statistics
|
91
|
-
results[
|
92
|
-
|
101
|
+
results["summary"] = self._generate_summary(results["resources"])
|
102
|
+
|
93
103
|
# Save results to Sprint 1 artifacts
|
94
104
|
self._save_results(results)
|
95
|
-
|
105
|
+
|
96
106
|
return results
|
97
|
-
|
107
|
+
|
98
108
|
def _collect_service_resources(self, account_id: str, service: str) -> List[Dict]:
|
99
109
|
"""Collect resources for a specific service in an account."""
|
100
110
|
resources = []
|
101
|
-
|
111
|
+
|
102
112
|
try:
|
103
113
|
# Assume role if cross-account
|
104
114
|
session = self._get_account_session(account_id)
|
105
|
-
|
106
|
-
if service ==
|
115
|
+
|
116
|
+
if service == "ec2":
|
107
117
|
resources = self._collect_ec2_resources(session)
|
108
|
-
elif service ==
|
118
|
+
elif service == "s3":
|
109
119
|
resources = self._collect_s3_resources(session)
|
110
|
-
elif service ==
|
120
|
+
elif service == "rds":
|
111
121
|
resources = self._collect_rds_resources(session)
|
112
|
-
elif service ==
|
122
|
+
elif service == "lambda":
|
113
123
|
resources = self._collect_lambda_resources(session)
|
114
|
-
elif service ==
|
124
|
+
elif service == "dynamodb":
|
115
125
|
resources = self._collect_dynamodb_resources(session)
|
116
|
-
elif service ==
|
126
|
+
elif service == "vpc":
|
117
127
|
resources = self._collect_vpc_resources(session)
|
118
|
-
elif service ==
|
128
|
+
elif service == "iam":
|
119
129
|
resources = self._collect_iam_resources(session)
|
120
130
|
# Add more services as needed
|
121
|
-
|
122
|
-
self.discovery_metrics[
|
123
|
-
|
131
|
+
|
132
|
+
self.discovery_metrics["total_resources"] += len(resources)
|
133
|
+
|
124
134
|
except Exception as e:
|
125
135
|
print(f"Error in {service} collection: {e}")
|
126
|
-
|
136
|
+
|
127
137
|
return resources
|
128
|
-
|
138
|
+
|
129
139
|
def _collect_ec2_resources(self, session) -> List[Dict]:
|
130
140
|
"""Collect EC2 instances with cost and utilization data."""
|
131
|
-
ec2 = session.client(
|
141
|
+
ec2 = session.client("ec2")
|
132
142
|
resources = []
|
133
|
-
|
143
|
+
|
134
144
|
try:
|
135
145
|
# Get all instances
|
136
146
|
response = ec2.describe_instances()
|
137
|
-
for reservation in response.get(
|
138
|
-
for instance in reservation.get(
|
139
|
-
resources.append(
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
147
|
+
for reservation in response.get("Reservations", []):
|
148
|
+
for instance in reservation.get("Instances", []):
|
149
|
+
resources.append(
|
150
|
+
{
|
151
|
+
"resource_type": "ec2_instance",
|
152
|
+
"resource_id": instance["InstanceId"],
|
153
|
+
"state": instance["State"]["Name"],
|
154
|
+
"instance_type": instance["InstanceType"],
|
155
|
+
"launch_time": str(instance.get("LaunchTime", "")),
|
156
|
+
"tags": {tag["Key"]: tag["Value"] for tag in instance.get("Tags", [])},
|
157
|
+
"cost_data": self._estimate_ec2_cost(instance["InstanceType"]),
|
158
|
+
"optimization_potential": self._analyze_ec2_optimization(instance),
|
159
|
+
}
|
160
|
+
)
|
149
161
|
except Exception as e:
|
150
162
|
print(f"EC2 collection error: {e}")
|
151
|
-
|
163
|
+
|
152
164
|
return resources
|
153
|
-
|
165
|
+
|
154
166
|
def _collect_s3_resources(self, session) -> List[Dict]:
|
155
167
|
"""Collect S3 buckets with storage analysis."""
|
156
|
-
s3 = session.client(
|
168
|
+
s3 = session.client("s3")
|
157
169
|
resources = []
|
158
|
-
|
170
|
+
|
159
171
|
try:
|
160
172
|
response = s3.list_buckets()
|
161
|
-
for bucket in response.get(
|
173
|
+
for bucket in response.get("Buckets", []):
|
162
174
|
# Get bucket details
|
163
175
|
bucket_info = {
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
176
|
+
"resource_type": "s3_bucket",
|
177
|
+
"resource_id": bucket["Name"],
|
178
|
+
"creation_date": str(bucket["CreationDate"]),
|
179
|
+
"storage_class_analysis": self._analyze_s3_storage_class(session, bucket["Name"]),
|
168
180
|
}
|
169
181
|
resources.append(bucket_info)
|
170
182
|
except Exception as e:
|
171
183
|
print(f"S3 collection error: {e}")
|
172
|
-
|
184
|
+
|
173
185
|
return resources
|
174
|
-
|
186
|
+
|
175
187
|
def _generate_summary(self, resources: Dict) -> Dict:
|
176
188
|
"""Generate comprehensive summary with cost insights."""
|
177
189
|
summary = {
|
178
|
-
|
179
|
-
|
180
|
-
len(service_resources)
|
181
|
-
for account in resources.values()
|
182
|
-
for service_resources in account.values()
|
190
|
+
"total_accounts": len(resources),
|
191
|
+
"total_resources": sum(
|
192
|
+
len(service_resources) for account in resources.values() for service_resources in account.values()
|
183
193
|
),
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
194
|
+
"by_service": {},
|
195
|
+
"cost_optimization_potential": 0,
|
196
|
+
"compliance_issues": 0,
|
197
|
+
"security_findings": 0,
|
188
198
|
}
|
189
|
-
|
199
|
+
|
190
200
|
# Count resources by service
|
191
201
|
for account_resources in resources.values():
|
192
202
|
for service, service_resources in account_resources.items():
|
193
|
-
if service not in summary[
|
194
|
-
summary[
|
195
|
-
summary[
|
196
|
-
|
203
|
+
if service not in summary["by_service"]:
|
204
|
+
summary["by_service"][service] = 0
|
205
|
+
summary["by_service"][service] += len(service_resources)
|
206
|
+
|
197
207
|
return summary
|
198
|
-
|
208
|
+
|
199
209
|
def generate_visualization(self, results: Dict) -> str:
|
200
210
|
"""
|
201
211
|
Generate HTML visualization of discovered resources.
|
202
|
-
|
212
|
+
|
203
213
|
Returns:
|
204
214
|
Path to generated HTML file
|
205
215
|
"""
|
206
216
|
html_content = self._create_visualization_html(results)
|
207
|
-
|
208
|
-
output_path =
|
209
|
-
with open(output_path,
|
217
|
+
|
218
|
+
output_path = "artifacts/sprint-1/inventory/visualization.html"
|
219
|
+
with open(output_path, "w") as f:
|
210
220
|
f.write(html_content)
|
211
|
-
|
221
|
+
|
212
222
|
print(f"Visualization generated: {output_path}")
|
213
223
|
return output_path
|
214
|
-
|
224
|
+
|
215
225
|
def _create_visualization_html(self, results: Dict) -> str:
|
216
226
|
"""Create interactive HTML dashboard with D3.js visualization."""
|
217
227
|
html = f"""
|
218
228
|
<!DOCTYPE html>
|
219
229
|
<html>
|
220
230
|
<head>
|
221
|
-
<title>AWS
|
231
|
+
<title>AWS Multi-Account Inventory - Sprint 1</title>
|
222
232
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
223
233
|
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
224
234
|
<style>
|
@@ -238,15 +248,15 @@ class ComprehensiveCollector(BaseResourceCollector):
|
|
238
248
|
<div class="metrics">
|
239
249
|
<div class="metric">
|
240
250
|
<h3>Total Accounts</h3>
|
241
|
-
<div class="value">{results[
|
251
|
+
<div class="value">{results["summary"]["total_accounts"]}</div>
|
242
252
|
</div>
|
243
253
|
<div class="metric">
|
244
254
|
<h3>Total Resources</h3>
|
245
|
-
<div class="value">{results[
|
255
|
+
<div class="value">{results["summary"]["total_resources"]}</div>
|
246
256
|
</div>
|
247
257
|
<div class="metric">
|
248
258
|
<h3>Services Discovered</h3>
|
249
|
-
<div class="value">{len(results[
|
259
|
+
<div class="value">{len(results["summary"]["by_service"])}</div>
|
250
260
|
</div>
|
251
261
|
</div>
|
252
262
|
|
@@ -258,7 +268,7 @@ class ComprehensiveCollector(BaseResourceCollector):
|
|
258
268
|
|
259
269
|
<script>
|
260
270
|
// Resource distribution chart
|
261
|
-
var serviceData = {json.dumps(results[
|
271
|
+
var serviceData = {json.dumps(results["summary"]["by_service"])};
|
262
272
|
var data = [{{
|
263
273
|
x: Object.keys(serviceData),
|
264
274
|
y: Object.values(serviceData),
|
@@ -280,156 +290,153 @@ class ComprehensiveCollector(BaseResourceCollector):
|
|
280
290
|
</html>
|
281
291
|
"""
|
282
292
|
return html
|
283
|
-
|
293
|
+
|
284
294
|
def _save_results(self, results: Dict):
|
285
295
|
"""Save results to Sprint 1 artifacts directory."""
|
286
|
-
output_path =
|
287
|
-
with open(output_path,
|
296
|
+
output_path = "artifacts/sprint-1/inventory/resources.json"
|
297
|
+
with open(output_path, "w") as f:
|
288
298
|
json.dump(results, f, indent=2, default=str)
|
289
299
|
print(f"Inventory saved: {output_path}")
|
290
|
-
|
300
|
+
|
291
301
|
def _discover_all_accounts(self) -> List[str]:
|
292
302
|
"""Discover all accounts in the organization (enhanced for multi-account org)."""
|
293
303
|
# Enhanced mock for comprehensive organization discovery
|
294
|
-
base_accounts = [
|
295
|
-
|
304
|
+
base_accounts = ["123456789012", "234567890123", "345678901234"]
|
305
|
+
|
296
306
|
# Generate additional accounts to simulate large organization
|
297
307
|
additional_accounts = []
|
298
308
|
for i in range(4, 61): # Up to multi-account total
|
299
309
|
account_id = str(100000000000 + i * 11111)
|
300
310
|
additional_accounts.append(account_id)
|
301
|
-
|
311
|
+
|
302
312
|
all_accounts = base_accounts + additional_accounts
|
303
313
|
print(f"🏢 Organization Discovery: {len(all_accounts)} accounts found")
|
304
314
|
return all_accounts
|
305
|
-
|
315
|
+
|
306
316
|
def _get_account_session(self, account_id: str):
|
307
317
|
"""Get boto3 session for a specific account."""
|
308
318
|
# In production, this would assume cross-account role
|
309
319
|
# For now, return default session
|
310
320
|
return boto3.Session(profile_name=self.profile) if self.profile else boto3.Session()
|
311
|
-
|
321
|
+
|
312
322
|
def _estimate_ec2_cost(self, instance_type: str) -> Dict:
|
313
323
|
"""Estimate monthly cost for EC2 instance type."""
|
314
324
|
# Simplified cost estimation - in production use AWS Pricing API
|
315
325
|
hourly_costs = {
|
316
|
-
|
317
|
-
|
318
|
-
|
326
|
+
"t2.micro": 0.0116,
|
327
|
+
"t2.small": 0.023,
|
328
|
+
"t2.medium": 0.046,
|
329
|
+
"t3.micro": 0.0104,
|
330
|
+
"t3.small": 0.021,
|
331
|
+
"t3.medium": 0.042,
|
332
|
+
"m5.large": 0.096,
|
333
|
+
"m5.xlarge": 0.192,
|
334
|
+
"m5.2xlarge": 0.384,
|
319
335
|
}
|
320
336
|
hourly = hourly_costs.get(instance_type, 0.1)
|
321
|
-
return {
|
322
|
-
|
323
|
-
'monthly': hourly * 24 * 30,
|
324
|
-
'annual': hourly * 24 * 365
|
325
|
-
}
|
326
|
-
|
337
|
+
return {"hourly": hourly, "monthly": hourly * 24 * 30, "annual": hourly * 24 * 365}
|
338
|
+
|
327
339
|
def _analyze_ec2_optimization(self, instance: Dict) -> Dict:
|
328
340
|
"""Analyze EC2 instance for optimization potential."""
|
329
341
|
return {
|
330
|
-
|
331
|
-
|
342
|
+
"rightsizing_potential": "high" if "large" in instance["InstanceType"] else "low",
|
343
|
+
"savings_estimate": 0.3 if "large" in instance["InstanceType"] else 0.1,
|
332
344
|
}
|
333
|
-
|
345
|
+
|
334
346
|
def _analyze_s3_storage_class(self, session, bucket_name: str) -> Dict:
|
335
347
|
"""Analyze S3 bucket for storage class optimization."""
|
336
|
-
return {
|
337
|
-
|
338
|
-
'recommended_class': 'INTELLIGENT_TIERING',
|
339
|
-
'potential_savings': '30%'
|
340
|
-
}
|
341
|
-
|
348
|
+
return {"current_class": "STANDARD", "recommended_class": "INTELLIGENT_TIERING", "potential_savings": "30%"}
|
349
|
+
|
342
350
|
def _collect_rds_resources(self, session) -> List[Dict]:
|
343
351
|
"""Collect RDS instances."""
|
344
|
-
rds = session.client(
|
352
|
+
rds = session.client("rds")
|
345
353
|
resources = []
|
346
|
-
|
354
|
+
|
347
355
|
try:
|
348
356
|
response = rds.describe_db_instances()
|
349
|
-
for db in response.get(
|
350
|
-
resources.append(
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
+
for db in response.get("DBInstances", []):
|
358
|
+
resources.append(
|
359
|
+
{
|
360
|
+
"resource_type": "rds_instance",
|
361
|
+
"resource_id": db["DBInstanceIdentifier"],
|
362
|
+
"engine": db["Engine"],
|
363
|
+
"instance_class": db["DBInstanceClass"],
|
364
|
+
"storage_gb": db["AllocatedStorage"],
|
365
|
+
}
|
366
|
+
)
|
357
367
|
except Exception as e:
|
358
368
|
print(f"RDS collection error: {e}")
|
359
|
-
|
369
|
+
|
360
370
|
return resources
|
361
|
-
|
371
|
+
|
362
372
|
def _collect_lambda_resources(self, session) -> List[Dict]:
|
363
373
|
"""Collect Lambda functions."""
|
364
|
-
lambda_client = session.client(
|
374
|
+
lambda_client = session.client("lambda")
|
365
375
|
resources = []
|
366
|
-
|
376
|
+
|
367
377
|
try:
|
368
378
|
response = lambda_client.list_functions()
|
369
|
-
for func in response.get(
|
370
|
-
resources.append(
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
379
|
+
for func in response.get("Functions", []):
|
380
|
+
resources.append(
|
381
|
+
{
|
382
|
+
"resource_type": "lambda_function",
|
383
|
+
"resource_id": func["FunctionName"],
|
384
|
+
"runtime": func["Runtime"],
|
385
|
+
"memory_mb": func["MemorySize"],
|
386
|
+
"timeout": func["Timeout"],
|
387
|
+
}
|
388
|
+
)
|
377
389
|
except Exception as e:
|
378
390
|
print(f"Lambda collection error: {e}")
|
379
|
-
|
391
|
+
|
380
392
|
return resources
|
381
|
-
|
393
|
+
|
382
394
|
def _collect_dynamodb_resources(self, session) -> List[Dict]:
|
383
395
|
"""Collect DynamoDB tables."""
|
384
|
-
dynamodb = session.client(
|
396
|
+
dynamodb = session.client("dynamodb")
|
385
397
|
resources = []
|
386
|
-
|
398
|
+
|
387
399
|
try:
|
388
400
|
response = dynamodb.list_tables()
|
389
|
-
for table_name in response.get(
|
390
|
-
resources.append({
|
391
|
-
'resource_type': 'dynamodb_table',
|
392
|
-
'resource_id': table_name
|
393
|
-
})
|
401
|
+
for table_name in response.get("TableNames", []):
|
402
|
+
resources.append({"resource_type": "dynamodb_table", "resource_id": table_name})
|
394
403
|
except Exception as e:
|
395
404
|
print(f"DynamoDB collection error: {e}")
|
396
|
-
|
405
|
+
|
397
406
|
return resources
|
398
|
-
|
407
|
+
|
399
408
|
def _collect_vpc_resources(self, session) -> List[Dict]:
|
400
409
|
"""Collect VPC resources."""
|
401
|
-
ec2 = session.client(
|
410
|
+
ec2 = session.client("ec2")
|
402
411
|
resources = []
|
403
|
-
|
412
|
+
|
404
413
|
try:
|
405
414
|
response = ec2.describe_vpcs()
|
406
|
-
for vpc in response.get(
|
407
|
-
resources.append(
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
415
|
+
for vpc in response.get("Vpcs", []):
|
416
|
+
resources.append(
|
417
|
+
{
|
418
|
+
"resource_type": "vpc",
|
419
|
+
"resource_id": vpc["VpcId"],
|
420
|
+
"cidr_block": vpc["CidrBlock"],
|
421
|
+
"is_default": vpc.get("IsDefault", False),
|
422
|
+
}
|
423
|
+
)
|
413
424
|
except Exception as e:
|
414
425
|
print(f"VPC collection error: {e}")
|
415
|
-
|
426
|
+
|
416
427
|
return resources
|
417
|
-
|
428
|
+
|
418
429
|
def _collect_iam_resources(self, session) -> List[Dict]:
|
419
430
|
"""Collect IAM resources."""
|
420
|
-
iam = session.client(
|
431
|
+
iam = session.client("iam")
|
421
432
|
resources = []
|
422
|
-
|
433
|
+
|
423
434
|
try:
|
424
435
|
# Collect IAM roles
|
425
436
|
response = iam.list_roles()
|
426
|
-
for role in response.get(
|
427
|
-
resources.append({
|
428
|
-
'resource_type': 'iam_role',
|
429
|
-
'resource_id': role['RoleName'],
|
430
|
-
'arn': role['Arn']
|
431
|
-
})
|
437
|
+
for role in response.get("Roles", []):
|
438
|
+
resources.append({"resource_type": "iam_role", "resource_id": role["RoleName"], "arn": role["Arn"]})
|
432
439
|
except Exception as e:
|
433
440
|
print(f"IAM collection error: {e}")
|
434
|
-
|
435
|
-
return resources
|
441
|
+
|
442
|
+
return resources
|