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,188 @@
1
+ """IAM 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 IAMCollector(BaseResourceCollector):
11
+ """Collector for AWS IAM resources (roles, users, groups, policies)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "iam"
16
+
17
+ @property
18
+ def is_global_service(self) -> bool:
19
+ return True
20
+
21
+ def collect(self) -> List[Resource]:
22
+ """Collect IAM resources.
23
+
24
+ Returns:
25
+ List of IAM resources (roles, users, groups, policies)
26
+ """
27
+ resources = []
28
+ account_id = self._get_account_id()
29
+
30
+ # Collect roles
31
+ resources.extend(self._collect_roles(account_id))
32
+
33
+ # Collect users
34
+ resources.extend(self._collect_users(account_id))
35
+
36
+ # Collect groups
37
+ resources.extend(self._collect_groups(account_id))
38
+
39
+ # Collect policies (customer-managed only)
40
+ resources.extend(self._collect_policies(account_id))
41
+
42
+ self.logger.debug(f"Collected {len(resources)} IAM resources")
43
+ return resources
44
+
45
+ def _collect_roles(self, account_id: str) -> List[Resource]:
46
+ """Collect IAM roles."""
47
+ resources = []
48
+ client = self._create_client()
49
+
50
+ try:
51
+ paginator = client.get_paginator("list_roles")
52
+ for page in paginator.paginate():
53
+ for role in page["Roles"]:
54
+ # Build ARN
55
+ arn = role["Arn"]
56
+
57
+ # Extract tags
58
+ tags = {}
59
+ try:
60
+ tag_response = client.list_role_tags(RoleName=role["RoleName"])
61
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("Tags", [])}
62
+ except Exception as e:
63
+ self.logger.debug(f"Could not get tags for role {role['RoleName']}: {e}")
64
+
65
+ # Create resource
66
+ resource = Resource(
67
+ arn=arn,
68
+ resource_type="AWS::IAM::Role",
69
+ name=role["RoleName"],
70
+ region="global",
71
+ tags=tags,
72
+ config_hash=compute_config_hash(role),
73
+ created_at=role.get("CreateDate"),
74
+ raw_config=role,
75
+ )
76
+ resources.append(resource)
77
+
78
+ except Exception as e:
79
+ self.logger.error(f"Error collecting IAM roles: {e}")
80
+
81
+ return resources
82
+
83
+ def _collect_users(self, account_id: str) -> List[Resource]:
84
+ """Collect IAM users."""
85
+ resources = []
86
+ client = self._create_client()
87
+
88
+ try:
89
+ paginator = client.get_paginator("list_users")
90
+ for page in paginator.paginate():
91
+ for user in page["Users"]:
92
+ # Build ARN
93
+ arn = user["Arn"]
94
+
95
+ # Extract tags
96
+ tags = {}
97
+ try:
98
+ tag_response = client.list_user_tags(UserName=user["UserName"])
99
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("Tags", [])}
100
+ except Exception as e:
101
+ self.logger.debug(f"Could not get tags for user {user['UserName']}: {e}")
102
+
103
+ # Create resource
104
+ resource = Resource(
105
+ arn=arn,
106
+ resource_type="AWS::IAM::User",
107
+ name=user["UserName"],
108
+ region="global",
109
+ tags=tags,
110
+ config_hash=compute_config_hash(user),
111
+ created_at=user.get("CreateDate"),
112
+ raw_config=user,
113
+ )
114
+ resources.append(resource)
115
+
116
+ except Exception as e:
117
+ self.logger.error(f"Error collecting IAM users: {e}")
118
+
119
+ return resources
120
+
121
+ def _collect_groups(self, account_id: str) -> List[Resource]:
122
+ """Collect IAM groups."""
123
+ resources = []
124
+ client = self._create_client()
125
+
126
+ try:
127
+ paginator = client.get_paginator("list_groups")
128
+ for page in paginator.paginate():
129
+ for group in page["Groups"]:
130
+ # Build ARN
131
+ arn = group["Arn"]
132
+
133
+ # Create resource (groups don't support tags)
134
+ resource = Resource(
135
+ arn=arn,
136
+ resource_type="AWS::IAM::Group",
137
+ name=group["GroupName"],
138
+ region="global",
139
+ tags={},
140
+ config_hash=compute_config_hash(group),
141
+ created_at=group.get("CreateDate"),
142
+ raw_config=group,
143
+ )
144
+ resources.append(resource)
145
+
146
+ except Exception as e:
147
+ self.logger.error(f"Error collecting IAM groups: {e}")
148
+
149
+ return resources
150
+
151
+ def _collect_policies(self, account_id: str) -> List[Resource]:
152
+ """Collect customer-managed IAM policies."""
153
+ resources = []
154
+ client = self._create_client()
155
+
156
+ try:
157
+ paginator = client.get_paginator("list_policies")
158
+ # Only get customer-managed policies (not AWS-managed)
159
+ for page in paginator.paginate(Scope="Local"):
160
+ for policy in page["Policies"]:
161
+ # Build ARN
162
+ arn = policy["Arn"]
163
+
164
+ # Extract tags
165
+ tags = {}
166
+ try:
167
+ tag_response = client.list_policy_tags(PolicyArn=arn)
168
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("Tags", [])}
169
+ except Exception as e:
170
+ self.logger.debug(f"Could not get tags for policy {policy['PolicyName']}: {e}")
171
+
172
+ # Create resource
173
+ resource = Resource(
174
+ arn=arn,
175
+ resource_type="AWS::IAM::Policy",
176
+ name=policy["PolicyName"],
177
+ region="global",
178
+ tags=tags,
179
+ config_hash=compute_config_hash(policy),
180
+ created_at=policy.get("CreateDate"),
181
+ raw_config=policy,
182
+ )
183
+ resources.append(resource)
184
+
185
+ except Exception as e:
186
+ self.logger.error(f"Error collecting IAM policies: {e}")
187
+
188
+ return resources
@@ -0,0 +1,111 @@
1
+ """KMS 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 KMSCollector(BaseResourceCollector):
11
+ """Collector for AWS KMS (Key Management Service) resources."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "kms"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect KMS keys.
19
+
20
+ Collects customer-managed KMS keys (not AWS-managed keys).
21
+
22
+ Returns:
23
+ List of KMS key resources
24
+ """
25
+ resources = []
26
+ client = self._create_client()
27
+
28
+ try:
29
+ paginator = client.get_paginator("list_keys")
30
+ for page in paginator.paginate():
31
+ for key_item in page.get("Keys", []):
32
+ key_id = key_item["KeyId"]
33
+ key_arn = key_item["KeyArn"]
34
+
35
+ try:
36
+ # Get key metadata
37
+ key_metadata = client.describe_key(KeyId=key_id)["KeyMetadata"]
38
+
39
+ # Skip AWS-managed keys (we only want customer-managed keys)
40
+ if key_metadata.get("KeyManager") == "AWS":
41
+ continue
42
+
43
+ # Skip keys that are pending deletion
44
+ if key_metadata.get("KeyState") == "PendingDeletion":
45
+ self.logger.debug(f"Skipping key {key_id} - pending deletion")
46
+ continue
47
+
48
+ key_alias = None
49
+
50
+ # Get key aliases
51
+ try:
52
+ aliases_response = client.list_aliases(KeyId=key_id)
53
+ aliases = aliases_response.get("Aliases", [])
54
+ if aliases:
55
+ # Use first alias as the name
56
+ key_alias = aliases[0]["AliasName"]
57
+ except Exception as e:
58
+ self.logger.debug(f"Could not get aliases for key {key_id}: {e}")
59
+
60
+ # Get tags
61
+ tags = {}
62
+ try:
63
+ tag_response = client.list_resource_tags(KeyId=key_id)
64
+ for tag in tag_response.get("Tags", []):
65
+ tags[tag["TagKey"]] = tag["TagValue"]
66
+ except Exception as e:
67
+ self.logger.debug(f"Could not get tags for key {key_id}: {e}")
68
+
69
+ # Get key rotation status
70
+ rotation_enabled = False
71
+ try:
72
+ rotation_response = client.get_key_rotation_status(KeyId=key_id)
73
+ rotation_enabled = rotation_response.get("KeyRotationEnabled", False)
74
+ except Exception as e:
75
+ self.logger.debug(f"Could not get rotation status for key {key_id}: {e}")
76
+
77
+ # Build config for hash
78
+ config = {
79
+ **key_metadata,
80
+ "RotationEnabled": rotation_enabled,
81
+ "Aliases": [key_alias] if key_alias else [],
82
+ }
83
+
84
+ # Extract creation date
85
+ created_at = key_metadata.get("CreationDate")
86
+
87
+ # Use alias as name if available, otherwise use key ID
88
+ name = key_alias if key_alias else key_id
89
+
90
+ # Create resource
91
+ resource = Resource(
92
+ arn=key_arn,
93
+ resource_type="AWS::KMS::Key",
94
+ name=name,
95
+ region=self.region,
96
+ tags=tags,
97
+ config_hash=compute_config_hash(config),
98
+ created_at=created_at,
99
+ raw_config=config,
100
+ )
101
+ resources.append(resource)
102
+
103
+ except Exception as e:
104
+ self.logger.debug(f"Error processing key {key_id}: {e}")
105
+ continue
106
+
107
+ except Exception as e:
108
+ self.logger.error(f"Error collecting KMS keys in {self.region}: {e}")
109
+
110
+ self.logger.debug(f"Collected {len(resources)} KMS keys in {self.region}")
111
+ return resources
@@ -0,0 +1,139 @@
1
+ """Lambda resource collector."""
2
+
3
+ from datetime import datetime
4
+ from typing import List, Optional
5
+
6
+ from ...models.resource import Resource
7
+ from ...utils.hash import compute_config_hash
8
+ from .base import BaseResourceCollector
9
+
10
+
11
+ def _parse_lambda_timestamp(timestamp_str: Optional[str]) -> Optional[datetime]:
12
+ """Parse Lambda's ISO-8601 timestamp format.
13
+
14
+ Lambda returns timestamps like: 2024-01-15T10:30:00.000+0000
15
+
16
+ Args:
17
+ timestamp_str: ISO-8601 formatted timestamp string
18
+
19
+ Returns:
20
+ Parsed datetime or None if parsing fails
21
+ """
22
+ if not timestamp_str:
23
+ return None
24
+ try:
25
+ # Lambda format: 2024-01-15T10:30:00.000+0000
26
+ # Python's fromisoformat doesn't handle +0000 format, need to normalize
27
+ normalized = timestamp_str.replace("+0000", "+00:00").replace("-0000", "+00:00")
28
+ return datetime.fromisoformat(normalized)
29
+ except (ValueError, AttributeError):
30
+ return None
31
+
32
+
33
+ class LambdaCollector(BaseResourceCollector):
34
+ """Collector for AWS Lambda functions and layers."""
35
+
36
+ @property
37
+ def service_name(self) -> str:
38
+ return "lambda"
39
+
40
+ def collect(self) -> List[Resource]:
41
+ """Collect Lambda resources.
42
+
43
+ Returns:
44
+ List of Lambda functions and layers
45
+ """
46
+ resources = []
47
+ account_id = self._get_account_id()
48
+
49
+ # Collect functions
50
+ resources.extend(self._collect_functions(account_id))
51
+
52
+ # Collect layers
53
+ resources.extend(self._collect_layers(account_id))
54
+
55
+ self.logger.debug(f"Collected {len(resources)} Lambda resources in {self.region}")
56
+ return resources
57
+
58
+ def _collect_functions(self, account_id: str) -> List[Resource]:
59
+ """Collect Lambda functions."""
60
+ resources = []
61
+ client = self._create_client()
62
+
63
+ try:
64
+ paginator = client.get_paginator("list_functions")
65
+ for page in paginator.paginate():
66
+ for function in page["Functions"]:
67
+ function_name = function["FunctionName"]
68
+ function_arn = function["FunctionArn"]
69
+
70
+ # Get full function configuration (includes tags)
71
+ try:
72
+ full_config = client.get_function(FunctionName=function_name)
73
+ tags = full_config.get("Tags", {})
74
+ config_data = full_config.get("Configuration", function)
75
+ except Exception as e:
76
+ self.logger.debug(f"Could not get full config for {function_name}: {e}")
77
+ tags = {}
78
+ config_data = function
79
+
80
+ # Parse LastModified timestamp (set on creation and updates)
81
+ last_modified = _parse_lambda_timestamp(config_data.get("LastModified"))
82
+
83
+ # Create resource
84
+ resource = Resource(
85
+ arn=function_arn,
86
+ resource_type="AWS::Lambda::Function",
87
+ name=function_name,
88
+ region=self.region,
89
+ tags=tags,
90
+ config_hash=compute_config_hash(config_data),
91
+ created_at=last_modified,
92
+ raw_config=config_data,
93
+ )
94
+ resources.append(resource)
95
+
96
+ except Exception as e:
97
+ self.logger.error(f"Error collecting Lambda functions in {self.region}: {e}")
98
+
99
+ return resources
100
+
101
+ def _collect_layers(self, account_id: str) -> List[Resource]:
102
+ """Collect Lambda layers."""
103
+ resources = []
104
+ client = self._create_client()
105
+
106
+ try:
107
+ paginator = client.get_paginator("list_layers")
108
+ for page in paginator.paginate():
109
+ for layer in page["Layers"]:
110
+ layer_name = layer["LayerName"]
111
+ layer_arn = layer["LayerArn"]
112
+
113
+ # Get latest version info
114
+ try:
115
+ latest_version = layer.get("LatestMatchingVersion", {})
116
+ layer_version_arn = latest_version.get("LayerVersionArn", layer_arn)
117
+ # Parse CreatedDate timestamp (same format as function LastModified)
118
+ created_at = _parse_lambda_timestamp(latest_version.get("CreatedDate"))
119
+
120
+ # Create resource
121
+ resource = Resource(
122
+ arn=layer_version_arn,
123
+ resource_type="AWS::Lambda::LayerVersion",
124
+ name=layer_name,
125
+ region=self.region,
126
+ tags={}, # Layers don't support tags
127
+ config_hash=compute_config_hash(layer),
128
+ created_at=created_at,
129
+ raw_config=layer,
130
+ )
131
+ resources.append(resource)
132
+
133
+ except Exception as e:
134
+ self.logger.debug(f"Could not get layer version for {layer_name}: {e}")
135
+
136
+ except Exception as e:
137
+ self.logger.error(f"Error collecting Lambda layers in {self.region}: {e}")
138
+
139
+ return resources
@@ -0,0 +1,109 @@
1
+ """RDS 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 RDSCollector(BaseResourceCollector):
11
+ """Collector for AWS RDS resources (instances, clusters, snapshots)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "rds"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect RDS resources.
19
+
20
+ Returns:
21
+ List of RDS resources
22
+ """
23
+ resources = []
24
+ account_id = self._get_account_id()
25
+
26
+ # Collect DB instances
27
+ resources.extend(self._collect_db_instances(account_id))
28
+
29
+ # Collect DB clusters (Aurora)
30
+ resources.extend(self._collect_db_clusters(account_id))
31
+
32
+ self.logger.debug(f"Collected {len(resources)} RDS resources in {self.region}")
33
+ return resources
34
+
35
+ def _collect_db_instances(self, account_id: str) -> List[Resource]:
36
+ """Collect RDS DB instances."""
37
+ resources = []
38
+ client = self._create_client()
39
+
40
+ try:
41
+ paginator = client.get_paginator("describe_db_instances")
42
+ for page in paginator.paginate():
43
+ for db_instance in page["DBInstances"]:
44
+ db_id = db_instance["DBInstanceIdentifier"]
45
+ db_arn = db_instance["DBInstanceArn"]
46
+
47
+ # Extract tags
48
+ tags = {}
49
+ try:
50
+ tag_response = client.list_tags_for_resource(ResourceName=db_arn)
51
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("TagList", [])}
52
+ except Exception as e:
53
+ self.logger.debug(f"Could not get tags for DB instance {db_id}: {e}")
54
+
55
+ # Create resource
56
+ resource = Resource(
57
+ arn=db_arn,
58
+ resource_type="AWS::RDS::DBInstance",
59
+ name=db_id,
60
+ region=self.region,
61
+ tags=tags,
62
+ config_hash=compute_config_hash(db_instance),
63
+ created_at=db_instance.get("InstanceCreateTime"),
64
+ raw_config=db_instance,
65
+ )
66
+ resources.append(resource)
67
+
68
+ except Exception as e:
69
+ self.logger.error(f"Error collecting RDS DB instances in {self.region}: {e}")
70
+
71
+ return resources
72
+
73
+ def _collect_db_clusters(self, account_id: str) -> List[Resource]:
74
+ """Collect RDS DB clusters (Aurora)."""
75
+ resources = []
76
+ client = self._create_client()
77
+
78
+ try:
79
+ paginator = client.get_paginator("describe_db_clusters")
80
+ for page in paginator.paginate():
81
+ for db_cluster in page["DBClusters"]:
82
+ cluster_id = db_cluster["DBClusterIdentifier"]
83
+ cluster_arn = db_cluster["DBClusterArn"]
84
+
85
+ # Extract tags
86
+ tags = {}
87
+ try:
88
+ tag_response = client.list_tags_for_resource(ResourceName=cluster_arn)
89
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("TagList", [])}
90
+ except Exception as e:
91
+ self.logger.debug(f"Could not get tags for DB cluster {cluster_id}: {e}")
92
+
93
+ # Create resource
94
+ resource = Resource(
95
+ arn=cluster_arn,
96
+ resource_type="AWS::RDS::DBCluster",
97
+ name=cluster_id,
98
+ region=self.region,
99
+ tags=tags,
100
+ config_hash=compute_config_hash(db_cluster),
101
+ created_at=db_cluster.get("ClusterCreateTime"),
102
+ raw_config=db_cluster,
103
+ )
104
+ resources.append(resource)
105
+
106
+ except Exception as e:
107
+ self.logger.error(f"Error collecting RDS DB clusters in {self.region}: {e}")
108
+
109
+ return resources
@@ -0,0 +1,86 @@
1
+ """Route53 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 Route53Collector(BaseResourceCollector):
11
+ """Collector for Amazon Route53 resources (Hosted Zones)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "route53"
16
+
17
+ @property
18
+ def is_global_service(self) -> bool:
19
+ """Route53 is a global service."""
20
+ return True
21
+
22
+ def collect(self) -> List[Resource]:
23
+ """Collect Route53 resources.
24
+
25
+ Collects:
26
+ - Hosted Zones (public and private)
27
+ - Resource Record Sets within each zone
28
+
29
+ Returns:
30
+ List of Route53 hosted zone resources
31
+ """
32
+ resources = []
33
+ client = self._create_client()
34
+
35
+ try:
36
+ # Collect hosted zones
37
+ paginator = client.get_paginator("list_hosted_zones")
38
+ for page in paginator.paginate():
39
+ for zone in page.get("HostedZones", []):
40
+ zone_id = zone["Id"].split("/")[-1] # Extract ID from '/hostedzone/Z123'
41
+ zone_name = zone["Name"]
42
+
43
+ # Get tags
44
+ tags = {}
45
+ try:
46
+ tag_response = client.list_tags_for_resource(ResourceType="hostedzone", ResourceId=zone_id)
47
+ for tag in tag_response.get("Tags", []):
48
+ tags[tag["Key"]] = tag["Value"]
49
+ except Exception as e:
50
+ self.logger.debug(f"Could not get tags for hosted zone {zone_id}: {e}")
51
+
52
+ # Get record count and additional details
53
+ try:
54
+ zone_details = client.get_hosted_zone(Id=zone["Id"])
55
+ hosted_zone_info = zone_details.get("HostedZone", {})
56
+ # Merge basic info with detailed info
57
+ full_zone = {**zone, **hosted_zone_info}
58
+ except Exception as e:
59
+ self.logger.debug(f"Could not get details for hosted zone {zone_id}: {e}")
60
+ full_zone = zone
61
+
62
+ # Build ARN (Route53 hosted zones use a specific ARN format)
63
+ # Note: ARNs for Route53 are not standard across all operations
64
+ arn = f"arn:aws:route53:::hostedzone/{zone_id}"
65
+
66
+ # Route53 doesn't provide creation timestamp, use None
67
+ created_at = None
68
+
69
+ # Create resource
70
+ resource = Resource(
71
+ arn=arn,
72
+ resource_type="AWS::Route53::HostedZone",
73
+ name=zone_name,
74
+ region="global", # Route53 is global
75
+ tags=tags,
76
+ config_hash=compute_config_hash(full_zone),
77
+ created_at=created_at,
78
+ raw_config=full_zone,
79
+ )
80
+ resources.append(resource)
81
+
82
+ except Exception as e:
83
+ self.logger.error(f"Error collecting Route53 hosted zones: {e}")
84
+
85
+ self.logger.debug(f"Collected {len(resources)} Route53 hosted zones")
86
+ return resources