aws-cost-calculator-cli 1.5.1__py3-none-any.whl → 1.6.0__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-cost-calculator-cli
3
- Version: 1.5.1
3
+ Version: 1.6.0
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
@@ -49,36 +49,51 @@ pip install -e .
49
49
 
50
50
  ## Quick Start
51
51
 
52
- ### 1. Login to AWS SSO
52
+ ### Authentication Methods
53
53
 
54
+ The CLI supports three authentication methods:
55
+
56
+ #### 1. SSO (Recommended)
54
57
  ```bash
55
- aws sso login --profile my_aws_profile
58
+ # The CLI will automatically trigger SSO login if needed
59
+ cc calculate --profile khoros --sso khoros_umbrella
56
60
  ```
57
61
 
58
- **Note:** You need to do this before running cost calculations. The SSO session typically lasts 8-12 hours.
59
-
60
- ### 2. Initialize a profile
62
+ #### 2. Static Credentials
63
+ ```bash
64
+ cc calculate --profile khoros \
65
+ --access-key-id ASIA3D3QOXPO6EBAPXVI \
66
+ --secret-access-key /9ijZEUoszN/S2A8IlCrHpW+1fMZ7aUb7fPvU0dL \
67
+ --session-token IQoJb3JpZ2luX2VjENr...
68
+ ```
61
69
 
70
+ #### 3. Environment Variables
62
71
  ```bash
63
- cc init --profile myprofile \
64
- --aws-profile my_aws_profile \
65
- --accounts "123456789012,234567890123,345678901234"
72
+ # For SSO
73
+ export AWS_PROFILE=khoros_umbrella
74
+ cc calculate --profile khoros
75
+
76
+ # For static credentials
77
+ export AWS_ACCESS_KEY_ID=ASIA...
78
+ export AWS_SECRET_ACCESS_KEY=...
79
+ export AWS_SESSION_TOKEN=...
80
+ cc calculate --profile khoros
66
81
  ```
67
82
 
68
- ### 3. Calculate costs
83
+ ### Basic Usage
69
84
 
70
85
  ```bash
71
86
  # Default: Today minus 2 days, going back 30 days
72
- cc calculate --profile myprofile
87
+ cc calculate --profile khoros --sso khoros_umbrella
73
88
 
74
89
  # Specific start date
75
- cc calculate --profile myprofile --start-date 2025-11-04
90
+ cc calculate --profile khoros --sso khoros_umbrella --start-date 2025-11-04
76
91
 
77
92
  # Custom offset and window
78
- cc calculate --profile myprofile --offset 2 --window 30
93
+ cc calculate --profile khoros --sso khoros_umbrella --offset 2 --window 30
79
94
 
80
95
  # JSON output
81
- cc calculate --profile myprofile --json-output
96
+ cc calculate --profile khoros --sso khoros_umbrella --json-output
82
97
  ```
83
98
 
84
99
  ### 4. Analyze cost trends
@@ -0,0 +1,13 @@
1
+ aws_cost_calculator_cli-1.6.0.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=sJYvzbdHCxOEcCgOjZs4o9MOogV1Yh8r7x0hJtd__K0,38639
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.6.0.dist-info/METADATA,sha256=AaLbUH-anBc1lv2o2DpdJR3v0tSavhPtDo5Sjb7VsHA,8612
10
+ aws_cost_calculator_cli-1.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ aws_cost_calculator_cli-1.6.0.dist-info/entry_points.txt,sha256=_5Qy4EcHbYVYrdgOu1E48faMHb9fLUl5VJ3djDHuJBo,47
12
+ aws_cost_calculator_cli-1.6.0.dist-info/top_level.txt,sha256=PRwGPPlNqASfyhGHDjSfyl4SXeE7GF3OVTu1tY1Uqyc,16
13
+ aws_cost_calculator_cli-1.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
cost_calculator/cli.py CHANGED
@@ -19,49 +19,143 @@ from cost_calculator.drill import format_drill_down_markdown
19
19
  from cost_calculator.executor import execute_trends, execute_monthly, execute_drill
20
20
 
21
21
 
