@electrolux-oss/plugin-infrawallet-backend 0.1.7 → 0.1.8
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
CHANGED
|
@@ -42,6 +42,33 @@ export interface Config {
|
|
|
42
42
|
},
|
|
43
43
|
];
|
|
44
44
|
};
|
|
45
|
+
metricProviders?: {
|
|
46
|
+
datadog?: [
|
|
47
|
+
{
|
|
48
|
+
name: string;
|
|
49
|
+
/**
|
|
50
|
+
* @visibility secret
|
|
51
|
+
*/
|
|
52
|
+
apiKey: string;
|
|
53
|
+
/**
|
|
54
|
+
* @visibility secret
|
|
55
|
+
*/
|
|
56
|
+
applicationKey: string;
|
|
57
|
+
ddSite: string;
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
grafanaCloud?: [
|
|
61
|
+
{
|
|
62
|
+
name: string;
|
|
63
|
+
url: string;
|
|
64
|
+
datasourceUid: string;
|
|
65
|
+
/**
|
|
66
|
+
* @visibility secret
|
|
67
|
+
*/
|
|
68
|
+
token: string;
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
};
|
|
45
72
|
};
|
|
46
73
|
};
|
|
47
74
|
}
|
package/dist/index.cjs.js
CHANGED
|
@@ -13,13 +13,43 @@ 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');
|
|
16
17
|
var bigquery = require('@google-cloud/bigquery');
|
|
18
|
+
var fetch = require('node-fetch');
|
|
17
19
|
|
|
18
20
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
19
21
|
|
|
20
22
|
var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
21
23
|
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
22
24
|
var moment__default = /*#__PURE__*/_interopDefaultCompat(moment);
|
|
25
|
+
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
|
|
26
|
+
|
|
27
|
+
async function getWallet(database, walletName) {
|
|
28
|
+
const client = await database.getClient();
|
|
29
|
+
const result = await client("wallets").where("name", walletName).first();
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
async function getWalletMetricSettings(database, walletName) {
|
|
33
|
+
const client = await database.getClient();
|
|
34
|
+
const metricSettings = await client.select("business_metrics.*").from("business_metrics").where("wallets.name", walletName).join("wallets", "business_metrics.wallet_id", "=", "wallets.id");
|
|
35
|
+
return metricSettings;
|
|
36
|
+
}
|
|
37
|
+
async function updateOrInsertWalletMetricSetting(database, walletSetting) {
|
|
38
|
+
const client = await database.getClient();
|
|
39
|
+
const result = await client("business_metrics").insert(walletSetting).onConflict("id").merge();
|
|
40
|
+
if (result[0] > 0) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
async function deleteWalletMetricSetting(database, walletSetting) {
|
|
46
|
+
const client = await database.getClient();
|
|
47
|
+
const result = await client("business_metrics").where("id", walletSetting.id).del();
|
|
48
|
+
if (result > 0) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
23
53
|
|
|
24
54
|
async function getCategoryMappings(database, provider) {
|
|
25
55
|
const result = {};
|
|
@@ -69,6 +99,19 @@ async function getReportsFromCache(cache, provider, configKey, query) {
|
|
|
69
99
|
const cachedCosts = await cache.get(cacheKey);
|
|
70
100
|
return cachedCosts;
|
|
71
101
|
}
|
|
102
|
+
async function getMetricsFromCache(cache, provider, configKey, query) {
|
|
103
|
+
const cacheKey = [
|
|
104
|
+
provider,
|
|
105
|
+
configKey,
|
|
106
|
+
query.name,
|
|
107
|
+
query.query,
|
|
108
|
+
query.granularity,
|
|
109
|
+
query.startTime,
|
|
110
|
+
query.endTime
|
|
111
|
+
].join("_");
|
|
112
|
+
const cachedMetrics = await cache.get(cacheKey);
|
|
113
|
+
return cachedMetrics;
|
|
114
|
+
}
|
|
72
115
|
async function setReportsToCache(cache, reports, provider, configKey, query, ttl) {
|
|
73
116
|
const cacheKey = [
|
|
74
117
|
provider,
|
|
@@ -83,6 +126,21 @@ async function setReportsToCache(cache, reports, provider, configKey, query, ttl
|
|
|
83
126
|
ttl: ttl
|
|
84
127
|
});
|
|
85
128
|
}
|
|
129
|
+
async function setMetricsToCache(cache, metrics, provider, configKey, query, ttl) {
|
|
130
|
+
const cacheKey = [
|
|
131
|
+
provider,
|
|
132
|
+
configKey,
|
|
133
|
+
query.name,
|
|
134
|
+
query.query,
|
|
135
|
+
query.granularity,
|
|
136
|
+
query.startTime,
|
|
137
|
+
query.endTime
|
|
138
|
+
].join("_");
|
|
139
|
+
const crypto = require("crypto");
|
|
140
|
+
await cache.set(crypto.createHash("md5").update(cacheKey).digest("hex"), metrics, {
|
|
141
|
+
ttl: ttl
|
|
142
|
+
});
|
|
143
|
+
}
|
|
86
144
|
|
|
87
145
|
class InfraWalletClient {
|
|
88
146
|
constructor(providerName, config, database, cache, logger) {
|
|
@@ -449,6 +507,136 @@ class AzureClient extends InfraWalletClient {
|
|
|
449
507
|
}
|
|
450
508
|
}
|
|
451
509
|
|
|
510
|
+
class MetricProvider {
|
|
511
|
+
constructor(providerName, config, database, cache, logger) {
|
|
512
|
+
this.providerName = providerName;
|
|
513
|
+
this.config = config;
|
|
514
|
+
this.database = database;
|
|
515
|
+
this.cache = cache;
|
|
516
|
+
this.logger = logger;
|
|
517
|
+
}
|
|
518
|
+
async getMetrics(query) {
|
|
519
|
+
const conf = this.config.getOptionalConfigArray(
|
|
520
|
+
`backend.infraWallet.metricProviders.${this.providerName.toLowerCase()}`
|
|
521
|
+
);
|
|
522
|
+
if (!conf) {
|
|
523
|
+
return { metrics: [], errors: [] };
|
|
524
|
+
}
|
|
525
|
+
const promises = [];
|
|
526
|
+
const results = [];
|
|
527
|
+
const errors = [];
|
|
528
|
+
for (const c of conf) {
|
|
529
|
+
const configName = c.getString("name");
|
|
530
|
+
const client = await this.initProviderClient(c);
|
|
531
|
+
const dbClient = await this.database.getClient();
|
|
532
|
+
const metricSettings = await dbClient.where({
|
|
533
|
+
"wallets.name": query.walletName,
|
|
534
|
+
"business_metrics.metric_provider": this.providerName.toLowerCase(),
|
|
535
|
+
"business_metrics.config_name": configName
|
|
536
|
+
}).select("business_metrics.*").from("business_metrics").join("wallets", "business_metrics.wallet_id", "=", "wallets.id");
|
|
537
|
+
for (const metric of metricSettings || []) {
|
|
538
|
+
const promise = (async () => {
|
|
539
|
+
try {
|
|
540
|
+
const fullQuery = {
|
|
541
|
+
name: metric.metric_name,
|
|
542
|
+
query: metric.query,
|
|
543
|
+
...query
|
|
544
|
+
};
|
|
545
|
+
const cachedMetrics = await getMetricsFromCache(this.cache, this.providerName, configName, fullQuery);
|
|
546
|
+
if (cachedMetrics) {
|
|
547
|
+
this.logger.debug(`${this.providerName}/${configName}/${fullQuery.name} metrics from cache`);
|
|
548
|
+
cachedMetrics.map((m) => {
|
|
549
|
+
results.push(m);
|
|
550
|
+
});
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const metricResponse = await this.fetchMetrics(c, client, fullQuery);
|
|
554
|
+
const transformedMetrics = await this.transformMetricData(c, fullQuery, metricResponse);
|
|
555
|
+
await setMetricsToCache(
|
|
556
|
+
this.cache,
|
|
557
|
+
transformedMetrics,
|
|
558
|
+
this.providerName,
|
|
559
|
+
configName,
|
|
560
|
+
fullQuery,
|
|
561
|
+
60 * 60 * 2 * 1e3
|
|
562
|
+
);
|
|
563
|
+
transformedMetrics.map((value) => {
|
|
564
|
+
results.push(value);
|
|
565
|
+
});
|
|
566
|
+
} catch (e) {
|
|
567
|
+
this.logger.error(e);
|
|
568
|
+
errors.push({
|
|
569
|
+
provider: this.providerName,
|
|
570
|
+
name: `${this.providerName}/${configName}/${metric.getString("metricName")}`,
|
|
571
|
+
error: e.message
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
})();
|
|
575
|
+
promises.push(promise);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
await Promise.all(promises);
|
|
579
|
+
return {
|
|
580
|
+
metrics: results,
|
|
581
|
+
errors
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
class DatadogProvider extends MetricProvider {
|
|
587
|
+
static create(config, database, cache, logger) {
|
|
588
|
+
return new DatadogProvider("Datadog", config, database, cache, logger);
|
|
589
|
+
}
|
|
590
|
+
async initProviderClient(config) {
|
|
591
|
+
const apiKey = config.getString("apiKey");
|
|
592
|
+
const applicationKey = config.getString("applicationKey");
|
|
593
|
+
const ddSite = config.getString("ddSite");
|
|
594
|
+
const configuration = datadogApiClient.client.createConfiguration({
|
|
595
|
+
baseServer: new datadogApiClient.client.BaseServerConfiguration(ddSite, {}),
|
|
596
|
+
authMethods: {
|
|
597
|
+
apiKeyAuth: apiKey,
|
|
598
|
+
appKeyAuth: applicationKey
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
const client = new datadogApiClient.v1.MetricsApi(configuration);
|
|
602
|
+
return client;
|
|
603
|
+
}
|
|
604
|
+
async fetchMetrics(_metricProviderConfig, client, query) {
|
|
605
|
+
var _a;
|
|
606
|
+
const params = {
|
|
607
|
+
from: parseInt(query.startTime, 10) / 1e3,
|
|
608
|
+
to: parseInt(query.endTime, 10) / 1e3,
|
|
609
|
+
query: (_a = query.query) == null ? void 0 : _a.replaceAll("IW_INTERVAL", query.granularity === "daily" ? "86400" : "2592000")
|
|
610
|
+
};
|
|
611
|
+
return client.queryMetrics(params).then((data) => {
|
|
612
|
+
if (data.status === "ok") {
|
|
613
|
+
return data;
|
|
614
|
+
}
|
|
615
|
+
throw new Error(data.error);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
async transformMetricData(_metricProviderConfig, query, metricResponse) {
|
|
619
|
+
const transformedData = [];
|
|
620
|
+
for (const series of metricResponse.series) {
|
|
621
|
+
const metricName = query.name;
|
|
622
|
+
const tagSet = series.tagSet;
|
|
623
|
+
const metric = {
|
|
624
|
+
id: `${metricName} ${tagSet.length === 0 ? "" : tagSet}`,
|
|
625
|
+
provider: this.providerName,
|
|
626
|
+
name: metricName,
|
|
627
|
+
reports: {}
|
|
628
|
+
};
|
|
629
|
+
for (const point of series.pointlist) {
|
|
630
|
+
const period = moment__default.default(point[0]).format(query.granularity === "daily" ? "YYYY-MM-DD" : "YYYY-MM");
|
|
631
|
+
const value = point[1];
|
|
632
|
+
metric.reports[period] = value;
|
|
633
|
+
}
|
|
634
|
+
transformedData.push(metric);
|
|
635
|
+
}
|
|
636
|
+
return transformedData;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
452
640
|
class GCPClient extends InfraWalletClient {
|
|
453
641
|
static create(config, database, cache, logger) {
|
|
454
642
|
return new GCPClient("GCP", config, database, cache, logger);
|
|
@@ -541,11 +729,73 @@ class GCPClient extends InfraWalletClient {
|
|
|
541
729
|
}
|
|
542
730
|
}
|
|
543
731
|
|
|
544
|
-
|
|
732
|
+
class GrafanaCloudProvider extends MetricProvider {
|
|
733
|
+
static create(config, database, cache, logger) {
|
|
734
|
+
return new GrafanaCloudProvider("GrafanaCloud", config, database, cache, logger);
|
|
735
|
+
}
|
|
736
|
+
async initProviderClient(_config) {
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
async fetchMetrics(metricProviderConfig, _client, query) {
|
|
740
|
+
var _a;
|
|
741
|
+
const url = metricProviderConfig.getString("url");
|
|
742
|
+
const datasourceUid = metricProviderConfig.getString("datasourceUid");
|
|
743
|
+
const token = metricProviderConfig.getString("token");
|
|
744
|
+
const headers = {
|
|
745
|
+
"Content-Type": "application/json",
|
|
746
|
+
Authorization: `Bearer ${token}`
|
|
747
|
+
};
|
|
748
|
+
const payload = {
|
|
749
|
+
queries: [
|
|
750
|
+
{
|
|
751
|
+
datasource: {
|
|
752
|
+
uid: datasourceUid
|
|
753
|
+
},
|
|
754
|
+
expr: (_a = query.query) == null ? void 0 : _a.replaceAll("IW_INTERVAL", query.granularity === "daily" ? "1d" : "30d"),
|
|
755
|
+
refId: "A"
|
|
756
|
+
}
|
|
757
|
+
],
|
|
758
|
+
from: query.startTime,
|
|
759
|
+
to: query.endTime
|
|
760
|
+
};
|
|
761
|
+
const response = await fetch__default.default(`${url}/api/ds/query`, {
|
|
762
|
+
method: "post",
|
|
763
|
+
body: JSON.stringify(payload),
|
|
764
|
+
headers
|
|
765
|
+
});
|
|
766
|
+
const data = await response.json();
|
|
767
|
+
return data;
|
|
768
|
+
}
|
|
769
|
+
async transformMetricData(_metricProviderConfig, query, metricResponse) {
|
|
770
|
+
const transformedData = [];
|
|
771
|
+
const metricName = query.name;
|
|
772
|
+
const metric = {
|
|
773
|
+
id: metricName,
|
|
774
|
+
provider: this.providerName,
|
|
775
|
+
name: metricName,
|
|
776
|
+
reports: {}
|
|
777
|
+
};
|
|
778
|
+
const periods = metricResponse.results.A.frames[0].data.values[0];
|
|
779
|
+
const values = metricResponse.results.A.frames[0].data.values[1];
|
|
780
|
+
for (let i = 0; i < periods.length; i++) {
|
|
781
|
+
const period = moment__default.default(periods[i]).format(query.granularity === "daily" ? "YYYY-MM-DD" : "YYYY-MM");
|
|
782
|
+
const value = values[i];
|
|
783
|
+
metric.reports[period] = value;
|
|
784
|
+
}
|
|
785
|
+
transformedData.push(metric);
|
|
786
|
+
return transformedData;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const COST_CLIENT_MAPPINGS = {
|
|
545
791
|
aws: AwsClient,
|
|
546
792
|
azure: AzureClient,
|
|
547
793
|
gcp: GCPClient
|
|
548
794
|
};
|
|
795
|
+
const METRIC_PROVIDER_MAPPINGS = {
|
|
796
|
+
datadog: DatadogProvider,
|
|
797
|
+
grafanacloud: GrafanaCloudProvider
|
|
798
|
+
};
|
|
549
799
|
|
|
550
800
|
async function setUpDatabase(database) {
|
|
551
801
|
var _a;
|
|
@@ -579,8 +829,8 @@ async function createRouter(options) {
|
|
|
579
829
|
const errors = [];
|
|
580
830
|
const conf = config.getConfig("backend.infraWallet.integrations");
|
|
581
831
|
conf.keys().forEach((provider) => {
|
|
582
|
-
if (provider in
|
|
583
|
-
const client =
|
|
832
|
+
if (provider in COST_CLIENT_MAPPINGS) {
|
|
833
|
+
const client = COST_CLIENT_MAPPINGS[provider].create(config, database, cache, logger);
|
|
584
834
|
const fetchCloudCosts = (async () => {
|
|
585
835
|
try {
|
|
586
836
|
const clientResponse = await client.getCostReports({
|
|
@@ -615,6 +865,98 @@ async function createRouter(options) {
|
|
|
615
865
|
response.json({ data: results, errors, status: 200 });
|
|
616
866
|
}
|
|
617
867
|
});
|
|
868
|
+
router.get("/:walletName/metrics", async (request, response) => {
|
|
869
|
+
const walletName = request.params.walletName;
|
|
870
|
+
const granularity = request.query.granularity;
|
|
871
|
+
const startTime = request.query.startTime;
|
|
872
|
+
const endTime = request.query.endTime;
|
|
873
|
+
const promises = [];
|
|
874
|
+
const results = [];
|
|
875
|
+
const errors = [];
|
|
876
|
+
const conf = config.getConfig("backend.infraWallet.metricProviders");
|
|
877
|
+
conf.keys().forEach((provider) => {
|
|
878
|
+
if (provider in METRIC_PROVIDER_MAPPINGS) {
|
|
879
|
+
const client = METRIC_PROVIDER_MAPPINGS[provider].create(config, database, cache, logger);
|
|
880
|
+
const fetchMetrics = (async () => {
|
|
881
|
+
try {
|
|
882
|
+
const metricResponse = await client.getMetrics({
|
|
883
|
+
walletName,
|
|
884
|
+
granularity,
|
|
885
|
+
startTime,
|
|
886
|
+
endTime
|
|
887
|
+
});
|
|
888
|
+
metricResponse.errors.forEach((e) => {
|
|
889
|
+
errors.push(e);
|
|
890
|
+
});
|
|
891
|
+
metricResponse.metrics.forEach((metric) => {
|
|
892
|
+
results.push(metric);
|
|
893
|
+
});
|
|
894
|
+
} catch (e) {
|
|
895
|
+
logger.error(e);
|
|
896
|
+
errors.push({
|
|
897
|
+
provider: client.constructor.name,
|
|
898
|
+
name: client.constructor.name,
|
|
899
|
+
error: e.message
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
})();
|
|
903
|
+
promises.push(fetchMetrics);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
await Promise.all(promises);
|
|
907
|
+
if (errors.length > 0) {
|
|
908
|
+
response.status(207).json({ data: results, errors, status: 207 });
|
|
909
|
+
} else {
|
|
910
|
+
response.json({ data: results, errors, status: 200 });
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
router.get("/:walletName", async (request, response) => {
|
|
914
|
+
const walletName = request.params.walletName;
|
|
915
|
+
const wallet = await getWallet(database, walletName);
|
|
916
|
+
if (wallet === void 0) {
|
|
917
|
+
response.status(404).json({ error: "Wallet not found", status: 404 });
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
response.json({ data: wallet, status: 200 });
|
|
921
|
+
});
|
|
922
|
+
router.get("/:walletName/metrics_setting", async (request, response) => {
|
|
923
|
+
const walletName = request.params.walletName;
|
|
924
|
+
const metricSettings = await getWalletMetricSettings(database, walletName);
|
|
925
|
+
response.json({ data: metricSettings, status: 200 });
|
|
926
|
+
});
|
|
927
|
+
router.get("/metric/metric_configs", async (_request, response) => {
|
|
928
|
+
const conf = config.getConfig("backend.infraWallet.metricProviders");
|
|
929
|
+
const configNames = [];
|
|
930
|
+
conf.keys().forEach((provider) => {
|
|
931
|
+
const configs = conf.getOptionalConfigArray(provider);
|
|
932
|
+
if (configs) {
|
|
933
|
+
configs.forEach((c) => {
|
|
934
|
+
configNames.push({ metric_provider: provider, config_name: c.getString("name") });
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
response.json({ data: configNames, status: 200 });
|
|
939
|
+
});
|
|
940
|
+
router.put("/:walletName/metrics_setting", async (request, response) => {
|
|
941
|
+
var _a;
|
|
942
|
+
const readOnly = (_a = config.getOptionalBoolean("infraWallet.settings.readOnly")) != null ? _a : false;
|
|
943
|
+
if (readOnly) {
|
|
944
|
+
response.status(403).json({ error: "API not enabled in read-only mode", status: 403 });
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const updatedMetricSetting = await updateOrInsertWalletMetricSetting(database, request.body);
|
|
948
|
+
response.json({ updated: updatedMetricSetting, status: 200 });
|
|
949
|
+
});
|
|
950
|
+
router.delete("/:walletName/metrics_setting", async (request, response) => {
|
|
951
|
+
var _a;
|
|
952
|
+
const readOnly = (_a = config.getOptionalBoolean("infraWallet.settings.readOnly")) != null ? _a : false;
|
|
953
|
+
if (readOnly) {
|
|
954
|
+
response.status(403).json({ error: "API not enabled in read-only mode", status: 403 });
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const deletedMetricSetting = await deleteWalletMetricSetting(database, request.body);
|
|
958
|
+
response.json({ deleted: deletedMetricSetting, status: 200 });
|
|
959
|
+
});
|
|
618
960
|
router.use(backendCommon.errorHandler());
|
|
619
961
|
return router;
|
|
620
962
|
}
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/service/functions.ts","../src/service/InfraWalletClient.ts","../src/service/AwsClient.ts","../src/service/AzureClient.ts","../src/service/GCPClient.ts","../src/service/consts.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["import { CacheService, DatabaseService } from '@backstage/backend-plugin-api';\nimport { CategoryMapping, CostQuery, Report } from './types';\n\nexport async function getCategoryMappings(\n database: DatabaseService,\n provider: string,\n): Promise<{ [service: string]: string }> {\n const result: { [service: string]: string } = {};\n const client = await database.getClient();\n const default_mappings = await client\n .where({ provider: provider.toLowerCase() })\n .select()\n .from<CategoryMapping>('category_mappings_default');\n default_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 JSON.parse(mapping.cloud_service_names).forEach((service: string) => {\n result[service] = mapping.category;\n });\n } else {\n mapping.cloud_service_names.forEach((service: string) => {\n result[service] = mapping.category;\n });\n }\n });\n\n // check if there are any records defined by user\n const override_mappings = await client\n .where({ provider: provider })\n .select()\n .from<CategoryMapping>('category_mappings_override');\n override_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 JSON.parse(mapping.cloud_service_names).forEach((service: string) => {\n result[service] = mapping.category;\n });\n } else {\n mapping.cloud_service_names.forEach((service: string) => {\n result[service] = mapping.category;\n });\n }\n });\n\n return result;\n}\n\nexport function getCategoryByServiceName(serviceName: string, categoryMappings: { [service: string]: string }): string {\n if (serviceName in categoryMappings) {\n return categoryMappings[serviceName];\n }\n\n return 'Uncategorized';\n}\n\nexport async function getReportsFromCache(\n cache: CacheService,\n provider: string,\n configKey: string,\n query: CostQuery,\n): Promise<Report[] | undefined> {\n const cacheKey = [\n provider,\n configKey,\n query.filters,\n query.groups,\n query.granularity,\n query.startTime,\n query.endTime,\n ].join('_');\n const cachedCosts = (await cache.get(cacheKey)) as Report[] | undefined;\n return cachedCosts;\n}\n\nexport async function setReportsToCache(\n cache: CacheService,\n reports: Report[],\n provider: string,\n configKey: string,\n query: CostQuery,\n ttl?: number,\n) {\n const cacheKey = [\n provider,\n configKey,\n query.filters,\n query.groups,\n query.granularity,\n query.startTime,\n query.endTime,\n ].join('_');\n await cache.set(cacheKey, reports, {\n ttl: ttl ?? 60 * 60 * 2 * 1000,\n }); // cache for 2 hours by default\n}\n","import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { getCategoryMappings, getReportsFromCache, setReportsToCache } from './functions';\nimport { ClientResponse, CloudProviderError, CostQuery, Report } from './types';\n\nexport abstract class InfraWalletClient {\n constructor(\n protected readonly providerName: string,\n protected readonly config: Config,\n protected readonly database: DatabaseService,\n protected readonly cache: CacheService,\n protected readonly logger: LoggerService,\n ) {}\n\n convertServiceName(serviceName: string): string {\n return serviceName;\n }\n\n abstract initCloudClient(subAccountConfig: Config): Promise<any>;\n\n abstract fetchCostsFromCloud(subAccountConfig: Config, client: any, query: CostQuery): Promise<any>;\n\n abstract transformCostsData(\n subAccountConfig: Config,\n query: CostQuery,\n costResponse: any,\n categoryMappings: { [service: string]: string },\n ): Promise<Report[]>;\n\n async getCostReports(query: CostQuery): Promise<ClientResponse> {\n const conf = this.config.getOptionalConfigArray(\n `backend.infraWallet.integrations.${this.providerName.toLowerCase()}`,\n );\n if (!conf) {\n return { reports: [], errors: [] };\n }\n\n const promises = [];\n const results: Report[] = [];\n const errors: CloudProviderError[] = [];\n\n for (const c of conf) {\n const accountName = c.getString('name');\n\n // first check if there is any cached\n const cachedCosts = await getReportsFromCache(this.cache, this.providerName, accountName, query);\n if (cachedCosts) {\n this.logger.debug(`${this.providerName}/${accountName} costs from cache`);\n cachedCosts.map(cost => {\n results.push(cost);\n });\n continue;\n }\n\n const promise = (async () => {\n try {\n const client = await this.initCloudClient(c);\n const costResponse = await this.fetchCostsFromCloud(c, client, query);\n\n const categoryMappings = await getCategoryMappings(this.database, this.providerName);\n const transformedReports = await this.transformCostsData(c, query, costResponse, categoryMappings);\n\n // cache the results for 2 hours\n await setReportsToCache(\n this.cache,\n transformedReports,\n this.providerName,\n accountName,\n query,\n 60 * 60 * 2 * 1000,\n );\n\n transformedReports.map((value: any) => {\n results.push(value);\n });\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.providerName,\n name: `${this.providerName}/${accountName}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n return {\n reports: results,\n errors: errors,\n };\n }\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 { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { getCategoryByServiceName } from './functions';\nimport { CostQuery, Report } from './types';\n\nexport class AwsClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new AwsClient('AWS', config, database, cache, logger);\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 ['Managed Streaming for Apache Kafka', 'MSK (Managed Streaming for Apache Kafka)'],\n ['Elastic Container Service for Kubernetes', 'EKS (Elastic Container Service for Kubernetes)'],\n ['Elastic Container Service', 'ECS (Elastic Container Service)'],\n ['EC2 Container Registry (ECR)', 'ECR (Elastic Container Registry)'],\n ['Simple Queue Service', 'SQS (Simple Queue Service)'],\n ['Simple Notification Service', 'SNS (Simple Notification Service)'],\n ['Database Migration Service', 'DMS (Database Migration 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 `${this.providerName}/${convertedName}`;\n }\n\n async initCloudClient(subAccountConfig: Config): Promise<any> {\n const accountId = subAccountConfig.getString('accountId');\n const assumedRoleName = subAccountConfig.getString('assumedRoleName');\n const accessKeyId = subAccountConfig.getOptionalString('accessKeyId');\n const accessKeySecret = subAccountConfig.getOptionalString('accessKeySecret');\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 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?.SecretAccessKey as string,\n sessionToken: assumeRoleResponse.Credentials?.SessionToken as string,\n },\n });\n\n return awsCeClient;\n }\n\n async fetchCostsFromCloud(_subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\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 client.send(getCostCommand);\n\n costAndUsageResults = costAndUsageResults.concat(costAndUsageResponse.ResultsByTime);\n nextPageToken = costAndUsageResponse.NextPageToken;\n } while (nextPageToken);\n\n return costAndUsageResults;\n }\n\n async transformCostsData(\n subAccountConfig: Config,\n query: CostQuery,\n costResponse: any,\n categoryMappings: { [service: string]: string },\n ): Promise<Report[]> {\n const accountName = subAccountConfig.getString('name');\n const tags = subAccountConfig.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 transformedData = reduce(\n costResponse,\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 = `${accountName}_${serviceName}`;\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n name: `${this.providerName}/${accountName}`,\n service: this.convertServiceName(serviceName),\n category: getCategoryByServiceName(serviceName, categoryMappings),\n provider: this.providerName,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n const groupMetrics = group.Metrics;\n\n if (groupMetrics !== undefined) {\n accumulator[keyName].reports[period] = parseFloat(groupMetrics.UnblendedCost.Amount ?? '0.0');\n }\n });\n }\n\n return accumulator;\n },\n {},\n );\n return Object.values(transformedData);\n }\n}\n","import { CostManagementClient, QueryDefinition } from '@azure/arm-costmanagement';\nimport { createHttpHeaders, createPipelineRequest } from '@azure/core-rest-pipeline';\nimport { ClientSecretCredential } from '@azure/identity';\nimport { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { getCategoryByServiceName } from './functions';\nimport { CostQuery, Report } from './types';\n\nexport class AzureClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new AzureClient('Azure', config, database, cache, logger);\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 `${this.providerName}/${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(client: CostManagementClient, url: string, body: any, maxRetries = 5): 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 } else if (response.status === 429) {\n const retryAfter = parseInt(\n response.headers.get('x-ms-ratelimit-microsoft.costmanagement-entity-retry-after') || '60',\n 10,\n );\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 initCloudClient(config: Config): Promise<any> {\n const tenantId = config.getString('tenantId');\n const clientId = config.getString('clientId');\n const clientSecret = config.getString('clientSecret');\n const credential = new ClientSecretCredential(tenantId as string, clientId as string, clientSecret as string);\n const client = new CostManagementClient(credential);\n\n return client;\n }\n\n async fetchCostsFromCloud(subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n // Azure SDK doesn't support pagination, so sending HTTP request directly\n const subscriptionId = subAccountConfig.getString('subscriptionId');\n const url = `https://management.azure.com/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/query?api-version=2022-10-01`;\n\n const groupPairs = [{ type: 'Dimension', name: 'ServiceName' }];\n const queryDefinition: QueryDefinition = {\n type: 'ActualCost',\n dataset: {\n granularity: query.granularity,\n aggregation: { totalCostUSD: { name: 'CostUSD', function: 'Sum' } },\n grouping: groupPairs,\n },\n timeframe: 'Custom',\n timePeriod: {\n from: moment(parseInt(query.startTime, 10)).toDate(),\n to: moment(parseInt(query.endTime, 10)).toDate(),\n },\n };\n\n let result = await this.fetchDataWithRetry(client, url, queryDefinition);\n let allResults = result.properties.rows;\n\n while (result.properties.nextLink) {\n result = await this.fetchDataWithRetry(client, result.properties.nextLink, queryDefinition);\n allResults = allResults.concat(result.properties.rows);\n }\n\n return allResults;\n }\n\n async transformCostsData(\n subAccountConfig: Config,\n query: CostQuery,\n costResponse: any,\n categoryMappings: { [service: string]: string },\n ): Promise<Report[]> {\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 accountName = subAccountConfig.getString('name');\n const groupPairs = [{ type: 'Dimension', name: 'ServiceName' }];\n const tags = subAccountConfig.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 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 = accountName;\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: `${this.providerName}/${accountName}`,\n service: this.convertServiceName(serviceName),\n category: getCategoryByServiceName(serviceName, categoryMappings),\n provider: this.providerName,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n if (!moment(date).isBefore(moment(parseInt(query.startTime, 10)))) {\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 return Object.values(transformedData);\n }\n}\n","import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { BigQuery } from '@google-cloud/bigquery';\nimport { reduce } from 'lodash';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { getCategoryByServiceName } from './functions';\nimport { CostQuery, Report } from './types';\n\nexport class GCPClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new GCPClient('GCP', config, database, cache, logger);\n }\n\n convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Google Cloud'];\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n return `${this.providerName}/${convertedName}`;\n }\n\n async initCloudClient(subAccountConfig: Config): Promise<any> {\n const keyFilePath = subAccountConfig.getString('keyFilePath');\n const projectId = subAccountConfig.getString('projectId');\n // Configure a JWT auth client\n const options = {\n keyFilename: keyFilePath,\n projectId: projectId,\n };\n\n // Initialize the BigQuery API\n const bigqueryClient = new BigQuery(options);\n\n return bigqueryClient;\n }\n\n async fetchCostsFromCloud(subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n const projectId = subAccountConfig.getString('projectId');\n const datasetId = subAccountConfig.getString('datasetId');\n const tableId = subAccountConfig.getString('tableId');\n\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(CAST(cost AS NUMERIC)) + SUM(IFNULL((SELECT SUM(CAST(c.amount AS NUMERIC)) FROM UNNEST(credits) AS c), 0))) 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 client.createQueryJob({\n query: sql,\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 transformCostsData(\n subAccountConfig: Config,\n _query: CostQuery,\n costResponse: any,\n categoryMappings: { [service: string]: string },\n ): Promise<Report[]> {\n const accountName = subAccountConfig.getString('name');\n const tags = subAccountConfig.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 transformedData = reduce(\n costResponse,\n (acc: { [key: string]: Report }, row) => {\n const period = row.period;\n const keyName = `${accountName}_${row.project}_${row.service}`;\n\n if (!acc[keyName]) {\n acc[keyName] = {\n id: keyName,\n name: `${this.providerName}/${accountName}`,\n service: this.convertServiceName(row.service),\n category: getCategoryByServiceName(row.service, categoryMappings),\n provider: this.providerName,\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] = parseFloat(row.total_cost);\n\n return acc;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n}\n","import { AwsClient } from './AwsClient';\nimport { AzureClient } from './AzureClient';\nimport { GCPClient } from './GCPClient';\n\nexport const PROVIDER_CLIENT_MAPPINGS: {\n [provider: string]: any;\n} = {\n aws: AwsClient,\n azure: AzureClient,\n gcp: GCPClient,\n};\n","import { errorHandler } from '@backstage/backend-common';\nimport { CacheService, DatabaseService, LoggerService, resolvePackagePath } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { PROVIDER_CLIENT_MAPPINGS } from './consts';\nimport { CloudProviderError, 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('@electrolux-oss/plugin-infrawallet-backend', 'migrations');\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n // insert default category_mappings to the database\n const seedsDir = resolvePackagePath('@electrolux-oss/plugin-infrawallet-backend', 'seeds');\n await client.seed.run({ directory: seedsDir });\n}\n\nexport async function createRouter(options: RouterOptions): 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 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 const errors: CloudProviderError[] = [];\n\n const conf = config.getConfig('backend.infraWallet.integrations');\n conf.keys().forEach((provider: string) => {\n if (provider in PROVIDER_CLIENT_MAPPINGS) {\n const client: InfraWalletClient = PROVIDER_CLIENT_MAPPINGS[provider].create(config, database, cache, logger);\n const fetchCloudCosts = (async () => {\n try {\n const clientResponse = await client.getCostReports({\n filters: filters,\n groups: groups,\n granularity: granularity,\n startTime: startTime,\n endTime: endTime,\n });\n clientResponse.errors.forEach((e: CloudProviderError) => {\n errors.push(e);\n });\n clientResponse.reports.forEach((cost: Report) => {\n results.push(cost);\n });\n } catch (e) {\n logger.error(e);\n errors.push({\n provider: client.constructor.name,\n name: client.constructor.name,\n error: e.message,\n });\n }\n })();\n promises.push(fetchCloudCosts);\n }\n });\n\n await Promise.all(promises);\n\n if (errors.length > 0) {\n response.status(207).json({ data: results, errors: errors, status: 207 });\n } else {\n response.json({ data: results, errors: errors, status: 200 });\n }\n });\n\n router.use(errorHandler());\n return router;\n}\n","import { coreServices, createBackendPlugin } 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","resolvePackagePath","Router","express","errorHandler","createBackendPlugin","coreServices"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/controllers/MetricSettingController.ts","../src/service/functions.ts","../src/service/InfraWalletClient.ts","../src/service/AwsClient.ts","../src/service/AzureClient.ts","../src/service/MetricProvider.ts","../src/service/DatadogProvider.ts","../src/service/GCPClient.ts","../src/service/GrafanaCloudProvider.ts","../src/service/consts.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport { MetricSetting, Wallet } from '../service/types';\n\nexport async function getWallet(database: DatabaseService, walletName: string): Promise<Wallet | undefined> {\n const client = await database.getClient();\n const result = await client<Wallet>('wallets').where('name', walletName).first();\n\n return result;\n}\n\nexport async function getWalletMetricSettings(database: DatabaseService, walletName: string): Promise<MetricSetting[]> {\n const client = await database.getClient();\n\n const metricSettings = await client\n .select('business_metrics.*')\n .from<MetricSetting>('business_metrics')\n .where('wallets.name', walletName)\n .join('wallets', 'business_metrics.wallet_id', '=', 'wallets.id');\n\n return metricSettings;\n}\n\nexport async function updateOrInsertWalletMetricSetting(\n database: DatabaseService,\n walletSetting: MetricSetting,\n): Promise<boolean> {\n const client = await database.getClient();\n const result: number[] = await client('business_metrics').insert(walletSetting).onConflict('id').merge();\n\n if (result[0] > 0) {\n return true;\n }\n\n return false;\n}\n\nexport async function deleteWalletMetricSetting(\n database: DatabaseService,\n walletSetting: MetricSetting,\n): Promise<boolean> {\n const client = await database.getClient();\n const result: number = await client('business_metrics').where('id', walletSetting.id).del();\n\n if (result > 0) {\n return true;\n }\n\n return false;\n}\n","import { CacheService, DatabaseService } from '@backstage/backend-plugin-api';\nimport { CategoryMapping, CostQuery, Metric, MetricQuery, Report } from './types';\n\nexport async function getCategoryMappings(\n database: DatabaseService,\n provider: string,\n): Promise<{ [service: string]: string }> {\n const result: { [service: string]: string } = {};\n const client = await database.getClient();\n const default_mappings = await client\n .where({ provider: provider.toLowerCase() })\n .select()\n .from<CategoryMapping>('category_mappings_default');\n default_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 JSON.parse(mapping.cloud_service_names).forEach((service: string) => {\n result[service] = mapping.category;\n });\n } else {\n mapping.cloud_service_names.forEach((service: string) => {\n result[service] = mapping.category;\n });\n }\n });\n\n // check if there are any records defined by user\n const override_mappings = await client\n .where({ provider: provider })\n .select()\n .from<CategoryMapping>('category_mappings_override');\n override_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 JSON.parse(mapping.cloud_service_names).forEach((service: string) => {\n result[service] = mapping.category;\n });\n } else {\n mapping.cloud_service_names.forEach((service: string) => {\n result[service] = mapping.category;\n });\n }\n });\n\n return result;\n}\n\nexport function getCategoryByServiceName(serviceName: string, categoryMappings: { [service: string]: string }): string {\n if (serviceName in categoryMappings) {\n return categoryMappings[serviceName];\n }\n\n return 'Uncategorized';\n}\n\nexport async function getReportsFromCache(\n cache: CacheService,\n provider: string,\n configKey: string,\n query: CostQuery,\n): Promise<Report[] | undefined> {\n const cacheKey = [\n provider,\n configKey,\n query.filters,\n query.groups,\n query.granularity,\n query.startTime,\n query.endTime,\n ].join('_');\n const cachedCosts = (await cache.get(cacheKey)) as Report[] | undefined;\n return cachedCosts;\n}\n\nexport async function getMetricsFromCache(\n cache: CacheService,\n provider: string,\n configKey: string,\n query: MetricQuery,\n): Promise<Metric[] | undefined> {\n const cacheKey = [\n provider,\n configKey,\n query.name,\n query.query,\n query.granularity,\n query.startTime,\n query.endTime,\n ].join('_');\n const cachedMetrics = (await cache.get(cacheKey)) as Metric[] | undefined;\n return cachedMetrics;\n}\n\nexport async function setReportsToCache(\n cache: CacheService,\n reports: Report[],\n provider: string,\n configKey: string,\n query: CostQuery,\n ttl?: number,\n) {\n const cacheKey = [\n provider,\n configKey,\n query.filters,\n query.groups,\n query.granularity,\n query.startTime,\n query.endTime,\n ].join('_');\n await cache.set(cacheKey, reports, {\n ttl: ttl ?? 60 * 60 * 2 * 1000,\n }); // cache for 2 hours by default\n}\n\nexport async function setMetricsToCache(\n cache: CacheService,\n metrics: Metric[],\n provider: string,\n configKey: string,\n query: MetricQuery,\n ttl?: number,\n) {\n const cacheKey = [\n provider,\n configKey,\n query.name,\n query.query,\n query.granularity,\n query.startTime,\n query.endTime,\n ].join('_');\n const crypto = require('crypto');\n await cache.set(crypto.createHash('md5').update(cacheKey).digest('hex'), metrics, {\n ttl: ttl ?? 60 * 60 * 2 * 1000,\n }); // cache for 2 hours by default\n}\n","import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { getCategoryMappings, getReportsFromCache, setReportsToCache } from './functions';\nimport { ClientResponse, CloudProviderError, CostQuery, Report } from './types';\n\nexport abstract class InfraWalletClient {\n constructor(\n protected readonly providerName: string,\n protected readonly config: Config,\n protected readonly database: DatabaseService,\n protected readonly cache: CacheService,\n protected readonly logger: LoggerService,\n ) {}\n\n convertServiceName(serviceName: string): string {\n return serviceName;\n }\n\n abstract initCloudClient(subAccountConfig: Config): Promise<any>;\n\n abstract fetchCostsFromCloud(subAccountConfig: Config, client: any, query: CostQuery): Promise<any>;\n\n abstract transformCostsData(\n subAccountConfig: Config,\n query: CostQuery,\n costResponse: any,\n categoryMappings: { [service: string]: string },\n ): Promise<Report[]>;\n\n async getCostReports(query: CostQuery): Promise<ClientResponse> {\n const conf = this.config.getOptionalConfigArray(\n `backend.infraWallet.integrations.${this.providerName.toLowerCase()}`,\n );\n if (!conf) {\n return { reports: [], errors: [] };\n }\n\n const promises = [];\n const results: Report[] = [];\n const errors: CloudProviderError[] = [];\n\n for (const c of conf) {\n const accountName = c.getString('name');\n\n // first check if there is any cached\n const cachedCosts = await getReportsFromCache(this.cache, this.providerName, accountName, query);\n if (cachedCosts) {\n this.logger.debug(`${this.providerName}/${accountName} costs from cache`);\n cachedCosts.map(cost => {\n results.push(cost);\n });\n continue;\n }\n\n const promise = (async () => {\n try {\n const client = await this.initCloudClient(c);\n const costResponse = await this.fetchCostsFromCloud(c, client, query);\n\n const categoryMappings = await getCategoryMappings(this.database, this.providerName);\n const transformedReports = await this.transformCostsData(c, query, costResponse, categoryMappings);\n\n // cache the results for 2 hours\n await setReportsToCache(\n this.cache,\n transformedReports,\n this.providerName,\n accountName,\n query,\n 60 * 60 * 2 * 1000,\n );\n\n transformedReports.map((value: any) => {\n results.push(value);\n });\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.providerName,\n name: `${this.providerName}/${accountName}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n await Promise.all(promises);\n return {\n reports: results,\n errors: errors,\n };\n }\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 { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { getCategoryByServiceName } from './functions';\nimport { CostQuery, Report } from './types';\n\nexport class AwsClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new AwsClient('AWS', config, database, cache, logger);\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 ['Managed Streaming for Apache Kafka', 'MSK (Managed Streaming for Apache Kafka)'],\n ['Elastic Container Service for Kubernetes', 'EKS (Elastic Container Service for Kubernetes)'],\n ['Elastic Container Service', 'ECS (Elastic Container Service)'],\n ['EC2 Container Registry (ECR)', 'ECR (Elastic Container Registry)'],\n ['Simple Queue Service', 'SQS (Simple Queue Service)'],\n ['Simple Notification Service', 'SNS (Simple Notification Service)'],\n ['Database Migration Service', 'DMS (Database Migration 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 `${this.providerName}/${convertedName}`;\n }\n\n async initCloudClient(subAccountConfig: Config): Promise<any> {\n const accountId = subAccountConfig.getString('accountId');\n const assumedRoleName = subAccountConfig.getString('assumedRoleName');\n const accessKeyId = subAccountConfig.getOptionalString('accessKeyId');\n const accessKeySecret = subAccountConfig.getOptionalString('accessKeySecret');\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 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?.SecretAccessKey as string,\n sessionToken: assumeRoleResponse.Credentials?.SessionToken as string,\n },\n });\n\n return awsCeClient;\n }\n\n async fetchCostsFromCloud(_subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\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 client.send(getCostCommand);\n\n costAndUsageResults = costAndUsageResults.concat(costAndUsageResponse.ResultsByTime);\n nextPageToken = costAndUsageResponse.NextPageToken;\n } while (nextPageToken);\n\n return costAndUsageResults;\n }\n\n async transformCostsData(\n subAccountConfig: Config,\n query: CostQuery,\n costResponse: any,\n categoryMappings: { [service: string]: string },\n ): Promise<Report[]> {\n const accountName = subAccountConfig.getString('name');\n const tags = subAccountConfig.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 transformedData = reduce(\n costResponse,\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 = `${accountName}_${serviceName}`;\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n name: `${this.providerName}/${accountName}`,\n service: this.convertServiceName(serviceName),\n category: getCategoryByServiceName(serviceName, categoryMappings),\n provider: this.providerName,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n const groupMetrics = group.Metrics;\n\n if (groupMetrics !== undefined) {\n accumulator[keyName].reports[period] = parseFloat(groupMetrics.UnblendedCost.Amount ?? '0.0');\n }\n });\n }\n\n return accumulator;\n },\n {},\n );\n return Object.values(transformedData);\n }\n}\n","import { CostManagementClient, QueryDefinition } from '@azure/arm-costmanagement';\nimport { createHttpHeaders, createPipelineRequest } from '@azure/core-rest-pipeline';\nimport { ClientSecretCredential } from '@azure/identity';\nimport { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { reduce } from 'lodash';\nimport moment from 'moment';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { getCategoryByServiceName } from './functions';\nimport { CostQuery, Report } from './types';\n\nexport class AzureClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new AzureClient('Azure', config, database, cache, logger);\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 `${this.providerName}/${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(client: CostManagementClient, url: string, body: any, maxRetries = 5): 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 } else if (response.status === 429) {\n const retryAfter = parseInt(\n response.headers.get('x-ms-ratelimit-microsoft.costmanagement-entity-retry-after') || '60',\n 10,\n );\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 initCloudClient(config: Config): Promise<any> {\n const tenantId = config.getString('tenantId');\n const clientId = config.getString('clientId');\n const clientSecret = config.getString('clientSecret');\n const credential = new ClientSecretCredential(tenantId as string, clientId as string, clientSecret as string);\n const client = new CostManagementClient(credential);\n\n return client;\n }\n\n async fetchCostsFromCloud(subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n // Azure SDK doesn't support pagination, so sending HTTP request directly\n const subscriptionId = subAccountConfig.getString('subscriptionId');\n const url = `https://management.azure.com/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/query?api-version=2022-10-01`;\n\n const groupPairs = [{ type: 'Dimension', name: 'ServiceName' }];\n const queryDefinition: QueryDefinition = {\n type: 'ActualCost',\n dataset: {\n granularity: query.granularity,\n aggregation: { totalCostUSD: { name: 'CostUSD', function: 'Sum' } },\n grouping: groupPairs,\n },\n timeframe: 'Custom',\n timePeriod: {\n from: moment(parseInt(query.startTime, 10)).toDate(),\n to: moment(parseInt(query.endTime, 10)).toDate(),\n },\n };\n\n let result = await this.fetchDataWithRetry(client, url, queryDefinition);\n let allResults = result.properties.rows;\n\n while (result.properties.nextLink) {\n result = await this.fetchDataWithRetry(client, result.properties.nextLink, queryDefinition);\n allResults = allResults.concat(result.properties.rows);\n }\n\n return allResults;\n }\n\n async transformCostsData(\n subAccountConfig: Config,\n query: CostQuery,\n costResponse: any,\n categoryMappings: { [service: string]: string },\n ): Promise<Report[]> {\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 accountName = subAccountConfig.getString('name');\n const groupPairs = [{ type: 'Dimension', name: 'ServiceName' }];\n const tags = subAccountConfig.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 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 = accountName;\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: `${this.providerName}/${accountName}`,\n service: this.convertServiceName(serviceName),\n category: getCategoryByServiceName(serviceName, categoryMappings),\n provider: this.providerName,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n if (!moment(date).isBefore(moment(parseInt(query.startTime, 10)))) {\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 return Object.values(transformedData);\n }\n}\n","import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { getMetricsFromCache, setMetricsToCache } from './functions';\nimport { CloudProviderError, Metric, MetricQuery, MetricSetting, MetricResponse } from './types';\n\nexport abstract class MetricProvider {\n constructor(\n protected readonly providerName: string,\n protected readonly config: Config,\n protected readonly database: DatabaseService,\n protected readonly cache: CacheService,\n protected readonly logger: LoggerService,\n ) {}\n\n abstract initProviderClient(metricProviderConfig: Config): Promise<any>;\n\n abstract fetchMetrics(metricProviderConfig: Config, client: any, query: MetricQuery): Promise<any>;\n\n abstract transformMetricData(\n metricProviderConfig: Config,\n query: MetricQuery,\n metricResponse: any,\n ): Promise<Metric[]>;\n\n async getMetrics(query: MetricQuery): Promise<MetricResponse> {\n const conf = this.config.getOptionalConfigArray(\n `backend.infraWallet.metricProviders.${this.providerName.toLowerCase()}`,\n );\n if (!conf) {\n return { metrics: [], errors: [] };\n }\n\n const promises = [];\n const results: Metric[] = [];\n const errors: CloudProviderError[] = [];\n\n for (const c of conf) {\n const configName = c.getString('name');\n const client = await this.initProviderClient(c);\n const dbClient = await this.database.getClient();\n\n const metricSettings = await dbClient\n .where({\n 'wallets.name': query.walletName,\n 'business_metrics.metric_provider': this.providerName.toLowerCase(),\n 'business_metrics.config_name': configName,\n })\n .select('business_metrics.*')\n .from<MetricSetting>('business_metrics')\n .join('wallets', 'business_metrics.wallet_id', '=', 'wallets.id');\n\n for (const metric of metricSettings || []) {\n const promise = (async () => {\n try {\n const fullQuery: MetricQuery = {\n name: metric.metric_name,\n query: metric.query,\n ...query,\n };\n\n // first check if there is any cached metrics\n const cachedMetrics = await getMetricsFromCache(this.cache, this.providerName, configName, fullQuery);\n if (cachedMetrics) {\n this.logger.debug(`${this.providerName}/${configName}/${fullQuery.name} metrics from cache`);\n cachedMetrics.map(m => {\n results.push(m);\n });\n return;\n }\n\n const metricResponse = await this.fetchMetrics(c, client, fullQuery);\n const transformedMetrics = await this.transformMetricData(c, fullQuery, metricResponse);\n\n // cache the results for 2 hours\n await setMetricsToCache(\n this.cache,\n transformedMetrics,\n this.providerName,\n configName,\n fullQuery,\n 60 * 60 * 2 * 1000,\n );\n\n transformedMetrics.map((value: any) => {\n results.push(value);\n });\n } catch (e) {\n this.logger.error(e);\n errors.push({\n provider: this.providerName,\n name: `${this.providerName}/${configName}/${metric.getString('metricName')}`,\n error: e.message,\n });\n }\n })();\n promises.push(promise);\n }\n }\n await Promise.all(promises);\n return {\n metrics: results,\n errors: errors,\n };\n }\n}\n","import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { v1 as datadogApiV1, client as datadogClient } from '@datadog/datadog-api-client';\nimport moment from 'moment';\nimport { MetricProvider } from './MetricProvider';\nimport { Metric, MetricQuery } from './types';\n\nexport class DatadogProvider extends MetricProvider {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new DatadogProvider('Datadog', config, database, cache, logger);\n }\n\n async initProviderClient(config: Config): Promise<any> {\n const apiKey = config.getString('apiKey');\n const applicationKey = config.getString('applicationKey');\n const ddSite = config.getString('ddSite');\n const configuration = datadogClient.createConfiguration({\n baseServer: new datadogClient.BaseServerConfiguration(ddSite, {}),\n authMethods: {\n apiKeyAuth: apiKey,\n appKeyAuth: applicationKey,\n },\n });\n const client = new datadogApiV1.MetricsApi(configuration);\n return client;\n }\n\n async fetchMetrics(_metricProviderConfig: Config, client: any, query: MetricQuery): Promise<any> {\n const params: datadogApiV1.MetricsApiQueryMetricsRequest = {\n from: parseInt(query.startTime, 10) / 1000,\n to: parseInt(query.endTime, 10) / 1000,\n query: query.query?.replaceAll('IW_INTERVAL', query.granularity === 'daily' ? '86400' : '2592000') as string,\n };\n return client.queryMetrics(params).then((data: datadogApiV1.MetricsQueryResponse) => {\n if (data.status === 'ok') {\n return data;\n }\n throw new Error(data.error);\n });\n }\n\n async transformMetricData(_metricProviderConfig: Config, query: MetricQuery, metricResponse: any): Promise<Metric[]> {\n const transformedData = [];\n\n for (const series of metricResponse.series) {\n const metricName = query.name as string;\n const tagSet = series.tagSet;\n\n const metric: Metric = {\n id: `${metricName} ${tagSet.length === 0 ? '' : tagSet}`,\n provider: this.providerName,\n name: metricName,\n reports: {},\n };\n\n for (const point of series.pointlist) {\n const period = moment(point[0]).format(query.granularity === 'daily' ? 'YYYY-MM-DD' : 'YYYY-MM');\n const value = point[1];\n metric.reports[period] = value;\n }\n\n transformedData.push(metric);\n }\n\n return transformedData;\n }\n}\n","import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { BigQuery } from '@google-cloud/bigquery';\nimport { reduce } from 'lodash';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { getCategoryByServiceName } from './functions';\nimport { CostQuery, Report } from './types';\n\nexport class GCPClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new GCPClient('GCP', config, database, cache, logger);\n }\n\n convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Google Cloud'];\n\n for (const prefix of prefixes) {\n if (serviceName.startsWith(prefix)) {\n convertedName = serviceName.slice(prefix.length).trim();\n }\n }\n\n return `${this.providerName}/${convertedName}`;\n }\n\n async initCloudClient(subAccountConfig: Config): Promise<any> {\n const keyFilePath = subAccountConfig.getString('keyFilePath');\n const projectId = subAccountConfig.getString('projectId');\n // Configure a JWT auth client\n const options = {\n keyFilename: keyFilePath,\n projectId: projectId,\n };\n\n // Initialize the BigQuery API\n const bigqueryClient = new BigQuery(options);\n\n return bigqueryClient;\n }\n\n async fetchCostsFromCloud(subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n const projectId = subAccountConfig.getString('projectId');\n const datasetId = subAccountConfig.getString('datasetId');\n const tableId = subAccountConfig.getString('tableId');\n\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(CAST(cost AS NUMERIC)) + SUM(IFNULL((SELECT SUM(CAST(c.amount AS NUMERIC)) FROM UNNEST(credits) AS c), 0))) 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 client.createQueryJob({\n query: sql,\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 transformCostsData(\n subAccountConfig: Config,\n _query: CostQuery,\n costResponse: any,\n categoryMappings: { [service: string]: string },\n ): Promise<Report[]> {\n const accountName = subAccountConfig.getString('name');\n const tags = subAccountConfig.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 transformedData = reduce(\n costResponse,\n (acc: { [key: string]: Report }, row) => {\n const period = row.period;\n const keyName = `${accountName}_${row.project}_${row.service}`;\n\n if (!acc[keyName]) {\n acc[keyName] = {\n id: keyName,\n name: `${this.providerName}/${accountName}`,\n service: this.convertServiceName(row.service),\n category: getCategoryByServiceName(row.service, categoryMappings),\n provider: this.providerName,\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] = parseFloat(row.total_cost);\n\n return acc;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n}\n","import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport moment from 'moment';\nimport fetch from 'node-fetch';\nimport { MetricProvider } from './MetricProvider';\nimport { Metric, MetricQuery } from './types';\n\nexport class GrafanaCloudProvider extends MetricProvider {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new GrafanaCloudProvider('GrafanaCloud', config, database, cache, logger);\n }\n\n async initProviderClient(_config: Config): Promise<any> {\n // for now we don't need to use Grafana Cloud SDK\n return null;\n }\n\n async fetchMetrics(metricProviderConfig: Config, _client: any, query: MetricQuery): Promise<any> {\n const url = metricProviderConfig.getString('url');\n const datasourceUid = metricProviderConfig.getString('datasourceUid');\n const token = metricProviderConfig.getString('token');\n\n const headers = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n };\n const payload = {\n queries: [\n {\n datasource: {\n uid: datasourceUid,\n },\n expr: query.query?.replaceAll('IW_INTERVAL', query.granularity === 'daily' ? '1d' : '30d'),\n refId: 'A',\n },\n ],\n from: query.startTime,\n to: query.endTime,\n };\n\n const response = await fetch(`${url}/api/ds/query`, {\n method: 'post',\n body: JSON.stringify(payload),\n headers: headers,\n });\n const data = await response.json();\n\n return data;\n }\n\n async transformMetricData(_metricProviderConfig: Config, query: MetricQuery, metricResponse: any): Promise<Metric[]> {\n const transformedData = [];\n\n const metricName = query.name as string;\n const metric: Metric = {\n id: metricName,\n provider: this.providerName,\n name: metricName,\n reports: {},\n };\n\n // TODO: iterate all the series\n const periods = metricResponse.results.A.frames[0].data.values[0];\n const values = metricResponse.results.A.frames[0].data.values[1];\n for (let i = 0; i < periods.length; i++) {\n const period = moment(periods[i]).format(query.granularity === 'daily' ? 'YYYY-MM-DD' : 'YYYY-MM');\n const value = values[i];\n metric.reports[period] = value;\n }\n\n transformedData.push(metric);\n return transformedData;\n }\n}\n","import { AwsClient } from './AwsClient';\nimport { AzureClient } from './AzureClient';\nimport { DatadogProvider } from './DatadogProvider';\nimport { GCPClient } from './GCPClient';\nimport { GrafanaCloudProvider } from './GrafanaCloudProvider';\n\nexport const COST_CLIENT_MAPPINGS: {\n [provider: string]: any;\n} = {\n aws: AwsClient,\n azure: AzureClient,\n gcp: GCPClient,\n};\n\nexport const METRIC_PROVIDER_MAPPINGS: {\n [provider: string]: any;\n} = {\n datadog: DatadogProvider,\n grafanacloud: GrafanaCloudProvider,\n};\n","import { errorHandler } from '@backstage/backend-common';\nimport { CacheService, DatabaseService, LoggerService, resolvePackagePath } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport {\n deleteWalletMetricSetting,\n getWallet,\n getWalletMetricSettings,\n updateOrInsertWalletMetricSetting,\n} from '../controllers/MetricSettingController';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { MetricProvider } from './MetricProvider';\nimport { COST_CLIENT_MAPPINGS, METRIC_PROVIDER_MAPPINGS } from './consts';\nimport { CloudProviderError, Metric, MetricSetting, 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('@electrolux-oss/plugin-infrawallet-backend', 'migrations');\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n // insert default category_mappings to the database\n const seedsDir = resolvePackagePath('@electrolux-oss/plugin-infrawallet-backend', 'seeds');\n await client.seed.run({ directory: seedsDir });\n}\n\nexport async function createRouter(options: RouterOptions): 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 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 const errors: CloudProviderError[] = [];\n\n const conf = config.getConfig('backend.infraWallet.integrations');\n conf.keys().forEach((provider: string) => {\n if (provider in COST_CLIENT_MAPPINGS) {\n const client: InfraWalletClient = COST_CLIENT_MAPPINGS[provider].create(config, database, cache, logger);\n const fetchCloudCosts = (async () => {\n try {\n const clientResponse = await client.getCostReports({\n filters: filters,\n groups: groups,\n granularity: granularity,\n startTime: startTime,\n endTime: endTime,\n });\n clientResponse.errors.forEach((e: CloudProviderError) => {\n errors.push(e);\n });\n clientResponse.reports.forEach((cost: Report) => {\n results.push(cost);\n });\n } catch (e) {\n logger.error(e);\n errors.push({\n provider: client.constructor.name,\n name: client.constructor.name,\n error: e.message,\n });\n }\n })();\n promises.push(fetchCloudCosts);\n }\n });\n\n await Promise.all(promises);\n\n if (errors.length > 0) {\n response.status(207).json({ data: results, errors: errors, status: 207 });\n } else {\n response.json({ data: results, errors: errors, status: 200 });\n }\n });\n\n router.get('/:walletName/metrics', async (request, response) => {\n const walletName = request.params.walletName;\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: Metric[] = [];\n const errors: CloudProviderError[] = [];\n\n const conf = config.getConfig('backend.infraWallet.metricProviders');\n conf.keys().forEach((provider: string) => {\n if (provider in METRIC_PROVIDER_MAPPINGS) {\n const client: MetricProvider = METRIC_PROVIDER_MAPPINGS[provider].create(config, database, cache, logger);\n const fetchMetrics = (async () => {\n try {\n const metricResponse = await client.getMetrics({\n walletName: walletName,\n granularity: granularity,\n startTime: startTime,\n endTime: endTime,\n });\n metricResponse.errors.forEach((e: CloudProviderError) => {\n errors.push(e);\n });\n metricResponse.metrics.forEach((metric: Metric) => {\n results.push(metric);\n });\n } catch (e) {\n logger.error(e);\n errors.push({\n provider: client.constructor.name,\n name: client.constructor.name,\n error: e.message,\n });\n }\n })();\n promises.push(fetchMetrics);\n }\n });\n\n await Promise.all(promises);\n\n if (errors.length > 0) {\n response.status(207).json({ data: results, errors: errors, status: 207 });\n } else {\n response.json({ data: results, errors: errors, status: 200 });\n }\n });\n\n router.get('/:walletName', async (request, response) => {\n const walletName = request.params.walletName;\n const wallet = await getWallet(database, walletName);\n if (wallet === undefined) {\n response.status(404).json({ error: 'Wallet not found', status: 404 });\n return;\n }\n\n response.json({ data: wallet, status: 200 });\n });\n\n router.get('/:walletName/metrics_setting', async (request, response) => {\n const walletName = request.params.walletName;\n const metricSettings = await getWalletMetricSettings(database, walletName);\n response.json({ data: metricSettings, status: 200 });\n });\n\n router.get('/metric/metric_configs', async (_request, response) => {\n const conf = config.getConfig('backend.infraWallet.metricProviders');\n const configNames: { metric_provider: string; config_name: string }[] = [];\n conf.keys().forEach((provider: string) => {\n const configs = conf.getOptionalConfigArray(provider);\n if (configs) {\n configs.forEach(c => {\n configNames.push({ metric_provider: provider, config_name: c.getString('name') });\n });\n }\n });\n\n response.json({ data: configNames, status: 200 });\n });\n\n router.put('/:walletName/metrics_setting', async (request, response) => {\n const readOnly = config.getOptionalBoolean('infraWallet.settings.readOnly') ?? false;\n\n if (readOnly) {\n response.status(403).json({ error: 'API not enabled in read-only mode', status: 403 });\n return;\n }\n\n const updatedMetricSetting = await updateOrInsertWalletMetricSetting(database, request.body as MetricSetting);\n response.json({ updated: updatedMetricSetting, status: 200 });\n });\n\n router.delete('/:walletName/metrics_setting', async (request, response) => {\n const readOnly = config.getOptionalBoolean('infraWallet.settings.readOnly') ?? false;\n\n if (readOnly) {\n response.status(403).json({ error: 'API not enabled in read-only mode', status: 403 });\n return;\n }\n\n const deletedMetricSetting = await deleteWalletMetricSetting(database, request.body as MetricSetting);\n response.json({ deleted: deletedMetricSetting, status: 200 });\n });\n\n router.use(errorHandler());\n return router;\n}\n","import { coreServices, createBackendPlugin } 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","datadogClient","datadogApiV1","BigQuery","fetch","resolvePackagePath","Router","express","errorHandler","createBackendPlugin","coreServices"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param { import("knex").Knex } knex
|
|
3
|
+
* @returns { Promise<void> }
|
|
4
|
+
*/
|
|
5
|
+
exports.up = async function up(knex) {
|
|
6
|
+
await knex.schema
|
|
7
|
+
//
|
|
8
|
+
// wallets
|
|
9
|
+
//
|
|
10
|
+
.createTable('wallets', table => {
|
|
11
|
+
table.comment('Wallets defined by users');
|
|
12
|
+
table.uuid('id').defaultTo(knex.fn.uuid()).primary().notNullable().comment('Auto-generated ID of a wallet');
|
|
13
|
+
table.string('name').notNullable().comment('The display name for a wallet');
|
|
14
|
+
table.string('currency').notNullable().comment('The currency for displaying the costs');
|
|
15
|
+
table.string('description').comment('The description of a wallet');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await knex.schema
|
|
19
|
+
//
|
|
20
|
+
// business metric configurations for wallets
|
|
21
|
+
//
|
|
22
|
+
.createTable('business_metrics', table => {
|
|
23
|
+
table.comment('Business metric configurations for wallets');
|
|
24
|
+
table
|
|
25
|
+
.uuid('id')
|
|
26
|
+
.defaultTo(knex.fn.uuid())
|
|
27
|
+
.primary()
|
|
28
|
+
.notNullable()
|
|
29
|
+
.comment('Auto-generated ID of a metric configuration');
|
|
30
|
+
table.uuid('wallet_id').notNullable().comment('The ID of the wallet that has this metric configuration');
|
|
31
|
+
table.string('metric_provider').notNullable().comment('Provider type, either datadog or grafanacloud');
|
|
32
|
+
table.string('config_name').notNullable().comment('Name of a specific metric provider config');
|
|
33
|
+
table.string('metric_name').notNullable().comment('Display name of a metric');
|
|
34
|
+
table.text('description').comment('Description of a metric');
|
|
35
|
+
table
|
|
36
|
+
.text('query')
|
|
37
|
+
.notNullable()
|
|
38
|
+
.comment('Query string (`IW_INTERVAL` will be replaced with the interval value based on the granularity)');
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param { import("knex").Knex } knex
|
|
44
|
+
* @returns { Promise<void> }
|
|
45
|
+
*/
|
|
46
|
+
exports.down = async function down(knex) {
|
|
47
|
+
await knex.schema.dropTableIfExists('wallets');
|
|
48
|
+
await knex.schema.dropTableIfExists('business_metrics');
|
|
49
|
+
};
|
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.8",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "backend-plugin"
|
|
6
6
|
},
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@backstage/backend-plugin-api": "^0.6.18",
|
|
45
45
|
"@backstage/config": "^1.2.0",
|
|
46
46
|
"@backstage/types": "^1.1.1",
|
|
47
|
+
"@datadog/datadog-api-client": "^1.26.0",
|
|
47
48
|
"@google-cloud/bigquery": "7.7.1",
|
|
48
49
|
"@types/express": "*",
|
|
49
50
|
"express": "^4.17.1",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
exports.seed = async knex => {
|
|
2
|
+
await knex('wallets')
|
|
3
|
+
.count('id as c')
|
|
4
|
+
.then(async result => {
|
|
5
|
+
if (result[0].c === 0 || result[0].c === '0') {
|
|
6
|
+
// only insert a record for default wallet when the table is empty
|
|
7
|
+
await knex('wallets').insert([
|
|
8
|
+
{ name: 'default', currency: 'usd', description: 'The auto-created default wallet.' },
|
|
9
|
+
]);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|