aws-inventory-manager 0.17.12__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 (152) hide show
  1. aws_inventory_manager-0.17.12.dist-info/LICENSE +21 -0
  2. aws_inventory_manager-0.17.12.dist-info/METADATA +1292 -0
  3. aws_inventory_manager-0.17.12.dist-info/RECORD +152 -0
  4. aws_inventory_manager-0.17.12.dist-info/WHEEL +5 -0
  5. aws_inventory_manager-0.17.12.dist-info/entry_points.txt +2 -0
  6. aws_inventory_manager-0.17.12.dist-info/top_level.txt +1 -0
  7. src/__init__.py +3 -0
  8. src/aws/__init__.py +11 -0
  9. src/aws/client.py +128 -0
  10. src/aws/credentials.py +191 -0
  11. src/aws/rate_limiter.py +177 -0
  12. src/cli/__init__.py +12 -0
  13. src/cli/config.py +130 -0
  14. src/cli/main.py +4046 -0
  15. src/cloudtrail/__init__.py +5 -0
  16. src/cloudtrail/query.py +642 -0
  17. src/config_service/__init__.py +21 -0
  18. src/config_service/collector.py +346 -0
  19. src/config_service/detector.py +256 -0
  20. src/config_service/resource_type_mapping.py +328 -0
  21. src/cost/__init__.py +5 -0
  22. src/cost/analyzer.py +226 -0
  23. src/cost/explorer.py +209 -0
  24. src/cost/reporter.py +237 -0
  25. src/delta/__init__.py +5 -0
  26. src/delta/calculator.py +206 -0
  27. src/delta/differ.py +185 -0
  28. src/delta/formatters.py +272 -0
  29. src/delta/models.py +154 -0
  30. src/delta/reporter.py +234 -0
  31. src/matching/__init__.py +6 -0
  32. src/matching/config.py +52 -0
  33. src/matching/normalizer.py +450 -0
  34. src/matching/prompts.py +33 -0
  35. src/models/__init__.py +21 -0
  36. src/models/config_diff.py +135 -0
  37. src/models/cost_report.py +87 -0
  38. src/models/deletion_operation.py +104 -0
  39. src/models/deletion_record.py +97 -0
  40. src/models/delta_report.py +122 -0
  41. src/models/efs_resource.py +80 -0
  42. src/models/elasticache_resource.py +90 -0
  43. src/models/group.py +318 -0
  44. src/models/inventory.py +133 -0
  45. src/models/protection_rule.py +123 -0
  46. src/models/report.py +288 -0
  47. src/models/resource.py +111 -0
  48. src/models/security_finding.py +102 -0
  49. src/models/snapshot.py +122 -0
  50. src/restore/__init__.py +20 -0
  51. src/restore/audit.py +175 -0
  52. src/restore/cleaner.py +461 -0
  53. src/restore/config.py +209 -0
  54. src/restore/deleter.py +976 -0
  55. src/restore/dependency.py +254 -0
  56. src/restore/safety.py +115 -0
  57. src/security/__init__.py +0 -0
  58. src/security/checks/__init__.py +0 -0
  59. src/security/checks/base.py +56 -0
  60. src/security/checks/ec2_checks.py +88 -0
  61. src/security/checks/elasticache_checks.py +149 -0
  62. src/security/checks/iam_checks.py +102 -0
  63. src/security/checks/rds_checks.py +140 -0
  64. src/security/checks/s3_checks.py +95 -0
  65. src/security/checks/secrets_checks.py +96 -0
  66. src/security/checks/sg_checks.py +142 -0
  67. src/security/cis_mapper.py +97 -0
  68. src/security/models.py +53 -0
  69. src/security/reporter.py +174 -0
  70. src/security/scanner.py +87 -0
  71. src/snapshot/__init__.py +6 -0
  72. src/snapshot/capturer.py +453 -0
  73. src/snapshot/filter.py +259 -0
  74. src/snapshot/inventory_storage.py +236 -0
  75. src/snapshot/report_formatter.py +250 -0
  76. src/snapshot/reporter.py +189 -0
  77. src/snapshot/resource_collectors/__init__.py +5 -0
  78. src/snapshot/resource_collectors/apigateway.py +140 -0
  79. src/snapshot/resource_collectors/backup.py +136 -0
  80. src/snapshot/resource_collectors/base.py +81 -0
  81. src/snapshot/resource_collectors/cloudformation.py +55 -0
  82. src/snapshot/resource_collectors/cloudwatch.py +109 -0
  83. src/snapshot/resource_collectors/codebuild.py +69 -0
  84. src/snapshot/resource_collectors/codepipeline.py +82 -0
  85. src/snapshot/resource_collectors/dynamodb.py +65 -0
  86. src/snapshot/resource_collectors/ec2.py +240 -0
  87. src/snapshot/resource_collectors/ecs.py +215 -0
  88. src/snapshot/resource_collectors/efs_collector.py +102 -0
  89. src/snapshot/resource_collectors/eks.py +200 -0
  90. src/snapshot/resource_collectors/elasticache_collector.py +79 -0
  91. src/snapshot/resource_collectors/elb.py +126 -0
  92. src/snapshot/resource_collectors/eventbridge.py +156 -0
  93. src/snapshot/resource_collectors/glue.py +199 -0
  94. src/snapshot/resource_collectors/iam.py +188 -0
  95. src/snapshot/resource_collectors/kms.py +111 -0
  96. src/snapshot/resource_collectors/lambda_func.py +139 -0
  97. src/snapshot/resource_collectors/rds.py +109 -0
  98. src/snapshot/resource_collectors/route53.py +86 -0
  99. src/snapshot/resource_collectors/s3.py +105 -0
  100. src/snapshot/resource_collectors/secretsmanager.py +70 -0
  101. src/snapshot/resource_collectors/sns.py +68 -0
  102. src/snapshot/resource_collectors/sqs.py +82 -0
  103. src/snapshot/resource_collectors/ssm.py +160 -0
  104. src/snapshot/resource_collectors/stepfunctions.py +74 -0
  105. src/snapshot/resource_collectors/vpcendpoints.py +79 -0
  106. src/snapshot/resource_collectors/waf.py +159 -0
  107. src/snapshot/storage.py +351 -0
  108. src/storage/__init__.py +21 -0
  109. src/storage/audit_store.py +419 -0
  110. src/storage/database.py +294 -0
  111. src/storage/group_store.py +763 -0
  112. src/storage/inventory_store.py +320 -0
  113. src/storage/resource_store.py +416 -0
  114. src/storage/schema.py +339 -0
  115. src/storage/snapshot_store.py +363 -0
  116. src/utils/__init__.py +12 -0
  117. src/utils/export.py +305 -0
  118. src/utils/hash.py +60 -0
  119. src/utils/logging.py +63 -0
  120. src/utils/pagination.py +41 -0
  121. src/utils/paths.py +51 -0
  122. src/utils/progress.py +41 -0
  123. src/utils/unsupported_resources.py +306 -0
  124. src/web/__init__.py +5 -0
  125. src/web/app.py +97 -0
  126. src/web/dependencies.py +69 -0
  127. src/web/routes/__init__.py +1 -0
  128. src/web/routes/api/__init__.py +18 -0
  129. src/web/routes/api/charts.py +156 -0
  130. src/web/routes/api/cleanup.py +186 -0
  131. src/web/routes/api/filters.py +253 -0
  132. src/web/routes/api/groups.py +305 -0
  133. src/web/routes/api/inventories.py +80 -0
  134. src/web/routes/api/queries.py +202 -0
  135. src/web/routes/api/resources.py +393 -0
  136. src/web/routes/api/snapshots.py +314 -0
  137. src/web/routes/api/views.py +260 -0
  138. src/web/routes/pages.py +198 -0
  139. src/web/services/__init__.py +1 -0
  140. src/web/templates/base.html +955 -0
  141. src/web/templates/components/navbar.html +31 -0
  142. src/web/templates/components/sidebar.html +104 -0
  143. src/web/templates/pages/audit_logs.html +86 -0
  144. src/web/templates/pages/cleanup.html +279 -0
  145. src/web/templates/pages/dashboard.html +227 -0
  146. src/web/templates/pages/diff.html +175 -0
  147. src/web/templates/pages/error.html +30 -0
  148. src/web/templates/pages/groups.html +721 -0
  149. src/web/templates/pages/queries.html +246 -0
  150. src/web/templates/pages/resources.html +2429 -0
  151. src/web/templates/pages/snapshot_detail.html +271 -0
  152. src/web/templates/pages/snapshots.html +429 -0
