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.
- aws_inventory_manager-0.17.12.dist-info/LICENSE +21 -0
- aws_inventory_manager-0.17.12.dist-info/METADATA +1292 -0
- aws_inventory_manager-0.17.12.dist-info/RECORD +152 -0
- aws_inventory_manager-0.17.12.dist-info/WHEEL +5 -0
- aws_inventory_manager-0.17.12.dist-info/entry_points.txt +2 -0
- aws_inventory_manager-0.17.12.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 +4046 -0
- src/cloudtrail/__init__.py +5 -0
- src/cloudtrail/query.py +642 -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/matching/__init__.py +6 -0
- src/matching/config.py +52 -0
- src/matching/normalizer.py +450 -0
- src/matching/prompts.py +33 -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 +453 -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/glue.py +199 -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 +763 -0
- src/storage/inventory_store.py +320 -0
- src/storage/resource_store.py +416 -0
- src/storage/schema.py +339 -0
- src/storage/snapshot_store.py +363 -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 +393 -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 +955 -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 +2429 -0
- src/web/templates/pages/snapshot_detail.html +271 -0
- src/web/templates/pages/snapshots.html +429 -0
src/delta/reporter.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Delta report formatting and display."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from ..models.delta_report import DeltaReport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DeltaReporter:
|
|
13
|
+
"""Format and display delta reports."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, console: Optional[Console] = None):
|
|
16
|
+
"""Initialize delta reporter.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
console: Rich console instance (creates new one if not provided)
|
|
20
|
+
"""
|
|
21
|
+
self.console = console or Console()
|
|
22
|
+
|
|
23
|
+
def display(self, report: DeltaReport, show_details: bool = False) -> None:
|
|
24
|
+
"""Display delta report to console.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
report: DeltaReport to display
|
|
28
|
+
show_details: Whether to show detailed resource information
|
|
29
|
+
"""
|
|
30
|
+
# Header
|
|
31
|
+
self.console.print()
|
|
32
|
+
self.console.print(
|
|
33
|
+
Panel(
|
|
34
|
+
f"[bold]Resource Delta Report[/bold]\n"
|
|
35
|
+
f"Reference Snapshot: {report.baseline_snapshot_name}\n"
|
|
36
|
+
f"Generated: {report.generated_at.strftime('%Y-%m-%d %H:%M:%S UTC')}",
|
|
37
|
+
style="cyan",
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
self.console.print()
|
|
41
|
+
|
|
42
|
+
# Check if there are any changes
|
|
43
|
+
if not report.has_changes:
|
|
44
|
+
self.console.print(
|
|
45
|
+
"[green]✓ No changes detected - environment matches reference snapshot[/green]", style="bold"
|
|
46
|
+
)
|
|
47
|
+
self.console.print()
|
|
48
|
+
self.console.print(f"Total resources: {report.baseline_resource_count}")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
# Summary statistics
|
|
52
|
+
self._display_summary(report)
|
|
53
|
+
|
|
54
|
+
# Group changes by service
|
|
55
|
+
grouped = report.group_by_service()
|
|
56
|
+
|
|
57
|
+
# Display changes by service
|
|
58
|
+
for service_type in sorted(grouped.keys()):
|
|
59
|
+
changes = grouped[service_type]
|
|
60
|
+
if any(changes.values()):
|
|
61
|
+
self._display_service_changes(service_type, changes, show_details)
|
|
62
|
+
|
|
63
|
+
# Display drift details if available
|
|
64
|
+
if report.drift_report is not None and report.drift_report.total_changes > 0:
|
|
65
|
+
from .formatters import DriftFormatter
|
|
66
|
+
|
|
67
|
+
self.console.print()
|
|
68
|
+
self.console.print("[bold cyan]═" * 50 + "[/bold cyan]")
|
|
69
|
+
formatter = DriftFormatter(self.console)
|
|
70
|
+
formatter.display(report.drift_report)
|
|
71
|
+
|
|
72
|
+
def _display_summary(self, report: DeltaReport) -> None:
|
|
73
|
+
"""Display summary statistics."""
|
|
74
|
+
table = Table(title="Summary", show_header=True, header_style="bold magenta")
|
|
75
|
+
table.add_column("Change Type", style="cyan", width=15)
|
|
76
|
+
table.add_column("Count", justify="right", style="yellow", width=10)
|
|
77
|
+
|
|
78
|
+
added_count = len(report.added_resources)
|
|
79
|
+
deleted_count = len(report.deleted_resources)
|
|
80
|
+
modified_count = len(report.modified_resources)
|
|
81
|
+
unchanged_count = report.unchanged_count
|
|
82
|
+
|
|
83
|
+
if added_count > 0:
|
|
84
|
+
table.add_row("➕ Added", f"[green]{added_count}[/green]")
|
|
85
|
+
if deleted_count > 0:
|
|
86
|
+
table.add_row("➖ Deleted", f"[red]{deleted_count}[/red]")
|
|
87
|
+
if modified_count > 0:
|
|
88
|
+
table.add_row("🔄 Modified", f"[yellow]{modified_count}[/yellow]")
|
|
89
|
+
if unchanged_count > 0:
|
|
90
|
+
table.add_row("✓ Unchanged", str(unchanged_count))
|
|
91
|
+
|
|
92
|
+
table.add_row("━" * 15, "━" * 10, style="dim")
|
|
93
|
+
table.add_row("[bold]Total Changes", f"[bold]{report.total_changes}")
|
|
94
|
+
|
|
95
|
+
self.console.print(table)
|
|
96
|
+
self.console.print()
|
|
97
|
+
|
|
98
|
+
def _display_service_changes(self, service_type: str, changes: dict, show_details: bool) -> None:
|
|
99
|
+
"""Display changes for a specific service type.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
service_type: AWS resource type (e.g., 'AWS::EC2::Instance')
|
|
103
|
+
changes: Dictionary with 'added', 'deleted', 'modified' lists
|
|
104
|
+
show_details: Whether to show detailed information
|
|
105
|
+
"""
|
|
106
|
+
# Count total changes for this service
|
|
107
|
+
total = len(changes["added"]) + len(changes["deleted"]) + len(changes["modified"])
|
|
108
|
+
if total == 0:
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
self.console.print(f"[bold cyan]{service_type}[/bold cyan] ({total} changes)")
|
|
112
|
+
|
|
113
|
+
# Create table for this service
|
|
114
|
+
table = Table(show_header=True, box=None, padding=(0, 2))
|
|
115
|
+
table.add_column("Change", width=8)
|
|
116
|
+
table.add_column("Name", style="white")
|
|
117
|
+
table.add_column("Region", width=15)
|
|
118
|
+
table.add_column("ARN" if show_details else "Tags", style="dim")
|
|
119
|
+
|
|
120
|
+
# Added resources
|
|
121
|
+
for resource in changes["added"]:
|
|
122
|
+
tags_str = self._format_tags(resource.tags)
|
|
123
|
+
arn_or_tags = resource.arn if show_details else tags_str
|
|
124
|
+
table.add_row("[green]➕ Add[/green]", resource.name, resource.region, arn_or_tags)
|
|
125
|
+
|
|
126
|
+
# Deleted resources
|
|
127
|
+
for resource in changes["deleted"]:
|
|
128
|
+
tags_str = self._format_tags(resource.tags)
|
|
129
|
+
arn_or_tags = resource.arn if show_details else tags_str
|
|
130
|
+
table.add_row("[red]➖ Del[/red]", resource.name, resource.region, arn_or_tags)
|
|
131
|
+
|
|
132
|
+
# Modified resources
|
|
133
|
+
for change in changes["modified"]:
|
|
134
|
+
tags_str = self._format_tags(change.resource.tags)
|
|
135
|
+
arn_or_tags = change.resource.arn if show_details else tags_str
|
|
136
|
+
table.add_row("[yellow]🔄 Mod[/yellow]", change.resource.name, change.resource.region, arn_or_tags)
|
|
137
|
+
|
|
138
|
+
self.console.print(table)
|
|
139
|
+
self.console.print()
|
|
140
|
+
|
|
141
|
+
def _format_tags(self, tags: dict) -> str:
|
|
142
|
+
"""Format tags dictionary as a string.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
tags: Dictionary of tag key-value pairs
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Formatted string like "Env=prod, App=web"
|
|
149
|
+
"""
|
|
150
|
+
if not tags:
|
|
151
|
+
return "-"
|
|
152
|
+
|
|
153
|
+
# Show up to 3 most important tags
|
|
154
|
+
important_keys = ["Name", "Environment", "Project", "Team", "Application"]
|
|
155
|
+
selected_tags = []
|
|
156
|
+
|
|
157
|
+
# First add important tags if present
|
|
158
|
+
for key in important_keys:
|
|
159
|
+
if key in tags:
|
|
160
|
+
selected_tags.append(f"{key}={tags[key]}")
|
|
161
|
+
|
|
162
|
+
# Add other tags up to limit
|
|
163
|
+
for key, value in tags.items():
|
|
164
|
+
if key not in important_keys and len(selected_tags) < 3:
|
|
165
|
+
selected_tags.append(f"{key}={value}")
|
|
166
|
+
|
|
167
|
+
return ", ".join(selected_tags[:3])
|
|
168
|
+
|
|
169
|
+
def export_json(self, report: DeltaReport, filepath: str) -> None:
|
|
170
|
+
"""Export delta report to JSON file.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
report: DeltaReport to export
|
|
174
|
+
filepath: Destination file path
|
|
175
|
+
"""
|
|
176
|
+
from ..utils.export import export_to_json
|
|
177
|
+
|
|
178
|
+
export_to_json(report.to_dict(), filepath)
|
|
179
|
+
self.console.print(f"[green]✓ Delta report exported to {filepath}[/green]")
|
|
180
|
+
|
|
181
|
+
def export_csv(self, report: DeltaReport, filepath: str) -> None:
|
|
182
|
+
"""Export delta report to CSV file.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
report: DeltaReport to export
|
|
186
|
+
filepath: Destination file path
|
|
187
|
+
"""
|
|
188
|
+
from ..utils.export import export_to_csv
|
|
189
|
+
|
|
190
|
+
# Flatten the report into rows
|
|
191
|
+
rows = []
|
|
192
|
+
|
|
193
|
+
for resource in report.added_resources:
|
|
194
|
+
rows.append(
|
|
195
|
+
{
|
|
196
|
+
"change_type": "added",
|
|
197
|
+
"resource_type": resource.resource_type,
|
|
198
|
+
"name": resource.name,
|
|
199
|
+
"arn": resource.arn,
|
|
200
|
+
"region": resource.region,
|
|
201
|
+
"tags": str(resource.tags),
|
|
202
|
+
"created_at": resource.created_at.isoformat() if resource.created_at else None,
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
for resource in report.deleted_resources:
|
|
207
|
+
rows.append(
|
|
208
|
+
{
|
|
209
|
+
"change_type": "deleted",
|
|
210
|
+
"resource_type": resource.resource_type,
|
|
211
|
+
"name": resource.name,
|
|
212
|
+
"arn": resource.arn,
|
|
213
|
+
"region": resource.region,
|
|
214
|
+
"tags": str(resource.tags),
|
|
215
|
+
"created_at": resource.created_at.isoformat() if resource.created_at else None,
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
for change in report.modified_resources:
|
|
220
|
+
rows.append(
|
|
221
|
+
{
|
|
222
|
+
"change_type": "modified",
|
|
223
|
+
"resource_type": change.resource.resource_type,
|
|
224
|
+
"name": change.resource.name,
|
|
225
|
+
"arn": change.resource.arn,
|
|
226
|
+
"region": change.resource.region,
|
|
227
|
+
"tags": str(change.resource.tags),
|
|
228
|
+
"old_hash": change.old_config_hash,
|
|
229
|
+
"new_hash": change.new_config_hash,
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
export_to_csv(rows, filepath)
|
|
234
|
+
self.console.print(f"[green]✓ Delta report exported to {filepath}[/green]")
|
src/matching/__init__.py
ADDED
src/matching/config.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Configuration for resource name normalization."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class NormalizerConfig:
|
|
10
|
+
"""Configuration for the resource normalizer."""
|
|
11
|
+
|
|
12
|
+
# OpenAI API configuration
|
|
13
|
+
api_key: Optional[str] = None
|
|
14
|
+
base_url: Optional[str] = None
|
|
15
|
+
model: str = "gpt-4o-mini"
|
|
16
|
+
|
|
17
|
+
# Batch settings
|
|
18
|
+
max_batch_size: int = 50
|
|
19
|
+
timeout_seconds: int = 60
|
|
20
|
+
max_retries: int = 3
|
|
21
|
+
|
|
22
|
+
# Patterns to detect "random" names that need AI normalization
|
|
23
|
+
random_patterns: List[str] = field(
|
|
24
|
+
default_factory=lambda: [
|
|
25
|
+
r"-[a-f0-9]{8,}$", # Hex suffix: -a1b2c3d4e5
|
|
26
|
+
r"-[A-Z0-9]{8,}$", # CloudFormation suffix: -ABCD1234XYZ
|
|
27
|
+
r"_[a-z0-9]{5,}$", # Underscore suffix (Bedrock): _jnwn1
|
|
28
|
+
r"-\d{10,}$", # Timestamp suffix: -1704067200
|
|
29
|
+
r"\d{12}", # Account ID anywhere: 123456789012
|
|
30
|
+
r"^(subnet|vpc|vol|sg|i|rtb|igw|nat|eni)-[a-f0-9]+$", # AWS resource IDs
|
|
31
|
+
]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_env(cls) -> "NormalizerConfig":
|
|
36
|
+
"""Load configuration from environment variables.
|
|
37
|
+
|
|
38
|
+
Environment variables:
|
|
39
|
+
OPENAI_API_KEY: API key for OpenAI-compatible endpoint
|
|
40
|
+
OPENAI_BASE_URL: Custom API endpoint URL
|
|
41
|
+
OPENAI_MODEL: Model name (default: gpt-4o-mini)
|
|
42
|
+
"""
|
|
43
|
+
return cls(
|
|
44
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
|
45
|
+
base_url=os.getenv("OPENAI_BASE_URL"),
|
|
46
|
+
model=os.getenv("OPENAI_MODEL", "gpt-4o-mini"),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def is_ai_enabled(self) -> bool:
|
|
51
|
+
"""Check if AI normalization is available."""
|
|
52
|
+
return bool(self.api_key)
|