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,416 @@
|
|
|
1
|
+
"""Resource query operations for SQLite backend."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
from .database import Database, json_deserialize
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ResourceStore:
|
|
14
|
+
"""Query operations for resources across snapshots."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, db: Database):
|
|
17
|
+
"""Initialize resource store.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
db: Database connection manager
|
|
21
|
+
"""
|
|
22
|
+
self.db = db
|
|
23
|
+
|
|
24
|
+
def query_raw(self, sql: str, params: Tuple = ()) -> List[Dict[str, Any]]:
|
|
25
|
+
"""Execute raw SQL query on the database.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
sql: SQL query (should be SELECT only)
|
|
29
|
+
params: Query parameters
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of result dictionaries
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
ValueError: If query is not a SELECT statement
|
|
36
|
+
"""
|
|
37
|
+
# Basic SQL injection prevention - only allow SELECT
|
|
38
|
+
sql_upper = sql.strip().upper()
|
|
39
|
+
if not sql_upper.startswith("SELECT"):
|
|
40
|
+
raise ValueError("Only SELECT queries are allowed")
|
|
41
|
+
|
|
42
|
+
# Block dangerous keywords
|
|
43
|
+
dangerous = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "CREATE", "TRUNCATE"]
|
|
44
|
+
for keyword in dangerous:
|
|
45
|
+
if re.search(rf"\b{keyword}\b", sql_upper):
|
|
46
|
+
raise ValueError(f"Query contains forbidden keyword: {keyword}")
|
|
47
|
+
|
|
48
|
+
return self.db.fetchall(sql, params)
|
|
49
|
+
|
|
50
|
+
def search(
|
|
51
|
+
self,
|
|
52
|
+
arn_pattern: Optional[str] = None,
|
|
53
|
+
resource_type: Optional[str] = None,
|
|
54
|
+
region: Optional[str] = None,
|
|
55
|
+
tag_key: Optional[str] = None,
|
|
56
|
+
tag_value: Optional[str] = None,
|
|
57
|
+
snapshot_name: Optional[str] = None,
|
|
58
|
+
created_before: Optional[datetime] = None,
|
|
59
|
+
created_after: Optional[datetime] = None,
|
|
60
|
+
limit: int = 100,
|
|
61
|
+
offset: int = 0,
|
|
62
|
+
) -> List[Dict[str, Any]]:
|
|
63
|
+
"""Search resources with filters.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
arn_pattern: ARN pattern to match (supports % wildcard)
|
|
67
|
+
resource_type: Filter by resource type (exact or partial)
|
|
68
|
+
region: Filter by region
|
|
69
|
+
tag_key: Filter by tag key
|
|
70
|
+
tag_value: Filter by tag value (requires tag_key)
|
|
71
|
+
snapshot_name: Limit to specific snapshot
|
|
72
|
+
created_before: Resources created before this date
|
|
73
|
+
created_after: Resources created after this date
|
|
74
|
+
limit: Maximum results to return
|
|
75
|
+
offset: Offset for pagination
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of matching resources with snapshot info
|
|
79
|
+
"""
|
|
80
|
+
conditions = []
|
|
81
|
+
params: List[Any] = []
|
|
82
|
+
|
|
83
|
+
# Build query with joins
|
|
84
|
+
base_query = """
|
|
85
|
+
SELECT DISTINCT
|
|
86
|
+
r.arn,
|
|
87
|
+
r.resource_type,
|
|
88
|
+
r.name,
|
|
89
|
+
r.region,
|
|
90
|
+
r.config_hash,
|
|
91
|
+
r.created_at,
|
|
92
|
+
r.source,
|
|
93
|
+
r.canonical_name,
|
|
94
|
+
r.normalized_name,
|
|
95
|
+
r.normalization_method,
|
|
96
|
+
s.name as snapshot_name,
|
|
97
|
+
s.created_at as snapshot_created_at,
|
|
98
|
+
s.account_id
|
|
99
|
+
FROM resources r
|
|
100
|
+
JOIN snapshots s ON r.snapshot_id = s.id
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
# Tag join if filtering by tags
|
|
104
|
+
if tag_key:
|
|
105
|
+
base_query += " JOIN resource_tags t ON r.id = t.resource_id"
|
|
106
|
+
conditions.append("t.key = ?")
|
|
107
|
+
params.append(tag_key)
|
|
108
|
+
if tag_value:
|
|
109
|
+
conditions.append("t.value = ?")
|
|
110
|
+
params.append(tag_value)
|
|
111
|
+
|
|
112
|
+
# ARN filter
|
|
113
|
+
if arn_pattern:
|
|
114
|
+
if "%" in arn_pattern:
|
|
115
|
+
conditions.append("r.arn LIKE ?")
|
|
116
|
+
else:
|
|
117
|
+
conditions.append("r.arn LIKE ?")
|
|
118
|
+
arn_pattern = f"%{arn_pattern}%"
|
|
119
|
+
params.append(arn_pattern)
|
|
120
|
+
|
|
121
|
+
# Resource type filter
|
|
122
|
+
if resource_type:
|
|
123
|
+
if ":" in resource_type:
|
|
124
|
+
conditions.append("r.resource_type = ?")
|
|
125
|
+
else:
|
|
126
|
+
conditions.append("r.resource_type LIKE ?")
|
|
127
|
+
resource_type = f"%{resource_type}%"
|
|
128
|
+
params.append(resource_type)
|
|
129
|
+
|
|
130
|
+
# Region filter
|
|
131
|
+
if region:
|
|
132
|
+
conditions.append("r.region = ?")
|
|
133
|
+
params.append(region)
|
|
134
|
+
|
|
135
|
+
# Snapshot filter
|
|
136
|
+
if snapshot_name:
|
|
137
|
+
conditions.append("s.name = ?")
|
|
138
|
+
params.append(snapshot_name)
|
|
139
|
+
|
|
140
|
+
# Date filters
|
|
141
|
+
if created_before:
|
|
142
|
+
conditions.append("r.created_at < ?")
|
|
143
|
+
params.append(created_before.isoformat())
|
|
144
|
+
|
|
145
|
+
if created_after:
|
|
146
|
+
conditions.append("r.created_at >= ?")
|
|
147
|
+
params.append(created_after.isoformat())
|
|
148
|
+
|
|
149
|
+
# Build final query
|
|
150
|
+
if conditions:
|
|
151
|
+
base_query += " WHERE " + " AND ".join(conditions)
|
|
152
|
+
|
|
153
|
+
base_query += " ORDER BY s.created_at DESC, r.arn"
|
|
154
|
+
base_query += f" LIMIT {limit} OFFSET {offset}"
|
|
155
|
+
|
|
156
|
+
return self.db.fetchall(base_query, tuple(params))
|
|
157
|
+
|
|
158
|
+
def get_history(self, arn: str) -> List[Dict[str, Any]]:
|
|
159
|
+
"""Get all snapshots containing a specific resource.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
arn: Resource ARN
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of snapshots with resource details, ordered by date
|
|
166
|
+
"""
|
|
167
|
+
return self.db.fetchall(
|
|
168
|
+
"""
|
|
169
|
+
SELECT
|
|
170
|
+
s.name as snapshot_name,
|
|
171
|
+
s.created_at as snapshot_created_at,
|
|
172
|
+
s.account_id,
|
|
173
|
+
r.config_hash,
|
|
174
|
+
r.created_at as resource_created_at,
|
|
175
|
+
r.source
|
|
176
|
+
FROM resources r
|
|
177
|
+
JOIN snapshots s ON r.snapshot_id = s.id
|
|
178
|
+
WHERE r.arn = ?
|
|
179
|
+
ORDER BY s.created_at DESC
|
|
180
|
+
""",
|
|
181
|
+
(arn,),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def get_stats(
|
|
185
|
+
self,
|
|
186
|
+
snapshot_name: Optional[str] = None,
|
|
187
|
+
group_by: str = "type",
|
|
188
|
+
) -> List[Dict[str, Any]]:
|
|
189
|
+
"""Get resource statistics.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
snapshot_name: Limit to specific snapshot (None for all)
|
|
193
|
+
group_by: Grouping field - 'type', 'region', 'service', 'snapshot'
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List of statistics grouped by specified field
|
|
197
|
+
"""
|
|
198
|
+
group_field = {
|
|
199
|
+
"type": "r.resource_type",
|
|
200
|
+
"region": "r.region",
|
|
201
|
+
"service": "SUBSTR(r.resource_type, 1, INSTR(r.resource_type, ':') - 1)",
|
|
202
|
+
"snapshot": "s.name",
|
|
203
|
+
}.get(group_by, "r.resource_type")
|
|
204
|
+
|
|
205
|
+
base_query = f"""
|
|
206
|
+
SELECT
|
|
207
|
+
{group_field} as group_key,
|
|
208
|
+
COUNT(*) as count
|
|
209
|
+
FROM resources r
|
|
210
|
+
JOIN snapshots s ON r.snapshot_id = s.id
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
params: List[str] = []
|
|
214
|
+
if snapshot_name:
|
|
215
|
+
base_query += " WHERE s.name = ?"
|
|
216
|
+
params.append(snapshot_name)
|
|
217
|
+
|
|
218
|
+
base_query += f" GROUP BY {group_field} ORDER BY count DESC"
|
|
219
|
+
|
|
220
|
+
return self.db.fetchall(base_query, tuple(params))
|
|
221
|
+
|
|
222
|
+
def compare_snapshots(
|
|
223
|
+
self,
|
|
224
|
+
snapshot1_name: str,
|
|
225
|
+
snapshot2_name: str,
|
|
226
|
+
) -> Dict[str, List[Dict[str, Any]]]:
|
|
227
|
+
"""Compare resources between two snapshots.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
snapshot1_name: First (older) snapshot name
|
|
231
|
+
snapshot2_name: Second (newer) snapshot name
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Dict with 'added', 'removed', 'modified' resource lists
|
|
235
|
+
"""
|
|
236
|
+
# Get resources from both snapshots indexed by ARN
|
|
237
|
+
snap1_resources = self.db.fetchall(
|
|
238
|
+
"""
|
|
239
|
+
SELECT r.arn, r.resource_type, r.name, r.region, r.config_hash
|
|
240
|
+
FROM resources r
|
|
241
|
+
JOIN snapshots s ON r.snapshot_id = s.id
|
|
242
|
+
WHERE s.name = ?
|
|
243
|
+
""",
|
|
244
|
+
(snapshot1_name,),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
snap2_resources = self.db.fetchall(
|
|
248
|
+
"""
|
|
249
|
+
SELECT r.arn, r.resource_type, r.name, r.region, r.config_hash
|
|
250
|
+
FROM resources r
|
|
251
|
+
JOIN snapshots s ON r.snapshot_id = s.id
|
|
252
|
+
WHERE s.name = ?
|
|
253
|
+
""",
|
|
254
|
+
(snapshot2_name,),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
snap1_by_arn = {r["arn"]: r for r in snap1_resources}
|
|
258
|
+
snap2_by_arn = {r["arn"]: r for r in snap2_resources}
|
|
259
|
+
|
|
260
|
+
snap1_arns = set(snap1_by_arn.keys())
|
|
261
|
+
snap2_arns = set(snap2_by_arn.keys())
|
|
262
|
+
|
|
263
|
+
added = [dict(snap2_by_arn[arn]) for arn in (snap2_arns - snap1_arns)]
|
|
264
|
+
removed = [dict(snap1_by_arn[arn]) for arn in (snap1_arns - snap2_arns)]
|
|
265
|
+
|
|
266
|
+
# Find modified (same ARN, different hash)
|
|
267
|
+
modified = []
|
|
268
|
+
for arn in snap1_arns & snap2_arns:
|
|
269
|
+
if snap1_by_arn[arn]["config_hash"] != snap2_by_arn[arn]["config_hash"]:
|
|
270
|
+
modified.append(
|
|
271
|
+
{
|
|
272
|
+
"arn": arn,
|
|
273
|
+
"resource_type": snap2_by_arn[arn]["resource_type"],
|
|
274
|
+
"name": snap2_by_arn[arn]["name"],
|
|
275
|
+
"region": snap2_by_arn[arn]["region"],
|
|
276
|
+
"old_hash": snap1_by_arn[arn]["config_hash"],
|
|
277
|
+
"new_hash": snap2_by_arn[arn]["config_hash"],
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
"added": added,
|
|
283
|
+
"removed": removed,
|
|
284
|
+
"modified": modified,
|
|
285
|
+
"summary": {
|
|
286
|
+
"snapshot1": snapshot1_name,
|
|
287
|
+
"snapshot2": snapshot2_name,
|
|
288
|
+
"snapshot1_count": len(snap1_resources),
|
|
289
|
+
"snapshot2_count": len(snap2_resources),
|
|
290
|
+
"added_count": len(added),
|
|
291
|
+
"removed_count": len(removed),
|
|
292
|
+
"modified_count": len(modified),
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
def get_tags_for_resource(self, arn: str, snapshot_name: Optional[str] = None) -> Dict[str, str]:
|
|
297
|
+
"""Get tags for a specific resource.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
arn: Resource ARN
|
|
301
|
+
snapshot_name: Specific snapshot (uses most recent if None)
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Dict of tag key-value pairs
|
|
305
|
+
"""
|
|
306
|
+
query = """
|
|
307
|
+
SELECT t.key, t.value
|
|
308
|
+
FROM resource_tags t
|
|
309
|
+
JOIN resources r ON t.resource_id = r.id
|
|
310
|
+
JOIN snapshots s ON r.snapshot_id = s.id
|
|
311
|
+
WHERE r.arn = ?
|
|
312
|
+
"""
|
|
313
|
+
params: List[str] = [arn]
|
|
314
|
+
|
|
315
|
+
if snapshot_name:
|
|
316
|
+
query += " AND s.name = ?"
|
|
317
|
+
params.append(snapshot_name)
|
|
318
|
+
else:
|
|
319
|
+
query += " ORDER BY s.created_at DESC LIMIT 100"
|
|
320
|
+
|
|
321
|
+
rows = self.db.fetchall(query, tuple(params))
|
|
322
|
+
return {row["key"]: row["value"] for row in rows}
|
|
323
|
+
|
|
324
|
+
def find_by_tag(
|
|
325
|
+
self,
|
|
326
|
+
tag_key: str,
|
|
327
|
+
tag_value: Optional[str] = None,
|
|
328
|
+
snapshot_name: Optional[str] = None,
|
|
329
|
+
limit: int = 100,
|
|
330
|
+
) -> List[Dict[str, Any]]:
|
|
331
|
+
"""Find resources by tag.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
tag_key: Tag key to search for
|
|
335
|
+
tag_value: Optional tag value to match
|
|
336
|
+
snapshot_name: Limit to specific snapshot
|
|
337
|
+
limit: Maximum results
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
List of matching resources
|
|
341
|
+
"""
|
|
342
|
+
query = """
|
|
343
|
+
SELECT DISTINCT
|
|
344
|
+
r.arn,
|
|
345
|
+
r.resource_type,
|
|
346
|
+
r.name,
|
|
347
|
+
r.region,
|
|
348
|
+
s.name as snapshot_name,
|
|
349
|
+
t.key as tag_key,
|
|
350
|
+
t.value as tag_value
|
|
351
|
+
FROM resources r
|
|
352
|
+
JOIN snapshots s ON r.snapshot_id = s.id
|
|
353
|
+
JOIN resource_tags t ON r.id = t.resource_id
|
|
354
|
+
WHERE t.key = ?
|
|
355
|
+
"""
|
|
356
|
+
params: List[Any] = [tag_key]
|
|
357
|
+
|
|
358
|
+
if tag_value:
|
|
359
|
+
query += " AND t.value = ?"
|
|
360
|
+
params.append(tag_value)
|
|
361
|
+
|
|
362
|
+
if snapshot_name:
|
|
363
|
+
query += " AND s.name = ?"
|
|
364
|
+
params.append(snapshot_name)
|
|
365
|
+
|
|
366
|
+
query += f" ORDER BY s.created_at DESC LIMIT {limit}"
|
|
367
|
+
|
|
368
|
+
return self.db.fetchall(query, tuple(params))
|
|
369
|
+
|
|
370
|
+
def get_unique_resource_types(self, snapshot_name: Optional[str] = None) -> List[str]:
|
|
371
|
+
"""Get list of unique resource types.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
snapshot_name: Limit to specific snapshot
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
List of resource type strings
|
|
378
|
+
"""
|
|
379
|
+
query = """
|
|
380
|
+
SELECT DISTINCT r.resource_type
|
|
381
|
+
FROM resources r
|
|
382
|
+
"""
|
|
383
|
+
params: List[str] = []
|
|
384
|
+
|
|
385
|
+
if snapshot_name:
|
|
386
|
+
query += " JOIN snapshots s ON r.snapshot_id = s.id WHERE s.name = ?"
|
|
387
|
+
params.append(snapshot_name)
|
|
388
|
+
|
|
389
|
+
query += " ORDER BY r.resource_type"
|
|
390
|
+
|
|
391
|
+
rows = self.db.fetchall(query, tuple(params))
|
|
392
|
+
return [row["resource_type"] for row in rows]
|
|
393
|
+
|
|
394
|
+
def get_unique_regions(self, snapshot_name: Optional[str] = None) -> List[str]:
|
|
395
|
+
"""Get list of unique regions.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
snapshot_name: Limit to specific snapshot
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
List of region strings
|
|
402
|
+
"""
|
|
403
|
+
query = """
|
|
404
|
+
SELECT DISTINCT r.region
|
|
405
|
+
FROM resources r
|
|
406
|
+
"""
|
|
407
|
+
params: List[str] = []
|
|
408
|
+
|
|
409
|
+
if snapshot_name:
|
|
410
|
+
query += " JOIN snapshots s ON r.snapshot_id = s.id WHERE s.name = ?"
|
|
411
|
+
params.append(snapshot_name)
|
|
412
|
+
|
|
413
|
+
query += " ORDER BY r.region"
|
|
414
|
+
|
|
415
|
+
rows = self.db.fetchall(query, tuple(params))
|
|
416
|
+
return [row["region"] for row in rows]
|