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.
- aws_inventory_manager-0.13.2.dist-info/LICENSE +21 -0
- aws_inventory_manager-0.13.2.dist-info/METADATA +1226 -0
- aws_inventory_manager-0.13.2.dist-info/RECORD +145 -0
- aws_inventory_manager-0.13.2.dist-info/WHEEL +5 -0
- aws_inventory_manager-0.13.2.dist-info/entry_points.txt +2 -0
- aws_inventory_manager-0.13.2.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 +3626 -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/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 +451 -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/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 +749 -0
- src/storage/inventory_store.py +320 -0
- src/storage/resource_store.py +413 -0
- src/storage/schema.py +288 -0
- src/storage/snapshot_store.py +346 -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 +379 -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 +949 -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 +2251 -0
- src/web/templates/pages/snapshot_detail.html +271 -0
- src/web/templates/pages/snapshots.html +429 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""Inventory storage operations for SQLite backend."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from ..models.inventory import Inventory
|
|
8
|
+
from .database import Database, json_deserialize, json_serialize
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InventoryStore:
|
|
14
|
+
"""CRUD operations for inventories in SQLite database."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, db: Database):
|
|
17
|
+
"""Initialize inventory store.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
db: Database connection manager
|
|
21
|
+
"""
|
|
22
|
+
self.db = db
|
|
23
|
+
|
|
24
|
+
def save(self, inventory: Inventory) -> int:
|
|
25
|
+
"""Save or update inventory in database.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
inventory: Inventory to save
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Database ID of saved inventory
|
|
32
|
+
"""
|
|
33
|
+
# Get active snapshot ID if set
|
|
34
|
+
active_snapshot_id = None
|
|
35
|
+
if inventory.active_snapshot:
|
|
36
|
+
# Look up snapshot ID by name (remove file extensions if present)
|
|
37
|
+
snap_name = inventory.active_snapshot.replace(".yaml.gz", "").replace(".yaml", "")
|
|
38
|
+
row = self.db.fetchone("SELECT id FROM snapshots WHERE name = ?", (snap_name,))
|
|
39
|
+
if row:
|
|
40
|
+
active_snapshot_id = row["id"]
|
|
41
|
+
|
|
42
|
+
with self.db.transaction() as cursor:
|
|
43
|
+
# Check if inventory exists
|
|
44
|
+
existing = self.db.fetchone(
|
|
45
|
+
"SELECT id FROM inventories WHERE name = ? AND account_id = ?",
|
|
46
|
+
(inventory.name, inventory.account_id),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if existing:
|
|
50
|
+
# Update existing
|
|
51
|
+
cursor.execute(
|
|
52
|
+
"""
|
|
53
|
+
UPDATE inventories SET
|
|
54
|
+
description = ?,
|
|
55
|
+
include_tags = ?,
|
|
56
|
+
exclude_tags = ?,
|
|
57
|
+
active_snapshot_id = ?,
|
|
58
|
+
last_updated = ?
|
|
59
|
+
WHERE id = ?
|
|
60
|
+
""",
|
|
61
|
+
(
|
|
62
|
+
inventory.description,
|
|
63
|
+
json_serialize(inventory.include_tags),
|
|
64
|
+
json_serialize(inventory.exclude_tags),
|
|
65
|
+
active_snapshot_id,
|
|
66
|
+
inventory.last_updated.isoformat(),
|
|
67
|
+
existing["id"],
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
inventory_id = existing["id"]
|
|
71
|
+
|
|
72
|
+
# Update snapshot links
|
|
73
|
+
self._update_snapshot_links(cursor, inventory_id, inventory.snapshots)
|
|
74
|
+
|
|
75
|
+
logger.debug(f"Updated inventory '{inventory.name}' (id={inventory_id})")
|
|
76
|
+
else:
|
|
77
|
+
# Insert new
|
|
78
|
+
cursor.execute(
|
|
79
|
+
"""
|
|
80
|
+
INSERT INTO inventories (
|
|
81
|
+
name, account_id, description, include_tags, exclude_tags,
|
|
82
|
+
active_snapshot_id, created_at, last_updated
|
|
83
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
84
|
+
""",
|
|
85
|
+
(
|
|
86
|
+
inventory.name,
|
|
87
|
+
inventory.account_id,
|
|
88
|
+
inventory.description,
|
|
89
|
+
json_serialize(inventory.include_tags),
|
|
90
|
+
json_serialize(inventory.exclude_tags),
|
|
91
|
+
active_snapshot_id,
|
|
92
|
+
inventory.created_at.isoformat(),
|
|
93
|
+
inventory.last_updated.isoformat(),
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
inventory_id = cursor.lastrowid
|
|
97
|
+
|
|
98
|
+
# Add snapshot links
|
|
99
|
+
self._update_snapshot_links(cursor, inventory_id, inventory.snapshots)
|
|
100
|
+
|
|
101
|
+
logger.debug(f"Saved inventory '{inventory.name}' (id={inventory_id})")
|
|
102
|
+
|
|
103
|
+
return inventory_id
|
|
104
|
+
|
|
105
|
+
def _update_snapshot_links(self, cursor, inventory_id: int, snapshot_names: List[str]) -> None:
|
|
106
|
+
"""Update inventory-snapshot links.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
cursor: Database cursor
|
|
110
|
+
inventory_id: Inventory ID
|
|
111
|
+
snapshot_names: List of snapshot names to link
|
|
112
|
+
"""
|
|
113
|
+
# Clear existing links
|
|
114
|
+
cursor.execute("DELETE FROM inventory_snapshots WHERE inventory_id = ?", (inventory_id,))
|
|
115
|
+
|
|
116
|
+
# Add new links
|
|
117
|
+
for snap_name in snapshot_names:
|
|
118
|
+
# Remove file extensions if present
|
|
119
|
+
snap_name = snap_name.replace(".yaml.gz", "").replace(".yaml", "")
|
|
120
|
+
row = self.db.fetchone("SELECT id FROM snapshots WHERE name = ?", (snap_name,))
|
|
121
|
+
if row:
|
|
122
|
+
cursor.execute(
|
|
123
|
+
"INSERT OR IGNORE INTO inventory_snapshots (inventory_id, snapshot_id) VALUES (?, ?)",
|
|
124
|
+
(inventory_id, row["id"]),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def load(self, name: str, account_id: str) -> Optional[Inventory]:
|
|
128
|
+
"""Load inventory by name and account.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
name: Inventory name
|
|
132
|
+
account_id: AWS account ID
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Inventory object or None if not found
|
|
136
|
+
"""
|
|
137
|
+
row = self.db.fetchone(
|
|
138
|
+
"SELECT * FROM inventories WHERE name = ? AND account_id = ?",
|
|
139
|
+
(name, account_id),
|
|
140
|
+
)
|
|
141
|
+
if not row:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
return self._row_to_inventory(row)
|
|
145
|
+
|
|
146
|
+
def _row_to_inventory(self, row: Dict[str, Any]) -> Inventory:
|
|
147
|
+
"""Convert database row to Inventory object.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
row: Database row dict
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Inventory object
|
|
154
|
+
"""
|
|
155
|
+
inventory_id = row["id"]
|
|
156
|
+
|
|
157
|
+
# Get linked snapshot names
|
|
158
|
+
snapshot_rows = self.db.fetchall(
|
|
159
|
+
"""
|
|
160
|
+
SELECT s.name FROM snapshots s
|
|
161
|
+
JOIN inventory_snapshots is_link ON s.id = is_link.snapshot_id
|
|
162
|
+
WHERE is_link.inventory_id = ?
|
|
163
|
+
ORDER BY s.created_at DESC
|
|
164
|
+
""",
|
|
165
|
+
(inventory_id,),
|
|
166
|
+
)
|
|
167
|
+
snapshots = [r["name"] for r in snapshot_rows]
|
|
168
|
+
|
|
169
|
+
# Get active snapshot name
|
|
170
|
+
active_snapshot = None
|
|
171
|
+
if row["active_snapshot_id"]:
|
|
172
|
+
active_row = self.db.fetchone(
|
|
173
|
+
"SELECT name FROM snapshots WHERE id = ?",
|
|
174
|
+
(row["active_snapshot_id"],),
|
|
175
|
+
)
|
|
176
|
+
if active_row:
|
|
177
|
+
active_snapshot = active_row["name"]
|
|
178
|
+
|
|
179
|
+
# Parse timestamps
|
|
180
|
+
created_at = datetime.fromisoformat(row["created_at"])
|
|
181
|
+
if created_at.tzinfo is None:
|
|
182
|
+
created_at = created_at.replace(tzinfo=timezone.utc)
|
|
183
|
+
|
|
184
|
+
last_updated = datetime.fromisoformat(row["last_updated"])
|
|
185
|
+
if last_updated.tzinfo is None:
|
|
186
|
+
last_updated = last_updated.replace(tzinfo=timezone.utc)
|
|
187
|
+
|
|
188
|
+
return Inventory(
|
|
189
|
+
name=row["name"],
|
|
190
|
+
account_id=row["account_id"],
|
|
191
|
+
description=row["description"] or "",
|
|
192
|
+
include_tags=json_deserialize(row["include_tags"]) or {},
|
|
193
|
+
exclude_tags=json_deserialize(row["exclude_tags"]) or {},
|
|
194
|
+
snapshots=snapshots,
|
|
195
|
+
active_snapshot=active_snapshot,
|
|
196
|
+
created_at=created_at,
|
|
197
|
+
last_updated=last_updated,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def list_all(self) -> List[Inventory]:
|
|
201
|
+
"""List all inventories.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
List of all Inventory objects
|
|
205
|
+
"""
|
|
206
|
+
rows = self.db.fetchall(
|
|
207
|
+
"SELECT * FROM inventories ORDER BY account_id, name"
|
|
208
|
+
)
|
|
209
|
+
return [self._row_to_inventory(row) for row in rows]
|
|
210
|
+
|
|
211
|
+
def list_by_account(self, account_id: str) -> List[Inventory]:
|
|
212
|
+
"""List inventories for a specific account.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
account_id: AWS account ID
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
List of Inventory objects for the account
|
|
219
|
+
"""
|
|
220
|
+
rows = self.db.fetchall(
|
|
221
|
+
"SELECT * FROM inventories WHERE account_id = ? ORDER BY name",
|
|
222
|
+
(account_id,),
|
|
223
|
+
)
|
|
224
|
+
return [self._row_to_inventory(row) for row in rows]
|
|
225
|
+
|
|
226
|
+
def delete(self, name: str, account_id: str) -> bool:
|
|
227
|
+
"""Delete inventory.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
name: Inventory name
|
|
231
|
+
account_id: AWS account ID
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
True if deleted, False if not found
|
|
235
|
+
"""
|
|
236
|
+
with self.db.transaction() as cursor:
|
|
237
|
+
cursor.execute(
|
|
238
|
+
"DELETE FROM inventories WHERE name = ? AND account_id = ?",
|
|
239
|
+
(name, account_id),
|
|
240
|
+
)
|
|
241
|
+
deleted = cursor.rowcount > 0
|
|
242
|
+
|
|
243
|
+
if deleted:
|
|
244
|
+
logger.debug(f"Deleted inventory '{name}' for account {account_id}")
|
|
245
|
+
return deleted
|
|
246
|
+
|
|
247
|
+
def exists(self, name: str, account_id: str) -> bool:
|
|
248
|
+
"""Check if inventory exists.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
name: Inventory name
|
|
252
|
+
account_id: AWS account ID
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
True if exists
|
|
256
|
+
"""
|
|
257
|
+
row = self.db.fetchone(
|
|
258
|
+
"SELECT 1 FROM inventories WHERE name = ? AND account_id = ?",
|
|
259
|
+
(name, account_id),
|
|
260
|
+
)
|
|
261
|
+
return row is not None
|
|
262
|
+
|
|
263
|
+
def add_snapshot_to_inventory(
|
|
264
|
+
self, name: str, account_id: str, snapshot_name: str, set_active: bool = False
|
|
265
|
+
) -> bool:
|
|
266
|
+
"""Add a snapshot to an inventory.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
name: Inventory name
|
|
270
|
+
account_id: AWS account ID
|
|
271
|
+
snapshot_name: Snapshot name to add
|
|
272
|
+
set_active: Whether to set this as the active snapshot
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
True if successful, False if inventory not found
|
|
276
|
+
"""
|
|
277
|
+
inventory = self.load(name, account_id)
|
|
278
|
+
if not inventory:
|
|
279
|
+
return False
|
|
280
|
+
|
|
281
|
+
inventory.add_snapshot(snapshot_name, set_active)
|
|
282
|
+
self.save(inventory)
|
|
283
|
+
return True
|
|
284
|
+
|
|
285
|
+
def remove_snapshot_from_inventory(
|
|
286
|
+
self, name: str, account_id: str, snapshot_name: str
|
|
287
|
+
) -> bool:
|
|
288
|
+
"""Remove a snapshot from an inventory.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
name: Inventory name
|
|
292
|
+
account_id: AWS account ID
|
|
293
|
+
snapshot_name: Snapshot name to remove
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
True if successful, False if inventory not found
|
|
297
|
+
"""
|
|
298
|
+
inventory = self.load(name, account_id)
|
|
299
|
+
if not inventory:
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
inventory.remove_snapshot(snapshot_name)
|
|
303
|
+
self.save(inventory)
|
|
304
|
+
return True
|
|
305
|
+
|
|
306
|
+
def get_id(self, name: str, account_id: str) -> Optional[int]:
|
|
307
|
+
"""Get database ID for inventory.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
name: Inventory name
|
|
311
|
+
account_id: AWS account ID
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Database ID or None
|
|
315
|
+
"""
|
|
316
|
+
row = self.db.fetchone(
|
|
317
|
+
"SELECT id FROM inventories WHERE name = ? AND account_id = ?",
|
|
318
|
+
(name, account_id),
|
|
319
|
+
)
|
|
320
|
+
return row["id"] if row else None
|