aws-cost-calculator-cli 1.3.0__py3-none-any.whl → 1.5.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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-cost-calculator-cli
3
- Version: 1.3.0
3
+ Version: 1.5.1
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
@@ -23,6 +23,7 @@ Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: click>=8.0.0
25
25
  Requires-Dist: boto3>=1.26.0
26
+ Requires-Dist: requests>=2.28.0
26
27
  Dynamic: author
27
28
  Dynamic: classifier
28
29
  Dynamic: description
@@ -241,14 +242,17 @@ The `drill` command allows you to investigate cost changes at different levels o
241
242
  3. **Drill deeper:** `cc drill --service "EC2 - Other" --account 123` → See usage types
242
243
 
243
244
  **Features:**
244
- - **Automatic grouping**: Shows the next level of detail based on your filters
245
- - No filters Shows services
246
- - Service only Shows accounts using that service
247
- - Account only Shows services in that account
248
- - Service + Account Shows usage types
249
- - **Top 10 Increases/Decreases**: For each week comparison
250
- - **Total rows**: Sum of top 10 changes
251
- - **Filters**: Only shows changes >$10 and >5%
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
252
256
 
253
257
  Example output:
254
258
  ```
@@ -0,0 +1,13 @@
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,,
@@ -0,0 +1,84 @@
1
+ """
2
+ API client for calling Lambda backend.
3
+ Falls back to local execution if API is not configured.
4
+ """
5
+ import os
6
+ import json
7
+ import requests
8
+ from pathlib import Path
9
+
10
+
11
+ def get_api_config():
12
+ """
13
+ Get API configuration.
14
+
15
+ Returns:
16
+ dict: API configuration with api_secret, or None if not configured
17
+ """
18
+ api_secret = os.environ.get('COST_API_SECRET', '')
19
+
20
+ if api_secret:
21
+ return {'api_secret': api_secret}
22
+
23
+ return None
24
+
25
+
26
+ def call_lambda_api(endpoint, credentials, accounts, **kwargs):
27
+ """
28
+ Call Lambda API endpoint.
29
+
30
+ Args:
31
+ endpoint: API endpoint name ('trends', 'monthly', 'drill')
32
+ credentials: dict with AWS credentials
33
+ accounts: list of account IDs
34
+ **kwargs: additional parameters for the specific endpoint
35
+
36
+ Returns:
37
+ dict: API response data
38
+
39
+ Raises:
40
+ Exception: if API call fails
41
+ """
42
+ api_config = get_api_config()
43
+
44
+ if not api_config:
45
+ raise Exception("API not configured. Set COST_API_SECRET environment variable.")
46
+
47
+ # Map endpoint names to Lambda URLs
48
+ endpoint_urls = {
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/'
54
+ }
55
+
56
+ url = endpoint_urls.get(endpoint)
57
+
58
+ if not url:
59
+ raise Exception(f"Unknown endpoint: {endpoint}")
60
+
61
+ # Build request payload
62
+ payload = {
63
+ 'credentials': credentials,
64
+ 'accounts': accounts
65
+ }
66
+ payload.update(kwargs)
67
+
68
+ # Make API call
69
+ headers = {
70
+ 'X-API-Secret': api_config['api_secret'],
71
+ 'Content-Type': 'application/json'
72
+ }
73
+
74
+ response = requests.post(url, headers=headers, json=payload, timeout=300)
75
+
76
+ if response.status_code != 200:
77
+ raise Exception(f"API call failed: {response.status_code} - {response.text}")
78
+
79
+ return response.json()
80
+
81
+
82
+ def is_api_configured():
83
+ """Check if API is configured."""
84
+ return get_api_config() is not None
cost_calculator/cli.py CHANGED
@@ -13,9 +13,10 @@ import boto3
13
13
  import json
14
14
  from datetime import datetime, timedelta
15
15
  from pathlib import Path
16
- from cost_calculator.trends import analyze_trends, format_trends_markdown
17
- from cost_calculator.monthly import analyze_monthly_trends, format_monthly_markdown
18
- from cost_calculator.drill import analyze_drill_down, format_drill_down_markdown
16
+ from cost_calculator.trends import format_trends_markdown
17
+ from cost_calculator.monthly import format_monthly_markdown
18
+ from cost_calculator.drill import format_drill_down_markdown
19
+ from cost_calculator.executor import execute_trends, execute_monthly, execute_drill
19
20
 
20
21
 
21
22
  def load_profile(profile_name):
@@ -553,48 +554,11 @@ def trends(profile, weeks, output, json_output):
553
554
  # Load profile configuration
554
555
  config = load_profile(profile)
555
556
 
556
- # Initialize boto3 client
557
- try:
558
- if 'aws_profile' in config:
559
- aws_profile = config['aws_profile']
560
- click.echo(f"AWS Profile: {aws_profile} (SSO)")
561
- session = boto3.Session(profile_name=aws_profile)
562
- ce_client = session.client('ce', region_name='us-east-1')
563
- else:
564
- creds = config['credentials']
565
- click.echo(f"AWS Credentials: Static")
566
-
567
- session_kwargs = {
568
- 'aws_access_key_id': creds['aws_access_key_id'],
569
- 'aws_secret_access_key': creds['aws_secret_access_key'],
570
- 'region_name': creds.get('region', 'us-east-1')
571
- }
572
-
573
- if 'aws_session_token' in creds:
574
- session_kwargs['aws_session_token'] = creds['aws_session_token']
575
-
576
- session = boto3.Session(**session_kwargs)
577
- ce_client = session.client('ce')
578
-
579
- except Exception as e:
580
- if 'Token has expired' in str(e) or 'sso' in str(e).lower():
581
- if 'aws_profile' in config:
582
- raise click.ClickException(
583
- f"AWS SSO session expired or not initialized.\n"
584
- f"Run: aws sso login --profile {config['aws_profile']}"
585
- )
586
- else:
587
- raise click.ClickException(
588
- f"AWS credentials expired.\n"
589
- f"Run: cc configure --profile {profile}"
590
- )
591
- raise
592
-
593
557
  click.echo(f"Analyzing last {weeks} weeks...")
594
558
  click.echo("")
595
559
 
596
- # Analyze trends
597
- trends_data = analyze_trends(ce_client, config['accounts'], num_weeks=weeks)
560
+ # Execute via API or locally
561
+ trends_data = execute_trends(config, weeks)
598
562
 
599
563
  if json_output:
600
564
  # Output as JSON
@@ -656,34 +620,11 @@ def monthly(profile, months, output, json_output):
656
620
  # Load profile configuration
657
621
  config = load_profile(profile)
658
622
 
659
- # Initialize boto3 client
660
- try:
661
- if 'aws_profile' in config:
662
- aws_profile = config['aws_profile']
663
- click.echo(f"AWS Profile: {aws_profile} (SSO)")
664
- session = boto3.Session(profile_name=aws_profile)
665
- else:
666
- # Use static credentials
667
- creds = config['credentials']
668
- click.echo("AWS Credentials: Static")
669
- session = boto3.Session(
670
- aws_access_key_id=creds['aws_access_key_id'],
671
- aws_secret_access_key=creds['aws_secret_access_key'],
672
- aws_session_token=creds.get('aws_session_token')
673
- )
674
-
675
- ce_client = session.client('ce', region_name='us-east-1')
676
- except Exception as e:
677
- raise click.ClickException(f"Failed to initialize AWS session: {str(e)}")
678
-
679
- # Get account list
680
- accounts = config['accounts']
681
-
682
623
  click.echo(f"Analyzing last {months} months...")
683
624
  click.echo("")
684
625
 
685
- # Analyze monthly trends
686
- monthly_data = analyze_monthly_trends(ce_client, accounts, months)
626
+ # Execute via API or locally
627
+ monthly_data = execute_monthly(config, months)
687
628
 
688
629
  if json_output:
689
630
  # Output as JSON
@@ -746,29 +687,6 @@ def drill(profile, weeks, service, account, usage_type, output, json_output):
746
687
  # Load profile configuration
747
688
  config = load_profile(profile)
748
689
 
749
- # Initialize boto3 client
750
- try:
751
- if 'aws_profile' in config:
752
- aws_profile = config['aws_profile']
753
- click.echo(f"AWS Profile: {aws_profile} (SSO)")
754
- session = boto3.Session(profile_name=aws_profile)
755
- else:
756
- # Use static credentials
757
- creds = config['credentials']
758
- click.echo("AWS Credentials: Static")
759
- session = boto3.Session(
760
- aws_access_key_id=creds['aws_access_key_id'],
761
- aws_secret_access_key=creds['aws_secret_access_key'],
762
- aws_session_token=creds.get('aws_session_token')
763
- )
764
-
765
- ce_client = session.client('ce', region_name='us-east-1')
766
- except Exception as e:
767
- raise click.ClickException(f"Failed to initialize AWS session: {str(e)}")
768
-
769
- # Get account list
770
- accounts = config['accounts']
771
-
772
690
  # Show filters
773
691
  click.echo(f"Analyzing last {weeks} weeks...")
774
692
  if service:
@@ -779,13 +697,8 @@ def drill(profile, weeks, service, account, usage_type, output, json_output):
779
697
  click.echo(f" Usage type filter: {usage_type}")
780
698
  click.echo("")
781
699
 
782
- # Analyze with drill-down
783
- drill_data = analyze_drill_down(
784
- ce_client, accounts, weeks,
785
- service_filter=service,
786
- account_filter=account,
787
- usage_type_filter=usage_type
788
- )
700
+ # Execute via API or locally
701
+ drill_data = execute_drill(config, weeks, service, account, usage_type)
789
702
 
790
703
  if json_output:
791
704
  # Output as JSON
@@ -844,5 +757,119 @@ def drill(profile, weeks, service, account, usage_type, output, json_output):
844
757
  click.echo("")
845
758
 
846
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
+
847
874
  if __name__ == '__main__':
848
875
  cli()
@@ -0,0 +1,230 @@
1
+ """
2
+ Executor that routes to either API or local execution.
3
+ """
4
+ import boto3
5
+ import click
6
+ from cost_calculator.api_client import is_api_configured, call_lambda_api
7
+
8
+
9
+ def get_credentials_dict(config):
10
+ """
11
+ Extract credentials from config in format needed for API.
12
+
13
+ Returns:
14
+ dict with access_key, secret_key, session_token
15
+ """
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()
21
+
22
+ return {
23
+ 'access_key': frozen_creds.access_key,
24
+ 'secret_key': frozen_creds.secret_key,
25
+ 'session_token': frozen_creds.token
26
+ }
27
+ else:
28
+ # Use static credentials
29
+ creds = config['credentials']
30
+ result = {
31
+ 'access_key': creds['aws_access_key_id'],
32
+ 'secret_key': creds['aws_secret_access_key']
33
+ }
34
+ if 'aws_session_token' in creds:
35
+ result['session_token'] = creds['aws_session_token']
36
+ return result
37
+
38
+
39
+ def execute_trends(config, weeks):
40
+ """
41
+ Execute trends analysis via API or locally.
42
+
43
+ Returns:
44
+ dict: trends data
45
+ """
46
+ accounts = config['accounts']
47
+
48
+ if is_api_configured():
49
+ # Use API
50
+ click.echo("Using Lambda API...")
51
+ credentials = get_credentials_dict(config)
52
+ return call_lambda_api('trends', credentials, accounts, weeks=weeks)
53
+ else:
54
+ # Use local execution
55
+ click.echo("Using local execution...")
56
+ from cost_calculator.trends import analyze_trends
57
+
58
+ # Initialize boto3 client
59
+ if 'aws_profile' in config:
60
+ session = boto3.Session(profile_name=config['aws_profile'])
61
+ else:
62
+ creds = config['credentials']
63
+ session_kwargs = {
64
+ 'aws_access_key_id': creds['aws_access_key_id'],
65
+ 'aws_secret_access_key': creds['aws_secret_access_key'],
66
+ 'region_name': creds.get('region', 'us-east-1')
67
+ }
68
+ if 'aws_session_token' in creds:
69
+ session_kwargs['aws_session_token'] = creds['aws_session_token']
70
+ session = boto3.Session(**session_kwargs)
71
+
72
+ ce_client = session.client('ce', region_name='us-east-1')
73
+ return analyze_trends(ce_client, accounts, weeks)
74
+
75
+
76
+ def execute_monthly(config, months):
77
+ """
78
+ Execute monthly analysis via API or locally.
79
+
80
+ Returns:
81
+ dict: monthly data
82
+ """
83
+ accounts = config['accounts']
84
+
85
+ if is_api_configured():
86
+ # Use API
87
+ click.echo("Using Lambda API...")
88
+ credentials = get_credentials_dict(config)
89
+ return call_lambda_api('monthly', credentials, accounts, months=months)
90
+ else:
91
+ # Use local execution
92
+ click.echo("Using local execution...")
93
+ from cost_calculator.monthly import analyze_monthly_trends
94
+
95
+ # Initialize boto3 client
96
+ if 'aws_profile' in config:
97
+ session = boto3.Session(profile_name=config['aws_profile'])
98
+ else:
99
+ creds = config['credentials']
100
+ session_kwargs = {
101
+ 'aws_access_key_id': creds['aws_access_key_id'],
102
+ 'aws_secret_access_key': creds['aws_secret_access_key'],
103
+ 'region_name': creds.get('region', 'us-east-1')
104
+ }
105
+ if 'aws_session_token' in creds:
106
+ session_kwargs['aws_session_token'] = creds['aws_session_token']
107
+ session = boto3.Session(**session_kwargs)
108
+
109
+ ce_client = session.client('ce', region_name='us-east-1')
110
+ return analyze_monthly_trends(ce_client, accounts, months)
111
+
112
+
113
+ def execute_drill(config, weeks, service_filter=None, account_filter=None, usage_type_filter=None):
114
+ """
115
+ Execute drill-down analysis via API or locally.
116
+
117
+ Returns:
118
+ dict: drill data
119
+ """
120
+ accounts = config['accounts']
121
+
122
+ if is_api_configured():
123
+ # Use API
124
+ click.echo("Using Lambda API...")
125
+ credentials = get_credentials_dict(config)
126
+ kwargs = {'weeks': weeks}
127
+ if service_filter:
128
+ kwargs['service'] = service_filter
129
+ if account_filter:
130
+ kwargs['account'] = account_filter
131
+ if usage_type_filter:
132
+ kwargs['usage_type'] = usage_type_filter
133
+ return call_lambda_api('drill', credentials, accounts, **kwargs)
134
+ else:
135
+ # Use local execution
136
+ click.echo("Using local execution...")
137
+ from cost_calculator.drill import analyze_drill_down
138
+
139
+ # Initialize boto3 client
140
+ if 'aws_profile' in config:
141
+ session = boto3.Session(profile_name=config['aws_profile'])
142
+ else:
143
+ creds = config['credentials']
144
+ session_kwargs = {
145
+ 'aws_access_key_id': creds['aws_access_key_id'],
146
+ 'aws_secret_access_key': creds['aws_secret_access_key'],
147
+ 'region_name': creds.get('region', 'us-east-1')
148
+ }
149
+ if 'aws_session_token' in creds:
150
+ session_kwargs['aws_session_token'] = creds['aws_session_token']
151
+ session = boto3.Session(**session_kwargs)
152
+
153
+ ce_client = session.client('ce', region_name='us-east-1')
154
+ return analyze_drill_down(
155
+ ce_client, accounts, weeks,
156
+ service_filter=service_filter,
157
+ account_filter=account_filter,
158
+ usage_type_filter=usage_type_filter
159
+ )
160
+
161
+
162
+ def execute_analyze(config, weeks, analysis_type, pattern=None, min_cost=None):
163
+ """
164
+ Execute pandas-based analysis via API.
165
+ Note: This only works via API (requires pandas layer).
166
+
167
+ Returns:
168
+ dict: analysis results
169
+ """
170
+ accounts = config['accounts']
171
+
172
+ if not is_api_configured():
173
+ raise click.ClickException(
174
+ "Analyze command requires API configuration.\n"
175
+ "Set COST_API_SECRET environment variable."
176
+ )
177
+
178
+ credentials = get_credentials_dict(config)
179
+ kwargs = {'weeks': weeks, 'type': analysis_type}
180
+
181
+ if pattern:
182
+ kwargs['pattern'] = pattern
183
+ if min_cost:
184
+ kwargs['min_cost'] = min_cost
185
+
186
+ return call_lambda_api('analyze', credentials, accounts, **kwargs)
187
+
188
+
189
+ def execute_profile_operation(operation, profile_name=None, accounts=None, description=None):
190
+ """
191
+ Execute profile CRUD operations via API.
192
+
193
+ Returns:
194
+ dict: operation result
195
+ """
196
+ if not is_api_configured():
197
+ raise click.ClickException(
198
+ "Profile commands require API configuration.\n"
199
+ "Set COST_API_SECRET environment variable."
200
+ )
201
+
202
+ # Profile operations don't need AWS credentials, just API secret
203
+ import os
204
+ import requests
205
+ import json
206
+
207
+ api_secret = os.environ.get('COST_API_SECRET', '')
208
+
209
+ # Use profiles endpoint (hardcoded URL)
210
+ url = 'https://64g7jq7sjygec2zmll5lsghrpi0txrzo.lambda-url.us-east-1.on.aws/'
211
+
212
+ payload = {'operation': operation}
213
+ if profile_name:
214
+ payload['profile_name'] = profile_name
215
+ if accounts:
216
+ payload['accounts'] = accounts
217
+ if description:
218
+ payload['description'] = description
219
+
220
+ headers = {
221
+ 'X-API-Secret': api_secret,
222
+ 'Content-Type': 'application/json'
223
+ }
224
+
225
+ response = requests.post(url, headers=headers, json=payload, timeout=60)
226
+
227
+ if response.status_code != 200:
228
+ raise Exception(f"API call failed: {response.status_code} - {response.text}")
229
+
230
+ return response.json()
@@ -1,11 +0,0 @@
1
- aws_cost_calculator_cli-1.3.0.dist-info/licenses/LICENSE,sha256=cYtmQZHNGGTXOtg3T7LHDRneleaH0dHXHfxFV3WR50Y,1079
2
- cost_calculator/__init__.py,sha256=PJeIqvWh5AYJVrJxPPkI4pJnAt37rIjasrNS0I87kaM,52
3
- cost_calculator/cli.py,sha256=YJOWA-uWSMEZc4QHof8DF_An6VpKmTsxh0X_G31y1SM,31018
4
- cost_calculator/drill.py,sha256=hGi-prLgZDvNMMICQc4fl3LenM7YaZ3To_Ei4LKwrdc,10543
5
- cost_calculator/monthly.py,sha256=6k9F8S7djhX1wGV3-T1MZP7CvWbbfhSTEaddwCfVu5M,7932
6
- cost_calculator/trends.py,sha256=k_s4ylBX50sqoiM_fwepi58HW01zz767FMJhQUPDznk,12246
7
- aws_cost_calculator_cli-1.3.0.dist-info/METADATA,sha256=jZYbRF46p0qvjkw4Dqwja-4cnOJv59i7zTzmFXxfCsw,7761
8
- aws_cost_calculator_cli-1.3.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
9
- aws_cost_calculator_cli-1.3.0.dist-info/entry_points.txt,sha256=_5Qy4EcHbYVYrdgOu1E48faMHb9fLUl5VJ3djDHuJBo,47
10
- aws_cost_calculator_cli-1.3.0.dist-info/top_level.txt,sha256=PRwGPPlNqASfyhGHDjSfyl4SXeE7GF3OVTu1tY1Uqyc,16
11
- aws_cost_calculator_cli-1.3.0.dist-info/RECORD,,