aws-inventory-manager 0.13.2__py3-none-any.whl → 0.16.0__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 → aws_inventory_manager-0.16.0.dist-info}/METADATA +1 -1
- {aws_inventory_manager-0.13.2.dist-info → aws_inventory_manager-0.16.0.dist-info}/RECORD +20 -13
- src/cli/main.py +202 -3
- src/cloudtrail/__init__.py +5 -0
- src/cloudtrail/query.py +419 -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/snapshot/capturer.py +2 -0
- src/snapshot/resource_collectors/glue.py +199 -0
- src/storage/group_store.py +22 -8
- src/storage/resource_store.py +3 -0
- src/storage/schema.py +52 -1
- src/storage/snapshot_store.py +19 -2
- src/web/templates/pages/resources.html +3 -0
- {aws_inventory_manager-0.13.2.dist-info → aws_inventory_manager-0.16.0.dist-info}/LICENSE +0 -0
- {aws_inventory_manager-0.13.2.dist-info → aws_inventory_manager-0.16.0.dist-info}/WHEEL +0 -0
- {aws_inventory_manager-0.13.2.dist-info → aws_inventory_manager-0.16.0.dist-info}/entry_points.txt +0 -0
- {aws_inventory_manager-0.13.2.dist-info → aws_inventory_manager-0.16.0.dist-info}/top_level.txt +0 -0
src/storage/group_store.py
CHANGED
|
@@ -485,7 +485,8 @@ class GroupStore:
|
|
|
485
485
|
params.append(region_filter)
|
|
486
486
|
|
|
487
487
|
query = f"""
|
|
488
|
-
SELECT r.arn, r.resource_type, r.name, r.canonical_name
|
|
488
|
+
SELECT r.arn, r.resource_type, r.name, r.canonical_name,
|
|
489
|
+
r.normalized_name, r.normalization_method
|
|
489
490
|
FROM resources r
|
|
490
491
|
WHERE {" AND ".join(conditions)}
|
|
491
492
|
ORDER BY r.resource_type, r.name
|
|
@@ -494,17 +495,24 @@ class GroupStore:
|
|
|
494
495
|
resource_rows = self.db.fetchall(query, tuple(params))
|
|
495
496
|
|
|
496
497
|
# Create group with members
|
|
497
|
-
#
|
|
498
|
+
# Choose the best match strategy based on how the resource was normalized
|
|
498
499
|
members = []
|
|
499
500
|
for row in resource_rows:
|
|
500
501
|
physical_name = row["name"] or extract_resource_name(row["arn"], row["resource_type"])
|
|
501
|
-
|
|
502
|
+
normalized_name = row.get("normalized_name")
|
|
503
|
+
normalization_method = row.get("normalization_method") or "none"
|
|
502
504
|
|
|
503
|
-
#
|
|
504
|
-
if
|
|
505
|
-
|
|
505
|
+
# Choose match strategy based on normalization method
|
|
506
|
+
if normalization_method == "tag:logical-id":
|
|
507
|
+
# CloudFormation logical ID - most reliable
|
|
508
|
+
resource_name = row.get("canonical_name") or normalized_name
|
|
506
509
|
match_strategy = "logical_id"
|
|
510
|
+
elif normalization_method in ("tag:Name", "pattern"):
|
|
511
|
+
# Name tag or pattern extraction - use normalized name
|
|
512
|
+
resource_name = normalized_name or physical_name
|
|
513
|
+
match_strategy = "normalized"
|
|
507
514
|
else:
|
|
515
|
+
# No normalization - use physical name
|
|
508
516
|
resource_name = physical_name
|
|
509
517
|
match_strategy = "physical_name"
|
|
510
518
|
|
|
@@ -672,10 +680,12 @@ class GroupStore:
|
|
|
672
680
|
# Use NOT EXISTS to find resources not in group
|
|
673
681
|
# Match strategy determines how to compare:
|
|
674
682
|
# - 'logical_id': match on canonical_name (CloudFormation logical ID)
|
|
683
|
+
# - 'normalized': match on normalized_name (pattern-stripped semantic name)
|
|
675
684
|
# - 'physical_name': match on physical name or ARN
|
|
676
685
|
rows = self.db.fetchall(
|
|
677
686
|
"""
|
|
678
|
-
SELECT r.arn, r.resource_type, r.name, r.region, r.created_at,
|
|
687
|
+
SELECT r.arn, r.resource_type, r.name, r.region, r.created_at,
|
|
688
|
+
r.canonical_name, r.normalized_name, r.normalization_method
|
|
679
689
|
FROM resources r
|
|
680
690
|
WHERE r.snapshot_id = ?
|
|
681
691
|
AND NOT EXISTS (
|
|
@@ -684,6 +694,7 @@ class GroupStore:
|
|
|
684
694
|
AND r.resource_type = gm.resource_type
|
|
685
695
|
AND (
|
|
686
696
|
(gm.match_strategy = 'logical_id' AND r.canonical_name = gm.resource_name)
|
|
697
|
+
OR (gm.match_strategy = 'normalized' AND r.normalized_name = gm.resource_name)
|
|
687
698
|
OR (COALESCE(gm.match_strategy, 'physical_name') = 'physical_name' AND COALESCE(r.name, r.arn) = gm.resource_name)
|
|
688
699
|
)
|
|
689
700
|
)
|
|
@@ -727,14 +738,17 @@ class GroupStore:
|
|
|
727
738
|
# Use INNER JOIN to find resources in group
|
|
728
739
|
# Match strategy determines how to compare:
|
|
729
740
|
# - 'logical_id': match on canonical_name (CloudFormation logical ID)
|
|
741
|
+
# - 'normalized': match on normalized_name (pattern-stripped semantic name)
|
|
730
742
|
# - 'physical_name': match on physical name or ARN
|
|
731
743
|
rows = self.db.fetchall(
|
|
732
744
|
"""
|
|
733
|
-
SELECT r.arn, r.resource_type, r.name, r.region, r.created_at,
|
|
745
|
+
SELECT r.arn, r.resource_type, r.name, r.region, r.created_at,
|
|
746
|
+
r.canonical_name, r.normalized_name, r.normalization_method
|
|
734
747
|
FROM resources r
|
|
735
748
|
INNER JOIN resource_group_members gm
|
|
736
749
|
ON (
|
|
737
750
|
(gm.match_strategy = 'logical_id' AND r.canonical_name = gm.resource_name)
|
|
751
|
+
OR (gm.match_strategy = 'normalized' AND r.normalized_name = gm.resource_name)
|
|
738
752
|
OR (COALESCE(gm.match_strategy, 'physical_name') = 'physical_name' AND COALESCE(r.name, r.arn) = gm.resource_name)
|
|
739
753
|
)
|
|
740
754
|
AND r.resource_type = gm.resource_type
|
src/storage/resource_store.py
CHANGED
src/storage/schema.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""SQLite schema definitions for AWS Inventory Manager."""
|
|
2
2
|
|
|
3
|
-
SCHEMA_VERSION = "1.
|
|
3
|
+
SCHEMA_VERSION = "1.2.0"
|
|
4
4
|
|
|
5
5
|
# Schema creation SQL
|
|
6
6
|
SCHEMA_SQL = """
|
|
@@ -40,6 +40,9 @@ CREATE TABLE IF NOT EXISTS resources (
|
|
|
40
40
|
created_at TIMESTAMP,
|
|
41
41
|
source TEXT DEFAULT 'direct_api',
|
|
42
42
|
canonical_name TEXT,
|
|
43
|
+
normalized_name TEXT,
|
|
44
|
+
extracted_patterns TEXT,
|
|
45
|
+
normalization_method TEXT,
|
|
43
46
|
FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
44
47
|
UNIQUE(snapshot_id, arn)
|
|
45
48
|
);
|
|
@@ -190,6 +193,7 @@ CREATE INDEX IF NOT EXISTS idx_resources_created ON resources(created_at);
|
|
|
190
193
|
CREATE INDEX IF NOT EXISTS idx_resources_snapshot ON resources(snapshot_id);
|
|
191
194
|
CREATE INDEX IF NOT EXISTS idx_resources_type_region ON resources(resource_type, region);
|
|
192
195
|
CREATE INDEX IF NOT EXISTS idx_resources_canonical_name_type ON resources(canonical_name, resource_type);
|
|
196
|
+
CREATE INDEX IF NOT EXISTS idx_resources_normalized_name_type ON resources(normalized_name, resource_type);
|
|
193
197
|
|
|
194
198
|
-- Tags indexes (for efficient tag queries)
|
|
195
199
|
CREATE INDEX IF NOT EXISTS idx_tags_resource ON resource_tags(resource_id);
|
|
@@ -266,6 +270,53 @@ MIGRATIONS = {
|
|
|
266
270
|
WHERE canonical_name IS NULL
|
|
267
271
|
""",
|
|
268
272
|
],
|
|
273
|
+
"1.2.0": [
|
|
274
|
+
# Add normalized_name column for pattern-stripped names
|
|
275
|
+
"ALTER TABLE resources ADD COLUMN normalized_name TEXT",
|
|
276
|
+
# Add extracted_patterns column for storing what was stripped (JSON)
|
|
277
|
+
"ALTER TABLE resources ADD COLUMN extracted_patterns TEXT",
|
|
278
|
+
# Add normalization_method column for tracking how normalization was done
|
|
279
|
+
"ALTER TABLE resources ADD COLUMN normalization_method TEXT",
|
|
280
|
+
# Backfill normalized_name from CloudFormation logical-id tag
|
|
281
|
+
"""
|
|
282
|
+
UPDATE resources
|
|
283
|
+
SET normalized_name = (
|
|
284
|
+
SELECT value FROM resource_tags
|
|
285
|
+
WHERE resource_tags.resource_id = resources.id
|
|
286
|
+
AND key = 'aws:cloudformation:logical-id'
|
|
287
|
+
),
|
|
288
|
+
normalization_method = 'tag:logical-id'
|
|
289
|
+
WHERE normalized_name IS NULL
|
|
290
|
+
AND EXISTS (
|
|
291
|
+
SELECT 1 FROM resource_tags
|
|
292
|
+
WHERE resource_tags.resource_id = resources.id
|
|
293
|
+
AND key = 'aws:cloudformation:logical-id'
|
|
294
|
+
)
|
|
295
|
+
""",
|
|
296
|
+
# Backfill from Name tag
|
|
297
|
+
"""
|
|
298
|
+
UPDATE resources
|
|
299
|
+
SET normalized_name = (
|
|
300
|
+
SELECT value FROM resource_tags
|
|
301
|
+
WHERE resource_tags.resource_id = resources.id
|
|
302
|
+
AND key = 'Name'
|
|
303
|
+
),
|
|
304
|
+
normalization_method = 'tag:Name'
|
|
305
|
+
WHERE normalized_name IS NULL
|
|
306
|
+
AND EXISTS (
|
|
307
|
+
SELECT 1 FROM resource_tags
|
|
308
|
+
WHERE resource_tags.resource_id = resources.id
|
|
309
|
+
AND key = 'Name'
|
|
310
|
+
)
|
|
311
|
+
""",
|
|
312
|
+
# Fallback to physical name (pattern extraction needs Python, done on re-snapshot)
|
|
313
|
+
"""
|
|
314
|
+
UPDATE resources
|
|
315
|
+
SET normalized_name = COALESCE(name, arn),
|
|
316
|
+
normalization_method = 'none'
|
|
317
|
+
WHERE normalized_name IS NULL
|
|
318
|
+
""",
|
|
319
|
+
],
|
|
269
320
|
}
|
|
270
321
|
|
|
271
322
|
|
src/storage/snapshot_store.py
CHANGED
|
@@ -6,6 +6,7 @@ from datetime import datetime, timezone
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any, Dict, List, Optional
|
|
8
8
|
|
|
9
|
+
from ..matching import ResourceNormalizer
|
|
9
10
|
from ..models.resource import Resource
|
|
10
11
|
from ..models.snapshot import Snapshot
|
|
11
12
|
from .database import Database, json_deserialize, json_serialize
|
|
@@ -54,6 +55,9 @@ class SnapshotStore:
|
|
|
54
55
|
Returns:
|
|
55
56
|
Database ID of saved snapshot
|
|
56
57
|
"""
|
|
58
|
+
# Create normalizer for computing normalized names
|
|
59
|
+
normalizer = ResourceNormalizer()
|
|
60
|
+
|
|
57
61
|
with self.db.transaction() as cursor:
|
|
58
62
|
# Insert snapshot
|
|
59
63
|
cursor.execute(
|
|
@@ -83,13 +87,23 @@ class SnapshotStore:
|
|
|
83
87
|
|
|
84
88
|
# Insert resources
|
|
85
89
|
for resource in snapshot.resources:
|
|
90
|
+
# Compute canonical name (for backward compatibility)
|
|
86
91
|
canonical = compute_canonical_name(resource.name, resource.tags, resource.arn)
|
|
92
|
+
|
|
93
|
+
# Compute normalized name with pattern extraction
|
|
94
|
+
norm_result = normalizer.normalize_single(
|
|
95
|
+
resource.name,
|
|
96
|
+
resource.resource_type,
|
|
97
|
+
resource.tags,
|
|
98
|
+
)
|
|
99
|
+
|
|
87
100
|
cursor.execute(
|
|
88
101
|
"""
|
|
89
102
|
INSERT INTO resources (
|
|
90
103
|
snapshot_id, arn, resource_type, name, region,
|
|
91
|
-
config_hash, raw_config, created_at, source, canonical_name
|
|
92
|
-
|
|
104
|
+
config_hash, raw_config, created_at, source, canonical_name,
|
|
105
|
+
normalized_name, extracted_patterns, normalization_method
|
|
106
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
93
107
|
""",
|
|
94
108
|
(
|
|
95
109
|
snapshot_id,
|
|
@@ -102,6 +116,9 @@ class SnapshotStore:
|
|
|
102
116
|
resource.created_at.isoformat() if resource.created_at else None,
|
|
103
117
|
resource.source,
|
|
104
118
|
canonical,
|
|
119
|
+
norm_result.normalized_name,
|
|
120
|
+
json_serialize(norm_result.extracted_patterns),
|
|
121
|
+
norm_result.method,
|
|
105
122
|
),
|
|
106
123
|
)
|
|
107
124
|
resource_id = cursor.lastrowid
|
|
@@ -122,6 +122,9 @@
|
|
|
122
122
|
{ field: 'resource_type', label: 'Type', visible: true, isBase: true, width: 160, frozen: false },
|
|
123
123
|
{ field: 'region', label: 'Region', visible: true, isBase: true, width: 130, frozen: false },
|
|
124
124
|
{ field: 'snapshot_name', label: 'Snapshot', visible: true, isBase: true, width: 180, frozen: false },
|
|
125
|
+
{ field: 'canonical_name', label: 'Canonical Name', visible: false, isBase: true, width: 200, frozen: false },
|
|
126
|
+
{ field: 'normalized_name', label: 'Normalized Name', visible: false, isBase: true, width: 200, frozen: false },
|
|
127
|
+
{ field: 'normalization_method', label: 'Normalization Method', visible: false, isBase: true, width: 150, frozen: false },
|
|
125
128
|
{ field: 'tags', label: 'All Tags', visible: false, isBase: true, width: 280, frozen: false },
|
|
126
129
|
{ field: 'created_at', label: 'Created', visible: false, isBase: true, width: 180, frozen: false },
|
|
127
130
|
{ field: 'config_hash', label: 'Config Hash', visible: false, isBase: true, width: 160, frozen: false }
|
|
File without changes
|
|
File without changes
|
{aws_inventory_manager-0.13.2.dist-info → aws_inventory_manager-0.16.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{aws_inventory_manager-0.13.2.dist-info → aws_inventory_manager-0.16.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|