aws-inventory-manager 0.13.2__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/LICENSE +21 -0
- aws_inventory_manager-0.13.2.dist-info/METADATA +1226 -0
- aws_inventory_manager-0.13.2.dist-info/RECORD +145 -0
- aws_inventory_manager-0.13.2.dist-info/WHEEL +5 -0
- aws_inventory_manager-0.13.2.dist-info/entry_points.txt +2 -0
- aws_inventory_manager-0.13.2.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 +3626 -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/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 +451 -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/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 +749 -0
- src/storage/inventory_store.py +320 -0
- src/storage/resource_store.py +413 -0
- src/storage/schema.py +288 -0
- src/storage/snapshot_store.py +346 -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 +379 -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 +949 -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 +2251 -0
- src/web/templates/pages/snapshot_detail.html +271 -0
- src/web/templates/pages/snapshots.html +429 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Resource type mapping between AWS Config and collectors.
|
|
2
|
+
|
|
3
|
+
AWS Config supports ~80+ resource types. This module maps which types
|
|
4
|
+
can be collected via Config vs which must use direct API calls.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Set
|
|
10
|
+
|
|
11
|
+
# Resource types supported by AWS Config
|
|
12
|
+
# Reference: https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html
|
|
13
|
+
CONFIG_SUPPORTED_TYPES: Set[str] = {
|
|
14
|
+
# EC2
|
|
15
|
+
"AWS::EC2::CustomerGateway",
|
|
16
|
+
"AWS::EC2::EIP",
|
|
17
|
+
"AWS::EC2::Host",
|
|
18
|
+
"AWS::EC2::Instance",
|
|
19
|
+
"AWS::EC2::InternetGateway",
|
|
20
|
+
"AWS::EC2::NetworkAcl",
|
|
21
|
+
"AWS::EC2::NetworkInterface",
|
|
22
|
+
"AWS::EC2::RouteTable",
|
|
23
|
+
"AWS::EC2::SecurityGroup",
|
|
24
|
+
"AWS::EC2::Subnet",
|
|
25
|
+
"AWS::EC2::Volume",
|
|
26
|
+
"AWS::EC2::VPC",
|
|
27
|
+
"AWS::EC2::VPNConnection",
|
|
28
|
+
"AWS::EC2::VPNGateway",
|
|
29
|
+
"AWS::EC2::NatGateway",
|
|
30
|
+
"AWS::EC2::EgressOnlyInternetGateway",
|
|
31
|
+
"AWS::EC2::FlowLog",
|
|
32
|
+
"AWS::EC2::TransitGateway",
|
|
33
|
+
"AWS::EC2::TransitGatewayAttachment",
|
|
34
|
+
"AWS::EC2::TransitGatewayRouteTable",
|
|
35
|
+
"AWS::EC2::LaunchTemplate",
|
|
36
|
+
# IAM
|
|
37
|
+
"AWS::IAM::User",
|
|
38
|
+
"AWS::IAM::Group",
|
|
39
|
+
"AWS::IAM::Role",
|
|
40
|
+
"AWS::IAM::Policy",
|
|
41
|
+
# S3
|
|
42
|
+
"AWS::S3::Bucket",
|
|
43
|
+
"AWS::S3::AccountPublicAccessBlock",
|
|
44
|
+
# Lambda
|
|
45
|
+
"AWS::Lambda::Function",
|
|
46
|
+
"AWS::Lambda::Alias",
|
|
47
|
+
# RDS
|
|
48
|
+
"AWS::RDS::DBInstance",
|
|
49
|
+
"AWS::RDS::DBCluster",
|
|
50
|
+
"AWS::RDS::DBClusterSnapshot",
|
|
51
|
+
"AWS::RDS::DBSecurityGroup",
|
|
52
|
+
"AWS::RDS::DBSnapshot",
|
|
53
|
+
"AWS::RDS::DBSubnetGroup",
|
|
54
|
+
"AWS::RDS::EventSubscription",
|
|
55
|
+
# DynamoDB
|
|
56
|
+
"AWS::DynamoDB::Table",
|
|
57
|
+
# CloudWatch
|
|
58
|
+
"AWS::Logs::LogGroup",
|
|
59
|
+
"AWS::CloudWatch::Alarm",
|
|
60
|
+
# SNS
|
|
61
|
+
"AWS::SNS::Topic",
|
|
62
|
+
# SQS
|
|
63
|
+
"AWS::SQS::Queue",
|
|
64
|
+
# ELB
|
|
65
|
+
"AWS::ElasticLoadBalancing::LoadBalancer",
|
|
66
|
+
"AWS::ElasticLoadBalancingV2::LoadBalancer",
|
|
67
|
+
"AWS::ElasticLoadBalancingV2::TargetGroup",
|
|
68
|
+
# ECS
|
|
69
|
+
"AWS::ECS::Cluster",
|
|
70
|
+
"AWS::ECS::Service",
|
|
71
|
+
"AWS::ECS::TaskDefinition",
|
|
72
|
+
# EKS
|
|
73
|
+
"AWS::EKS::Cluster",
|
|
74
|
+
"AWS::EKS::FargateProfile",
|
|
75
|
+
"AWS::EKS::Nodegroup",
|
|
76
|
+
# KMS
|
|
77
|
+
"AWS::KMS::Key",
|
|
78
|
+
# Secrets Manager
|
|
79
|
+
"AWS::SecretsManager::Secret",
|
|
80
|
+
# API Gateway
|
|
81
|
+
"AWS::ApiGateway::RestApi",
|
|
82
|
+
"AWS::ApiGateway::Stage",
|
|
83
|
+
"AWS::ApiGatewayV2::Api",
|
|
84
|
+
"AWS::ApiGatewayV2::Stage",
|
|
85
|
+
# CloudFormation
|
|
86
|
+
"AWS::CloudFormation::Stack",
|
|
87
|
+
# Auto Scaling
|
|
88
|
+
"AWS::AutoScaling::AutoScalingGroup",
|
|
89
|
+
"AWS::AutoScaling::LaunchConfiguration",
|
|
90
|
+
"AWS::AutoScaling::ScalingPolicy",
|
|
91
|
+
# CloudTrail
|
|
92
|
+
"AWS::CloudTrail::Trail",
|
|
93
|
+
# CodeBuild
|
|
94
|
+
"AWS::CodeBuild::Project",
|
|
95
|
+
# CodePipeline
|
|
96
|
+
"AWS::CodePipeline::Pipeline",
|
|
97
|
+
# Config
|
|
98
|
+
"AWS::Config::ConfigurationRecorder",
|
|
99
|
+
"AWS::Config::ConformancePackCompliance",
|
|
100
|
+
"AWS::Config::ResourceCompliance",
|
|
101
|
+
# Elasticsearch/OpenSearch
|
|
102
|
+
"AWS::Elasticsearch::Domain",
|
|
103
|
+
"AWS::OpenSearch::Domain",
|
|
104
|
+
# ElastiCache
|
|
105
|
+
"AWS::ElastiCache::CacheCluster",
|
|
106
|
+
"AWS::ElastiCache::ReplicationGroup",
|
|
107
|
+
# EFS
|
|
108
|
+
"AWS::EFS::FileSystem",
|
|
109
|
+
"AWS::EFS::AccessPoint",
|
|
110
|
+
# EventBridge
|
|
111
|
+
"AWS::Events::Rule",
|
|
112
|
+
"AWS::Events::EventBus",
|
|
113
|
+
# Kinesis
|
|
114
|
+
"AWS::Kinesis::Stream",
|
|
115
|
+
"AWS::KinesisFirehose::DeliveryStream",
|
|
116
|
+
# Redshift
|
|
117
|
+
"AWS::Redshift::Cluster",
|
|
118
|
+
"AWS::Redshift::ClusterParameterGroup",
|
|
119
|
+
"AWS::Redshift::ClusterSecurityGroup",
|
|
120
|
+
"AWS::Redshift::ClusterSubnetGroup",
|
|
121
|
+
# SSM
|
|
122
|
+
"AWS::SSM::ManagedInstanceInventory",
|
|
123
|
+
"AWS::SSM::PatchCompliance",
|
|
124
|
+
"AWS::SSM::AssociationCompliance",
|
|
125
|
+
"AWS::SSM::FileData",
|
|
126
|
+
# Step Functions
|
|
127
|
+
"AWS::StepFunctions::StateMachine",
|
|
128
|
+
"AWS::StepFunctions::Activity",
|
|
129
|
+
# VPC
|
|
130
|
+
"AWS::EC2::VPCEndpoint",
|
|
131
|
+
"AWS::EC2::VPCEndpointService",
|
|
132
|
+
"AWS::EC2::VPCPeeringConnection",
|
|
133
|
+
# ACM
|
|
134
|
+
"AWS::ACM::Certificate",
|
|
135
|
+
# Backup
|
|
136
|
+
"AWS::Backup::BackupPlan",
|
|
137
|
+
"AWS::Backup::BackupSelection",
|
|
138
|
+
"AWS::Backup::BackupVault",
|
|
139
|
+
"AWS::Backup::RecoveryPoint",
|
|
140
|
+
# Network Firewall
|
|
141
|
+
"AWS::NetworkFirewall::Firewall",
|
|
142
|
+
"AWS::NetworkFirewall::FirewallPolicy",
|
|
143
|
+
"AWS::NetworkFirewall::RuleGroup",
|
|
144
|
+
# WAF
|
|
145
|
+
"AWS::WAF::RateBasedRule",
|
|
146
|
+
"AWS::WAF::Rule",
|
|
147
|
+
"AWS::WAF::RuleGroup",
|
|
148
|
+
"AWS::WAF::WebACL",
|
|
149
|
+
"AWS::WAFv2::WebACL",
|
|
150
|
+
"AWS::WAFv2::RuleGroup",
|
|
151
|
+
"AWS::WAFv2::IPSet",
|
|
152
|
+
"AWS::WAFv2::RegexPatternSet",
|
|
153
|
+
"AWS::WAFv2::ManagedRuleSet",
|
|
154
|
+
# Shield
|
|
155
|
+
"AWS::Shield::Protection",
|
|
156
|
+
"AWS::ShieldRegional::Protection",
|
|
157
|
+
# Systems Manager
|
|
158
|
+
"AWS::SSM::Parameter",
|
|
159
|
+
# Route53 (limited support)
|
|
160
|
+
"AWS::Route53::HostedZone",
|
|
161
|
+
# Glue
|
|
162
|
+
"AWS::Glue::Job",
|
|
163
|
+
"AWS::Glue::Classifier",
|
|
164
|
+
"AWS::Glue::Crawler",
|
|
165
|
+
"AWS::Glue::Database",
|
|
166
|
+
"AWS::Glue::Table",
|
|
167
|
+
# Athena
|
|
168
|
+
"AWS::Athena::WorkGroup",
|
|
169
|
+
"AWS::Athena::DataCatalog",
|
|
170
|
+
# EMR
|
|
171
|
+
"AWS::EMR::Cluster",
|
|
172
|
+
"AWS::EMR::SecurityConfiguration",
|
|
173
|
+
# SageMaker
|
|
174
|
+
"AWS::SageMaker::CodeRepository",
|
|
175
|
+
"AWS::SageMaker::Model",
|
|
176
|
+
"AWS::SageMaker::NotebookInstance",
|
|
177
|
+
"AWS::SageMaker::Workteam",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Resource types that should ALWAYS use direct API collectors
|
|
181
|
+
# (even if technically supported by Config, the direct API is better)
|
|
182
|
+
DIRECT_API_ONLY_TYPES: Set[str] = {
|
|
183
|
+
# Route53 has better data via direct API
|
|
184
|
+
"AWS::Route53::HostedZone",
|
|
185
|
+
# WAF has complex regional/CloudFront distinction
|
|
186
|
+
"AWS::WAFv2::WebACL::Regional",
|
|
187
|
+
"AWS::WAFv2::WebACL::CloudFront",
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Mapping from collector service_name to AWS Config resource types
|
|
191
|
+
COLLECTOR_TO_CONFIG_TYPES: dict[str, list[str]] = {
|
|
192
|
+
"ec2": [
|
|
193
|
+
"AWS::EC2::Instance",
|
|
194
|
+
"AWS::EC2::Volume",
|
|
195
|
+
"AWS::EC2::VPC",
|
|
196
|
+
"AWS::EC2::SecurityGroup",
|
|
197
|
+
"AWS::EC2::Subnet",
|
|
198
|
+
"AWS::EC2::NatGateway",
|
|
199
|
+
"AWS::EC2::InternetGateway",
|
|
200
|
+
"AWS::EC2::RouteTable",
|
|
201
|
+
"AWS::EC2::NetworkAcl",
|
|
202
|
+
"AWS::EC2::NetworkInterface",
|
|
203
|
+
"AWS::EC2::EIP",
|
|
204
|
+
],
|
|
205
|
+
"iam": [
|
|
206
|
+
"AWS::IAM::Role",
|
|
207
|
+
"AWS::IAM::User",
|
|
208
|
+
"AWS::IAM::Group",
|
|
209
|
+
"AWS::IAM::Policy",
|
|
210
|
+
],
|
|
211
|
+
"s3": [
|
|
212
|
+
"AWS::S3::Bucket",
|
|
213
|
+
],
|
|
214
|
+
"lambda": [
|
|
215
|
+
"AWS::Lambda::Function",
|
|
216
|
+
],
|
|
217
|
+
"rds": [
|
|
218
|
+
"AWS::RDS::DBInstance",
|
|
219
|
+
"AWS::RDS::DBCluster",
|
|
220
|
+
],
|
|
221
|
+
"dynamodb": [
|
|
222
|
+
"AWS::DynamoDB::Table",
|
|
223
|
+
],
|
|
224
|
+
"cloudwatch": [
|
|
225
|
+
"AWS::Logs::LogGroup",
|
|
226
|
+
"AWS::CloudWatch::Alarm",
|
|
227
|
+
],
|
|
228
|
+
"sns": [
|
|
229
|
+
"AWS::SNS::Topic",
|
|
230
|
+
],
|
|
231
|
+
"sqs": [
|
|
232
|
+
"AWS::SQS::Queue",
|
|
233
|
+
],
|
|
234
|
+
"elb": [
|
|
235
|
+
"AWS::ElasticLoadBalancing::LoadBalancer",
|
|
236
|
+
"AWS::ElasticLoadBalancingV2::LoadBalancer",
|
|
237
|
+
"AWS::ElasticLoadBalancingV2::TargetGroup",
|
|
238
|
+
],
|
|
239
|
+
"ecs": [
|
|
240
|
+
"AWS::ECS::Cluster",
|
|
241
|
+
"AWS::ECS::Service",
|
|
242
|
+
"AWS::ECS::TaskDefinition",
|
|
243
|
+
],
|
|
244
|
+
"eks": [
|
|
245
|
+
"AWS::EKS::Cluster",
|
|
246
|
+
"AWS::EKS::Nodegroup",
|
|
247
|
+
"AWS::EKS::FargateProfile",
|
|
248
|
+
],
|
|
249
|
+
"kms": [
|
|
250
|
+
"AWS::KMS::Key",
|
|
251
|
+
],
|
|
252
|
+
"secretsmanager": [
|
|
253
|
+
"AWS::SecretsManager::Secret",
|
|
254
|
+
],
|
|
255
|
+
"apigateway": [
|
|
256
|
+
"AWS::ApiGateway::RestApi",
|
|
257
|
+
"AWS::ApiGateway::Stage",
|
|
258
|
+
"AWS::ApiGatewayV2::Api",
|
|
259
|
+
],
|
|
260
|
+
"cloudformation": [
|
|
261
|
+
"AWS::CloudFormation::Stack",
|
|
262
|
+
],
|
|
263
|
+
"codebuild": [
|
|
264
|
+
"AWS::CodeBuild::Project",
|
|
265
|
+
],
|
|
266
|
+
"codepipeline": [
|
|
267
|
+
"AWS::CodePipeline::Pipeline",
|
|
268
|
+
],
|
|
269
|
+
"elasticache": [
|
|
270
|
+
"AWS::ElastiCache::CacheCluster",
|
|
271
|
+
"AWS::ElastiCache::ReplicationGroup",
|
|
272
|
+
],
|
|
273
|
+
"efs": [
|
|
274
|
+
"AWS::EFS::FileSystem",
|
|
275
|
+
"AWS::EFS::AccessPoint",
|
|
276
|
+
],
|
|
277
|
+
"eventbridge": [
|
|
278
|
+
"AWS::Events::Rule",
|
|
279
|
+
"AWS::Events::EventBus",
|
|
280
|
+
],
|
|
281
|
+
"stepfunctions": [
|
|
282
|
+
"AWS::StepFunctions::StateMachine",
|
|
283
|
+
],
|
|
284
|
+
"backup": [
|
|
285
|
+
"AWS::Backup::BackupPlan",
|
|
286
|
+
"AWS::Backup::BackupVault",
|
|
287
|
+
],
|
|
288
|
+
"ssm": [
|
|
289
|
+
"AWS::SSM::Parameter",
|
|
290
|
+
],
|
|
291
|
+
"vpcendpoints": [
|
|
292
|
+
"AWS::EC2::VPCEndpoint",
|
|
293
|
+
],
|
|
294
|
+
"waf": [
|
|
295
|
+
"AWS::WAFv2::WebACL",
|
|
296
|
+
"AWS::WAFv2::RuleGroup",
|
|
297
|
+
"AWS::WAFv2::IPSet",
|
|
298
|
+
],
|
|
299
|
+
"route53": [
|
|
300
|
+
"AWS::Route53::HostedZone",
|
|
301
|
+
],
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def is_config_supported_type(resource_type: str) -> bool:
|
|
306
|
+
"""Check if a resource type is supported by AWS Config.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
resource_type: AWS resource type (e.g., "AWS::EC2::Instance")
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
True if the type can be collected via AWS Config
|
|
313
|
+
"""
|
|
314
|
+
if resource_type in DIRECT_API_ONLY_TYPES:
|
|
315
|
+
return False
|
|
316
|
+
return resource_type in CONFIG_SUPPORTED_TYPES
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def get_config_types_for_service(service_name: str) -> list[str]:
|
|
320
|
+
"""Get AWS Config resource types for a collector service.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
service_name: Collector service name (e.g., "ec2", "s3")
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
List of AWS Config resource types for this service
|
|
327
|
+
"""
|
|
328
|
+
return COLLECTOR_TO_CONFIG_TYPES.get(service_name, [])
|
src/cost/__init__.py
ADDED
src/cost/analyzer.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Cost analyzer for inventory snapshots."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from typing import Any, Dict, Optional, Set
|
|
7
|
+
|
|
8
|
+
from ..models.cost_report import CostBreakdown, CostReport
|
|
9
|
+
from ..models.snapshot import Snapshot
|
|
10
|
+
from .explorer import CostExplorerClient
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CostAnalyzer:
|
|
16
|
+
"""Analyze costs for inventory snapshots."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, cost_explorer: CostExplorerClient):
|
|
19
|
+
"""Initialize cost analyzer.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
cost_explorer: Cost Explorer client instance
|
|
23
|
+
"""
|
|
24
|
+
self.cost_explorer = cost_explorer
|
|
25
|
+
|
|
26
|
+
def analyze(
|
|
27
|
+
self,
|
|
28
|
+
snapshot: Snapshot,
|
|
29
|
+
start_date: Optional[datetime] = None,
|
|
30
|
+
end_date: Optional[datetime] = None,
|
|
31
|
+
granularity: str = "MONTHLY",
|
|
32
|
+
has_deltas: bool = False,
|
|
33
|
+
delta_report: Optional[Any] = None,
|
|
34
|
+
) -> CostReport:
|
|
35
|
+
"""Analyze costs and separate snapshot resources.
|
|
36
|
+
|
|
37
|
+
This implementation uses a simplified heuristic approach:
|
|
38
|
+
1. Get total costs by service
|
|
39
|
+
2. Estimate baseline portion based on resource counts in snapshot
|
|
40
|
+
3. Remaining costs are attributed to non-baseline resources
|
|
41
|
+
|
|
42
|
+
Note: For precise cost attribution, AWS would need to provide
|
|
43
|
+
per-resource cost data, which Cost Explorer doesn't directly expose.
|
|
44
|
+
This gives a good approximation based on service-level costs.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
snapshot: The baseline snapshot
|
|
48
|
+
start_date: Start date for cost analysis (default: snapshot date)
|
|
49
|
+
end_date: End date for cost analysis (default: today)
|
|
50
|
+
granularity: Cost granularity - DAILY or MONTHLY
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
CostReport with baseline and non-baseline cost breakdown
|
|
54
|
+
"""
|
|
55
|
+
# Default date range: from snapshot creation to today
|
|
56
|
+
if not start_date:
|
|
57
|
+
start_date = snapshot.created_at
|
|
58
|
+
# Remove timezone for Cost Explorer API (uses dates only, no time)
|
|
59
|
+
start_date = start_date.replace(tzinfo=None)
|
|
60
|
+
|
|
61
|
+
if not end_date:
|
|
62
|
+
end_date = datetime.now()
|
|
63
|
+
|
|
64
|
+
# Ensure both dates are timezone-naive for comparison
|
|
65
|
+
if hasattr(start_date, "tzinfo") and start_date.tzinfo is not None:
|
|
66
|
+
start_date = start_date.replace(tzinfo=None)
|
|
67
|
+
if hasattr(end_date, "tzinfo") and end_date.tzinfo is not None:
|
|
68
|
+
end_date = end_date.replace(tzinfo=None)
|
|
69
|
+
|
|
70
|
+
# Ensure start_date is before end_date
|
|
71
|
+
# AWS Cost Explorer requires at least 1 day difference
|
|
72
|
+
if start_date >= end_date:
|
|
73
|
+
# If dates are the same or inverted, set end_date to start_date + 1 day
|
|
74
|
+
end_date = start_date + timedelta(days=1)
|
|
75
|
+
|
|
76
|
+
logger.debug(f"Analyzing costs from {start_date.strftime('%Y-%m-%d')} " f"to {end_date.strftime('%Y-%m-%d')}")
|
|
77
|
+
|
|
78
|
+
# Execute data completeness check and cost retrieval in parallel
|
|
79
|
+
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
80
|
+
# Submit both tasks
|
|
81
|
+
completeness_future = executor.submit(self.cost_explorer.check_data_completeness, end_date)
|
|
82
|
+
costs_future = executor.submit(self.cost_explorer.get_costs_by_service, start_date, end_date, granularity)
|
|
83
|
+
|
|
84
|
+
# Wait for both to complete
|
|
85
|
+
is_complete, data_through, lag_days = completeness_future.result()
|
|
86
|
+
service_costs = costs_future.result()
|
|
87
|
+
|
|
88
|
+
# If no deltas (no resource changes), ALL costs are baseline
|
|
89
|
+
if not has_deltas:
|
|
90
|
+
logger.debug("No resource changes detected - all costs are from snapshot resources")
|
|
91
|
+
baseline_costs = service_costs.copy()
|
|
92
|
+
non_baseline_costs: Dict[str, float] = {}
|
|
93
|
+
baseline_total = sum(baseline_costs.values())
|
|
94
|
+
non_baseline_total = 0.0
|
|
95
|
+
total_cost = baseline_total
|
|
96
|
+
baseline_pct = 100.0
|
|
97
|
+
non_baseline_pct = 0.0
|
|
98
|
+
else:
|
|
99
|
+
# There are deltas - we can't accurately split costs without per-resource pricing
|
|
100
|
+
# Show total only with a note that we can't split accurately
|
|
101
|
+
logger.debug("Resource changes detected - showing total costs only")
|
|
102
|
+
baseline_costs = service_costs.copy()
|
|
103
|
+
non_baseline_costs = {}
|
|
104
|
+
baseline_total = sum(baseline_costs.values())
|
|
105
|
+
non_baseline_total = 0.0
|
|
106
|
+
total_cost = baseline_total
|
|
107
|
+
baseline_pct = 100.0
|
|
108
|
+
non_baseline_pct = 0.0
|
|
109
|
+
# Note: We could enhance this in the future to track specific resource costs
|
|
110
|
+
# For now, we show total and list the delta resources separately
|
|
111
|
+
|
|
112
|
+
# Create cost breakdowns
|
|
113
|
+
baseline_breakdown = CostBreakdown(
|
|
114
|
+
total=baseline_total,
|
|
115
|
+
by_service=baseline_costs,
|
|
116
|
+
percentage=baseline_pct,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
non_baseline_breakdown = CostBreakdown(
|
|
120
|
+
total=non_baseline_total,
|
|
121
|
+
by_service=non_baseline_costs,
|
|
122
|
+
percentage=non_baseline_pct,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Create cost report
|
|
126
|
+
report = CostReport(
|
|
127
|
+
generated_at=datetime.now(),
|
|
128
|
+
baseline_snapshot_name=snapshot.name,
|
|
129
|
+
period_start=start_date,
|
|
130
|
+
period_end=end_date,
|
|
131
|
+
baseline_costs=baseline_breakdown,
|
|
132
|
+
non_baseline_costs=non_baseline_breakdown,
|
|
133
|
+
total_cost=total_cost,
|
|
134
|
+
data_complete=is_complete,
|
|
135
|
+
data_through=data_through,
|
|
136
|
+
lag_days=lag_days,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
logger.info(
|
|
140
|
+
f"Cost analysis complete: Baseline=${baseline_total:.2f}, "
|
|
141
|
+
f"Non-baseline=${non_baseline_total:.2f}, Total=${total_cost:.2f}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return report
|
|
145
|
+
|
|
146
|
+
def _get_baseline_service_mapping(self, snapshot: Snapshot) -> Set[str]:
|
|
147
|
+
"""Get set of AWS service names that have baseline resources.
|
|
148
|
+
|
|
149
|
+
Maps our resource types (e.g., 'AWS::EC2::Instance') to Cost Explorer
|
|
150
|
+
service names (e.g., 'Amazon Elastic Compute Cloud - Compute').
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
snapshot: Baseline snapshot
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Set of AWS service names from Cost Explorer
|
|
157
|
+
"""
|
|
158
|
+
# Mapping from our resource types to Cost Explorer service names
|
|
159
|
+
service_name_map = {
|
|
160
|
+
"AWS::EC2::Instance": "Amazon Elastic Compute Cloud - Compute",
|
|
161
|
+
"AWS::EC2::Volume": "Amazon Elastic Compute Cloud - Compute",
|
|
162
|
+
"AWS::EC2::VPC": "Amazon Elastic Compute Cloud - Compute",
|
|
163
|
+
"AWS::EC2::SecurityGroup": "Amazon Elastic Compute Cloud - Compute",
|
|
164
|
+
"AWS::EC2::Subnet": "Amazon Elastic Compute Cloud - Compute",
|
|
165
|
+
"AWS::EC2::VPCEndpoint::Interface": "Amazon Elastic Compute Cloud - Compute",
|
|
166
|
+
"AWS::EC2::VPCEndpoint::Gateway": "Amazon Elastic Compute Cloud - Compute",
|
|
167
|
+
"AWS::Lambda::Function": "AWS Lambda",
|
|
168
|
+
"AWS::Lambda::LayerVersion": "AWS Lambda",
|
|
169
|
+
"AWS::S3::Bucket": "Amazon Simple Storage Service",
|
|
170
|
+
"AWS::RDS::DBInstance": "Amazon Relational Database Service",
|
|
171
|
+
"AWS::RDS::DBCluster": "Amazon Relational Database Service",
|
|
172
|
+
"AWS::IAM::Role": "AWS Identity and Access Management",
|
|
173
|
+
"AWS::IAM::User": "AWS Identity and Access Management",
|
|
174
|
+
"AWS::IAM::Policy": "AWS Identity and Access Management",
|
|
175
|
+
"AWS::IAM::Group": "AWS Identity and Access Management",
|
|
176
|
+
"AWS::CloudWatch::Alarm": "Amazon CloudWatch",
|
|
177
|
+
"AWS::CloudWatch::CompositeAlarm": "Amazon CloudWatch",
|
|
178
|
+
"AWS::Logs::LogGroup": "Amazon CloudWatch",
|
|
179
|
+
"AWS::SNS::Topic": "Amazon Simple Notification Service",
|
|
180
|
+
"AWS::SQS::Queue": "Amazon Simple Queue Service",
|
|
181
|
+
"AWS::DynamoDB::Table": "Amazon DynamoDB",
|
|
182
|
+
"AWS::ElasticLoadBalancing::LoadBalancer": "Elastic Load Balancing",
|
|
183
|
+
"AWS::ElasticLoadBalancingV2::LoadBalancer::Application": "Elastic Load Balancing",
|
|
184
|
+
"AWS::ElasticLoadBalancingV2::LoadBalancer::Network": "Elastic Load Balancing",
|
|
185
|
+
"AWS::ElasticLoadBalancingV2::LoadBalancer::Gateway": "Elastic Load Balancing",
|
|
186
|
+
"AWS::CloudFormation::Stack": "AWS CloudFormation",
|
|
187
|
+
"AWS::ApiGateway::RestApi": "Amazon API Gateway",
|
|
188
|
+
"AWS::ApiGatewayV2::Api::HTTP": "Amazon API Gateway",
|
|
189
|
+
"AWS::ApiGatewayV2::Api::WebSocket": "Amazon API Gateway",
|
|
190
|
+
"AWS::Events::EventBus": "Amazon EventBridge",
|
|
191
|
+
"AWS::Events::Rule": "Amazon EventBridge",
|
|
192
|
+
"AWS::SecretsManager::Secret": "AWS Secrets Manager",
|
|
193
|
+
"AWS::KMS::Key": "AWS Key Management Service",
|
|
194
|
+
"AWS::SSM::Parameter": "AWS Systems Manager",
|
|
195
|
+
"AWS::SSM::Document": "AWS Systems Manager",
|
|
196
|
+
"AWS::Route53::HostedZone": "Amazon Route 53",
|
|
197
|
+
"AWS::ECS::Cluster": "Amazon EC2 Container Service",
|
|
198
|
+
"AWS::ECS::Service": "Amazon EC2 Container Service",
|
|
199
|
+
"AWS::ECS::TaskDefinition": "Amazon EC2 Container Service",
|
|
200
|
+
"AWS::StepFunctions::StateMachine": "AWS Step Functions",
|
|
201
|
+
"AWS::WAFv2::WebACL::Regional": "AWS WAF",
|
|
202
|
+
"AWS::WAFv2::WebACL::CloudFront": "AWS WAF",
|
|
203
|
+
"AWS::EKS::Cluster": "Amazon Elastic Kubernetes Service",
|
|
204
|
+
"AWS::EKS::Nodegroup": "Amazon Elastic Kubernetes Service",
|
|
205
|
+
"AWS::EKS::FargateProfile": "Amazon Elastic Kubernetes Service",
|
|
206
|
+
"AWS::CodePipeline::Pipeline": "AWS CodePipeline",
|
|
207
|
+
"AWS::CodeBuild::Project": "AWS CodeBuild",
|
|
208
|
+
"AWS::Backup::BackupPlan": "AWS Backup",
|
|
209
|
+
"AWS::Backup::BackupVault": "AWS Backup",
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
baseline_services = set()
|
|
213
|
+
|
|
214
|
+
# Get unique resource types from snapshot
|
|
215
|
+
resource_types = set()
|
|
216
|
+
for resource in snapshot.resources:
|
|
217
|
+
resource_types.add(resource.resource_type)
|
|
218
|
+
|
|
219
|
+
# Map to Cost Explorer service names
|
|
220
|
+
for resource_type in resource_types:
|
|
221
|
+
if resource_type in service_name_map:
|
|
222
|
+
baseline_services.add(service_name_map[resource_type])
|
|
223
|
+
|
|
224
|
+
logger.debug(f"Baseline services: {baseline_services}")
|
|
225
|
+
|
|
226
|
+
return baseline_services
|