reverse-diagrams 1.3.4__py3-none-any.whl → 2.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.
@@ -0,0 +1,228 @@
1
+ """EC2 service plugin for generating EC2 infrastructure diagrams."""
2
+ import logging
3
+ from typing import Dict, Any, List
4
+
5
+ from src.plugins.base import AWSServicePlugin, PluginMetadata
6
+ from src.aws.client_manager import AWSClientManager
7
+ from src.models import DiagramConfig
8
+ from src.utils.concurrent import get_concurrent_processor
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class EC2Plugin(AWSServicePlugin):
14
+ """Plugin for AWS EC2 service diagram generation."""
15
+
16
+ @property
17
+ def metadata(self) -> PluginMetadata:
18
+ """Get plugin metadata."""
19
+ return PluginMetadata(
20
+ name="ec2",
21
+ version="1.0.0",
22
+ description="Generate diagrams for EC2 instances, VPCs, and related resources",
23
+ author="Reverse Diagrams Team",
24
+ aws_services=["ec2"],
25
+ dependencies=[]
26
+ )
27
+
28
+ def collect_data(self, client_manager: AWSClientManager, region: str, **kwargs) -> Dict[str, Any]:
29
+ """
30
+ Collect EC2 data from AWS.
31
+
32
+ Args:
33
+ client_manager: AWS client manager
34
+ region: AWS region
35
+ **kwargs: Additional parameters
36
+
37
+ Returns:
38
+ Dictionary containing EC2 data
39
+ """
40
+ logger.debug(f"Collecting EC2 data from region {region}")
41
+
42
+ data = {
43
+ "region": region,
44
+ "vpcs": [],
45
+ "instances": [],
46
+ "security_groups": [],
47
+ "subnets": [],
48
+ "internet_gateways": [],
49
+ "route_tables": []
50
+ }
51
+
52
+ try:
53
+ # Collect VPCs
54
+ vpcs_response = client_manager.call_api("ec2", "describe_vpcs")
55
+ data["vpcs"] = vpcs_response.get("Vpcs", [])
56
+ logger.debug(f"Found {len(data['vpcs'])} VPCs")
57
+
58
+ # Collect EC2 instances
59
+ instances_response = client_manager.call_api("ec2", "describe_instances")
60
+ instances = []
61
+ for reservation in instances_response.get("Reservations", []):
62
+ instances.extend(reservation.get("Instances", []))
63
+ data["instances"] = instances
64
+ logger.debug(f"Found {len(data['instances'])} EC2 instances")
65
+
66
+ # Collect Security Groups
67
+ sg_response = client_manager.call_api("ec2", "describe_security_groups")
68
+ data["security_groups"] = sg_response.get("SecurityGroups", [])
69
+ logger.debug(f"Found {len(data['security_groups'])} security groups")
70
+
71
+ # Collect Subnets
72
+ subnets_response = client_manager.call_api("ec2", "describe_subnets")
73
+ data["subnets"] = subnets_response.get("Subnets", [])
74
+ logger.debug(f"Found {len(data['subnets'])} subnets")
75
+
76
+ # Collect Internet Gateways
77
+ igw_response = client_manager.call_api("ec2", "describe_internet_gateways")
78
+ data["internet_gateways"] = igw_response.get("InternetGateways", [])
79
+ logger.debug(f"Found {len(data['internet_gateways'])} internet gateways")
80
+
81
+ # Collect Route Tables
82
+ rt_response = client_manager.call_api("ec2", "describe_route_tables")
83
+ data["route_tables"] = rt_response.get("RouteTables", [])
84
+ logger.debug(f"Found {len(data['route_tables'])} route tables")
85
+
86
+ except Exception as e:
87
+ logger.error(f"Failed to collect EC2 data: {e}")
88
+ raise
89
+
90
+ return data
91
+
92
+ def generate_diagram_code(self, data: Dict[str, Any], config: DiagramConfig) -> str:
93
+ """
94
+ Generate diagram code for EC2 resources.
95
+
96
+ Args:
97
+ data: EC2 data collected from AWS
98
+ config: Diagram configuration
99
+
100
+ Returns:
101
+ Python code for generating EC2 diagram
102
+ """
103
+ logger.debug("Generating EC2 diagram code")
104
+
105
+ code_lines = [
106
+ "from diagrams import Diagram, Cluster, Edge",
107
+ "from diagrams.aws.compute import EC2",
108
+ "from diagrams.aws.network import VPC, PrivateSubnet, PublicSubnet, InternetGateway, RouteTable",
109
+ "from diagrams.aws.security import SecurityGroup",
110
+ "",
111
+ f'with Diagram("{config.title}", show=False, direction="{config.direction}"):'
112
+ ]
113
+
114
+ # Group instances by VPC
115
+ vpc_instances = {}
116
+ for instance in data.get("instances", []):
117
+ vpc_id = instance.get("VpcId", "no-vpc")
118
+ if vpc_id not in vpc_instances:
119
+ vpc_instances[vpc_id] = []
120
+ vpc_instances[vpc_id].append(instance)
121
+
122
+ # Generate VPC clusters
123
+ for vpc in data.get("vpcs", []):
124
+ vpc_id = vpc["VpcId"]
125
+ vpc_name = self._get_resource_name(vpc)
126
+
127
+ code_lines.append(f' with Cluster("VPC: {vpc_name}"):')
128
+
129
+ # Add instances in this VPC
130
+ instances_in_vpc = vpc_instances.get(vpc_id, [])
131
+ for i, instance in enumerate(instances_in_vpc):
132
+ instance_name = self._get_resource_name(instance)
133
+ state = instance.get("State", {}).get("Name", "unknown")
134
+
135
+ code_lines.append(
136
+ f' instance_{i} = EC2("{instance_name}\\n{state}")'
137
+ )
138
+
139
+ # Add subnets in this VPC
140
+ vpc_subnets = [s for s in data.get("subnets", []) if s.get("VpcId") == vpc_id]
141
+ for subnet in vpc_subnets:
142
+ subnet_name = self._get_resource_name(subnet)
143
+ subnet_type = "Public" if self._is_public_subnet(subnet, data) else "Private"
144
+ subnet_class = "PublicSubnet" if subnet_type == "Public" else "PrivateSubnet"
145
+
146
+ code_lines.append(
147
+ f' {subnet_class}("{subnet_name}")'
148
+ )
149
+
150
+ # Add instances not in any VPC (EC2-Classic)
151
+ if "no-vpc" in vpc_instances:
152
+ code_lines.append(' with Cluster("EC2-Classic"):')
153
+ for i, instance in enumerate(vpc_instances["no-vpc"]):
154
+ instance_name = self._get_resource_name(instance)
155
+ state = instance.get("State", {}).get("Name", "unknown")
156
+
157
+ code_lines.append(
158
+ f' classic_instance_{i} = EC2("{instance_name}\\n{state}")'
159
+ )
160
+
161
+ return "\n".join(code_lines)
162
+
163
+ def _get_resource_name(self, resource: Dict[str, Any]) -> str:
164
+ """
165
+ Get a human-readable name for a resource.
166
+
167
+ Args:
168
+ resource: AWS resource dictionary
169
+
170
+ Returns:
171
+ Resource name
172
+ """
173
+ # Try to get name from tags
174
+ tags = resource.get("Tags", [])
175
+ for tag in tags:
176
+ if tag.get("Key") == "Name":
177
+ return tag.get("Value", "Unnamed")
178
+
179
+ # Fallback to resource ID
180
+ for id_key in ["InstanceId", "VpcId", "SubnetId", "GroupId"]:
181
+ if id_key in resource:
182
+ return resource[id_key]
183
+
184
+ return "Unknown"
185
+
186
+ def _is_public_subnet(self, subnet: Dict[str, Any], data: Dict[str, Any]) -> bool:
187
+ """
188
+ Determine if a subnet is public based on route tables.
189
+
190
+ Args:
191
+ subnet: Subnet dictionary
192
+ data: Full EC2 data
193
+
194
+ Returns:
195
+ True if subnet is public, False otherwise
196
+ """
197
+ subnet_id = subnet.get("SubnetId")
198
+
199
+ # Check route tables for internet gateway routes
200
+ for rt in data.get("route_tables", []):
201
+ # Check if this route table is associated with the subnet
202
+ associations = rt.get("Associations", [])
203
+ subnet_associated = any(
204
+ assoc.get("SubnetId") == subnet_id for assoc in associations
205
+ )
206
+
207
+ if subnet_associated:
208
+ # Check for internet gateway routes
209
+ routes = rt.get("Routes", [])
210
+ has_igw_route = any(
211
+ route.get("GatewayId", "").startswith("igw-")
212
+ for route in routes
213
+ )
214
+ if has_igw_route:
215
+ return True
216
+
217
+ return False
218
+
219
+ def get_required_permissions(self) -> List[str]:
220
+ """Get required AWS permissions for EC2 plugin."""
221
+ return [
222
+ "ec2:DescribeVpcs",
223
+ "ec2:DescribeInstances",
224
+ "ec2:DescribeSecurityGroups",
225
+ "ec2:DescribeSubnets",
226
+ "ec2:DescribeInternetGateways",
227
+ "ec2:DescribeRouteTables"
228
+ ]