@@ -0,0 +1,250 @@
1
+ """
2
+ Report output formatting using Rich for terminal UI.
3
+
4
+ This module contains the ReportFormatter class responsible for rendering
5
+ reports to the terminal with Rich tables, progress bars, and formatted output.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import List, Optional
11
+
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+
15
+ from src.models.report import DetailedResource, ResourceSummary, SnapshotMetadata
16
+ from src.utils.pagination import paginate_resources
17
+
18
+
19
+ class ReportFormatter:
20
+ """
21
+ Formats and renders reports using Rich terminal UI.
22
+
23
+ Handles rendering of headers, summaries, tables, and progress bars
24
+ for snapshot resource reports.
25
+ """
26
+
27
+ def __init__(self, console: Optional[Console] = None) -> None:
28
+ """
29
+ Initialize formatter with Rich Console.
30
+
31
+ Args:
32
+ console: Rich Console instance (creates default if not provided)
33
+ """
34
+ self.console = console or Console()
35
+
36
+ def format_summary(
37
+ self,
38
+ metadata: SnapshotMetadata,
39
+ summary: ResourceSummary,
40
+ has_filters: bool = False,
41
+ ) -> None:
42
+ """
43
+ Format and display complete summary report.
44
+
45
+ Orchestrates all render methods to display header, service breakdown,
46
+ region breakdown, and type breakdown.
47
+
48
+ Args:
49
+ metadata: Snapshot metadata for header
50
+ summary: Resource summary with aggregated counts
51
+ has_filters: Whether filters were applied (shows "Filtered" indicator)
52
+ """
53
+ # Render header
54
+ self._render_header(metadata, has_filters)
55
+
56
+ # Add spacing
57
+ self.console.print()
58
+
59
+ # Render summary sections
60
+ self.console.print("📊 [bold]Resource Summary[/bold]\n")
61
+ self.console.print(f"Total Resources: [bold cyan]{summary.total_count:,}[/bold cyan]\n")
62
+
63
+ if summary.total_count > 0:
64
+ # Render service breakdown
65
+ self._render_service_breakdown(summary)
66
+
67
+ # Render region breakdown
68
+ self._render_region_breakdown(summary)
69
+
70
+ # Render type breakdown (top 10)
71
+ self._render_type_breakdown(summary)
72
+
73
+ def _render_header(self, metadata: SnapshotMetadata, has_filters: bool = False) -> None:
74
+ """
75
+ Render report header with snapshot metadata.
76
+
77
+ Args:
78
+ metadata: Snapshot metadata
79
+ has_filters: Whether to show "Filtered" indicator
80
+ """
81
+ title = f"Snapshot Report: {metadata.name}"
82
+ if has_filters:
83
+ title += " (Filtered)"
84
+
85
+ # Create header panel
86
+ header_content = f"""[bold]Inventory:[/bold] {metadata.inventory_name}
87
+ [bold]Account ID:[/bold] {metadata.account_id}
88
+ [bold]Created:[/bold] {metadata.created_at.strftime('%Y-%m-%d %H:%M:%S UTC')}
89
+ [bold]Regions:[/bold] {metadata.region_summary}"""
90
+
91
+ panel = Panel(
92
+ header_content,
93
+ title=title,
94
+ border_style="blue",
95
+ expand=False,
96
+ )
97
+
98
+ self.console.print(panel)
99
+
100
+ def _render_service_breakdown(self, summary: ResourceSummary) -> None:
101
+ """
102
+ Render service breakdown with progress bars.
103
+
104
+ Args:
105
+ summary: Resource summary with service counts
106
+ """
107
+ if not summary.by_service:
108
+ return
109
+
110
+ self.console.print("[bold]By Service:[/bold]")
111
+
112
+ # Get top services (sorted by count)
113
+ top_services = summary.top_services(limit=10)
114
+
115
+ for service, count in top_services:
116
+ percentage = (count / summary.total_count) * 100
117
+ bar_length = int((count / summary.total_count) * 30)
118
+ bar = "█" * bar_length + "░" * (30 - bar_length)
119
+
120
+ self.console.print(f" {service:15} {count:5} {bar} ({percentage:.1f}%)")
121
+
122
+ self.console.print()
123
+
124
+ def _render_region_breakdown(self, summary: ResourceSummary) -> None:
125
+ """
126
+ Render region breakdown with progress bars.
127
+
128
+ Args:
129
+ summary: Resource summary with region counts
130
+ """
131
+ if not summary.by_region:
132
+ return
133
+
134
+ self.console.print("[bold]By Region:[/bold]")
135
+
136
+ # Get top regions (sorted by count)
137
+ top_regions = summary.top_regions(limit=10)
138
+
139
+ for region, count in top_regions:
140
+ percentage = (count / summary.total_count) * 100
141
+ bar_length = int((count / summary.total_count) * 30)
142
+ bar = "█" * bar_length + "░" * (30 - bar_length)
143
+
144
+ self.console.print(f" {region:15} {count:5} {bar} ({percentage:.1f}%)")
145
+
146
+ self.console.print()
147
+
148
+ def _render_type_breakdown(self, summary: ResourceSummary) -> None:
149
+ """
150
+ Render resource type breakdown showing top 10 types.
151
+
152
+ Args:
153
+ summary: Resource summary with type counts
154
+ """
155
+ if not summary.by_type:
156
+ return
157
+
158
+ self.console.print("[bold]Top 10 Resource Types:[/bold]")
159
+
160
+ # Get top 10 resource types
161
+ sorted_types = sorted(
162
+ summary.by_type.items(),
163
+ key=lambda x: x[1],
164
+ reverse=True,
165
+ )[:10]
166
+
167
+ for resource_type, count in sorted_types:
168
+ percentage = (count / summary.total_count) * 100
169
+ self.console.print(f" {resource_type:45} {count:5} ({percentage:.1f}%)")
170
+
171
+ self.console.print()
172
+
173
+ def format_detailed(
174
+ self,
175
+ metadata: SnapshotMetadata,
176
+ resources: List[DetailedResource],
177
+ page_size: int = 100,
178
+ ) -> None:
179
+ """
180
+ Format and display detailed resource view with pagination.
181
+
182
+ Args:
183
+ metadata: Snapshot metadata for header
184
+ resources: List of detailed resources to display
185
+ page_size: Number of resources per page (default: 100)
186
+ """
187
+ # Render header
188
+ self._render_header(metadata, has_filters=False)
189
+ self.console.print()
190
+
191
+ total_resources = len(resources)
192
+ self.console.print(f"Total Resources: [bold cyan]{total_resources:,}[/bold cyan]\n")
193
+
194
+ if total_resources == 0:
195
+ return
196
+
197
+ # Paginate resources
198
+ pages = list(paginate_resources(resources, page_size=page_size))
199
+ total_pages = len(pages)
200
+
201
+ # Display pages
202
+ for page_num, page in enumerate(pages, start=1):
203
+ for idx, resource in enumerate(page, start=1):
204
+ global_idx = (page_num - 1) * page_size + idx
205
+ self._render_detailed_resource(resource, global_idx, total_resources)
206
+
207
+ # Show pagination info (but no prompts - non-interactive for now)
208
+ if page_num < total_pages:
209
+ self.console.print(f"\n[dim]─── Page {page_num} of {total_pages} ───[/dim]\n")
210
+
211
+ def _render_detailed_resource(
212
+ self,
213
+ resource: DetailedResource,
214
+ index: int,
215
+ total: int,
216
+ ) -> None:
217
+ """
218
+ Render a single detailed resource.
219
+
220
+ Args:
221
+ resource: DetailedResource to display
222
+ index: Resource index (1-based)
223
+ total: Total number of resources
224
+ """
225
+ self.console.print("─" * 65)
226
+ self.console.print(f"[bold]Resource {index}/{total}[/bold]\n")
227
+
228
+ self.console.print(f"[bold]ARN:[/bold] {resource.arn}")
229
+ self.console.print(f"[bold]Type:[/bold] {resource.resource_type}")
230
+ self.console.print(f"[bold]Name:[/bold] {resource.name}")
231
+ self.console.print(f"[bold]Region:[/bold] {resource.region}")
232
+
233
+ # Show creation date if available
234
+ if resource.created_at:
235
+ age_str = ""
236
+ if resource.age_days is not None:
237
+ age_str = f" ({resource.age_days} days ago)"
238
+ created_str = resource.created_at.strftime("%Y-%m-%d %H:%M:%S UTC")
239
+ self.console.print(f"[bold]Created:[/bold] {created_str}{age_str}")
240
+
241
+ # Show tags
242
+ self.console.print()
243
+ if resource.tags:
244
+ self.console.print("[bold]Tags:[/bold]")
245
+ for key, value in resource.tags.items():
246
+ self.console.print(f" {key:20} {value}")
247
+ else:
248
+ self.console.print("[dim]No tags[/dim]")
249
+
250
+ self.console.print()
@@ -0,0 +1,189 @@
1
+ """
2
+ Snapshot report generation logic.
3
+
4
+ This module contains the SnapshotReporter class responsible for generating
5
+ reports from snapshot data, including summary generation, filtering, and
6
+ detailed resource views.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import TYPE_CHECKING, Generator, Optional
12
+
13
+ from src.models.report import DetailedResource, FilterCriteria, FilteredResource, ResourceSummary, SnapshotMetadata
14
+
15
+ if TYPE_CHECKING:
16
+ from src.models.snapshot import Snapshot
17
+
18
+
19
+ class SnapshotReporter:
20
+ """
21
+ Generates reports from snapshot data.
22
+
23
+ This class handles extracting metadata, generating summaries, and
24
+ preparing data for formatting and display.
25
+ """
26
+
27
+ def __init__(self, snapshot: Snapshot) -> None:
28
+ """
29
+ Initialize reporter with a snapshot.
30
+
31
+ Args:
32
+ snapshot: The snapshot to generate reports from
33
+ """
34
+ self.snapshot = snapshot
35
+
36
+ def _extract_metadata(self) -> SnapshotMetadata:
37
+ """
38
+ Extract metadata from snapshot for report header.
39
+
40
+ Returns:
41
+ SnapshotMetadata with snapshot information
42
+ """
43
+ return SnapshotMetadata(
44
+ name=self.snapshot.name,
45
+ created_at=self.snapshot.created_at,
46
+ account_id=self.snapshot.account_id,
47
+ regions=self.snapshot.regions,
48
+ inventory_name=self.snapshot.inventory_name,
49
+ total_resource_count=len(self.snapshot.resources),
50
+ )
51
+
52
+ def generate_summary(self) -> ResourceSummary:
53
+ """
54
+ Generate aggregated resource summary from snapshot.
55
+
56
+ Uses streaming single-pass aggregation for memory efficiency.
57
+ Does not load entire dataset into memory.
58
+
59
+ Returns:
60
+ ResourceSummary with aggregated counts
61
+ """
62
+ summary = ResourceSummary()
63
+
64
+ # Single-pass streaming aggregation
65
+ for resource in self.snapshot.resources:
66
+ summary.total_count += 1
67
+
68
+ # Extract service from resource type
69
+ # Handle both formats: "AWS::EC2::Instance" and "ec2:instance"
70
+ if "::" in resource.resource_type:
71
+ # CloudFormation format: AWS::EC2::Instance
72
+ parts = resource.resource_type.split("::")
73
+ service = parts[1] if len(parts) >= 2 else "Unknown"
74
+ else:
75
+ # Simplified format: ec2:instance
76
+ service = (
77
+ resource.resource_type.split(":")[0] if ":" in resource.resource_type else resource.resource_type
78
+ )
79
+
80
+ # Aggregate by service
81
+ summary.by_service[service] = summary.by_service.get(service, 0) + 1
82
+
83
+ # Aggregate by region
84
+ summary.by_region[resource.region] = summary.by_region.get(resource.region, 0) + 1
85
+
86
+ # Aggregate by type
87
+ summary.by_type[resource.resource_type] = summary.by_type.get(resource.resource_type, 0) + 1
88
+
89
+ return summary
90
+
91
+ def get_filtered_resources(self, criteria: FilterCriteria) -> Generator[FilteredResource, None, None]:
92
+ """
93
+ Get filtered resources as generator (memory-efficient streaming).
94
+
95
+ Args:
96
+ criteria: Filter criteria with resource types and/or regions
97
+
98
+ Yields:
99
+ FilteredResource objects matching the criteria
100
+ """
101
+ for resource in self.snapshot.resources:
102
+ # Convert Resource to FilteredResource for matching
103
+ filtered_resource = FilteredResource(
104
+ arn=resource.arn,
105
+ resource_type=resource.resource_type,
106
+ name=resource.name,
107
+ region=resource.region,
108
+ )
109
+
110
+ # Apply filter criteria
111
+ if criteria.matches_resource(filtered_resource):
112
+ yield filtered_resource
113
+
114
+ def generate_filtered_summary(self, criteria: FilterCriteria) -> ResourceSummary:
115
+ """
116
+ Generate summary for filtered resources only.
117
+
118
+ Args:
119
+ criteria: Filter criteria
120
+
121
+ Returns:
122
+ ResourceSummary with counts for filtered resources only
123
+ """
124
+ summary = ResourceSummary()
125
+
126
+ # Single-pass streaming aggregation of filtered resources
127
+ for filtered_resource in self.get_filtered_resources(criteria):
128
+ summary.total_count += 1
129
+
130
+ # Extract service from resource type
131
+ if "::" in filtered_resource.resource_type:
132
+ parts = filtered_resource.resource_type.split("::")
133
+ service = parts[1] if len(parts) >= 2 else "Unknown"
134
+ else:
135
+ service = (
136
+ filtered_resource.resource_type.split(":")[0]
137
+ if ":" in filtered_resource.resource_type
138
+ else filtered_resource.resource_type
139
+ )
140
+
141
+ # Aggregate by service
142
+ summary.by_service[service] = summary.by_service.get(service, 0) + 1
143
+
144
+ # Aggregate by region
145
+ summary.by_region[filtered_resource.region] = summary.by_region.get(filtered_resource.region, 0) + 1
146
+
147
+ # Aggregate by type
148
+ summary.by_type[filtered_resource.resource_type] = (
149
+ summary.by_type.get(filtered_resource.resource_type, 0) + 1
150
+ )
151
+
152
+ return summary
153
+
154
+ def get_detailed_resources(
155
+ self, criteria: Optional[FilterCriteria] = None
156
+ ) -> Generator[DetailedResource, None, None]:
157
+ """
158
+ Get detailed resource information as generator.
159
+
160
+ Args:
161
+ criteria: Optional filter criteria to limit resources
162
+
163
+ Yields:
164
+ DetailedResource objects with full information (tags, creation date, etc.)
165
+ """
166
+ for resource in self.snapshot.resources:
167
+ # Apply filtering if criteria provided
168
+ if criteria:
169
+ filtered_resource = FilteredResource(
170
+ arn=resource.arn,
171
+ resource_type=resource.resource_type,
172
+ name=resource.name,
173
+ region=resource.region,
174
+ )
175
+ if not criteria.matches_resource(filtered_resource):
176
+ continue
177
+
178
+ # Convert Resource to DetailedResource
179
+ detailed_resource = DetailedResource(
180
+ arn=resource.arn,
181
+ resource_type=resource.resource_type,
182
+ name=resource.name,
183
+ region=resource.region,
184
+ tags=resource.tags,
185
+ created_at=resource.created_at,
186
+ config_hash=resource.config_hash,
187
+ )
188
+
189
+ yield detailed_resource
@@ -0,0 +1,5 @@
1
+ """Resource collectors for AWS services."""
2
+
3
+ from typing import List
4
+
5
+ __all__: List[str] = []
@@ -0,0 +1,140 @@
1
+ """API Gateway resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class APIGatewayCollector(BaseResourceCollector):
11
+ """Collector for AWS API Gateway resources (REST, HTTP, WebSocket APIs)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "apigateway"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect API Gateway resources.
19
+
20
+ Collects:
21
+ - REST APIs (v1)
22
+ - HTTP APIs (v2)
23
+ - WebSocket APIs (v2)
24
+
25
+ Returns:
26
+ List of API Gateway APIs
27
+ """
28
+ resources = []
29
+
30
+ # Collect REST APIs (v1)
31
+ resources.extend(self._collect_rest_apis())
32
+
33
+ # Collect HTTP and WebSocket APIs (v2)
34
+ resources.extend(self._collect_v2_apis())
35
+
36
+ self.logger.debug(f"Collected {len(resources)} API Gateway APIs in {self.region}")
37
+ return resources
38
+
39
+ def _collect_rest_apis(self) -> List[Resource]:
40
+ """Collect REST APIs (API Gateway v1).
41
+
42
+ Returns:
43
+ List of REST API resources
44
+ """
45
+ resources = []
46
+ client = self._create_client()
47
+
48
+ try:
49
+ paginator = client.get_paginator("get_rest_apis")
50
+ for page in paginator.paginate():
51
+ for api in page.get("items", []):
52
+ api_id = api["id"]
53
+ api_name = api["name"]
54
+
55
+ # Get tags
56
+ tags = {}
57
+ try:
58
+ tag_response = client.get_tags(
59
+ resourceArn=f"arn:aws:apigateway:{self.region}::/restapis/{api_id}"
60
+ )
61
+ tags = tag_response.get("tags", {})
62
+ except Exception as e:
63
+ self.logger.debug(f"Could not get tags for REST API {api_id}: {e}")
64
+
65
+ # Build ARN
66
+ arn = f"arn:aws:apigateway:{self.region}::/restapis/{api_id}"
67
+
68
+ # Extract creation date
69
+ created_at = api.get("createdDate")
70
+
71
+ # Create resource
72
+ resource = Resource(
73
+ arn=arn,
74
+ resource_type="AWS::ApiGateway::RestApi",
75
+ name=api_name,
76
+ region=self.region,
77
+ tags=tags,
78
+ config_hash=compute_config_hash(api),
79
+ created_at=created_at,
80
+ raw_config=api,
81
+ )
82
+ resources.append(resource)
83
+
84
+ except Exception as e:
85
+ self.logger.error(f"Error collecting REST APIs in {self.region}: {e}")
86
+
87
+ return resources
88
+
89
+ def _collect_v2_apis(self) -> List[Resource]:
90
+ """Collect HTTP and WebSocket APIs (API Gateway v2).
91
+
92
+ Returns:
93
+ List of v2 API resources
94
+ """
95
+ resources = []
96
+
97
+ try:
98
+ client = self._create_client("apigatewayv2")
99
+
100
+ paginator = client.get_paginator("get_apis")
101
+ for page in paginator.paginate():
102
+ for api in page.get("Items", []):
103
+ api_id = api["ApiId"]
104
+ api_name = api["Name"]
105
+ protocol_type = api["ProtocolType"] # HTTP or WEBSOCKET
106
+
107
+ # Get tags
108
+ tags = api.get("Tags", {})
109
+
110
+ # Build ARN
111
+ arn = f"arn:aws:apigateway:{self.region}::/apis/{api_id}"
112
+
113
+ # Extract creation date
114
+ created_at = api.get("CreatedDate")
115
+
116
+ # Determine resource type based on protocol
117
+ if protocol_type == "HTTP":
118
+ resource_type = "AWS::ApiGatewayV2::Api::HTTP"
119
+ elif protocol_type == "WEBSOCKET":
120
+ resource_type = "AWS::ApiGatewayV2::Api::WebSocket"
121
+ else:
122
+ resource_type = f"AWS::ApiGatewayV2::Api::{protocol_type}"
123
+
124
+ # Create resource
125
+ resource = Resource(
126
+ arn=arn,
127
+ resource_type=resource_type,
128
+ name=api_name,
129
+ region=self.region,
130
+ tags=tags,
131
+ config_hash=compute_config_hash(api),
132
+ created_at=created_at,
133
+ raw_config=api,
134
+ )
135
+ resources.append(resource)
136
+
137
+ except Exception as e:
138
+ self.logger.error(f"Error collecting API Gateway v2 APIs in {self.region}: {e}")
139
+
140
+ return resources