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