@electrolux-oss/plugin-infrawallet-backend 0.1.2 → 0.1.3
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/config.d.ts +10 -0
- package/dist/index.cjs.js +351 -110
- package/dist/index.cjs.js.map +1 -1
- package/package.json +3 -1
- package/seeds/20240618121807_default-gcp-category-mappings.js +71 -0
package/config.d.ts
CHANGED
package/dist/index.cjs.js
CHANGED
|
@@ -12,6 +12,8 @@ var lodash = require('lodash');
|
|
|
12
12
|
var moment = require('moment');
|
|
13
13
|
var armCostmanagement = require('@azure/arm-costmanagement');
|
|
14
14
|
var identity = require('@azure/identity');
|
|
15
|
+
var coreRestPipeline = require('@azure/core-rest-pipeline');
|
|
16
|
+
var bigquery = require('@google-cloud/bigquery');
|
|
15
17
|
|
|
16
18
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
17
19
|
|
|
@@ -43,12 +45,42 @@ function getCategoryByServiceName(serviceName, categoryMappings) {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
class AwsClient {
|
|
46
|
-
constructor(config, database) {
|
|
48
|
+
constructor(config, database, logger) {
|
|
47
49
|
this.config = config;
|
|
48
50
|
this.database = database;
|
|
51
|
+
this.logger = logger;
|
|
49
52
|
}
|
|
50
|
-
static create(config, database) {
|
|
51
|
-
return new AwsClient(config, database);
|
|
53
|
+
static create(config, database, logger) {
|
|
54
|
+
return new AwsClient(config, database, logger);
|
|
55
|
+
}
|
|
56
|
+
convertServiceName(serviceName) {
|
|
57
|
+
let convertedName = serviceName;
|
|
58
|
+
const prefixes = ["Amazon", "AWS"];
|
|
59
|
+
const aliases = /* @__PURE__ */ new Map([
|
|
60
|
+
["Elastic Compute Cloud - Compute", "EC2 - Instances"],
|
|
61
|
+
["Virtual Private Cloud", "VPC (Virtual Private Cloud)"],
|
|
62
|
+
["Relational Database Service", "RDS (Relational Database Service)"],
|
|
63
|
+
["Simple Storage Service", "S3 (Simple Storage Service)"],
|
|
64
|
+
[
|
|
65
|
+
"Managed Streaming for Apache Kafka",
|
|
66
|
+
"MSK (Managed Streaming for Apache Kafka)"
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"Elastic Container Service for Kubernetes",
|
|
70
|
+
"EKS (Elastic Container Service for Kubernetes)"
|
|
71
|
+
],
|
|
72
|
+
["Simple Queue Service", "SQS (Simple Queue Service)"],
|
|
73
|
+
["Simple Notification Service", "SNS (Simple Notification Service)"]
|
|
74
|
+
]);
|
|
75
|
+
for (const prefix of prefixes) {
|
|
76
|
+
if (serviceName.startsWith(prefix)) {
|
|
77
|
+
convertedName = serviceName.slice(prefix.length).trim();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (aliases.has(convertedName)) {
|
|
81
|
+
convertedName = aliases.get(convertedName) || convertedName;
|
|
82
|
+
}
|
|
83
|
+
return `AWS/${convertedName}`;
|
|
52
84
|
}
|
|
53
85
|
async fetchCostsFromCloud(query) {
|
|
54
86
|
const conf = this.config.getOptionalConfigArray(
|
|
@@ -93,74 +125,92 @@ class AwsClient {
|
|
|
93
125
|
}
|
|
94
126
|
const promise = (async () => {
|
|
95
127
|
var _a, _b, _c;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
});
|
|
112
|
-
const input = {
|
|
113
|
-
TimePeriod: {
|
|
114
|
-
Start: moment__default.default(parseInt(query.startTime, 10)).format("YYYY-MM-DD"),
|
|
115
|
-
End: moment__default.default(parseInt(query.endTime, 10)).format("YYYY-MM-DD")
|
|
116
|
-
},
|
|
117
|
-
Granularity: query.granularity.toUpperCase(),
|
|
118
|
-
Filter: { Dimensions: { Key: "RECORD_TYPE", Values: ["Usage"] } },
|
|
119
|
-
GroupBy: [{ Type: "DIMENSION", Key: "SERVICE" }],
|
|
120
|
-
Metrics: ["UnblendedCost"]
|
|
121
|
-
};
|
|
122
|
-
const getCostCommand = new clientCostExplorer.GetCostAndUsageCommand(input);
|
|
123
|
-
const costAndusageResponse = await awsCeClient.send(getCostCommand);
|
|
124
|
-
const transformedData = lodash.reduce(
|
|
125
|
-
costAndusageResponse.ResultsByTime,
|
|
126
|
-
(acc, row) => {
|
|
127
|
-
var _a2;
|
|
128
|
-
const rowTime = (_a2 = row.TimePeriod) == null ? void 0 : _a2.Start;
|
|
129
|
-
const period = rowTime ? rowTime.substring(0, 7) : "unknown";
|
|
130
|
-
if (row.Groups) {
|
|
131
|
-
row.Groups.forEach((group) => {
|
|
132
|
-
var _a3;
|
|
133
|
-
const groupKeys = group.Keys ? group.Keys[0] : "";
|
|
134
|
-
const keyName = `${name}_${groupKeys}`;
|
|
135
|
-
if (!acc[keyName]) {
|
|
136
|
-
acc[keyName] = {
|
|
137
|
-
id: keyName,
|
|
138
|
-
name,
|
|
139
|
-
service: `${groupKeys} (AWS)`,
|
|
140
|
-
category: getCategoryByServiceName(
|
|
141
|
-
groupKeys,
|
|
142
|
-
categoryMappings
|
|
143
|
-
),
|
|
144
|
-
provider: "AWS",
|
|
145
|
-
reports: {},
|
|
146
|
-
...tagKeyValues
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
const groupMetrics = group.Metrics;
|
|
150
|
-
if (groupMetrics !== void 0) {
|
|
151
|
-
acc[keyName].reports[period] = parseFloat(
|
|
152
|
-
(_a3 = groupMetrics.UnblendedCost.Amount) != null ? _a3 : "0.0"
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
128
|
+
try {
|
|
129
|
+
const client = new clientSts.STSClient(stsParams);
|
|
130
|
+
const commandInput = {
|
|
131
|
+
// AssumeRoleRequest
|
|
132
|
+
RoleArn: `arn:aws:iam::${accountId}:role/${assumedRoleName}`,
|
|
133
|
+
RoleSessionName: "AssumeRoleSession1"
|
|
134
|
+
};
|
|
135
|
+
const assumeRoleCommand = new clientSts.AssumeRoleCommand(commandInput);
|
|
136
|
+
const assumeRoleResponse = await client.send(assumeRoleCommand);
|
|
137
|
+
const awsCeClient = new clientCostExplorer.CostExplorerClient({
|
|
138
|
+
region: "us-east-1",
|
|
139
|
+
credentials: {
|
|
140
|
+
accessKeyId: (_a = assumeRoleResponse.Credentials) == null ? void 0 : _a.AccessKeyId,
|
|
141
|
+
secretAccessKey: (_b = assumeRoleResponse.Credentials) == null ? void 0 : _b.SecretAccessKey,
|
|
142
|
+
sessionToken: (_c = assumeRoleResponse.Credentials) == null ? void 0 : _c.SessionToken
|
|
156
143
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
144
|
+
});
|
|
145
|
+
let costAndUsageResults = [];
|
|
146
|
+
let nextPageToken = void 0;
|
|
147
|
+
do {
|
|
148
|
+
const input = {
|
|
149
|
+
TimePeriod: {
|
|
150
|
+
Start: moment__default.default(parseInt(query.startTime, 10)).format("YYYY-MM-DD"),
|
|
151
|
+
End: moment__default.default(parseInt(query.endTime, 10)).format("YYYY-MM-DD")
|
|
152
|
+
},
|
|
153
|
+
Granularity: query.granularity.toUpperCase(),
|
|
154
|
+
Filter: { Dimensions: { Key: "RECORD_TYPE", Values: ["Usage"] } },
|
|
155
|
+
GroupBy: [{ Type: "DIMENSION", Key: "SERVICE" }],
|
|
156
|
+
Metrics: ["UnblendedCost"],
|
|
157
|
+
NextPageToken: nextPageToken
|
|
158
|
+
};
|
|
159
|
+
const getCostCommand = new clientCostExplorer.GetCostAndUsageCommand(input);
|
|
160
|
+
const costAndUsageResponse = await awsCeClient.send(getCostCommand);
|
|
161
|
+
costAndUsageResults = costAndUsageResults.concat(costAndUsageResponse.ResultsByTime);
|
|
162
|
+
nextPageToken = costAndUsageResponse.NextPageToken;
|
|
163
|
+
} while (nextPageToken);
|
|
164
|
+
const transformedData = lodash.reduce(
|
|
165
|
+
costAndUsageResults,
|
|
166
|
+
(accumulator, row) => {
|
|
167
|
+
var _a2;
|
|
168
|
+
const rowTime = (_a2 = row.TimePeriod) == null ? void 0 : _a2.Start;
|
|
169
|
+
let period = "unknown";
|
|
170
|
+
if (rowTime) {
|
|
171
|
+
if (query.granularity.toUpperCase() === "MONTHLY") {
|
|
172
|
+
period = rowTime.substring(0, 7);
|
|
173
|
+
} else {
|
|
174
|
+
period = rowTime;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (row.Groups) {
|
|
178
|
+
row.Groups.forEach((group) => {
|
|
179
|
+
var _a3;
|
|
180
|
+
const serviceName = group.Keys ? group.Keys[0] : "";
|
|
181
|
+
const keyName = `${name}_${serviceName}`;
|
|
182
|
+
if (!accumulator[keyName]) {
|
|
183
|
+
accumulator[keyName] = {
|
|
184
|
+
id: keyName,
|
|
185
|
+
name: `AWS/${name}`,
|
|
186
|
+
service: this.convertServiceName(serviceName),
|
|
187
|
+
category: getCategoryByServiceName(
|
|
188
|
+
serviceName,
|
|
189
|
+
categoryMappings
|
|
190
|
+
),
|
|
191
|
+
provider: "AWS",
|
|
192
|
+
reports: {},
|
|
193
|
+
...tagKeyValues
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const groupMetrics = group.Metrics;
|
|
197
|
+
if (groupMetrics !== void 0) {
|
|
198
|
+
accumulator[keyName].reports[period] = parseFloat(
|
|
199
|
+
(_a3 = groupMetrics.UnblendedCost.Amount) != null ? _a3 : "0.0"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return accumulator;
|
|
205
|
+
},
|
|
206
|
+
{}
|
|
207
|
+
);
|
|
208
|
+
Object.values(transformedData).map((value) => {
|
|
209
|
+
results.push(value);
|
|
210
|
+
});
|
|
211
|
+
} catch (e) {
|
|
212
|
+
this.logger.error(e);
|
|
213
|
+
}
|
|
164
214
|
})();
|
|
165
215
|
promises.push(promise);
|
|
166
216
|
}
|
|
@@ -170,15 +220,61 @@ class AwsClient {
|
|
|
170
220
|
}
|
|
171
221
|
|
|
172
222
|
class AzureClient {
|
|
173
|
-
constructor(config, database) {
|
|
223
|
+
constructor(config, database, logger) {
|
|
174
224
|
this.config = config;
|
|
175
225
|
this.database = database;
|
|
226
|
+
this.logger = logger;
|
|
227
|
+
}
|
|
228
|
+
static create(config, database, logger) {
|
|
229
|
+
return new AzureClient(config, database, logger);
|
|
176
230
|
}
|
|
177
|
-
|
|
178
|
-
|
|
231
|
+
convertServiceName(serviceName) {
|
|
232
|
+
let convertedName = serviceName;
|
|
233
|
+
const prefixes = ["Azure"];
|
|
234
|
+
for (const prefix of prefixes) {
|
|
235
|
+
if (serviceName.startsWith(prefix)) {
|
|
236
|
+
convertedName = serviceName.slice(prefix.length).trim();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return `Azure/${convertedName}`;
|
|
240
|
+
}
|
|
241
|
+
formatDate(dateNumber) {
|
|
242
|
+
const dateString = dateNumber.toString();
|
|
243
|
+
if (dateString.length !== 8) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const year = dateString.slice(0, 4);
|
|
247
|
+
const month = dateString.slice(4, 6);
|
|
248
|
+
const day = dateString.slice(6);
|
|
249
|
+
return `${year}-${month}-${day}`;
|
|
250
|
+
}
|
|
251
|
+
async fetchDataWithRetry(client, url, body, maxRetries = 5) {
|
|
252
|
+
let retries = 0;
|
|
253
|
+
while (retries < maxRetries) {
|
|
254
|
+
const request = coreRestPipeline.createPipelineRequest({
|
|
255
|
+
url,
|
|
256
|
+
method: "POST",
|
|
257
|
+
body: JSON.stringify(body),
|
|
258
|
+
headers: coreRestPipeline.createHttpHeaders({
|
|
259
|
+
"Content-Type": "application/json"
|
|
260
|
+
})
|
|
261
|
+
});
|
|
262
|
+
const response = await client.pipeline.sendRequest(client, request);
|
|
263
|
+
if (response.status === 200) {
|
|
264
|
+
return JSON.parse(response.bodyAsText || "{}");
|
|
265
|
+
} else if (response.status === 429) {
|
|
266
|
+
const retryAfter = parseInt(response.headers.get("x-ms-ratelimit-microsoft.costmanagement-entity-retry-after") || "60", 10);
|
|
267
|
+
this.logger.warn(`Hit Azure rate limit, retrying after ${retryAfter} seconds...`);
|
|
268
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
|
|
269
|
+
retries++;
|
|
270
|
+
} else {
|
|
271
|
+
throw new Error(response.bodyAsText);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
throw new Error("Max retries exceeded");
|
|
179
275
|
}
|
|
180
276
|
async queryAzureCostExplorer(azureClient, subscription, granularity, groups, startDate, endDate) {
|
|
181
|
-
const
|
|
277
|
+
const url = `https://management.azure.com/subscriptions/${subscription}/providers/Microsoft.CostManagement/query?api-version=2022-10-01`;
|
|
182
278
|
const query = {
|
|
183
279
|
type: "ActualCost",
|
|
184
280
|
dataset: {
|
|
@@ -188,12 +284,17 @@ class AzureClient {
|
|
|
188
284
|
},
|
|
189
285
|
timeframe: "Custom",
|
|
190
286
|
timePeriod: {
|
|
191
|
-
from: startDate.
|
|
192
|
-
to: endDate.
|
|
287
|
+
from: startDate.toDate(),
|
|
288
|
+
to: endDate.toDate()
|
|
193
289
|
}
|
|
194
290
|
};
|
|
195
|
-
|
|
196
|
-
|
|
291
|
+
let result = await this.fetchDataWithRetry(azureClient, url, query);
|
|
292
|
+
let allResults = result.properties.rows;
|
|
293
|
+
while (result.properties.nextLink) {
|
|
294
|
+
result = await this.fetchDataWithRetry(azureClient, result.properties.nextLink, query);
|
|
295
|
+
allResults = allResults.concat(result.properties.rows);
|
|
296
|
+
}
|
|
297
|
+
return allResults;
|
|
197
298
|
}
|
|
198
299
|
async fetchCostsFromCloud(query) {
|
|
199
300
|
const conf = this.config.getOptionalConfigArray(
|
|
@@ -202,6 +303,10 @@ class AzureClient {
|
|
|
202
303
|
if (!conf) {
|
|
203
304
|
return [];
|
|
204
305
|
}
|
|
306
|
+
const categoryMappings = await getCategoryMappings(
|
|
307
|
+
this.database,
|
|
308
|
+
"azure"
|
|
309
|
+
);
|
|
205
310
|
const promises = [];
|
|
206
311
|
const results = [];
|
|
207
312
|
const groupPairs = [{ type: "Dimension", name: "ServiceName" }];
|
|
@@ -223,10 +328,6 @@ class AzureClient {
|
|
|
223
328
|
const [k, v] = tag.split(":");
|
|
224
329
|
tagKeyValues[k.trim()] = v.trim();
|
|
225
330
|
});
|
|
226
|
-
const categoryMappings = await getCategoryMappings(
|
|
227
|
-
this.database,
|
|
228
|
-
"azure"
|
|
229
|
-
);
|
|
230
331
|
const promise = (async () => {
|
|
231
332
|
try {
|
|
232
333
|
const costResponse = await this.queryAzureCostExplorer(
|
|
@@ -238,28 +339,163 @@ class AzureClient {
|
|
|
238
339
|
moment__default.default(parseInt(query.endTime, 10))
|
|
239
340
|
);
|
|
240
341
|
const transformedData = lodash.reduce(
|
|
241
|
-
costResponse
|
|
242
|
-
(
|
|
342
|
+
costResponse,
|
|
343
|
+
(accumulator, row) => {
|
|
344
|
+
const cost = row[0];
|
|
345
|
+
let date = row[1];
|
|
346
|
+
const serviceName = row[2];
|
|
347
|
+
if (query.granularity.toUpperCase() === "DAILY") {
|
|
348
|
+
date = this.formatDate(date);
|
|
349
|
+
}
|
|
243
350
|
let keyName = name;
|
|
244
351
|
for (let i = 0; i < groupPairs.length; i++) {
|
|
245
352
|
keyName += `->${row[i + 2]}`;
|
|
246
353
|
}
|
|
247
|
-
if (!
|
|
248
|
-
|
|
354
|
+
if (!accumulator[keyName]) {
|
|
355
|
+
accumulator[keyName] = {
|
|
249
356
|
id: keyName,
|
|
250
|
-
name
|
|
251
|
-
service:
|
|
252
|
-
category: getCategoryByServiceName(
|
|
357
|
+
name: `Azure/${name}`,
|
|
358
|
+
service: this.convertServiceName(serviceName),
|
|
359
|
+
category: getCategoryByServiceName(serviceName, categoryMappings),
|
|
253
360
|
provider: "Azure",
|
|
254
361
|
reports: {},
|
|
255
362
|
...tagKeyValues
|
|
256
363
|
};
|
|
257
364
|
}
|
|
258
|
-
if (!moment__default.default(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
365
|
+
if (!moment__default.default(date).isBefore(moment__default.default(parseInt(query.startTime, 10)))) {
|
|
366
|
+
if (query.granularity.toUpperCase() === "MONTHLY") {
|
|
367
|
+
const yearMonth = date.substring(0, 7);
|
|
368
|
+
accumulator[keyName].reports[yearMonth] = parseFloat(cost);
|
|
369
|
+
} else {
|
|
370
|
+
accumulator[keyName].reports[date] = parseFloat(cost);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return accumulator;
|
|
374
|
+
},
|
|
375
|
+
{}
|
|
376
|
+
);
|
|
377
|
+
Object.values(transformedData).map((value) => {
|
|
378
|
+
results.push(value);
|
|
379
|
+
});
|
|
380
|
+
} catch (e) {
|
|
381
|
+
this.logger.error(e);
|
|
382
|
+
}
|
|
383
|
+
})();
|
|
384
|
+
promises.push(promise);
|
|
385
|
+
}
|
|
386
|
+
await Promise.all(promises);
|
|
387
|
+
return results;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
class GCPClient {
|
|
392
|
+
constructor(config, database, logger) {
|
|
393
|
+
this.config = config;
|
|
394
|
+
this.database = database;
|
|
395
|
+
this.logger = logger;
|
|
396
|
+
}
|
|
397
|
+
static create(config, database, logger) {
|
|
398
|
+
return new GCPClient(config, database, logger);
|
|
399
|
+
}
|
|
400
|
+
convertServiceName(serviceName) {
|
|
401
|
+
let convertedName = serviceName;
|
|
402
|
+
const prefixes = ["GCP"];
|
|
403
|
+
for (const prefix of prefixes) {
|
|
404
|
+
if (serviceName.startsWith(prefix)) {
|
|
405
|
+
convertedName = serviceName.slice(prefix.length).trim();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return `GCP/${convertedName}`;
|
|
409
|
+
}
|
|
410
|
+
async queryBigQuery(keyFilePath, projectId, datasetId, tableId, query) {
|
|
411
|
+
const options = {
|
|
412
|
+
keyFilename: keyFilePath,
|
|
413
|
+
projectId
|
|
414
|
+
};
|
|
415
|
+
const bigquery$1 = new bigquery.BigQuery(options);
|
|
416
|
+
try {
|
|
417
|
+
const periodFormat = query.granularity.toUpperCase() === "MONTHLY" ? "%Y-%m" : "%Y-%m-%d";
|
|
418
|
+
const sql = `
|
|
419
|
+
SELECT
|
|
420
|
+
project.name AS project,
|
|
421
|
+
service.description AS service,
|
|
422
|
+
FORMAT_TIMESTAMP('${periodFormat}', usage_start_time) AS period,
|
|
423
|
+
SUM(cost) AS total_cost
|
|
424
|
+
FROM
|
|
425
|
+
\`${projectId}.${datasetId}.${tableId}\`
|
|
426
|
+
WHERE
|
|
427
|
+
project.name IS NOT NULL
|
|
428
|
+
AND cost > 0
|
|
429
|
+
AND usage_start_time >= TIMESTAMP_MILLIS(${query.startTime})
|
|
430
|
+
AND usage_start_time <= TIMESTAMP_MILLIS(${query.endTime})
|
|
431
|
+
GROUP BY
|
|
432
|
+
project, service, period
|
|
433
|
+
ORDER BY
|
|
434
|
+
project, period, total_cost DESC`;
|
|
435
|
+
const [job] = await bigquery$1.createQueryJob({
|
|
436
|
+
query: sql,
|
|
437
|
+
location: "US"
|
|
438
|
+
});
|
|
439
|
+
const [rows] = await job.getQueryResults();
|
|
440
|
+
return rows;
|
|
441
|
+
} catch (err) {
|
|
442
|
+
throw new Error(err.message);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async fetchCostsFromCloud(query) {
|
|
446
|
+
const conf = this.config.getOptionalConfigArray(
|
|
447
|
+
"backend.infraWallet.integrations.gcp"
|
|
448
|
+
);
|
|
449
|
+
if (!conf) {
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
const promises = [];
|
|
453
|
+
const results = [];
|
|
454
|
+
for (const c of conf) {
|
|
455
|
+
const name = c.getString("name");
|
|
456
|
+
const keyFilePath = c.getString("keyFilePath");
|
|
457
|
+
const projectId = c.getString("projectId");
|
|
458
|
+
const datasetId = c.getString("datasetId");
|
|
459
|
+
const tableId = c.getString("tableId");
|
|
460
|
+
const tags = c.getOptionalStringArray("tags");
|
|
461
|
+
const tagKeyValues = {};
|
|
462
|
+
tags == null ? void 0 : tags.forEach((tag) => {
|
|
463
|
+
const [k, v] = tag.split(":");
|
|
464
|
+
tagKeyValues[k.trim()] = v.trim();
|
|
465
|
+
});
|
|
466
|
+
const categoryMappings = await getCategoryMappings(this.database, "gcp");
|
|
467
|
+
const promise = (async () => {
|
|
468
|
+
try {
|
|
469
|
+
const costResponse = await this.queryBigQuery(
|
|
470
|
+
keyFilePath,
|
|
471
|
+
projectId,
|
|
472
|
+
datasetId,
|
|
473
|
+
tableId,
|
|
474
|
+
query
|
|
475
|
+
);
|
|
476
|
+
const transformedData = lodash.reduce(
|
|
477
|
+
costResponse,
|
|
478
|
+
(acc, row) => {
|
|
479
|
+
const period = row.period;
|
|
480
|
+
const keyName = `${name}_${row.project}_${row.service}`;
|
|
481
|
+
if (!acc[keyName]) {
|
|
482
|
+
acc[keyName] = {
|
|
483
|
+
id: keyName,
|
|
484
|
+
name: `GCP/${name}`,
|
|
485
|
+
service: this.convertServiceName(row.service),
|
|
486
|
+
category: getCategoryByServiceName(
|
|
487
|
+
row.service,
|
|
488
|
+
categoryMappings
|
|
489
|
+
),
|
|
490
|
+
provider: "GCP",
|
|
491
|
+
reports: {},
|
|
492
|
+
...{ project: row.project },
|
|
493
|
+
// TODO: how should we handle the project field? for now, we add project name as a field in the report
|
|
494
|
+
...tagKeyValues
|
|
495
|
+
// note that if there is a tag `project:foo` in config, it overrides the project field set above
|
|
496
|
+
};
|
|
262
497
|
}
|
|
498
|
+
acc[keyName].reports[period] = row.total_cost;
|
|
263
499
|
return acc;
|
|
264
500
|
},
|
|
265
501
|
{}
|
|
@@ -268,7 +504,7 @@ class AzureClient {
|
|
|
268
504
|
results.push(value);
|
|
269
505
|
});
|
|
270
506
|
} catch (e) {
|
|
271
|
-
|
|
507
|
+
this.logger.error(e);
|
|
272
508
|
}
|
|
273
509
|
})();
|
|
274
510
|
promises.push(promise);
|
|
@@ -306,9 +542,10 @@ async function createRouter(options) {
|
|
|
306
542
|
await setUpDatabase(database);
|
|
307
543
|
const router = Router__default.default();
|
|
308
544
|
router.use(express__default.default.json());
|
|
309
|
-
const azureClient = AzureClient.create(config, database);
|
|
310
|
-
const awsClient = AwsClient.create(config, database);
|
|
311
|
-
const
|
|
545
|
+
const azureClient = AzureClient.create(config, database, logger);
|
|
546
|
+
const awsClient = AwsClient.create(config, database, logger);
|
|
547
|
+
const gcpClient = GCPClient.create(config, database, logger);
|
|
548
|
+
const cloudClients = [azureClient, awsClient, gcpClient];
|
|
312
549
|
router.get("/health", (_, response) => {
|
|
313
550
|
logger.info("PONG!");
|
|
314
551
|
response.json({ status: "ok" });
|
|
@@ -338,19 +575,23 @@ async function createRouter(options) {
|
|
|
338
575
|
results.push(cost);
|
|
339
576
|
});
|
|
340
577
|
} else {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
578
|
+
try {
|
|
579
|
+
const costs = await client.fetchCostsFromCloud({
|
|
580
|
+
filters,
|
|
581
|
+
groups,
|
|
582
|
+
granularity,
|
|
583
|
+
startTime,
|
|
584
|
+
endTime
|
|
585
|
+
});
|
|
586
|
+
await cache.set(cacheKey, costs, {
|
|
587
|
+
ttl: 60 * 60 * 2 * 1e3
|
|
588
|
+
});
|
|
589
|
+
costs.forEach((cost) => {
|
|
590
|
+
results.push(cost);
|
|
591
|
+
});
|
|
592
|
+
} catch (e) {
|
|
593
|
+
logger.error(e);
|
|
594
|
+
}
|
|
354
595
|
}
|
|
355
596
|
})();
|
|
356
597
|
promises.push(fetchCloudCosts);
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/service/functions.ts","../src/service/AwsClient.ts","../src/service/AzureClient.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport { CategoryMapping } from './types';\n\nexport async function getCategoryMappings(\n database: DatabaseService,\n provider: string,\n): Promise<{ [category: string]: string[] }> {\n const result: { [category: string]: string[] } = {};\n const client = await database.getClient();\n const mappings = await client\n .where({ provider: provider })\n .select()\n .from<CategoryMapping>('category_mappings');\n mappings.forEach(mapping => {\n if (typeof mapping.cloud_service_names === 'string') {\n // just in case if the database such as sqlite does not support JSON column\n result[mapping.category] = JSON.parse(mapping.cloud_service_names);\n } else {\n result[mapping.category] = mapping.cloud_service_names;\n }\n });\n return result;\n}\n\nexport function getCategoryByServiceName(\n serviceName: string,\n categoryMappings: { [category: string]: string[] },\n): string {\n for (const key of Object.keys(categoryMappings)) {\n const serviceNames = categoryMappings[key];\n if (serviceNames && serviceNames.includes(serviceName)) {\n return key;\n }\n }\n\n return 'Uncategorized';\n}\n","import {\n CostExplorerClient,\n GetCostAndUsageCommand,\n GetCostAndUsageCommandInput,\n Granularity,\n} from '@aws-sdk/client-cost-explorer';\nimport { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts';\nimport { DatabaseService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport { CostQuery, Report } from './types';\nimport { getCategoryMappings, getCategoryByServiceName } from './functions';\n\nexport class AwsClient implements InfraWalletApi {\n static create(config: Config, database: DatabaseService) {\n return new AwsClient(config, database);\n }\n\n constructor(\n private readonly config: Config,\n private readonly database: DatabaseService,\n ) {}\n\n async fetchCostsFromCloud(query: CostQuery): Promise<Report[]> {\n const conf = this.config.getOptionalConfigArray(\n 'backend.infraWallet.integrations.aws',\n );\n if (!conf) {\n return [];\n }\n\n const promises = [];\n const results: Report[] = [];\n const groupPairs = [];\n query.groups.split(',').forEach(group => {\n if (group.includes(':')) {\n const [type, name] = group.split(':');\n groupPairs.push({ type, name });\n }\n });\n\n for (const c of conf) {\n const name = c.getString('name');\n const accountId = c.getString('accountId');\n const assumedRoleName = c.getString('assumedRoleName');\n const accessKeyId = c.getOptionalString('accessKeyId');\n const accessKeySecret = c.getOptionalString('accessKeySecret');\n const tags = c.getOptionalStringArray('tags');\n const tagKeyValues: { [key: string]: string } = {};\n tags?.forEach(tag => {\n const [k, v] = tag.split(':');\n tagKeyValues[k.trim()] = v.trim();\n });\n const categoryMappings = await getCategoryMappings(this.database, 'aws');\n\n let stsParams = {};\n if (accessKeyId && accessKeySecret) {\n stsParams = {\n region: 'us-east-1',\n credentials: {\n accessKeyId: accessKeyId as string,\n secretAccessKey: accessKeySecret as string,\n },\n };\n } else {\n stsParams = {\n region: 'us-east-1',\n };\n }\n const promise = (async () => {\n const client = new STSClient(stsParams);\n const commandInput = {\n // AssumeRoleRequest\n RoleArn: `arn:aws:iam::${accountId}:role/${assumedRoleName}`,\n RoleSessionName: 'AssumeRoleSession1',\n };\n const assumeRoleCommand = new AssumeRoleCommand(commandInput);\n const assumeRoleResponse = await client.send(assumeRoleCommand);\n // init aws cost explorer client\n const awsCeClient = new CostExplorerClient({\n region: 'us-east-1',\n credentials: {\n accessKeyId: assumeRoleResponse.Credentials?.AccessKeyId as string,\n secretAccessKey: assumeRoleResponse.Credentials\n ?.SecretAccessKey as string,\n sessionToken: assumeRoleResponse.Credentials\n ?.SessionToken as string,\n },\n });\n\n // query this aws account's cost and usage using @aws-sdk/client-cost-explorer\n const input: GetCostAndUsageCommandInput = {\n TimePeriod: {\n Start: moment(parseInt(query.startTime, 10)).format('YYYY-MM-DD'),\n End: moment(parseInt(query.endTime, 10)).format('YYYY-MM-DD'),\n },\n Granularity: query.granularity.toUpperCase() as Granularity,\n Filter: { Dimensions: { Key: 'RECORD_TYPE', Values: ['Usage'] } },\n GroupBy: [{ Type: 'DIMENSION', Key: 'SERVICE' }],\n Metrics: ['UnblendedCost'],\n };\n \n const getCostCommand = new GetCostAndUsageCommand(input);\n const costAndusageResponse = await awsCeClient.send(getCostCommand);\n\n const transformedData = reduce(\n costAndusageResponse.ResultsByTime,\n (acc: { [key: string]: Report }, row) => {\n const rowTime = row.TimePeriod?.Start;\n const period = rowTime ? rowTime.substring(0, 7): 'unknown';\n if (row.Groups) {\n row.Groups.forEach(group => {\n const groupKeys = group.Keys ? group.Keys[0] : '';\n const keyName = `${name}_${groupKeys}`;\n \n if (!acc[keyName]) {\n acc[keyName] = {\n id: keyName,\n name: name,\n service: `${groupKeys} (AWS)`,\n category: getCategoryByServiceName(\n groupKeys,\n categoryMappings,\n ),\n provider: 'AWS',\n reports: {},\n ...tagKeyValues,\n };\n }\n\n const groupMetrics = group.Metrics;\n \n if (groupMetrics !== undefined) {\n acc[keyName].reports[period] = parseFloat(\n groupMetrics.UnblendedCost.Amount ?? '0.0',\n );\n }\n });\n }\n\n return acc;\n },\n {},\n );\n\n Object.values(transformedData).map((value: any) => {\n results.push(value);\n });\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n return results;\n }\n}\n","import { CostManagementClient } from '@azure/arm-costmanagement';\nimport { ClientSecretCredential } from '@azure/identity';\nimport { DatabaseService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport { CostQuery, Report } from './types';\nimport { getCategoryMappings, getCategoryByServiceName } from './functions';\n\nexport class AzureClient implements InfraWalletApi {\n static create(config: Config, database: DatabaseService) {\n return new AzureClient(config, database);\n }\n\n constructor(\n private readonly config: Config,\n private readonly database: DatabaseService,\n ) {}\n\n async queryAzureCostExplorer(\n azureClient: any,\n subscription: string,\n granularity: string,\n groups: { type: string; name: string }[],\n startDate: moment.Moment,\n endDate: moment.Moment,\n ) {\n const scope = `/subscriptions/${subscription}`;\n\n const query = {\n type: 'ActualCost',\n dataset: {\n granularity: granularity,\n aggregation: { totalCostUSD: { name: 'CostUSD', function: 'Sum' } },\n grouping: groups,\n },\n timeframe: 'Custom',\n timePeriod: {\n from: startDate.toISOString(),\n to: endDate.toISOString(),\n },\n };\n\n const result = await azureClient.query.usage(scope, query);\n return result;\n }\n\n async fetchCostsFromCloud(query: CostQuery): Promise<Report[]> {\n const conf = this.config.getOptionalConfigArray(\n 'backend.infraWallet.integrations.azure',\n );\n if (!conf) {\n return [];\n }\n\n const promises = [];\n const results: Report[] = [];\n\n const groupPairs = [{ type: 'Dimension', name: 'ServiceName' }];\n for (const c of conf) {\n const name = c.getString('name');\n const subscriptionId = c.getString('subscriptionId');\n const tenantId = c.getString('tenantId');\n const clientId = c.getString('clientId');\n const clientSecret = c.getString('clientSecret');\n const credential = new ClientSecretCredential(\n tenantId as string,\n clientId as string,\n clientSecret as string,\n );\n const client = new CostManagementClient(credential);\n const tags = c.getOptionalStringArray('tags');\n const tagKeyValues: { [key: string]: string } = {};\n tags?.forEach(tag => {\n const [k, v] = tag.split(':');\n tagKeyValues[k.trim()] = v.trim();\n });\n const categoryMappings = await getCategoryMappings(\n this.database,\n 'azure',\n );\n\n const promise = (async () => {\n try {\n const costResponse = await this.queryAzureCostExplorer(\n client,\n subscriptionId as string,\n query.granularity,\n groupPairs,\n moment(parseInt(query.startTime, 10)),\n moment(parseInt(query.endTime, 10)),\n );\n\n const transformedData = reduce(\n costResponse.rows,\n (acc: { [key: string]: Report }, row) => {\n let keyName = name;\n for (let i = 0; i < groupPairs.length; i++) {\n keyName += `->${row[i + 2]}`;\n }\n\n if (!acc[keyName]) {\n acc[keyName] = {\n id: keyName,\n name: name,\n service: `${row[2]} (Azure)`,\n category: getCategoryByServiceName(row[2], categoryMappings),\n provider: 'Azure',\n reports: {},\n ...tagKeyValues,\n };\n }\n\n if (\n !moment(row[1]).isBefore(moment(parseInt(query.startTime, 10)))\n ) {\n acc[keyName].reports[row[1].substring(0, 7)] = parseFloat(\n row[0],\n );\n }\n return acc;\n },\n {},\n );\n\n Object.values(transformedData).map((value: Report) => {\n results.push(value);\n });\n } catch (e) {\n throw new Error(e.message);\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n return results;\n }\n}\n","import { errorHandler } from '@backstage/backend-common';\nimport {\n CacheService,\n DatabaseService,\n LoggerService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { AwsClient } from './AwsClient';\nimport { AzureClient } from './AzureClient';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport { Report } from './types';\n\nexport interface RouterOptions {\n logger: LoggerService;\n config: Config;\n cache: CacheService;\n database: DatabaseService;\n}\n\nasync function setUpDatabase(database: DatabaseService) {\n // check database migrations\n const client = await database.getClient();\n const migrationsDir = resolvePackagePath(\n '@electrolux-oss/plugin-infrawallet-backend',\n 'migrations',\n );\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n // if there are no category mappings, seed the database\n const category_mappings_count = await client('category_mappings').count(\n 'id as c',\n );\n if (\n category_mappings_count[0].c === 0 ||\n category_mappings_count[0].c === '0'\n ) {\n const seedsDir = resolvePackagePath(\n '@electrolux-oss/plugin-infrawallet-backend',\n 'seeds',\n );\n await client.seed.run({ directory: seedsDir });\n }\n}\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { logger, config, cache, database } = options;\n // do database migrations here to support the legacy backend system\n await setUpDatabase(database);\n\n const router = Router();\n router.use(express.json());\n\n const azureClient = AzureClient.create(config, database);\n const awsClient = AwsClient.create(config, database);\n const cloudClients: InfraWalletApi[] = [azureClient, awsClient];\n\n router.get('/health', (_, response) => {\n logger.info('PONG!');\n response.json({ status: 'ok' });\n });\n\n router.get('/reports', async (request, response) => {\n const filters = request.query.filters as string;\n const groups = request.query.groups as string;\n const granularity = request.query.granularity as string;\n const startTime = request.query.startTime as string;\n const endTime = request.query.endTime as string;\n const promises: Promise<void>[] = [];\n const results: Report[] = [];\n\n cloudClients.forEach(async client => {\n const fetchCloudCosts = (async () => {\n const cacheKey = [\n client.constructor.name,\n filters,\n groups,\n granularity,\n startTime,\n endTime,\n ].join('_');\n const cachedCosts = (await cache.get(cacheKey)) as Report[] | undefined;\n if (cachedCosts) {\n logger.debug(`${client.constructor.name} costs from cache`);\n cachedCosts.forEach(cost => {\n results.push(cost);\n });\n } else {\n const costs = await client.fetchCostsFromCloud({\n filters: filters,\n groups: groups,\n granularity: granularity,\n startTime: startTime,\n endTime: endTime,\n });\n await cache.set(cacheKey, costs, {\n ttl: 60 * 60 * 2 * 1000,\n }); // cache for 2 hours\n costs.forEach(cost => {\n results.push(cost);\n });\n }\n })();\n promises.push(fetchCloudCosts);\n });\n\n await Promise.all(promises);\n\n response.json({ data: results, status: 'ok' });\n });\n\n router.use(errorHandler());\n return router;\n}\n","import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\n\n/**\n * infraWalletPlugin backend plugin\n *\n * @public\n */\nexport const infraWalletPlugin = createBackendPlugin({\n pluginId: 'infrawallet',\n register(env) {\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n cache: coreServices.cache,\n database: coreServices.database,\n },\n async init({ httpRouter, logger, config, cache, database }) {\n httpRouter.use(\n await createRouter({\n logger,\n config,\n cache,\n database,\n }),\n );\n httpRouter.addAuthPolicy({\n path: '/health',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["STSClient","AssumeRoleCommand","CostExplorerClient","moment","GetCostAndUsageCommand","reduce","_a","ClientSecretCredential","CostManagementClient","resolvePackagePath","Router","express","errorHandler","createBackendPlugin","coreServices"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/service/functions.ts","../src/service/AwsClient.ts","../src/service/AzureClient.ts","../src/service/GCPClient.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport { CategoryMapping } from './types';\n\nexport async function getCategoryMappings(\n database: DatabaseService,\n provider: string,\n): Promise<{ [category: string]: string[] }> {\n const result: { [category: string]: string[] } = {};\n const client = await database.getClient();\n const mappings = await client\n .where({ provider: provider })\n .select()\n .from<CategoryMapping>('category_mappings');\n mappings.forEach(mapping => {\n if (typeof mapping.cloud_service_names === 'string') {\n // just in case if the database such as sqlite does not support JSON column\n result[mapping.category] = JSON.parse(mapping.cloud_service_names);\n } else {\n result[mapping.category] = mapping.cloud_service_names;\n }\n });\n return result;\n}\n\nexport function getCategoryByServiceName(\n serviceName: string,\n categoryMappings: { [category: string]: string[] },\n): string {\n for (const key of Object.keys(categoryMappings)) {\n const serviceNames = categoryMappings[key];\n if (serviceNames && serviceNames.includes(serviceName)) {\n return key;\n }\n }\n\n return 'Uncategorized';\n}\n","import {\n CostExplorerClient,\n GetCostAndUsageCommand,\n GetCostAndUsageCommandInput,\n Granularity,\n} from '@aws-sdk/client-cost-explorer';\nimport { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts';\nimport { LoggerService, DatabaseService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport { CostQuery, Report } from './types';\nimport { getCategoryMappings, getCategoryByServiceName } from './functions';\n\nexport class AwsClient implements InfraWalletApi {\n static create(\n config: Config,\n database: DatabaseService,\n logger: LoggerService,\n ) {\n return new AwsClient(config, database, logger);\n }\n\n constructor(\n private readonly config: Config,\n private readonly database: DatabaseService,\n private readonly logger: LoggerService,\n ) {\n }\n\n convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Amazon', 'AWS'];\n\n const aliases = new Map<string, string>([\n ['Elastic Compute Cloud - Compute', 'EC2 - Instances'],\n ['Virtual Private Cloud', 'VPC (Virtual Private Cloud)'],\n ['Relational Database Service', 'RDS (Relational Database Service)'],\n ['Simple Storage Service', 'S3 (Simple Storage Service)'],\n [\n 'Managed Streaming for Apache Kafka',\n 'MSK (Managed Streaming for Apache Kafka)',\n ],\n [\n 'Elastic Container Service for Kubernetes',\n 'EKS (Elastic Container Service for Kubernetes)',\n ],\n ['Simple Queue Service', 'SQS (Simple Queue Service)'],\n ['Simple Notification Service', 'SNS (Simple Notification Service)'],\n ]);\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n if (aliases.has(convertedName)) {\n convertedName = aliases.get(convertedName) || convertedName;\n }\n\n return `AWS/${convertedName}`;\n }\n\n async fetchCostsFromCloud(query: CostQuery): Promise<Report[]> {\n const conf = this.config.getOptionalConfigArray(\n 'backend.infraWallet.integrations.aws',\n );\n if (!conf) {\n return [];\n }\n\n const promises = [];\n const results: Report[] = [];\n const groupPairs = [];\n query.groups.split(',').forEach(group => {\n if (group.includes(':')) {\n const [type, name] = group.split(':');\n groupPairs.push({ type, name });\n }\n });\n\n for (const c of conf) {\n const name = c.getString('name');\n const accountId = c.getString('accountId');\n const assumedRoleName = c.getString('assumedRoleName');\n const accessKeyId = c.getOptionalString('accessKeyId');\n const accessKeySecret = c.getOptionalString('accessKeySecret');\n const tags = c.getOptionalStringArray('tags');\n const tagKeyValues: { [key: string]: string } = {};\n tags?.forEach(tag => {\n const [k, v] = tag.split(':');\n tagKeyValues[k.trim()] = v.trim();\n });\n const categoryMappings = await getCategoryMappings(this.database, 'aws');\n\n let stsParams = {};\n if (accessKeyId && accessKeySecret) {\n stsParams = {\n region: 'us-east-1',\n credentials: {\n accessKeyId: accessKeyId as string,\n secretAccessKey: accessKeySecret as string,\n },\n };\n } else {\n stsParams = {\n region: 'us-east-1',\n };\n }\n const promise = (async () => {\n try {\n const client = new STSClient(stsParams);\n const commandInput = {\n // AssumeRoleRequest\n RoleArn: `arn:aws:iam::${accountId}:role/${assumedRoleName}`,\n RoleSessionName: 'AssumeRoleSession1',\n };\n const assumeRoleCommand = new AssumeRoleCommand(commandInput);\n const assumeRoleResponse = await client.send(assumeRoleCommand);\n // init aws cost explorer client\n const awsCeClient = new CostExplorerClient({\n region: 'us-east-1',\n credentials: {\n accessKeyId: assumeRoleResponse.Credentials\n ?.AccessKeyId as string,\n secretAccessKey: assumeRoleResponse.Credentials\n ?.SecretAccessKey as string,\n sessionToken: assumeRoleResponse.Credentials\n ?.SessionToken as string,\n },\n });\n\n // query this aws account's cost and usage using @aws-sdk/client-cost-explorer\n let costAndUsageResults: any[] = [];\n let nextPageToken = undefined;\n\n do {\n const input: GetCostAndUsageCommandInput = {\n TimePeriod: {\n Start: moment(parseInt(query.startTime, 10)).format('YYYY-MM-DD'),\n End: moment(parseInt(query.endTime, 10)).format('YYYY-MM-DD'),\n },\n Granularity: query.granularity.toUpperCase() as Granularity,\n Filter: { Dimensions: { Key: 'RECORD_TYPE', Values: ['Usage'] } },\n GroupBy: [{ Type: 'DIMENSION', Key: 'SERVICE' }],\n Metrics: ['UnblendedCost'],\n NextPageToken: nextPageToken,\n };\n\n const getCostCommand = new GetCostAndUsageCommand(input);\n const costAndUsageResponse = await awsCeClient.send(getCostCommand);\n\n costAndUsageResults = costAndUsageResults.concat(costAndUsageResponse.ResultsByTime);\n nextPageToken = costAndUsageResponse.NextPageToken;\n } while (nextPageToken);\n\n const transformedData = reduce(\n costAndUsageResults,\n (accumulator: { [key: string]: Report }, row) => {\n const rowTime = row.TimePeriod?.Start;\n let period = 'unknown';\n if (rowTime) {\n if (query.granularity.toUpperCase() === 'MONTHLY') {\n period = rowTime.substring(0, 7);\n } else {\n period = rowTime;\n }\n }\n if (row.Groups) {\n row.Groups.forEach((group: any) => {\n const serviceName = group.Keys ? group.Keys[0] : '';\n const keyName = `${name}_${serviceName}`;\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n name: `AWS/${name}`,\n service: this.convertServiceName(serviceName),\n category: getCategoryByServiceName(\n serviceName,\n categoryMappings,\n ),\n provider: 'AWS',\n reports: {},\n ...tagKeyValues,\n };\n }\n\n const groupMetrics = group.Metrics;\n\n if (groupMetrics !== undefined) {\n accumulator[keyName].reports[period] = parseFloat(\n groupMetrics.UnblendedCost.Amount ?? '0.0',\n );\n }\n });\n }\n\n return accumulator;\n },\n {},\n );\n\n Object.values(transformedData).map((value: any) => {\n results.push(value);\n });\n } catch (e) {\n this.logger.error(e);\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n return results;\n }\n}\n","import { CostManagementClient, QueryDefinition } from '@azure/arm-costmanagement';\nimport { ClientSecretCredential } from '@azure/identity';\nimport { createHttpHeaders, createPipelineRequest } from \"@azure/core-rest-pipeline\";\nimport { LoggerService, DatabaseService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport { CostQuery, Report } from './types';\nimport { getCategoryMappings, getCategoryByServiceName } from './functions';\n\nexport class AzureClient implements InfraWalletApi {\n static create(config: Config, database: DatabaseService, logger: LoggerService) {\n return new AzureClient(config, database, logger);\n }\n\n constructor(\n private readonly config: Config,\n private readonly database: DatabaseService,\n private readonly logger: LoggerService,\n ) {\n }\n\n convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Azure'];\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n return `Azure/${convertedName}`;\n }\n\n formatDate(dateNumber: number): string | null {\n // dateNumber example: 20240407\n const dateString = dateNumber.toString();\n\n if (dateString.length !== 8) {\n return null;\n }\n\n const year = dateString.slice(0, 4);\n const month = dateString.slice(4, 6);\n const day = dateString.slice(6);\n\n return `${year}-${month}-${day}`;\n }\n\n async fetchDataWithRetry(\n client: CostManagementClient,\n url: string,\n body: any,\n maxRetries = 5\n ): Promise<any> {\n let retries = 0;\n\n while (retries < maxRetries) {\n const request = createPipelineRequest({\n url: url,\n method: \"POST\",\n body: JSON.stringify(body),\n headers: createHttpHeaders({\n \"Content-Type\": \"application/json\"\n }),\n });\n const response = await client.pipeline.sendRequest(client, request);\n if (response.status === 200) {\n return JSON.parse(response.bodyAsText || '{}');\n }\n else if (response.status === 429) {\n const retryAfter = parseInt(response.headers.get(\"x-ms-ratelimit-microsoft.costmanagement-entity-retry-after\") || '60', 10);\n this.logger.warn(`Hit Azure rate limit, retrying after ${retryAfter} seconds...`);\n await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));\n retries++;\n } else {\n throw new Error(response.bodyAsText as string);\n }\n }\n\n throw new Error('Max retries exceeded');\n }\n\n async queryAzureCostExplorer(\n azureClient: CostManagementClient,\n subscription: string,\n granularity: string,\n groups: { type: string; name: string }[],\n startDate: moment.Moment,\n endDate: moment.Moment,\n ) {\n // Azure SDK doesn't support pagination, so sending HTTP request directly\n const url = `https://management.azure.com/subscriptions/${subscription}/providers/Microsoft.CostManagement/query?api-version=2022-10-01`;\n\n const query: QueryDefinition = {\n type: 'ActualCost',\n dataset: {\n granularity: granularity,\n aggregation: { totalCostUSD: { name: 'CostUSD', function: 'Sum' } },\n grouping: groups,\n },\n timeframe: 'Custom',\n timePeriod: {\n from: startDate.toDate(),\n to: endDate.toDate(),\n },\n };\n\n let result = await this.fetchDataWithRetry(azureClient, url, query);\n let allResults = result.properties.rows;\n\n while (result.properties.nextLink) {\n result = await this.fetchDataWithRetry(azureClient, result.properties.nextLink, query);\n allResults = allResults.concat(result.properties.rows);\n }\n\n return allResults;\n }\n\n async fetchCostsFromCloud(query: CostQuery): Promise<Report[]> {\n const conf = this.config.getOptionalConfigArray(\n 'backend.infraWallet.integrations.azure',\n );\n if (!conf) {\n return [];\n }\n\n const categoryMappings = await getCategoryMappings(\n this.database,\n 'azure',\n );\n\n const promises = [];\n const results: Report[] = [];\n\n const groupPairs = [{ type: 'Dimension', name: 'ServiceName' }];\n for (const c of conf) {\n const name = c.getString('name');\n const subscriptionId = c.getString('subscriptionId');\n const tenantId = c.getString('tenantId');\n const clientId = c.getString('clientId');\n const clientSecret = c.getString('clientSecret');\n const credential = new ClientSecretCredential(\n tenantId as string,\n clientId as string,\n clientSecret as string,\n );\n const client = new CostManagementClient(credential);\n const tags = c.getOptionalStringArray('tags');\n const tagKeyValues: { [key: string]: string } = {};\n tags?.forEach(tag => {\n const [k, v] = tag.split(':');\n tagKeyValues[k.trim()] = v.trim();\n });\n\n const promise = (async () => {\n try {\n const costResponse = await this.queryAzureCostExplorer(\n client,\n subscriptionId as string,\n query.granularity,\n groupPairs,\n moment(parseInt(query.startTime, 10)),\n moment(parseInt(query.endTime, 10)),\n );\n\n /* \n Monthly cost sample:\n [\n 123.456,\n \"2024-04-07T00:00:00\", // BillingMonth\n \"Azure App Service\",\n \"EUR\"\n ]\n \n Daily cost sample:\n [\n 12.3456,\n 20240407, // UsageDate\n \"Azure App Service\",\n \"EUR\"\n ]\n */\n const transformedData = reduce(\n costResponse,\n (accumulator: { [key: string]: Report }, row) => {\n const cost = row[0];\n let date = row[1];\n const serviceName = row[2];\n\n if (query.granularity.toUpperCase() === 'DAILY') {\n // 20240407 -> \"2024-04-07\"\n date = this.formatDate(date);\n }\n\n let keyName = name;\n for (let i = 0; i < groupPairs.length; i++) {\n keyName += `->${row[i + 2]}`;\n }\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n name: `Azure/${name}`,\n service: this.convertServiceName(serviceName),\n category: getCategoryByServiceName(serviceName, categoryMappings),\n provider: 'Azure',\n reports: {},\n ...tagKeyValues,\n };\n }\n\n if (\n !moment(date).isBefore(moment(parseInt(query.startTime, 10)))\n ) {\n if (query.granularity.toUpperCase() === 'MONTHLY') {\n const yearMonth = date.substring(0, 7);\n accumulator[keyName].reports[yearMonth] = parseFloat(cost);\n } else {\n accumulator[keyName].reports[date] = parseFloat(cost);\n }\n }\n return accumulator;\n },\n {},\n );\n\n Object.values(transformedData).map((value: Report) => {\n results.push(value);\n });\n } catch (e) {\n this.logger.error(e);\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n return results;\n }\n}\n","import { LoggerService, DatabaseService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { BigQuery } from '@google-cloud/bigquery';\nimport { reduce } from 'lodash';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport { CostQuery, Report } from './types';\nimport { getCategoryByServiceName, getCategoryMappings } from './functions';\n\nexport class GCPClient implements InfraWalletApi {\n static create(\n config: Config,\n database: DatabaseService,\n logger: LoggerService,\n ) {\n return new GCPClient(config, database, logger);\n }\n\n constructor(\n private readonly config: Config,\n private readonly database: DatabaseService,\n private readonly logger: LoggerService,\n ) {}\n\n convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['GCP'];\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n return `GCP/${convertedName}`;\n }\n\n async queryBigQuery(\n keyFilePath: string,\n projectId: string,\n datasetId: string,\n tableId: string,\n query: CostQuery,\n ) {\n // Configure a JWT auth client\n const options = {\n keyFilename: keyFilePath,\n projectId: projectId,\n };\n\n // Initialize the BigQuery API\n const bigquery = new BigQuery(options);\n try {\n const periodFormat = query.granularity.toUpperCase() === 'MONTHLY' ? '%Y-%m' : '%Y-%m-%d';\n const sql = `\n SELECT\n project.name AS project,\n service.description AS service,\n FORMAT_TIMESTAMP('${periodFormat}', usage_start_time) AS period,\n SUM(cost) AS total_cost\n FROM\n \\`${projectId}.${datasetId}.${tableId}\\`\n WHERE\n project.name IS NOT NULL\n AND cost > 0\n AND usage_start_time >= TIMESTAMP_MILLIS(${query.startTime})\n AND usage_start_time <= TIMESTAMP_MILLIS(${query.endTime})\n GROUP BY\n project, service, period\n ORDER BY\n project, period, total_cost DESC`;\n\n // Run the query as a job\n const [job] = await bigquery.createQueryJob({\n query: sql,\n location: 'US',\n });\n\n // Wait for the query to finish\n const [rows] = await job.getQueryResults();\n\n return rows;\n } catch (err) {\n throw new Error(err.message);\n }\n }\n\n async fetchCostsFromCloud(query: CostQuery): Promise<Report[]> {\n const conf = this.config.getOptionalConfigArray(\n 'backend.infraWallet.integrations.gcp',\n );\n if (!conf) {\n return [];\n }\n\n const promises = [];\n const results: Report[] = [];\n\n for (const c of conf) {\n const name = c.getString('name');\n const keyFilePath = c.getString('keyFilePath');\n const projectId = c.getString('projectId');\n const datasetId = c.getString('datasetId');\n const tableId = c.getString('tableId');\n const tags = c.getOptionalStringArray('tags');\n const tagKeyValues: { [key: string]: string } = {};\n tags?.forEach(tag => {\n const [k, v] = tag.split(':');\n tagKeyValues[k.trim()] = v.trim();\n });\n const categoryMappings = await getCategoryMappings(this.database, 'gcp');\n\n const promise = (async () => {\n try {\n const costResponse = await this.queryBigQuery(\n keyFilePath,\n projectId,\n datasetId,\n tableId,\n query,\n );\n const transformedData = reduce(\n costResponse,\n (acc: { [key: string]: Report }, row) => {\n const period = row.period;\n const keyName = `${name}_${row.project}_${row.service}`;\n\n if (!acc[keyName]) {\n acc[keyName] = {\n id: keyName,\n name: `GCP/${name}`,\n service: this.convertServiceName(row.service),\n category: getCategoryByServiceName(\n row.service,\n categoryMappings,\n ),\n provider: 'GCP',\n reports: {},\n ...{ project: row.project }, // TODO: how should we handle the project field? for now, we add project name as a field in the report\n ...tagKeyValues, // note that if there is a tag `project:foo` in config, it overrides the project field set above\n };\n }\n\n acc[keyName].reports[period] = row.total_cost;\n\n return acc;\n },\n {},\n );\n\n Object.values(transformedData).map((value: any) => {\n results.push(value);\n });\n } catch (e) {\n this.logger.error(e);\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n return results;\n }\n}\n","import { errorHandler } from '@backstage/backend-common';\nimport {\n CacheService,\n DatabaseService,\n LoggerService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { AwsClient } from './AwsClient';\nimport { AzureClient } from './AzureClient';\nimport { GCPClient } from './GCPClient';\nimport { InfraWalletApi } from './InfraWalletApi';\nimport { Report } from './types';\n\nexport interface RouterOptions {\n logger: LoggerService;\n config: Config;\n cache: CacheService;\n database: DatabaseService;\n}\n\nasync function setUpDatabase(database: DatabaseService) {\n // check database migrations\n const client = await database.getClient();\n const migrationsDir = resolvePackagePath(\n '@electrolux-oss/plugin-infrawallet-backend',\n 'migrations',\n );\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n // if there are no category mappings, seed the database\n const category_mappings_count = await client('category_mappings').count(\n 'id as c',\n );\n if (\n category_mappings_count[0].c === 0 ||\n category_mappings_count[0].c === '0'\n ) {\n const seedsDir = resolvePackagePath(\n '@electrolux-oss/plugin-infrawallet-backend',\n 'seeds',\n );\n await client.seed.run({ directory: seedsDir });\n }\n}\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { logger, config, cache, database } = options;\n // do database migrations here to support the legacy backend system\n await setUpDatabase(database);\n\n const router = Router();\n router.use(express.json());\n\n const azureClient = AzureClient.create(config, database, logger);\n const awsClient = AwsClient.create(config, database, logger);\n const gcpClient = GCPClient.create(config, database, logger);\n const cloudClients: InfraWalletApi[] = [azureClient, awsClient, gcpClient];\n\n router.get('/health', (_, response) => {\n logger.info('PONG!');\n response.json({ status: 'ok' });\n });\n\n router.get('/reports', async (request, response) => {\n const filters = request.query.filters as string;\n const groups = request.query.groups as string;\n const granularity = request.query.granularity as string;\n const startTime = request.query.startTime as string;\n const endTime = request.query.endTime as string;\n const promises: Promise<void>[] = [];\n const results: Report[] = [];\n\n cloudClients.forEach(async client => {\n const fetchCloudCosts = (async () => {\n const cacheKey = [\n client.constructor.name,\n filters,\n groups,\n granularity,\n startTime,\n endTime,\n ].join('_');\n const cachedCosts = (await cache.get(cacheKey)) as Report[] | undefined;\n if (cachedCosts) {\n logger.debug(`${client.constructor.name} costs from cache`);\n cachedCosts.forEach(cost => {\n results.push(cost);\n });\n } else {\n try {\n const costs = await client.fetchCostsFromCloud({\n filters: filters,\n groups: groups,\n granularity: granularity,\n startTime: startTime,\n endTime: endTime,\n });\n await cache.set(cacheKey, costs, {\n ttl: 60 * 60 * 2 * 1000,\n }); // cache for 2 hours\n costs.forEach(cost => {\n results.push(cost);\n });\n } catch (e) {\n logger.error(e);\n }\n }\n })();\n promises.push(fetchCloudCosts);\n });\n\n await Promise.all(promises);\n\n response.json({ data: results, status: 'ok' });\n });\n\n router.use(errorHandler());\n return router;\n}\n","import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\n\n/**\n * infraWalletPlugin backend plugin\n *\n * @public\n */\nexport const infraWalletPlugin = createBackendPlugin({\n pluginId: 'infrawallet',\n register(env) {\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n cache: coreServices.cache,\n database: coreServices.database,\n },\n async init({ httpRouter, logger, config, cache, database }) {\n httpRouter.use(\n await createRouter({\n logger,\n config,\n cache,\n database,\n }),\n );\n httpRouter.addAuthPolicy({\n path: '/health',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["STSClient","AssumeRoleCommand","CostExplorerClient","moment","GetCostAndUsageCommand","reduce","_a","createPipelineRequest","createHttpHeaders","ClientSecretCredential","CostManagementClient","bigquery","BigQuery","resolvePackagePath","Router","express","errorHandler","createBackendPlugin","coreServices"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electrolux-oss/plugin-infrawallet-backend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "backend-plugin"
|
|
6
6
|
},
|
|
@@ -37,12 +37,14 @@
|
|
|
37
37
|
"@aws-sdk/client-cost-explorer": "^3.554.0",
|
|
38
38
|
"@aws-sdk/client-sts": "^3.554.0",
|
|
39
39
|
"@azure/arm-costmanagement": "1.0.0-beta.1",
|
|
40
|
+
"@azure/core-rest-pipeline": "1.16.0",
|
|
40
41
|
"@azure/identity": "4.1.0",
|
|
41
42
|
"@backstage/backend-common": "^0.22.0",
|
|
42
43
|
"@backstage/backend-defaults": "^0.2.18",
|
|
43
44
|
"@backstage/backend-plugin-api": "^0.6.18",
|
|
44
45
|
"@backstage/config": "^1.2.0",
|
|
45
46
|
"@backstage/types": "^1.1.1",
|
|
47
|
+
"@google-cloud/bigquery": "7.7.1",
|
|
46
48
|
"@types/express": "*",
|
|
47
49
|
"express": "^4.17.1",
|
|
48
50
|
"express-promise-router": "^4.1.0",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// this default mapping is taken from the Google Cloud Platform Services Summary page
|
|
2
|
+
// https://cloud.google.com/terms/services
|
|
3
|
+
exports.seed = async knex => {
|
|
4
|
+
await knex('category_mappings').insert([
|
|
5
|
+
{
|
|
6
|
+
provider: 'gcp',
|
|
7
|
+
category: 'Compute',
|
|
8
|
+
cloud_service_names: JSON.stringify(['App Engine', 'Batch', 'Compute Engine', 'Google Cloud VMware Engine (GCVE)', 'VM Manager', 'Workload Manager', 'Cloud Run', 'Cloud Functions', 'Cloud Functions for Firebase', 'Cloud Scheduler', 'Cloud Tasks', 'Eventarc', 'Workflows']),
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
provider: 'gcp',
|
|
12
|
+
category: 'Storage',
|
|
13
|
+
cloud_service_names: JSON.stringify(['Backup for GKE', 'Cloud Storage', 'Persistent Disk', 'Cloud Filestore', 'Cloud Storage for Firebase', 'NetApp Volumes']),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
provider: 'gcp',
|
|
17
|
+
category: 'Database',
|
|
18
|
+
cloud_service_names: JSON.stringify(['AlloyDB', 'Cloud Bigtable', 'Datastore', 'Firestore', 'Memorystore', 'Cloud Memorystore for Redis', 'Cloud Spanner', 'Cloud SQL']),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
provider: 'gcp',
|
|
22
|
+
category: 'Networking',
|
|
23
|
+
cloud_service_names: JSON.stringify(['Networking', 'Cloud CDN', 'Cloud DNS', 'Cloud IDS (Cloud Intrusion Detection System)', 'Cloud Interconnect', 'Cloud Load Balancing', 'Cloud NAT (Network Address Translation)', 'Cloud NGFW', 'Cloud NGFW Enterprise', 'Cloud Router', 'Cloud VPN', 'Google Cloud Armor', 'Google Cloud Armor Enterprise', 'Media CDN', 'Network Connectivity Center', 'Network Intelligence Center', 'Network Service Tiers', 'Secure Web Proxy (SWP)', 'Service Directory', 'Spectrum Access System', 'Telecom Network Automation', 'Traffic Director', 'Virtual Private Cloud']),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
provider: 'gcp',
|
|
27
|
+
category: 'Operations',
|
|
28
|
+
cloud_service_names: JSON.stringify(['Cloud Logging', 'Cloud Error Reporting', 'Cloud Monitoring', 'Cloud Profiler', 'Cloud Trace', 'Google Cloud Backup and DR']),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
provider: 'gcp',
|
|
32
|
+
category: 'Developer Tools',
|
|
33
|
+
cloud_service_names: JSON.stringify(['Artifact Registry', 'Assured Open Source Software (AOSS)', 'Cloud Build', 'Cloud Deploy', 'Cloud Source Repositories', 'Cloud Workstations', 'Container Registry', 'Firebase Test Lab', 'Secure Source Manager', 'Test Lab']),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
provider: 'gcp',
|
|
37
|
+
category: 'Analytics',
|
|
38
|
+
cloud_service_names: JSON.stringify(['BigQuery', 'BigQuery Storage API', 'Cloud Composer', 'Cloud Data Fusion', 'Cloud Life Sciences', 'Data Catalog', 'Dataform', 'Dataplex', 'Dataflow', 'Dataproc', 'Dataproc Metastore', 'Datastream', 'Google Earth Engine', 'Looker (Google Cloud core)', 'Looker Studio', 'Looker Studio Pro', 'Cloud Pub/Sub']),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
provider: 'gcp',
|
|
42
|
+
category: 'Artificial Intelligence',
|
|
43
|
+
cloud_service_names: JSON.stringify(['Agent Assist', 'Anti Money Laundering AI (AML AI)', 'Contact Center AI (CCAI)', 'Contact Center AI Insights', 'Contact Center AI (CCAI) Platform', 'Dialogflow Essentials', 'Dialogflow Customer Experience Edition', 'Cloud Dialogflow API', 'Document AI', 'Document Workbench', 'Human-in-the-Loop AI', 'Document AI Warehouse', 'Talent Solution', 'Translation Hub', 'Vertex AI Search for Industry', 'Vertex AI Search for Retail', 'Recommendations AI', 'Recommendation Engine API', 'Retail Search', 'Cloud Natural Language API', 'Cloud Translation API', 'Cloud Vision', 'Cloud Vision', 'Media Translation API', 'Speaker ID', 'Speech On Device', 'Speech-to-Text', 'Cloud Text-to-Speech API', 'Timeseries Insights API', 'Video Intelligence API', 'Visual Inspection AI', 'AI Platform Data Labeling', 'AI Platform Training and Prediction', 'AutoML', 'Vertex AI Platform', 'Vertex AI Neural Architecture Search (NAS)', 'Vertex AI Vision', 'Gemini for Google Cloud', 'Vertex AI API', 'Vertex AI Conversation', 'Vertex AI Model Garden', 'Vertex AI Search', 'Vertex AI Studio', 'Vertex AI']),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
provider: 'gcp',
|
|
47
|
+
category: 'Application Integration',
|
|
48
|
+
cloud_service_names: JSON.stringify(['Apigee', 'Apigee Edge', 'API Gateway', 'Application Integration', 'Cloud Endpoints', 'Integration Connectors']),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
provider: 'gcp',
|
|
52
|
+
category: 'Containers',
|
|
53
|
+
cloud_service_names: JSON.stringify(['Kubernetes Engine', 'Google Kubernetes Engine', 'GKE Enterprise', 'Config Sync', 'Policy Controller', 'Identity Service', 'GKE Enterprise Integration with Google Cloud Platform Services', 'GKE Enterprise Premium Software', 'Service Mesh', 'GKE Autopilot', 'Connect', 'GKE Hub']),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
provider: 'gcp',
|
|
57
|
+
category: 'Security, Identity, & Compliance',
|
|
58
|
+
cloud_service_names: JSON.stringify(['Access Transparency', 'Assured Workloads', 'Binary Authorization', 'Certificate Authority Service', 'Certificate Manager', 'Cloud Asset Inventory', 'Cloud External Key Manager', 'Cloud EKM','Cloud HSM', 'Cloud Key Management Service (KMS)', 'Event Threat Detection', 'Key Access Justifications (KAJ)', 'Risk Manager', 'Security Command Center', 'Sensitive Data Protection', 'VPC Service Controls', 'Secret Manager', 'Web Security Scanner', 'Access Approval', 'Access Context Manager', 'Chrome Enterprise Premium', 'Cloud Identity Services', 'Firebase App Check', 'Firebase Authentication', 'Google Cloud Identity-Aware Proxy', 'Identity & Access Management (IAM)', 'Identity Platform', 'Managed Service for Microsoft Active Directory (AD)', 'Organization Policy', 'Resource Manager API']),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
provider: 'gcp',
|
|
62
|
+
category: 'Internet of Things',
|
|
63
|
+
cloud_service_names: JSON.stringify(['Iot Core']),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
provider: 'gcp',
|
|
67
|
+
category: 'Developer Tools',
|
|
68
|
+
cloud_service_names: JSON.stringify(['Google Cloud App', 'Cloud Deployment Manager', 'Cloud Shell', 'Recommenders', 'Service Infrastructure']),
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
};
|