aws-inventory-manager 0.13.2__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.

Potentially problematic release.


This version of aws-inventory-manager might be problematic. Click here for more details.

Files changed (145) hide show
  1. aws_inventory_manager-0.13.2.dist-info/LICENSE +21 -0
  2. aws_inventory_manager-0.13.2.dist-info/METADATA +1226 -0
  3. aws_inventory_manager-0.13.2.dist-info/RECORD +145 -0
  4. aws_inventory_manager-0.13.2.dist-info/WHEEL +5 -0
  5. aws_inventory_manager-0.13.2.dist-info/entry_points.txt +2 -0
  6. aws_inventory_manager-0.13.2.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 +3626 -0
  15. src/config_service/__init__.py +21 -0
  16. src/config_service/collector.py +346 -0
  17. src/config_service/detector.py +256 -0
  18. src/config_service/resource_type_mapping.py +328 -0
  19. src/cost/__init__.py +5 -0
  20. src/cost/analyzer.py +226 -0
  21. src/cost/explorer.py +209 -0
  22. src/cost/reporter.py +237 -0
  23. src/delta/__init__.py +5 -0
  24. src/delta/calculator.py +206 -0
  25. src/delta/differ.py +185 -0
  26. src/delta/formatters.py +272 -0
  27. src/delta/models.py +154 -0
  28. src/delta/reporter.py +234 -0
  29. src/models/__init__.py +21 -0
  30. src/models/config_diff.py +135 -0
  31. src/models/cost_report.py +87 -0
  32. src/models/deletion_operation.py +104 -0
  33. src/models/deletion_record.py +97 -0
  34. src/models/delta_report.py +122 -0
  35. src/models/efs_resource.py +80 -0
  36. src/models/elasticache_resource.py +90 -0
  37. src/models/group.py +318 -0
  38. src/models/inventory.py +133 -0
  39. src/models/protection_rule.py +123 -0
  40. src/models/report.py +288 -0
  41. src/models/resource.py +111 -0
  42. src/models/security_finding.py +102 -0
  43. src/models/snapshot.py +122 -0
  44. src/restore/__init__.py +20 -0
  45. src/restore/audit.py +175 -0
  46. src/restore/cleaner.py +461 -0
  47. src/restore/config.py +209 -0
  48. src/restore/deleter.py +976 -0
  49. src/restore/dependency.py +254 -0
  50. src/restore/safety.py +115 -0
  51. src/security/__init__.py +0 -0
  52. src/security/checks/__init__.py +0 -0
  53. src/security/checks/base.py +56 -0
  54. src/security/checks/ec2_checks.py +88 -0
  55. src/security/checks/elasticache_checks.py +149 -0
  56. src/security/checks/iam_checks.py +102 -0
  57. src/security/checks/rds_checks.py +140 -0
  58. src/security/checks/s3_checks.py +95 -0
  59. src/security/checks/secrets_checks.py +96 -0
  60. src/security/checks/sg_checks.py +142 -0
  61. src/security/cis_mapper.py +97 -0
  62. src/security/models.py +53 -0
  63. src/security/reporter.py +174 -0
  64. src/security/scanner.py +87 -0
  65. src/snapshot/__init__.py +6 -0
  66. src/snapshot/capturer.py +451 -0
  67. src/snapshot/filter.py +259 -0
  68. src/snapshot/inventory_storage.py +236 -0
  69. src/snapshot/report_formatter.py +250 -0
  70. src/snapshot/reporter.py +189 -0
  71. src/snapshot/resource_collectors/__init__.py +5 -0
  72. src/snapshot/resource_collectors/apigateway.py +140 -0
  73. src/snapshot/resource_collectors/backup.py +136 -0
  74. src/snapshot/resource_collectors/base.py +81 -0
  75. src/snapshot/resource_collectors/cloudformation.py +55 -0
  76. src/snapshot/resource_collectors/cloudwatch.py +109 -0
  77. src/snapshot/resource_collectors/codebuild.py +69 -0
  78. src/snapshot/resource_collectors/codepipeline.py +82 -0
  79. src/snapshot/resource_collectors/dynamodb.py +65 -0
  80. src/snapshot/resource_collectors/ec2.py +240 -0
  81. src/snapshot/resource_collectors/ecs.py +215 -0
  82. src/snapshot/resource_collectors/efs_collector.py +102 -0
  83. src/snapshot/resource_collectors/eks.py +200 -0
  84. src/snapshot/resource_collectors/elasticache_collector.py +79 -0
  85. src/snapshot/resource_collectors/elb.py +126 -0
  86. src/snapshot/resource_collectors/eventbridge.py +156 -0
  87. src/snapshot/resource_collectors/iam.py +188 -0
  88. src/snapshot/resource_collectors/kms.py +111 -0
  89. src/snapshot/resource_collectors/lambda_func.py +139 -0
  90. src/snapshot/resource_collectors/rds.py +109 -0
  91. src/snapshot/resource_collectors/route53.py +86 -0
  92. src/snapshot/resource_collectors/s3.py +105 -0
  93. src/snapshot/resource_collectors/secretsmanager.py +70 -0
  94. src/snapshot/resource_collectors/sns.py +68 -0
  95. src/snapshot/resource_collectors/sqs.py +82 -0
  96. src/snapshot/resource_collectors/ssm.py +160 -0
  97. src/snapshot/resource_collectors/stepfunctions.py +74 -0
  98. src/snapshot/resource_collectors/vpcendpoints.py +79 -0
  99. src/snapshot/resource_collectors/waf.py +159 -0
  100. src/snapshot/storage.py +351 -0
  101. src/storage/__init__.py +21 -0
  102. src/storage/audit_store.py +419 -0
  103. src/storage/database.py +294 -0
  104. src/storage/group_store.py +749 -0
  105. src/storage/inventory_store.py +320 -0
  106. src/storage/resource_store.py +413 -0
  107. src/storage/schema.py +288 -0
  108. src/storage/snapshot_store.py +346 -0
  109. src/utils/__init__.py +12 -0
  110. src/utils/export.py +305 -0
  111. src/utils/hash.py +60 -0
  112. src/utils/logging.py +63 -0
  113. src/utils/pagination.py +41 -0
  114. src/utils/paths.py +51 -0
  115. src/utils/progress.py +41 -0
  116. src/utils/unsupported_resources.py +306 -0
  117. src/web/__init__.py +5 -0
  118. src/web/app.py +97 -0
  119. src/web/dependencies.py +69 -0
  120. src/web/routes/__init__.py +1 -0
  121. src/web/routes/api/__init__.py +18 -0
  122. src/web/routes/api/charts.py +156 -0
  123. src/web/routes/api/cleanup.py +186 -0
  124. src/web/routes/api/filters.py +253 -0
  125. src/web/routes/api/groups.py +305 -0
  126. src/web/routes/api/inventories.py +80 -0
  127. src/web/routes/api/queries.py +202 -0
  128. src/web/routes/api/resources.py +379 -0
  129. src/web/routes/api/snapshots.py +314 -0
  130. src/web/routes/api/views.py +260 -0
  131. src/web/routes/pages.py +198 -0
  132. src/web/services/__init__.py +1 -0
  133. src/web/templates/base.html +949 -0
  134. src/web/templates/components/navbar.html +31 -0
  135. src/web/templates/components/sidebar.html +104 -0
  136. src/web/templates/pages/audit_logs.html +86 -0
  137. src/web/templates/pages/cleanup.html +279 -0
  138. src/web/templates/pages/dashboard.html +227 -0
  139. src/web/templates/pages/diff.html +175 -0
  140. src/web/templates/pages/error.html +30 -0
  141. src/web/templates/pages/groups.html +721 -0
  142. src/web/templates/pages/queries.html +246 -0
  143. src/web/templates/pages/resources.html +2251 -0
  144. src/web/templates/pages/snapshot_detail.html +271 -0
  145. src/web/templates/pages/snapshots.html +429 -0