22
+ def apply_auth_options(config, sso=None, access_key_id=None, secret_access_key=None, session_token=None):
23
+ """Apply authentication options to profile config
24
+
25
+ Args:
26
+ config: Profile configuration dict
27
+ sso: AWS SSO profile name
28
+ access_key_id: AWS Access Key ID
29
+ secret_access_key: AWS Secret Access Key
30
+ session_token: AWS Session Token
31
+
32
+ Returns:
33
+ Updated config dict
34
+ """
35
+ import subprocess
36
+
37
+ if sso:
38
+ # SSO authentication - trigger login if needed
39
+ try:
40
+ # Test if SSO session is valid
41
+ result = subprocess.run(
42
+ ['aws', 'sts', 'get-caller-identity', '--profile', sso],
43
+ capture_output=True,
44
+ text=True,
45
+ timeout=5
46
+ )
47
+ if result.returncode != 0:
48
+ if 'expired' in result.stderr.lower() or 'token' in result.stderr.lower():
49
+ click.echo(f"SSO session expired or not initialized. Logging in...")
50
+ subprocess.run(['aws', 'sso', 'login', '--profile', sso], check=True)
51
+ except Exception as e:
52
+ click.echo(f"Warning: Could not verify SSO session: {e}")
53
+
54
+ config['aws_profile'] = sso
55
+ elif access_key_id and secret_access_key:
56
+ # Static credentials provided via CLI
57
+ config['credentials'] = {
58
+ 'aws_access_key_id': access_key_id,
59
+ 'aws_secret_access_key': secret_access_key,
60
+ 'region': 'us-east-1'
61
+ }
62
+ if session_token:
63
+ config['credentials']['aws_session_token'] = session_token
64
+
65
+ return config
66
+
67
+
22
68
  def load_profile(profile_name):
23
- """Load profile configuration from ~/.config/cost-calculator/profiles.json"""
69
+ """Load profile configuration from local file or DynamoDB API"""
70
+ import os
71
+ import requests
72
+
24
73
  config_dir = Path.home() / '.config' / 'cost-calculator'
25
74
  config_file = config_dir / 'profiles.json'
26
75
  creds_file = config_dir / 'credentials.json'
27
76
 
28
- if not config_file.exists():
77
+ # Try local file first
78
+ if config_file.exists():
79
+ with open(config_file) as f:
80
+ profiles = json.load(f)
81
+
82
+ if profile_name in profiles:
83
+ profile = profiles[profile_name]
84
+
85
+ # Load credentials if using static credentials (not SSO)
86
+ if 'aws_profile' not in profile:
87
+ if not creds_file.exists():
88
+ # Try environment variables
89
+ if os.environ.get('AWS_ACCESS_KEY_ID'):
90
+ profile['credentials'] = {
91
+ 'aws_access_key_id': os.environ['AWS_ACCESS_KEY_ID'],
92
+ 'aws_secret_access_key': os.environ['AWS_SECRET_ACCESS_KEY'],
93
+ 'aws_session_token': os.environ.get('AWS_SESSION_TOKEN')
94
+ }
95
+ return profile
96
+
97
+ raise click.ClickException(
98
+ f"No credentials found for profile '{profile_name}'.\n"
99
+ f"Run: cc configure --profile {profile_name}"
100
+ )
101
+
102
+ with open(creds_file) as f:
103
+ creds = json.load(f)
104
+
105
+ if profile_name not in creds:
106
+ raise click.ClickException(
107
+ f"No credentials found for profile '{profile_name}'.\n"
108
+ f"Run: cc configure --profile {profile_name}"
109
+ )
110
+
111
+ profile['credentials'] = creds[profile_name]
112
+
113
+ return profile
114
+
115
+ # Profile not found locally - try DynamoDB API
116
+ api_secret = os.environ.get('COST_API_SECRET')
117
+ if not api_secret:
29
118
  raise click.ClickException(
30
- f"Profile configuration not found at {config_file}\n"
119
+ f"Profile '{profile_name}' not found locally and COST_API_SECRET not set.\n"
31
120
  f"Run: cc init --profile {profile_name}"
32
121
  )
33
122
 
