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
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]")
@@ -0,0 +1,6 @@
1
+ """Resource name normalization for intelligent matching."""
2
+
3
+ from .config import NormalizerConfig
4
+ from .normalizer import NormalizationResult, ResourceNormalizer
5
+
6
+ __all__ = ["ResourceNormalizer", "NormalizationResult", "NormalizerConfig"]
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)