aws-cost-calculator-cli 1.0.3__tar.gz

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.
@@ -0,0 +1,130 @@
1
+ # AWS Cost Calculation Methodology
2
+
3
+ ## Formula Overview
4
+
5
+ The cost calculator computes a **daily rate** and **annual projection** for AWS spend across multiple accounts using a standardized methodology that matches the AWS Cost Explorer console.
6
+
7
+ ## What's Included
8
+
9
+ ### Operational Costs
10
+ - **All AWS services** billed under `BILLING_ENTITY = "AWS"`
11
+ - **Metric:** Net Amortized Cost
12
+ - **Accounts:** Multiple linked AWS accounts
13
+ - **Date Range:** 30-day rolling window, offset by 2 days from calculation date
14
+
15
+ ### Support Costs
16
+ - **AWS Support fees** from the 1st of the month containing the end date
17
+ - **Allocation:** 50% allocation (÷2)
18
+ - **Distribution:** Divided by days in the support month (e.g., 31 for October)
19
+
20
+ ## What's Excluded
21
+
22
+ 1. **Tax** - Excluded via `RECORD_TYPE != "Tax"`
23
+ 2. **Marketplace Services** - Excluded via `BILLING_ENTITY = "AWS"` filter
24
+ - Third-party SaaS subscriptions
25
+ - Marketplace software licenses
26
+ - Non-AWS vendor services
27
+ 3. **Support** - Excluded from operational costs, calculated separately
28
+
29
+ ## Calculation Steps
30
+
31
+ ### Step 1: Date Range Calculation
32
+ ```
33
+ Today = Calculation date (e.g., Nov 4, 2025)
34
+ Offset = 2 days (default)
35
+ Window = 30 days (default)
36
+
37
+ End Date = Today - Offset = Nov 2, 2025
38
+ Start Date = End Date - Window = Oct 3, 2025
39
+
40
+ Analysis Period: Oct 3 - Nov 2, 2025 (30 days)
41
+ ```
42
+
43
+ ### Step 2: Operational Cost Calculation
44
+ ```
45
+ Total Operational Cost = Sum of daily Net Amortized Costs
46
+ WHERE:
47
+ - Linked Account IN [configured accounts]
48
+ - Billing Entity = "AWS"
49
+ - Record Type NOT IN ["Tax", "Support"]
50
+ - Date BETWEEN Start Date AND End Date
51
+
52
+ Days in Support Month = Days in month containing End Date (e.g., 31 for October)
53
+
54
+ Daily Operational = Total Operational Cost ÷ Days in Support Month
55
+ ```
56
+
57
+ **Note:** We divide by the days in the support month (31 for October), NOT the window size (30 days). This matches the AWS console calculation.
58
+
59
+ ### Step 3: Support Cost Calculation
60
+ ```
61
+ Support Date = 1st of month containing End Date (e.g., Nov 1 for Oct 3-Nov 2 period)
62
+
63
+ Support Cost = Net Amortized Cost on Support Date
64
+ WHERE:
65
+ - Linked Account IN [configured accounts]
66
+ - Record Type = "Support"
67
+ - Date = Support Date
68
+
69
+ Support Per Day = (Support Cost ÷ 2) ÷ Days in Support Month
70
+ ```
71
+
72
+ **Rationale:** Support is charged on the 1st of each month for the previous month's usage. For an October analysis, we use the November 1st support charge (which reflects October usage based on September costs).
73
+
74
+ ### Step 4: Final Calculation
75
+ ```
76
+ Daily Rate = Daily Operational + Support Per Day
77
+
78
+ Annual Projection = Daily Rate × 365
79
+ ```
80
+
81
+ ## Example Calculation
82
+
83
+ **Analysis Period:** Oct 3 - Nov 2, 2025
84
+
85
+ ```
86
+ Total Operational Cost: $450,000.00
87
+ Days in October: 31
88
+ Daily Operational: $450,000.00 ÷ 31 = $14,516.13
89
+
90
+ Support (Nov 1): $15,000.00
91
+ Support Per Day: ($15,000.00 ÷ 2) ÷ 31 = $241.94
92
+
93
+ Daily Rate: $14,516.13 + $241.94 = $14,758.07
94
+ Annual Projection: $14,758.07 × 365 = $5,386,695
95
+ ```
96
+
97
+ ## Key Principles
98
+
99
+ 1. **Consistency:** Always use the same filters and methodology
100
+ 2. **Accuracy:** Match AWS Cost Explorer console calculations exactly
101
+ 3. **Transparency:** All costs are Net Amortized (includes RI/SP discounts)
102
+ 4. **Allocation:** Support can be split between entities (default 50/50)
103
+ 5. **Timeliness:** Use T-2 offset to ensure data completeness
104
+
105
+ ## Validation
106
+
107
+ To validate the calculation:
108
+ 1. Open AWS Cost Explorer console
109
+ 2. Set date range to the analysis period
110
+ 3. Filter by your linked accounts
111
+ 4. Set Billing Entity = AWS
112
+ 5. Exclude Tax and Support
113
+ 6. Compare total: Should match "Total Operational Cost"
114
+ 7. Daily average in console ÷ 31 days should match "Daily Operational"
115
+
116
+ ## CLI Usage
117
+
118
+ ```bash
119
+ # Default calculation (today - 2 days, 30-day window)
120
+ cc calculate --profile myprofile
121
+
122
+ # Specific start date
123
+ cc calculate --profile myprofile --start-date 2025-11-04
124
+
125
+ # Custom offset and window
126
+ cc calculate --profile myprofile --offset 2 --window 30
127
+
128
+ # JSON output
129
+ cc calculate --profile myprofile --json-output
130
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Cost Optimization Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ include COST_CALCULATION_METHODOLOGY.md
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: aws-cost-calculator-cli
3
+ Version: 1.0.3
4
+ Summary: AWS Cost Calculator CLI - Calculate daily and annual AWS costs across multiple accounts
5
+ Home-page: https://github.com/yourusername/cost-calculator
6
+ Author: Cost Optimization Team
7
+ Author-email:
8
+ Project-URL: Documentation, https://github.com/yourusername/cost-calculator/blob/main/README.md
9
+ Keywords: aws cost calculator billing optimization cloud
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: Topic :: System :: Monitoring
14
+ Classifier: Topic :: Utilities
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: click>=8.0.0
25
+ Requires-Dist: boto3>=1.26.0
26
+ Dynamic: author
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: keywords
32
+ Dynamic: license-file
33
+ Dynamic: project-url
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ # AWS Cost Calculator (cc)
39
+
40
+ A CLI tool to quickly calculate AWS costs across multiple accounts.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ cd ~/cost-calculator
46
+ pip install -e .
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ### 1. Login to AWS SSO
52
+
53
+ ```bash
54
+ aws sso login --profile my_aws_profile
55
+ ```
56
+
57
+ **Note:** You need to do this before running cost calculations. The SSO session typically lasts 8-12 hours.
58
+
59
+ ### 2. Initialize a profile
60
+
61
+ ```bash
62
+ cc init --profile myprofile \
63
+ --aws-profile my_aws_profile \
64
+ --accounts "123456789012,234567890123,345678901234"
65
+ ```
66
+
67
+ ### 3. Calculate costs
68
+
69
+ ```bash
70
+ # Default: Today minus 2 days, going back 30 days
71
+ cc calculate --profile myprofile
72
+
73
+ # Specific start date
74
+ cc calculate --profile myprofile --start-date 2025-11-04
75
+
76
+ # Custom offset and window
77
+ cc calculate --profile myprofile --offset 2 --window 30
78
+
79
+ # JSON output
80
+ cc calculate --profile myprofile --json-output
81
+ ```
82
+
83
+ ### 4. List profiles
84
+
85
+ ```bash
86
+ cc list-profiles
87
+ ```
88
+
89
+ ## How It Works
90
+
91
+ ### Date Calculation
92
+ - **Start Date**: Defaults to today, or specify with `--start-date`
93
+ - **Offset**: Days to go back from start date (default: 2)
94
+ - **Window**: Number of days to analyze (default: 30)
95
+
96
+ Example: If today is Nov 4, 2025:
97
+ - With offset=2, window=30: Analyzes Oct 3 - Nov 2 (30 days)
98
+
99
+ ### Cost Calculation
100
+ 1. **Operational Costs**: Sum of daily costs ÷ window days
101
+ 2. **Support Allocation**:
102
+ - Gets support cost from the analysis month
103
+ - Divides by 2 (50% allocation)
104
+ - Divides by days in that month
105
+ 3. **Daily Rate**: Operational + Support per day
106
+ 4. **Annual Projection**: Daily rate × 365
107
+
108
+ ### Filters Applied
109
+ - **Billing Entity**: AWS only (excludes marketplace)
110
+ - **Excluded**: Tax, Support (calculated separately)
111
+ - **Metric**: Net Amortized Cost
112
+
113
+ ## Configuration
114
+
115
+ Profiles are stored in: `~/.config/cost-calculator/profiles.json`
116
+
117
+ Example:
118
+ ```json
119
+ {
120
+ "myprofile": {
121
+ "aws_profile": "my_aws_profile",
122
+ "accounts": ["123456789012", "234567890123", "345678901234"]
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## Examples
128
+
129
+ ```bash
130
+ # Quick daily check
131
+ cc calculate --profile myprofile
132
+
133
+ # Historical analysis
134
+ cc calculate --profile myprofile --start-date 2025-10-01
135
+
136
+ # Export to JSON for processing
137
+ cc calculate --profile myprofile --json-output > costs.json
138
+
139
+ # Different window size
140
+ cc calculate --profile myprofile --window 60
141
+ ```
142
+
143
+ ## Output
144
+
145
+ ```
146
+ Analyzing: 2025-10-03 to 2025-11-02 (30 days)
147
+ AWS Profile: my_aws_profile
148
+ Accounts: 3
149
+
150
+ Fetching cost data...
151
+ Fetching support costs...
152
+ ============================================================
153
+ Period: 2025-10-03 to 2025-11-02
154
+ Days analyzed: 30
155
+ ============================================================
156
+ Total operational cost: $450,000.00
157
+ Daily operational: $14,516.13
158
+ Support (month): $15,000.00
159
+ Support per day (÷2÷days): $241.94
160
+ ============================================================
161
+ DAILY RATE: $14,758.07
162
+ ANNUAL PROJECTION: $5,386,695
163
+ ============================================================
164
+ ```
@@ -0,0 +1,127 @@
1
+ # AWS Cost Calculator (cc)
2
+
3
+ A CLI tool to quickly calculate AWS costs across multiple accounts.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ cd ~/cost-calculator
9
+ pip install -e .
10
+ ```
11
+
12
+ ## Quick Start
13
+
14
+ ### 1. Login to AWS SSO
15
+
16
+ ```bash
17
+ aws sso login --profile my_aws_profile
18
+ ```
19
+
20
+ **Note:** You need to do this before running cost calculations. The SSO session typically lasts 8-12 hours.
21
+
22
+ ### 2. Initialize a profile
23
+
24
+ ```bash
25
+ cc init --profile myprofile \
26
+ --aws-profile my_aws_profile \
27
+ --accounts "123456789012,234567890123,345678901234"
28
+ ```
29
+
30
+ ### 3. Calculate costs
31
+
32
+ ```bash
33
+ # Default: Today minus 2 days, going back 30 days
34
+ cc calculate --profile myprofile
35
+
36
+ # Specific start date
37
+ cc calculate --profile myprofile --start-date 2025-11-04
38
+
39
+ # Custom offset and window
40
+ cc calculate --profile myprofile --offset 2 --window 30
41
+
42
+ # JSON output
43
+ cc calculate --profile myprofile --json-output
44
+ ```
45
+
46
+ ### 4. List profiles
47
+
48
+ ```bash
49
+ cc list-profiles
50
+ ```
51
+
52
+ ## How It Works
53
+
54
+ ### Date Calculation
55
+ - **Start Date**: Defaults to today, or specify with `--start-date`
56
+ - **Offset**: Days to go back from start date (default: 2)
57
+ - **Window**: Number of days to analyze (default: 30)
58
+
59
+ Example: If today is Nov 4, 2025:
60
+ - With offset=2, window=30: Analyzes Oct 3 - Nov 2 (30 days)
61
+
62
+ ### Cost Calculation
63
+ 1. **Operational Costs**: Sum of daily costs ÷ window days
64
+ 2. **Support Allocation**:
65
+ - Gets support cost from the analysis month
66
+ - Divides by 2 (50% allocation)
67
+ - Divides by days in that month
68
+ 3. **Daily Rate**: Operational + Support per day
69
+ 4. **Annual Projection**: Daily rate × 365
70
+
71
+ ### Filters Applied
72
+ - **Billing Entity**: AWS only (excludes marketplace)
73
+ - **Excluded**: Tax, Support (calculated separately)
74
+ - **Metric**: Net Amortized Cost
75
+
76
+ ## Configuration
77
+
78
+ Profiles are stored in: `~/.config/cost-calculator/profiles.json`
79
+
80
+ Example:
81
+ ```json
82
+ {
83
+ "myprofile": {
84
+ "aws_profile": "my_aws_profile",
85
+ "accounts": ["123456789012", "234567890123", "345678901234"]
86
+ }
87
+ }
88
+ ```
89
+
90
+ ## Examples
91
+
92
+ ```bash
93
+ # Quick daily check
94
+ cc calculate --profile myprofile
95
+
96
+ # Historical analysis
97
+ cc calculate --profile myprofile --start-date 2025-10-01
98
+
99
+ # Export to JSON for processing
100
+ cc calculate --profile myprofile --json-output > costs.json
101
+
102
+ # Different window size
103
+ cc calculate --profile myprofile --window 60
104
+ ```
105
+
106
+ ## Output
107
+
108
+ ```
109
+ Analyzing: 2025-10-03 to 2025-11-02 (30 days)
110
+ AWS Profile: my_aws_profile
111
+ Accounts: 3
112
+
113
+ Fetching cost data...
114
+ Fetching support costs...
115
+ ============================================================
116
+ Period: 2025-10-03 to 2025-11-02
117
+ Days analyzed: 30
118
+ ============================================================
119
+ Total operational cost: $450,000.00
120
+ Daily operational: $14,516.13
121
+ Support (month): $15,000.00
122
+ Support per day (÷2÷days): $241.94
123
+ ============================================================
124
+ DAILY RATE: $14,758.07
125
+ ANNUAL PROJECTION: $5,386,695
126
+ ============================================================
127
+ ```
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: aws-cost-calculator-cli
3
+ Version: 1.0.3
4
+ Summary: AWS Cost Calculator CLI - Calculate daily and annual AWS costs across multiple accounts
5
+ Home-page: https://github.com/yourusername/cost-calculator
6
+ Author: Cost Optimization Team
7
+ Author-email:
8
+ Project-URL: Documentation, https://github.com/yourusername/cost-calculator/blob/main/README.md
9
+ Keywords: aws cost calculator billing optimization cloud
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: Topic :: System :: Monitoring
14
+ Classifier: Topic :: Utilities
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: click>=8.0.0
25
+ Requires-Dist: boto3>=1.26.0
26
+ Dynamic: author
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: keywords
32
+ Dynamic: license-file
33
+ Dynamic: project-url
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ # AWS Cost Calculator (cc)
39
+
40
+ A CLI tool to quickly calculate AWS costs across multiple accounts.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ cd ~/cost-calculator
46
+ pip install -e .
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ### 1. Login to AWS SSO
52
+
53
+ ```bash
54
+ aws sso login --profile my_aws_profile
55
+ ```
56
+
57
+ **Note:** You need to do this before running cost calculations. The SSO session typically lasts 8-12 hours.
58
+
59
+ ### 2. Initialize a profile
60
+
61
+ ```bash
62
+ cc init --profile myprofile \
63
+ --aws-profile my_aws_profile \
64
+ --accounts "123456789012,234567890123,345678901234"
65
+ ```
66
+
67
+ ### 3. Calculate costs
68
+
69
+ ```bash
70
+ # Default: Today minus 2 days, going back 30 days
71
+ cc calculate --profile myprofile
72
+
73
+ # Specific start date
74
+ cc calculate --profile myprofile --start-date 2025-11-04
75
+
76
+ # Custom offset and window
77
+ cc calculate --profile myprofile --offset 2 --window 30
78
+
79
+ # JSON output
80
+ cc calculate --profile myprofile --json-output
81
+ ```
82
+
83
+ ### 4. List profiles
84
+
85
+ ```bash
86
+ cc list-profiles
87
+ ```
88
+
89
+ ## How It Works
90
+
91
+ ### Date Calculation
92
+ - **Start Date**: Defaults to today, or specify with `--start-date`
93
+ - **Offset**: Days to go back from start date (default: 2)
94
+ - **Window**: Number of days to analyze (default: 30)
95
+
96
+ Example: If today is Nov 4, 2025:
97
+ - With offset=2, window=30: Analyzes Oct 3 - Nov 2 (30 days)
98
+
99
+ ### Cost Calculation
100
+ 1. **Operational Costs**: Sum of daily costs ÷ window days
101
+ 2. **Support Allocation**:
102
+ - Gets support cost from the analysis month
103
+ - Divides by 2 (50% allocation)
104
+ - Divides by days in that month
105
+ 3. **Daily Rate**: Operational + Support per day
106
+ 4. **Annual Projection**: Daily rate × 365
107
+
108
+ ### Filters Applied
109
+ - **Billing Entity**: AWS only (excludes marketplace)
110
+ - **Excluded**: Tax, Support (calculated separately)
111
+ - **Metric**: Net Amortized Cost
112
+
113
+ ## Configuration
114
+
115
+ Profiles are stored in: `~/.config/cost-calculator/profiles.json`
116
+
117
+ Example:
118
+ ```json
119
+ {
120
+ "myprofile": {
121
+ "aws_profile": "my_aws_profile",
122
+ "accounts": ["123456789012", "234567890123", "345678901234"]
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## Examples
128
+
129
+ ```bash
130
+ # Quick daily check
131
+ cc calculate --profile myprofile
132
+
133
+ # Historical analysis
134
+ cc calculate --profile myprofile --start-date 2025-10-01
135
+
136
+ # Export to JSON for processing
137
+ cc calculate --profile myprofile --json-output > costs.json
138
+
139
+ # Different window size
140
+ cc calculate --profile myprofile --window 60
141
+ ```
142
+
143
+ ## Output
144
+
145
+ ```
146
+ Analyzing: 2025-10-03 to 2025-11-02 (30 days)
147
+ AWS Profile: my_aws_profile
148
+ Accounts: 3
149
+
150
+ Fetching cost data...
151
+ Fetching support costs...
152
+ ============================================================
153
+ Period: 2025-10-03 to 2025-11-02
154
+ Days analyzed: 30
155
+ ============================================================
156
+ Total operational cost: $450,000.00
157
+ Daily operational: $14,516.13
158
+ Support (month): $15,000.00
159
+ Support per day (÷2÷days): $241.94
160
+ ============================================================
161
+ DAILY RATE: $14,758.07
162
+ ANNUAL PROJECTION: $5,386,695
163
+ ============================================================
164
+ ```
@@ -0,0 +1,13 @@
1
+ COST_CALCULATION_METHODOLOGY.md
2
+ LICENSE
3
+ MANIFEST.in
4
+ README.md
5
+ setup.py
6
+ aws_cost_calculator_cli.egg-info/PKG-INFO
7
+ aws_cost_calculator_cli.egg-info/SOURCES.txt
8
+ aws_cost_calculator_cli.egg-info/dependency_links.txt
9
+ aws_cost_calculator_cli.egg-info/entry_points.txt
10
+ aws_cost_calculator_cli.egg-info/requires.txt
11
+ aws_cost_calculator_cli.egg-info/top_level.txt
12
+ cost_calculator/__init__.py
13
+ cost_calculator/cli.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cc = cost_calculator.cli:cli
@@ -0,0 +1,2 @@
1
+ click>=8.0.0
2
+ boto3>=1.26.0
@@ -0,0 +1,2 @@
1
+ """AWS Cost Calculator CLI"""
2
+ __version__ = '1.0.0'
@@ -0,0 +1,543 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AWS Cost Calculator CLI
4
+
5
+ Usage:
6
+ cc --profile myprofile
7
+ cc --profile myprofile --start-date 2025-11-04
8
+ cc --profile myprofile --offset 2 --window 30
9
+ """
10
+
11
+ import click
12
+ import boto3
13
+ import json
14
+ from datetime import datetime, timedelta
15
+ from pathlib import Path
16
+
17
+
18
+ def load_profile(profile_name):
19
+ """Load profile configuration from ~/.config/cost-calculator/profiles.json"""
20
+ config_dir = Path.home() / '.config' / 'cost-calculator'
21
+ config_file = config_dir / 'profiles.json'
22
+ creds_file = config_dir / 'credentials.json'
23
+
24
+ if not config_file.exists():
25
+ raise click.ClickException(
26
+ f"Profile configuration not found at {config_file}\n"
27
+ f"Run: cc init --profile {profile_name}"
28
+ )
29
+
30
+ with open(config_file) as f:
31
+ profiles = json.load(f)
32
+
33
+ if profile_name not in profiles:
34
+ raise click.ClickException(
35
+ f"Profile '{profile_name}' not found in {config_file}\n"
36
+ f"Available profiles: {', '.join(profiles.keys())}"
37
+ )
38
+
39
+ profile = profiles[profile_name]
40
+
41
+ # Load credentials if using static credentials (not SSO)
42
+ if 'aws_profile' not in profile:
43
+ if not creds_file.exists():
44
+ raise click.ClickException(
45
+ f"No credentials found for profile '{profile_name}'.\n"
46
+ f"Run: cc configure --profile {profile_name}"
47
+ )
48
+
49
+ with open(creds_file) as f:
50
+ creds = json.load(f)
51
+
52
+ if profile_name not in creds:
53
+ raise click.ClickException(
54
+ f"No credentials found for profile '{profile_name}'.\n"
55
+ f"Run: cc configure --profile {profile_name}"
56
+ )
57
+
58
+ profile['credentials'] = creds[profile_name]
59
+
60
+ return profile
61
+
62
+
63
+ def calculate_costs(profile_config, accounts, start_date, offset, window):
64
+ """
65
+ Calculate AWS costs for the specified period.
66
+
67
+ Args:
68
+ profile_config: Profile configuration (with aws_profile or credentials)
69
+ accounts: List of AWS account IDs
70
+ start_date: Start date (defaults to today)
71
+ offset: Days to go back from start_date (default: 2)
72
+ window: Number of days to analyze (default: 30)
73
+
74
+ Returns:
75
+ dict with cost breakdown
76
+ """
77
+ # Calculate date range
78
+ if start_date:
79
+ end_date = datetime.strptime(start_date, '%Y-%m-%d')
80
+ else:
81
+ end_date = datetime.now()
82
+
83
+ # Go back by offset days
84
+ end_date = end_date - timedelta(days=offset)
85
+
86
+ # Start date is window days before end_date
87
+ start_date_calc = end_date - timedelta(days=window)
88
+
89
+ # Format for API (end date is exclusive, so add 1 day)
90
+ api_start = start_date_calc.strftime('%Y-%m-%d')
91
+ api_end = (end_date + timedelta(days=1)).strftime('%Y-%m-%d')
92
+
93
+ click.echo(f"Analyzing: {api_start} to {end_date.strftime('%Y-%m-%d')} ({window} days)")
94
+
95
+ # Initialize boto3 client
96
+ try:
97
+ if 'aws_profile' in profile_config:
98
+ # SSO-based authentication
99
+ aws_profile = profile_config['aws_profile']
100
+ click.echo(f"AWS Profile: {aws_profile} (SSO)")
101
+ click.echo(f"Accounts: {len(accounts)}")
102
+ click.echo("")
103
+ session = boto3.Session(profile_name=aws_profile)
104
+ ce_client = session.client('ce', region_name='us-east-1')
105
+ else:
106
+ # Static credentials
107
+ creds = profile_config['credentials']
108
+ click.echo(f"AWS Credentials: Static")
109
+ click.echo(f"Accounts: {len(accounts)}")
110
+ click.echo("")
111
+
112
+ session_kwargs = {
113
+ 'aws_access_key_id': creds['aws_access_key_id'],
114
+ 'aws_secret_access_key': creds['aws_secret_access_key'],
115
+ 'region_name': creds.get('region', 'us-east-1')
116
+ }
117
+
118
+ if 'aws_session_token' in creds:
119
+ session_kwargs['aws_session_token'] = creds['aws_session_token']
120
+
121
+ session = boto3.Session(**session_kwargs)
122
+ ce_client = session.client('ce')
123
+
124
+ except Exception as e:
125
+ if 'Token has expired' in str(e) or 'sso' in str(e).lower():
126
+ if 'aws_profile' in profile_config:
127
+ raise click.ClickException(
128
+ f"AWS SSO session expired or not initialized.\n"
129
+ f"Run: aws sso login --profile {profile_config['aws_profile']}"
130
+ )
131
+ else:
132
+ raise click.ClickException(
133
+ f"AWS credentials expired.\n"
134
+ f"Run: cc configure --profile <profile_name>"
135
+ )
136
+ raise
137
+
138
+ # Build filter
139
+ cost_filter = {
140
+ "And": [
141
+ {
142
+ "Dimensions": {
143
+ "Key": "LINKED_ACCOUNT",
144
+ "Values": accounts
145
+ }
146
+ },
147
+ {
148
+ "Dimensions": {
149
+ "Key": "BILLING_ENTITY",
150
+ "Values": ["AWS"]
151
+ }
152
+ },
153
+ {
154
+ "Not": {
155
+ "Dimensions": {
156
+ "Key": "RECORD_TYPE",
157
+ "Values": ["Tax", "Support"]
158
+ }
159
+ }
160
+ }
161
+ ]
162
+ }
163
+
164
+ # Get daily costs
165
+ click.echo("Fetching cost data...")
166
+ try:
167
+ response = ce_client.get_cost_and_usage(
168
+ TimePeriod={
169
+ 'Start': api_start,
170
+ 'End': api_end
171
+ },
172
+ Granularity='DAILY',
173
+ Metrics=['NetAmortizedCost'],
174
+ Filter=cost_filter
175
+ )
176
+ except Exception as e:
177
+ if 'Token has expired' in str(e) or 'expired' in str(e).lower():
178
+ raise click.ClickException(
179
+ f"AWS SSO session expired.\n"
180
+ f"Run: aws sso login --profile {aws_profile}"
181
+ )
182
+ raise
183
+
184
+ # Calculate total
185
+ total_cost = sum(
186
+ float(day['Total']['NetAmortizedCost']['Amount'])
187
+ for day in response['ResultsByTime']
188
+ )
189
+
190
+ # Get support cost from the 1st of the month containing the end date
191
+ # Support is charged on the 1st of each month for the previous month's usage
192
+ # For Oct 3-Nov 2 analysis, we get support from Nov 1 (which is October's support)
193
+ support_month_date = end_date.replace(day=1)
194
+ support_date_str = support_month_date.strftime('%Y-%m-%d')
195
+ support_date_end = (support_month_date + timedelta(days=1)).strftime('%Y-%m-%d')
196
+
197
+ click.echo("Fetching support costs...")
198
+ support_response = ce_client.get_cost_and_usage(
199
+ TimePeriod={
200
+ 'Start': support_date_str,
201
+ 'End': support_date_end
202
+ },
203
+ Granularity='DAILY',
204
+ Metrics=['NetAmortizedCost'],
205
+ Filter={
206
+ "And": [
207
+ {
208
+ "Dimensions": {
209
+ "Key": "LINKED_ACCOUNT",
210
+ "Values": accounts
211
+ }
212
+ },
213
+ {
214
+ "Dimensions": {
215
+ "Key": "RECORD_TYPE",
216
+ "Values": ["Support"]
217
+ }
218
+ }
219
+ ]
220
+ }
221
+ )
222
+
223
+ support_cost = float(support_response['ResultsByTime'][0]['Total']['NetAmortizedCost']['Amount'])
224
+
225
+ # Calculate days in the month that the support covers
226
+ # Support on Nov 1 covers October (31 days)
227
+ support_month = support_month_date - timedelta(days=1) # Go back to previous month
228
+ days_in_support_month = support_month.day # This gives us the last day of the month
229
+
230
+ # Support allocation: divide by 2 (half to Khoros), then by days in month
231
+ support_per_day = (support_cost / 2) / days_in_support_month
232
+
233
+ # Calculate daily rate
234
+ # NOTE: We divide operational by window, but support by days_in_support_month
235
+ # This matches the console's calculation method
236
+ daily_operational = total_cost / days_in_support_month # Use 31 for October, not 30
237
+ daily_total = daily_operational + support_per_day
238
+
239
+ # Annual projection
240
+ annual = daily_total * 365
241
+
242
+ return {
243
+ 'period': {
244
+ 'start': api_start,
245
+ 'end': end_date.strftime('%Y-%m-%d'),
246
+ 'days': window
247
+ },
248
+ 'costs': {
249
+ 'total_operational': total_cost,
250
+ 'daily_operational': daily_operational,
251
+ 'support_month': support_cost,
252
+ 'support_per_day': support_per_day,
253
+ 'daily_total': daily_total,
254
+ 'annual_projection': annual
255
+ }
256
+ }
257
+
258
+
259
+ @click.group()
260
+ def cli():
261
+ """
262
+ AWS Cost Calculator - Calculate daily and annual AWS costs
263
+
264
+ \b
265
+ Two authentication methods:
266
+ 1. AWS SSO (recommended for interactive use)
267
+ 2. Static credentials (for automation/CI)
268
+
269
+ \b
270
+ Quick Start:
271
+ # SSO Method
272
+ aws sso login --profile my_aws_profile
273
+ cc init --profile myprofile --aws-profile my_aws_profile --accounts "123,456,789"
274
+ cc calculate --profile myprofile
275
+
276
+ # Static Credentials Method
277
+ cc init --profile myprofile --aws-profile dummy --accounts "123,456,789"
278
+ cc configure --profile myprofile
279
+ cc calculate --profile myprofile
280
+
281
+ \b
282
+ For detailed documentation, see:
283
+ - COST_CALCULATION_METHODOLOGY.md
284
+ - README.md
285
+ """
286
+ pass
287
+
288
+
289
+ @cli.command()
290
+ @click.option('--profile', required=True, help='Profile name (e.g., myprofile)')
291
+ @click.option('--start-date', help='Start date (YYYY-MM-DD, default: today)')
292
+ @click.option('--offset', default=2, help='Days to go back from start date (default: 2)')
293
+ @click.option('--window', default=30, help='Number of days to analyze (default: 30)')
294
+ @click.option('--json-output', is_flag=True, help='Output as JSON')
295
+ def calculate(profile, start_date, offset, window, json_output):
296
+ """Calculate AWS costs for the specified period"""
297
+
298
+ # Load profile configuration
299
+ config = load_profile(profile)
300
+
301
+ # Calculate costs
302
+ result = calculate_costs(
303
+ profile_config=config,
304
+ accounts=config['accounts'],
305
+ start_date=start_date,
306
+ offset=offset,
307
+ window=window
308
+ )
309
+
310
+ if json_output:
311
+ click.echo(json.dumps(result, indent=2))
312
+ else:
313
+ # Pretty print results
314
+ click.echo("=" * 60)
315
+ click.echo(f"Period: {result['period']['start']} to {result['period']['end']}")
316
+ click.echo(f"Days analyzed: {result['period']['days']}")
317
+ click.echo("=" * 60)
318
+ click.echo(f"Total operational cost: ${result['costs']['total_operational']:,.2f}")
319
+ click.echo(f"Daily operational: ${result['costs']['daily_operational']:,.2f}")
320
+ click.echo(f"Support (month): ${result['costs']['support_month']:,.2f}")
321
+ click.echo(f"Support per day (÷2÷days): ${result['costs']['support_per_day']:,.2f}")
322
+ click.echo("=" * 60)
323
+ click.echo(f"DAILY RATE: ${result['costs']['daily_total']:,.2f}")
324
+ click.echo(f"ANNUAL PROJECTION: ${result['costs']['annual_projection']:,.0f}")
325
+ click.echo("=" * 60)
326
+
327
+
328
+ @cli.command()
329
+ @click.option('--profile', required=True, help='Profile name to create')
330
+ @click.option('--aws-profile', required=True, help='AWS CLI profile name')
331
+ @click.option('--accounts', required=True, help='Comma-separated list of account IDs')
332
+ def init(profile, aws_profile, accounts):
333
+ """Initialize a new profile configuration"""
334
+
335
+ config_dir = Path.home() / '.config' / 'cost-calculator'
336
+ config_file = config_dir / 'profiles.json'
337
+
338
+ # Create config directory if it doesn't exist
339
+ config_dir.mkdir(parents=True, exist_ok=True)
340
+
341
+ # Load existing profiles or create new
342
+ if config_file.exists() and config_file.stat().st_size > 0:
343
+ try:
344
+ with open(config_file) as f:
345
+ profiles = json.load(f)
346
+ except json.JSONDecodeError:
347
+ profiles = {}
348
+ else:
349
+ profiles = {}
350
+
351
+ # Parse accounts
352
+ account_list = [acc.strip() for acc in accounts.split(',')]
353
+
354
+ # Add new profile
355
+ profiles[profile] = {
356
+ 'aws_profile': aws_profile,
357
+ 'accounts': account_list
358
+ }
359
+
360
+ # Save
361
+ with open(config_file, 'w') as f:
362
+ json.dump(profiles, f, indent=2)
363
+
364
+ click.echo(f"✓ Profile '{profile}' created with {len(account_list)} accounts")
365
+ click.echo(f"✓ Configuration saved to {config_file}")
366
+ click.echo(f"\nUsage: cc calculate --profile {profile}")
367
+
368
+
369
+ @cli.command()
370
+ def list_profiles():
371
+ """List all configured profiles"""
372
+
373
+ config_file = Path.home() / '.config' / 'cost-calculator' / 'profiles.json'
374
+
375
+ if not config_file.exists():
376
+ click.echo("No profiles configured. Run: cc init --profile <name>")
377
+ return
378
+
379
+ with open(config_file) as f:
380
+ profiles = json.load(f)
381
+
382
+ if not profiles:
383
+ click.echo("No profiles configured.")
384
+ return
385
+
386
+ click.echo("Configured profiles:")
387
+ click.echo("")
388
+ for name, config in profiles.items():
389
+ click.echo(f" {name}")
390
+ if 'aws_profile' in config:
391
+ click.echo(f" AWS Profile: {config['aws_profile']} (SSO)")
392
+ else:
393
+ click.echo(f" AWS Credentials: Configured (Static)")
394
+ click.echo(f" Accounts: {len(config['accounts'])}")
395
+ click.echo("")
396
+
397
+
398
+ @cli.command()
399
+ def setup():
400
+ """Show setup instructions for manual profile configuration"""
401
+ import platform
402
+
403
+ system = platform.system()
404
+
405
+ if system == "Windows":
406
+ config_path = "%USERPROFILE%\\.config\\cost-calculator\\profiles.json"
407
+ config_path_example = "C:\\Users\\YourName\\.config\\cost-calculator\\profiles.json"
408
+ mkdir_cmd = "mkdir %USERPROFILE%\\.config\\cost-calculator"
409
+ edit_cmd = "notepad %USERPROFILE%\\.config\\cost-calculator\\profiles.json"
410
+ else: # macOS/Linux
411
+ config_path = "~/.config/cost-calculator/profiles.json"
412
+ config_path_example = "/Users/yourname/.config/cost-calculator/profiles.json"
413
+ mkdir_cmd = "mkdir -p ~/.config/cost-calculator"
414
+ edit_cmd = "nano ~/.config/cost-calculator/profiles.json"
415
+
416
+ click.echo("=" * 70)
417
+ click.echo("AWS Cost Calculator - Manual Profile Setup")
418
+ click.echo("=" * 70)
419
+ click.echo("")
420
+ click.echo(f"Platform: {system}")
421
+ click.echo(f"Config location: {config_path}")
422
+ click.echo("")
423
+ click.echo("Step 1: Create the config directory")
424
+ click.echo(f" {mkdir_cmd}")
425
+ click.echo("")
426
+ click.echo("Step 2: Create the profiles.json file")
427
+ click.echo(f" {edit_cmd}")
428
+ click.echo("")
429
+ click.echo("Step 3: Add your profile configuration (JSON format):")
430
+ click.echo("")
431
+ click.echo(' {')
432
+ click.echo(' "myprofile": {')
433
+ click.echo(' "aws_profile": "my_aws_profile",')
434
+ click.echo(' "accounts": [')
435
+ click.echo(' "123456789012",')
436
+ click.echo(' "234567890123",')
437
+ click.echo(' "345678901234"')
438
+ click.echo(' ]')
439
+ click.echo(' }')
440
+ click.echo(' }')
441
+ click.echo("")
442
+ click.echo("Step 4: Save the file")
443
+ click.echo("")
444
+ click.echo("Step 5: Verify it works")
445
+ click.echo(" cc list-profiles")
446
+ click.echo("")
447
+ click.echo("Step 6: Configure AWS credentials")
448
+ click.echo(" Option A (SSO):")
449
+ click.echo(" aws sso login --profile my_aws_profile")
450
+ click.echo(" cc calculate --profile myprofile")
451
+ click.echo("")
452
+ click.echo(" Option B (Static credentials):")
453
+ click.echo(" cc configure --profile myprofile")
454
+ click.echo(" cc calculate --profile myprofile")
455
+ click.echo("")
456
+ click.echo("=" * 70)
457
+ click.echo("")
458
+ click.echo("For multiple profiles, add more entries to the JSON:")
459
+ click.echo("")
460
+ click.echo(' {')
461
+ click.echo(' "profile1": { ... },')
462
+ click.echo(' "profile2": { ... }')
463
+ click.echo(' }')
464
+ click.echo("")
465
+ click.echo(f"Full path example: {config_path_example}")
466
+ click.echo("=" * 70)
467
+
468
+
469
+ @cli.command()
470
+ @click.option('--profile', required=True, help='Profile name to configure')
471
+ @click.option('--access-key-id', prompt=True, hide_input=False, help='AWS Access Key ID')
472
+ @click.option('--secret-access-key', prompt=True, hide_input=True, help='AWS Secret Access Key')
473
+ @click.option('--session-token', default='', help='AWS Session Token (optional, for temporary credentials)')
474
+ @click.option('--region', default='us-east-1', help='AWS Region (default: us-east-1)')
475
+ def configure(profile, access_key_id, secret_access_key, session_token, region):
476
+ """Configure AWS credentials for a profile (alternative to SSO)"""
477
+
478
+ config_dir = Path.home() / '.config' / 'cost-calculator'
479
+ config_file = config_dir / 'profiles.json'
480
+ creds_file = config_dir / 'credentials.json'
481
+
482
+ # Create config directory if it doesn't exist
483
+ config_dir.mkdir(parents=True, exist_ok=True)
484
+
485
+ # Load existing profiles
486
+ if config_file.exists() and config_file.stat().st_size > 0:
487
+ try:
488
+ with open(config_file) as f:
489
+ profiles = json.load(f)
490
+ except json.JSONDecodeError:
491
+ profiles = {}
492
+ else:
493
+ profiles = {}
494
+
495
+ # Check if profile exists
496
+ if profile not in profiles:
497
+ click.echo(f"Error: Profile '{profile}' not found. Create it first with: cc init --profile {profile}")
498
+ return
499
+
500
+ # Remove aws_profile if it exists (switching from SSO to static creds)
501
+ if 'aws_profile' in profiles[profile]:
502
+ del profiles[profile]['aws_profile']
503
+
504
+ # Save updated profile
505
+ with open(config_file, 'w') as f:
506
+ json.dump(profiles, f, indent=2)
507
+
508
+ # Load or create credentials file
509
+ if creds_file.exists() and creds_file.stat().st_size > 0:
510
+ try:
511
+ with open(creds_file) as f:
512
+ creds = json.load(f)
513
+ except json.JSONDecodeError:
514
+ creds = {}
515
+ else:
516
+ creds = {}
517
+
518
+ # Store credentials (encrypted would be better, but for now just file permissions)
519
+ creds[profile] = {
520
+ 'aws_access_key_id': access_key_id,
521
+ 'aws_secret_access_key': secret_access_key,
522
+ 'region': region
523
+ }
524
+
525
+ if session_token:
526
+ creds[profile]['aws_session_token'] = session_token
527
+
528
+ # Save credentials with restricted permissions
529
+ with open(creds_file, 'w') as f:
530
+ json.dump(creds, f, indent=2)
531
+
532
+ # Set file permissions to 600 (owner read/write only)
533
+ creds_file.chmod(0o600)
534
+
535
+ click.echo(f"✓ AWS credentials configured for profile '{profile}'")
536
+ click.echo(f"✓ Credentials saved to {creds_file} (permissions: 600)")
537
+ click.echo(f"\nUsage: cc calculate --profile {profile}")
538
+ click.echo("\nNote: Credentials are stored locally. For temporary credentials,")
539
+ click.echo(" you'll need to reconfigure when they expire.")
540
+
541
+
542
+ if __name__ == '__main__':
543
+ cli()
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,43 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name='aws-cost-calculator-cli',
8
+ version='1.0.3',
9
+ packages=find_packages(),
10
+ install_requires=[
11
+ 'click>=8.0.0',
12
+ 'boto3>=1.26.0',
13
+ ],
14
+ entry_points={
15
+ 'console_scripts': [
16
+ 'cc=cost_calculator.cli:cli',
17
+ ],
18
+ },
19
+ author='Cost Optimization Team',
20
+ author_email='',
21
+ description='AWS Cost Calculator CLI - Calculate daily and annual AWS costs across multiple accounts',
22
+ long_description=long_description,
23
+ long_description_content_type="text/markdown",
24
+ url='https://github.com/yourusername/cost-calculator',
25
+ project_urls={
26
+ 'Documentation': 'https://github.com/yourusername/cost-calculator/blob/main/README.md',
27
+ },
28
+ classifiers=[
29
+ 'Development Status :: 4 - Beta',
30
+ 'Intended Audience :: Developers',
31
+ 'Intended Audience :: System Administrators',
32
+ 'Topic :: System :: Monitoring',
33
+ 'Topic :: Utilities',
34
+ 'License :: OSI Approved :: MIT License',
35
+ 'Programming Language :: Python :: 3',
36
+ 'Programming Language :: Python :: 3.8',
37
+ 'Programming Language :: Python :: 3.9',
38
+ 'Programming Language :: Python :: 3.10',
39
+ 'Programming Language :: Python :: 3.11',
40
+ ],
41
+ python_requires='>=3.8',
42
+ keywords='aws cost calculator billing optimization cloud',
43
+ )