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
|
@@ -5,7 +5,9 @@ src/aws/credentials.py,sha256=mxj8lQf7TRG5dznQJ37rGQqfB-viHh_5VCt1C4xgqG0,6374
|
|
|
5
5
|
src/aws/rate_limiter.py,sha256=b3dV4A9ftUsGgVMOQmv3IVO0Fkym2o1QDHKpFOdSYbg,5464
|
|
6
6
|
src/cli/__init__.py,sha256=zstPcm000JdpuUuMRuqL2TkC-bnCpm0syHjAyu8ELcY,387
|
|
7
7
|
src/cli/config.py,sha256=to_hVDxQh0WNANPDQKYlnZzCsFS6_bnrE745wZgRQe4,4286
|
|
8
|
-
src/cli/main.py,sha256=
|
|
8
|
+
src/cli/main.py,sha256=VUF4os0G-ACS4TE9GN61PyAjgKUVkl833_ulL13uFNY,155571
|
|
9
|
+
src/cloudtrail/__init__.py,sha256=lFrH6WLbeJQ5fCfVdyxQrta3Mr_RYQAM4N-IwyazdZI,179
|
|
10
|
+
src/cloudtrail/query.py,sha256=0uq3j9HQL7uHXyZlTB9CJqQ754fbEjwdpCipIhexMz0,13687
|
|
9
11
|
src/config_service/__init__.py,sha256=fqYfceBP_kPnp8PjYW9D_FqsWqrrOzsQTftBgp7-aTE,585
|
|
10
12
|
src/config_service/collector.py,sha256=uW6m1feceYCOgANGWvxhbw23etcaqSQpmdIu-DTsWuk,12254
|
|
11
13
|
src/config_service/detector.py,sha256=BoJC32DXN2f8x8U8RiOmjkGtnrTQrRuO568FHcMt_xU,8531
|
|
@@ -20,6 +22,10 @@ src/delta/differ.py,sha256=-7MESqWyO8srPhs9OlsLJjYx57qPwQRh93GsatqnoW8,6692
|
|
|
20
22
|
src/delta/formatters.py,sha256=xhzph_iLGLD8yyflYCMTFZqWPmW9tB7hZsVHsgQDrzM,9209
|
|
21
23
|
src/delta/models.py,sha256=_biiC136e-C2GljlIeW7ql5vC47Ss1M8a-jgw9K9rFg,5103
|
|
22
24
|
src/delta/reporter.py,sha256=yqzof9snkJyJiOeGoLAla2pmnFNIPlR3brg7xrstpNA,8775
|
|
25
|
+
src/matching/__init__.py,sha256=cidLzXlQo29bV0OXX7AC5A9OLcEStB0NHb61WeatDDo,239
|
|
26
|
+
src/matching/config.py,sha256=eUACdOEUMyEjFzEuKxx4TevcpTIyey555Oq11yV9_6U,1703
|
|
27
|
+
src/matching/normalizer.py,sha256=D_ZNU9U43byVtpLsTF7B6K6CayKjtQ4TKxkSpNzDwTs,15846
|
|
28
|
+
src/matching/prompts.py,sha256=2sqsWktMcygvgOLoHpMeNO1I7_JqprhmGlwzV9S-vz4,1678
|
|
23
29
|
src/models/__init__.py,sha256=5DFL_8ZgLE28EG8ORumMT3EfhFAxXrFa3yzQpQBSQxc,533
|
|
24
30
|
src/models/config_diff.py,sha256=elh4oUgoln_sDC6lmkqfSd8k1pX9oqBPqTEgTksw5f8,4237
|
|
25
31
|
src/models/cost_report.py,sha256=BzyWrMewHau7EpQyLm4WoesVY3WDPBOtGVdk_H8USuM,3079
|
|
@@ -57,7 +63,7 @@ src/security/checks/s3_checks.py,sha256=usHrGBmmIZc4PEDSMRRE1H8A_NwUxV2kiIVeLaPC
|
|
|
57
63
|
src/security/checks/secrets_checks.py,sha256=2bJY0Hqb1PwUYid5XZCC6y4hgWle3sRtwYNVh4jmUBw,3631
|
|
58
64
|
src/security/checks/sg_checks.py,sha256=BmipY31beybajxSbS_HoDkSXFYTQFhjXWNxuYYPEP2U,4731
|
|
59
65
|
src/snapshot/__init__.py,sha256=trHfRe_8jxjQVb48cDLVH3gs2Y7YQtsX2gFL4OB9qRc,237
|
|
60
|
-
src/snapshot/capturer.py,sha256=
|
|
66
|
+
src/snapshot/capturer.py,sha256=5Pg3vITpsvr1r0J-wesUb7KnMLsAbxttd0EKsnF3K3Q,18046
|
|
61
67
|
src/snapshot/filter.py,sha256=UPVTi23NHJYKFTD4ss8PazDHh-NIC2lWLvjJnr9Y7QY,9158
|
|
62
68
|
src/snapshot/inventory_storage.py,sha256=eOwGzYhUrvnv_usgEaUF-0_cDGGf21k-vjRRKP7Z5Hs,7739
|
|
63
69
|
src/snapshot/report_formatter.py,sha256=WR7Adqq5FjJqu3iIWAnOZ9yp6AuJH1kA6uKKpT7jrT4,8266
|
|
@@ -79,6 +85,7 @@ src/snapshot/resource_collectors/eks.py,sha256=T7Rh4ij3frxYgIfAcZNk0xNpoahwBfsQW
|
|
|
79
85
|
src/snapshot/resource_collectors/elasticache_collector.py,sha256=g7mVa9ZlE0DBITnA-i565REZRpumWMnqZuLVHcVO0Gw,2830
|
|
80
86
|
src/snapshot/resource_collectors/elb.py,sha256=af3IeKUv_FUfcLwYcnDajh4_4eSWtelvoazKCptehnQ,5125
|
|
81
87
|
src/snapshot/resource_collectors/eventbridge.py,sha256=abHxh0Iob6YrumdzmFugpRGWFqE0uVRVt89gajdq5q4,6062
|
|
88
|
+
src/snapshot/resource_collectors/glue.py,sha256=InEzfjcTDfVeKlM_XATEHKuOimle0QvaNBkbAYmPDto,7779
|
|
82
89
|
src/snapshot/resource_collectors/iam.py,sha256=N5QQO7E5opV-3CiOuoW_W0x-m_2alOjYs0-Q_loS-rQ,6732
|
|
83
90
|
src/snapshot/resource_collectors/kms.py,sha256=ioJUO2C7XawrGGnvpNA1pVT0GdHDI4pmbeqpACgf7Dc,4524
|
|
84
91
|
src/snapshot/resource_collectors/lambda_func.py,sha256=bjlWj_orHlU7vVT91c9ZcEAsXSPB7f4jgUWAMfa3C1w,5289
|
|
@@ -95,11 +102,11 @@ src/snapshot/resource_collectors/waf.py,sha256=HUNshQ_WHbIK3nAyNwhb21uHkMC7ywSOk
|
|
|
95
102
|
src/storage/__init__.py,sha256=4LrT-bm45zLK6VUyKsx3jyK_L270A48t-w2-0ceb9PM,554
|
|
96
103
|
src/storage/audit_store.py,sha256=zO7L9o-8N31PsiLJ72wvmodoDrFtFeya05Ebfes8a5Y,14123
|
|
97
104
|
src/storage/database.py,sha256=j-IJ4LiNs7PgXH0L3FtMlmbId1TaGjNCcYjgn3dt7sQ,9465
|
|
98
|
-
src/storage/group_store.py,sha256=
|
|
105
|
+
src/storage/group_store.py,sha256=RLQK1dBkSbXiFtEw9twpoUFgnWiJWo6MyIMhVQkYoU8,25828
|
|
99
106
|
src/storage/inventory_store.py,sha256=hRxdL2oqDTvtIuyNiR9IoBFjH1jOe3DV43C8Ybt-aTU,10601
|
|
100
|
-
src/storage/resource_store.py,sha256=
|
|
101
|
-
src/storage/schema.py,sha256=
|
|
102
|
-
src/storage/snapshot_store.py,sha256=
|
|
107
|
+
src/storage/resource_store.py,sha256=mfKiVQgok2z_PWi7cJx2VD_aMxL6ioI7TaPpEV5hS40,13220
|
|
108
|
+
src/storage/schema.py,sha256=JvHl5GrSPNyFntH7KDVMVG-_rebo3uxCtVoOUwxFKCc,12275
|
|
109
|
+
src/storage/snapshot_store.py,sha256=6BzGjsIljEetGQ88qXoGP5tsFxPNitT5ZVW2oRoCV_I,12589
|
|
103
110
|
src/utils/__init__.py,sha256=1qjjL3wxB7ye6s3AT4h5hQ9Xgi7y0WllUMmTpiZWC54,284
|
|
104
111
|
src/utils/export.py,sha256=lBUxLGkvtdzTFq2VKRmHgf18ZROiorHGGHlbeG7ixxc,8929
|
|
105
112
|
src/utils/hash.py,sha256=w6jJqNR64IEcgMIrIY-M1QXuo_CkH0sbT8I6JcFQJqo,1747
|
|
@@ -134,12 +141,12 @@ src/web/templates/pages/diff.html,sha256=UeRqKd6Op2XXNkNg916km7KICMPngh8FVxiaSQu
|
|
|
134
141
|
src/web/templates/pages/error.html,sha256=rCnXvdqhrXQX8RungpL35q6iAU77ntQTnC-B52zYkdo,1715
|
|
135
142
|
src/web/templates/pages/groups.html,sha256=N2Bj44Bx5hTiXap9b_dIPjan_CS_9pAqJeJYAs5o-z0,43176
|
|
136
143
|
src/web/templates/pages/queries.html,sha256=sf167CjkCQmx0jZQsEorgSO2N3zVyISdEIpxkhZVr7E,12443
|
|
137
|
-
src/web/templates/pages/resources.html,sha256=
|
|
144
|
+
src/web/templates/pages/resources.html,sha256=hGr2-NDUlk16RWMEkebEMjtAlEpwVvy59KujoZ3_jCk,124172
|
|
138
145
|
src/web/templates/pages/snapshot_detail.html,sha256=hHoM_smOhVnDhsqHRrjv_Bk3V2WsUcsjOhFEy6qqDDs,12457
|
|
139
146
|
src/web/templates/pages/snapshots.html,sha256=3b47IQgkN4mxJpqjUaJ_lPqYUqM2Icz44Ad8CesZpvM,22536
|
|
140
|
-
aws_inventory_manager-0.
|
|
141
|
-
aws_inventory_manager-0.
|
|
142
|
-
aws_inventory_manager-0.
|
|
143
|
-
aws_inventory_manager-0.
|
|
144
|
-
aws_inventory_manager-0.
|
|
145
|
-
aws_inventory_manager-0.
|
|
147
|
+
aws_inventory_manager-0.16.0.dist-info/LICENSE,sha256=-lY65BqqcGV9QVjIoTpYEB0Jaddm9j-cS5ICDRBRQo0,1071
|
|
148
|
+
aws_inventory_manager-0.16.0.dist-info/METADATA,sha256=AK-ihmCdQsRVdob6LbCZ9U0CmogO0wmvKChT1WMXWdM,42531
|
|
149
|
+
aws_inventory_manager-0.16.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
150
|
+
aws_inventory_manager-0.16.0.dist-info/entry_points.txt,sha256=Ktdhto-PER5BtwWKYBtU_-FS3ngiGtAGGeoLzRW2qUw,44
|
|
151
|
+
aws_inventory_manager-0.16.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
152
|
+
aws_inventory_manager-0.16.0.dist-info/RECORD,,
|
src/cli/main.py
CHANGED
|
@@ -823,8 +823,11 @@ def snapshot_create(
|
|
|
823
823
|
exclude_tags: Optional[str] = typer.Option(
|
|
824
824
|
None, "--exclude-tags", help="Exclude resources with ANY of these tags (Key=Value,Key2=Value2)"
|
|
825
825
|
),
|
|
826
|
+
created_by_role: Optional[str] = typer.Option(
|
|
827
|
+
None, "--created-by-role", help="Tag resources created by this IAM role with _created_by_role (queries CloudTrail, 90-day limit)"
|
|
828
|
+
),
|
|
826
829
|
use_config: bool = typer.Option(
|
|
827
|
-
|
|
830
|
+
False, "--config", help="Use AWS Config for collection when available (default: disabled, use direct API)"
|
|
828
831
|
),
|
|
829
832
|
config_aggregator: Optional[str] = typer.Option(
|
|
830
833
|
None, "--config-aggregator", help="AWS Config Aggregator name for multi-account collection"
|
|
@@ -835,7 +838,7 @@ def snapshot_create(
|
|
|
835
838
|
):
|
|
836
839
|
"""Create a new snapshot of AWS resources.
|
|
837
840
|
|
|
838
|
-
Captures resources from
|
|
841
|
+
Captures resources from 26 AWS services:
|
|
839
842
|
- IAM: Roles, Users, Groups, Policies
|
|
840
843
|
- Lambda: Functions, Layers
|
|
841
844
|
- S3: Buckets
|
|
@@ -860,6 +863,7 @@ def snapshot_create(
|
|
|
860
863
|
- CodePipeline: Pipelines
|
|
861
864
|
- CodeBuild: Projects
|
|
862
865
|
- Backup: Backup Plans, Backup Vaults
|
|
866
|
+
- Glue: Databases, Tables, Crawlers, Jobs, Connections
|
|
863
867
|
|
|
864
868
|
Historical Baselines & Filtering:
|
|
865
869
|
Use --before-date, --after-date, --include-tags, and/or --exclude-tags to create
|
|
@@ -1041,6 +1045,50 @@ def snapshot_create(
|
|
|
1041
1045
|
config_aggregator=config_aggregator,
|
|
1042
1046
|
)
|
|
1043
1047
|
|
|
1048
|
+
# Tag resources created by role if specified (uses CloudTrail)
|
|
1049
|
+
if created_by_role:
|
|
1050
|
+
from ..cloudtrail import CloudTrailQuery
|
|
1051
|
+
|
|
1052
|
+
console.print(f"\n🔍 Checking creator role: [bold]{created_by_role}[/bold]")
|
|
1053
|
+
console.print(" Querying CloudTrail (this may take a moment)...")
|
|
1054
|
+
|
|
1055
|
+
ct_query = CloudTrailQuery(profile_name=aws_profile, regions=region_list)
|
|
1056
|
+
created_resources = ct_query.get_created_resource_names(
|
|
1057
|
+
role_arn=created_by_role,
|
|
1058
|
+
days_back=90,
|
|
1059
|
+
regions=region_list,
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
# Build a set of (name, type) tuples for matching
|
|
1063
|
+
created_set = set()
|
|
1064
|
+
for resource_type, names in created_resources.items():
|
|
1065
|
+
for name in names:
|
|
1066
|
+
created_set.add((name, resource_type))
|
|
1067
|
+
|
|
1068
|
+
# Tag resources that were created by this role (add to tags dict)
|
|
1069
|
+
matched_count = 0
|
|
1070
|
+
for resource in snapshot.resources:
|
|
1071
|
+
is_match = False
|
|
1072
|
+
# Match by name and type
|
|
1073
|
+
if (resource.name, resource.resource_type) in created_set:
|
|
1074
|
+
is_match = True
|
|
1075
|
+
# Also try matching by ARN components
|
|
1076
|
+
elif resource.arn:
|
|
1077
|
+
arn_name = resource.arn.split("/")[-1].split(":")[-1]
|
|
1078
|
+
if (arn_name, resource.resource_type) in created_set:
|
|
1079
|
+
is_match = True
|
|
1080
|
+
|
|
1081
|
+
if is_match:
|
|
1082
|
+
matched_count += 1
|
|
1083
|
+
# Add created-by tag to resource (internal tracking, not AWS tag)
|
|
1084
|
+
if resource.tags is None:
|
|
1085
|
+
resource.tags = {}
|
|
1086
|
+
resource.tags["_created_by_role"] = created_by_role
|
|
1087
|
+
|
|
1088
|
+
console.print(f" Found {len(created_resources)} resource types in CloudTrail")
|
|
1089
|
+
console.print(f" Tagged {matched_count}/{len(snapshot.resources)} resources as created by this role")
|
|
1090
|
+
console.print(f" [dim](Resources older than 90 days won't appear in CloudTrail)[/dim]")
|
|
1091
|
+
|
|
1044
1092
|
# T018: Check for zero resources after filtering
|
|
1045
1093
|
if snapshot.resource_count == 0:
|
|
1046
1094
|
console.print("⚠️ Warning: Snapshot contains 0 resources after filtering", style="bold yellow")
|
|
@@ -1147,7 +1195,7 @@ def snapshot_create(
|
|
|
1147
1195
|
console.print("\n [dim]Use --verbose to see detailed breakdown by resource type[/dim]")
|
|
1148
1196
|
elif not use_config:
|
|
1149
1197
|
console.print("\nCollection Method:")
|
|
1150
|
-
console.print(" All resources collected via Direct API (--
|
|
1198
|
+
console.print(" All resources collected via Direct API (use --config to enable AWS Config)")
|
|
1151
1199
|
|
|
1152
1200
|
except typer.Exit:
|
|
1153
1201
|
# Re-raise Exit exceptions (normal exit codes)
|
|
@@ -3617,6 +3665,157 @@ def serve(
|
|
|
3617
3665
|
)
|
|
3618
3666
|
|
|
3619
3667
|
|
|
3668
|
+
# =============================================================================
|
|
3669
|
+
# Normalize Command (AI Normalization)
|
|
3670
|
+
# =============================================================================
|
|
3671
|
+
|
|
3672
|
+
|
|
3673
|
+
@app.command()
|
|
3674
|
+
def normalize(
|
|
3675
|
+
snapshot: str = typer.Option(..., "--snapshot", "-s", help="Snapshot name to normalize"),
|
|
3676
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Preview normalizations without saving"),
|
|
3677
|
+
use_ai: bool = typer.Option(True, "--ai/--no-ai", help="Use AI for ambiguous names (default: enabled)"),
|
|
3678
|
+
):
|
|
3679
|
+
"""Re-run AI normalization on an existing snapshot.
|
|
3680
|
+
|
|
3681
|
+
This command updates the normalized_name column for all resources
|
|
3682
|
+
in the specified snapshot using AI-based name normalization.
|
|
3683
|
+
|
|
3684
|
+
Use this to:
|
|
3685
|
+
- Backfill normalized names for snapshots created before AI normalization
|
|
3686
|
+
- Re-normalize with updated AI models or prompts
|
|
3687
|
+
- Preview normalizations with --dry-run before committing
|
|
3688
|
+
|
|
3689
|
+
Example:
|
|
3690
|
+
awsinv normalize --snapshot my-snapshot-20260113
|
|
3691
|
+
awsinv normalize --snapshot my-snapshot --dry-run
|
|
3692
|
+
awsinv normalize --snapshot my-snapshot --no-ai
|
|
3693
|
+
"""
|
|
3694
|
+
global config
|
|
3695
|
+
if config is None:
|
|
3696
|
+
config = Config.load()
|
|
3697
|
+
|
|
3698
|
+
from ..storage import Database, SnapshotStore
|
|
3699
|
+
|
|
3700
|
+
try:
|
|
3701
|
+
from ..matching import NormalizerConfig, ResourceNormalizer
|
|
3702
|
+
except ImportError:
|
|
3703
|
+
console.print(
|
|
3704
|
+
"[red]AI dependencies not installed.[/red]\n"
|
|
3705
|
+
"Install with: [cyan]pip install aws-inventory-manager[ai][/cyan]"
|
|
3706
|
+
)
|
|
3707
|
+
raise typer.Exit(code=1)
|
|
3708
|
+
|
|
3709
|
+
# Initialize database
|
|
3710
|
+
db = Database(config.storage_path)
|
|
3711
|
+
snapshot_store = SnapshotStore(db)
|
|
3712
|
+
|
|
3713
|
+
# Check snapshot exists
|
|
3714
|
+
if not snapshot_store.exists(snapshot):
|
|
3715
|
+
console.print(f"[red]✗ Snapshot '{snapshot}' not found[/red]")
|
|
3716
|
+
raise typer.Exit(code=1)
|
|
3717
|
+
|
|
3718
|
+
# Load the snapshot
|
|
3719
|
+
console.print(f"[cyan]Loading snapshot '[bold]{snapshot}[/bold]'...[/cyan]")
|
|
3720
|
+
snapshot_obj = snapshot_store.load(snapshot)
|
|
3721
|
+
|
|
3722
|
+
if not snapshot_obj or not snapshot_obj.resources:
|
|
3723
|
+
console.print(f"[yellow]⚠ Snapshot '{snapshot}' has no resources to normalize[/yellow]")
|
|
3724
|
+
raise typer.Exit(code=0)
|
|
3725
|
+
|
|
3726
|
+
console.print(f" Found [bold]{len(snapshot_obj.resources)}[/bold] resources")
|
|
3727
|
+
|
|
3728
|
+
# Initialize normalizer
|
|
3729
|
+
normalizer_config = NormalizerConfig.from_env()
|
|
3730
|
+
|
|
3731
|
+
if use_ai and not normalizer_config.is_ai_enabled:
|
|
3732
|
+
console.print("[yellow]⚠ OPENAI_API_KEY not set - using rules-based normalization only[/yellow]")
|
|
3733
|
+
use_ai = False
|
|
3734
|
+
|
|
3735
|
+
normalizer = ResourceNormalizer(normalizer_config)
|
|
3736
|
+
|
|
3737
|
+
# Prepare resources for normalization
|
|
3738
|
+
resource_dicts = [
|
|
3739
|
+
{
|
|
3740
|
+
"arn": r.arn,
|
|
3741
|
+
"name": r.name,
|
|
3742
|
+
"resource_type": r.resource_type,
|
|
3743
|
+
"tags": r.tags,
|
|
3744
|
+
}
|
|
3745
|
+
for r in snapshot_obj.resources
|
|
3746
|
+
]
|
|
3747
|
+
|
|
3748
|
+
# Run normalization
|
|
3749
|
+
if use_ai:
|
|
3750
|
+
console.print("[cyan]Running AI-assisted normalization...[/cyan]")
|
|
3751
|
+
else:
|
|
3752
|
+
console.print("[cyan]Running rules-based normalization...[/cyan]")
|
|
3753
|
+
|
|
3754
|
+
with console.status("[bold green]Normalizing resources..."):
|
|
3755
|
+
normalized_names = normalizer.normalize_resources(resource_dicts, use_ai=use_ai)
|
|
3756
|
+
|
|
3757
|
+
console.print(f" Normalized [bold]{len(normalized_names)}[/bold] resource names")
|
|
3758
|
+
|
|
3759
|
+
if normalizer.tokens_used > 0:
|
|
3760
|
+
console.print(f" AI tokens used: [dim]{normalizer.tokens_used}[/dim]")
|
|
3761
|
+
|
|
3762
|
+
# Show preview in dry-run mode
|
|
3763
|
+
if dry_run:
|
|
3764
|
+
console.print("\n[yellow]DRY RUN - No changes saved[/yellow]\n")
|
|
3765
|
+
|
|
3766
|
+
# Build a table of changes
|
|
3767
|
+
table = Table(title="Normalization Preview (first 20)")
|
|
3768
|
+
table.add_column("Resource Type", style="cyan")
|
|
3769
|
+
table.add_column("Original Name", style="white")
|
|
3770
|
+
table.add_column("Normalized Name", style="green")
|
|
3771
|
+
|
|
3772
|
+
count = 0
|
|
3773
|
+
for r in snapshot_obj.resources:
|
|
3774
|
+
if count >= 20:
|
|
3775
|
+
break
|
|
3776
|
+
norm_name = normalized_names.get(r.arn, r.name)
|
|
3777
|
+
# Only show if different or meaningful
|
|
3778
|
+
table.add_row(
|
|
3779
|
+
r.resource_type.split("::")[-1] if r.resource_type else "Unknown",
|
|
3780
|
+
r.name or "(no name)",
|
|
3781
|
+
norm_name,
|
|
3782
|
+
)
|
|
3783
|
+
count += 1
|
|
3784
|
+
|
|
3785
|
+
console.print(table)
|
|
3786
|
+
|
|
3787
|
+
if len(snapshot_obj.resources) > 20:
|
|
3788
|
+
console.print(f"\n[dim]... and {len(snapshot_obj.resources) - 20} more resources[/dim]")
|
|
3789
|
+
|
|
3790
|
+
console.print("\n[yellow]Run without --dry-run to save changes[/yellow]")
|
|
3791
|
+
raise typer.Exit(code=0)
|
|
3792
|
+
|
|
3793
|
+
# Update database with normalized names
|
|
3794
|
+
console.print("[cyan]Updating database...[/cyan]")
|
|
3795
|
+
|
|
3796
|
+
snapshot_id = snapshot_store.get_id(snapshot)
|
|
3797
|
+
if snapshot_id is None:
|
|
3798
|
+
console.print("[red]✗ Failed to get snapshot ID[/red]")
|
|
3799
|
+
raise typer.Exit(code=1)
|
|
3800
|
+
|
|
3801
|
+
updated_count = 0
|
|
3802
|
+
with db.transaction() as cursor:
|
|
3803
|
+
for r in snapshot_obj.resources:
|
|
3804
|
+
norm_name = normalized_names.get(r.arn)
|
|
3805
|
+
if norm_name:
|
|
3806
|
+
cursor.execute(
|
|
3807
|
+
"""
|
|
3808
|
+
UPDATE resources
|
|
3809
|
+
SET normalized_name = ?
|
|
3810
|
+
WHERE snapshot_id = ? AND arn = ?
|
|
3811
|
+
""",
|
|
3812
|
+
(norm_name, snapshot_id, r.arn),
|
|
3813
|
+
)
|
|
3814
|
+
updated_count += cursor.rowcount
|
|
3815
|
+
|
|
3816
|
+
console.print(f"[green]✓ Updated {updated_count} resources with normalized names[/green]")
|
|
3817
|
+
|
|
3818
|
+
|
|
3620
3819
|
def cli_main():
|
|
3621
3820
|
"""Entry point for console script."""
|
|
3622
3821
|
app()
|