aws-inventory-manager 0.2.0__py3-none-any.whl → 0.3.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.2.0.dist-info → aws_inventory_manager-0.3.0.dist-info}/METADATA +31 -5
- {aws_inventory_manager-0.2.0.dist-info → aws_inventory_manager-0.3.0.dist-info}/RECORD +12 -8
- src/cli/main.py +273 -0
- src/models/report.py +276 -0
- src/snapshot/report_formatter.py +250 -0
- src/snapshot/reporter.py +189 -0
- src/utils/export.py +219 -1
- src/utils/pagination.py +41 -0
- {aws_inventory_manager-0.2.0.dist-info → aws_inventory_manager-0.3.0.dist-info}/WHEEL +0 -0
- {aws_inventory_manager-0.2.0.dist-info → aws_inventory_manager-0.3.0.dist-info}/entry_points.txt +0 -0
- {aws_inventory_manager-0.2.0.dist-info → aws_inventory_manager-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {aws_inventory_manager-0.2.0.dist-info → aws_inventory_manager-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-inventory-manager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: AWS Resource Inventory Management & Delta Tracking CLI tool
|
|
5
5
|
Author-email: Troy Larson <troy@calvinware.com>
|
|
6
6
|
License: MIT
|
|
@@ -63,6 +63,7 @@ A Python CLI tool that captures point-in-time snapshots of AWS resources organiz
|
|
|
63
63
|
|
|
64
64
|
- **📦 Inventory Management**: Organize snapshots into named inventories with optional tag-based filters
|
|
65
65
|
- **📸 Resource Snapshots**: Capture complete inventory of AWS resources across multiple regions
|
|
66
|
+
- **📋 Snapshot Reporting**: Generate comprehensive reports with filtering, detailed views, and export to JSON/CSV/TXT
|
|
66
67
|
- **🔄 Delta Tracking**: Identify resources added, modified, or removed since a snapshot
|
|
67
68
|
- **💰 Cost Analysis**: Analyze costs for resources within a specific inventory
|
|
68
69
|
- **🔧 Resource Restoration**: Remove resources added since a snapshot to return to that state
|
|
@@ -109,13 +110,28 @@ This captures all resources in `us-east-1` and stores them in the `prod-baseline
|
|
|
109
110
|
- Deploy new resources, update configurations, etc.
|
|
110
111
|
- Then take another snapshot to track what changed
|
|
111
112
|
|
|
112
|
-
**4.
|
|
113
|
+
**4. View snapshot report** (see what's in your snapshot)
|
|
114
|
+
```bash
|
|
115
|
+
# Summary view with resource counts by service, region, and type
|
|
116
|
+
awsinv snapshot report --inventory prod-baseline
|
|
117
|
+
|
|
118
|
+
# Detailed view showing all resources with tags and metadata
|
|
119
|
+
awsinv snapshot report --inventory prod-baseline --detailed
|
|
120
|
+
|
|
121
|
+
# Filter by resource type
|
|
122
|
+
awsinv snapshot report --inventory prod-baseline --resource-type ec2
|
|
123
|
+
|
|
124
|
+
# Export to JSON, CSV, or TXT
|
|
125
|
+
awsinv snapshot report --inventory prod-baseline --export report.json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**5. Compare snapshots** (see what changed)
|
|
113
129
|
```bash
|
|
114
130
|
awsinv delta --snapshot initial --inventory prod-baseline
|
|
115
131
|
```
|
|
116
132
|
This shows all resources added, removed, or modified since the `initial` snapshot.
|
|
117
133
|
|
|
118
|
-
**
|
|
134
|
+
**6. Analyze costs**
|
|
119
135
|
```bash
|
|
120
136
|
# Costs since snapshot was created
|
|
121
137
|
awsinv cost --snapshot initial --inventory prod-baseline
|
|
@@ -125,7 +141,7 @@ awsinv cost --snapshot initial --inventory prod-baseline \
|
|
|
125
141
|
--start-date 2025-01-01 --end-date 2025-01-31
|
|
126
142
|
```
|
|
127
143
|
|
|
128
|
-
**
|
|
144
|
+
**7. List your resources**
|
|
129
145
|
```bash
|
|
130
146
|
# List all inventories
|
|
131
147
|
awsinv inventory list
|
|
@@ -456,6 +472,16 @@ awsinv snapshot create [name] \
|
|
|
456
472
|
[--compress] \
|
|
457
473
|
[--profile <aws-profile>]
|
|
458
474
|
|
|
475
|
+
# Generate snapshot report
|
|
476
|
+
awsinv snapshot report [snapshot-name] \
|
|
477
|
+
[--inventory <inventory-name>] \
|
|
478
|
+
[--resource-type <type>] \
|
|
479
|
+
[--region <region>] \
|
|
480
|
+
[--detailed] \
|
|
481
|
+
[--page-size <number>] \
|
|
482
|
+
[--export <file.json|file.csv|file.txt>] \
|
|
483
|
+
[--profile <aws-profile>]
|
|
484
|
+
|
|
459
485
|
# List all snapshots
|
|
460
486
|
awsinv snapshot list [--profile <aws-profile>]
|
|
461
487
|
|
|
@@ -503,6 +529,6 @@ MIT License - see LICENSE file for details
|
|
|
503
529
|
|
|
504
530
|
---
|
|
505
531
|
|
|
506
|
-
**Version**: 0.
|
|
532
|
+
**Version**: 0.3.0
|
|
507
533
|
**Status**: Alpha
|
|
508
534
|
**Python**: 3.8 - 3.13
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
aws_inventory_manager-0.
|
|
1
|
+
aws_inventory_manager-0.3.0.dist-info/licenses/LICENSE,sha256=-lY65BqqcGV9QVjIoTpYEB0Jaddm9j-cS5ICDRBRQo0,1071
|
|
2
2
|
src/__init__.py,sha256=XRWrw_3Q5Lb1Kxh8DnBcuiOeNWsIamXW6pUN6tKiXyA,74
|
|
3
3
|
src/aws/__init__.py,sha256=FcFY2cn8ZQCnActrZYH1hD0x4uFkM9bMBDOmuHA3_mw,253
|
|
4
4
|
src/aws/client.py,sha256=6HKHoOwAjlxfT2gJoSAZnzLCv-hIlLxXtGHqW8qu4xo,4082
|
|
@@ -6,7 +6,7 @@ src/aws/credentials.py,sha256=mxj8lQf7TRG5dznQJ37rGQqfB-viHh_5VCt1C4xgqG0,6374
|
|
|
6
6
|
src/aws/rate_limiter.py,sha256=b3dV4A9ftUsGgVMOQmv3IVO0Fkym2o1QDHKpFOdSYbg,5464
|
|
7
7
|
src/cli/__init__.py,sha256=aqMdryo16KNn_92UfbL8Gn6C1kUJK2McWIt9PY7BrHQ,113
|
|
8
8
|
src/cli/config.py,sha256=FMLDg5OrmFzBJR_jO5Qg5ery-E9oXt-VPbs_5M0KtVA,4280
|
|
9
|
-
src/cli/main.py,sha256=
|
|
9
|
+
src/cli/main.py,sha256=VJU79I-tN4iJ-jrfGykAijlv96cHWAYAI-0qT0Owcu0,71349
|
|
10
10
|
src/cost/__init__.py,sha256=WpTwsWAm8wyCkB65gsCnzu89-r5G_hgrbL9zTqvQ8j8,122
|
|
11
11
|
src/cost/analyzer.py,sha256=02WFkZErD-zfu1Jp8UDym3wTjuB0c3l5jadZmbGbBJk,10201
|
|
12
12
|
src/cost/explorer.py,sha256=c4Yj7w5gmvoeD9iY7hseinj3Ij-g9Rh8YXWYZVkO6u4,6847
|
|
@@ -18,12 +18,15 @@ src/models/__init__.py,sha256=GPMBPxpV0-GAEZdCYbPn7p7t_ICSFfzDuu-aYSrQXs8,395
|
|
|
18
18
|
src/models/cost_report.py,sha256=BzyWrMewHau7EpQyLm4WoesVY3WDPBOtGVdk_H8USuM,3079
|
|
19
19
|
src/models/delta_report.py,sha256=IUY1ulWvwPzLyVcj3NKFrkHoiTQTilHsiMU8Oe4OyOk,4500
|
|
20
20
|
src/models/inventory.py,sha256=x5rm9pjHryjsMvjNWY-I9MDNC7hSzs79zYriO9-K7dM,4840
|
|
21
|
+
src/models/report.py,sha256=Ubd2OdBavCRA4LN8aJiLrjhDzPCcx4-US4cTkZII5SM,8853
|
|
21
22
|
src/models/resource.py,sha256=6B7MQL_0mW2PKx0YvnAGjp0549vP8R61U9QZ-P0Zbj8,3192
|
|
22
23
|
src/models/snapshot.py,sha256=JLZzS4toRDyEvFWefHZFhBbZBY4_eyOrlkSkqWcKPLY,4204
|
|
23
24
|
src/snapshot/__init__.py,sha256=trHfRe_8jxjQVb48cDLVH3gs2Y7YQtsX2gFL4OB9qRc,237
|
|
24
25
|
src/snapshot/capturer.py,sha256=YVeUHLSufuoZD49vTERPQGR95IZAZb0yqFZ6IGI1Urs,13059
|
|
25
26
|
src/snapshot/filter.py,sha256=WjR96C5mpyjWvxyDcD9gniIYRgTCDRQTJZR0PedHQtU,8503
|
|
26
27
|
src/snapshot/inventory_storage.py,sha256=Ocq9GfMb8e-E80rewmK1LPwYNfNxbO-tPg4p1LFnerE,9079
|
|
28
|
+
src/snapshot/report_formatter.py,sha256=WR7Adqq5FjJqu3iIWAnOZ9yp6AuJH1kA6uKKpT7jrT4,8266
|
|
29
|
+
src/snapshot/reporter.py,sha256=CZp5fj6RcHoD5a3-1unwq5KKIxrQA38rLWCjspnyvvI,6628
|
|
27
30
|
src/snapshot/storage.py,sha256=MQZN2W4aBFG69NU-gwGw8LNvae0z6UMJPeL02qq-dYM,8466
|
|
28
31
|
src/snapshot/resource_collectors/__init__.py,sha256=QSJiStvbmqd_uCI6c4T03-3PMb0UmxAROZwVHPk25gc,94
|
|
29
32
|
src/snapshot/resource_collectors/apigateway.py,sha256=X916NdfRzUMNmMuyJdB_BrgTu1p7P7NwslPxWcPW4CU,4748
|
|
@@ -53,13 +56,14 @@ src/snapshot/resource_collectors/stepfunctions.py,sha256=I8TYniEzSX2jKZbSX6WI02e
|
|
|
53
56
|
src/snapshot/resource_collectors/vpcendpoints.py,sha256=tclcLPRxFmE5vwQQPwFQv2nbOc-4qVFnUaSJ4oDMU7w,2994
|
|
54
57
|
src/snapshot/resource_collectors/waf.py,sha256=HUNshQ_WHbIK3nAyNwhb21uHkMC7ywSOke28b-5ECRg,6461
|
|
55
58
|
src/utils/__init__.py,sha256=1qjjL3wxB7ye6s3AT4h5hQ9Xgi7y0WllUMmTpiZWC54,284
|
|
56
|
-
src/utils/export.py,sha256=
|
|
59
|
+
src/utils/export.py,sha256=lBUxLGkvtdzTFq2VKRmHgf18ZROiorHGGHlbeG7ixxc,8929
|
|
57
60
|
src/utils/hash.py,sha256=w6jJqNR64IEcgMIrIY-M1QXuo_CkH0sbT8I6JcFQJqo,1747
|
|
58
61
|
src/utils/logging.py,sha256=D39u9xrDSr_HlqedKa2yz-vmbCKulNAkJNVfG571UXY,2558
|
|
62
|
+
src/utils/pagination.py,sha256=BoMk2lP65-tGP1U6aUAuJPU7M-JroSvyfpScLKcGYxg,1153
|
|
59
63
|
src/utils/paths.py,sha256=EgO41-XE1KOmF0drY2YlJkzNQIb3WPsrGtPvsc0hVqY,1607
|
|
60
64
|
src/utils/progress.py,sha256=H8_6AtVsw-_-P40E57ECofkFw0v1Lq6uEVSofuxCXxY,936
|
|
61
|
-
aws_inventory_manager-0.
|
|
62
|
-
aws_inventory_manager-0.
|
|
63
|
-
aws_inventory_manager-0.
|
|
64
|
-
aws_inventory_manager-0.
|
|
65
|
-
aws_inventory_manager-0.
|
|
65
|
+
aws_inventory_manager-0.3.0.dist-info/METADATA,sha256=3vuy5TJ4vheil4LsQtsmJQEQK7GIhNM2iNGIiB7Mxl8,15326
|
|
66
|
+
aws_inventory_manager-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
67
|
+
aws_inventory_manager-0.3.0.dist-info/entry_points.txt,sha256=Ktdhto-PER5BtwWKYBtU_-FS3ngiGtAGGeoLzRW2qUw,44
|
|
68
|
+
aws_inventory_manager-0.3.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
69
|
+
aws_inventory_manager-0.3.0.dist-info/RECORD,,
|
src/cli/main.py
CHANGED
|
@@ -1127,6 +1127,279 @@ def snapshot_delete(
|
|
|
1127
1127
|
raise typer.Exit(code=1)
|
|
1128
1128
|
|
|
1129
1129
|
|
|
1130
|
+
@snapshot_app.command("report")
|
|
1131
|
+
def snapshot_report(
|
|
1132
|
+
snapshot_name: Optional[str] = typer.Argument(None, help="Snapshot name (default: active snapshot)"),
|
|
1133
|
+
inventory: Optional[str] = typer.Option(None, "--inventory", help="Inventory name (required if multiple exist)"),
|
|
1134
|
+
profile: Optional[str] = typer.Option(None, "--profile", help="AWS profile name"),
|
|
1135
|
+
storage_path: Optional[str] = typer.Option(None, "--storage-path", help="Override storage location"),
|
|
1136
|
+
resource_type: Optional[list[str]] = typer.Option(
|
|
1137
|
+
None, "--resource-type", help="Filter by resource type (can specify multiple)"
|
|
1138
|
+
),
|
|
1139
|
+
region: Optional[list[str]] = typer.Option(None, "--region", help="Filter by region (can specify multiple)"),
|
|
1140
|
+
detailed: bool = typer.Option(
|
|
1141
|
+
False, "--detailed", help="Show detailed resource information (ARN, tags, creation date)"
|
|
1142
|
+
),
|
|
1143
|
+
page_size: int = typer.Option(100, "--page-size", help="Resources per page in detailed view (default: 100)"),
|
|
1144
|
+
export: Optional[str] = typer.Option(
|
|
1145
|
+
None, "--export", help="Export report to file (format detected from extension: .json, .csv, .txt)"
|
|
1146
|
+
),
|
|
1147
|
+
):
|
|
1148
|
+
"""Display resource summary report for a snapshot.
|
|
1149
|
+
|
|
1150
|
+
Shows aggregated resource counts by service, region, and type with
|
|
1151
|
+
visual progress bars and formatted output. Can export to JSON, CSV, or TXT formats.
|
|
1152
|
+
|
|
1153
|
+
Snapshot Selection (in order of precedence):
|
|
1154
|
+
1. Explicit snapshot name argument
|
|
1155
|
+
2. Most recent snapshot from specified --inventory
|
|
1156
|
+
3. Active snapshot (set via 'awsinv snapshot set-active')
|
|
1157
|
+
|
|
1158
|
+
Examples:
|
|
1159
|
+
awsinv snapshot report # Report on active snapshot
|
|
1160
|
+
awsinv snapshot report baseline-2025-01 # Report on specific snapshot
|
|
1161
|
+
awsinv snapshot report --inventory prod # Most recent snapshot from 'prod' inventory
|
|
1162
|
+
awsinv snapshot report --resource-type ec2 # Filter by resource type
|
|
1163
|
+
awsinv snapshot report --region us-east-1 # Filter by region
|
|
1164
|
+
awsinv snapshot report --resource-type ec2 --resource-type lambda # Multiple filters
|
|
1165
|
+
awsinv snapshot report --export report.json # Export full report to JSON
|
|
1166
|
+
awsinv snapshot report --export resources.csv # Export resources to CSV
|
|
1167
|
+
awsinv snapshot report --export summary.txt # Export summary to TXT
|
|
1168
|
+
awsinv snapshot report --detailed --export details.json # Export detailed view
|
|
1169
|
+
"""
|
|
1170
|
+
from ..models.report import FilterCriteria
|
|
1171
|
+
from ..snapshot.report_formatter import ReportFormatter
|
|
1172
|
+
from ..snapshot.reporter import SnapshotReporter
|
|
1173
|
+
from ..utils.export import detect_format, export_report_csv, export_report_json, export_report_txt
|
|
1174
|
+
|
|
1175
|
+
try:
|
|
1176
|
+
# Use provided storage path or default from config
|
|
1177
|
+
storage = SnapshotStorage(storage_path or config.storage_path)
|
|
1178
|
+
|
|
1179
|
+
# Determine which snapshot to load
|
|
1180
|
+
target_snapshot_name: str
|
|
1181
|
+
if snapshot_name:
|
|
1182
|
+
# Explicit snapshot name provided
|
|
1183
|
+
target_snapshot_name = snapshot_name
|
|
1184
|
+
elif inventory:
|
|
1185
|
+
# Inventory specified - find most recent snapshot from that inventory
|
|
1186
|
+
from datetime import datetime as dt
|
|
1187
|
+
from typing import TypedDict
|
|
1188
|
+
|
|
1189
|
+
class InventorySnapshot(TypedDict):
|
|
1190
|
+
name: str
|
|
1191
|
+
created_at: dt
|
|
1192
|
+
|
|
1193
|
+
all_snapshots = storage.list_snapshots()
|
|
1194
|
+
inventory_snapshots: list[InventorySnapshot] = []
|
|
1195
|
+
|
|
1196
|
+
for snap_meta in all_snapshots:
|
|
1197
|
+
try:
|
|
1198
|
+
snap = storage.load_snapshot(snap_meta["name"])
|
|
1199
|
+
if snap.inventory_name == inventory:
|
|
1200
|
+
inventory_snapshots.append(
|
|
1201
|
+
InventorySnapshot(
|
|
1202
|
+
name=snap.name,
|
|
1203
|
+
created_at=snap.created_at,
|
|
1204
|
+
)
|
|
1205
|
+
)
|
|
1206
|
+
except Exception:
|
|
1207
|
+
continue
|
|
1208
|
+
|
|
1209
|
+
if not inventory_snapshots:
|
|
1210
|
+
console.print(f"✗ No snapshots found for inventory '{inventory}'", style="bold red")
|
|
1211
|
+
console.print("\nCreate a snapshot first:")
|
|
1212
|
+
console.print(f" awsinv snapshot create --inventory {inventory}")
|
|
1213
|
+
raise typer.Exit(code=1)
|
|
1214
|
+
|
|
1215
|
+
# Sort by created_at and pick most recent
|
|
1216
|
+
inventory_snapshots.sort(key=lambda x: x["created_at"], reverse=True)
|
|
1217
|
+
target_snapshot_name = inventory_snapshots[0]["name"]
|
|
1218
|
+
console.print(
|
|
1219
|
+
f"ℹ Using most recent snapshot from inventory '{inventory}': {target_snapshot_name}", style="dim"
|
|
1220
|
+
)
|
|
1221
|
+
else:
|
|
1222
|
+
# Try to get active snapshot
|
|
1223
|
+
active_name = storage.get_active_snapshot_name()
|
|
1224
|
+
if not active_name:
|
|
1225
|
+
console.print("✗ No active snapshot found", style="bold red")
|
|
1226
|
+
console.print("\nSet an active snapshot with:")
|
|
1227
|
+
console.print(" awsinv snapshot set-active <name>")
|
|
1228
|
+
console.print("\nOr specify a snapshot explicitly:")
|
|
1229
|
+
console.print(" awsinv snapshot report <snapshot-name>")
|
|
1230
|
+
console.print("\nOr specify an inventory to use the most recent snapshot:")
|
|
1231
|
+
console.print(" awsinv snapshot report --inventory <inventory-name>")
|
|
1232
|
+
raise typer.Exit(code=1)
|
|
1233
|
+
target_snapshot_name = active_name
|
|
1234
|
+
|
|
1235
|
+
# Load the snapshot
|
|
1236
|
+
try:
|
|
1237
|
+
snapshot = storage.load_snapshot(target_snapshot_name)
|
|
1238
|
+
except FileNotFoundError:
|
|
1239
|
+
console.print(f"✗ Snapshot '{target_snapshot_name}' not found", style="bold red")
|
|
1240
|
+
|
|
1241
|
+
# Show available snapshots
|
|
1242
|
+
try:
|
|
1243
|
+
all_snapshots = storage.list_snapshots()
|
|
1244
|
+
if all_snapshots:
|
|
1245
|
+
console.print("\nAvailable snapshots:")
|
|
1246
|
+
for snap_name in all_snapshots[:5]:
|
|
1247
|
+
console.print(f" • {snap_name}")
|
|
1248
|
+
if len(all_snapshots) > 5:
|
|
1249
|
+
console.print(f" ... and {len(all_snapshots) - 5} more")
|
|
1250
|
+
console.print("\nRun 'awsinv snapshot list' to see all snapshots.")
|
|
1251
|
+
except Exception:
|
|
1252
|
+
pass
|
|
1253
|
+
|
|
1254
|
+
raise typer.Exit(code=1)
|
|
1255
|
+
|
|
1256
|
+
# Handle empty snapshot
|
|
1257
|
+
if snapshot.resource_count == 0:
|
|
1258
|
+
console.print(f"⚠️ Warning: Snapshot '{snapshot.name}' contains 0 resources", style="yellow")
|
|
1259
|
+
console.print("\nNo report to generate.")
|
|
1260
|
+
raise typer.Exit(code=0)
|
|
1261
|
+
|
|
1262
|
+
# Create filter criteria if filters provided
|
|
1263
|
+
has_filters = bool(resource_type or region)
|
|
1264
|
+
criteria = None
|
|
1265
|
+
if has_filters:
|
|
1266
|
+
criteria = FilterCriteria(
|
|
1267
|
+
resource_types=resource_type if resource_type else None,
|
|
1268
|
+
regions=region if region else None,
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1271
|
+
# Generate report
|
|
1272
|
+
reporter = SnapshotReporter(snapshot)
|
|
1273
|
+
metadata = reporter._extract_metadata()
|
|
1274
|
+
|
|
1275
|
+
# Detailed view vs Summary view
|
|
1276
|
+
if detailed:
|
|
1277
|
+
# Get detailed resources (with optional filtering)
|
|
1278
|
+
detailed_resources = list(reporter.get_detailed_resources(criteria))
|
|
1279
|
+
|
|
1280
|
+
# Export mode
|
|
1281
|
+
if export:
|
|
1282
|
+
try:
|
|
1283
|
+
# Detect format from file extension
|
|
1284
|
+
export_format = detect_format(export)
|
|
1285
|
+
|
|
1286
|
+
# Export based on format
|
|
1287
|
+
if export_format == "json":
|
|
1288
|
+
# For JSON, export full report structure with detailed resources
|
|
1289
|
+
summary = (
|
|
1290
|
+
reporter.generate_filtered_summary(criteria) if criteria else reporter.generate_summary()
|
|
1291
|
+
)
|
|
1292
|
+
export_path = export_report_json(export, metadata, summary, detailed_resources)
|
|
1293
|
+
console.print(
|
|
1294
|
+
f"✓ Exported {len(detailed_resources):,} resources to JSON: {export_path}",
|
|
1295
|
+
style="bold green",
|
|
1296
|
+
)
|
|
1297
|
+
elif export_format == "csv":
|
|
1298
|
+
# For CSV, export detailed resources
|
|
1299
|
+
export_path = export_report_csv(export, detailed_resources)
|
|
1300
|
+
console.print(
|
|
1301
|
+
f"✓ Exported {len(detailed_resources):,} resources to CSV: {export_path}",
|
|
1302
|
+
style="bold green",
|
|
1303
|
+
)
|
|
1304
|
+
elif export_format == "txt":
|
|
1305
|
+
# For TXT, export summary (detailed view doesn't make sense for plain text)
|
|
1306
|
+
summary = (
|
|
1307
|
+
reporter.generate_filtered_summary(criteria) if criteria else reporter.generate_summary()
|
|
1308
|
+
)
|
|
1309
|
+
export_path = export_report_txt(export, metadata, summary)
|
|
1310
|
+
console.print(f"✓ Exported summary to TXT: {export_path}", style="bold green")
|
|
1311
|
+
except FileExistsError as e:
|
|
1312
|
+
console.print(f"✗ {e}", style="bold red")
|
|
1313
|
+
console.print("\nUse a different filename or delete the existing file.", style="yellow")
|
|
1314
|
+
raise typer.Exit(code=1)
|
|
1315
|
+
except FileNotFoundError as e:
|
|
1316
|
+
console.print(f"✗ {e}", style="bold red")
|
|
1317
|
+
raise typer.Exit(code=1)
|
|
1318
|
+
except ValueError as e:
|
|
1319
|
+
console.print(f"✗ {e}", style="bold red")
|
|
1320
|
+
raise typer.Exit(code=1)
|
|
1321
|
+
else:
|
|
1322
|
+
# Display mode - show filter information if applied
|
|
1323
|
+
if criteria:
|
|
1324
|
+
console.print("\n[bold cyan]Filters Applied:[/bold cyan]")
|
|
1325
|
+
if resource_type:
|
|
1326
|
+
console.print(f" • Resource Types: {', '.join(resource_type)}")
|
|
1327
|
+
if region:
|
|
1328
|
+
console.print(f" • Regions: {', '.join(region)}")
|
|
1329
|
+
console.print(
|
|
1330
|
+
f" • Matching Resources: {len(detailed_resources):,} (of {snapshot.resource_count:,} total)\n"
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
# Format and display detailed view
|
|
1334
|
+
formatter = ReportFormatter(console)
|
|
1335
|
+
formatter.format_detailed(metadata, detailed_resources, page_size=page_size)
|
|
1336
|
+
else:
|
|
1337
|
+
# Generate summary (filtered or full)
|
|
1338
|
+
if criteria:
|
|
1339
|
+
summary = reporter.generate_filtered_summary(criteria)
|
|
1340
|
+
else:
|
|
1341
|
+
summary = reporter.generate_summary()
|
|
1342
|
+
|
|
1343
|
+
# Export mode
|
|
1344
|
+
if export:
|
|
1345
|
+
try:
|
|
1346
|
+
# Detect format from file extension
|
|
1347
|
+
export_format = detect_format(export)
|
|
1348
|
+
|
|
1349
|
+
# Export based on format
|
|
1350
|
+
if export_format == "json":
|
|
1351
|
+
# For JSON, export full report structure
|
|
1352
|
+
# Get all resources for complete export
|
|
1353
|
+
all_resources = list(reporter.get_detailed_resources(criteria))
|
|
1354
|
+
export_path = export_report_json(export, metadata, summary, all_resources)
|
|
1355
|
+
console.print(
|
|
1356
|
+
f"✓ Exported {summary.total_count:,} resources to JSON: {export_path}", style="bold green"
|
|
1357
|
+
)
|
|
1358
|
+
elif export_format == "csv":
|
|
1359
|
+
# For CSV, export resources
|
|
1360
|
+
all_resources = list(reporter.get_detailed_resources(criteria))
|
|
1361
|
+
export_path = export_report_csv(export, all_resources)
|
|
1362
|
+
console.print(
|
|
1363
|
+
f"✓ Exported {len(all_resources):,} resources to CSV: {export_path}", style="bold green"
|
|
1364
|
+
)
|
|
1365
|
+
elif export_format == "txt":
|
|
1366
|
+
# For TXT, export summary only
|
|
1367
|
+
export_path = export_report_txt(export, metadata, summary)
|
|
1368
|
+
console.print(f"✓ Exported summary to TXT: {export_path}", style="bold green")
|
|
1369
|
+
except FileExistsError as e:
|
|
1370
|
+
console.print(f"✗ {e}", style="bold red")
|
|
1371
|
+
console.print("\nUse a different filename or delete the existing file.", style="yellow")
|
|
1372
|
+
raise typer.Exit(code=1)
|
|
1373
|
+
except FileNotFoundError as e:
|
|
1374
|
+
console.print(f"✗ {e}", style="bold red")
|
|
1375
|
+
raise typer.Exit(code=1)
|
|
1376
|
+
except ValueError as e:
|
|
1377
|
+
console.print(f"✗ {e}", style="bold red")
|
|
1378
|
+
raise typer.Exit(code=1)
|
|
1379
|
+
else:
|
|
1380
|
+
# Display mode - show filter information
|
|
1381
|
+
if criteria:
|
|
1382
|
+
console.print("\n[bold cyan]Filters Applied:[/bold cyan]")
|
|
1383
|
+
if resource_type:
|
|
1384
|
+
console.print(f" • Resource Types: {', '.join(resource_type)}")
|
|
1385
|
+
if region:
|
|
1386
|
+
console.print(f" • Regions: {', '.join(region)}")
|
|
1387
|
+
console.print(
|
|
1388
|
+
f" • Matching Resources: {summary.total_count:,} (of {snapshot.resource_count:,} total)\n"
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
# Format and display summary report
|
|
1392
|
+
formatter = ReportFormatter(console)
|
|
1393
|
+
formatter.format_summary(metadata, summary, has_filters=has_filters)
|
|
1394
|
+
|
|
1395
|
+
except typer.Exit:
|
|
1396
|
+
raise
|
|
1397
|
+
except Exception as e:
|
|
1398
|
+
console.print(f"✗ Error generating report: {e}", style="bold red")
|
|
1399
|
+
logger.exception("Error in snapshot report command")
|
|
1400
|
+
raise typer.Exit(code=2)
|
|
1401
|
+
|
|
1402
|
+
|
|
1130
1403
|
@app.command()
|
|
1131
1404
|
def delta(
|
|
1132
1405
|
snapshot: Optional[str] = typer.Option(
|