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.
- aws_inventory_manager-0.17.12.dist-info/LICENSE +21 -0
- aws_inventory_manager-0.17.12.dist-info/METADATA +1292 -0
- aws_inventory_manager-0.17.12.dist-info/RECORD +152 -0
- aws_inventory_manager-0.17.12.dist-info/WHEEL +5 -0
- aws_inventory_manager-0.17.12.dist-info/entry_points.txt +2 -0
- aws_inventory_manager-0.17.12.dist-info/top_level.txt +1 -0
- src/__init__.py +3 -0
- src/aws/__init__.py +11 -0
- src/aws/client.py +128 -0
- src/aws/credentials.py +191 -0
- src/aws/rate_limiter.py +177 -0
- src/cli/__init__.py +12 -0
- src/cli/config.py +130 -0
- src/cli/main.py +4046 -0
- src/cloudtrail/__init__.py +5 -0
- src/cloudtrail/query.py +642 -0
- src/config_service/__init__.py +21 -0
- src/config_service/collector.py +346 -0
- src/config_service/detector.py +256 -0
- src/config_service/resource_type_mapping.py +328 -0
- src/cost/__init__.py +5 -0
- src/cost/analyzer.py +226 -0
- src/cost/explorer.py +209 -0
- src/cost/reporter.py +237 -0
- src/delta/__init__.py +5 -0
- src/delta/calculator.py +206 -0
- src/delta/differ.py +185 -0
- src/delta/formatters.py +272 -0
- src/delta/models.py +154 -0
- src/delta/reporter.py +234 -0
- src/matching/__init__.py +6 -0
- src/matching/config.py +52 -0
- src/matching/normalizer.py +450 -0
- src/matching/prompts.py +33 -0
- src/models/__init__.py +21 -0
- src/models/config_diff.py +135 -0
- src/models/cost_report.py +87 -0
- src/models/deletion_operation.py +104 -0
- src/models/deletion_record.py +97 -0
- src/models/delta_report.py +122 -0
- src/models/efs_resource.py +80 -0
- src/models/elasticache_resource.py +90 -0
- src/models/group.py +318 -0
- src/models/inventory.py +133 -0
- src/models/protection_rule.py +123 -0
- src/models/report.py +288 -0
- src/models/resource.py +111 -0
- src/models/security_finding.py +102 -0
- src/models/snapshot.py +122 -0
- src/restore/__init__.py +20 -0
- src/restore/audit.py +175 -0
- src/restore/cleaner.py +461 -0
- src/restore/config.py +209 -0
- src/restore/deleter.py +976 -0
- src/restore/dependency.py +254 -0
- src/restore/safety.py +115 -0
- src/security/__init__.py +0 -0
- src/security/checks/__init__.py +0 -0
- src/security/checks/base.py +56 -0
- src/security/checks/ec2_checks.py +88 -0
- src/security/checks/elasticache_checks.py +149 -0
- src/security/checks/iam_checks.py +102 -0
- src/security/checks/rds_checks.py +140 -0
- src/security/checks/s3_checks.py +95 -0
- src/security/checks/secrets_checks.py +96 -0
- src/security/checks/sg_checks.py +142 -0
- src/security/cis_mapper.py +97 -0
- src/security/models.py +53 -0
- src/security/reporter.py +174 -0
- src/security/scanner.py +87 -0
- src/snapshot/__init__.py +6 -0
- src/snapshot/capturer.py +453 -0
- src/snapshot/filter.py +259 -0
- src/snapshot/inventory_storage.py +236 -0
- src/snapshot/report_formatter.py +250 -0
- src/snapshot/reporter.py +189 -0
- src/snapshot/resource_collectors/__init__.py +5 -0
- src/snapshot/resource_collectors/apigateway.py +140 -0
- src/snapshot/resource_collectors/backup.py +136 -0
- src/snapshot/resource_collectors/base.py +81 -0
- src/snapshot/resource_collectors/cloudformation.py +55 -0
- src/snapshot/resource_collectors/cloudwatch.py +109 -0
- src/snapshot/resource_collectors/codebuild.py +69 -0
- src/snapshot/resource_collectors/codepipeline.py +82 -0
- src/snapshot/resource_collectors/dynamodb.py +65 -0
- src/snapshot/resource_collectors/ec2.py +240 -0
- src/snapshot/resource_collectors/ecs.py +215 -0
- src/snapshot/resource_collectors/efs_collector.py +102 -0
- src/snapshot/resource_collectors/eks.py +200 -0
- src/snapshot/resource_collectors/elasticache_collector.py +79 -0
- src/snapshot/resource_collectors/elb.py +126 -0
- src/snapshot/resource_collectors/eventbridge.py +156 -0
- src/snapshot/resource_collectors/glue.py +199 -0
- src/snapshot/resource_collectors/iam.py +188 -0
- src/snapshot/resource_collectors/kms.py +111 -0
- src/snapshot/resource_collectors/lambda_func.py +139 -0
- src/snapshot/resource_collectors/rds.py +109 -0
- src/snapshot/resource_collectors/route53.py +86 -0
- src/snapshot/resource_collectors/s3.py +105 -0
- src/snapshot/resource_collectors/secretsmanager.py +70 -0
- src/snapshot/resource_collectors/sns.py +68 -0
- src/snapshot/resource_collectors/sqs.py +82 -0
- src/snapshot/resource_collectors/ssm.py +160 -0
- src/snapshot/resource_collectors/stepfunctions.py +74 -0
- src/snapshot/resource_collectors/vpcendpoints.py +79 -0
- src/snapshot/resource_collectors/waf.py +159 -0
- src/snapshot/storage.py +351 -0
- src/storage/__init__.py +21 -0
- src/storage/audit_store.py +419 -0
- src/storage/database.py +294 -0
- src/storage/group_store.py +763 -0
- src/storage/inventory_store.py +320 -0
- src/storage/resource_store.py +416 -0
- src/storage/schema.py +339 -0
- src/storage/snapshot_store.py +363 -0
- src/utils/__init__.py +12 -0
- src/utils/export.py +305 -0
- src/utils/hash.py +60 -0
- src/utils/logging.py +63 -0
- src/utils/pagination.py +41 -0
- src/utils/paths.py +51 -0
- src/utils/progress.py +41 -0
- src/utils/unsupported_resources.py +306 -0
- src/web/__init__.py +5 -0
- src/web/app.py +97 -0
- src/web/dependencies.py +69 -0
- src/web/routes/__init__.py +1 -0
- src/web/routes/api/__init__.py +18 -0
- src/web/routes/api/charts.py +156 -0
- src/web/routes/api/cleanup.py +186 -0
- src/web/routes/api/filters.py +253 -0
- src/web/routes/api/groups.py +305 -0
- src/web/routes/api/inventories.py +80 -0
- src/web/routes/api/queries.py +202 -0
- src/web/routes/api/resources.py +393 -0
- src/web/routes/api/snapshots.py +314 -0
- src/web/routes/api/views.py +260 -0
- src/web/routes/pages.py +198 -0
- src/web/services/__init__.py +1 -0
- src/web/templates/base.html +955 -0
- src/web/templates/components/navbar.html +31 -0
- src/web/templates/components/sidebar.html +104 -0
- src/web/templates/pages/audit_logs.html +86 -0
- src/web/templates/pages/cleanup.html +279 -0
- src/web/templates/pages/dashboard.html +227 -0
- src/web/templates/pages/diff.html +175 -0
- src/web/templates/pages/error.html +30 -0
- src/web/templates/pages/groups.html +721 -0
- src/web/templates/pages/queries.html +246 -0
- src/web/templates/pages/resources.html +2429 -0
- src/web/templates/pages/snapshot_detail.html +271 -0
- src/web/templates/pages/snapshots.html +429 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""EC2 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 EC2Collector(BaseResourceCollector):
|
|
11
|
+
"""Collector for AWS EC2 resources (instances, volumes, VPCs, security groups, etc.)."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def service_name(self) -> str:
|
|
15
|
+
return "ec2"
|
|
16
|
+
|
|
17
|
+
def collect(self) -> List[Resource]:
|
|
18
|
+
"""Collect EC2 resources.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
List of EC2 resources
|
|
22
|
+
"""
|
|
23
|
+
resources = []
|
|
24
|
+
account_id = self._get_account_id()
|
|
25
|
+
|
|
26
|
+
# Collect instances
|
|
27
|
+
resources.extend(self._collect_instances(account_id))
|
|
28
|
+
|
|
29
|
+
# Collect volumes
|
|
30
|
+
resources.extend(self._collect_volumes(account_id))
|
|
31
|
+
|
|
32
|
+
# Collect VPCs
|
|
33
|
+
resources.extend(self._collect_vpcs(account_id))
|
|
34
|
+
|
|
35
|
+
# Collect security groups
|
|
36
|
+
resources.extend(self._collect_security_groups(account_id))
|
|
37
|
+
|
|
38
|
+
# Collect subnets
|
|
39
|
+
resources.extend(self._collect_subnets(account_id))
|
|
40
|
+
|
|
41
|
+
self.logger.debug(f"Collected {len(resources)} EC2 resources in {self.region}")
|
|
42
|
+
return resources
|
|
43
|
+
|
|
44
|
+
def _collect_instances(self, account_id: str) -> List[Resource]:
|
|
45
|
+
"""Collect EC2 instances."""
|
|
46
|
+
resources = []
|
|
47
|
+
client = self._create_client()
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
paginator = client.get_paginator("describe_instances")
|
|
51
|
+
for page in paginator.paginate():
|
|
52
|
+
for reservation in page["Reservations"]:
|
|
53
|
+
for instance in reservation["Instances"]:
|
|
54
|
+
instance_id = instance["InstanceId"]
|
|
55
|
+
|
|
56
|
+
# Extract tags
|
|
57
|
+
tags = {}
|
|
58
|
+
for tag in instance.get("Tags", []):
|
|
59
|
+
tags[tag["Key"]] = tag["Value"]
|
|
60
|
+
|
|
61
|
+
# Get instance name from tags
|
|
62
|
+
name = tags.get("Name", instance_id)
|
|
63
|
+
|
|
64
|
+
# Build ARN
|
|
65
|
+
arn = f"arn:aws:ec2:{self.region}:{account_id}:instance/{instance_id}"
|
|
66
|
+
|
|
67
|
+
# Create resource
|
|
68
|
+
resource = Resource(
|
|
69
|
+
arn=arn,
|
|
70
|
+
resource_type="AWS::EC2::Instance",
|
|
71
|
+
name=name,
|
|
72
|
+
region=self.region,
|
|
73
|
+
tags=tags,
|
|
74
|
+
config_hash=compute_config_hash(instance),
|
|
75
|
+
created_at=instance.get("LaunchTime"),
|
|
76
|
+
raw_config=instance,
|
|
77
|
+
)
|
|
78
|
+
resources.append(resource)
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
self.logger.error(f"Error collecting EC2 instances in {self.region}: {e}")
|
|
82
|
+
|
|
83
|
+
return resources
|
|
84
|
+
|
|
85
|
+
def _collect_volumes(self, account_id: str) -> List[Resource]:
|
|
86
|
+
"""Collect EBS volumes."""
|
|
87
|
+
resources = []
|
|
88
|
+
client = self._create_client()
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
paginator = client.get_paginator("describe_volumes")
|
|
92
|
+
for page in paginator.paginate():
|
|
93
|
+
for volume in page["Volumes"]:
|
|
94
|
+
volume_id = volume["VolumeId"]
|
|
95
|
+
|
|
96
|
+
# Extract tags
|
|
97
|
+
tags = {}
|
|
98
|
+
for tag in volume.get("Tags", []):
|
|
99
|
+
tags[tag["Key"]] = tag["Value"]
|
|
100
|
+
|
|
101
|
+
# Get volume name from tags
|
|
102
|
+
name = tags.get("Name", volume_id)
|
|
103
|
+
|
|
104
|
+
# Build ARN
|
|
105
|
+
arn = f"arn:aws:ec2:{self.region}:{account_id}:volume/{volume_id}"
|
|
106
|
+
|
|
107
|
+
# Create resource
|
|
108
|
+
resource = Resource(
|
|
109
|
+
arn=arn,
|
|
110
|
+
resource_type="AWS::EC2::Volume",
|
|
111
|
+
name=name,
|
|
112
|
+
region=self.region,
|
|
113
|
+
tags=tags,
|
|
114
|
+
config_hash=compute_config_hash(volume),
|
|
115
|
+
created_at=volume.get("CreateTime"),
|
|
116
|
+
raw_config=volume,
|
|
117
|
+
)
|
|
118
|
+
resources.append(resource)
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
self.logger.error(f"Error collecting EBS volumes in {self.region}: {e}")
|
|
122
|
+
|
|
123
|
+
return resources
|
|
124
|
+
|
|
125
|
+
def _collect_vpcs(self, account_id: str) -> List[Resource]:
|
|
126
|
+
"""Collect VPCs."""
|
|
127
|
+
resources = []
|
|
128
|
+
client = self._create_client()
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
response = client.describe_vpcs()
|
|
132
|
+
for vpc in response["Vpcs"]:
|
|
133
|
+
vpc_id = vpc["VpcId"]
|
|
134
|
+
|
|
135
|
+
# Extract tags
|
|
136
|
+
tags = {}
|
|
137
|
+
for tag in vpc.get("Tags", []):
|
|
138
|
+
tags[tag["Key"]] = tag["Value"]
|
|
139
|
+
|
|
140
|
+
# Get VPC name from tags
|
|
141
|
+
name = tags.get("Name", vpc_id)
|
|
142
|
+
|
|
143
|
+
# Build ARN
|
|
144
|
+
arn = f"arn:aws:ec2:{self.region}:{account_id}:vpc/{vpc_id}"
|
|
145
|
+
|
|
146
|
+
# Create resource
|
|
147
|
+
resource = Resource(
|
|
148
|
+
arn=arn,
|
|
149
|
+
resource_type="AWS::EC2::VPC",
|
|
150
|
+
name=name,
|
|
151
|
+
region=self.region,
|
|
152
|
+
tags=tags,
|
|
153
|
+
config_hash=compute_config_hash(vpc),
|
|
154
|
+
created_at=None, # VPCs don't have creation timestamp
|
|
155
|
+
raw_config=vpc,
|
|
156
|
+
)
|
|
157
|
+
resources.append(resource)
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
self.logger.error(f"Error collecting VPCs in {self.region}: {e}")
|
|
161
|
+
|
|
162
|
+
return resources
|
|
163
|
+
|
|
164
|
+
def _collect_security_groups(self, account_id: str) -> List[Resource]:
|
|
165
|
+
"""Collect security groups."""
|
|
166
|
+
resources = []
|
|
167
|
+
client = self._create_client()
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
paginator = client.get_paginator("describe_security_groups")
|
|
171
|
+
for page in paginator.paginate():
|
|
172
|
+
for sg in page["SecurityGroups"]:
|
|
173
|
+
sg_id = sg["GroupId"]
|
|
174
|
+
sg_name = sg["GroupName"]
|
|
175
|
+
|
|
176
|
+
# Extract tags
|
|
177
|
+
tags = {}
|
|
178
|
+
for tag in sg.get("Tags", []):
|
|
179
|
+
tags[tag["Key"]] = tag["Value"]
|
|
180
|
+
|
|
181
|
+
# Build ARN
|
|
182
|
+
arn = f"arn:aws:ec2:{self.region}:{account_id}:security-group/{sg_id}"
|
|
183
|
+
|
|
184
|
+
# Create resource
|
|
185
|
+
resource = Resource(
|
|
186
|
+
arn=arn,
|
|
187
|
+
resource_type="AWS::EC2::SecurityGroup",
|
|
188
|
+
name=sg_name,
|
|
189
|
+
region=self.region,
|
|
190
|
+
tags=tags,
|
|
191
|
+
config_hash=compute_config_hash(sg),
|
|
192
|
+
created_at=None, # Security groups don't have creation timestamp
|
|
193
|
+
raw_config=sg,
|
|
194
|
+
)
|
|
195
|
+
resources.append(resource)
|
|
196
|
+
|
|
197
|
+
except Exception as e:
|
|
198
|
+
self.logger.error(f"Error collecting security groups in {self.region}: {e}")
|
|
199
|
+
|
|
200
|
+
return resources
|
|
201
|
+
|
|
202
|
+
def _collect_subnets(self, account_id: str) -> List[Resource]:
|
|
203
|
+
"""Collect subnets."""
|
|
204
|
+
resources = []
|
|
205
|
+
client = self._create_client()
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
paginator = client.get_paginator("describe_subnets")
|
|
209
|
+
for page in paginator.paginate():
|
|
210
|
+
for subnet in page["Subnets"]:
|
|
211
|
+
subnet_id = subnet["SubnetId"]
|
|
212
|
+
|
|
213
|
+
# Extract tags
|
|
214
|
+
tags = {}
|
|
215
|
+
for tag in subnet.get("Tags", []):
|
|
216
|
+
tags[tag["Key"]] = tag["Value"]
|
|
217
|
+
|
|
218
|
+
# Get subnet name from tags
|
|
219
|
+
name = tags.get("Name", subnet_id)
|
|
220
|
+
|
|
221
|
+
# Build ARN
|
|
222
|
+
arn = f"arn:aws:ec2:{self.region}:{account_id}:subnet/{subnet_id}"
|
|
223
|
+
|
|
224
|
+
# Create resource
|
|
225
|
+
resource = Resource(
|
|
226
|
+
arn=arn,
|
|
227
|
+
resource_type="AWS::EC2::Subnet",
|
|
228
|
+
name=name,
|
|
229
|
+
region=self.region,
|
|
230
|
+
tags=tags,
|
|
231
|
+
config_hash=compute_config_hash(subnet),
|
|
232
|
+
created_at=None, # Subnets don't have creation timestamp
|
|
233
|
+
raw_config=subnet,
|
|
234
|
+
)
|
|
235
|
+
resources.append(resource)
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
self.logger.error(f"Error collecting subnets in {self.region}: {e}")
|
|
239
|
+
|
|
240
|
+
return resources
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""ECS 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 ECSCollector(BaseResourceCollector):
|
|
11
|
+
"""Collector for Amazon ECS (Elastic Container Service) resources."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def service_name(self) -> str:
|
|
15
|
+
return "ecs"
|
|
16
|
+
|
|
17
|
+
def collect(self) -> List[Resource]:
|
|
18
|
+
"""Collect ECS resources.
|
|
19
|
+
|
|
20
|
+
Collects:
|
|
21
|
+
- ECS Clusters
|
|
22
|
+
- ECS Services (within each cluster)
|
|
23
|
+
- ECS Task Definitions (active revisions)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List of ECS resources
|
|
27
|
+
"""
|
|
28
|
+
resources = []
|
|
29
|
+
|
|
30
|
+
# Collect clusters
|
|
31
|
+
resources.extend(self._collect_clusters())
|
|
32
|
+
|
|
33
|
+
# Collect services (across all clusters)
|
|
34
|
+
resources.extend(self._collect_services())
|
|
35
|
+
|
|
36
|
+
# Collect task definitions
|
|
37
|
+
resources.extend(self._collect_task_definitions())
|
|
38
|
+
|
|
39
|
+
self.logger.debug(f"Collected {len(resources)} ECS resources in {self.region}")
|
|
40
|
+
return resources
|
|
41
|
+
|
|
42
|
+
def _collect_clusters(self) -> List[Resource]:
|
|
43
|
+
"""Collect ECS clusters.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
List of ECS cluster resources
|
|
47
|
+
"""
|
|
48
|
+
resources = []
|
|
49
|
+
client = self._create_client()
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
paginator = client.get_paginator("list_clusters")
|
|
53
|
+
for page in paginator.paginate():
|
|
54
|
+
cluster_arns = page.get("clusterArns", [])
|
|
55
|
+
|
|
56
|
+
if not cluster_arns:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
# Get detailed info for clusters (in batches of 100)
|
|
60
|
+
for i in range(0, len(cluster_arns), 100):
|
|
61
|
+
batch = cluster_arns[i : i + 100]
|
|
62
|
+
try:
|
|
63
|
+
response = client.describe_clusters(clusters=batch, include=["TAGS"])
|
|
64
|
+
for cluster in response.get("clusters", []):
|
|
65
|
+
cluster_name = cluster["clusterName"]
|
|
66
|
+
cluster_arn = cluster["clusterArn"]
|
|
67
|
+
|
|
68
|
+
# Extract tags
|
|
69
|
+
tags = {}
|
|
70
|
+
for tag in cluster.get("tags", []):
|
|
71
|
+
tags[tag["key"]] = tag["value"]
|
|
72
|
+
|
|
73
|
+
# Extract creation timestamp (not always available)
|
|
74
|
+
created_at = None
|
|
75
|
+
|
|
76
|
+
# Create resource
|
|
77
|
+
resource = Resource(
|
|
78
|
+
arn=cluster_arn,
|
|
79
|
+
resource_type="AWS::ECS::Cluster",
|
|
80
|
+
name=cluster_name,
|
|
81
|
+
region=self.region,
|
|
82
|
+
tags=tags,
|
|
83
|
+
config_hash=compute_config_hash(cluster),
|
|
84
|
+
created_at=created_at,
|
|
85
|
+
raw_config=cluster,
|
|
86
|
+
)
|
|
87
|
+
resources.append(resource)
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
self.logger.debug(f"Error describing cluster batch: {e}")
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
self.logger.error(f"Error collecting ECS clusters in {self.region}: {e}")
|
|
94
|
+
|
|
95
|
+
return resources
|
|
96
|
+
|
|
97
|
+
def _collect_services(self) -> List[Resource]:
|
|
98
|
+
"""Collect ECS services across all clusters.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of ECS service resources
|
|
102
|
+
"""
|
|
103
|
+
resources = []
|
|
104
|
+
client = self._create_client()
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
# First, get all clusters
|
|
108
|
+
cluster_arns = []
|
|
109
|
+
paginator = client.get_paginator("list_clusters")
|
|
110
|
+
for page in paginator.paginate():
|
|
111
|
+
cluster_arns.extend(page.get("clusterArns", []))
|
|
112
|
+
|
|
113
|
+
# Collect services from each cluster
|
|
114
|
+
for cluster_arn in cluster_arns:
|
|
115
|
+
try:
|
|
116
|
+
service_paginator = client.get_paginator("list_services")
|
|
117
|
+
service_arns = []
|
|
118
|
+
for page in service_paginator.paginate(cluster=cluster_arn):
|
|
119
|
+
service_arns.extend(page.get("serviceArns", []))
|
|
120
|
+
|
|
121
|
+
if not service_arns:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Get detailed info for services (in batches of 10)
|
|
125
|
+
for i in range(0, len(service_arns), 10):
|
|
126
|
+
batch = service_arns[i : i + 10]
|
|
127
|
+
try:
|
|
128
|
+
response = client.describe_services(cluster=cluster_arn, services=batch, include=["TAGS"])
|
|
129
|
+
for service in response.get("services", []):
|
|
130
|
+
service_name = service["serviceName"]
|
|
131
|
+
service_arn = service["serviceArn"]
|
|
132
|
+
|
|
133
|
+
# Extract tags
|
|
134
|
+
tags = {}
|
|
135
|
+
for tag in service.get("tags", []):
|
|
136
|
+
tags[tag["key"]] = tag["value"]
|
|
137
|
+
|
|
138
|
+
# Extract creation date
|
|
139
|
+
created_at = service.get("createdAt")
|
|
140
|
+
|
|
141
|
+
# Create resource
|
|
142
|
+
resource = Resource(
|
|
143
|
+
arn=service_arn,
|
|
144
|
+
resource_type="AWS::ECS::Service",
|
|
145
|
+
name=service_name,
|
|
146
|
+
region=self.region,
|
|
147
|
+
tags=tags,
|
|
148
|
+
config_hash=compute_config_hash(service),
|
|
149
|
+
created_at=created_at,
|
|
150
|
+
raw_config=service,
|
|
151
|
+
)
|
|
152
|
+
resources.append(resource)
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
self.logger.debug(f"Error describing service batch: {e}")
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
self.logger.debug(f"Error collecting services from cluster {cluster_arn}: {e}")
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
self.logger.error(f"Error collecting ECS services in {self.region}: {e}")
|
|
162
|
+
|
|
163
|
+
return resources
|
|
164
|
+
|
|
165
|
+
def _collect_task_definitions(self) -> List[Resource]:
|
|
166
|
+
"""Collect ECS task definitions (active revisions only).
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
List of ECS task definition resources
|
|
170
|
+
"""
|
|
171
|
+
resources = []
|
|
172
|
+
client = self._create_client()
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
paginator = client.get_paginator("list_task_definitions")
|
|
176
|
+
for page in paginator.paginate(status="ACTIVE"):
|
|
177
|
+
for task_def_arn in page.get("taskDefinitionArns", []):
|
|
178
|
+
try:
|
|
179
|
+
# Get task definition details
|
|
180
|
+
response = client.describe_task_definition(taskDefinition=task_def_arn, include=["TAGS"])
|
|
181
|
+
task_def = response.get("taskDefinition", {})
|
|
182
|
+
|
|
183
|
+
# Extract family and revision
|
|
184
|
+
family = task_def.get("family", "unknown")
|
|
185
|
+
revision = task_def.get("revision", 0)
|
|
186
|
+
name = f"{family}:{revision}"
|
|
187
|
+
|
|
188
|
+
# Extract tags
|
|
189
|
+
tags = {}
|
|
190
|
+
for tag in response.get("tags", []):
|
|
191
|
+
tags[tag["key"]] = tag["value"]
|
|
192
|
+
|
|
193
|
+
# Task definitions don't have creation timestamp
|
|
194
|
+
created_at = None
|
|
195
|
+
|
|
196
|
+
# Create resource
|
|
197
|
+
resource = Resource(
|
|
198
|
+
arn=task_def_arn,
|
|
199
|
+
resource_type="AWS::ECS::TaskDefinition",
|
|
200
|
+
name=name,
|
|
201
|
+
region=self.region,
|
|
202
|
+
tags=tags,
|
|
203
|
+
config_hash=compute_config_hash(task_def),
|
|
204
|
+
created_at=created_at,
|
|
205
|
+
raw_config=task_def,
|
|
206
|
+
)
|
|
207
|
+
resources.append(resource)
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
self.logger.debug(f"Error describing task definition {task_def_arn}: {e}")
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
self.logger.error(f"Error collecting ECS task definitions in {self.region}: {e}")
|
|
214
|
+
|
|
215
|
+
return resources
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""EFS resource collector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from botocore.exceptions import ClientError
|
|
8
|
+
|
|
9
|
+
from ...models.efs_resource import EFSFileSystem
|
|
10
|
+
from ...models.resource import Resource
|
|
11
|
+
from .base import BaseResourceCollector
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EFSCollector(BaseResourceCollector):
|
|
15
|
+
"""Collector for AWS EFS (Elastic File System) resources."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def service_name(self) -> str:
|
|
19
|
+
return "efs"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def is_global_service(self) -> bool:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
def collect(self) -> List[Resource]:
|
|
26
|
+
"""Collect EFS file systems.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of EFS file system resources
|
|
30
|
+
"""
|
|
31
|
+
resources = []
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
client = self._create_client()
|
|
35
|
+
|
|
36
|
+
# Use paginator for describe_file_systems
|
|
37
|
+
paginator = client.get_paginator("describe_file_systems")
|
|
38
|
+
|
|
39
|
+
for page in paginator.paginate():
|
|
40
|
+
for fs in page.get("FileSystems", []):
|
|
41
|
+
try:
|
|
42
|
+
# Extract basic file system information
|
|
43
|
+
file_system_id = fs["FileSystemId"]
|
|
44
|
+
arn = fs["FileSystemArn"]
|
|
45
|
+
created_at = fs.get("CreationTime")
|
|
46
|
+
lifecycle_state = fs.get("LifeCycleState", "unknown")
|
|
47
|
+
performance_mode = fs.get("PerformanceMode", "generalPurpose")
|
|
48
|
+
encrypted = fs.get("Encrypted", False)
|
|
49
|
+
kms_key_id = fs.get("KmsKeyId")
|
|
50
|
+
|
|
51
|
+
# Extract tags
|
|
52
|
+
tags = {}
|
|
53
|
+
for tag in fs.get("Tags", []):
|
|
54
|
+
tags[tag["Key"]] = tag["Value"]
|
|
55
|
+
|
|
56
|
+
# Create EFSFileSystem model
|
|
57
|
+
efs_fs = EFSFileSystem(
|
|
58
|
+
file_system_id=file_system_id,
|
|
59
|
+
arn=arn,
|
|
60
|
+
encryption_enabled=encrypted,
|
|
61
|
+
kms_key_id=kms_key_id,
|
|
62
|
+
performance_mode=performance_mode,
|
|
63
|
+
lifecycle_state=lifecycle_state,
|
|
64
|
+
tags=tags,
|
|
65
|
+
region=self.region,
|
|
66
|
+
created_at=created_at,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Convert to Resource
|
|
70
|
+
resource_dict = efs_fs.to_resource_dict()
|
|
71
|
+
resource = Resource(
|
|
72
|
+
arn=resource_dict["arn"],
|
|
73
|
+
resource_type=resource_dict["resource_type"],
|
|
74
|
+
name=resource_dict["name"],
|
|
75
|
+
region=resource_dict["region"],
|
|
76
|
+
tags=resource_dict["tags"],
|
|
77
|
+
config_hash=resource_dict["config_hash"],
|
|
78
|
+
created_at=resource_dict["created_at"],
|
|
79
|
+
raw_config=resource_dict["raw_config"],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
resources.append(resource)
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
self.logger.debug(f"Error processing EFS file system {fs.get('FileSystemId', 'unknown')}: {e}")
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
except ClientError as e:
|
|
89
|
+
error_code = e.response.get("Error", {}).get("Code", "Unknown")
|
|
90
|
+
# Handle regions where EFS is not available or access denied
|
|
91
|
+
if error_code in ["OptInRequired", "AccessDenied", "InvalidAction"]:
|
|
92
|
+
self.logger.debug(f"EFS not available or access denied in {self.region}: {error_code}")
|
|
93
|
+
else:
|
|
94
|
+
self.logger.error(f"Error collecting EFS file systems in {self.region}: {e}")
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
self.logger.error(f"Error collecting EFS file systems in {self.region}: {e}")
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
self.logger.debug(f"Collected {len(resources)} EFS file systems in {self.region}")
|
|
102
|
+
return resources
|