@@ -0,0 +1,451 @@
1
+ """Snapshot capture coordinator for AWS resources."""
2
+
3
+ import logging
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from datetime import datetime, timezone
6
+ from threading import Lock
7
+ from typing import TYPE_CHECKING, Dict, List, Optional, Type
8
+
9
+ import boto3
10
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
11
+
12
+ from ..models.snapshot import Snapshot
13
+
14
+ if TYPE_CHECKING:
15
+ from .filter import ResourceFilter
16
+
17
+ # Import Config service integration
18
+ from ..config_service.collector import ConfigResourceCollector
19
+ from ..config_service.detector import (
20
+ ConfigAvailability,
21
+ detect_config_availability,
22
+ )
23
+ from ..config_service.resource_type_mapping import (
24
+ COLLECTOR_TO_CONFIG_TYPES,
25
+ is_config_supported_type,
26
+ )
27
+ from .resource_collectors.apigateway import APIGatewayCollector
28
+ from .resource_collectors.backup import BackupCollector
29
+ from .resource_collectors.base import BaseResourceCollector
30
+ from .resource_collectors.cloudformation import CloudFormationCollector
31
+ from .resource_collectors.cloudwatch import CloudWatchCollector
32
+ from .resource_collectors.codebuild import CodeBuildCollector
33
+ from .resource_collectors.codepipeline import CodePipelineCollector
34
+ from .resource_collectors.dynamodb import DynamoDBCollector
35
+ from .resource_collectors.ec2 import EC2Collector
36
+ from .resource_collectors.ecs import ECSCollector
37
+ from .resource_collectors.efs_collector import EFSCollector
38
+ from .resource_collectors.eks import EKSCollector
39
+ from .resource_collectors.elasticache_collector import ElastiCacheCollector
40
+ from .resource_collectors.elb import ELBCollector
41
+ from .resource_collectors.eventbridge import EventBridgeCollector
42
+ from .resource_collectors.iam import IAMCollector
43
+ from .resource_collectors.kms import KMSCollector
44
+ from .resource_collectors.lambda_func import LambdaCollector
45
+ from .resource_collectors.rds import RDSCollector
46
+ from .resource_collectors.route53 import Route53Collector
47
+ from .resource_collectors.s3 import S3Collector
48
+ from .resource_collectors.secretsmanager import SecretsManagerCollector
49
+ from .resource_collectors.sns import SNSCollector
50
+ from .resource_collectors.sqs import SQSCollector
51
+ from .resource_collectors.ssm import SSMCollector
52
+ from .resource_collectors.stepfunctions import StepFunctionsCollector
53
+ from .resource_collectors.vpcendpoints import VPCEndpointsCollector
54
+ from .resource_collectors.waf import WAFCollector
55
+
56
+ logger = logging.getLogger(__name__)
57
+
58
+
59
+ # Registry of all available collectors
60
+ COLLECTOR_REGISTRY: List[Type[BaseResourceCollector]] = [
61
+ IAMCollector,
62
+ LambdaCollector,
63
+ S3Collector,
64
+ EC2Collector,
65
+ RDSCollector,
66
+ CloudWatchCollector,
67
+ SNSCollector,
68
+ SQSCollector,
69
+ DynamoDBCollector,
70
+ ELBCollector,
71
+ EFSCollector,
72
+ ElastiCacheCollector,
73
+ CloudFormationCollector,
74
+ APIGatewayCollector,
75
+ EventBridgeCollector,
76
+ SecretsManagerCollector,
77
+ KMSCollector,
78
+ SSMCollector,
79
+ Route53Collector,
80
+ ECSCollector,
81
+ StepFunctionsCollector,
82
+ VPCEndpointsCollector,
83
+ WAFCollector,
84
+ EKSCollector,
85
+ CodePipelineCollector,
86
+ CodeBuildCollector,
87
+ BackupCollector,
88
+ ]
89
+
90
+
91
+ def create_snapshot(
92
+ name: str,
93
+ regions: List[str],
94
+ account_id: str,
95
+ profile_name: Optional[str] = None,
96
+ set_active: bool = True,
97
+ resource_types: Optional[List[str]] = None,
98
+ parallel_workers: int = 10,
99
+ resource_filter: Optional["ResourceFilter"] = None,
100
+ inventory_name: str = "default",
101
+ use_config: bool = True,
102
+ config_aggregator: Optional[str] = None,
103
+ ) -> Snapshot:
104
+ """Create a comprehensive snapshot of AWS resources.
105
+
106
+ Args:
107
+ name: Snapshot name
108
+ regions: List of AWS regions to scan
109
+ account_id: AWS account ID
110
+ profile_name: AWS profile name (optional)
111
+ set_active: Whether to set as active baseline
112
+ resource_types: Optional list of resource types to collect (e.g., ['iam', 'lambda'])
113
+ parallel_workers: Number of parallel collection tasks
114
+ resource_filter: Optional ResourceFilter for date/tag-based filtering
115
+ inventory_name: Name of inventory this snapshot belongs to (default: "default")
116
+ use_config: Use AWS Config for collection when available (default: True)
117
+ config_aggregator: Optional Config Aggregator name for multi-account collection
118
+
119
+ Returns:
120
+ Snapshot instance with captured resources
121
+ """
122
+ logger.debug(f"Creating snapshot '{name}' for regions: {regions}")
123
+ if use_config:
124
+ logger.debug("AWS Config collection enabled (will fall back to direct API if unavailable)")
125
+
126
+ # Create session with optional profile
127
+ session_kwargs = {}
128
+ if profile_name:
129
+ session_kwargs["profile_name"] = profile_name
130
+
131
+ session = boto3.Session(**session_kwargs)
132
+
133
+ # Detect AWS Config availability per region if Config collection is enabled
134
+ config_availability: Dict[str, ConfigAvailability] = {}
135
+ collection_sources: Dict[str, str] = {} # Track source per resource type
136
+
137
+ if use_config:
138
+ logger.debug("Detecting AWS Config availability...")
139
+ for region in regions:
140
+ try:
141
+ availability = detect_config_availability(session, region, profile_name)
142
+ config_availability[region] = availability
143
+ if availability.is_enabled:
144
+ logger.debug(f" {region}: Config enabled (all_supported={availability.recording_group_all_supported})")
145
+ else:
146
+ logger.debug(f" {region}: Config not available ({availability.error_message})")
147
+ except Exception as e:
148
+ logger.debug(f" {region}: Config detection failed ({e})")
149
+ config_availability[region] = ConfigAvailability(region=region, error_message=str(e))
150
+
151
+ # Collect resources
152
+ all_resources = []
153
+ resource_counts = {} # Track counts per service for progress
154
+ collection_errors = [] # Track errors for summary
155
+
156
+ # Expected errors that we'll suppress (service not enabled, pagination issues, etc.)
157
+ expected_error_patterns = [
158
+ "Operation cannot be paginated",
159
+ "is not subscribed",
160
+ "AccessDenied",
161
+ "not authorized",
162
+ "InvalidAction",
163
+ "OptInRequired",
164
+ ]
165
+
166
+ def is_expected_error(error_msg: str) -> bool:
167
+ """Check if error is expected and can be safely ignored."""
168
+ return any(pattern in error_msg for pattern in expected_error_patterns)
169
+
170
+ with Progress(
171
+ SpinnerColumn(),
172
+ TextColumn("[bold blue]{task.description}"),
173
+ BarColumn(),
174
+ TaskProgressColumn(),
175
+ ) as progress:
176
+ # Determine which collectors to use
177
+ collectors_to_use = _get_collectors(resource_types)
178
+
179
+ # Separate global and regional collectors
180
+ # Create temporary instances to check is_global_service property
181
+ global_collectors = []
182
+ regional_collectors = []
183
+ for c in collectors_to_use:
184
+ temp_instance = c(session, "us-east-1")
185
+ if temp_instance.is_global_service:
186
+ global_collectors.append(c)
187
+ else:
188
+ regional_collectors.append(c)
189
+
190
+ total_tasks = len(global_collectors) + (len(regional_collectors) * len(regions))
191
+ main_task = progress.add_task(
192
+ f"[bold]Collecting AWS resources from {len(regions)} region(s)...", total=total_tasks
193
+ )
194
+
195
+ # Thread-safe lock for updating shared state
196
+ lock = Lock()
197
+
198
+ def collect_service(collector_class: Type[BaseResourceCollector], region: str, is_global: bool = False) -> Dict:
199
+ """Collect resources for a single service in a region (thread-safe)."""
200
+ try:
201
+ collector = collector_class(session, region)
202
+ service_name = collector.service_name.upper()
203
+ region_label = "global" if is_global else region
204
+
205
+ # Update progress (thread-safe)
206
+ with lock:
207
+ progress.update(main_task, description=f"📦 {service_name} • {region_label}")
208
+
209
+ resources = []
210
+ source = "direct_api"
211
+
212
+ # Check if we should use AWS Config for this service/region
213
+ config_region = "us-east-1" if is_global else region
214
+ region_config = config_availability.get(config_region)
215
+ service_config_types = COLLECTOR_TO_CONFIG_TYPES.get(collector.service_name, [])
216
+
217
+ if (
218
+ use_config
219
+ and region_config
220
+ and region_config.is_enabled
221
+ and service_config_types
222
+ ):
223
+ # Try to collect via AWS Config
224
+ try:
225
+ config_collector = ConfigResourceCollector(
226
+ session, config_region, profile_name, region_config
227
+ )
228
+
229
+ # Collect each resource type this service handles
230
+ for config_type in service_config_types:
231
+ if region_config.supports_resource_type(config_type):
232
+ type_resources = config_collector.collect_by_type(config_type)
233
+ resources.extend(type_resources)
234
+ with lock:
235
+ collection_sources[config_type] = "config"
236
+
237
+ if resources:
238
+ source = "config"
239
+ logger.debug(
240
+ f"Collected {len(resources)} {service_name} resources via Config from {region_label}"
241
+ )
242
+
243
+ except Exception as config_error:
244
+ logger.debug(
245
+ f"Config collection failed for {service_name} in {region_label}, "
246
+ f"falling back to direct API: {config_error}"
247
+ )
248
+ resources = [] # Reset to trigger fallback
249
+
250
+ # Fall back to direct API if Config didn't work or isn't available
251
+ if not resources:
252
+ resources = collector.collect()
253
+ source = "direct_api"
254
+ # Track source for each resource type
255
+ for resource in resources:
256
+ with lock:
257
+ if resource.resource_type not in collection_sources:
258
+ collection_sources[resource.resource_type] = "direct_api"
259
+
260
+ logger.debug(f"Collected {len(resources)} {service_name} resources from {region_label} via {source}")
261
+
262
+ return {
263
+ "success": True,
264
+ "resources": resources,
265
+ "service": service_name,
266
+ "region": region_label,
267
+ "source": source,
268
+ }
269
+
270
+ except Exception as e:
271
+ error_msg = str(e)
272
+ service_name = collector_class.__name__.replace("Collector", "").upper()
273
+ region_label = "global" if is_global else region
274
+
275
+ if not is_expected_error(error_msg):
276
+ logger.debug(f"Collection error - {service_name} ({region_label}): {error_msg[:80]}")
277
+ return {
278
+ "success": False,
279
+ "error": {"service": service_name, "region": region_label, "error": error_msg[:100]},
280
+ }
281
+ else:
282
+ logger.debug(f"Skipping {service_name} in {region_label} (not available): {error_msg[:80]}")
283
+ return {"success": False, "expected": True}
284
+
285
+ # Create list of collection tasks
286
+ collection_tasks = []
287
+
288
+ # Add global service tasks
289
+ for collector_class in global_collectors:
290
+ collection_tasks.append((collector_class, "us-east-1", True))
291
+
292
+ # Add regional service tasks
293
+ for region in regions:
294
+ for collector_class in regional_collectors:
295
+ collection_tasks.append((collector_class, region, False))
296
+
297
+ # Execute collections in parallel
298
+ with ThreadPoolExecutor(max_workers=parallel_workers) as executor:
299
+ # Submit all tasks
300
+ future_to_task = {
301
+ executor.submit(collect_service, collector_class, region, is_global): (
302
+ collector_class,
303
+ region,
304
+ is_global,
305
+ )
306
+ for collector_class, region, is_global in collection_tasks
307
+ }
308
+
309
+ # Process completed tasks
310
+ for future in as_completed(future_to_task):
311
+ result = future.result()
312
+
313
+ if result["success"]:
314
+ with lock:
315
+ all_resources.extend(result["resources"])
316
+ if result["region"] == "global":
317
+ resource_counts[result["service"]] = len(result["resources"])
318
+ else:
319
+ key = f"{result['service']}_{result['region']}"
320
+ resource_counts[key] = len(result["resources"])
321
+ elif not result.get("expected", False):
322
+ with lock:
323
+ collection_errors.append(result["error"])
324
+
325
+ # Advance progress (thread-safe)
326
+ with lock:
327
+ progress.advance(main_task)
328
+
329
+ progress.update(main_task, description=f"[bold green]✓ Successfully collected {len(all_resources)} resources")
330
+
331
+ # Log summary of collection errors if any (but not expected ones)
332
+ if collection_errors:
333
+ logger.debug(f"\nCollection completed with {len(collection_errors)} service(s) unavailable")
334
+ logger.debug("Services that failed:")
335
+ for error in collection_errors:
336
+ logger.debug(f" - {error['service']} ({error['region']}): {error['error']}")
337
+
338
+ # Apply filters if specified
339
+ total_before_filter = len(all_resources)
340
+ filters_applied = None
341
+
342
+ if resource_filter:
343
+ logger.debug(f"Applying filters: {resource_filter.get_filter_summary()}")
344
+ all_resources = resource_filter.apply(all_resources)
345
+ filter_stats = resource_filter.get_statistics_summary()
346
+
347
+ filters_applied = {
348
+ "date_filters": {
349
+ "before_date": resource_filter.before_date.isoformat() if resource_filter.before_date else None,
350
+ "after_date": resource_filter.after_date.isoformat() if resource_filter.after_date else None,
351
+ },
352
+ "tag_filters": resource_filter.required_tags,
353
+ "statistics": filter_stats,
354
+ }
355
+
356
+ logger.debug(
357
+ f"Filtering complete: {filter_stats['total_collected']} collected, "
358
+ f"{filter_stats['final_count']} matched filters"
359
+ )
360
+
361
+ # Calculate service counts
362
+ service_counts: Dict[str, int] = {}
363
+ for resource in all_resources:
364
+ service_counts[resource.resource_type] = service_counts.get(resource.resource_type, 0) + 1
365
+
366
+ # Build Config-related metadata
367
+ config_enabled_regions = [
368
+ region for region, avail in config_availability.items()
369
+ if avail.is_enabled
370
+ ] if use_config else []
371
+
372
+ # Create snapshot
373
+ snapshot = Snapshot(
374
+ name=name,
375
+ created_at=datetime.now(timezone.utc),
376
+ account_id=account_id,
377
+ regions=regions,
378
+ resources=all_resources,
379
+ metadata={
380
+ "tool": "aws-inventory-manager",
381
+ "version": "1.0.0",
382
+ "collectors_used": [c(session, "us-east-1").service_name for c in collectors_to_use],
383
+ "collection_errors": collection_errors if collection_errors else None,
384
+ "use_config": use_config,
385
+ "config_aggregator": config_aggregator,
386
+ "config_enabled_regions": config_enabled_regions if config_enabled_regions else None,
387
+ "collection_sources": collection_sources if collection_sources else None,
388
+ },
389
+ is_active=set_active,
390
+ service_counts=service_counts,
391
+ filters_applied=filters_applied,
392
+ total_resources_before_filter=total_before_filter if resource_filter else None,
393
+ inventory_name=inventory_name,
394
+ )
395
+
396
+ logger.debug(f"Snapshot '{name}' created with {len(all_resources)} resources")
397
+
398
+ return snapshot
399
+
400
+
401
+ def create_snapshot_mvp(
402
+ name: str,
403
+ regions: List[str],
404
+ account_id: str,
405
+ profile_name: Optional[str] = None,
406
+ set_active: bool = True,
407
+ ) -> Snapshot:
408
+ """Create snapshot using the full implementation.
409
+
410
+ This is a wrapper for backward compatibility with the MVP CLI code.
411
+
412
+ Args:
413
+ name: Snapshot name
414
+ regions: List of AWS regions to scan
415
+ account_id: AWS account ID
416
+ profile_name: AWS profile name (optional)
417
+ set_active: Whether to set as active baseline
418
+
419
+ Returns:
420
+ Snapshot instance with captured resources
421
+ """
422
+ return create_snapshot(
423
+ name=name,
424
+ regions=regions,
425
+ account_id=account_id,
426
+ profile_name=profile_name,
427
+ set_active=set_active,
428
+ )
429
+
430
+
431
+ def _get_collectors(resource_types: Optional[List[str]] = None) -> List[Type[BaseResourceCollector]]:
432
+ """Get list of collectors to use based on resource type filter.
433
+
434
+ Args:
435
+ resource_types: Optional list of service names to filter (e.g., ['iam', 'lambda'])
436
+
437
+ Returns:
438
+ List of collector classes to use
439
+ """
440
+ if not resource_types:
441
+ return COLLECTOR_REGISTRY
442
+
443
+ # Filter collectors based on service name
444
+ filtered = []
445
+ for collector_class in COLLECTOR_REGISTRY:
446
+ # Create temporary instance to check service name
447
+ temp_collector = collector_class(boto3.Session(), "us-east-1")
448
+ if temp_collector.service_name in resource_types:
449
+ filtered.append(collector_class)
450
+
451
+ return filtered if filtered else COLLECTOR_REGISTRY