@electrolux-oss/plugin-infrawallet-backend 1.1.0-20250901061058-90165b1 → 1.1.0-20251122142003-7ac6f9a
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/dist/cost-clients/AwsClient.cjs.js +34 -7
- package/dist/cost-clients/AwsClient.cjs.js.map +1 -1
- package/dist/cost-clients/AzureClient.cjs.js +31 -2
- package/dist/cost-clients/AzureClient.cjs.js.map +1 -1
- package/dist/cost-clients/ConfluentClient.cjs.js +50 -2
- package/dist/cost-clients/ConfluentClient.cjs.js.map +1 -1
- package/dist/cost-clients/CustomProviderClient.cjs.js +30 -1
- package/dist/cost-clients/CustomProviderClient.cjs.js.map +1 -1
- package/dist/cost-clients/DatadogClient.cjs.js +38 -2
- package/dist/cost-clients/DatadogClient.cjs.js.map +1 -1
- package/dist/cost-clients/ElasticCloudClient.cjs.js +39 -7
- package/dist/cost-clients/ElasticCloudClient.cjs.js.map +1 -1
- package/dist/cost-clients/GCPClient.cjs.js +29 -2
- package/dist/cost-clients/GCPClient.cjs.js.map +1 -1
- package/dist/cost-clients/GitHubClient.cjs.js +28 -2
- package/dist/cost-clients/GitHubClient.cjs.js.map +1 -1
- package/dist/cost-clients/InfraWalletClient.cjs.js +3 -0
- package/dist/cost-clients/InfraWalletClient.cjs.js.map +1 -1
- package/dist/cost-clients/MockClient.cjs.js +18 -0
- package/dist/cost-clients/MockClient.cjs.js.map +1 -1
- package/dist/cost-clients/MongoAtlasClient.cjs.js +56 -24
- package/dist/cost-clients/MongoAtlasClient.cjs.js.map +1 -1
- package/dist/schemas/AzureBilling.cjs.js +15 -7
- package/dist/schemas/AzureBilling.cjs.js.map +1 -1
- package/dist/schemas/ConfluentBilling.cjs.js +20 -8
- package/dist/schemas/ConfluentBilling.cjs.js.map +1 -1
- package/dist/schemas/DatadogBilling.cjs.js +14 -5
- package/dist/schemas/DatadogBilling.cjs.js.map +1 -1
- package/dist/schemas/ElasticBilling.cjs.js +5 -5
- package/dist/schemas/ElasticBilling.cjs.js.map +1 -1
- package/dist/schemas/GCPBilling.cjs.js +48 -18
- package/dist/schemas/GCPBilling.cjs.js.map +1 -1
- package/dist/service/functions.cjs.js +6 -0
- package/dist/service/functions.cjs.js.map +1 -1
- package/dist/tasks/fetchAndSaveCosts.cjs.js +1 -1
- package/dist/tasks/fetchAndSaveCosts.cjs.js.map +1 -1
- package/mock/mock_response.json +252 -0
- package/package.json +1 -1
|
@@ -211,24 +211,44 @@ class AwsClient extends InfraWalletClient.InfraWalletClient {
|
|
|
211
211
|
const [k, v] = tag.split(":");
|
|
212
212
|
tagKeyValues[k.trim()] = v.trim();
|
|
213
213
|
});
|
|
214
|
+
let processedRecords = 0;
|
|
215
|
+
let filteredOutZeroAmount = 0;
|
|
216
|
+
let filteredOutMissingFields = 0;
|
|
217
|
+
let filteredOutInvalidDate = 0;
|
|
218
|
+
const filteredOutTimeRange = 0;
|
|
219
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
220
|
+
const totalRecords = costResponse?.length || 0;
|
|
214
221
|
const transformedData = lodash.reduce(
|
|
215
222
|
costResponse,
|
|
216
223
|
(accumulator, row) => {
|
|
217
224
|
const rowTime = row.TimePeriod?.Start;
|
|
218
225
|
let period = "unknown";
|
|
219
|
-
if (rowTime) {
|
|
220
|
-
|
|
226
|
+
if (!rowTime) {
|
|
227
|
+
filteredOutInvalidDate++;
|
|
228
|
+
return accumulator;
|
|
221
229
|
}
|
|
230
|
+
period = functions.getBillingPeriod(query.granularity, rowTime, "YYYY-MM-DD");
|
|
222
231
|
if (row.Groups) {
|
|
223
232
|
row.Groups.forEach((group) => {
|
|
224
233
|
const accountId = group.Keys ? group.Keys[0] : "";
|
|
225
234
|
const accountName = this.accounts.get(accountId) || accountId;
|
|
235
|
+
const serviceName = group.Keys ? group.Keys[1] : "";
|
|
236
|
+
const groupMetrics = group.Metrics;
|
|
237
|
+
if (!accountId || !serviceName || !groupMetrics?.UnblendedCost?.Amount) {
|
|
238
|
+
filteredOutMissingFields++;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const amount = functions.parseCost(groupMetrics.UnblendedCost.Amount);
|
|
242
|
+
if (amount === 0) {
|
|
243
|
+
filteredOutZeroAmount++;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
226
246
|
if (!this.evaluateIntegrationFilters(accountName, integrationConfig)) {
|
|
227
247
|
return;
|
|
228
248
|
}
|
|
229
|
-
const serviceName = group.Keys ? group.Keys[1] : "";
|
|
230
249
|
const keyName = `${accountId}_${serviceName}`;
|
|
231
250
|
if (!accumulator[keyName]) {
|
|
251
|
+
uniqueKeys.add(keyName);
|
|
232
252
|
accumulator[keyName] = {
|
|
233
253
|
id: keyName,
|
|
234
254
|
account: `${this.provider}/${accountName} (${accountId})`,
|
|
@@ -240,16 +260,23 @@ class AwsClient extends InfraWalletClient.InfraWalletClient {
|
|
|
240
260
|
...tagKeyValues
|
|
241
261
|
};
|
|
242
262
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
accumulator[keyName].reports[period] = functions.parseCost(groupMetrics.UnblendedCost.Amount);
|
|
246
|
-
}
|
|
263
|
+
accumulator[keyName].reports[period] = amount;
|
|
264
|
+
processedRecords++;
|
|
247
265
|
});
|
|
248
266
|
}
|
|
249
267
|
return accumulator;
|
|
250
268
|
},
|
|
251
269
|
{}
|
|
252
270
|
);
|
|
271
|
+
this.logTransformationSummary({
|
|
272
|
+
processed: processedRecords,
|
|
273
|
+
uniqueReports: uniqueKeys.size,
|
|
274
|
+
zeroAmount: filteredOutZeroAmount,
|
|
275
|
+
missingFields: filteredOutMissingFields,
|
|
276
|
+
invalidDate: filteredOutInvalidDate,
|
|
277
|
+
timeRange: filteredOutTimeRange,
|
|
278
|
+
totalRecords
|
|
279
|
+
});
|
|
253
280
|
return Object.values(transformedData);
|
|
254
281
|
}
|
|
255
282
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AwsClient.cjs.js","sources":["../../src/cost-clients/AwsClient.ts"],"sourcesContent":["import {\n CostExplorerClient,\n Dimension,\n Expression,\n GetCostAndUsageCommand,\n GetCostAndUsageCommandInput,\n GetTagsCommand,\n GetTagsCommandInput,\n Granularity,\n GroupDefinitionType,\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 { CategoryMappingService } from '../service/CategoryMappingService';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { getBillingPeriod, parseCost, parseTags } from '../service/functions';\nimport { CostQuery, Report, TagsQuery } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { AWSGetCostAndUsageResponseSchema, AWSGetTagsResponseSchema } from '../schemas/AWSBilling';\nimport { ZodError } from 'zod';\n\nexport class AwsClient extends InfraWalletClient {\n private readonly accounts: Map<string, string> = new Map();\n\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new AwsClient(CLOUD_PROVIDER.AWS, config, database, cache, logger);\n }\n\n protected 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.provider}/${convertedName}`;\n }\n\n protected async initCloudClient(integrationConfig: Config): Promise<any> {\n const accountId = integrationConfig.getString('accountId');\n const assumedRoleName = integrationConfig.getOptionalString('assumedRoleName');\n const accessKeyId = integrationConfig.getOptionalString('accessKeyId');\n let secretAccessKey: string | undefined;\n const region = 'us-east-1';\n // Attempt to get the new, preferred key\n const newSecretAccessKey = integrationConfig.getOptionalString('secretAccessKey');\n // Attempt to get the old, deprecated key\n const oldAccessKeySecret = integrationConfig.getOptionalString('accessKeySecret');\n\n if (newSecretAccessKey) {\n // If the new key is present, use it\n secretAccessKey = newSecretAccessKey;\n } else if (oldAccessKeySecret) {\n // If the new key is NOT present, but the old one IS, use the old one and log a warning\n secretAccessKey = oldAccessKeySecret;\n this.logger.warn(`The 'accessKeySecret' configuration key is deprecated. Please rename it to 'secretAccessKey'.`);\n }\n\n if (!accessKeyId && !secretAccessKey && !assumedRoleName) {\n // No credentials provided in configuration, assuming credentials are available in the environment\n return new CostExplorerClient({ region: region });\n }\n\n let credentials = undefined;\n if (accessKeyId || secretAccessKey) {\n if (accessKeyId && secretAccessKey) {\n credentials = {\n accessKeyId: accessKeyId,\n secretAccessKey: secretAccessKey,\n };\n } else {\n throw new Error('Both accessKeyId and secretAccessKey must be provided');\n }\n }\n\n if (assumedRoleName === undefined) {\n return new CostExplorerClient({\n region: region,\n credentials: credentials,\n });\n }\n\n // Assume role\n const client = new STSClient({\n region: region,\n credentials: credentials,\n });\n const commandInput = {\n // AssumeRoleRequest\n RoleArn: `arn:aws:iam::${accountId}:role/${assumedRoleName}`,\n RoleSessionName: 'InfraWallet',\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: region,\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 private async _fetchTags(client: any, query: TagsQuery, tagKey?: string): Promise<string[]> {\n const tags: string[] = [];\n let nextPageToken = undefined;\n\n do {\n const input: GetTagsCommandInput = {\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 TagKey: tagKey,\n };\n const command = new GetTagsCommand(input);\n const response = await client.send(command);\n\n try {\n AWSGetTagsResponseSchema.parse(response);\n this.logger.debug(`AWS tags response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`AWS tags response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n for (const tag of response.Tags) {\n if (tag) {\n tags.push(tag);\n }\n }\n\n nextPageToken = response.NextPageToken;\n } while (nextPageToken);\n\n tags.sort((a, b) => a.localeCompare(b));\n return tags;\n }\n\n protected async fetchTagKeys(\n _integrationConfig: Config,\n client: any,\n query: TagsQuery,\n ): Promise<{ tagKeys: string[]; provider: CLOUD_PROVIDER }> {\n const tagKeys = await this._fetchTags(client, query);\n return { tagKeys: tagKeys, provider: this.provider };\n }\n\n protected async fetchTagValues(\n _integrationConfig: Config,\n client: any,\n query: TagsQuery,\n tagKey: string,\n ): Promise<{ tagValues: string[]; provider: CLOUD_PROVIDER }> {\n const tagValues = await this._fetchTags(client, query, tagKey);\n return { tagValues: tagValues, provider: this.provider };\n }\n\n protected async fetchCosts(_integrationConfig: 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 let filterExpression: Expression = { Dimensions: { Key: Dimension.RECORD_TYPE, Values: ['Usage'] } };\n const tags = parseTags(query.tags);\n if (tags.length) {\n let tagsExpression: Expression = {};\n\n if (tags.length === 1) {\n if (tags[0].value) {\n tagsExpression = { Tags: { Key: tags[0].key, Values: [tags[0].value] } };\n }\n } else {\n const tagList: Expression[] = [];\n for (const tag of tags) {\n if (tag.value) {\n tagList.push({ Tags: { Key: tag.key, Values: [tag.value] } });\n }\n }\n tagsExpression = { Or: tagList };\n }\n\n filterExpression = { And: [filterExpression, tagsExpression] };\n }\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: filterExpression,\n GroupBy: [\n { Type: GroupDefinitionType.DIMENSION, Key: Dimension.LINKED_ACCOUNT },\n { Type: GroupDefinitionType.DIMENSION, Key: Dimension.SERVICE },\n ],\n Metrics: ['UnblendedCost'],\n NextPageToken: nextPageToken,\n };\n\n const getCostCommand = new GetCostAndUsageCommand(input);\n const costAndUsageResponse = await client.send(getCostCommand);\n\n try {\n AWSGetCostAndUsageResponseSchema.parse(costAndUsageResponse);\n this.logger.debug(`AWS cost and usage response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`AWS cost and usage response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n // get AWS account names\n for (const accountAttributes of costAndUsageResponse.DimensionValueAttributes) {\n const accountId = accountAttributes.Value;\n const accountName = accountAttributes.Attributes.description;\n this.accounts.set(accountId, accountName);\n }\n\n costAndUsageResults = costAndUsageResults.concat(costAndUsageResponse.ResultsByTime);\n nextPageToken = costAndUsageResponse.NextPageToken;\n } while (nextPageToken);\n\n return costAndUsageResults;\n }\n\n protected async transformCostsData(\n integrationConfig: Config,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n const categoryMappingService = CategoryMappingService.getInstance();\n const tags = integrationConfig.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 period = getBillingPeriod(query.granularity, rowTime, 'YYYY-MM-DD');\n }\n if (row.Groups) {\n row.Groups.forEach((group: any) => {\n const accountId = group.Keys ? group.Keys[0] : '';\n const accountName = this.accounts.get(accountId) || accountId;\n\n if (!this.evaluateIntegrationFilters(accountName, integrationConfig)) {\n return;\n }\n\n const serviceName = group.Keys ? group.Keys[1] : '';\n const keyName = `${accountId}_${serviceName}`;\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName} (${accountId})`,\n service: this.convertServiceName(serviceName),\n category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n const groupMetrics = group.Metrics;\n\n if (groupMetrics !== undefined) {\n accumulator[keyName].reports[period] = parseCost(groupMetrics.UnblendedCost.Amount);\n }\n });\n }\n\n return accumulator;\n },\n {},\n );\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","CostExplorerClient","STSClient","AssumeRoleCommand","moment","GetTagsCommand","AWSGetTagsResponseSchema","ZodError","Dimension","parseTags","GroupDefinitionType","GetCostAndUsageCommand","AWSGetCostAndUsageResponseSchema","CategoryMappingService","reduce","getBillingPeriod","PROVIDER_TYPE","parseCost"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AwsClient.cjs.js","sources":["../../src/cost-clients/AwsClient.ts"],"sourcesContent":["import {\n CostExplorerClient,\n Dimension,\n Expression,\n GetCostAndUsageCommand,\n GetCostAndUsageCommandInput,\n GetTagsCommand,\n GetTagsCommandInput,\n Granularity,\n GroupDefinitionType,\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 { CategoryMappingService } from '../service/CategoryMappingService';\nimport { CLOUD_PROVIDER, PROVIDER_TYPE } from '../service/consts';\nimport { getBillingPeriod, parseCost, parseTags } from '../service/functions';\nimport { CostQuery, Report, TagsQuery } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { AWSGetCostAndUsageResponseSchema, AWSGetTagsResponseSchema } from '../schemas/AWSBilling';\nimport { ZodError } from 'zod';\n\nexport class AwsClient extends InfraWalletClient {\n private readonly accounts: Map<string, string> = new Map();\n\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new AwsClient(CLOUD_PROVIDER.AWS, config, database, cache, logger);\n }\n\n protected 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.provider}/${convertedName}`;\n }\n\n protected async initCloudClient(integrationConfig: Config): Promise<any> {\n const accountId = integrationConfig.getString('accountId');\n const assumedRoleName = integrationConfig.getOptionalString('assumedRoleName');\n const accessKeyId = integrationConfig.getOptionalString('accessKeyId');\n let secretAccessKey: string | undefined;\n const region = 'us-east-1';\n // Attempt to get the new, preferred key\n const newSecretAccessKey = integrationConfig.getOptionalString('secretAccessKey');\n // Attempt to get the old, deprecated key\n const oldAccessKeySecret = integrationConfig.getOptionalString('accessKeySecret');\n\n if (newSecretAccessKey) {\n // If the new key is present, use it\n secretAccessKey = newSecretAccessKey;\n } else if (oldAccessKeySecret) {\n // If the new key is NOT present, but the old one IS, use the old one and log a warning\n secretAccessKey = oldAccessKeySecret;\n this.logger.warn(`The 'accessKeySecret' configuration key is deprecated. Please rename it to 'secretAccessKey'.`);\n }\n\n if (!accessKeyId && !secretAccessKey && !assumedRoleName) {\n // No credentials provided in configuration, assuming credentials are available in the environment\n return new CostExplorerClient({ region: region });\n }\n\n let credentials = undefined;\n if (accessKeyId || secretAccessKey) {\n if (accessKeyId && secretAccessKey) {\n credentials = {\n accessKeyId: accessKeyId,\n secretAccessKey: secretAccessKey,\n };\n } else {\n throw new Error('Both accessKeyId and secretAccessKey must be provided');\n }\n }\n\n if (assumedRoleName === undefined) {\n return new CostExplorerClient({\n region: region,\n credentials: credentials,\n });\n }\n\n // Assume role\n const client = new STSClient({\n region: region,\n credentials: credentials,\n });\n const commandInput = {\n // AssumeRoleRequest\n RoleArn: `arn:aws:iam::${accountId}:role/${assumedRoleName}`,\n RoleSessionName: 'InfraWallet',\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: region,\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 private async _fetchTags(client: any, query: TagsQuery, tagKey?: string): Promise<string[]> {\n const tags: string[] = [];\n let nextPageToken = undefined;\n\n do {\n const input: GetTagsCommandInput = {\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 TagKey: tagKey,\n };\n const command = new GetTagsCommand(input);\n const response = await client.send(command);\n\n try {\n AWSGetTagsResponseSchema.parse(response);\n this.logger.debug(`AWS tags response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`AWS tags response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n for (const tag of response.Tags) {\n if (tag) {\n tags.push(tag);\n }\n }\n\n nextPageToken = response.NextPageToken;\n } while (nextPageToken);\n\n tags.sort((a, b) => a.localeCompare(b));\n return tags;\n }\n\n protected async fetchTagKeys(\n _integrationConfig: Config,\n client: any,\n query: TagsQuery,\n ): Promise<{ tagKeys: string[]; provider: CLOUD_PROVIDER }> {\n const tagKeys = await this._fetchTags(client, query);\n return { tagKeys: tagKeys, provider: this.provider };\n }\n\n protected async fetchTagValues(\n _integrationConfig: Config,\n client: any,\n query: TagsQuery,\n tagKey: string,\n ): Promise<{ tagValues: string[]; provider: CLOUD_PROVIDER }> {\n const tagValues = await this._fetchTags(client, query, tagKey);\n return { tagValues: tagValues, provider: this.provider };\n }\n\n protected async fetchCosts(_integrationConfig: 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 let filterExpression: Expression = { Dimensions: { Key: Dimension.RECORD_TYPE, Values: ['Usage'] } };\n const tags = parseTags(query.tags);\n if (tags.length) {\n let tagsExpression: Expression = {};\n\n if (tags.length === 1) {\n if (tags[0].value) {\n tagsExpression = { Tags: { Key: tags[0].key, Values: [tags[0].value] } };\n }\n } else {\n const tagList: Expression[] = [];\n for (const tag of tags) {\n if (tag.value) {\n tagList.push({ Tags: { Key: tag.key, Values: [tag.value] } });\n }\n }\n tagsExpression = { Or: tagList };\n }\n\n filterExpression = { And: [filterExpression, tagsExpression] };\n }\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: filterExpression,\n GroupBy: [\n { Type: GroupDefinitionType.DIMENSION, Key: Dimension.LINKED_ACCOUNT },\n { Type: GroupDefinitionType.DIMENSION, Key: Dimension.SERVICE },\n ],\n Metrics: ['UnblendedCost'],\n NextPageToken: nextPageToken,\n };\n\n const getCostCommand = new GetCostAndUsageCommand(input);\n const costAndUsageResponse = await client.send(getCostCommand);\n\n try {\n AWSGetCostAndUsageResponseSchema.parse(costAndUsageResponse);\n this.logger.debug(`AWS cost and usage response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`AWS cost and usage response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n // get AWS account names\n for (const accountAttributes of costAndUsageResponse.DimensionValueAttributes) {\n const accountId = accountAttributes.Value;\n const accountName = accountAttributes.Attributes.description;\n this.accounts.set(accountId, accountName);\n }\n\n costAndUsageResults = costAndUsageResults.concat(costAndUsageResponse.ResultsByTime);\n nextPageToken = costAndUsageResponse.NextPageToken;\n } while (nextPageToken);\n\n return costAndUsageResults;\n }\n\n protected async transformCostsData(\n integrationConfig: Config,\n query: CostQuery,\n costResponse: any,\n ): Promise<Report[]> {\n const categoryMappingService = CategoryMappingService.getInstance();\n const tags = integrationConfig.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 // Initialize tracking variables\n let processedRecords = 0;\n let filteredOutZeroAmount = 0;\n let filteredOutMissingFields = 0;\n let filteredOutInvalidDate = 0;\n const filteredOutTimeRange = 0;\n const uniqueKeys = new Set<string>();\n const totalRecords = costResponse?.length || 0;\n\n const transformedData = reduce(\n costResponse,\n (accumulator: { [key: string]: Report }, row) => {\n const rowTime = row.TimePeriod?.Start;\n let period = 'unknown';\n\n // Check for invalid date\n if (!rowTime) {\n filteredOutInvalidDate++;\n return accumulator;\n }\n\n period = getBillingPeriod(query.granularity, rowTime, 'YYYY-MM-DD');\n\n if (row.Groups) {\n row.Groups.forEach((group: any) => {\n const accountId = group.Keys ? group.Keys[0] : '';\n const accountName = this.accounts.get(accountId) || accountId;\n const serviceName = group.Keys ? group.Keys[1] : '';\n const groupMetrics = group.Metrics;\n\n // Check for missing fields\n if (!accountId || !serviceName || !groupMetrics?.UnblendedCost?.Amount) {\n filteredOutMissingFields++;\n return;\n }\n\n const amount = parseCost(groupMetrics.UnblendedCost.Amount);\n\n // Check for zero amount\n if (amount === 0) {\n filteredOutZeroAmount++;\n return;\n }\n\n if (!this.evaluateIntegrationFilters(accountName, integrationConfig)) {\n return;\n }\n\n const keyName = `${accountId}_${serviceName}`;\n\n if (!accumulator[keyName]) {\n uniqueKeys.add(keyName);\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName} (${accountId})`,\n service: this.convertServiceName(serviceName),\n category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n accumulator[keyName].reports[period] = amount;\n processedRecords++;\n });\n }\n\n return accumulator;\n },\n {},\n );\n\n this.logTransformationSummary({\n processed: processedRecords,\n uniqueReports: uniqueKeys.size,\n zeroAmount: filteredOutZeroAmount,\n missingFields: filteredOutMissingFields,\n invalidDate: filteredOutInvalidDate,\n timeRange: filteredOutTimeRange,\n totalRecords,\n });\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","CostExplorerClient","STSClient","AssumeRoleCommand","moment","GetTagsCommand","AWSGetTagsResponseSchema","ZodError","Dimension","parseTags","GroupDefinitionType","GetCostAndUsageCommand","AWSGetCostAndUsageResponseSchema","CategoryMappingService","reduce","getBillingPeriod","parseCost","PROVIDER_TYPE"],"mappings":"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"}
|
|
@@ -179,12 +179,28 @@ class AzureClient extends InfraWalletClient.InfraWalletClient {
|
|
|
179
179
|
const [k, v] = tag.split(":");
|
|
180
180
|
tagKeyValues[k.trim()] = v.trim();
|
|
181
181
|
});
|
|
182
|
+
let processedRecords = 0;
|
|
183
|
+
let filteredOutZeroAmount = 0;
|
|
184
|
+
let filteredOutMissingFields = 0;
|
|
185
|
+
const filteredOutInvalidDate = 0;
|
|
186
|
+
let filteredOutTimeRange = 0;
|
|
187
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
188
|
+
const totalRecords = costResponse?.length || 0;
|
|
182
189
|
const transformedData = lodash.reduce(
|
|
183
190
|
costResponse,
|
|
184
191
|
(accumulator, row) => {
|
|
185
192
|
const cost = row[0];
|
|
186
193
|
let date = row[1];
|
|
187
194
|
const serviceName = row[2];
|
|
195
|
+
if (cost === void 0 || cost === null || !date || !serviceName) {
|
|
196
|
+
filteredOutMissingFields++;
|
|
197
|
+
return accumulator;
|
|
198
|
+
}
|
|
199
|
+
const amount = functions.parseCost(cost);
|
|
200
|
+
if (amount === 0) {
|
|
201
|
+
filteredOutZeroAmount++;
|
|
202
|
+
return accumulator;
|
|
203
|
+
}
|
|
188
204
|
if (query.granularity === consts.GRANULARITY.DAILY) {
|
|
189
205
|
date = functions.getBillingPeriod(query.granularity, date.toString(), "YYYYMMDD");
|
|
190
206
|
}
|
|
@@ -193,6 +209,7 @@ class AzureClient extends InfraWalletClient.InfraWalletClient {
|
|
|
193
209
|
keyName += `->${row[i + 2]}`;
|
|
194
210
|
}
|
|
195
211
|
if (!accumulator[keyName]) {
|
|
212
|
+
uniqueKeys.add(keyName);
|
|
196
213
|
accumulator[keyName] = {
|
|
197
214
|
id: keyName,
|
|
198
215
|
account: `${this.provider}/${accountName} (${subscriptionId})`,
|
|
@@ -207,15 +224,27 @@ class AzureClient extends InfraWalletClient.InfraWalletClient {
|
|
|
207
224
|
if (!moment__default.default(date).isBefore(moment__default.default(parseInt(query.startTime, 10)))) {
|
|
208
225
|
if (query.granularity === consts.GRANULARITY.MONTHLY) {
|
|
209
226
|
const yearMonth = functions.getBillingPeriod(query.granularity, date, "YYYY-MM-DDTHH:mm:ss");
|
|
210
|
-
accumulator[keyName].reports[yearMonth] =
|
|
227
|
+
accumulator[keyName].reports[yearMonth] = amount;
|
|
211
228
|
} else {
|
|
212
|
-
accumulator[keyName].reports[date] =
|
|
229
|
+
accumulator[keyName].reports[date] = amount;
|
|
213
230
|
}
|
|
231
|
+
processedRecords++;
|
|
232
|
+
} else {
|
|
233
|
+
filteredOutTimeRange++;
|
|
214
234
|
}
|
|
215
235
|
return accumulator;
|
|
216
236
|
},
|
|
217
237
|
{}
|
|
218
238
|
);
|
|
239
|
+
this.logTransformationSummary({
|
|
240
|
+
processed: processedRecords,
|
|
241
|
+
uniqueReports: uniqueKeys.size,
|
|
242
|
+
zeroAmount: filteredOutZeroAmount,
|
|
243
|
+
missingFields: filteredOutMissingFields,
|
|
244
|
+
invalidDate: filteredOutInvalidDate,
|
|
245
|
+
timeRange: filteredOutTimeRange,
|
|
246
|
+
totalRecords
|
|
247
|
+
});
|
|
219
248
|
return Object.values(transformedData);
|
|
220
249
|
}
|
|
221
250
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AzureClient.cjs.js","sources":["../../src/cost-clients/AzureClient.ts"],"sourcesContent":["import { CostManagementClient, QueryDefinition, QueryFilter } 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 { CategoryMappingService } from '../service/CategoryMappingService';\nimport { CLOUD_PROVIDER, GRANULARITY, PROVIDER_TYPE } from '../service/consts';\nimport { getBillingPeriod, parseCost, parseTags } from '../service/functions';\nimport { CostQuery, Report, TagsQuery } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { AzureBillingResponseSchema } from '../schemas/AzureBilling';\nimport { ZodError } from 'zod';\n\nexport class AzureClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new AzureClient(CLOUD_PROVIDER.AZURE, config, database, cache, logger);\n }\n\n protected 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.provider}/${convertedName}`;\n }\n\n private 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 ClientType: 'InfraWallet',\n }),\n });\n const response = await client.pipeline.sendRequest(client, request);\n if (response.status === 200) {\n const parsedResponse = JSON.parse(response.bodyAsText || '{}');\n\n try {\n AzureBillingResponseSchema.parse(parsedResponse);\n this.logger.debug(`Azure billing response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Azure billing response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n return parsedResponse;\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 // If tagKey is specified, returns all tag values of that key.\n // Otherwise returns all available tag keys.\n private async _fetchTags(subAccountConfig: Config, client: any, query: TagsQuery, tagKey: string): Promise<string[]> {\n const subscriptionId = subAccountConfig.getString('subscriptionId');\n const url = `https://management.azure.com/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/query?api-version=2021-10-01`;\n\n const queryDefinition: QueryDefinition = {\n type: 'ActualCost',\n dataset: {\n granularity: 'None',\n grouping: [{ type: 'TagKey', name: tagKey }],\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 const tags: string[] = [];\n for (const row of allResults) {\n if (tagKey === '') {\n if (row[0] && !row[0].startsWith('hidden-')) {\n tags.push(row[0]);\n }\n } else if (row[1]) {\n tags.push(row[1]);\n }\n }\n tags.sort((a, b) => a.localeCompare(b));\n\n return tags;\n }\n\n protected 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 protected async fetchTagKeys(\n subAccountConfig: Config,\n client: any,\n query: TagsQuery,\n ): Promise<{ tagKeys: string[]; provider: CLOUD_PROVIDER }> {\n const tagKeys = await this._fetchTags(subAccountConfig, client, query, '');\n return { tagKeys: tagKeys, provider: this.provider };\n }\n\n protected async fetchTagValues(\n subAccountConfig: Config,\n client: any,\n query: TagsQuery,\n tagKey: string,\n ): Promise<{ tagValues: string[]; provider: CLOUD_PROVIDER }> {\n const tagValues = await this._fetchTags(subAccountConfig, client, query, tagKey);\n return { tagValues: tagValues, provider: this.provider };\n }\n\n protected async fetchCosts(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=2023-11-01`;\n\n const groupPairs = [{ type: 'Dimension', name: 'ServiceName' }];\n let filter: QueryFilter | undefined = undefined;\n const tags = parseTags(query.tags);\n if (tags.length) {\n if (tags.length === 1) {\n if (tags[0].value) {\n filter = {\n tags: { name: tags[0].key, operator: 'In', values: [tags[0].value] },\n };\n }\n } else {\n const tagList: QueryFilter[] = [];\n for (const tag of tags) {\n if (tag.value) {\n tagList.push({ tags: { name: tag.key, operator: 'In', values: [tag.value] } });\n }\n }\n filter = { or: tagList };\n }\n }\n\n const queryDefinition: QueryDefinition = {\n type: 'ActualCost',\n dataset: {\n granularity: query.granularity,\n aggregation: { totalCostUSD: { name: 'CostUSD', function: 'Sum' } },\n grouping: groupPairs,\n filter: filter,\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 protected async transformCostsData(subAccountConfig: Config, query: CostQuery, costResponse: any): 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 categoryMappingService = CategoryMappingService.getInstance();\n const accountName = subAccountConfig.getString('name');\n const subscriptionId = subAccountConfig.getString('subscriptionId');\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 === GRANULARITY.DAILY) {\n // 20240407 -> \"2024-04-07\"\n date = getBillingPeriod(query.granularity, date.toString(), 'YYYYMMDD');\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 account: `${this.provider}/${accountName} (${subscriptionId})`,\n service: this.convertServiceName(serviceName),\n category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n if (!moment(date).isBefore(moment(parseInt(query.startTime, 10)))) {\n if (query.granularity === GRANULARITY.MONTHLY) {\n const yearMonth = getBillingPeriod(query.granularity, date, 'YYYY-MM-DDTHH:mm:ss');\n accumulator[keyName].reports[yearMonth] = parseCost(cost);\n } else {\n accumulator[keyName].reports[date] = parseCost(cost);\n }\n }\n return accumulator;\n },\n {},\n );\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","createPipelineRequest","createHttpHeaders","AzureBillingResponseSchema","ZodError","moment","ClientSecretCredential","CostManagementClient","parseTags","CategoryMappingService","reduce","GRANULARITY","getBillingPeriod","PROVIDER_TYPE","parseCost"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AzureClient.cjs.js","sources":["../../src/cost-clients/AzureClient.ts"],"sourcesContent":["import { CostManagementClient, QueryDefinition, QueryFilter } 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 { CategoryMappingService } from '../service/CategoryMappingService';\nimport { CLOUD_PROVIDER, GRANULARITY, PROVIDER_TYPE } from '../service/consts';\nimport { getBillingPeriod, parseCost, parseTags } from '../service/functions';\nimport { CostQuery, Report, TagsQuery } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport { AzureBillingResponseSchema } from '../schemas/AzureBilling';\nimport { ZodError } from 'zod';\n\nexport class AzureClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new AzureClient(CLOUD_PROVIDER.AZURE, config, database, cache, logger);\n }\n\n protected 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.provider}/${convertedName}`;\n }\n\n private 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 ClientType: 'InfraWallet',\n }),\n });\n const response = await client.pipeline.sendRequest(client, request);\n if (response.status === 200) {\n const parsedResponse = JSON.parse(response.bodyAsText || '{}');\n\n try {\n AzureBillingResponseSchema.parse(parsedResponse);\n this.logger.debug(`Azure billing response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Azure billing response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n return parsedResponse;\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 // If tagKey is specified, returns all tag values of that key.\n // Otherwise returns all available tag keys.\n private async _fetchTags(subAccountConfig: Config, client: any, query: TagsQuery, tagKey: string): Promise<string[]> {\n const subscriptionId = subAccountConfig.getString('subscriptionId');\n const url = `https://management.azure.com/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/query?api-version=2021-10-01`;\n\n const queryDefinition: QueryDefinition = {\n type: 'ActualCost',\n dataset: {\n granularity: 'None',\n grouping: [{ type: 'TagKey', name: tagKey }],\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 const tags: string[] = [];\n for (const row of allResults) {\n if (tagKey === '') {\n if (row[0] && !row[0].startsWith('hidden-')) {\n tags.push(row[0]);\n }\n } else if (row[1]) {\n tags.push(row[1]);\n }\n }\n tags.sort((a, b) => a.localeCompare(b));\n\n return tags;\n }\n\n protected 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 protected async fetchTagKeys(\n subAccountConfig: Config,\n client: any,\n query: TagsQuery,\n ): Promise<{ tagKeys: string[]; provider: CLOUD_PROVIDER }> {\n const tagKeys = await this._fetchTags(subAccountConfig, client, query, '');\n return { tagKeys: tagKeys, provider: this.provider };\n }\n\n protected async fetchTagValues(\n subAccountConfig: Config,\n client: any,\n query: TagsQuery,\n tagKey: string,\n ): Promise<{ tagValues: string[]; provider: CLOUD_PROVIDER }> {\n const tagValues = await this._fetchTags(subAccountConfig, client, query, tagKey);\n return { tagValues: tagValues, provider: this.provider };\n }\n\n protected async fetchCosts(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=2023-11-01`;\n\n const groupPairs = [{ type: 'Dimension', name: 'ServiceName' }];\n let filter: QueryFilter | undefined = undefined;\n const tags = parseTags(query.tags);\n if (tags.length) {\n if (tags.length === 1) {\n if (tags[0].value) {\n filter = {\n tags: { name: tags[0].key, operator: 'In', values: [tags[0].value] },\n };\n }\n } else {\n const tagList: QueryFilter[] = [];\n for (const tag of tags) {\n if (tag.value) {\n tagList.push({ tags: { name: tag.key, operator: 'In', values: [tag.value] } });\n }\n }\n filter = { or: tagList };\n }\n }\n\n const queryDefinition: QueryDefinition = {\n type: 'ActualCost',\n dataset: {\n granularity: query.granularity,\n aggregation: { totalCostUSD: { name: 'CostUSD', function: 'Sum' } },\n grouping: groupPairs,\n filter: filter,\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 protected async transformCostsData(subAccountConfig: Config, query: CostQuery, costResponse: any): 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 categoryMappingService = CategoryMappingService.getInstance();\n const accountName = subAccountConfig.getString('name');\n const subscriptionId = subAccountConfig.getString('subscriptionId');\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 // Initialize tracking variables\n let processedRecords = 0;\n let filteredOutZeroAmount = 0;\n let filteredOutMissingFields = 0;\n const filteredOutInvalidDate = 0;\n let filteredOutTimeRange = 0;\n const uniqueKeys = new Set<string>();\n const totalRecords = costResponse?.length || 0;\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 // Check for missing fields\n if (cost === undefined || cost === null || !date || !serviceName) {\n filteredOutMissingFields++;\n return accumulator;\n }\n\n const amount = parseCost(cost);\n\n // Check for zero amount\n if (amount === 0) {\n filteredOutZeroAmount++;\n return accumulator;\n }\n\n if (query.granularity === GRANULARITY.DAILY) {\n // 20240407 -> \"2024-04-07\"\n date = getBillingPeriod(query.granularity, date.toString(), 'YYYYMMDD');\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 uniqueKeys.add(keyName);\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName} (${subscriptionId})`,\n service: this.convertServiceName(serviceName),\n category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...tagKeyValues,\n };\n }\n\n if (!moment(date).isBefore(moment(parseInt(query.startTime, 10)))) {\n if (query.granularity === GRANULARITY.MONTHLY) {\n const yearMonth = getBillingPeriod(query.granularity, date, 'YYYY-MM-DDTHH:mm:ss');\n accumulator[keyName].reports[yearMonth] = amount;\n } else {\n accumulator[keyName].reports[date] = amount;\n }\n processedRecords++;\n } else {\n filteredOutTimeRange++;\n }\n\n return accumulator;\n },\n {},\n );\n\n this.logTransformationSummary({\n processed: processedRecords,\n uniqueReports: uniqueKeys.size,\n zeroAmount: filteredOutZeroAmount,\n missingFields: filteredOutMissingFields,\n invalidDate: filteredOutInvalidDate,\n timeRange: filteredOutTimeRange,\n totalRecords,\n });\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","createPipelineRequest","createHttpHeaders","AzureBillingResponseSchema","ZodError","moment","ClientSecretCredential","CostManagementClient","parseTags","CategoryMappingService","reduce","parseCost","GRANULARITY","getBillingPeriod","PROVIDER_TYPE"],"mappings":"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"}
|
|
@@ -282,13 +282,36 @@ class ConfluentClient extends InfraWalletClient.InfraWalletClient {
|
|
|
282
282
|
this.logger.warn("No valid cost data to transform");
|
|
283
283
|
return [];
|
|
284
284
|
}
|
|
285
|
+
this.logger.debug(`Starting transformation of ${costResponse.data.length} Confluent cost records`);
|
|
286
|
+
if (costResponse.data.length > 0) {
|
|
287
|
+
const sampleRecord = costResponse.data[0];
|
|
288
|
+
this.logger.debug(`Sample Confluent record structure: ${JSON.stringify(Object.keys(sampleRecord))}`);
|
|
289
|
+
this.logger.debug(
|
|
290
|
+
`Sample record - start_date: ${sampleRecord.start_date}, line_type: ${sampleRecord.line_type}, price: ${sampleRecord.price}, amount: ${sampleRecord.amount}, original_amount: ${sampleRecord.original_amount}, discount_amount: ${sampleRecord.discount_amount}, product: ${sampleRecord.product}, network_access_type: ${sampleRecord.network_access_type}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
let filteredOutZeroAmount = 0;
|
|
294
|
+
let filteredOutMissingFields = 0;
|
|
295
|
+
let filteredOutInvalidDate = 0;
|
|
296
|
+
let filteredOutTimeRange = 0;
|
|
297
|
+
let processedRecords = 0;
|
|
298
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
299
|
+
const uniqueServices = /* @__PURE__ */ new Set();
|
|
300
|
+
const uniqueResources = /* @__PURE__ */ new Set();
|
|
285
301
|
const transformedData = costResponse.data.reduce((accumulator, line) => {
|
|
286
|
-
const amount = parseFloat(line.amount) || 0;
|
|
302
|
+
const amount = parseFloat(line.amount) || parseFloat(line.price) || 0;
|
|
287
303
|
if (amount === 0) {
|
|
304
|
+
filteredOutZeroAmount++;
|
|
305
|
+
return accumulator;
|
|
306
|
+
}
|
|
307
|
+
if (!line.start_date || !line.line_type || !line.amount && !line.price) {
|
|
308
|
+
filteredOutMissingFields++;
|
|
288
309
|
return accumulator;
|
|
289
310
|
}
|
|
290
311
|
const parsedStartDate = moment__default.default(line.start_date);
|
|
291
312
|
if (!parsedStartDate.isValid()) {
|
|
313
|
+
filteredOutInvalidDate++;
|
|
314
|
+
this.logger.debug(`Invalid start_date: ${line.start_date}`);
|
|
292
315
|
return accumulator;
|
|
293
316
|
}
|
|
294
317
|
let billingPeriod = void 0;
|
|
@@ -297,14 +320,20 @@ class ConfluentClient extends InfraWalletClient.InfraWalletClient {
|
|
|
297
320
|
} else {
|
|
298
321
|
billingPeriod = parsedStartDate.format("YYYY-MM-DD");
|
|
299
322
|
}
|
|
300
|
-
const
|
|
323
|
+
const baseServiceName = this.capitalizeWords(line.line_type);
|
|
324
|
+
const productName = line.product ? this.capitalizeWords(line.product) : null;
|
|
325
|
+
const serviceName = productName && productName !== baseServiceName ? `${productName} ${baseServiceName}` : baseServiceName;
|
|
301
326
|
const resourceName = line.resource?.display_name || "Unknown";
|
|
302
327
|
const envDisplayName = line.envDisplayName || "Unknown";
|
|
328
|
+
const networkAccessType = line.network_access_type;
|
|
329
|
+
uniqueServices.add(serviceName);
|
|
330
|
+
uniqueResources.add(resourceName);
|
|
303
331
|
const keyName = `${accountName}->${categoryMappingService.getCategoryByServiceName(
|
|
304
332
|
this.provider,
|
|
305
333
|
serviceName
|
|
306
334
|
)}->${resourceName}`;
|
|
307
335
|
if (!accumulator[keyName]) {
|
|
336
|
+
uniqueKeys.add(keyName);
|
|
308
337
|
accumulator[keyName] = {
|
|
309
338
|
id: keyName,
|
|
310
339
|
account: `${this.provider}/${accountName}`,
|
|
@@ -315,14 +344,33 @@ class ConfluentClient extends InfraWalletClient.InfraWalletClient {
|
|
|
315
344
|
reports: {},
|
|
316
345
|
...{ project: envDisplayName },
|
|
317
346
|
...{ cluster: resourceName },
|
|
347
|
+
...networkAccessType && { networkAccessType },
|
|
318
348
|
...tagKeyValues
|
|
319
349
|
};
|
|
320
350
|
}
|
|
321
351
|
if (!moment__default.default(billingPeriod).isBefore(moment__default.default(parseInt(query.startTime, 10)))) {
|
|
322
352
|
accumulator[keyName].reports[billingPeriod] = (accumulator[keyName].reports[billingPeriod] || 0) + amount;
|
|
353
|
+
processedRecords++;
|
|
354
|
+
} else {
|
|
355
|
+
filteredOutTimeRange++;
|
|
323
356
|
}
|
|
324
357
|
return accumulator;
|
|
325
358
|
}, {});
|
|
359
|
+
this.logTransformationSummary({
|
|
360
|
+
processed: processedRecords,
|
|
361
|
+
uniqueReports: uniqueKeys.size,
|
|
362
|
+
zeroAmount: filteredOutZeroAmount,
|
|
363
|
+
missingFields: filteredOutMissingFields,
|
|
364
|
+
invalidDate: filteredOutInvalidDate,
|
|
365
|
+
timeRange: filteredOutTimeRange,
|
|
366
|
+
totalRecords: costResponse.data.length
|
|
367
|
+
});
|
|
368
|
+
const keyArray = Array.from(uniqueKeys);
|
|
369
|
+
if (keyArray.length > 0) {
|
|
370
|
+
this.logger.info(`Unique services found: ${Array.from(uniqueServices).slice(0, 10).join(", ")}`);
|
|
371
|
+
this.logger.info(`Unique resources found: ${Array.from(uniqueResources).slice(0, 10).join(", ")}`);
|
|
372
|
+
this.logger.debug(`Sample unique keys: ${keyArray.slice(0, 5).join(", ")}`);
|
|
373
|
+
}
|
|
326
374
|
return Object.values(transformedData);
|
|
327
375
|
}
|
|
328
376
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfluentClient.cjs.js","sources":["../../src/cost-clients/ConfluentClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport moment from 'moment';\nimport { CategoryMappingService } from '../service/CategoryMappingService';\nimport {\n CLOUD_PROVIDER,\n PROVIDER_TYPE,\n NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS,\n GRANULARITY,\n} from '../service/consts';\nimport { cryptoRandom } from '../service/crypto';\nimport { ConfluentEnvironmentSchema, ConfluentBillingResponseSchema } from '../schemas/ConfluentBilling';\nimport { ZodError } from 'zod';\n\nexport class ConfluentClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new ConfluentClient(CLOUD_PROVIDER.CONFLUENT, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Confluent'];\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.provider}/${convertedName}`;\n }\n\n private capitalizeWords(str: string): string {\n return str\n .toLowerCase()\n .split('_')\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n }\n\n private async fetchEnvDisplayName(client: any, envId: string, retryCount = 0): Promise<string> {\n try {\n const url = `https://api.confluent.cloud/org/v2/environments/${envId}`;\n const response = await fetch(url, {\n method: 'GET',\n headers: client.headers,\n });\n\n if (response.status === 429 && retryCount < 3) {\n // Apply exponential backoff for rate limiting\n const retryAfter = parseInt(response.headers.get('retry-after') || '5', 10);\n const backoffTime = Math.min(30, retryAfter * Math.pow(2, retryCount));\n this.logger.info(\n `Rate limited when fetching environment name for ${envId}, backing off for ${backoffTime} seconds...`,\n );\n await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));\n return this.fetchEnvDisplayName(client, envId, retryCount + 1);\n }\n\n if (!response.ok) {\n this.logger.warn(`Failed to fetch environment name for ${envId}: ${response.statusText}`);\n return envId;\n }\n\n const jsonResponse = await response.json();\n\n try {\n ConfluentEnvironmentSchema.parse(jsonResponse);\n this.logger.debug(`Confluent environment response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Confluent environment response validation failed: ${error.message}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n return jsonResponse.display_name;\n } catch (error) {\n this.logger.warn(`Error fetching environment name for ${envId}: ${error.message}`);\n return envId;\n }\n }\n\n protected async initCloudClient(subAccountConfig: Config): Promise<any> {\n const apiKey = subAccountConfig.getString('apiKey');\n const apiSecret = subAccountConfig.getString('apiSecret');\n const auth = `${apiKey}:${apiSecret}`;\n\n const client = {\n headers: {\n Authorization: `Basic ${Buffer.from(auth).toString('base64')}`,\n 'Content-Type': 'application/json',\n },\n name: subAccountConfig.getString('name'),\n };\n\n return client;\n }\n\n private async fetchCostWithRetry(url: string, client: any, retryCount = 0, maxRetries = 5): Promise<any> {\n try {\n this.logger.debug(`Fetching Confluent costs from URL: ${url}, attempt ${retryCount + 1}`);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: client.headers,\n });\n\n if (response.status === 403) {\n const errorText = await response.text();\n this.logger.error(`Failed to fetch Confluent costs: 403 Forbidden - ${errorText}`);\n throw new Error(`Authorization failed: ${errorText}`);\n }\n\n if (response.status === 429 && retryCount < maxRetries) {\n // Apply exponential backoff with jitter for rate limiting\n const retryAfter = parseInt(response.headers.get('retry-after') || '30', 10);\n const jitter = cryptoRandom() * 2;\n const backoffTime = Math.min(120, retryAfter * Math.pow(1.5, retryCount) * jitter);\n this.logger.warn(`Rate limited, backing off for ${Math.ceil(backoffTime)} seconds...`);\n await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));\n return this.fetchCostWithRetry(url, client, retryCount + 1, maxRetries);\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const jsonResponse = await response.json();\n\n try {\n ConfluentBillingResponseSchema.parse(jsonResponse);\n this.logger.debug(`Confluent billing response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Confluent billing response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n return jsonResponse;\n } catch (error) {\n if (retryCount < maxRetries) {\n // Apply exponential backoff for general errors\n const backoffTime = Math.min(60, Math.pow(2, retryCount) * 3);\n this.logger.warn(`Error fetching costs, retrying in ${backoffTime} seconds: ${error.message}`);\n await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));\n return this.fetchCostWithRetry(url, client, retryCount + 1, maxRetries);\n }\n throw error;\n }\n }\n\n protected async fetchCosts(_subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n // Confluent API limits:\n // 1. Can only fetch 1 month at a time\n // 2. Can only go back exactly the number of months defined in NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS\n const LOOKBACK_MONTHS = NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS[CLOUD_PROVIDER.CONFLUENT];\n\n // Calculate the earliest date we can fetch\n const now = moment();\n const earliestAllowed = now.clone().subtract(LOOKBACK_MONTHS, 'months').startOf('month');\n\n // Convert query dates to moment objects\n const requestStartDate = moment(parseInt(query.startTime, 10));\n const requestEndDate = moment(parseInt(query.endTime, 10));\n\n // Adjust start date to be within the allowed range\n let startDate = requestStartDate.clone();\n if (startDate.isBefore(earliestAllowed)) {\n this.logger.info(\n `Confluent API only allows lookback of ${LOOKBACK_MONTHS} months. Adjusting start date from ${startDate.format('YYYY-MM-DD')} to ${earliestAllowed.format('YYYY-MM-DD')}`,\n );\n startDate = earliestAllowed.clone();\n }\n\n // Ensure we don't go past the requested end date\n const endDate = moment.min(requestEndDate, now);\n\n // Build monthly time ranges\n const monthlyRanges = [];\n const currentMonth = startDate.clone().startOf('month');\n\n while (currentMonth.isBefore(endDate) || currentMonth.isSame(endDate, 'month')) {\n const monthStart = currentMonth.clone().startOf('month');\n const monthEnd = moment.min(currentMonth.clone().endOf('month'), endDate);\n\n monthlyRanges.push({\n start: monthStart,\n end: monthEnd,\n });\n\n currentMonth.add(1, 'month');\n }\n\n this.logger.info(\n `Fetching Confluent costs for ${monthlyRanges.length} months from ${startDate.format('YYYY-MM')} to ${endDate.format('YYYY-MM')}`,\n );\n\n // Maximum number of concurrent requests to avoid overwhelming the API\n const maxConcurrentRequests = 2;\n const maxRetries = 4; // Maximum number of retries for a single month\n\n let aggregatedData: any[] = [];\n const envIdToName: Record<string, string> = {};\n\n // Process test request to check API access and potentially get first month's data\n try {\n // Use the most recent month for the test request as it's more likely to succeed\n const latestRange = monthlyRanges[monthlyRanges.length - 1];\n const testUrl = `https://api.confluent.cloud/billing/v1/costs?start_date=${latestRange.start.format(\n 'YYYY-MM-DD',\n )}&end_date=${latestRange.end.clone().add(1, 'd').format('YYYY-MM-DD')}`;\n\n this.logger.debug(`Testing Confluent API access for ${latestRange.start.format('YYYY-MM')}`);\n\n const testResponse = await this.fetchCostWithRetry(testUrl, client, 0, 2);\n\n if (testResponse.data && testResponse.data.length > 0) {\n // Process environment names for this data\n const envIds = Array.from(\n new Set(\n testResponse.data.map((item: any) => item.resource?.environment?.id).filter((id: any) => id !== undefined),\n ),\n );\n\n if (envIds.length > 0) {\n // Fetch environment names\n for (const envId of envIds) {\n if (typeof envId === 'string') {\n try {\n const name = await this.fetchEnvDisplayName(client, envId);\n envIdToName[envId] = name;\n } catch (error) {\n this.logger.warn(`Error fetching name for environment ${envId}: ${error.message}`);\n envIdToName[envId] = envId; // Fallback to using the ID\n }\n }\n }\n\n const dataWithEnvNames = testResponse.data\n .filter((item: any) => item.resource?.environment?.id)\n .map((item: any) => {\n const envId = item.resource.environment.id;\n return {\n ...item,\n envDisplayName: envIdToName[envId] || 'Unknown',\n };\n });\n\n aggregatedData = aggregatedData.concat(dataWithEnvNames);\n this.logger.info(`Successfully fetched costs for ${latestRange.start.format('YYYY-MM')}`);\n }\n\n // Remove this month from the ranges to process since we already got it\n monthlyRanges.pop();\n }\n } catch (error) {\n this.logger.error(`Error testing Confluent API access: ${error.message}`);\n }\n\n // Process all remaining months in batches to respect rate limits\n // This uses a sliding window approach to process months in parallel but with a limit\n for (let i = 0; i < monthlyRanges.length; i += maxConcurrentRequests) {\n const batch = monthlyRanges.slice(i, i + maxConcurrentRequests);\n\n try {\n const batchResults = await Promise.all(\n batch.map(async range => {\n const url = `https://api.confluent.cloud/billing/v1/costs?start_date=${range.start.format(\n 'YYYY-MM-DD',\n )}&end_date=${range.end.clone().add(1, 'd').format('YYYY-MM-DD')}`;\n\n try {\n const response = await this.fetchCostWithRetry(url, client, 0, maxRetries);\n\n this.logger.info(`Successfully fetched costs for ${range.start.format('YYYY-MM')}`);\n\n return {\n month: range.start.format('YYYY-MM'),\n data: response.data || [],\n };\n } catch (error) {\n this.logger.error(\n `Failed to fetch costs for ${range.start.format('YYYY-MM')} after multiple retries: ${error.message}`,\n );\n return {\n month: range.start.format('YYYY-MM'),\n data: [],\n };\n }\n }),\n );\n\n // Process batch results\n for (const result of batchResults) {\n if (result.data.length > 0) {\n // Extract environment IDs from this batch\n const envIds = Array.from(\n new Set(\n result.data\n .map((item: any) => item.resource?.environment?.id)\n .filter((id: any): id is string => typeof id === 'string'),\n ),\n );\n\n // Fetch any new environment names\n for (const envId of envIds) {\n if (typeof envId === 'string' && !(envId in envIdToName)) {\n // Add slight delay between env name requests\n await new Promise(resolve => setTimeout(resolve, 100));\n try {\n const name = await this.fetchEnvDisplayName(client, envId);\n envIdToName[envId] = name;\n } catch (error) {\n this.logger.warn(`Error fetching name for environment ${envId}: ${error.message}`);\n envIdToName[envId] = envId; // Fallback to using the ID\n }\n }\n }\n\n // Add environment names to the data\n const dataWithEnvNames = result.data\n .filter((item: any) => item.resource?.environment?.id)\n .map((item: any) => {\n const envId = item.resource.environment.id;\n return {\n ...item,\n envDisplayName: envIdToName[envId] || 'Unknown',\n };\n });\n\n aggregatedData = aggregatedData.concat(dataWithEnvNames);\n }\n }\n\n // Add a small delay between batches to help avoid rate limiting\n if (i + maxConcurrentRequests < monthlyRanges.length) {\n await new Promise(resolve => setTimeout(resolve, 1000));\n }\n } catch (error) {\n this.logger.error(`Error processing batch of months: ${error.message}`);\n }\n }\n\n // Log a summary of what we got\n if (aggregatedData.length === 0) {\n this.logger.error(`No cost data could be fetched from Confluent API. Check API key permissions.`);\n } else {\n this.logger.info(`Successfully fetched ${aggregatedData.length} cost entries from Confluent API.`);\n }\n\n return {\n data: aggregatedData,\n };\n }\n\n protected async transformCostsData(subAccountConfig: Config, query: CostQuery, costResponse: any): Promise<Report[]> {\n const categoryMappingService = CategoryMappingService.getInstance();\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 // Handle empty or invalid data\n if (!costResponse || !costResponse.data || !Array.isArray(costResponse.data)) {\n this.logger.warn('No valid cost data to transform');\n return [];\n }\n\n const transformedData = costResponse.data.reduce((accumulator: { [key: string]: Report }, line: any) => {\n const amount = parseFloat(line.amount) || 0;\n\n if (amount === 0) {\n return accumulator;\n }\n\n const parsedStartDate = moment(line.start_date);\n\n if (!parsedStartDate.isValid()) {\n return accumulator;\n }\n\n let billingPeriod = undefined;\n if (query.granularity === GRANULARITY.MONTHLY) {\n billingPeriod = parsedStartDate.format('YYYY-MM');\n } else {\n billingPeriod = parsedStartDate.format('YYYY-MM-DD');\n }\n\n const serviceName = this.capitalizeWords(line.line_type);\n const resourceName = line.resource?.display_name || 'Unknown';\n const envDisplayName = line.envDisplayName || 'Unknown';\n\n const keyName = `${accountName}->${categoryMappingService.getCategoryByServiceName(\n this.provider,\n serviceName,\n )}->${resourceName}`;\n\n if (!accumulator[keyName]) {\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName}`,\n service: this.convertServiceName(serviceName),\n category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...{ project: envDisplayName },\n ...{ cluster: resourceName },\n ...tagKeyValues,\n };\n }\n\n if (!moment(billingPeriod).isBefore(moment(parseInt(query.startTime, 10)))) {\n accumulator[keyName].reports[billingPeriod] = (accumulator[keyName].reports[billingPeriod] || 0) + amount;\n }\n\n return accumulator;\n }, {});\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","ConfluentEnvironmentSchema","ZodError","cryptoRandom","ConfluentBillingResponseSchema","NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS","moment","CategoryMappingService","GRANULARITY","PROVIDER_TYPE"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConfluentClient.cjs.js","sources":["../../src/cost-clients/ConfluentClient.ts"],"sourcesContent":["import { CacheService, DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { CostQuery, Report } from '../service/types';\nimport { InfraWalletClient } from './InfraWalletClient';\nimport moment from 'moment';\nimport { CategoryMappingService } from '../service/CategoryMappingService';\nimport {\n CLOUD_PROVIDER,\n PROVIDER_TYPE,\n NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS,\n GRANULARITY,\n} from '../service/consts';\nimport { cryptoRandom } from '../service/crypto';\nimport { ConfluentEnvironmentSchema, ConfluentBillingResponseSchema } from '../schemas/ConfluentBilling';\nimport { ZodError } from 'zod';\n\nexport class ConfluentClient extends InfraWalletClient {\n static create(config: Config, database: DatabaseService, cache: CacheService, logger: LoggerService) {\n return new ConfluentClient(CLOUD_PROVIDER.CONFLUENT, config, database, cache, logger);\n }\n\n protected convertServiceName(serviceName: string): string {\n let convertedName = serviceName;\n\n const prefixes = ['Confluent'];\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.provider}/${convertedName}`;\n }\n\n private capitalizeWords(str: string): string {\n return str\n .toLowerCase()\n .split('_')\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n }\n\n private async fetchEnvDisplayName(client: any, envId: string, retryCount = 0): Promise<string> {\n try {\n const url = `https://api.confluent.cloud/org/v2/environments/${envId}`;\n const response = await fetch(url, {\n method: 'GET',\n headers: client.headers,\n });\n\n if (response.status === 429 && retryCount < 3) {\n // Apply exponential backoff for rate limiting\n const retryAfter = parseInt(response.headers.get('retry-after') || '5', 10);\n const backoffTime = Math.min(30, retryAfter * Math.pow(2, retryCount));\n this.logger.info(\n `Rate limited when fetching environment name for ${envId}, backing off for ${backoffTime} seconds...`,\n );\n await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));\n return this.fetchEnvDisplayName(client, envId, retryCount + 1);\n }\n\n if (!response.ok) {\n this.logger.warn(`Failed to fetch environment name for ${envId}: ${response.statusText}`);\n return envId;\n }\n\n const jsonResponse = await response.json();\n\n try {\n ConfluentEnvironmentSchema.parse(jsonResponse);\n this.logger.debug(`Confluent environment response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Confluent environment response validation failed: ${error.message}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n return jsonResponse.display_name;\n } catch (error) {\n this.logger.warn(`Error fetching environment name for ${envId}: ${error.message}`);\n return envId;\n }\n }\n\n protected async initCloudClient(subAccountConfig: Config): Promise<any> {\n const apiKey = subAccountConfig.getString('apiKey');\n const apiSecret = subAccountConfig.getString('apiSecret');\n const auth = `${apiKey}:${apiSecret}`;\n\n const client = {\n headers: {\n Authorization: `Basic ${Buffer.from(auth).toString('base64')}`,\n 'Content-Type': 'application/json',\n },\n name: subAccountConfig.getString('name'),\n };\n\n return client;\n }\n\n private async fetchCostWithRetry(url: string, client: any, retryCount = 0, maxRetries = 5): Promise<any> {\n try {\n this.logger.debug(`Fetching Confluent costs from URL: ${url}, attempt ${retryCount + 1}`);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: client.headers,\n });\n\n if (response.status === 403) {\n const errorText = await response.text();\n this.logger.error(`Failed to fetch Confluent costs: 403 Forbidden - ${errorText}`);\n throw new Error(`Authorization failed: ${errorText}`);\n }\n\n if (response.status === 429 && retryCount < maxRetries) {\n // Apply exponential backoff with jitter for rate limiting\n const retryAfter = parseInt(response.headers.get('retry-after') || '30', 10);\n const jitter = cryptoRandom() * 2;\n const backoffTime = Math.min(120, retryAfter * Math.pow(1.5, retryCount) * jitter);\n this.logger.warn(`Rate limited, backing off for ${Math.ceil(backoffTime)} seconds...`);\n await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));\n return this.fetchCostWithRetry(url, client, retryCount + 1, maxRetries);\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const jsonResponse = await response.json();\n\n try {\n ConfluentBillingResponseSchema.parse(jsonResponse);\n this.logger.debug(`Confluent billing response validation passed`);\n } catch (error) {\n if (error instanceof ZodError) {\n this.logger.warn(`Confluent billing response validation failed: ${error.message}`);\n this.logger.debug(`Sample validation errors: ${JSON.stringify(error.errors.slice(0, 3))}`);\n } else {\n this.logger.warn(`Unexpected validation error: ${error.message}`);\n }\n }\n\n return jsonResponse;\n } catch (error) {\n if (retryCount < maxRetries) {\n // Apply exponential backoff for general errors\n const backoffTime = Math.min(60, Math.pow(2, retryCount) * 3);\n this.logger.warn(`Error fetching costs, retrying in ${backoffTime} seconds: ${error.message}`);\n await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));\n return this.fetchCostWithRetry(url, client, retryCount + 1, maxRetries);\n }\n throw error;\n }\n }\n\n protected async fetchCosts(_subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {\n // Confluent API limits:\n // 1. Can only fetch 1 month at a time\n // 2. Can only go back exactly the number of months defined in NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS\n const LOOKBACK_MONTHS = NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS[CLOUD_PROVIDER.CONFLUENT];\n\n // Calculate the earliest date we can fetch\n const now = moment();\n const earliestAllowed = now.clone().subtract(LOOKBACK_MONTHS, 'months').startOf('month');\n\n // Convert query dates to moment objects\n const requestStartDate = moment(parseInt(query.startTime, 10));\n const requestEndDate = moment(parseInt(query.endTime, 10));\n\n // Adjust start date to be within the allowed range\n let startDate = requestStartDate.clone();\n if (startDate.isBefore(earliestAllowed)) {\n this.logger.info(\n `Confluent API only allows lookback of ${LOOKBACK_MONTHS} months. Adjusting start date from ${startDate.format('YYYY-MM-DD')} to ${earliestAllowed.format('YYYY-MM-DD')}`,\n );\n startDate = earliestAllowed.clone();\n }\n\n // Ensure we don't go past the requested end date\n const endDate = moment.min(requestEndDate, now);\n\n // Build monthly time ranges\n const monthlyRanges = [];\n const currentMonth = startDate.clone().startOf('month');\n\n while (currentMonth.isBefore(endDate) || currentMonth.isSame(endDate, 'month')) {\n const monthStart = currentMonth.clone().startOf('month');\n const monthEnd = moment.min(currentMonth.clone().endOf('month'), endDate);\n\n monthlyRanges.push({\n start: monthStart,\n end: monthEnd,\n });\n\n currentMonth.add(1, 'month');\n }\n\n this.logger.info(\n `Fetching Confluent costs for ${monthlyRanges.length} months from ${startDate.format('YYYY-MM')} to ${endDate.format('YYYY-MM')}`,\n );\n\n // Maximum number of concurrent requests to avoid overwhelming the API\n const maxConcurrentRequests = 2;\n const maxRetries = 4; // Maximum number of retries for a single month\n\n let aggregatedData: any[] = [];\n const envIdToName: Record<string, string> = {};\n\n // Process test request to check API access and potentially get first month's data\n try {\n // Use the most recent month for the test request as it's more likely to succeed\n const latestRange = monthlyRanges[monthlyRanges.length - 1];\n const testUrl = `https://api.confluent.cloud/billing/v1/costs?start_date=${latestRange.start.format(\n 'YYYY-MM-DD',\n )}&end_date=${latestRange.end.clone().add(1, 'd').format('YYYY-MM-DD')}`;\n\n this.logger.debug(`Testing Confluent API access for ${latestRange.start.format('YYYY-MM')}`);\n\n const testResponse = await this.fetchCostWithRetry(testUrl, client, 0, 2);\n\n if (testResponse.data && testResponse.data.length > 0) {\n // Process environment names for this data\n const envIds = Array.from(\n new Set(\n testResponse.data.map((item: any) => item.resource?.environment?.id).filter((id: any) => id !== undefined),\n ),\n );\n\n if (envIds.length > 0) {\n // Fetch environment names\n for (const envId of envIds) {\n if (typeof envId === 'string') {\n try {\n const name = await this.fetchEnvDisplayName(client, envId);\n envIdToName[envId] = name;\n } catch (error) {\n this.logger.warn(`Error fetching name for environment ${envId}: ${error.message}`);\n envIdToName[envId] = envId; // Fallback to using the ID\n }\n }\n }\n\n const dataWithEnvNames = testResponse.data\n .filter((item: any) => item.resource?.environment?.id)\n .map((item: any) => {\n const envId = item.resource.environment.id;\n return {\n ...item,\n envDisplayName: envIdToName[envId] || 'Unknown',\n };\n });\n\n aggregatedData = aggregatedData.concat(dataWithEnvNames);\n this.logger.info(`Successfully fetched costs for ${latestRange.start.format('YYYY-MM')}`);\n }\n\n // Remove this month from the ranges to process since we already got it\n monthlyRanges.pop();\n }\n } catch (error) {\n this.logger.error(`Error testing Confluent API access: ${error.message}`);\n }\n\n // Process all remaining months in batches to respect rate limits\n // This uses a sliding window approach to process months in parallel but with a limit\n for (let i = 0; i < monthlyRanges.length; i += maxConcurrentRequests) {\n const batch = monthlyRanges.slice(i, i + maxConcurrentRequests);\n\n try {\n const batchResults = await Promise.all(\n batch.map(async range => {\n const url = `https://api.confluent.cloud/billing/v1/costs?start_date=${range.start.format(\n 'YYYY-MM-DD',\n )}&end_date=${range.end.clone().add(1, 'd').format('YYYY-MM-DD')}`;\n\n try {\n const response = await this.fetchCostWithRetry(url, client, 0, maxRetries);\n\n this.logger.info(`Successfully fetched costs for ${range.start.format('YYYY-MM')}`);\n\n return {\n month: range.start.format('YYYY-MM'),\n data: response.data || [],\n };\n } catch (error) {\n this.logger.error(\n `Failed to fetch costs for ${range.start.format('YYYY-MM')} after multiple retries: ${error.message}`,\n );\n return {\n month: range.start.format('YYYY-MM'),\n data: [],\n };\n }\n }),\n );\n\n // Process batch results\n for (const result of batchResults) {\n if (result.data.length > 0) {\n // Extract environment IDs from this batch\n const envIds = Array.from(\n new Set(\n result.data\n .map((item: any) => item.resource?.environment?.id)\n .filter((id: any): id is string => typeof id === 'string'),\n ),\n );\n\n // Fetch any new environment names\n for (const envId of envIds) {\n if (typeof envId === 'string' && !(envId in envIdToName)) {\n // Add slight delay between env name requests\n await new Promise(resolve => setTimeout(resolve, 100));\n try {\n const name = await this.fetchEnvDisplayName(client, envId);\n envIdToName[envId] = name;\n } catch (error) {\n this.logger.warn(`Error fetching name for environment ${envId}: ${error.message}`);\n envIdToName[envId] = envId; // Fallback to using the ID\n }\n }\n }\n\n // Add environment names to the data\n const dataWithEnvNames = result.data\n .filter((item: any) => item.resource?.environment?.id)\n .map((item: any) => {\n const envId = item.resource.environment.id;\n return {\n ...item,\n envDisplayName: envIdToName[envId] || 'Unknown',\n };\n });\n\n aggregatedData = aggregatedData.concat(dataWithEnvNames);\n }\n }\n\n // Add a small delay between batches to help avoid rate limiting\n if (i + maxConcurrentRequests < monthlyRanges.length) {\n await new Promise(resolve => setTimeout(resolve, 1000));\n }\n } catch (error) {\n this.logger.error(`Error processing batch of months: ${error.message}`);\n }\n }\n\n // Log a summary of what we got\n if (aggregatedData.length === 0) {\n this.logger.error(`No cost data could be fetched from Confluent API. Check API key permissions.`);\n } else {\n this.logger.info(`Successfully fetched ${aggregatedData.length} cost entries from Confluent API.`);\n }\n\n return {\n data: aggregatedData,\n };\n }\n\n protected async transformCostsData(subAccountConfig: Config, query: CostQuery, costResponse: any): Promise<Report[]> {\n const categoryMappingService = CategoryMappingService.getInstance();\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 // Handle empty or invalid data\n if (!costResponse || !costResponse.data || !Array.isArray(costResponse.data)) {\n this.logger.warn('No valid cost data to transform');\n return [];\n }\n\n this.logger.debug(`Starting transformation of ${costResponse.data.length} Confluent cost records`);\n\n // Log first few records structure for debugging\n if (costResponse.data.length > 0) {\n const sampleRecord = costResponse.data[0];\n this.logger.debug(`Sample Confluent record structure: ${JSON.stringify(Object.keys(sampleRecord))}`);\n this.logger.debug(\n `Sample record - start_date: ${sampleRecord.start_date}, line_type: ${sampleRecord.line_type}, price: ${sampleRecord.price}, amount: ${sampleRecord.amount}, original_amount: ${sampleRecord.original_amount}, discount_amount: ${sampleRecord.discount_amount}, product: ${sampleRecord.product}, network_access_type: ${sampleRecord.network_access_type}`,\n );\n }\n\n let filteredOutZeroAmount = 0;\n let filteredOutMissingFields = 0;\n let filteredOutInvalidDate = 0;\n let filteredOutTimeRange = 0;\n let processedRecords = 0;\n const uniqueKeys = new Set<string>();\n const uniqueServices = new Set<string>();\n const uniqueResources = new Set<string>();\n\n const transformedData = costResponse.data.reduce((accumulator: { [key: string]: Report }, line: any) => {\n // Use amount field first (final amount after discounts), then fall back to price field\n const amount = parseFloat(line.amount) || parseFloat(line.price) || 0;\n\n if (amount === 0) {\n filteredOutZeroAmount++;\n return accumulator;\n }\n\n // Skip records with missing critical fields\n if (!line.start_date || !line.line_type || (!line.amount && !line.price)) {\n filteredOutMissingFields++;\n return accumulator;\n }\n\n const parsedStartDate = moment(line.start_date);\n\n if (!parsedStartDate.isValid()) {\n filteredOutInvalidDate++;\n this.logger.debug(`Invalid start_date: ${line.start_date}`);\n return accumulator;\n }\n\n let billingPeriod = undefined;\n if (query.granularity === GRANULARITY.MONTHLY) {\n billingPeriod = parsedStartDate.format('YYYY-MM');\n } else {\n billingPeriod = parsedStartDate.format('YYYY-MM-DD');\n }\n\n // Create a more descriptive service name by combining product and line_type\n const baseServiceName = this.capitalizeWords(line.line_type);\n const productName = line.product ? this.capitalizeWords(line.product) : null;\n const serviceName =\n productName && productName !== baseServiceName ? `${productName} ${baseServiceName}` : baseServiceName;\n\n const resourceName = line.resource?.display_name || 'Unknown';\n const envDisplayName = line.envDisplayName || 'Unknown';\n const networkAccessType = line.network_access_type;\n\n uniqueServices.add(serviceName);\n uniqueResources.add(resourceName);\n\n const keyName = `${accountName}->${categoryMappingService.getCategoryByServiceName(\n this.provider,\n serviceName,\n )}->${resourceName}`;\n\n if (!accumulator[keyName]) {\n uniqueKeys.add(keyName);\n accumulator[keyName] = {\n id: keyName,\n account: `${this.provider}/${accountName}`,\n service: this.convertServiceName(serviceName),\n category: categoryMappingService.getCategoryByServiceName(this.provider, serviceName),\n provider: this.provider,\n providerType: PROVIDER_TYPE.INTEGRATION,\n reports: {},\n ...{ project: envDisplayName },\n ...{ cluster: resourceName },\n ...(networkAccessType && { networkAccessType }),\n ...tagKeyValues,\n };\n }\n\n if (!moment(billingPeriod).isBefore(moment(parseInt(query.startTime, 10)))) {\n accumulator[keyName].reports[billingPeriod] = (accumulator[keyName].reports[billingPeriod] || 0) + amount;\n processedRecords++;\n } else {\n filteredOutTimeRange++;\n }\n\n return accumulator;\n }, {});\n\n this.logTransformationSummary({\n processed: processedRecords,\n uniqueReports: uniqueKeys.size,\n zeroAmount: filteredOutZeroAmount,\n missingFields: filteredOutMissingFields,\n invalidDate: filteredOutInvalidDate,\n timeRange: filteredOutTimeRange,\n totalRecords: costResponse.data.length,\n });\n\n // Log a few unique keys to understand the grouping\n const keyArray = Array.from(uniqueKeys);\n if (keyArray.length > 0) {\n this.logger.info(`Unique services found: ${Array.from(uniqueServices).slice(0, 10).join(', ')}`);\n this.logger.info(`Unique resources found: ${Array.from(uniqueResources).slice(0, 10).join(', ')}`);\n this.logger.debug(`Sample unique keys: ${keyArray.slice(0, 5).join(', ')}`);\n }\n\n return Object.values(transformedData);\n }\n}\n"],"names":["InfraWalletClient","CLOUD_PROVIDER","ConfluentEnvironmentSchema","ZodError","cryptoRandom","ConfluentBillingResponseSchema","NUMBER_OF_MONTHS_FETCHING_HISTORICAL_COSTS","moment","CategoryMappingService","GRANULARITY","PROVIDER_TYPE"],"mappings":"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"}
|