infra-cost 0.1.0 → 0.2.1
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.
- package/README.md +362 -0
- package/dist/demo/test-enhanced-ui.cjs +1697 -0
- package/dist/demo/test-enhanced-ui.cjs.map +1 -0
- package/dist/demo/test-multi-cloud-dashboard.cjs +3386 -0
- package/dist/demo/test-multi-cloud-dashboard.cjs.map +1 -0
- package/dist/index.cjs +19083 -0
- package/dist/index.cjs.map +1 -0
- package/package.json +94 -19
- package/dist/index.js +0 -487
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infra-cost",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Multi-cloud FinOps CLI tool for comprehensive cost analysis and infrastructure optimization across AWS, GCP, Azure, Alibaba Cloud, and Oracle Cloud",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"author":
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Code Collab",
|
|
8
|
+
"email": "codecollab.co@gmail.com",
|
|
9
|
+
"url": "https://github.com/codecollab-co/infra-cost"
|
|
10
|
+
},
|
|
7
11
|
"files": [
|
|
8
12
|
"!tests/**/*",
|
|
9
13
|
"dist/**/*",
|
|
@@ -11,7 +15,8 @@
|
|
|
11
15
|
"bin/**/*"
|
|
12
16
|
],
|
|
13
17
|
"bin": {
|
|
14
|
-
"
|
|
18
|
+
"infra-cost": "./bin/index.js",
|
|
19
|
+
"aws-cost": "./bin/index.js"
|
|
15
20
|
},
|
|
16
21
|
"scripts": {
|
|
17
22
|
"build": "tsup",
|
|
@@ -20,22 +25,40 @@
|
|
|
20
25
|
"predev": "run-s clean",
|
|
21
26
|
"clean": "rm -rf dist",
|
|
22
27
|
"typecheck": "tsc --noEmit",
|
|
23
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
28
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
29
|
+
"version:check": "node scripts/version-manager.js check",
|
|
30
|
+
"version:next": "node scripts/version-manager.js next",
|
|
31
|
+
"version:bump:patch": "node scripts/version-manager.js bump patch",
|
|
32
|
+
"version:bump:minor": "node scripts/version-manager.js bump minor",
|
|
33
|
+
"version:bump:major": "node scripts/version-manager.js bump major",
|
|
34
|
+
"version:bump:prerelease": "node scripts/version-manager.js bump prerelease",
|
|
35
|
+
"version:set": "node scripts/version-manager.js set",
|
|
36
|
+
"publish:dry": "npm publish --dry-run",
|
|
37
|
+
"publish:beta": "npm publish --tag beta",
|
|
38
|
+
"publish:latest": "npm publish --tag latest",
|
|
39
|
+
"prepare-release": "./scripts/prepare-release.sh",
|
|
40
|
+
"prepublishOnly": "npm run build",
|
|
41
|
+
"postversion": "echo \"Don't forget to push the tag: git push origin v$npm_package_version\"",
|
|
42
|
+
"postpublish": "echo \"🎉 Published $(npm pkg get name)@$(npm pkg get version) to npm!\""
|
|
24
43
|
},
|
|
25
44
|
"keywords": [
|
|
26
45
|
"aws",
|
|
46
|
+
"gcp",
|
|
47
|
+
"azure",
|
|
48
|
+
"alibaba-cloud",
|
|
49
|
+
"oracle-cloud",
|
|
50
|
+
"multi-cloud",
|
|
27
51
|
"cost",
|
|
28
52
|
"cli",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"typescript"
|
|
33
|
-
"aws cli"
|
|
53
|
+
"infra-cost",
|
|
54
|
+
"cloud-cost",
|
|
55
|
+
"cost-analysis",
|
|
56
|
+
"typescript"
|
|
34
57
|
],
|
|
35
58
|
"license": "MIT",
|
|
36
59
|
"repository": {
|
|
37
60
|
"type": "git",
|
|
38
|
-
"url": "
|
|
61
|
+
"url": "https://github.com/codecollab-co/infra-cost.git"
|
|
39
62
|
},
|
|
40
63
|
"engines": {
|
|
41
64
|
"node": ">=12.0"
|
|
@@ -45,23 +68,75 @@
|
|
|
45
68
|
},
|
|
46
69
|
"homepage": "https://github.com/codecollab-co/infra-cost.git#readme",
|
|
47
70
|
"dependencies": {
|
|
48
|
-
"@aws-sdk/
|
|
49
|
-
"aws-sdk": "^
|
|
71
|
+
"@aws-sdk/client-budgets": "^3.908.0",
|
|
72
|
+
"@aws-sdk/client-cost-explorer": "^3.370.0",
|
|
73
|
+
"@aws-sdk/client-ec2": "^3.909.0",
|
|
74
|
+
"@aws-sdk/client-iam": "^3.370.0",
|
|
75
|
+
"@aws-sdk/client-lambda": "^3.908.0",
|
|
76
|
+
"@aws-sdk/client-rds": "^3.908.0",
|
|
77
|
+
"@aws-sdk/client-s3": "^3.908.0",
|
|
78
|
+
"@aws-sdk/client-sts": "^3.370.0",
|
|
79
|
+
"@aws-sdk/credential-providers": "^3.370.0",
|
|
80
|
+
"@aws-sdk/shared-ini-file-loader": "^3.370.0",
|
|
81
|
+
"axios": "^1.12.2",
|
|
50
82
|
"chalk": "^5.2.0",
|
|
83
|
+
"cli-progress": "^3.12.0",
|
|
84
|
+
"cli-table3": "^0.6.5",
|
|
51
85
|
"commander": "^10.0.0",
|
|
52
86
|
"dayjs": "^1.11.7",
|
|
53
87
|
"dotenv": "^16.0.3",
|
|
88
|
+
"exceljs": "^4.4.0",
|
|
89
|
+
"express": "^5.1.0",
|
|
90
|
+
"moment": "^2.30.1",
|
|
54
91
|
"node-fetch": "^3.3.0",
|
|
55
|
-
"ora": "^6.1.2"
|
|
92
|
+
"ora": "^6.1.2",
|
|
93
|
+
"puppeteer": "^24.24.1",
|
|
94
|
+
"xlsx": "^0.18.5"
|
|
56
95
|
},
|
|
57
96
|
"devDependencies": {
|
|
97
|
+
"@types/cli-progress": "^3.11.6",
|
|
98
|
+
"@types/express": "^5.0.3",
|
|
99
|
+
"@types/jest": "^30.0.0",
|
|
58
100
|
"@types/node": "^18.11.18",
|
|
101
|
+
"@types/supertest": "^6.0.3",
|
|
102
|
+
"jest": "^30.2.0",
|
|
59
103
|
"npm-run-all": "^4.1.5",
|
|
60
|
-
"
|
|
104
|
+
"supertest": "^7.1.4",
|
|
105
|
+
"ts-jest": "^29.4.5",
|
|
61
106
|
"ts-node": "^10.9.1",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
107
|
+
"tsup": "^6.5.0",
|
|
108
|
+
"typescript": "^4.9.4"
|
|
109
|
+
},
|
|
110
|
+
"peerDependencies": {
|
|
111
|
+
"@alicloud/pop-core": "^1.7.12",
|
|
112
|
+
"@azure/arm-costmanagement": "^1.0.0",
|
|
113
|
+
"@azure/arm-subscriptions": "^5.1.0",
|
|
114
|
+
"@azure/identity": "^3.1.0",
|
|
115
|
+
"@google-cloud/billing": "^4.1.0",
|
|
116
|
+
"@google-cloud/resource-manager": "^5.1.0",
|
|
117
|
+
"oci-sdk": "^2.69.0"
|
|
65
118
|
},
|
|
66
|
-
"
|
|
119
|
+
"peerDependenciesMeta": {
|
|
120
|
+
"@google-cloud/billing": {
|
|
121
|
+
"optional": true
|
|
122
|
+
},
|
|
123
|
+
"@google-cloud/resource-manager": {
|
|
124
|
+
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"@azure/arm-costmanagement": {
|
|
127
|
+
"optional": true
|
|
128
|
+
},
|
|
129
|
+
"@azure/arm-subscriptions": {
|
|
130
|
+
"optional": true
|
|
131
|
+
},
|
|
132
|
+
"@azure/identity": {
|
|
133
|
+
"optional": true
|
|
134
|
+
},
|
|
135
|
+
"@alicloud/pop-core": {
|
|
136
|
+
"optional": true
|
|
137
|
+
},
|
|
138
|
+
"oci-sdk": {
|
|
139
|
+
"optional": true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
67
142
|
}
|
package/dist/index.js
DELETED
|
@@ -1,487 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
|
|
4
|
-
// package.json
|
|
5
|
-
var package_default = {
|
|
6
|
-
name: "infra-cost",
|
|
7
|
-
version: "0.1.0",
|
|
8
|
-
description: "A CLI tool to perform cost analysis on your infra account's cost",
|
|
9
|
-
type: "module",
|
|
10
|
-
author: {
|
|
11
|
-
name: "Code Collab",
|
|
12
|
-
email: "codecollab.co@gmail.com",
|
|
13
|
-
url: "https://github.com/codecollab-co/infra-cost"
|
|
14
|
-
},
|
|
15
|
-
files: [
|
|
16
|
-
"!tests/**/*",
|
|
17
|
-
"dist/**/*",
|
|
18
|
-
"!dist/**/*.js.map",
|
|
19
|
-
"bin/**/*"
|
|
20
|
-
],
|
|
21
|
-
bin: {
|
|
22
|
-
"aws-cost": "./bin/index.js"
|
|
23
|
-
},
|
|
24
|
-
scripts: {
|
|
25
|
-
build: "tsup",
|
|
26
|
-
dev: "tsup --watch",
|
|
27
|
-
prebuild: "run-s clean",
|
|
28
|
-
predev: "run-s clean",
|
|
29
|
-
clean: "rm -rf dist",
|
|
30
|
-
typecheck: "tsc --noEmit",
|
|
31
|
-
test: 'echo "Error: no test specified" && exit 1'
|
|
32
|
-
},
|
|
33
|
-
keywords: [
|
|
34
|
-
"aws",
|
|
35
|
-
"cost",
|
|
36
|
-
"cli",
|
|
37
|
-
"aws-cost",
|
|
38
|
-
"aws-cost-cli",
|
|
39
|
-
"aws-costs",
|
|
40
|
-
"typescript",
|
|
41
|
-
"aws cli"
|
|
42
|
-
],
|
|
43
|
-
license: "MIT",
|
|
44
|
-
repository: {
|
|
45
|
-
type: "git",
|
|
46
|
-
url: "https://github.com/codecollab-co/infra-cost.git"
|
|
47
|
-
},
|
|
48
|
-
engines: {
|
|
49
|
-
node: ">=12.0"
|
|
50
|
-
},
|
|
51
|
-
bugs: {
|
|
52
|
-
url: "https://github.com/codecollab-co/infra-cost/issues"
|
|
53
|
-
},
|
|
54
|
-
homepage: "https://github.com/codecollab-co/infra-cost.git#readme",
|
|
55
|
-
dependencies: {
|
|
56
|
-
"@aws-sdk/shared-ini-file-loader": "^3.254.0",
|
|
57
|
-
"aws-sdk": "^2.1299.0",
|
|
58
|
-
chalk: "^5.2.0",
|
|
59
|
-
commander: "^10.0.0",
|
|
60
|
-
dayjs: "^1.11.7",
|
|
61
|
-
dotenv: "^16.0.3",
|
|
62
|
-
"node-fetch": "^3.3.0",
|
|
63
|
-
ora: "^6.1.2"
|
|
64
|
-
},
|
|
65
|
-
devDependencies: {
|
|
66
|
-
"@types/node": "^18.11.18",
|
|
67
|
-
"npm-run-all": "^4.1.5",
|
|
68
|
-
"ts-node": "^10.9.1",
|
|
69
|
-
tsup: "^8.4.0",
|
|
70
|
-
typescript: "^4.9.4"
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// src/account.ts
|
|
75
|
-
import AWS from "aws-sdk";
|
|
76
|
-
|
|
77
|
-
// src/logger.ts
|
|
78
|
-
import chalk from "chalk";
|
|
79
|
-
import ora from "ora";
|
|
80
|
-
function printFatalError(error) {
|
|
81
|
-
console.error(`
|
|
82
|
-
${chalk.bold.redBright.underline(`Error:`)}
|
|
83
|
-
${chalk.redBright(`${error}`)}
|
|
84
|
-
`);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
var spinner;
|
|
88
|
-
function showSpinner(text) {
|
|
89
|
-
if (!spinner) {
|
|
90
|
-
spinner = ora({ text: "" }).start();
|
|
91
|
-
}
|
|
92
|
-
spinner.text = text;
|
|
93
|
-
}
|
|
94
|
-
function hideSpinner() {
|
|
95
|
-
if (!spinner) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
spinner.stop();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// src/account.ts
|
|
102
|
-
async function getAccountAlias(awsConfig2) {
|
|
103
|
-
var _a;
|
|
104
|
-
showSpinner("Getting account alias");
|
|
105
|
-
const iam = new AWS.IAM(awsConfig2);
|
|
106
|
-
const accountAliases = await iam.listAccountAliases().promise();
|
|
107
|
-
const foundAlias = (_a = accountAliases == null ? void 0 : accountAliases["AccountAliases"]) == null ? void 0 : _a[0];
|
|
108
|
-
if (foundAlias) {
|
|
109
|
-
return foundAlias;
|
|
110
|
-
}
|
|
111
|
-
const sts = new AWS.STS(awsConfig2);
|
|
112
|
-
const accountInfo = await sts.getCallerIdentity().promise();
|
|
113
|
-
return (accountInfo == null ? void 0 : accountInfo.Account) || "";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// src/config.ts
|
|
117
|
-
import { loadSharedConfigFiles } from "@aws-sdk/shared-ini-file-loader";
|
|
118
|
-
import chalk2 from "chalk";
|
|
119
|
-
async function getAwsConfigFromOptionsOrFile(options2) {
|
|
120
|
-
const { profile, accessKey, secretKey, sessionToken, region } = options2;
|
|
121
|
-
if (accessKey || secretKey) {
|
|
122
|
-
if (!accessKey || !secretKey) {
|
|
123
|
-
printFatalError(`
|
|
124
|
-
You need to provide both of the following options:
|
|
125
|
-
${chalk2.bold("--access-key")}
|
|
126
|
-
${chalk2.bold("--secret-key")}
|
|
127
|
-
`);
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
credentials: {
|
|
131
|
-
accessKeyId: accessKey,
|
|
132
|
-
secretAccessKey: secretKey,
|
|
133
|
-
sessionToken
|
|
134
|
-
},
|
|
135
|
-
region
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
credentials: await loadAwsCredentials(profile),
|
|
140
|
-
region
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
async function loadAwsCredentials(profile = "default") {
|
|
144
|
-
var _a, _b, _c;
|
|
145
|
-
const configFiles = await loadSharedConfigFiles();
|
|
146
|
-
const credentialsFile = configFiles.credentialsFile;
|
|
147
|
-
const accessKey = (_a = credentialsFile == null ? void 0 : credentialsFile[profile]) == null ? void 0 : _a.aws_access_key_id;
|
|
148
|
-
const secretKey = (_b = credentialsFile == null ? void 0 : credentialsFile[profile]) == null ? void 0 : _b.aws_secret_access_key;
|
|
149
|
-
const sessionToken = (_c = credentialsFile == null ? void 0 : credentialsFile[profile]) == null ? void 0 : _c.aws_session_token;
|
|
150
|
-
if (!accessKey || !secretKey) {
|
|
151
|
-
const sharedCredentialsFile = process.env.AWS_SHARED_CREDENTIALS_FILE || "~/.aws/credentials";
|
|
152
|
-
const sharedConfigFile = process.env.AWS_CONFIG_FILE || "~/.aws/config";
|
|
153
|
-
printFatalError(`
|
|
154
|
-
Could not find the AWS credentials in the following files for the profile "${profile}":
|
|
155
|
-
${chalk2.bold(sharedCredentialsFile)}
|
|
156
|
-
${chalk2.bold(sharedConfigFile)}
|
|
157
|
-
|
|
158
|
-
If the config files exist at different locations, set the following environment variables:
|
|
159
|
-
${chalk2.bold(`AWS_SHARED_CREDENTIALS_FILE`)}
|
|
160
|
-
${chalk2.bold(`AWS_CONFIG_FILE`)}
|
|
161
|
-
|
|
162
|
-
You can also configure the credentials via the following command:
|
|
163
|
-
${chalk2.bold(`aws configure --profile ${profile}`)}
|
|
164
|
-
|
|
165
|
-
You can also provide the credentials via the following options:
|
|
166
|
-
${chalk2.bold(`--access-key`)}
|
|
167
|
-
${chalk2.bold(`--secret-key`)}
|
|
168
|
-
${chalk2.bold(`--region`)}
|
|
169
|
-
`);
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
accessKeyId: accessKey,
|
|
173
|
-
secretAccessKey: secretKey,
|
|
174
|
-
sessionToken
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// src/cost.ts
|
|
179
|
-
import AWS2 from "aws-sdk";
|
|
180
|
-
import dayjs from "dayjs";
|
|
181
|
-
async function getRawCostByService(awsConfig2) {
|
|
182
|
-
showSpinner("Getting pricing data");
|
|
183
|
-
const costExplorer = new AWS2.CostExplorer(awsConfig2);
|
|
184
|
-
const endDate = dayjs().subtract(1, "day");
|
|
185
|
-
const startDate = endDate.subtract(65, "day");
|
|
186
|
-
const pricingData = await costExplorer.getCostAndUsage({
|
|
187
|
-
TimePeriod: {
|
|
188
|
-
Start: startDate.format("YYYY-MM-DD"),
|
|
189
|
-
End: endDate.format("YYYY-MM-DD")
|
|
190
|
-
},
|
|
191
|
-
Granularity: "DAILY",
|
|
192
|
-
Filter: {
|
|
193
|
-
Not: {
|
|
194
|
-
Dimensions: {
|
|
195
|
-
Key: "RECORD_TYPE",
|
|
196
|
-
Values: ["Credit", "Refund", "Upfront", "Support"]
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
Metrics: ["UnblendedCost"],
|
|
201
|
-
GroupBy: [
|
|
202
|
-
{
|
|
203
|
-
Type: "DIMENSION",
|
|
204
|
-
Key: "SERVICE"
|
|
205
|
-
}
|
|
206
|
-
]
|
|
207
|
-
}).promise();
|
|
208
|
-
const costByService = {};
|
|
209
|
-
for (const day of pricingData.ResultsByTime) {
|
|
210
|
-
for (const group of day.Groups) {
|
|
211
|
-
const serviceName = group.Keys[0];
|
|
212
|
-
const cost = group.Metrics.UnblendedCost.Amount;
|
|
213
|
-
const costDate = day.TimePeriod.End;
|
|
214
|
-
costByService[serviceName] = costByService[serviceName] || {};
|
|
215
|
-
costByService[serviceName][costDate] = parseFloat(cost);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return costByService;
|
|
219
|
-
}
|
|
220
|
-
function calculateServiceTotals(rawCostByService) {
|
|
221
|
-
const totals = {
|
|
222
|
-
lastMonth: 0,
|
|
223
|
-
thisMonth: 0,
|
|
224
|
-
last7Days: 0,
|
|
225
|
-
yesterday: 0
|
|
226
|
-
};
|
|
227
|
-
const totalsByService = {
|
|
228
|
-
lastMonth: {},
|
|
229
|
-
thisMonth: {},
|
|
230
|
-
last7Days: {},
|
|
231
|
-
yesterday: {}
|
|
232
|
-
};
|
|
233
|
-
const startOfLastMonth = dayjs().subtract(1, "month").startOf("month");
|
|
234
|
-
const startOfThisMonth = dayjs().startOf("month");
|
|
235
|
-
const startOfLast7Days = dayjs().subtract(7, "day");
|
|
236
|
-
const startOfYesterday = dayjs().subtract(1, "day");
|
|
237
|
-
for (const service of Object.keys(rawCostByService)) {
|
|
238
|
-
const servicePrices = rawCostByService[service];
|
|
239
|
-
let lastMonthServiceTotal = 0;
|
|
240
|
-
let thisMonthServiceTotal = 0;
|
|
241
|
-
let last7DaysServiceTotal = 0;
|
|
242
|
-
let yesterdayServiceTotal = 0;
|
|
243
|
-
for (const date of Object.keys(servicePrices)) {
|
|
244
|
-
const price = servicePrices[date];
|
|
245
|
-
const dateObj = dayjs(date);
|
|
246
|
-
if (dateObj.isSame(startOfLastMonth, "month")) {
|
|
247
|
-
lastMonthServiceTotal += price;
|
|
248
|
-
}
|
|
249
|
-
if (dateObj.isSame(startOfThisMonth, "month")) {
|
|
250
|
-
thisMonthServiceTotal += price;
|
|
251
|
-
}
|
|
252
|
-
if (dateObj.isSame(startOfLast7Days, "week") && !dateObj.isSame(startOfYesterday, "day")) {
|
|
253
|
-
last7DaysServiceTotal += price;
|
|
254
|
-
}
|
|
255
|
-
if (dateObj.isSame(startOfYesterday, "day")) {
|
|
256
|
-
yesterdayServiceTotal += price;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
totalsByService.lastMonth[service] = lastMonthServiceTotal;
|
|
260
|
-
totalsByService.thisMonth[service] = thisMonthServiceTotal;
|
|
261
|
-
totalsByService.last7Days[service] = last7DaysServiceTotal;
|
|
262
|
-
totalsByService.yesterday[service] = yesterdayServiceTotal;
|
|
263
|
-
totals.lastMonth += lastMonthServiceTotal;
|
|
264
|
-
totals.thisMonth += thisMonthServiceTotal;
|
|
265
|
-
totals.last7Days += last7DaysServiceTotal;
|
|
266
|
-
totals.yesterday += yesterdayServiceTotal;
|
|
267
|
-
}
|
|
268
|
-
return {
|
|
269
|
-
totals,
|
|
270
|
-
totalsByService
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
async function getTotalCosts(awsConfig2) {
|
|
274
|
-
const rawCosts = await getRawCostByService(awsConfig2);
|
|
275
|
-
const totals = calculateServiceTotals(rawCosts);
|
|
276
|
-
return totals;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// src/printers/fancy.ts
|
|
280
|
-
import chalk3 from "chalk";
|
|
281
|
-
function printFancy(accountAlias, totals, isSummary = false) {
|
|
282
|
-
hideSpinner();
|
|
283
|
-
console.clear();
|
|
284
|
-
const totalCosts = totals.totals;
|
|
285
|
-
const serviceCosts = totals.totalsByService;
|
|
286
|
-
const allServices = Object.keys(serviceCosts.yesterday);
|
|
287
|
-
const sortedServiceNames = allServices.sort((a, b) => b.length - a.length);
|
|
288
|
-
const maxServiceLength = sortedServiceNames.reduce((max, service) => {
|
|
289
|
-
return Math.max(max, service.length);
|
|
290
|
-
}, 0) + 1;
|
|
291
|
-
const totalLastMonth = chalk3.green(`$${totalCosts.lastMonth.toFixed(2)}`);
|
|
292
|
-
const totalThisMonth = chalk3.green(`$${totalCosts.thisMonth.toFixed(2)}`);
|
|
293
|
-
const totalLast7Days = chalk3.green(`$${totalCosts.last7Days.toFixed(2)}`);
|
|
294
|
-
const totalYesterday = chalk3.bold.yellowBright(`$${totalCosts.yesterday.toFixed(2)}`);
|
|
295
|
-
console.log("");
|
|
296
|
-
console.log(`${"AWS Cost Report:".padStart(maxServiceLength + 1)} ${chalk3.bold.yellow(accountAlias)}`);
|
|
297
|
-
console.log("");
|
|
298
|
-
console.log(`${"Last Month".padStart(maxServiceLength)}: ${totalLastMonth}`);
|
|
299
|
-
console.log(`${"This Month".padStart(maxServiceLength)}: ${totalThisMonth}`);
|
|
300
|
-
console.log(`${"Last 7 days".padStart(maxServiceLength)}: ${totalLast7Days}`);
|
|
301
|
-
console.log(`${chalk3.bold("Yesterday".padStart(maxServiceLength))}: ${totalYesterday}`);
|
|
302
|
-
console.log("");
|
|
303
|
-
if (isSummary) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const headerPadLength = 11;
|
|
307
|
-
const serviceHeader = chalk3.white("Service".padStart(maxServiceLength));
|
|
308
|
-
const lastMonthHeader = chalk3.white(`Last Month`.padEnd(headerPadLength));
|
|
309
|
-
const thisMonthHeader = chalk3.white(`This Month`.padEnd(headerPadLength));
|
|
310
|
-
const last7DaysHeader = chalk3.white(`Last 7 Days`.padEnd(headerPadLength));
|
|
311
|
-
const yesterdayHeader = chalk3.bold.white("Yesterday".padEnd(headerPadLength));
|
|
312
|
-
console.log(`${serviceHeader} ${lastMonthHeader} ${thisMonthHeader} ${last7DaysHeader} ${yesterdayHeader}`);
|
|
313
|
-
for (let service of sortedServiceNames) {
|
|
314
|
-
const serviceLabel = chalk3.cyan(service.padStart(maxServiceLength));
|
|
315
|
-
const lastMonthTotal = chalk3.green(`$${serviceCosts.lastMonth[service].toFixed(2)}`.padEnd(headerPadLength));
|
|
316
|
-
const thisMonthTotal = chalk3.green(`$${serviceCosts.thisMonth[service].toFixed(2)}`.padEnd(headerPadLength));
|
|
317
|
-
const last7DaysTotal = chalk3.green(`$${serviceCosts.last7Days[service].toFixed(2)}`.padEnd(headerPadLength));
|
|
318
|
-
const yesterdayTotal = chalk3.bold.yellowBright(
|
|
319
|
-
`$${serviceCosts.yesterday[service].toFixed(2)}`.padEnd(headerPadLength)
|
|
320
|
-
);
|
|
321
|
-
console.log(`${serviceLabel} ${lastMonthTotal} ${thisMonthTotal} ${last7DaysTotal} ${yesterdayTotal}`);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// src/printers/json.ts
|
|
326
|
-
function printJson(accountAlias, totalCosts, isSummary = false) {
|
|
327
|
-
hideSpinner();
|
|
328
|
-
if (isSummary) {
|
|
329
|
-
console.log(
|
|
330
|
-
JSON.stringify(
|
|
331
|
-
{
|
|
332
|
-
account: accountAlias,
|
|
333
|
-
totals: totalCosts.totals
|
|
334
|
-
},
|
|
335
|
-
null,
|
|
336
|
-
2
|
|
337
|
-
)
|
|
338
|
-
);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
console.log(
|
|
342
|
-
JSON.stringify(
|
|
343
|
-
{
|
|
344
|
-
account: accountAlias,
|
|
345
|
-
...totalCosts
|
|
346
|
-
},
|
|
347
|
-
null,
|
|
348
|
-
2
|
|
349
|
-
)
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// src/printers/slack.ts
|
|
354
|
-
import fetch from "node-fetch";
|
|
355
|
-
function formatServiceBreakdown(costs2) {
|
|
356
|
-
const serviceCosts = costs2.totalsByService;
|
|
357
|
-
const sortedServices = Object.keys(serviceCosts.yesterday).filter((service) => serviceCosts.yesterday[service] > 0).sort((a, b) => serviceCosts.yesterday[b] - serviceCosts.yesterday[a]);
|
|
358
|
-
const serviceCostsYesterday = sortedServices.map((service) => {
|
|
359
|
-
return `> ${service}: \`$${serviceCosts.yesterday[service].toFixed(2)}\``;
|
|
360
|
-
});
|
|
361
|
-
return serviceCostsYesterday.join("\n");
|
|
362
|
-
}
|
|
363
|
-
async function notifySlack(accountAlias, costs2, isSummary, slackToken, slackChannel) {
|
|
364
|
-
const channel = slackChannel;
|
|
365
|
-
const totals = costs2.totals;
|
|
366
|
-
const serviceCosts = costs2.totalsByService;
|
|
367
|
-
let serviceCostsYesterday = [];
|
|
368
|
-
Object.keys(serviceCosts.yesterday).forEach((service) => {
|
|
369
|
-
serviceCosts.yesterday[service].toFixed(2);
|
|
370
|
-
serviceCostsYesterday.push(`${service}: $${serviceCosts.yesterday[service].toFixed(2)}`);
|
|
371
|
-
});
|
|
372
|
-
const summary = `> *Account: ${accountAlias}*
|
|
373
|
-
|
|
374
|
-
> *Summary *
|
|
375
|
-
> Total Yesterday: \`$${totals.yesterday.toFixed(2)}\`
|
|
376
|
-
> Total This Month: \`$${totals.thisMonth.toFixed(2)}\`
|
|
377
|
-
> Total Last Month: \`$${totals.lastMonth.toFixed(2)}\`
|
|
378
|
-
`;
|
|
379
|
-
const breakdown = `
|
|
380
|
-
> *Breakdown by Service:*
|
|
381
|
-
${formatServiceBreakdown(costs2)}
|
|
382
|
-
`;
|
|
383
|
-
let message = `${summary}`;
|
|
384
|
-
if (!isSummary) {
|
|
385
|
-
message += `${breakdown}`;
|
|
386
|
-
}
|
|
387
|
-
const response = await fetch("https://slack.com/api/chat.postMessage", {
|
|
388
|
-
method: "post",
|
|
389
|
-
body: JSON.stringify({
|
|
390
|
-
channel,
|
|
391
|
-
blocks: [
|
|
392
|
-
{
|
|
393
|
-
type: "section",
|
|
394
|
-
text: {
|
|
395
|
-
type: "mrkdwn",
|
|
396
|
-
text: message
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
]
|
|
400
|
-
}),
|
|
401
|
-
headers: {
|
|
402
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
403
|
-
Authorization: `Bearer ${slackToken}`
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
const data = await response.json();
|
|
407
|
-
if (!data.ok) {
|
|
408
|
-
const message2 = data.error || "Unknown error";
|
|
409
|
-
console.error(`
|
|
410
|
-
Failed to send message to Slack: ${message2}`);
|
|
411
|
-
process.exit(1);
|
|
412
|
-
}
|
|
413
|
-
console.log("\nSuccessfully sent message to Slack");
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// src/printers/text.ts
|
|
417
|
-
function printPlainSummary(accountAlias, costs2) {
|
|
418
|
-
hideSpinner();
|
|
419
|
-
console.clear();
|
|
420
|
-
console.log("");
|
|
421
|
-
console.log(`Account: ${accountAlias}`);
|
|
422
|
-
console.log("");
|
|
423
|
-
console.log("Totals:");
|
|
424
|
-
console.log(` Last Month: $${costs2.totals.lastMonth.toFixed(2)}`);
|
|
425
|
-
console.log(` This Month: $${costs2.totals.thisMonth.toFixed(2)}`);
|
|
426
|
-
console.log(` Last 7 Days: $${costs2.totals.last7Days.toFixed(2)}`);
|
|
427
|
-
console.log(` Yesterday: $${costs2.totals.yesterday.toFixed(2)}`);
|
|
428
|
-
}
|
|
429
|
-
function printPlainText(accountAlias, totals, isSummary = false) {
|
|
430
|
-
printPlainSummary(accountAlias, totals);
|
|
431
|
-
if (isSummary) {
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
const serviceTotals = totals.totalsByService;
|
|
435
|
-
const allServices = Object.keys(serviceTotals.yesterday).sort((a, b) => b.length - a.length);
|
|
436
|
-
console.log("");
|
|
437
|
-
console.log("Totals by Service:");
|
|
438
|
-
console.log(" Last Month:");
|
|
439
|
-
allServices.forEach((service) => {
|
|
440
|
-
console.log(` ${service}: $${serviceTotals.lastMonth[service].toFixed(2)}`);
|
|
441
|
-
});
|
|
442
|
-
console.log("");
|
|
443
|
-
console.log(" This Month:");
|
|
444
|
-
allServices.forEach((service) => {
|
|
445
|
-
console.log(` ${service}: $${serviceTotals.thisMonth[service].toFixed(2)}`);
|
|
446
|
-
});
|
|
447
|
-
console.log("");
|
|
448
|
-
console.log(" Last 7 Days:");
|
|
449
|
-
allServices.forEach((service) => {
|
|
450
|
-
console.log(` ${service}: $${serviceTotals.last7Days[service].toFixed(2)}`);
|
|
451
|
-
});
|
|
452
|
-
console.log("");
|
|
453
|
-
console.log(" Yesterday:");
|
|
454
|
-
allServices.forEach((service) => {
|
|
455
|
-
console.log(` ${service}: $${serviceTotals.yesterday[service].toFixed(2)}`);
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// src/index.ts
|
|
460
|
-
process.env.AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE = "1";
|
|
461
|
-
var program = new Command();
|
|
462
|
-
program.version(package_default.version).name("aws-cost").description(package_default.description).option("-p, --profile [profile]", "AWS profile to use", "default").option("-k, --access-key [key]", "AWS access key").option("-s, --secret-key [key]", "AWS secret key").option("-T, --session-token [key]", "AWS session Token").option("-r, --region [region]", "AWS region", "us-east-1").option("-j, --json", "Get the output as JSON").option("-u, --summary", "Get only the summary without service breakdown").option("-t, --text", "Get the output as plain text (no colors / tables)").option("-S, --slack-token [token]", "Token for the slack integration").option("-C, --slack-channel [channel]", "Channel to which the slack integration should post").option("-h, --help", "Get the help of the CLI").parse(process.argv);
|
|
463
|
-
var options = program.opts();
|
|
464
|
-
if (options.help) {
|
|
465
|
-
program.help();
|
|
466
|
-
process.exit(0);
|
|
467
|
-
}
|
|
468
|
-
var awsConfig = await getAwsConfigFromOptionsOrFile({
|
|
469
|
-
profile: options.profile,
|
|
470
|
-
accessKey: options.accessKey,
|
|
471
|
-
secretKey: options.secretKey,
|
|
472
|
-
sessionToken: options.sessionToken,
|
|
473
|
-
region: options.region
|
|
474
|
-
});
|
|
475
|
-
var alias = await getAccountAlias(awsConfig);
|
|
476
|
-
var costs = await getTotalCosts(awsConfig);
|
|
477
|
-
if (options.json) {
|
|
478
|
-
printJson(alias, costs, options.summary);
|
|
479
|
-
} else if (options.text) {
|
|
480
|
-
printPlainText(alias, costs, options.summary);
|
|
481
|
-
} else {
|
|
482
|
-
printFancy(alias, costs, options.summary);
|
|
483
|
-
}
|
|
484
|
-
if (options.slackToken && options.slackChannel) {
|
|
485
|
-
await notifySlack(alias, costs, options.summary, options.slackToken, options.slackChannel);
|
|
486
|
-
}
|
|
487
|
-
//# sourceMappingURL=index.js.map
|