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,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