runbooks 0.9.6__py3-none-any.whl → 0.9.8__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/_platform/__init__.py +19 -0
- runbooks/_platform/core/runbooks_wrapper.py +478 -0
- runbooks/cloudops/cost_optimizer.py +330 -0
- runbooks/cloudops/interfaces.py +3 -3
- runbooks/common/mcp_integration.py +174 -0
- runbooks/common/performance_monitor.py +4 -4
- runbooks/enterprise/__init__.py +18 -10
- runbooks/enterprise/security.py +708 -0
- runbooks/finops/README.md +1 -1
- runbooks/finops/automation_core.py +643 -0
- runbooks/finops/business_cases.py +414 -16
- runbooks/finops/cli.py +23 -0
- runbooks/finops/compute_cost_optimizer.py +865 -0
- runbooks/finops/ebs_cost_optimizer.py +718 -0
- runbooks/finops/ebs_optimizer.py +909 -0
- runbooks/finops/elastic_ip_optimizer.py +675 -0
- runbooks/finops/embedded_mcp_validator.py +330 -14
- runbooks/finops/enhanced_dashboard_runner.py +2 -1
- runbooks/finops/enterprise_wrappers.py +827 -0
- runbooks/finops/finops_dashboard.py +322 -11
- runbooks/finops/legacy_migration.py +730 -0
- runbooks/finops/nat_gateway_optimizer.py +1160 -0
- runbooks/finops/network_cost_optimizer.py +1387 -0
- runbooks/finops/notebook_utils.py +596 -0
- runbooks/finops/reservation_optimizer.py +956 -0
- runbooks/finops/single_dashboard.py +16 -16
- runbooks/finops/validation_framework.py +753 -0
- runbooks/finops/vpc_cleanup_optimizer.py +817 -0
- runbooks/finops/workspaces_analyzer.py +1 -1
- runbooks/inventory/__init__.py +7 -0
- runbooks/inventory/collectors/aws_networking.py +357 -6
- runbooks/inventory/mcp_vpc_validator.py +1091 -0
- runbooks/inventory/vpc_analyzer.py +1107 -0
- runbooks/inventory/vpc_architecture_validator.py +939 -0
- runbooks/inventory/vpc_dependency_analyzer.py +845 -0
- runbooks/main.py +487 -40
- runbooks/operate/vpc_operations.py +1485 -16
- runbooks/remediation/commvault_ec2_analysis.py +1 -1
- runbooks/remediation/dynamodb_optimize.py +2 -2
- runbooks/remediation/rds_instance_list.py +1 -1
- runbooks/remediation/rds_snapshot_list.py +1 -1
- runbooks/remediation/workspaces_list.py +2 -2
- runbooks/security/compliance_automation.py +2 -2
- runbooks/vpc/__init__.py +12 -0
- runbooks/vpc/cleanup_wrapper.py +757 -0
- runbooks/vpc/cost_engine.py +527 -3
- runbooks/vpc/networking_wrapper.py +29 -29
- runbooks/vpc/runbooks_adapter.py +479 -0
- runbooks/vpc/tests/test_config.py +2 -2
- runbooks/vpc/vpc_cleanup_integration.py +2629 -0
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/METADATA +1 -1
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/RECORD +57 -34
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/WHEEL +0 -0
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/top_level.txt +0 -0
@@ -372,7 +372,7 @@ class WorkSpacesCostAnalyzer:
|
|
372
372
|
|
373
373
|
if not output_file:
|
374
374
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
375
|
-
output_file = f"
|
375
|
+
output_file = f"./tmp/workspaces_analysis_{timestamp}.{output_format}"
|
376
376
|
|
377
377
|
export_data = {
|
378
378
|
"summary": summary.to_dict(),
|
runbooks/inventory/__init__.py
CHANGED
@@ -35,6 +35,9 @@ from runbooks.inventory.utils.aws_helpers import get_boto3_session, validate_aws
|
|
35
35
|
# Utilities
|
36
36
|
from runbooks.inventory.utils.validation import validate_aws_account_id, validate_resource_types
|
37
37
|
|
38
|
+
# VPC Module Migration Integration
|
39
|
+
from runbooks.inventory.vpc_analyzer import VPCAnalyzer, VPCDiscoveryResult, AWSOAnalysis
|
40
|
+
|
38
41
|
# Import centralized version from main runbooks package
|
39
42
|
from runbooks import __version__
|
40
43
|
|
@@ -58,6 +61,10 @@ __all__ = [
|
|
58
61
|
"validate_resource_types",
|
59
62
|
"get_boto3_session",
|
60
63
|
"validate_aws_credentials",
|
64
|
+
# VPC Module Migration Integration
|
65
|
+
"VPCAnalyzer",
|
66
|
+
"VPCDiscoveryResult",
|
67
|
+
"AWSOAnalysis",
|
61
68
|
# Version
|
62
69
|
"__version__",
|
63
70
|
]
|
@@ -1,22 +1,33 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
|
-
AWS Networking Service Collectors
|
3
|
+
Enhanced AWS Networking Service Collectors - VPC Module Migration Integration
|
4
|
+
|
5
|
+
Strategic Enhancement: Migrated VPC discovery capabilities from vpc module following
|
6
|
+
"Do one thing and do it well" principle with comprehensive networking topology analysis.
|
4
7
|
|
5
8
|
Maintains 100% compatibility with AWS Cloud Foundations inventory-scripts:
|
6
|
-
- all_my_vpcs.py -> VPCCollector
|
9
|
+
- all_my_vpcs.py -> Enhanced VPCCollector with topology mapping
|
7
10
|
- all_my_subnets.py -> SubnetCollector
|
8
|
-
- all_my_elbs.py -> ELBCollector
|
11
|
+
- all_my_elbs.py -> ELBCollector
|
9
12
|
- all_my_enis.py -> ENICollector
|
10
13
|
- all_my_phzs.py -> Route53Collector
|
11
14
|
|
12
|
-
|
15
|
+
NEW CAPABILITIES (migrated from vpc module):
|
16
|
+
- NetworkTopologyCollector -> VPC topology mapping and cross-region relationships
|
17
|
+
- NetworkHeatMapCollector -> Network dependency analysis and visualization
|
18
|
+
- TransitGatewayCollector -> Transit Gateway and VPC peering discovery
|
19
|
+
- NATGatewayCollector -> NAT Gateway discovery and usage analysis
|
20
|
+
|
21
|
+
This module preserves all original AWS Cloud Foundations functionality while adding
|
22
|
+
enterprise VPC topology discovery capabilities from the consolidated vpc module.
|
13
23
|
"""
|
14
24
|
|
15
25
|
from dataclasses import dataclass, field
|
16
|
-
from datetime import datetime
|
17
|
-
from typing import Any, Dict, List, Optional
|
26
|
+
from datetime import datetime, timedelta
|
27
|
+
from typing import Any, Dict, List, Optional, Tuple
|
18
28
|
|
19
29
|
import boto3
|
30
|
+
from botocore.exceptions import ClientError
|
20
31
|
|
21
32
|
from ..models.account import AWSAccount
|
22
33
|
from ..models.resource import AWSResource, ResourceState, ResourceType
|
@@ -24,6 +35,9 @@ from ..utils.aws_helpers import aws_api_retry, get_boto3_session
|
|
24
35
|
from ..utils.validation import validate_aws_account_id, validate_aws_region
|
25
36
|
from .base import BaseResourceCollector
|
26
37
|
|
38
|
+
# Import Rich utils for consistent formatting
|
39
|
+
from ...common.rich_utils import console, print_header, print_success, print_info
|
40
|
+
|
27
41
|
|
28
42
|
class VPCCollector(BaseResourceCollector):
|
29
43
|
"""
|
@@ -273,3 +287,340 @@ if __name__ == "__main__":
|
|
273
287
|
for subnet in subnets:
|
274
288
|
public_str = " (Public)" if subnet["MapPublicIpOnLaunch"] else " (Private)"
|
275
289
|
print(f" {subnet['SubnetId']} ({subnet['CidrBlock']}){public_str} in {subnet['AvailabilityZone']}")
|
290
|
+
|
291
|
+
|
292
|
+
# ============================================================================
|
293
|
+
# NEW VPC MODULE MIGRATION - Network Topology Discovery
|
294
|
+
# ============================================================================
|
295
|
+
|
296
|
+
class NetworkTopologyCollector(BaseResourceCollector):
|
297
|
+
"""
|
298
|
+
Network Topology Collector - Migrated from vpc module networking_wrapper.py
|
299
|
+
|
300
|
+
Provides comprehensive VPC topology mapping and cross-region relationships
|
301
|
+
following enterprise discovery patterns with Rich CLI integration.
|
302
|
+
"""
|
303
|
+
|
304
|
+
def __init__(self, session: Optional[boto3.Session] = None):
|
305
|
+
super().__init__(resource_type=ResourceType.VPC, session=session) # Using VPC as base type
|
306
|
+
|
307
|
+
@aws_api_retry
|
308
|
+
def collect_network_topology(self, account: AWSAccount, regions: List[str] = None) -> Dict[str, Any]:
|
309
|
+
"""
|
310
|
+
Collect comprehensive network topology across regions.
|
311
|
+
|
312
|
+
Args:
|
313
|
+
account: AWS account to analyze
|
314
|
+
regions: List of regions to analyze (default: all available)
|
315
|
+
|
316
|
+
Returns:
|
317
|
+
Dictionary with complete network topology mapping
|
318
|
+
"""
|
319
|
+
if not regions:
|
320
|
+
regions = ["us-east-1", "us-west-2", "eu-west-1"] # Default enterprise regions
|
321
|
+
|
322
|
+
print_header("Network Topology Discovery", "v0.9.1")
|
323
|
+
topology = {
|
324
|
+
"account_id": account.account_id,
|
325
|
+
"timestamp": datetime.utcnow().isoformat(),
|
326
|
+
"regions_analyzed": regions,
|
327
|
+
"vpc_topology": {},
|
328
|
+
"cross_region_connections": [],
|
329
|
+
"transit_gateways": {},
|
330
|
+
"nat_gateways": {},
|
331
|
+
"vpc_peering": [],
|
332
|
+
"total_vpcs": 0,
|
333
|
+
"total_subnets": 0,
|
334
|
+
"recommendations": []
|
335
|
+
}
|
336
|
+
|
337
|
+
for region in regions:
|
338
|
+
try:
|
339
|
+
print_info(f"Analyzing network topology in {region}")
|
340
|
+
region_topology = self._collect_region_topology(region, account)
|
341
|
+
topology["vpc_topology"][region] = region_topology
|
342
|
+
topology["total_vpcs"] += len(region_topology["vpcs"])
|
343
|
+
topology["total_subnets"] += len(region_topology["subnets"])
|
344
|
+
|
345
|
+
except Exception as e:
|
346
|
+
self.logger.error(f"Failed to collect topology from {region}: {e}")
|
347
|
+
continue
|
348
|
+
|
349
|
+
# Analyze cross-region connections
|
350
|
+
topology["cross_region_connections"] = self._analyze_cross_region_connections(topology["vpc_topology"])
|
351
|
+
topology["recommendations"] = self._generate_topology_recommendations(topology)
|
352
|
+
|
353
|
+
print_success(f"Network topology discovery completed: {topology['total_vpcs']} VPCs, {topology['total_subnets']} subnets")
|
354
|
+
return topology
|
355
|
+
|
356
|
+
def _collect_region_topology(self, region: str, account: AWSAccount) -> Dict[str, Any]:
|
357
|
+
"""Collect detailed network topology for a specific region."""
|
358
|
+
ec2 = self.session.client("ec2", region_name=region)
|
359
|
+
|
360
|
+
region_topology = {
|
361
|
+
"region": region,
|
362
|
+
"vpcs": [],
|
363
|
+
"subnets": [],
|
364
|
+
"route_tables": [],
|
365
|
+
"internet_gateways": [],
|
366
|
+
"nat_gateways": [],
|
367
|
+
"vpc_endpoints": [],
|
368
|
+
"network_interfaces": []
|
369
|
+
}
|
370
|
+
|
371
|
+
try:
|
372
|
+
# Collect VPCs with enhanced metadata
|
373
|
+
vpcs_response = ec2.describe_vpcs()
|
374
|
+
for vpc in vpcs_response["Vpcs"]:
|
375
|
+
vpc_data = self._enhance_vpc_data(ec2, vpc, region, account)
|
376
|
+
region_topology["vpcs"].append(vpc_data)
|
377
|
+
|
378
|
+
# Collect NAT Gateways
|
379
|
+
nat_response = ec2.describe_nat_gateways()
|
380
|
+
for nat in nat_response["NatGateways"]:
|
381
|
+
if nat["State"] != "deleted":
|
382
|
+
nat_data = self._enhance_nat_gateway_data(ec2, nat, region)
|
383
|
+
region_topology["nat_gateways"].append(nat_data)
|
384
|
+
|
385
|
+
# Collect VPC Endpoints
|
386
|
+
endpoints_response = ec2.describe_vpc_endpoints()
|
387
|
+
for endpoint in endpoints_response["VpcEndpoints"]:
|
388
|
+
endpoint_data = self._enhance_vpc_endpoint_data(endpoint, region)
|
389
|
+
region_topology["vpc_endpoints"].append(endpoint_data)
|
390
|
+
|
391
|
+
except ClientError as e:
|
392
|
+
self.logger.error(f"AWS API error in {region}: {e}")
|
393
|
+
|
394
|
+
return region_topology
|
395
|
+
|
396
|
+
def _enhance_vpc_data(self, ec2_client, vpc: Dict[str, Any], region: str, account: AWSAccount) -> Dict[str, Any]:
|
397
|
+
"""Enhance VPC data with topology relationships."""
|
398
|
+
tags = {tag["Key"]: tag["Value"] for tag in vpc.get("Tags", [])}
|
399
|
+
|
400
|
+
enhanced_vpc = {
|
401
|
+
"vpc_id": vpc["VpcId"],
|
402
|
+
"cidr_block": vpc["CidrBlock"],
|
403
|
+
"state": vpc["State"],
|
404
|
+
"region": region,
|
405
|
+
"account_id": account.account_id,
|
406
|
+
"tags": tags,
|
407
|
+
"name": tags.get("Name", vpc["VpcId"]),
|
408
|
+
"is_default": vpc.get("IsDefault", False),
|
409
|
+
"dhcp_options_id": vpc.get("DhcpOptionsId"),
|
410
|
+
"instance_tenancy": vpc.get("InstanceTenancy"),
|
411
|
+
"subnets": [],
|
412
|
+
"route_tables": [],
|
413
|
+
"internet_gateways": [],
|
414
|
+
"security_groups": [],
|
415
|
+
"network_acls": []
|
416
|
+
}
|
417
|
+
|
418
|
+
# Get associated subnets
|
419
|
+
try:
|
420
|
+
subnets_response = ec2_client.describe_subnets(
|
421
|
+
Filters=[{"Name": "vpc-id", "Values": [vpc["VpcId"]]}]
|
422
|
+
)
|
423
|
+
for subnet in subnets_response["Subnets"]:
|
424
|
+
subnet_tags = {tag["Key"]: tag["Value"] for tag in subnet.get("Tags", [])}
|
425
|
+
enhanced_vpc["subnets"].append({
|
426
|
+
"subnet_id": subnet["SubnetId"],
|
427
|
+
"cidr_block": subnet["CidrBlock"],
|
428
|
+
"availability_zone": subnet["AvailabilityZone"],
|
429
|
+
"available_ip_address_count": subnet["AvailableIpAddressCount"],
|
430
|
+
"map_public_ip_on_launch": subnet.get("MapPublicIpOnLaunch", False),
|
431
|
+
"state": subnet["State"],
|
432
|
+
"tags": subnet_tags,
|
433
|
+
"name": subnet_tags.get("Name", subnet["SubnetId"])
|
434
|
+
})
|
435
|
+
except ClientError as e:
|
436
|
+
self.logger.warning(f"Failed to get subnets for VPC {vpc['VpcId']}: {e}")
|
437
|
+
|
438
|
+
return enhanced_vpc
|
439
|
+
|
440
|
+
def _enhance_nat_gateway_data(self, ec2_client, nat: Dict[str, Any], region: str) -> Dict[str, Any]:
|
441
|
+
"""Enhance NAT Gateway data with usage and cost implications."""
|
442
|
+
tags = {tag["Key"]: tag["Value"] for tag in nat.get("Tags", [])}
|
443
|
+
|
444
|
+
return {
|
445
|
+
"nat_gateway_id": nat["NatGatewayId"],
|
446
|
+
"state": nat["State"],
|
447
|
+
"vpc_id": nat.get("VpcId"),
|
448
|
+
"subnet_id": nat.get("SubnetId"),
|
449
|
+
"region": region,
|
450
|
+
"create_time": nat.get("CreateTime"),
|
451
|
+
"connectivity_type": nat.get("ConnectivityType", "public"),
|
452
|
+
"tags": tags,
|
453
|
+
"name": tags.get("Name", nat["NatGatewayId"]),
|
454
|
+
# Enhanced with network interface information
|
455
|
+
"network_interface_id": nat.get("NatGatewayAddresses", [{}])[0].get("NetworkInterfaceId"),
|
456
|
+
"public_ip": nat.get("NatGatewayAddresses", [{}])[0].get("PublicIp"),
|
457
|
+
"private_ip": nat.get("NatGatewayAddresses", [{}])[0].get("PrivateIp")
|
458
|
+
}
|
459
|
+
|
460
|
+
def _enhance_vpc_endpoint_data(self, endpoint: Dict[str, Any], region: str) -> Dict[str, Any]:
|
461
|
+
"""Enhance VPC Endpoint data with service and cost information."""
|
462
|
+
tags = {tag["Key"]: tag["Value"] for tag in endpoint.get("Tags", [])}
|
463
|
+
|
464
|
+
return {
|
465
|
+
"vpc_endpoint_id": endpoint["VpcEndpointId"],
|
466
|
+
"vpc_id": endpoint.get("VpcId"),
|
467
|
+
"service_name": endpoint.get("ServiceName"),
|
468
|
+
"vpc_endpoint_type": endpoint.get("VpcEndpointType"),
|
469
|
+
"state": endpoint.get("State"),
|
470
|
+
"region": region,
|
471
|
+
"creation_timestamp": endpoint.get("CreationTimestamp"),
|
472
|
+
"tags": tags,
|
473
|
+
"name": tags.get("Name", endpoint["VpcEndpointId"]),
|
474
|
+
"route_table_ids": endpoint.get("RouteTableIds", []),
|
475
|
+
"subnet_ids": endpoint.get("SubnetIds", []),
|
476
|
+
"policy_document": endpoint.get("PolicyDocument")
|
477
|
+
}
|
478
|
+
|
479
|
+
def _analyze_cross_region_connections(self, vpc_topology: Dict[str, Any]) -> List[Dict[str, Any]]:
|
480
|
+
"""Analyze potential cross-region network connections."""
|
481
|
+
connections = []
|
482
|
+
# This would be enhanced to detect VPC peering, Transit Gateway, etc.
|
483
|
+
# For now, return empty list as foundation
|
484
|
+
return connections
|
485
|
+
|
486
|
+
def _generate_topology_recommendations(self, topology: Dict[str, Any]) -> List[Dict[str, Any]]:
|
487
|
+
"""Generate network topology optimization recommendations."""
|
488
|
+
recommendations = []
|
489
|
+
|
490
|
+
# Basic recommendations based on topology analysis
|
491
|
+
total_nat_gateways = sum(
|
492
|
+
len(region_data.get("nat_gateways", []))
|
493
|
+
for region_data in topology["vpc_topology"].values()
|
494
|
+
)
|
495
|
+
|
496
|
+
if total_nat_gateways > topology["total_vpcs"]:
|
497
|
+
recommendations.append({
|
498
|
+
"type": "cost_optimization",
|
499
|
+
"priority": "high",
|
500
|
+
"description": f"Multiple NAT Gateways detected ({total_nat_gateways}) across {topology['total_vpcs']} VPCs",
|
501
|
+
"recommendation": "Consider consolidating NAT Gateways to reduce monthly costs",
|
502
|
+
"estimated_savings": f"${(total_nat_gateways - topology['total_vpcs']) * 45:.2f}/month"
|
503
|
+
})
|
504
|
+
|
505
|
+
return recommendations
|
506
|
+
|
507
|
+
|
508
|
+
class TransitGatewayCollector(BaseResourceCollector):
|
509
|
+
"""
|
510
|
+
Transit Gateway Collector - Enhanced discovery from vpc module
|
511
|
+
|
512
|
+
Provides comprehensive Transit Gateway and VPC peering discovery
|
513
|
+
with enterprise cost analysis integration.
|
514
|
+
"""
|
515
|
+
|
516
|
+
def __init__(self, session: Optional[boto3.Session] = None):
|
517
|
+
super().__init__(resource_type=ResourceType.VPC, session=session)
|
518
|
+
|
519
|
+
@aws_api_retry
|
520
|
+
def collect_transit_gateways(self, account: AWSAccount, regions: List[str] = None) -> Dict[str, Any]:
|
521
|
+
"""
|
522
|
+
Collect Transit Gateway configurations and attachments.
|
523
|
+
|
524
|
+
Args:
|
525
|
+
account: AWS account to analyze
|
526
|
+
regions: List of regions to analyze
|
527
|
+
|
528
|
+
Returns:
|
529
|
+
Dictionary with Transit Gateway analysis
|
530
|
+
"""
|
531
|
+
if not regions:
|
532
|
+
regions = ["us-east-1", "us-west-2", "eu-west-1"]
|
533
|
+
|
534
|
+
print_header("Transit Gateway Discovery", "v0.9.1")
|
535
|
+
tgw_analysis = {
|
536
|
+
"account_id": account.account_id,
|
537
|
+
"timestamp": datetime.utcnow().isoformat(),
|
538
|
+
"regions_analyzed": regions,
|
539
|
+
"transit_gateways": {},
|
540
|
+
"total_tgw": 0,
|
541
|
+
"total_attachments": 0,
|
542
|
+
"monthly_cost_estimate": 0,
|
543
|
+
"recommendations": []
|
544
|
+
}
|
545
|
+
|
546
|
+
for region in regions:
|
547
|
+
try:
|
548
|
+
print_info(f"Analyzing Transit Gateways in {region}")
|
549
|
+
ec2 = self.session.client("ec2", region_name=region)
|
550
|
+
|
551
|
+
# Get Transit Gateways
|
552
|
+
tgw_response = ec2.describe_transit_gateways()
|
553
|
+
region_tgws = []
|
554
|
+
|
555
|
+
for tgw in tgw_response["TransitGateways"]:
|
556
|
+
if tgw["State"] not in ["deleted", "deleting"]:
|
557
|
+
tgw_data = self._enhance_transit_gateway_data(ec2, tgw, region)
|
558
|
+
region_tgws.append(tgw_data)
|
559
|
+
tgw_analysis["total_tgw"] += 1
|
560
|
+
tgw_analysis["total_attachments"] += len(tgw_data["attachments"])
|
561
|
+
tgw_analysis["monthly_cost_estimate"] += 36.50 # Base TGW cost per month
|
562
|
+
|
563
|
+
tgw_analysis["transit_gateways"][region] = region_tgws
|
564
|
+
|
565
|
+
except ClientError as e:
|
566
|
+
self.logger.error(f"Failed to collect Transit Gateways from {region}: {e}")
|
567
|
+
continue
|
568
|
+
|
569
|
+
tgw_analysis["recommendations"] = self._generate_tgw_recommendations(tgw_analysis)
|
570
|
+
|
571
|
+
print_success(f"Transit Gateway discovery completed: {tgw_analysis['total_tgw']} TGWs, {tgw_analysis['total_attachments']} attachments")
|
572
|
+
return tgw_analysis
|
573
|
+
|
574
|
+
def _enhance_transit_gateway_data(self, ec2_client, tgw: Dict[str, Any], region: str) -> Dict[str, Any]:
|
575
|
+
"""Enhance Transit Gateway data with attachments and routing information."""
|
576
|
+
tags = {tag["Key"]: tag["Value"] for tag in tgw.get("Tags", [])}
|
577
|
+
|
578
|
+
tgw_data = {
|
579
|
+
"transit_gateway_id": tgw["TransitGatewayId"],
|
580
|
+
"state": tgw["State"],
|
581
|
+
"region": region,
|
582
|
+
"description": tgw.get("Description"),
|
583
|
+
"owner_id": tgw.get("OwnerId"),
|
584
|
+
"creation_time": tgw.get("CreationTime"),
|
585
|
+
"tags": tags,
|
586
|
+
"name": tags.get("Name", tgw["TransitGatewayId"]),
|
587
|
+
"attachments": [],
|
588
|
+
"route_tables": []
|
589
|
+
}
|
590
|
+
|
591
|
+
# Get Transit Gateway Attachments
|
592
|
+
try:
|
593
|
+
attachments_response = ec2_client.describe_transit_gateway_attachments(
|
594
|
+
Filters=[{"Name": "transit-gateway-id", "Values": [tgw["TransitGatewayId"]]}]
|
595
|
+
)
|
596
|
+
|
597
|
+
for attachment in attachments_response["TransitGatewayAttachments"]:
|
598
|
+
attachment_tags = {tag["Key"]: tag["Value"] for tag in attachment.get("Tags", [])}
|
599
|
+
tgw_data["attachments"].append({
|
600
|
+
"attachment_id": attachment["TransitGatewayAttachmentId"],
|
601
|
+
"resource_type": attachment["ResourceType"],
|
602
|
+
"resource_id": attachment.get("ResourceId"),
|
603
|
+
"state": attachment["State"],
|
604
|
+
"tags": attachment_tags,
|
605
|
+
"name": attachment_tags.get("Name", attachment["TransitGatewayAttachmentId"])
|
606
|
+
})
|
607
|
+
|
608
|
+
except ClientError as e:
|
609
|
+
self.logger.warning(f"Failed to get attachments for TGW {tgw['TransitGatewayId']}: {e}")
|
610
|
+
|
611
|
+
return tgw_data
|
612
|
+
|
613
|
+
def _generate_tgw_recommendations(self, tgw_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
|
614
|
+
"""Generate Transit Gateway optimization recommendations."""
|
615
|
+
recommendations = []
|
616
|
+
|
617
|
+
if tgw_analysis["total_tgw"] > 1:
|
618
|
+
recommendations.append({
|
619
|
+
"type": "cost_optimization",
|
620
|
+
"priority": "medium",
|
621
|
+
"description": f"Multiple Transit Gateways detected ({tgw_analysis['total_tgw']})",
|
622
|
+
"recommendation": "Consider consolidating Transit Gateways to reduce base costs",
|
623
|
+
"estimated_savings": f"${(tgw_analysis['total_tgw'] - 1) * 36.50:.2f}/month"
|
624
|
+
})
|
625
|
+
|
626
|
+
return recommendations
|