runbooks 0.9.5__py3-none-any.whl → 0.9.7__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 (43) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/_platform/__init__.py +19 -0
  3. runbooks/_platform/core/runbooks_wrapper.py +478 -0
  4. runbooks/cloudops/cost_optimizer.py +330 -0
  5. runbooks/cloudops/interfaces.py +3 -3
  6. runbooks/finops/README.md +1 -1
  7. runbooks/finops/automation_core.py +643 -0
  8. runbooks/finops/business_cases.py +414 -16
  9. runbooks/finops/cli.py +23 -0
  10. runbooks/finops/compute_cost_optimizer.py +865 -0
  11. runbooks/finops/ebs_cost_optimizer.py +718 -0
  12. runbooks/finops/ebs_optimizer.py +909 -0
  13. runbooks/finops/elastic_ip_optimizer.py +675 -0
  14. runbooks/finops/embedded_mcp_validator.py +330 -14
  15. runbooks/finops/enterprise_wrappers.py +827 -0
  16. runbooks/finops/legacy_migration.py +730 -0
  17. runbooks/finops/nat_gateway_optimizer.py +1160 -0
  18. runbooks/finops/network_cost_optimizer.py +1387 -0
  19. runbooks/finops/notebook_utils.py +596 -0
  20. runbooks/finops/reservation_optimizer.py +956 -0
  21. runbooks/finops/validation_framework.py +753 -0
  22. runbooks/finops/workspaces_analyzer.py +593 -0
  23. runbooks/inventory/__init__.py +7 -0
  24. runbooks/inventory/collectors/aws_networking.py +357 -6
  25. runbooks/inventory/mcp_vpc_validator.py +1091 -0
  26. runbooks/inventory/vpc_analyzer.py +1107 -0
  27. runbooks/inventory/vpc_architecture_validator.py +939 -0
  28. runbooks/inventory/vpc_dependency_analyzer.py +845 -0
  29. runbooks/main.py +425 -39
  30. runbooks/operate/vpc_operations.py +1479 -16
  31. runbooks/remediation/commvault_ec2_analysis.py +5 -4
  32. runbooks/remediation/dynamodb_optimize.py +2 -2
  33. runbooks/remediation/rds_instance_list.py +1 -1
  34. runbooks/remediation/rds_snapshot_list.py +5 -4
  35. runbooks/remediation/workspaces_list.py +2 -2
  36. runbooks/security/compliance_automation.py +2 -2
  37. runbooks/vpc/tests/test_config.py +2 -2
  38. {runbooks-0.9.5.dist-info → runbooks-0.9.7.dist-info}/METADATA +1 -1
  39. {runbooks-0.9.5.dist-info → runbooks-0.9.7.dist-info}/RECORD +43 -24
  40. {runbooks-0.9.5.dist-info → runbooks-0.9.7.dist-info}/WHEEL +0 -0
  41. {runbooks-0.9.5.dist-info → runbooks-0.9.7.dist-info}/entry_points.txt +0 -0
  42. {runbooks-0.9.5.dist-info → runbooks-0.9.7.dist-info}/licenses/LICENSE +0 -0
  43. {runbooks-0.9.5.dist-info → runbooks-0.9.7.dist-info}/top_level.txt +0 -0
@@ -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
- This module preserves all original AWS Cloud Foundations functionality.
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