aws-cost-calculator-cli 1.4.0__py3-none-any.whl → 1.5.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.
- {aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/METADATA +12 -9
- aws_cost_calculator_cli-1.5.2.dist-info/RECORD +13 -0
- cost_calculator/api_client.py +13 -34
- cost_calculator/cli.py +114 -0
- cost_calculator/executor.py +93 -11
- aws_cost_calculator_cli-1.4.0.dist-info/RECORD +0 -13
- {aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/WHEEL +0 -0
- {aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/entry_points.txt +0 -0
- {aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/licenses/LICENSE +0 -0
- {aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/top_level.txt +0 -0
{aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-cost-calculator-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: AWS Cost Calculator CLI - Calculate daily and annual AWS costs across multiple accounts
|
|
5
5
|
Home-page: https://github.com/yourusername/cost-calculator
|
|
6
6
|
Author: Cost Optimization Team
|
|
@@ -242,14 +242,17 @@ The `drill` command allows you to investigate cost changes at different levels o
|
|
|
242
242
|
3. **Drill deeper:** `cc drill --service "EC2 - Other" --account 123` → See usage types
|
|
243
243
|
|
|
244
244
|
**Features:**
|
|
245
|
-
- **
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
- **
|
|
251
|
-
- **
|
|
252
|
-
- **
|
|
245
|
+
- **Week-over-week cost analysis**: Compare costs between consecutive weeks
|
|
246
|
+
- **Month-over-month cost analysis**: Compare costs between consecutive months
|
|
247
|
+
- **Drill-down analysis**: Analyze costs by service, account, or usage type
|
|
248
|
+
- **Pandas aggregations**: Time series analysis with sum, avg, std across all weeks
|
|
249
|
+
- **Volatility detection**: Identify services with high cost variability and outliers
|
|
250
|
+
- **Trend detection**: Auto-detect increasing/decreasing cost patterns
|
|
251
|
+
- **Search & filter**: Find services by pattern or cost threshold
|
|
252
|
+
- **Profile management**: CRUD operations for account profiles in DynamoDB
|
|
253
|
+
- **Markdown reports**: Generate formatted reports
|
|
254
|
+
- **JSON output**: Machine-readable output for automation
|
|
255
|
+
- **Lambda API backend**: Serverless backend with pandas/numpy support
|
|
253
256
|
|
|
254
257
|
Example output:
|
|
255
258
|
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
aws_cost_calculator_cli-1.5.2.dist-info/licenses/LICENSE,sha256=cYtmQZHNGGTXOtg3T7LHDRneleaH0dHXHfxFV3WR50Y,1079
|
|
2
|
+
cost_calculator/__init__.py,sha256=PJeIqvWh5AYJVrJxPPkI4pJnAt37rIjasrNS0I87kaM,52
|
|
3
|
+
cost_calculator/api_client.py,sha256=LUzQmveDF0X9MqAyThp9mbSzJzkOO73Pk4F7IEJjASU,2353
|
|
4
|
+
cost_calculator/cli.py,sha256=ufK28divdvrceEryWd8cCWjvG5pT2owaqprskX2epeQ,32589
|
|
5
|
+
cost_calculator/drill.py,sha256=hGi-prLgZDvNMMICQc4fl3LenM7YaZ3To_Ei4LKwrdc,10543
|
|
6
|
+
cost_calculator/executor.py,sha256=tVyyBtXIj9OPyG-xQj8CUmyFjDhb9IVK639360dUZDc,8076
|
|
7
|
+
cost_calculator/monthly.py,sha256=6k9F8S7djhX1wGV3-T1MZP7CvWbbfhSTEaddwCfVu5M,7932
|
|
8
|
+
cost_calculator/trends.py,sha256=k_s4ylBX50sqoiM_fwepi58HW01zz767FMJhQUPDznk,12246
|
|
9
|
+
aws_cost_calculator_cli-1.5.2.dist-info/METADATA,sha256=0wxy-jgVC-paubGHN87mDObETGr_u9qU4ZIP3xV49hM,8176
|
|
10
|
+
aws_cost_calculator_cli-1.5.2.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
11
|
+
aws_cost_calculator_cli-1.5.2.dist-info/entry_points.txt,sha256=_5Qy4EcHbYVYrdgOu1E48faMHb9fLUl5VJ3djDHuJBo,47
|
|
12
|
+
aws_cost_calculator_cli-1.5.2.dist-info/top_level.txt,sha256=PRwGPPlNqASfyhGHDjSfyl4SXeE7GF3OVTu1tY1Uqyc,16
|
|
13
|
+
aws_cost_calculator_cli-1.5.2.dist-info/RECORD,,
|
cost_calculator/api_client.py
CHANGED
|
@@ -10,33 +10,15 @@ from pathlib import Path
|
|
|
10
10
|
|
|
11
11
|
def get_api_config():
|
|
12
12
|
"""
|
|
13
|
-
Get API configuration
|
|
13
|
+
Get API configuration.
|
|
14
14
|
|
|
15
15
|
Returns:
|
|
16
|
-
dict
|
|
16
|
+
dict: API configuration with api_secret, or None if not configured
|
|
17
17
|
"""
|
|
18
|
-
|
|
19
|
-
base_url = os.environ.get('COST_API_URL')
|
|
20
|
-
api_secret = os.environ.get('COST_API_SECRET')
|
|
18
|
+
api_secret = os.environ.get('COST_API_SECRET', '')
|
|
21
19
|
|
|
22
|
-
if
|
|
23
|
-
return {
|
|
24
|
-
'base_url': base_url.rstrip('/'),
|
|
25
|
-
'api_secret': api_secret
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
# Try config file
|
|
29
|
-
config_dir = Path.home() / '.config' / 'cost-calculator'
|
|
30
|
-
api_config_file = config_dir / 'api_config.json'
|
|
31
|
-
|
|
32
|
-
if api_config_file.exists():
|
|
33
|
-
with open(api_config_file, 'r') as f:
|
|
34
|
-
config = json.load(f)
|
|
35
|
-
if 'base_url' in config and 'api_secret' in config:
|
|
36
|
-
return {
|
|
37
|
-
'base_url': config['base_url'].rstrip('/'),
|
|
38
|
-
'api_secret': config['api_secret']
|
|
39
|
-
}
|
|
20
|
+
if api_secret:
|
|
21
|
+
return {'api_secret': api_secret}
|
|
40
22
|
|
|
41
23
|
return None
|
|
42
24
|
|
|
@@ -60,21 +42,18 @@ def call_lambda_api(endpoint, credentials, accounts, **kwargs):
|
|
|
60
42
|
api_config = get_api_config()
|
|
61
43
|
|
|
62
44
|
if not api_config:
|
|
63
|
-
raise Exception("API not configured. Set
|
|
45
|
+
raise Exception("API not configured. Set COST_API_SECRET environment variable.")
|
|
64
46
|
|
|
65
|
-
# Map endpoint names to URLs
|
|
47
|
+
# Map endpoint names to Lambda URLs
|
|
66
48
|
endpoint_urls = {
|
|
67
|
-
'trends':
|
|
68
|
-
'monthly':
|
|
69
|
-
'drill':
|
|
49
|
+
'trends': 'https://pq3mqntc6vuwi4zw5flulsoleq0yiqtl.lambda-url.us-east-1.on.aws/',
|
|
50
|
+
'monthly': 'https://6aueebodw6q4zdeu3aaexb6tle0fqhhr.lambda-url.us-east-1.on.aws/',
|
|
51
|
+
'drill': 'https://3ncm2gzxrsyptrhud3ua3x5lju0akvsr.lambda-url.us-east-1.on.aws/',
|
|
52
|
+
'analyze': 'https://y6npmidtxwzg62nrqzkbacfs5q0edwgs.lambda-url.us-east-1.on.aws/',
|
|
53
|
+
'profiles': 'https://64g7jq7sjygec2zmll5lsghrpi0txrzo.lambda-url.us-east-1.on.aws/'
|
|
70
54
|
}
|
|
71
55
|
|
|
72
|
-
|
|
73
|
-
if '/trends' not in api_config['base_url']:
|
|
74
|
-
# Base URL is the function URL itself
|
|
75
|
-
url = api_config['base_url']
|
|
76
|
-
else:
|
|
77
|
-
url = endpoint_urls.get(endpoint)
|
|
56
|
+
url = endpoint_urls.get(endpoint)
|
|
78
57
|
|
|
79
58
|
if not url:
|
|
80
59
|
raise Exception(f"Unknown endpoint: {endpoint}")
|
cost_calculator/cli.py
CHANGED
|
@@ -757,5 +757,119 @@ def drill(profile, weeks, service, account, usage_type, output, json_output):
|
|
|
757
757
|
click.echo("")
|
|
758
758
|
|
|
759
759
|
|
|
760
|
+
@cli.command()
|
|
761
|
+
@click.option('--profile', required=True, help='Profile name')
|
|
762
|
+
@click.option('--type', 'analysis_type', default='summary',
|
|
763
|
+
type=click.Choice(['summary', 'volatility', 'trends', 'search']),
|
|
764
|
+
help='Analysis type')
|
|
765
|
+
@click.option('--weeks', default=12, help='Number of weeks (default: 12)')
|
|
766
|
+
@click.option('--pattern', help='Service search pattern (for search type)')
|
|
767
|
+
@click.option('--min-cost', type=float, help='Minimum cost filter (for search type)')
|
|
768
|
+
@click.option('--json-output', is_flag=True, help='Output as JSON')
|
|
769
|
+
def analyze(profile, analysis_type, weeks, pattern, min_cost, json_output):
|
|
770
|
+
"""Perform pandas-based analysis (aggregations, volatility, trends, search)"""
|
|
771
|
+
|
|
772
|
+
config = load_profile(profile)
|
|
773
|
+
|
|
774
|
+
if not json_output:
|
|
775
|
+
click.echo(f"Running {analysis_type} analysis for {weeks} weeks...")
|
|
776
|
+
|
|
777
|
+
from cost_calculator.executor import execute_analyze
|
|
778
|
+
result = execute_analyze(config, weeks, analysis_type, pattern, min_cost)
|
|
779
|
+
|
|
780
|
+
if json_output:
|
|
781
|
+
import json
|
|
782
|
+
click.echo(json.dumps(result, indent=2, default=str))
|
|
783
|
+
else:
|
|
784
|
+
# Format output based on type
|
|
785
|
+
if analysis_type == 'summary':
|
|
786
|
+
click.echo(f"\n📊 Summary ({result.get('total_services', 0)} services)")
|
|
787
|
+
click.echo(f"Weeks analyzed: {result.get('weeks_analyzed', 0)}")
|
|
788
|
+
click.echo(f"\nTop 10 Services (by total change):")
|
|
789
|
+
for svc in result.get('services', [])[:10]:
|
|
790
|
+
click.echo(f" {svc['service']}")
|
|
791
|
+
click.echo(f" Total: ${svc['change_sum']:,.2f}")
|
|
792
|
+
click.echo(f" Average: ${svc['change_mean']:,.2f}")
|
|
793
|
+
click.echo(f" Volatility: {svc['volatility']:.3f}")
|
|
794
|
+
|
|
795
|
+
elif analysis_type == 'volatility':
|
|
796
|
+
click.echo(f"\n📈 High Volatility Services:")
|
|
797
|
+
for svc in result.get('high_volatility_services', [])[:10]:
|
|
798
|
+
click.echo(f" {svc['service']}: CV={svc['coefficient_of_variation']:.3f}")
|
|
799
|
+
|
|
800
|
+
outliers = result.get('outliers', [])
|
|
801
|
+
if outliers:
|
|
802
|
+
click.echo(f"\n⚠️ Outliers ({len(outliers)}):")
|
|
803
|
+
for o in outliers[:5]:
|
|
804
|
+
click.echo(f" {o['service']} ({o['week']}): ${o['change']:,.2f} (z={o['z_score']:.2f})")
|
|
805
|
+
|
|
806
|
+
elif analysis_type == 'trends':
|
|
807
|
+
inc = result.get('increasing_trends', [])
|
|
808
|
+
dec = result.get('decreasing_trends', [])
|
|
809
|
+
|
|
810
|
+
click.echo(f"\n📈 Increasing Trends ({len(inc)}):")
|
|
811
|
+
for t in inc[:5]:
|
|
812
|
+
click.echo(f" {t['service']}: ${t['avg_change']:,.2f}/week")
|
|
813
|
+
|
|
814
|
+
click.echo(f"\n📉 Decreasing Trends ({len(dec)}):")
|
|
815
|
+
for t in dec[:5]:
|
|
816
|
+
click.echo(f" {t['service']}: ${t['avg_change']:,.2f}/week")
|
|
817
|
+
|
|
818
|
+
elif analysis_type == 'search':
|
|
819
|
+
matches = result.get('matches', [])
|
|
820
|
+
click.echo(f"\n🔍 Search Results ({len(matches)} matches)")
|
|
821
|
+
if pattern:
|
|
822
|
+
click.echo(f"Pattern: {pattern}")
|
|
823
|
+
if min_cost:
|
|
824
|
+
click.echo(f"Min cost: ${min_cost:,.2f}")
|
|
825
|
+
|
|
826
|
+
for m in matches[:20]:
|
|
827
|
+
click.echo(f" {m['service']}: ${m['curr_cost']:,.2f}")
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
@cli.command()
|
|
831
|
+
@click.argument('operation', type=click.Choice(['list', 'get', 'create', 'update', 'delete']))
|
|
832
|
+
@click.option('--name', help='Profile name')
|
|
833
|
+
@click.option('--accounts', help='Comma-separated account IDs')
|
|
834
|
+
@click.option('--description', help='Profile description')
|
|
835
|
+
def profile(operation, name, accounts, description):
|
|
836
|
+
"""Manage profiles (CRUD operations)"""
|
|
837
|
+
|
|
838
|
+
from cost_calculator.executor import execute_profile_operation
|
|
839
|
+
|
|
840
|
+
# Parse accounts if provided
|
|
841
|
+
account_list = None
|
|
842
|
+
if accounts:
|
|
843
|
+
account_list = [a.strip() for a in accounts.split(',')]
|
|
844
|
+
|
|
845
|
+
result = execute_profile_operation(
|
|
846
|
+
operation=operation,
|
|
847
|
+
profile_name=name,
|
|
848
|
+
accounts=account_list,
|
|
849
|
+
description=description
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
if operation == 'list':
|
|
853
|
+
profiles = result.get('profiles', [])
|
|
854
|
+
click.echo(f"\n📋 Profiles ({len(profiles)}):")
|
|
855
|
+
for p in profiles:
|
|
856
|
+
click.echo(f" {p['profile_name']}: {len(p.get('accounts', []))} accounts")
|
|
857
|
+
if p.get('description'):
|
|
858
|
+
click.echo(f" {p['description']}")
|
|
859
|
+
|
|
860
|
+
elif operation == 'get':
|
|
861
|
+
profile_data = result.get('profile', {})
|
|
862
|
+
click.echo(f"\n📋 Profile: {profile_data.get('profile_name')}")
|
|
863
|
+
click.echo(f"Accounts: {len(profile_data.get('accounts', []))}")
|
|
864
|
+
if profile_data.get('description'):
|
|
865
|
+
click.echo(f"Description: {profile_data['description']}")
|
|
866
|
+
click.echo(f"\nAccounts:")
|
|
867
|
+
for acc in profile_data.get('accounts', []):
|
|
868
|
+
click.echo(f" {acc}")
|
|
869
|
+
|
|
870
|
+
else:
|
|
871
|
+
click.echo(result.get('message', 'Operation completed'))
|
|
872
|
+
|
|
873
|
+
|
|
760
874
|
if __name__ == '__main__':
|
|
761
875
|
cli()
|
cost_calculator/executor.py
CHANGED
|
@@ -11,22 +11,33 @@ def get_credentials_dict(config):
|
|
|
11
11
|
Extract credentials from config in format needed for API.
|
|
12
12
|
|
|
13
13
|
Returns:
|
|
14
|
-
dict with access_key, secret_key, session_token
|
|
14
|
+
dict with access_key, secret_key, session_token, or None if profile is 'dummy'
|
|
15
15
|
"""
|
|
16
16
|
if 'aws_profile' in config:
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
frozen_creds = credentials.get_frozen_credentials()
|
|
17
|
+
# Skip credential loading for dummy profile (API-only mode)
|
|
18
|
+
if config['aws_profile'] == 'dummy':
|
|
19
|
+
return None
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
# Get temporary credentials from SSO session
|
|
22
|
+
try:
|
|
23
|
+
session = boto3.Session(profile_name=config['aws_profile'])
|
|
24
|
+
credentials = session.get_credentials()
|
|
25
|
+
frozen_creds = credentials.get_frozen_credentials()
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
'access_key': frozen_creds.access_key,
|
|
29
|
+
'secret_key': frozen_creds.secret_key,
|
|
30
|
+
'session_token': frozen_creds.token
|
|
31
|
+
}
|
|
32
|
+
except Exception:
|
|
33
|
+
# If profile not found, return None (API will handle)
|
|
34
|
+
return None
|
|
27
35
|
else:
|
|
28
36
|
# Use static credentials
|
|
29
|
-
creds = config
|
|
37
|
+
creds = config.get('credentials', {})
|
|
38
|
+
if not creds:
|
|
39
|
+
return None
|
|
40
|
+
|
|
30
41
|
result = {
|
|
31
42
|
'access_key': creds['aws_access_key_id'],
|
|
32
43
|
'secret_key': creds['aws_secret_access_key']
|
|
@@ -157,3 +168,74 @@ def execute_drill(config, weeks, service_filter=None, account_filter=None, usage
|
|
|
157
168
|
account_filter=account_filter,
|
|
158
169
|
usage_type_filter=usage_type_filter
|
|
159
170
|
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def execute_analyze(config, weeks, analysis_type, pattern=None, min_cost=None):
|
|
174
|
+
"""
|
|
175
|
+
Execute pandas-based analysis via API.
|
|
176
|
+
Note: This only works via API (requires pandas layer).
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
dict: analysis results
|
|
180
|
+
"""
|
|
181
|
+
accounts = config['accounts']
|
|
182
|
+
|
|
183
|
+
if not is_api_configured():
|
|
184
|
+
raise click.ClickException(
|
|
185
|
+
"Analyze command requires API configuration.\n"
|
|
186
|
+
"Set COST_API_SECRET environment variable."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
credentials = get_credentials_dict(config)
|
|
190
|
+
kwargs = {'weeks': weeks, 'type': analysis_type}
|
|
191
|
+
|
|
192
|
+
if pattern:
|
|
193
|
+
kwargs['pattern'] = pattern
|
|
194
|
+
if min_cost:
|
|
195
|
+
kwargs['min_cost'] = min_cost
|
|
196
|
+
|
|
197
|
+
return call_lambda_api('analyze', credentials, accounts, **kwargs)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def execute_profile_operation(operation, profile_name=None, accounts=None, description=None):
|
|
201
|
+
"""
|
|
202
|
+
Execute profile CRUD operations via API.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
dict: operation result
|
|
206
|
+
"""
|
|
207
|
+
if not is_api_configured():
|
|
208
|
+
raise click.ClickException(
|
|
209
|
+
"Profile commands require API configuration.\n"
|
|
210
|
+
"Set COST_API_SECRET environment variable."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Profile operations don't need AWS credentials, just API secret
|
|
214
|
+
import os
|
|
215
|
+
import requests
|
|
216
|
+
import json
|
|
217
|
+
|
|
218
|
+
api_secret = os.environ.get('COST_API_SECRET', '')
|
|
219
|
+
|
|
220
|
+
# Use profiles endpoint (hardcoded URL)
|
|
221
|
+
url = 'https://64g7jq7sjygec2zmll5lsghrpi0txrzo.lambda-url.us-east-1.on.aws/'
|
|
222
|
+
|
|
223
|
+
payload = {'operation': operation}
|
|
224
|
+
if profile_name:
|
|
225
|
+
payload['profile_name'] = profile_name
|
|
226
|
+
if accounts:
|
|
227
|
+
payload['accounts'] = accounts
|
|
228
|
+
if description:
|
|
229
|
+
payload['description'] = description
|
|
230
|
+
|
|
231
|
+
headers = {
|
|
232
|
+
'X-API-Secret': api_secret,
|
|
233
|
+
'Content-Type': 'application/json'
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
response = requests.post(url, headers=headers, json=payload, timeout=60)
|
|
237
|
+
|
|
238
|
+
if response.status_code != 200:
|
|
239
|
+
raise Exception(f"API call failed: {response.status_code} - {response.text}")
|
|
240
|
+
|
|
241
|
+
return response.json()
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
aws_cost_calculator_cli-1.4.0.dist-info/licenses/LICENSE,sha256=cYtmQZHNGGTXOtg3T7LHDRneleaH0dHXHfxFV3WR50Y,1079
|
|
2
|
-
cost_calculator/__init__.py,sha256=PJeIqvWh5AYJVrJxPPkI4pJnAt37rIjasrNS0I87kaM,52
|
|
3
|
-
cost_calculator/api_client.py,sha256=pSH2U0tOghDd3fisPcKqEJG3TghQYZi18HZmianHd6Y,2932
|
|
4
|
-
cost_calculator/cli.py,sha256=qK6WQcAM5W15NciGOUtPpqcEvgyua5n5GRRysB4NPWw,27631
|
|
5
|
-
cost_calculator/drill.py,sha256=hGi-prLgZDvNMMICQc4fl3LenM7YaZ3To_Ei4LKwrdc,10543
|
|
6
|
-
cost_calculator/executor.py,sha256=RZ45GuA8tzKqj_pJaZ-BVSc8xbxxWTi4yCGcKqvNsUg,5601
|
|
7
|
-
cost_calculator/monthly.py,sha256=6k9F8S7djhX1wGV3-T1MZP7CvWbbfhSTEaddwCfVu5M,7932
|
|
8
|
-
cost_calculator/trends.py,sha256=k_s4ylBX50sqoiM_fwepi58HW01zz767FMJhQUPDznk,12246
|
|
9
|
-
aws_cost_calculator_cli-1.4.0.dist-info/METADATA,sha256=LJkphxt8op0vOE_UJv-rGYRI4obstONupdOS8tYHGKg,7793
|
|
10
|
-
aws_cost_calculator_cli-1.4.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
11
|
-
aws_cost_calculator_cli-1.4.0.dist-info/entry_points.txt,sha256=_5Qy4EcHbYVYrdgOu1E48faMHb9fLUl5VJ3djDHuJBo,47
|
|
12
|
-
aws_cost_calculator_cli-1.4.0.dist-info/top_level.txt,sha256=PRwGPPlNqASfyhGHDjSfyl4SXeE7GF3OVTu1tY1Uqyc,16
|
|
13
|
-
aws_cost_calculator_cli-1.4.0.dist-info/RECORD,,
|
|
File without changes
|
{aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{aws_cost_calculator_cli-1.4.0.dist-info → aws_cost_calculator_cli-1.5.2.dist-info}/top_level.txt
RENAMED
|
File without changes
|