aws-cost-calculator-cli 1.11.1__py3-none-any.whl → 2.0.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.0.1.dist-info}/METADATA +1 -1
- aws_cost_calculator_cli-2.0.1.dist-info/RECORD +15 -0
- cost_calculator/cli.py +152 -168
- 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.0.1.dist-info}/WHEEL +0 -0
- {aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.0.1.dist-info}/entry_points.txt +0 -0
- {aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.0.1.dist-info}/top_level.txt +0 -0
{aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.0.1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-cost-calculator-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: AWS Cost Calculator CLI - Calculate daily and annual AWS costs across multiple accounts
|
|
5
5
|
Home-page: https://github.com/trilogy-group/aws-cost-calculator
|
|
6
6
|
Author: Cost Optimization Team
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
aws_cost_calculator_cli-2.0.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=7j_jK2PKOchF9oereH6GtDtTjDnlhiTaVuS5FFdeL6U,76371
|
|
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-2.0.1.dist-info/METADATA,sha256=vC_DLqKCR82e1D9uideemfOE6AihjB57fR4wS1F-WWc,11978
|
|
12
|
+
aws_cost_calculator_cli-2.0.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
13
|
+
aws_cost_calculator_cli-2.0.1.dist-info/entry_points.txt,sha256=_5Qy4EcHbYVYrdgOu1E48faMHb9fLUl5VJ3djDHuJBo,47
|
|
14
|
+
aws_cost_calculator_cli-2.0.1.dist-info/top_level.txt,sha256=PRwGPPlNqASfyhGHDjSfyl4SXeE7GF3OVTu1tY1Uqyc,16
|
|
15
|
+
aws_cost_calculator_cli-2.0.1.dist-info/RECORD,,
|
cost_calculator/cli.py
CHANGED
|
@@ -67,127 +67,107 @@ def apply_auth_options(config, sso=None, access_key_id=None, secret_access_key=N
|
|
|
67
67
|
return config
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def
|
|
71
|
-
"""
|
|
70
|
+
def get_api_secret():
|
|
71
|
+
"""Get API secret from config file or environment variable"""
|
|
72
72
|
import os
|
|
73
|
-
import requests
|
|
74
|
-
|
|
75
|
-
config_dir = Path.home() / '.config' / 'cost-calculator'
|
|
76
|
-
config_file = config_dir / 'profiles.json'
|
|
77
|
-
creds_file = config_dir / 'credentials.json'
|
|
78
73
|
|
|
79
|
-
#
|
|
74
|
+
# Check environment variable first
|
|
80
75
|
api_secret = os.environ.get('COST_API_SECRET')
|
|
81
76
|
if api_secret:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
timeout=10
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
if response.status_code == 200:
|
|
91
|
-
response_data = response.json()
|
|
92
|
-
# API returns {"profile": {...}} wrapper
|
|
93
|
-
profile_data = response_data.get('profile', response_data)
|
|
94
|
-
profile = {'accounts': profile_data['accounts']}
|
|
95
|
-
|
|
96
|
-
# If profile has aws_profile field, use it
|
|
97
|
-
if 'aws_profile' in profile_data:
|
|
98
|
-
profile['aws_profile'] = profile_data['aws_profile']
|
|
99
|
-
# Check for AWS_PROFILE environment variable (SSO support)
|
|
100
|
-
elif os.environ.get('AWS_PROFILE'):
|
|
101
|
-
profile['aws_profile'] = os.environ['AWS_PROFILE']
|
|
102
|
-
# Use environment credentials
|
|
103
|
-
elif os.environ.get('AWS_ACCESS_KEY_ID'):
|
|
104
|
-
profile['credentials'] = {
|
|
105
|
-
'aws_access_key_id': os.environ['AWS_ACCESS_KEY_ID'],
|
|
106
|
-
'aws_secret_access_key': os.environ['AWS_SECRET_ACCESS_KEY'],
|
|
107
|
-
'aws_session_token': os.environ.get('AWS_SESSION_TOKEN')
|
|
108
|
-
}
|
|
109
|
-
else:
|
|
110
|
-
# Try to find a matching AWS profile by name
|
|
111
|
-
# This allows "khoros" profile to work with "khoros_umbrella" AWS profile
|
|
112
|
-
import subprocess
|
|
113
|
-
try:
|
|
114
|
-
result = subprocess.run(
|
|
115
|
-
['aws', 'configure', 'list-profiles'],
|
|
116
|
-
capture_output=True,
|
|
117
|
-
text=True,
|
|
118
|
-
timeout=5
|
|
119
|
-
)
|
|
120
|
-
if result.returncode == 0:
|
|
121
|
-
available_profiles = result.stdout.strip().split('\n')
|
|
122
|
-
# Try exact match first
|
|
123
|
-
if profile_name in available_profiles:
|
|
124
|
-
profile['aws_profile'] = profile_name
|
|
125
|
-
# Try with common suffixes
|
|
126
|
-
elif f"{profile_name}_umbrella" in available_profiles:
|
|
127
|
-
profile['aws_profile'] = f"{profile_name}_umbrella"
|
|
128
|
-
elif f"{profile_name}-umbrella" in available_profiles:
|
|
129
|
-
profile['aws_profile'] = f"{profile_name}-umbrella"
|
|
130
|
-
elif f"{profile_name}_prod" in available_profiles:
|
|
131
|
-
profile['aws_profile'] = f"{profile_name}_prod"
|
|
132
|
-
# If no match found, leave it unset - user must provide --sso
|
|
133
|
-
except:
|
|
134
|
-
# If we can't list profiles, leave it unset - user must provide --sso
|
|
135
|
-
pass
|
|
136
|
-
|
|
137
|
-
return profile
|
|
138
|
-
else:
|
|
139
|
-
raise click.ClickException(
|
|
140
|
-
f"Profile '{profile_name}' not found in DynamoDB.\n"
|
|
141
|
-
f"Run: cc profile create --name {profile_name} --accounts \"...\""
|
|
142
|
-
)
|
|
143
|
-
except requests.exceptions.RequestException as e:
|
|
144
|
-
raise click.ClickException(
|
|
145
|
-
f"Failed to fetch profile from API: {e}\n"
|
|
146
|
-
)
|
|
77
|
+
return api_secret
|
|
78
|
+
|
|
79
|
+
# Check config file
|
|
80
|
+
config_dir = Path.home() / '.config' / 'cost-calculator'
|
|
81
|
+
config_file = config_dir / 'config.json'
|
|
147
82
|
|
|
148
|
-
# Fallback to local file if no API secret
|
|
149
83
|
if config_file.exists():
|
|
150
84
|
with open(config_file) as f:
|
|
151
|
-
|
|
85
|
+
config = json.load(f)
|
|
86
|
+
return config.get('api_secret')
|
|
87
|
+
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_profile(profile_name):
|
|
92
|
+
"""Load profile configuration from DynamoDB API (API-only, no local files)"""
|
|
93
|
+
import requests
|
|
94
|
+
|
|
95
|
+
# Get API secret
|
|
96
|
+
api_secret = get_api_secret()
|
|
97
|
+
|
|
98
|
+
if not api_secret:
|
|
99
|
+
raise click.ClickException(
|
|
100
|
+
"No API secret configured.\n"
|
|
101
|
+
"Run: cc configure --api-secret YOUR_SECRET\n"
|
|
102
|
+
"Or set environment variable: export COST_API_SECRET=YOUR_SECRET"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
response = requests.post(
|
|
107
|
+
'https://64g7jq7sjygec2zmll5lsghrpi0txrzo.lambda-url.us-east-1.on.aws/',
|
|
108
|
+
headers={'X-API-Secret': api_secret, 'Content-Type': 'application/json'},
|
|
109
|
+
json={'operation': 'get', 'profile_name': profile_name},
|
|
110
|
+
timeout=10
|
|
111
|
+
)
|
|
152
112
|
|
|
153
|
-
if
|
|
154
|
-
|
|
113
|
+
if response.status_code == 200:
|
|
114
|
+
response_data = response.json()
|
|
115
|
+
# API returns {"profile": {...}} wrapper
|
|
116
|
+
profile_data = response_data.get('profile', response_data)
|
|
117
|
+
profile = {'accounts': profile_data['accounts']}
|
|
155
118
|
|
|
156
|
-
#
|
|
157
|
-
if 'aws_profile'
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
f"Run: cc configure --profile {profile_name}"
|
|
119
|
+
# If profile has aws_profile field, use it
|
|
120
|
+
if 'aws_profile' in profile_data:
|
|
121
|
+
profile['aws_profile'] = profile_data['aws_profile']
|
|
122
|
+
# Check for AWS_PROFILE environment variable (SSO support)
|
|
123
|
+
elif os.environ.get('AWS_PROFILE'):
|
|
124
|
+
profile['aws_profile'] = os.environ['AWS_PROFILE']
|
|
125
|
+
# Use environment credentials
|
|
126
|
+
elif os.environ.get('AWS_ACCESS_KEY_ID'):
|
|
127
|
+
profile['credentials'] = {
|
|
128
|
+
'aws_access_key_id': os.environ['AWS_ACCESS_KEY_ID'],
|
|
129
|
+
'aws_secret_access_key': os.environ['AWS_SECRET_ACCESS_KEY'],
|
|
130
|
+
'aws_session_token': os.environ.get('AWS_SESSION_TOKEN')
|
|
131
|
+
}
|
|
132
|
+
else:
|
|
133
|
+
# Try to find a matching AWS profile by name
|
|
134
|
+
# This allows "khoros" profile to work with "khoros_umbrella" AWS profile
|
|
135
|
+
import subprocess
|
|
136
|
+
try:
|
|
137
|
+
result = subprocess.run(
|
|
138
|
+
['aws', 'configure', 'list-profiles'],
|
|
139
|
+
capture_output=True,
|
|
140
|
+
text=True,
|
|
141
|
+
timeout=5
|
|
180
142
|
)
|
|
181
|
-
|
|
182
|
-
|
|
143
|
+
if result.returncode == 0:
|
|
144
|
+
available_profiles = result.stdout.strip().split('\n')
|
|
145
|
+
# Try exact match first
|
|
146
|
+
if profile_name in available_profiles:
|
|
147
|
+
profile['aws_profile'] = profile_name
|
|
148
|
+
# Try with common suffixes
|
|
149
|
+
elif f"{profile_name}_umbrella" in available_profiles:
|
|
150
|
+
profile['aws_profile'] = f"{profile_name}_umbrella"
|
|
151
|
+
elif f"{profile_name}-umbrella" in available_profiles:
|
|
152
|
+
profile['aws_profile'] = f"{profile_name}-umbrella"
|
|
153
|
+
elif f"{profile_name}_prod" in available_profiles:
|
|
154
|
+
profile['aws_profile'] = f"{profile_name}_prod"
|
|
155
|
+
# If no match found, leave it unset - user must provide --sso
|
|
156
|
+
except:
|
|
157
|
+
# If we can't list profiles, leave it unset - user must provide --sso
|
|
158
|
+
pass
|
|
183
159
|
|
|
184
160
|
return profile
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
161
|
+
else:
|
|
162
|
+
raise click.ClickException(
|
|
163
|
+
f"Profile '{profile_name}' not found in DynamoDB.\n"
|
|
164
|
+
f"Run: cc profile create --name {profile_name} --accounts \"...\""
|
|
165
|
+
)
|
|
166
|
+
except requests.exceptions.RequestException as e:
|
|
167
|
+
raise click.ClickException(
|
|
168
|
+
f"Failed to fetch profile from API: {e}\n"
|
|
169
|
+
"Check your API secret and network connection."
|
|
170
|
+
)
|
|
191
171
|
|
|
192
172
|
|
|
193
173
|
def calculate_costs(profile_config, accounts, start_date, offset, window):
|
|
@@ -737,77 +717,81 @@ def setup():
|
|
|
737
717
|
|
|
738
718
|
|
|
739
719
|
@cli.command()
|
|
740
|
-
@click.option('--
|
|
741
|
-
@click.option('--
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
def configure(profile, access_key_id, secret_access_key, session_token, region):
|
|
746
|
-
"""Configure AWS credentials for a profile (alternative to SSO)"""
|
|
720
|
+
@click.option('--api-secret', help='API secret for DynamoDB profile access')
|
|
721
|
+
@click.option('--show', is_flag=True, help='Show current configuration')
|
|
722
|
+
def configure(api_secret, show):
|
|
723
|
+
"""
|
|
724
|
+
Configure Cost Calculator CLI settings.
|
|
747
725
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
creds_file = config_dir / 'credentials.json'
|
|
726
|
+
This tool requires an API secret to access profiles stored in DynamoDB.
|
|
727
|
+
The secret can be configured here or set via COST_API_SECRET environment variable.
|
|
751
728
|
|
|
752
|
-
|
|
729
|
+
Examples:
|
|
730
|
+
# Configure API secret
|
|
731
|
+
cc configure --api-secret YOUR_SECRET_KEY
|
|
732
|
+
|
|
733
|
+
# Show current configuration
|
|
734
|
+
cc configure --show
|
|
735
|
+
|
|
736
|
+
# Use environment variable instead (no configuration needed)
|
|
737
|
+
export COST_API_SECRET=YOUR_SECRET_KEY
|
|
738
|
+
"""
|
|
739
|
+
import os
|
|
740
|
+
|
|
741
|
+
config_dir = Path.home() / '.config' / 'cost-calculator'
|
|
753
742
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
743
|
+
config_file = config_dir / 'config.json'
|
|
754
744
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
745
|
+
if show:
|
|
746
|
+
# Show current configuration
|
|
747
|
+
if config_file.exists():
|
|
758
748
|
with open(config_file) as f:
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
749
|
+
config = json.load(f)
|
|
750
|
+
if 'api_secret' in config:
|
|
751
|
+
masked_secret = config['api_secret'][:8] + '...' + config['api_secret'][-4:]
|
|
752
|
+
click.echo(f"API Secret: {masked_secret} (configured)")
|
|
753
|
+
else:
|
|
754
|
+
click.echo("API Secret: Not configured")
|
|
755
|
+
else:
|
|
756
|
+
click.echo("No configuration file found")
|
|
757
|
+
|
|
758
|
+
# Check environment variable
|
|
759
|
+
import os
|
|
760
|
+
if os.environ.get('COST_API_SECRET'):
|
|
761
|
+
click.echo("Environment: COST_API_SECRET is set")
|
|
762
|
+
else:
|
|
763
|
+
click.echo("Environment: COST_API_SECRET is not set")
|
|
764
|
+
|
|
768
765
|
return
|
|
769
766
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
with open(config_file, 'w') as f:
|
|
776
|
-
json.dump(profiles, f, indent=2)
|
|
777
|
-
|
|
778
|
-
# Load or create credentials file
|
|
779
|
-
if creds_file.exists() and creds_file.stat().st_size > 0:
|
|
780
|
-
try:
|
|
781
|
-
with open(creds_file) as f:
|
|
782
|
-
creds = json.load(f)
|
|
783
|
-
except json.JSONDecodeError:
|
|
784
|
-
creds = {}
|
|
785
|
-
else:
|
|
786
|
-
creds = {}
|
|
767
|
+
if not api_secret:
|
|
768
|
+
raise click.ClickException(
|
|
769
|
+
"Please provide --api-secret or use --show to view current configuration\n"
|
|
770
|
+
"Example: cc configure --api-secret YOUR_SECRET_KEY"
|
|
771
|
+
)
|
|
787
772
|
|
|
788
|
-
#
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
}
|
|
773
|
+
# Load existing config
|
|
774
|
+
config = {}
|
|
775
|
+
if config_file.exists():
|
|
776
|
+
with open(config_file) as f:
|
|
777
|
+
config = json.load(f)
|
|
794
778
|
|
|
795
|
-
|
|
796
|
-
|
|
779
|
+
# Update API secret
|
|
780
|
+
config['api_secret'] = api_secret
|
|
797
781
|
|
|
798
|
-
# Save
|
|
799
|
-
with open(
|
|
800
|
-
json.dump(
|
|
782
|
+
# Save config
|
|
783
|
+
with open(config_file, 'w') as f:
|
|
784
|
+
json.dump(config, f, indent=2)
|
|
801
785
|
|
|
802
|
-
# Set
|
|
803
|
-
|
|
786
|
+
# Set restrictive permissions (Unix/Mac only - Windows uses different permission model)
|
|
787
|
+
import platform
|
|
788
|
+
if platform.system() != 'Windows':
|
|
789
|
+
os.chmod(config_file, 0o600)
|
|
804
790
|
|
|
805
|
-
|
|
806
|
-
click.echo(f"✓
|
|
807
|
-
click.echo(f"\
|
|
808
|
-
click.echo("\nNote:
|
|
809
|
-
click.echo(" you'll need to reconfigure when they expire.")
|
|
810
|
-
|
|
791
|
+
masked_secret = api_secret[:8] + '...' + api_secret[-4:]
|
|
792
|
+
click.echo(f"✓ API secret configured: {masked_secret}")
|
|
793
|
+
click.echo(f"\nYou can now run: cc calculate --profile PROFILE_NAME")
|
|
794
|
+
click.echo(f"\nNote: Profiles are stored in DynamoDB and accessed via the API.")
|
|
811
795
|
|
|
812
796
|
@cli.command()
|
|
813
797
|
@click.option('--profile', required=True, help='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
|
|
File without changes
|
{aws_cost_calculator_cli-1.11.1.dist-info → aws_cost_calculator_cli-2.0.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|