aws-cost-calculator-cli 1.11.1__py3-none-any.whl → 2.3.1__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-cost-calculator-cli might be problematic. Click here for more details.
- {aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.3.1.dist-info}/METADATA +1 -1
- aws_cost_calculator_cli-2.3.1.dist-info/RECORD +16 -0
- {aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.3.1.dist-info}/WHEEL +1 -1
- cost_calculator/api_client.py +18 -21
- cost_calculator/cli.py +575 -308
- cost_calculator/dimensions.py +141 -0
- cost_calculator/executor.py +25 -12
- aws_cost_calculator_cli-1.11.1.dist-info/RECORD +0 -15
- {aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.3.1.dist-info}/entry_points.txt +0 -0
- {aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.3.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dimension mapping between CLI, Cost Explorer, and Athena CUR.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for translating dimension names across different backends.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Dimension mapping: CLI dimension -> (CE dimension, Athena column)
|
|
8
|
+
DIMENSION_MAP = {
|
|
9
|
+
'service': ('SERVICE', 'line_item_product_code'),
|
|
10
|
+
'account': ('LINKED_ACCOUNT', 'line_item_usage_account_id'),
|
|
11
|
+
'region': ('REGION', 'product_region'),
|
|
12
|
+
'usage_type': ('USAGE_TYPE', 'line_item_usage_type'),
|
|
13
|
+
'resource': (None, 'line_item_resource_id'), # Athena only
|
|
14
|
+
'instance_type': ('INSTANCE_TYPE', 'product_instance_type'),
|
|
15
|
+
'operation': ('OPERATION', 'line_item_operation'),
|
|
16
|
+
'availability_zone': ('AVAILABILITY_ZONE', 'product_availability_zone'),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_ce_dimension(cli_dimension):
|
|
21
|
+
"""
|
|
22
|
+
Get Cost Explorer dimension key for a CLI dimension.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
cli_dimension: CLI dimension name (e.g., 'service', 'account')
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: Cost Explorer dimension key (e.g., 'SERVICE', 'LINKED_ACCOUNT')
|
|
29
|
+
None: If dimension is not available in Cost Explorer
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValueError: If dimension is unknown
|
|
33
|
+
"""
|
|
34
|
+
if cli_dimension not in DIMENSION_MAP:
|
|
35
|
+
raise ValueError(f"Unknown dimension: {cli_dimension}")
|
|
36
|
+
|
|
37
|
+
ce_dim, _ = DIMENSION_MAP[cli_dimension]
|
|
38
|
+
return ce_dim
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_athena_column(cli_dimension):
|
|
42
|
+
"""
|
|
43
|
+
Get Athena CUR column name for a CLI dimension.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
cli_dimension: CLI dimension name (e.g., 'service', 'account')
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: Athena column name (e.g., 'line_item_product_code', 'line_item_usage_account_id')
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If dimension is unknown
|
|
53
|
+
"""
|
|
54
|
+
if cli_dimension not in DIMENSION_MAP:
|
|
55
|
+
raise ValueError(f"Unknown dimension: {cli_dimension}")
|
|
56
|
+
|
|
57
|
+
_, athena_col = DIMENSION_MAP[cli_dimension]
|
|
58
|
+
return athena_col
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_athena_only(cli_dimension):
|
|
62
|
+
"""
|
|
63
|
+
Check if a dimension is only available in Athena (not in Cost Explorer).
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
cli_dimension: CLI dimension name
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
bool: True if dimension requires Athena, False otherwise
|
|
70
|
+
"""
|
|
71
|
+
if cli_dimension not in DIMENSION_MAP:
|
|
72
|
+
raise ValueError(f"Unknown dimension: {cli_dimension}")
|
|
73
|
+
|
|
74
|
+
ce_dim, _ = DIMENSION_MAP[cli_dimension]
|
|
75
|
+
return ce_dim is None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_available_dimensions(backend='auto'):
|
|
79
|
+
"""
|
|
80
|
+
Get list of available dimensions for a given backend.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
backend: 'auto', 'ce', or 'athena'
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
list: List of available dimension names
|
|
87
|
+
"""
|
|
88
|
+
if backend == 'athena':
|
|
89
|
+
# All dimensions available in Athena
|
|
90
|
+
return list(DIMENSION_MAP.keys())
|
|
91
|
+
elif backend == 'ce':
|
|
92
|
+
# Only dimensions with CE mapping
|
|
93
|
+
return [dim for dim, (ce_dim, _) in DIMENSION_MAP.items() if ce_dim is not None]
|
|
94
|
+
else: # auto
|
|
95
|
+
# All dimensions (backend will be auto-selected)
|
|
96
|
+
return list(DIMENSION_MAP.keys())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def validate_dimension_backend(dimension, backend):
|
|
100
|
+
"""
|
|
101
|
+
Validate that a dimension is available for the specified backend.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
dimension: CLI dimension name
|
|
105
|
+
backend: 'auto', 'ce', or 'athena'
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
tuple: (is_valid, error_message)
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
>>> validate_dimension_backend('resource', 'ce')
|
|
112
|
+
(False, "Dimension 'resource' requires Athena backend (not available in Cost Explorer)")
|
|
113
|
+
|
|
114
|
+
>>> validate_dimension_backend('service', 'ce')
|
|
115
|
+
(True, None)
|
|
116
|
+
"""
|
|
117
|
+
if dimension not in DIMENSION_MAP:
|
|
118
|
+
return False, f"Unknown dimension: {dimension}"
|
|
119
|
+
|
|
120
|
+
if backend == 'ce' and is_athena_only(dimension):
|
|
121
|
+
return False, f"Dimension '{dimension}' requires Athena backend (not available in Cost Explorer)"
|
|
122
|
+
|
|
123
|
+
return True, None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# Human-readable dimension descriptions
|
|
127
|
+
DIMENSION_DESCRIPTIONS = {
|
|
128
|
+
'service': 'AWS Service (e.g., EC2, S3, RDS)',
|
|
129
|
+
'account': 'AWS Account ID',
|
|
130
|
+
'region': 'AWS Region (e.g., us-east-1, eu-west-1)',
|
|
131
|
+
'usage_type': 'Usage Type (e.g., BoxUsage:t3.micro)',
|
|
132
|
+
'resource': 'Resource ID/ARN (Athena only)',
|
|
133
|
+
'instance_type': 'Instance Type (e.g., t3.micro, m5.large)',
|
|
134
|
+
'operation': 'Operation (e.g., RunInstances, CreateBucket)',
|
|
135
|
+
'availability_zone': 'Availability Zone (e.g., us-east-1a)',
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_dimension_description(dimension):
|
|
140
|
+
"""Get human-readable description for a dimension."""
|
|
141
|
+
return DIMENSION_DESCRIPTIONS.get(dimension, dimension)
|
cost_calculator/executor.py
CHANGED
|
@@ -92,12 +92,16 @@ def get_credentials_dict(config):
|
|
|
92
92
|
|
|
93
93
|
def execute_trends(config, weeks):
|
|
94
94
|
"""
|
|
95
|
-
Execute trends analysis via API
|
|
95
|
+
Execute trends analysis via API.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
config: Profile configuration
|
|
99
|
+
weeks: Number of weeks to analyze
|
|
96
100
|
|
|
97
101
|
Returns:
|
|
98
102
|
dict: trends data
|
|
99
103
|
"""
|
|
100
|
-
|
|
104
|
+
profile_name = config.get('profile_name', config.get('name'))
|
|
101
105
|
|
|
102
106
|
if not is_api_configured():
|
|
103
107
|
raise Exception(
|
|
@@ -110,7 +114,7 @@ def execute_trends(config, weeks):
|
|
|
110
114
|
credentials = get_credentials_dict(config)
|
|
111
115
|
if not credentials:
|
|
112
116
|
raise Exception("Failed to get AWS credentials. Check your AWS SSO session.")
|
|
113
|
-
return call_lambda_api('trends', credentials,
|
|
117
|
+
return call_lambda_api('trends', credentials, profile=profile_name, weeks=weeks)
|
|
114
118
|
|
|
115
119
|
|
|
116
120
|
def execute_monthly(config, months):
|
|
@@ -120,7 +124,7 @@ def execute_monthly(config, months):
|
|
|
120
124
|
Returns:
|
|
121
125
|
dict: monthly data
|
|
122
126
|
"""
|
|
123
|
-
|
|
127
|
+
profile_name = config.get('profile_name', config.get('name'))
|
|
124
128
|
|
|
125
129
|
if not is_api_configured():
|
|
126
130
|
raise Exception(
|
|
@@ -133,10 +137,10 @@ def execute_monthly(config, months):
|
|
|
133
137
|
credentials = get_credentials_dict(config)
|
|
134
138
|
if not credentials:
|
|
135
139
|
raise Exception("Failed to get AWS credentials. Check your AWS SSO session.")
|
|
136
|
-
return call_lambda_api('monthly', credentials,
|
|
140
|
+
return call_lambda_api('monthly', credentials, profile=profile_name, months=months)
|
|
137
141
|
|
|
138
142
|
|
|
139
|
-
def execute_drill(config, weeks, service_filter=None, account_filter=None, usage_type_filter=None, resources=False):
|
|
143
|
+
def execute_drill(config, weeks, service_filter=None, account_filter=None, usage_type_filter=None, resources=False, dimension=None, backend='auto'):
|
|
140
144
|
"""
|
|
141
145
|
Execute drill-down analysis via API.
|
|
142
146
|
|
|
@@ -147,11 +151,13 @@ def execute_drill(config, weeks, service_filter=None, account_filter=None, usage
|
|
|
147
151
|
account_filter: Optional account ID filter
|
|
148
152
|
usage_type_filter: Optional usage type filter
|
|
149
153
|
resources: If True, query CUR for resource-level details
|
|
154
|
+
dimension: Dimension to analyze by (service, account, region, usage_type, resource, etc.)
|
|
155
|
+
backend: Backend to use ('auto', 'ce', 'athena')
|
|
150
156
|
|
|
151
157
|
Returns:
|
|
152
158
|
dict: drill data or resource data
|
|
153
159
|
"""
|
|
154
|
-
|
|
160
|
+
profile_name = config.get('profile_name', config.get('name'))
|
|
155
161
|
|
|
156
162
|
if not is_api_configured():
|
|
157
163
|
raise Exception(
|
|
@@ -172,12 +178,16 @@ def execute_drill(config, weeks, service_filter=None, account_filter=None, usage
|
|
|
172
178
|
kwargs['account'] = account_filter
|
|
173
179
|
if usage_type_filter:
|
|
174
180
|
kwargs['usage_type'] = usage_type_filter
|
|
181
|
+
if dimension:
|
|
182
|
+
kwargs['dimension'] = dimension
|
|
183
|
+
if backend and backend != 'auto':
|
|
184
|
+
kwargs['backend'] = backend
|
|
175
185
|
if resources:
|
|
176
|
-
if not service_filter:
|
|
177
|
-
raise click.ClickException("--service is required when using --resources flag")
|
|
186
|
+
if not service_filter and not dimension:
|
|
187
|
+
raise click.ClickException("--service or --dimension is required when using --resources flag")
|
|
178
188
|
kwargs['resources'] = True
|
|
179
189
|
|
|
180
|
-
return call_lambda_api('drill', credentials,
|
|
190
|
+
return call_lambda_api('drill', credentials, profile=profile_name, **kwargs)
|
|
181
191
|
|
|
182
192
|
|
|
183
193
|
def execute_analyze(config, weeks, analysis_type, pattern=None, min_cost=None):
|
|
@@ -227,8 +237,11 @@ def execute_profile_operation(operation, profile_name=None, accounts=None, descr
|
|
|
227
237
|
|
|
228
238
|
api_secret = os.environ.get('COST_API_SECRET', '')
|
|
229
239
|
|
|
230
|
-
# Use profiles endpoint (
|
|
231
|
-
url =
|
|
240
|
+
# Use profiles endpoint (can be overridden via environment variable)
|
|
241
|
+
url = os.environ.get(
|
|
242
|
+
'COST_CALCULATOR_PROFILES_URL',
|
|
243
|
+
'https://64g7jq7sjygec2zmll5lsghrpi0txrzo.lambda-url.us-east-1.on.aws/'
|
|
244
|
+
)
|
|
232
245
|
|
|
233
246
|
payload = {'operation': operation}
|
|
234
247
|
if profile_name:
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
aws_cost_calculator_cli-1.11.1.dist-info/licenses/LICENSE,sha256=cYtmQZHNGGTXOtg3T7LHDRneleaH0dHXHfxFV3WR50Y,1079
|
|
2
|
-
cost_calculator/__init__.py,sha256=PJeIqvWh5AYJVrJxPPkI4pJnAt37rIjasrNS0I87kaM,52
|
|
3
|
-
cost_calculator/api_client.py,sha256=4ZI2XcGIN3FBeQqb7xOxQ91kCoeM43-rExiOELXoKBQ,2485
|
|
4
|
-
cost_calculator/cli.py,sha256=OudMcAitmJfWgZKxjlHtfXnx2SlKrYh6FzPTkHabJlI,77975
|
|
5
|
-
cost_calculator/cur.py,sha256=QaZ_nyDSw5_cti-h5Ho6eYLbqzY5TWoub24DpyzIiSs,9502
|
|
6
|
-
cost_calculator/drill.py,sha256=hGi-prLgZDvNMMICQc4fl3LenM7YaZ3To_Ei4LKwrdc,10543
|
|
7
|
-
cost_calculator/executor.py,sha256=yZTCUgJc1OpB892O3mq9ZA0Yekc7N-HvaW8xLFyrXjo,8681
|
|
8
|
-
cost_calculator/forensics.py,sha256=uhRo3I_zOeMEaBENHfgq65URga31W0Z4vzS2UN6VmTY,12819
|
|
9
|
-
cost_calculator/monthly.py,sha256=6k9F8S7djhX1wGV3-T1MZP7CvWbbfhSTEaddwCfVu5M,7932
|
|
10
|
-
cost_calculator/trends.py,sha256=k_s4ylBX50sqoiM_fwepi58HW01zz767FMJhQUPDznk,12246
|
|
11
|
-
aws_cost_calculator_cli-1.11.1.dist-info/METADATA,sha256=d-AHFprwq0-lD-D3ilWa7IGBxOszOdMqP7nJl5J0gyA,11979
|
|
12
|
-
aws_cost_calculator_cli-1.11.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
13
|
-
aws_cost_calculator_cli-1.11.1.dist-info/entry_points.txt,sha256=_5Qy4EcHbYVYrdgOu1E48faMHb9fLUl5VJ3djDHuJBo,47
|
|
14
|
-
aws_cost_calculator_cli-1.11.1.dist-info/top_level.txt,sha256=PRwGPPlNqASfyhGHDjSfyl4SXeE7GF3OVTu1tY1Uqyc,16
|
|
15
|
-
aws_cost_calculator_cli-1.11.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.3.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|