@electrolux-oss/plugin-infrawallet-backend 0.1.8 → 0.1.9
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 +245 -116
- package/dist/index.cjs.js.map +1 -1
- package/mock/mock_response.json +10654 -0
- package/package.json +3 -1
- package/seeds/default_category_mappings.js +9 -0
package/config.d.ts
CHANGED
|
@@ -41,6 +41,11 @@ export interface Config {
|
|
|
41
41
|
tags?: string[];
|
|
42
42
|
},
|
|
43
43
|
];
|
|
44
|
+
mock?: [
|
|
45
|
+
{
|
|
46
|
+
name: string;
|
|
47
|
+
},
|
|
48
|
+
];
|
|
44
49
|
};
|
|
45
50
|
metricProviders?: {
|
|
46
51
|
datadog?: [
|
|
@@ -68,6 +73,11 @@ export interface Config {
|
|
|
68
73
|
token: string;
|
|
69
74
|
},
|
|
70
75
|
];
|
|
76
|
+
mock?: [
|
|
77
|
+
{
|
|
78
|
+
name: string;
|
|
79
|
+
},
|
|
80
|
+
];
|
|
71
81
|
};
|
|
72
82
|
};
|
|
73
83
|
};
|
package/dist/index.cjs.js
CHANGED
|
@@ -13,16 +13,37 @@ var moment = require('moment');
|
|
|
13
13
|
var armCostmanagement = require('@azure/arm-costmanagement');
|
|
14
14
|
var coreRestPipeline = require('@azure/core-rest-pipeline');
|
|
15
15
|
var identity = require('@azure/identity');
|
|
16
|
-
var datadogApiClient = require('@datadog/datadog-api-client');
|
|
17
16
|
var bigquery = require('@google-cloud/bigquery');
|
|
17
|
+
var datadogApiClient = require('@datadog/datadog-api-client');
|
|
18
18
|
var fetch = require('node-fetch');
|
|
19
|
+
var fs = require('fs');
|
|
20
|
+
var upath = require('upath');
|
|
19
21
|
|
|
20
22
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
21
23
|
|
|
24
|
+
function _interopNamespaceCompat(e) {
|
|
25
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
26
|
+
var n = Object.create(null);
|
|
27
|
+
if (e) {
|
|
28
|
+
Object.keys(e).forEach(function (k) {
|
|
29
|
+
if (k !== 'default') {
|
|
30
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
31
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get: function () { return e[k]; }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
n.default = e;
|
|
39
|
+
return Object.freeze(n);
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
23
43
|
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
24
44
|
var moment__default = /*#__PURE__*/_interopDefaultCompat(moment);
|
|
25
45
|
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
|
|
46
|
+
var upath__namespace = /*#__PURE__*/_interopNamespaceCompat(upath);
|
|
26
47
|
|
|
27
48
|
async function getWallet(database, walletName) {
|
|
28
49
|
const client = await database.getClient();
|
|
@@ -54,29 +75,25 @@ async function deleteWalletMetricSetting(database, walletSetting) {
|
|
|
54
75
|
async function getCategoryMappings(database, provider) {
|
|
55
76
|
const result = {};
|
|
56
77
|
const client = await database.getClient();
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
63
|
-
} else {
|
|
64
|
-
mapping.cloud_service_names.forEach((service) => {
|
|
65
|
-
result[service] = mapping.category;
|
|
66
|
-
});
|
|
78
|
+
const defaultMappings = await client.where({ provider: provider.toLowerCase() }).select().from("category_mappings_default");
|
|
79
|
+
defaultMappings.forEach((mapping) => {
|
|
80
|
+
let services = mapping.cloud_service_names;
|
|
81
|
+
if (typeof services === "string") {
|
|
82
|
+
services = JSON.parse(services);
|
|
67
83
|
}
|
|
84
|
+
services.forEach((service) => {
|
|
85
|
+
result[service] = mapping.category;
|
|
86
|
+
});
|
|
68
87
|
});
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
-
} else {
|
|
76
|
-
mapping.cloud_service_names.forEach((service) => {
|
|
77
|
-
result[service] = mapping.category;
|
|
78
|
-
});
|
|
88
|
+
const overrideMappings = await client.where({ provider }).select().from("category_mappings_override");
|
|
89
|
+
overrideMappings.forEach((mapping) => {
|
|
90
|
+
let services = mapping.cloud_service_names;
|
|
91
|
+
if (typeof services === "string") {
|
|
92
|
+
services = JSON.parse(services);
|
|
79
93
|
}
|
|
94
|
+
services.forEach((service) => {
|
|
95
|
+
result[service] = mapping.category;
|
|
96
|
+
});
|
|
80
97
|
});
|
|
81
98
|
return result;
|
|
82
99
|
}
|
|
@@ -84,6 +101,14 @@ function getCategoryByServiceName(serviceName, categoryMappings) {
|
|
|
84
101
|
if (serviceName in categoryMappings) {
|
|
85
102
|
return categoryMappings[serviceName];
|
|
86
103
|
}
|
|
104
|
+
for (const service in categoryMappings) {
|
|
105
|
+
if (Object.hasOwn(categoryMappings, service)) {
|
|
106
|
+
const regex = new RegExp(service);
|
|
107
|
+
if (regex.test(serviceName)) {
|
|
108
|
+
return categoryMappings[service];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
87
112
|
return "Uncategorized";
|
|
88
113
|
}
|
|
89
114
|
async function getReportsFromCache(cache, provider, configKey, query) {
|
|
@@ -109,7 +134,8 @@ async function getMetricsFromCache(cache, provider, configKey, query) {
|
|
|
109
134
|
query.startTime,
|
|
110
135
|
query.endTime
|
|
111
136
|
].join("_");
|
|
112
|
-
const
|
|
137
|
+
const crypto = require("crypto");
|
|
138
|
+
const cachedMetrics = await cache.get(crypto.createHash("md5").update(cacheKey).digest("hex"));
|
|
113
139
|
return cachedMetrics;
|
|
114
140
|
}
|
|
115
141
|
async function setReportsToCache(cache, reports, provider, configKey, query, ttl) {
|
|
@@ -507,6 +533,98 @@ class AzureClient extends InfraWalletClient {
|
|
|
507
533
|
}
|
|
508
534
|
}
|
|
509
535
|
|
|
536
|
+
class GCPClient extends InfraWalletClient {
|
|
537
|
+
static create(config, database, cache, logger) {
|
|
538
|
+
return new GCPClient("GCP", config, database, cache, logger);
|
|
539
|
+
}
|
|
540
|
+
convertServiceName(serviceName) {
|
|
541
|
+
let convertedName = serviceName;
|
|
542
|
+
const prefixes = ["Google Cloud"];
|
|
543
|
+
for (const prefix of prefixes) {
|
|
544
|
+
if (serviceName.startsWith(prefix)) {
|
|
545
|
+
convertedName = serviceName.slice(prefix.length).trim();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return `${this.providerName}/${convertedName}`;
|
|
549
|
+
}
|
|
550
|
+
async initCloudClient(subAccountConfig) {
|
|
551
|
+
const keyFilePath = subAccountConfig.getString("keyFilePath");
|
|
552
|
+
const projectId = subAccountConfig.getString("projectId");
|
|
553
|
+
const options = {
|
|
554
|
+
keyFilename: keyFilePath,
|
|
555
|
+
projectId
|
|
556
|
+
};
|
|
557
|
+
const bigqueryClient = new bigquery.BigQuery(options);
|
|
558
|
+
return bigqueryClient;
|
|
559
|
+
}
|
|
560
|
+
async fetchCostsFromCloud(subAccountConfig, client, query) {
|
|
561
|
+
const projectId = subAccountConfig.getString("projectId");
|
|
562
|
+
const datasetId = subAccountConfig.getString("datasetId");
|
|
563
|
+
const tableId = subAccountConfig.getString("tableId");
|
|
564
|
+
try {
|
|
565
|
+
const periodFormat = query.granularity.toUpperCase() === "MONTHLY" ? "%Y-%m" : "%Y-%m-%d";
|
|
566
|
+
const sql = `
|
|
567
|
+
SELECT
|
|
568
|
+
project.name AS project,
|
|
569
|
+
service.description AS service,
|
|
570
|
+
FORMAT_TIMESTAMP('${periodFormat}', usage_start_time) AS period,
|
|
571
|
+
(SUM(CAST(cost AS NUMERIC)) + SUM(IFNULL((SELECT SUM(CAST(c.amount AS NUMERIC)) FROM UNNEST(credits) AS c), 0))) AS total_cost
|
|
572
|
+
FROM
|
|
573
|
+
\`${projectId}.${datasetId}.${tableId}\`
|
|
574
|
+
WHERE
|
|
575
|
+
project.name IS NOT NULL
|
|
576
|
+
AND cost > 0
|
|
577
|
+
AND usage_start_time >= TIMESTAMP_MILLIS(${query.startTime})
|
|
578
|
+
AND usage_start_time <= TIMESTAMP_MILLIS(${query.endTime})
|
|
579
|
+
GROUP BY
|
|
580
|
+
project, service, period
|
|
581
|
+
ORDER BY
|
|
582
|
+
project, period, total_cost DESC`;
|
|
583
|
+
const [job] = await client.createQueryJob({
|
|
584
|
+
query: sql
|
|
585
|
+
});
|
|
586
|
+
const [rows] = await job.getQueryResults();
|
|
587
|
+
return rows;
|
|
588
|
+
} catch (err) {
|
|
589
|
+
throw new Error(err.message);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async transformCostsData(subAccountConfig, _query, costResponse, categoryMappings) {
|
|
593
|
+
const accountName = subAccountConfig.getString("name");
|
|
594
|
+
const tags = subAccountConfig.getOptionalStringArray("tags");
|
|
595
|
+
const tagKeyValues = {};
|
|
596
|
+
tags == null ? void 0 : tags.forEach((tag) => {
|
|
597
|
+
const [k, v] = tag.split(":");
|
|
598
|
+
tagKeyValues[k.trim()] = v.trim();
|
|
599
|
+
});
|
|
600
|
+
const transformedData = lodash.reduce(
|
|
601
|
+
costResponse,
|
|
602
|
+
(acc, row) => {
|
|
603
|
+
const period = row.period;
|
|
604
|
+
const keyName = `${accountName}_${row.project}_${row.service}`;
|
|
605
|
+
if (!acc[keyName]) {
|
|
606
|
+
acc[keyName] = {
|
|
607
|
+
id: keyName,
|
|
608
|
+
name: `${this.providerName}/${accountName}`,
|
|
609
|
+
service: this.convertServiceName(row.service),
|
|
610
|
+
category: getCategoryByServiceName(row.service, categoryMappings),
|
|
611
|
+
provider: this.providerName,
|
|
612
|
+
reports: {},
|
|
613
|
+
...{ project: row.project },
|
|
614
|
+
// TODO: how should we handle the project field? for now, we add project name as a field in the report
|
|
615
|
+
...tagKeyValues
|
|
616
|
+
// note that if there is a tag `project:foo` in config, it overrides the project field set above
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
acc[keyName].reports[period] = parseFloat(row.total_cost);
|
|
620
|
+
return acc;
|
|
621
|
+
},
|
|
622
|
+
{}
|
|
623
|
+
);
|
|
624
|
+
return Object.values(transformedData);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
510
628
|
class MetricProvider {
|
|
511
629
|
constructor(providerName, config, database, cache, logger) {
|
|
512
630
|
this.providerName = providerName;
|
|
@@ -637,98 +755,6 @@ class DatadogProvider extends MetricProvider {
|
|
|
637
755
|
}
|
|
638
756
|
}
|
|
639
757
|
|
|
640
|
-
class GCPClient extends InfraWalletClient {
|
|
641
|
-
static create(config, database, cache, logger) {
|
|
642
|
-
return new GCPClient("GCP", config, database, cache, logger);
|
|
643
|
-
}
|
|
644
|
-
convertServiceName(serviceName) {
|
|
645
|
-
let convertedName = serviceName;
|
|
646
|
-
const prefixes = ["Google Cloud"];
|
|
647
|
-
for (const prefix of prefixes) {
|
|
648
|
-
if (serviceName.startsWith(prefix)) {
|
|
649
|
-
convertedName = serviceName.slice(prefix.length).trim();
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return `${this.providerName}/${convertedName}`;
|
|
653
|
-
}
|
|
654
|
-
async initCloudClient(subAccountConfig) {
|
|
655
|
-
const keyFilePath = subAccountConfig.getString("keyFilePath");
|
|
656
|
-
const projectId = subAccountConfig.getString("projectId");
|
|
657
|
-
const options = {
|
|
658
|
-
keyFilename: keyFilePath,
|
|
659
|
-
projectId
|
|
660
|
-
};
|
|
661
|
-
const bigqueryClient = new bigquery.BigQuery(options);
|
|
662
|
-
return bigqueryClient;
|
|
663
|
-
}
|
|
664
|
-
async fetchCostsFromCloud(subAccountConfig, client, query) {
|
|
665
|
-
const projectId = subAccountConfig.getString("projectId");
|
|
666
|
-
const datasetId = subAccountConfig.getString("datasetId");
|
|
667
|
-
const tableId = subAccountConfig.getString("tableId");
|
|
668
|
-
try {
|
|
669
|
-
const periodFormat = query.granularity.toUpperCase() === "MONTHLY" ? "%Y-%m" : "%Y-%m-%d";
|
|
670
|
-
const sql = `
|
|
671
|
-
SELECT
|
|
672
|
-
project.name AS project,
|
|
673
|
-
service.description AS service,
|
|
674
|
-
FORMAT_TIMESTAMP('${periodFormat}', usage_start_time) AS period,
|
|
675
|
-
(SUM(CAST(cost AS NUMERIC)) + SUM(IFNULL((SELECT SUM(CAST(c.amount AS NUMERIC)) FROM UNNEST(credits) AS c), 0))) AS total_cost
|
|
676
|
-
FROM
|
|
677
|
-
\`${projectId}.${datasetId}.${tableId}\`
|
|
678
|
-
WHERE
|
|
679
|
-
project.name IS NOT NULL
|
|
680
|
-
AND cost > 0
|
|
681
|
-
AND usage_start_time >= TIMESTAMP_MILLIS(${query.startTime})
|
|
682
|
-
AND usage_start_time <= TIMESTAMP_MILLIS(${query.endTime})
|
|
683
|
-
GROUP BY
|
|
684
|
-
project, service, period
|
|
685
|
-
ORDER BY
|
|
686
|
-
project, period, total_cost DESC`;
|
|
687
|
-
const [job] = await client.createQueryJob({
|
|
688
|
-
query: sql
|
|
689
|
-
});
|
|
690
|
-
const [rows] = await job.getQueryResults();
|
|
691
|
-
return rows;
|
|
692
|
-
} catch (err) {
|
|
693
|
-
throw new Error(err.message);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
async transformCostsData(subAccountConfig, _query, costResponse, categoryMappings) {
|
|
697
|
-
const accountName = subAccountConfig.getString("name");
|
|
698
|
-
const tags = subAccountConfig.getOptionalStringArray("tags");
|
|
699
|
-
const tagKeyValues = {};
|
|
700
|
-
tags == null ? void 0 : tags.forEach((tag) => {
|
|
701
|
-
const [k, v] = tag.split(":");
|
|
702
|
-
tagKeyValues[k.trim()] = v.trim();
|
|
703
|
-
});
|
|
704
|
-
const transformedData = lodash.reduce(
|
|
705
|
-
costResponse,
|
|
706
|
-
(acc, row) => {
|
|
707
|
-
const period = row.period;
|
|
708
|
-
const keyName = `${accountName}_${row.project}_${row.service}`;
|
|
709
|
-
if (!acc[keyName]) {
|
|
710
|
-
acc[keyName] = {
|
|
711
|
-
id: keyName,
|
|
712
|
-
name: `${this.providerName}/${accountName}`,
|
|
713
|
-
service: this.convertServiceName(row.service),
|
|
714
|
-
category: getCategoryByServiceName(row.service, categoryMappings),
|
|
715
|
-
provider: this.providerName,
|
|
716
|
-
reports: {},
|
|
717
|
-
...{ project: row.project },
|
|
718
|
-
// TODO: how should we handle the project field? for now, we add project name as a field in the report
|
|
719
|
-
...tagKeyValues
|
|
720
|
-
// note that if there is a tag `project:foo` in config, it overrides the project field set above
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
acc[keyName].reports[period] = parseFloat(row.total_cost);
|
|
724
|
-
return acc;
|
|
725
|
-
},
|
|
726
|
-
{}
|
|
727
|
-
);
|
|
728
|
-
return Object.values(transformedData);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
758
|
class GrafanaCloudProvider extends MetricProvider {
|
|
733
759
|
static create(config, database, cache, logger) {
|
|
734
760
|
return new GrafanaCloudProvider("GrafanaCloud", config, database, cache, logger);
|
|
@@ -787,14 +813,117 @@ class GrafanaCloudProvider extends MetricProvider {
|
|
|
787
813
|
}
|
|
788
814
|
}
|
|
789
815
|
|
|
816
|
+
class MockProvider extends MetricProvider {
|
|
817
|
+
static create(config, database, cache, logger) {
|
|
818
|
+
return new MockProvider("Mock", config, database, cache, logger);
|
|
819
|
+
}
|
|
820
|
+
async initProviderClient(_config) {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
async fetchMetrics(_metricProviderConfig, _client, _query) {
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
async transformMetricData(_metricProviderConfig, query, _metricResponse) {
|
|
827
|
+
var _a, _b;
|
|
828
|
+
const transformedData = [];
|
|
829
|
+
const metricName = query.name;
|
|
830
|
+
let mockSettings = {};
|
|
831
|
+
try {
|
|
832
|
+
mockSettings = JSON.parse(query.query);
|
|
833
|
+
} catch (e) {
|
|
834
|
+
}
|
|
835
|
+
const minValue = (_a = mockSettings.min) != null ? _a : 0;
|
|
836
|
+
const maxValue = (_b = mockSettings.max) != null ? _b : 1e3;
|
|
837
|
+
const metric = {
|
|
838
|
+
id: metricName,
|
|
839
|
+
provider: this.providerName,
|
|
840
|
+
name: metricName,
|
|
841
|
+
reports: {}
|
|
842
|
+
};
|
|
843
|
+
let cursor = moment__default.default(parseInt(query.startTime, 10));
|
|
844
|
+
while (cursor <= moment__default.default(parseInt(query.endTime, 10))) {
|
|
845
|
+
const period = cursor.format(query.granularity === "daily" ? "YYYY-MM-DD" : "YYYY-MM");
|
|
846
|
+
metric.reports[period] = Math.floor(Math.random() * (maxValue - minValue) + minValue);
|
|
847
|
+
cursor = cursor.add(1, query.granularity === "daily" ? "days" : "months");
|
|
848
|
+
}
|
|
849
|
+
transformedData.push(metric);
|
|
850
|
+
return transformedData;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
class MockClient extends InfraWalletClient {
|
|
855
|
+
static create(config, database, cache, logger) {
|
|
856
|
+
return new MockClient("mock", config, database, cache, logger);
|
|
857
|
+
}
|
|
858
|
+
async initCloudClient(config) {
|
|
859
|
+
this.logger.debug(`MockClient.initCloudClient called with config: ${JSON.stringify(config)}`);
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
async fetchCostsFromCloud(_subAccountConfig, _client, _query) {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
async transformCostsData(_subAccountConfig, query, _costResponse, _categoryMappings) {
|
|
866
|
+
try {
|
|
867
|
+
const startD = moment__default.default.unix(Number(query.startTime) / 1e3);
|
|
868
|
+
let endD = moment__default.default.unix(Number(query.endTime) / 1e3);
|
|
869
|
+
const mockDir = backendPluginApi.resolvePackagePath("@electrolux-oss/plugin-infrawallet-backend", "mock");
|
|
870
|
+
const mockFilePath = upath__namespace.join(mockDir, "mock_response.json");
|
|
871
|
+
const data = await fs.promises.readFile(mockFilePath, "utf8");
|
|
872
|
+
const jsonData = JSON.parse(data);
|
|
873
|
+
const currentDate = moment__default.default();
|
|
874
|
+
if (endD.isAfter(currentDate)) {
|
|
875
|
+
this.logger.warn("End Date is in the future, adjusting to current date.");
|
|
876
|
+
endD = currentDate.clone();
|
|
877
|
+
endD.add(1, "day");
|
|
878
|
+
}
|
|
879
|
+
const processedData = await Promise.all(
|
|
880
|
+
jsonData.map(async (item) => {
|
|
881
|
+
item.reports = {};
|
|
882
|
+
const StartDate = moment__default.default(startD);
|
|
883
|
+
let step;
|
|
884
|
+
let dateFormat = "YYYY-MM";
|
|
885
|
+
if (query.granularity.toLowerCase() === "monthly") {
|
|
886
|
+
step = "months";
|
|
887
|
+
dateFormat = "YYYY-MM";
|
|
888
|
+
} else if (query.granularity.toLowerCase() === "daily") {
|
|
889
|
+
step = "days";
|
|
890
|
+
dateFormat = "YYYY-MM-DD";
|
|
891
|
+
}
|
|
892
|
+
while (StartDate.isBefore(endD)) {
|
|
893
|
+
const dateString = StartDate.format(dateFormat);
|
|
894
|
+
if (query.granularity.toLowerCase() === "monthly") {
|
|
895
|
+
item.reports[dateString] = this.getRandomValue(0.4 * 30, 33.3 * 30);
|
|
896
|
+
} else {
|
|
897
|
+
item.reports[dateString] = this.getRandomValue(0.4, 33.3);
|
|
898
|
+
}
|
|
899
|
+
StartDate.add(1, step);
|
|
900
|
+
}
|
|
901
|
+
return item;
|
|
902
|
+
})
|
|
903
|
+
);
|
|
904
|
+
return processedData;
|
|
905
|
+
} catch (err) {
|
|
906
|
+
this.logger.error("Error while reading a file", err);
|
|
907
|
+
throw err;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
getRandomValue(min, max) {
|
|
911
|
+
const random = Math.random();
|
|
912
|
+
const amplifiedRandom = Math.pow(random, 3);
|
|
913
|
+
return amplifiedRandom * (max - min) + min;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
790
917
|
const COST_CLIENT_MAPPINGS = {
|
|
791
918
|
aws: AwsClient,
|
|
792
919
|
azure: AzureClient,
|
|
793
|
-
gcp: GCPClient
|
|
920
|
+
gcp: GCPClient,
|
|
921
|
+
mock: MockClient
|
|
794
922
|
};
|
|
795
923
|
const METRIC_PROVIDER_MAPPINGS = {
|
|
796
924
|
datadog: DatadogProvider,
|
|
797
|
-
grafanacloud: GrafanaCloudProvider
|
|
925
|
+
grafanacloud: GrafanaCloudProvider,
|
|
926
|
+
mock: MockProvider
|
|
798
927
|
};
|
|
799
928
|
|
|
800
929
|
async function setUpDatabase(database) {
|