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,200 @@
1
+ """EKS resource collector."""
2
+
3
+ from typing import List, Tuple
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class EKSCollector(BaseResourceCollector):
11
+ """Collector for Amazon EKS (Elastic Kubernetes Service) resources."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "eks"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect EKS resources.
19
+
20
+ Collects:
21
+ - EKS Clusters
22
+ - Node Groups (within each cluster)
23
+ - Fargate Profiles (within each cluster)
24
+
25
+ Returns:
26
+ List of EKS resources
27
+ """
28
+ resources = []
29
+
30
+ # Collect clusters
31
+ cluster_resources, cluster_names = self._collect_clusters()
32
+ resources.extend(cluster_resources)
33
+
34
+ # Collect node groups for each cluster
35
+ resources.extend(self._collect_node_groups(cluster_names))
36
+
37
+ # Collect Fargate profiles for each cluster
38
+ resources.extend(self._collect_fargate_profiles(cluster_names))
39
+
40
+ self.logger.debug(f"Collected {len(resources)} EKS resources in {self.region}")
41
+ return resources
42
+
43
+ def _collect_clusters(self) -> Tuple[List[Resource], List[str]]: # type: ignore
44
+ """Collect EKS clusters.
45
+
46
+ Returns:
47
+ Tuple of (list of cluster resources, list of cluster names)
48
+ """
49
+ resources = []
50
+ cluster_names = []
51
+ client = self._create_client()
52
+
53
+ try:
54
+ paginator = client.get_paginator("list_clusters")
55
+ for page in paginator.paginate():
56
+ for cluster_name in page.get("clusters", []):
57
+ cluster_names.append(cluster_name)
58
+
59
+ try:
60
+ # Get detailed cluster info
61
+ cluster_response = client.describe_cluster(name=cluster_name)
62
+ cluster = cluster_response.get("cluster", {})
63
+
64
+ cluster_arn = cluster.get("arn", "")
65
+
66
+ # Extract tags
67
+ tags = cluster.get("tags", {})
68
+
69
+ # Extract creation date
70
+ created_at = cluster.get("createdAt")
71
+
72
+ # Create resource
73
+ resource = Resource(
74
+ arn=cluster_arn,
75
+ resource_type="AWS::EKS::Cluster",
76
+ name=cluster_name,
77
+ region=self.region,
78
+ tags=tags,
79
+ config_hash=compute_config_hash(cluster),
80
+ created_at=created_at,
81
+ raw_config=cluster,
82
+ )
83
+ resources.append(resource)
84
+
85
+ except Exception as e:
86
+ self.logger.debug(f"Error processing cluster {cluster_name}: {e}")
87
+ continue
88
+
89
+ except Exception as e:
90
+ self.logger.error(f"Error collecting EKS clusters in {self.region}: {e}")
91
+
92
+ return resources, cluster_names
93
+
94
+ def _collect_node_groups(self, cluster_names: List[str]) -> List[Resource]:
95
+ """Collect EKS node groups for given clusters.
96
+
97
+ Args:
98
+ cluster_names: List of cluster names to collect node groups from
99
+
100
+ Returns:
101
+ List of node group resources
102
+ """
103
+ resources = []
104
+ client = self._create_client()
105
+
106
+ for cluster_name in cluster_names:
107
+ try:
108
+ paginator = client.get_paginator("list_nodegroups")
109
+ for page in paginator.paginate(clusterName=cluster_name):
110
+ for nodegroup_name in page.get("nodegroups", []):
111
+ try:
112
+ # Get detailed node group info
113
+ ng_response = client.describe_nodegroup(
114
+ clusterName=cluster_name, nodegroupName=nodegroup_name
115
+ )
116
+ nodegroup = ng_response.get("nodegroup", {})
117
+
118
+ ng_arn = nodegroup.get("nodegroupArn", "")
119
+
120
+ # Extract tags
121
+ tags = nodegroup.get("tags", {})
122
+
123
+ # Extract creation date
124
+ created_at = nodegroup.get("createdAt")
125
+
126
+ # Create resource
127
+ resource = Resource(
128
+ arn=ng_arn,
129
+ resource_type="AWS::EKS::Nodegroup",
130
+ name=f"{cluster_name}/{nodegroup_name}",
131
+ region=self.region,
132
+ tags=tags,
133
+ config_hash=compute_config_hash(nodegroup),
134
+ created_at=created_at,
135
+ raw_config=nodegroup,
136
+ )
137
+ resources.append(resource)
138
+
139
+ except Exception as e:
140
+ self.logger.debug(f"Error processing node group {nodegroup_name}: {e}")
141
+ continue
142
+
143
+ except Exception as e:
144
+ self.logger.debug(f"Error collecting node groups for cluster {cluster_name}: {e}")
145
+
146
+ return resources
147
+
148
+ def _collect_fargate_profiles(self, cluster_names: List[str]) -> List[Resource]:
149
+ """Collect EKS Fargate profiles for given clusters.
150
+
151
+ Args:
152
+ cluster_names: List of cluster names to collect Fargate profiles from
153
+
154
+ Returns:
155
+ List of Fargate profile resources
156
+ """
157
+ resources = []
158
+ client = self._create_client()
159
+
160
+ for cluster_name in cluster_names:
161
+ try:
162
+ paginator = client.get_paginator("list_fargate_profiles")
163
+ for page in paginator.paginate(clusterName=cluster_name):
164
+ for profile_name in page.get("fargateProfileNames", []):
165
+ try:
166
+ # Get detailed Fargate profile info
167
+ profile_response = client.describe_fargate_profile(
168
+ clusterName=cluster_name, fargateProfileName=profile_name
169
+ )
170
+ profile = profile_response.get("fargateProfile", {})
171
+
172
+ profile_arn = profile.get("fargateProfileArn", "")
173
+
174
+ # Extract tags
175
+ tags = profile.get("tags", {})
176
+
177
+ # Extract creation date
178
+ created_at = profile.get("createdAt")
179
+
180
+ # Create resource
181
+ resource = Resource(
182
+ arn=profile_arn,
183
+ resource_type="AWS::EKS::FargateProfile",
184
+ name=f"{cluster_name}/{profile_name}",
185
+ region=self.region,
186
+ tags=tags,
187
+ config_hash=compute_config_hash(profile),
188
+ created_at=created_at,
189
+ raw_config=profile,
190
+ )
191
+ resources.append(resource)
192
+
193
+ except Exception as e:
194
+ self.logger.debug(f"Error processing Fargate profile {profile_name}: {e}")
195
+ continue
196
+
197
+ except Exception as e:
198
+ self.logger.debug(f"Error collecting Fargate profiles for cluster {cluster_name}: {e}")
199
+
200
+ return resources
@@ -0,0 +1,79 @@
1
+ """ElastiCache resource collector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List
6
+
7
+ from ...models.resource import Resource
8
+ from ...utils.hash import compute_config_hash
9
+ from .base import BaseResourceCollector
10
+
11
+
12
+ class ElastiCacheCollector(BaseResourceCollector):
13
+ """Collector for AWS ElastiCache resources (Redis and Memcached clusters)."""
14
+
15
+ @property
16
+ def service_name(self) -> str:
17
+ return "elasticache"
18
+
19
+ def collect(self) -> List[Resource]:
20
+ """Collect ElastiCache cluster resources.
21
+
22
+ Collects both Redis and Memcached clusters using the ElastiCache API.
23
+
24
+ Returns:
25
+ List of ElastiCache cluster resources
26
+ """
27
+ resources = []
28
+ account_id = self._get_account_id()
29
+
30
+ # Collect cache clusters (both Redis and Memcached)
31
+ resources.extend(self._collect_cache_clusters(account_id))
32
+
33
+ self.logger.debug(f"Collected {len(resources)} ElastiCache clusters in {self.region}")
34
+ return resources
35
+
36
+ def _collect_cache_clusters(self, account_id: str) -> List[Resource]:
37
+ """Collect ElastiCache cache clusters.
38
+
39
+ Args:
40
+ account_id: AWS account ID
41
+
42
+ Returns:
43
+ List of Resource objects representing ElastiCache clusters
44
+ """
45
+ resources = []
46
+ client = self._create_client()
47
+
48
+ try:
49
+ paginator = client.get_paginator("describe_cache_clusters")
50
+ # ShowCacheNodeInfo=False for performance (we don't need node-level details)
51
+ for page in paginator.paginate(ShowCacheNodeInfo=False):
52
+ for cluster in page.get("CacheClusters", []):
53
+ cluster_id = cluster["CacheClusterId"]
54
+ cluster_arn = cluster["ARN"]
55
+
56
+ # Extract tags
57
+ tags = {}
58
+ try:
59
+ tag_response = client.list_tags_for_resource(ResourceName=cluster_arn)
60
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("TagList", [])}
61
+ except Exception as e:
62
+ self.logger.debug(f"Could not get tags for ElastiCache cluster {cluster_id}: {e}")
63
+
64
+ # Create resource
65
+ resource = Resource(
66
+ arn=cluster_arn,
67
+ resource_type="elasticache:cluster",
68
+ name=cluster_id,
69
+ region=self.region,
70
+ tags=tags,
71
+ config_hash=compute_config_hash(cluster),
72
+ raw_config=cluster,
73
+ )
74
+ resources.append(resource)
75
+
76
+ except Exception as e:
77
+ self.logger.error(f"Error collecting ElastiCache clusters in {self.region}: {e}")
78
+
79
+ return resources
@@ -0,0 +1,126 @@
1
+ """ELB/ALB 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 ELBCollector(BaseResourceCollector):
11
+ """Collector for AWS Load Balancer resources (Classic ELB, ALB, NLB)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "elb"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect ELB resources.
19
+
20
+ Returns:
21
+ List of load balancers
22
+ """
23
+ resources = []
24
+ account_id = self._get_account_id()
25
+
26
+ # Collect v2 load balancers (ALB, NLB, GWLB)
27
+ resources.extend(self._collect_v2_load_balancers(account_id))
28
+
29
+ # Collect classic load balancers
30
+ resources.extend(self._collect_classic_load_balancers(account_id))
31
+
32
+ self.logger.debug(f"Collected {len(resources)} load balancers in {self.region}")
33
+ return resources
34
+
35
+ def _collect_v2_load_balancers(self, account_id: str) -> List[Resource]:
36
+ """Collect ALB/NLB/GWLB load balancers."""
37
+ resources = []
38
+
39
+ try:
40
+ elbv2_client = self._create_client("elbv2")
41
+
42
+ paginator = elbv2_client.get_paginator("describe_load_balancers")
43
+ for page in paginator.paginate():
44
+ for lb in page.get("LoadBalancers", []):
45
+ lb_arn = lb["LoadBalancerArn"]
46
+ lb_name = lb["LoadBalancerName"]
47
+ lb_type = lb.get("Type", "application") # application, network, gateway
48
+
49
+ # Get tags
50
+ tags = {}
51
+ try:
52
+ tag_response = elbv2_client.describe_tags(ResourceArns=[lb_arn])
53
+ for tag_desc in tag_response.get("TagDescriptions", []):
54
+ tags = {tag["Key"]: tag["Value"] for tag in tag_desc.get("Tags", [])}
55
+ except Exception as e:
56
+ self.logger.debug(f"Could not get tags for load balancer {lb_name}: {e}")
57
+
58
+ # Determine resource type
59
+ if lb_type == "application":
60
+ resource_type = "AWS::ElasticLoadBalancingV2::LoadBalancer::Application"
61
+ elif lb_type == "network":
62
+ resource_type = "AWS::ElasticLoadBalancingV2::LoadBalancer::Network"
63
+ elif lb_type == "gateway":
64
+ resource_type = "AWS::ElasticLoadBalancingV2::LoadBalancer::Gateway"
65
+ else:
66
+ resource_type = "AWS::ElasticLoadBalancingV2::LoadBalancer"
67
+
68
+ # Create resource
69
+ resource = Resource(
70
+ arn=lb_arn,
71
+ resource_type=resource_type,
72
+ name=lb_name,
73
+ region=self.region,
74
+ tags=tags,
75
+ config_hash=compute_config_hash(lb),
76
+ created_at=lb.get("CreatedTime"),
77
+ raw_config=lb,
78
+ )
79
+ resources.append(resource)
80
+
81
+ except Exception as e:
82
+ self.logger.error(f"Error collecting v2 load balancers in {self.region}: {e}")
83
+
84
+ return resources
85
+
86
+ def _collect_classic_load_balancers(self, account_id: str) -> List[Resource]:
87
+ """Collect classic load balancers."""
88
+ resources = []
89
+
90
+ try:
91
+ elb_client = self._create_client("elb")
92
+
93
+ paginator = elb_client.get_paginator("describe_load_balancers")
94
+ for page in paginator.paginate():
95
+ for lb in page.get("LoadBalancerDescriptions", []):
96
+ lb_name = lb["LoadBalancerName"]
97
+
98
+ # Build ARN (classic ELBs don't return ARN directly)
99
+ lb_arn = f"arn:aws:elasticloadbalancing:{self.region}:{account_id}:loadbalancer/{lb_name}"
100
+
101
+ # Get tags
102
+ tags = {}
103
+ try:
104
+ tag_response = elb_client.describe_tags(LoadBalancerNames=[lb_name])
105
+ for tag_desc in tag_response.get("TagDescriptions", []):
106
+ tags = {tag["Key"]: tag["Value"] for tag in tag_desc.get("Tags", [])}
107
+ except Exception as e:
108
+ self.logger.debug(f"Could not get tags for classic ELB {lb_name}: {e}")
109
+
110
+ # Create resource
111
+ resource = Resource(
112
+ arn=lb_arn,
113
+ resource_type="AWS::ElasticLoadBalancing::LoadBalancer",
114
+ name=lb_name,
115
+ region=self.region,
116
+ tags=tags,
117
+ config_hash=compute_config_hash(lb),
118
+ created_at=lb.get("CreatedTime"),
119
+ raw_config=lb,
120
+ )
121
+ resources.append(resource)
122
+
123
+ except Exception as e:
124
+ self.logger.error(f"Error collecting classic load balancers in {self.region}: {e}")
125
+
126
+ return resources
@@ -0,0 +1,156 @@
1
+ """EventBridge 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 EventBridgeCollector(BaseResourceCollector):
11
+ """Collector for Amazon EventBridge resources (Event Buses, Rules)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "eventbridge"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect EventBridge resources.
19
+
20
+ Collects:
21
+ - Event Buses (custom and partner buses)
22
+ - Event Rules (across all buses)
23
+
24
+ Returns:
25
+ List of EventBridge resources
26
+ """
27
+ resources = []
28
+
29
+ # Collect Event Buses
30
+ resources.extend(self._collect_event_buses())
31
+
32
+ # Collect Event Rules (across all buses)
33
+ resources.extend(self._collect_event_rules())
34
+
35
+ self.logger.debug(f"Collected {len(resources)} EventBridge resources in {self.region}")
36
+ return resources
37
+
38
+ def _collect_event_buses(self) -> List[Resource]:
39
+ """Collect EventBridge Event Buses.
40
+
41
+ Returns:
42
+ List of Event Bus resources
43
+ """
44
+ resources = []
45
+ client = self._create_client("events")
46
+
47
+ try:
48
+ paginator = client.get_paginator("list_event_buses")
49
+ for page in paginator.paginate():
50
+ for bus in page.get("EventBuses", []):
51
+ bus_name = bus["Name"]
52
+ bus_arn = bus["Arn"]
53
+
54
+ # Get tags
55
+ tags = {}
56
+ try:
57
+ tag_response = client.list_tags_for_resource(ResourceARN=bus_arn)
58
+ for tag in tag_response.get("Tags", []):
59
+ tags[tag["Key"]] = tag["Value"]
60
+ except Exception as e:
61
+ self.logger.debug(f"Could not get tags for event bus {bus_name}: {e}")
62
+
63
+ # Extract creation time (if available)
64
+ created_at = bus.get("CreationTime")
65
+
66
+ # Create resource
67
+ resource = Resource(
68
+ arn=bus_arn,
69
+ resource_type="AWS::Events::EventBus",
70
+ name=bus_name,
71
+ region=self.region,
72
+ tags=tags,
73
+ config_hash=compute_config_hash(bus),
74
+ created_at=created_at,
75
+ raw_config=bus,
76
+ )
77
+ resources.append(resource)
78
+
79
+ except Exception as e:
80
+ self.logger.error(f"Error collecting EventBridge event buses in {self.region}: {e}")
81
+
82
+ return resources
83
+
84
+ def _collect_event_rules(self) -> List[Resource]:
85
+ """Collect EventBridge Rules across all event buses.
86
+
87
+ Returns:
88
+ List of Event Rule resources
89
+ """
90
+ resources = []
91
+ client = self._create_client("events")
92
+
93
+ try:
94
+ # First, get all event buses to collect rules from each
95
+ event_buses = ["default"] # Start with default bus
96
+
97
+ try:
98
+ paginator = client.get_paginator("list_event_buses")
99
+ for page in paginator.paginate():
100
+ for bus in page.get("EventBuses", []):
101
+ bus_name = bus["Name"]
102
+ if bus_name != "default":
103
+ event_buses.append(bus_name)
104
+ except Exception as e:
105
+ self.logger.debug(f"Error listing event buses: {e}")
106
+
107
+ # Collect rules from each bus
108
+ for bus_name in event_buses:
109
+ try:
110
+ paginator = client.get_paginator("list_rules")
111
+ for page in paginator.paginate(EventBusName=bus_name):
112
+ for rule in page.get("Rules", []):
113
+ rule_name = rule["Name"]
114
+ rule_arn = rule["Arn"]
115
+
116
+ # Get tags
117
+ tags = {}
118
+ try:
119
+ tag_response = client.list_tags_for_resource(ResourceARN=rule_arn)
120
+ for tag in tag_response.get("Tags", []):
121
+ tags[tag["Key"]] = tag["Value"]
122
+ except Exception as e:
123
+ self.logger.debug(f"Could not get tags for rule {rule_name}: {e}")
124
+
125
+ # Get full rule details
126
+ try:
127
+ rule_details = client.describe_rule(Name=rule_name, EventBusName=bus_name)
128
+ # Merge with basic rule info
129
+ full_rule = {**rule, **rule_details}
130
+ except Exception as e:
131
+ self.logger.debug(f"Could not get details for rule {rule_name}: {e}")
132
+ full_rule = rule
133
+
134
+ # Extract creation time (not typically available for rules)
135
+ created_at = None
136
+
137
+ # Create resource
138
+ resource = Resource(
139
+ arn=rule_arn,
140
+ resource_type="AWS::Events::Rule",
141
+ name=rule_name,
142
+ region=self.region,
143
+ tags=tags,
144
+ config_hash=compute_config_hash(full_rule),
145
+ created_at=created_at,
146
+ raw_config=full_rule,
147
+ )
148
+ resources.append(resource)
149
+
150
+ except Exception as e:
151
+ self.logger.error(f"Error collecting rules from event bus {bus_name}: {e}")
152
+
153
+ except Exception as e:
154
+ self.logger.error(f"Error collecting EventBridge rules in {self.region}: {e}")
155
+
156
+ return resources