34
- with open(config_file) as f:
35
- profiles = json.load(f)
36
-
37
- if profile_name not in profiles:
38
- raise click.ClickException(
39
- f"Profile '{profile_name}' not found in {config_file}\n"
40
- f"Available profiles: {', '.join(profiles.keys())}"
123
+ try:
124
+ response = requests.post(
125
+ 'https://64g7jq7sjygec2zmll5lsghrpi0txrzo.lambda-url.us-east-1.on.aws/',
126
+ headers={'X-API-Secret': api_secret, 'Content-Type': 'application/json'},
127
+ json={'operation': 'get', 'profile_name': profile_name},
128
+ timeout=10
41
129
  )
42
-
43
- profile = profiles[profile_name]
44
-
45
- # Load credentials if using static credentials (not SSO)
46
- if 'aws_profile' not in profile:
47
- if not creds_file.exists():
48
- raise click.ClickException(
49
- f"No credentials found for profile '{profile_name}'.\n"
50
- f"Run: cc configure --profile {profile_name}"
51
- )
52
-
53
- with open(creds_file) as f:
54
- creds = json.load(f)
55
130
 
56
- if profile_name not in creds:
131
+ if response.status_code == 200:
132
+ response_data = response.json()
133
+ # API returns {"profile": {...}} wrapper
134
+ profile_data = response_data.get('profile', response_data)
135
+ profile = {'accounts': profile_data['accounts']}
136
+
137
+ # Check for AWS_PROFILE environment variable (SSO support)
138
+ if os.environ.get('AWS_PROFILE'):
139
+ profile['aws_profile'] = os.environ['AWS_PROFILE']
140
+ # Use environment credentials
141
+ elif os.environ.get('AWS_ACCESS_KEY_ID'):
142
+ profile['credentials'] = {
143
+ 'aws_access_key_id': os.environ['AWS_ACCESS_KEY_ID'],
144
+ 'aws_secret_access_key': os.environ['AWS_SECRET_ACCESS_KEY'],
145
+ 'aws_session_token': os.environ.get('AWS_SESSION_TOKEN')
146
+ }
147
+
148
+ return profile
149
+ else:
57
150
  raise click.ClickException(
58
- f"No credentials found for profile '{profile_name}'.\n"
59
- f"Run: cc configure --profile {profile_name}"
151
+ f"Profile '{profile_name}' not found in DynamoDB.\n"
152
+ f"Run: cc profile create --name {profile_name} --accounts \"...\""
60
153
  )
61
-
62
- profile['credentials'] = creds[profile_name]
63
-
64
- return profile
154
+ except requests.exceptions.RequestException as e:
155
+ raise click.ClickException(
156
+ f"Failed to fetch profile from API: {e}\n"
157
+ f"Run: cc init --profile {profile_name}"
158
+ )
65
159
 
66
160
 
67
161
  def calculate_costs(profile_config, accounts, start_date, offset, window):
@@ -296,12 +390,30 @@ def cli():
296
390
  @click.option('--offset', default=2, help='Days to go back from start date (default: 2)')
297
391
  @click.option('--window', default=30, help='Number of days to analyze (default: 30)')
298
392
  @click.option('--json-output', is_flag=True, help='Output as JSON')
299
- def calculate(profile, start_date, offset, window, json_output):
300
- """Calculate AWS costs for the specified period"""
393
+ @click.option('--sso', help='AWS SSO profile name (e.g., khoros_umbrella)')
394
+ @click.option('--access-key-id', help='AWS Access Key ID (for static credentials)')
395
+ @click.option('--secret-access-key', help='AWS Secret Access Key (for static credentials)')
396
+ @click.option('--session-token', help='AWS Session Token (for static credentials)')
397
+ def calculate(profile, start_date, offset, window, json_output, sso, access_key_id, secret_access_key, session_token):
398
+ """Calculate AWS costs for the specified period
399
+
400
+ \b
401
+ Authentication Options:
402
+ 1. SSO: --sso <profile_name>
403
+ Example: cc calculate --profile khoros --sso khoros_umbrella
404
+
405
+ 2. Static Credentials: --access-key-id, --secret-access-key, --session-token
406
+ Example: cc calculate --profile khoros --access-key-id ASIA... --secret-access-key ... --session-token ...
407
+
408
+ 3. Environment Variables: AWS_PROFILE or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY
409
+ """
301
410
 
302
411
  # Load profile configuration
303
412
  config = load_profile(profile)
304
413
 
414
+ # Apply authentication options
415
+ config = apply_auth_options(config, sso, access_key_id, secret_access_key, session_token)
416
+
305
417
  # Calculate costs
306
418
  result = calculate_costs(
307
419
  profile_config=config,
@@ -547,12 +659,17 @@ def configure(profile, access_key_id, secret_access_key, session_token, region):
547
659
  @click.option('--profile', required=True, help='Profile name')
548
660
  @click.option('--weeks', default=3, help='Number of weeks to analyze (default: 3)')
549
661
  @click.option('--output', default='cost_trends.md', help='Output markdown file (default: cost_trends.md)')
550
- @click.option('--json-output', is_flag=True, help='Output as JSON instead of markdown')
551
- def trends(profile, weeks, output, json_output):
662
+ @click.option('--json-output', is_flag=True, help='Output as JSON')
663
+ @click.option('--sso', help='AWS SSO profile name')
664
+ @click.option('--access-key-id', help='AWS Access Key ID')
665
+ @click.option('--secret-access-key', help='AWS Secret Access Key')
666
+ @click.option('--session-token', help='AWS Session Token')
667
+ def trends(profile, weeks, output, json_output, sso, access_key_id, secret_access_key, session_token):
552
668
  """Analyze cost trends with Week-over-Week and Trailing 30-Day comparisons"""
553
669
 
554
670
  # Load profile configuration
555
671
  config = load_profile(profile)
672
+ config = apply_auth_options(config, sso, access_key_id, secret_access_key, session_token)
556
673
 
557
674
  click.echo(f"Analyzing last {weeks} weeks...")
558
675
  click.echo("")
@@ -613,12 +730,17 @@ def trends(profile, weeks, output, json_output):
613
730
  @click.option('--profile', required=True, help='Profile name')
614
731
  @click.option('--months', default=6, help='Number of months to analyze (default: 6)')
615
732
  @click.option('--output', default='monthly_trends.md', help='Output markdown file (default: monthly_trends.md)')
616
- @click.option('--json-output', is_flag=True, help='Output as JSON instead of markdown')
617
- def monthly(profile, months, output, json_output):
733
+ @click.option('--json-output', is_flag=True, help='Output as JSON')
734
+ @click.option('--sso', help='AWS SSO profile name')
735
+ @click.option('--access-key-id', help='AWS Access Key ID')
736
+ @click.option('--secret-access-key', help='AWS Secret Access Key')
737
+ @click.option('--session-token', help='AWS Session Token')
738
+ def monthly(profile, months, output, json_output, sso, access_key_id, secret_access_key, session_token):
618
739
  """Analyze month-over-month cost trends at service level"""
619
740
 
620
- # Load profile configuration
741
+ # Load profile
621
742
  config = load_profile(profile)
743
+ config = apply_auth_options(config, sso, access_key_id, secret_access_key, session_token)
622
744
 
623
745
  click.echo(f"Analyzing last {months} months...")
624
746
  click.echo("")
@@ -680,12 +802,17 @@ def monthly(profile, months, output, json_output):
680
802
  @click.option('--account', help='Filter by account ID')
681
803
  @click.option('--usage-type', help='Filter by usage type')
682
804
  @click.option('--output', default='drill_down.md', help='Output markdown file (default: drill_down.md)')
683
- @click.option('--json-output', is_flag=True, help='Output as JSON instead of markdown')
684
- def drill(profile, weeks, service, account, usage_type, output, json_output):
805
+ @click.option('--json-output', is_flag=True, help='Output as JSON')
806
+ @click.option('--sso', help='AWS SSO profile name')
807
+ @click.option('--access-key-id', help='AWS Access Key ID')
808
+ @click.option('--secret-access-key', help='AWS Secret Access Key')
809
+ @click.option('--session-token', help='AWS Session Token')
810
+ def drill(profile, weeks, service, account, usage_type, output, json_output, sso, access_key_id, secret_access_key, session_token):
685
811
  """Drill down into cost changes by service, account, or usage type"""
686
812
 
687
- # Load profile configuration
813
+ # Load profile
688
814
  config = load_profile(profile)
815
+ config = apply_auth_options(config, sso, access_key_id, secret_access_key, session_token)
689
816
 
690
817
  # Show filters
691
818
  click.echo(f"Analyzing last {weeks} weeks...")
@@ -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
- # Get temporary credentials from SSO session
18
- session = boto3.Session(profile_name=config['aws_profile'])
19
- credentials = session.get_credentials()
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
- return {
23
- 'access_key': frozen_creds.access_key,
24
- 'secret_key': frozen_creds.secret_key,
25
- 'session_token': frozen_creds.token
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['credentials']
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']
@@ -1,13 +0,0 @@
1
- aws_cost_calculator_cli-1.5.1.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=aPYEm8KdQl7xTpG1gvJ-2adAIJ2PW1_ly27xggQxNNE,7671
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.1.dist-info/METADATA,sha256=g9xQIorywmJ-Xq3zQO0IYdW7otVkkDkZhJwbXgN5PjU,8176
10
- aws_cost_calculator_cli-1.5.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
11
- aws_cost_calculator_cli-1.5.1.dist-info/entry_points.txt,sha256=_5Qy4EcHbYVYrdgOu1E48faMHb9fLUl5VJ3djDHuJBo,47
12
- aws_cost_calculator_cli-1.5.1.dist-info/top_level.txt,sha256=PRwGPPlNqASfyhGHDjSfyl4SXeE7GF3OVTu1tY1Uqyc,16
13
- aws_cost_calculator_cli-1.5.1.dist-info/RECORD,,