Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support account filtering at Integration level #108

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,25 @@ backend:

Datadog doesn't provide daily costs. Current daily costs are calculated by `monthly costs/number of days in that month`.

## Adjust Category Mappings if Needed
## Integration Filter

The category mappings are stored in the plugin's database. If there is no mapping found in the DB when initializing the plugin, the default mappings will be used. The default mappings can be found in the [plugins/infrawallet-backend/seeds/init.js](../plugins/infrawallet-backend/seeds/init.js) file. You can adjust this seed file to fit your needs, or update the database directly later on.
When integrating InfraWallet with your billing account, you have the ability to retrieve and display costs for all sub-accounts. However, if you want to limit the visibility of certain accounts, you can apply filters. Below is an example of how to configure this for AWS:

```yaml
backend:
infraWallet:
integrations:
aws:
- name: <unique_name_of_this_integration>
accountId: '<12-digit_account_ID>' # quoted as a string
...
filters:
- type: include # 'include' or 'exclude'
attribute: account
pattern: <regex_for_account_names> # Use a valid regex pattern to specify accounts
```

Currently, only AWS and Datadog integrations support filters.

## Install the Plugin

Expand Down
28 changes: 28 additions & 0 deletions plugins/infrawallet-backend/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ export interface Config {
*/
accessKeySecret?: string;
tags?: string[];
filters?: [
{
type: string;
attribute: string;
pattern: string;
},
];
},
];
gcp?: [
Expand Down Expand Up @@ -70,6 +77,27 @@ export interface Config {
tags?: string[];
},
];
datadog?: [
{
name: string;
/**
* @visibility secret
*/
apiKey: string;
/**
* @visibility secret
*/
applicationKey: string;
ddSite: string;
filters?: [
{
type: string;
attribute: string;
pattern: string;
},
];
},
];
mock?: [
{
name: string;
Expand Down
29 changes: 19 additions & 10 deletions plugins/infrawallet-backend/src/cost-clients/AwsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ export class AwsClient extends InfraWalletClient {
return `${this.provider}/${convertedName}`;
}

protected async initCloudClient(subAccountConfig: Config): Promise<any> {
const accountId = subAccountConfig.getString('accountId');
const assumedRoleName = subAccountConfig.getOptionalString('assumedRoleName');
const accessKeyId = subAccountConfig.getOptionalString('accessKeyId');
const accessKeySecret = subAccountConfig.getOptionalString('accessKeySecret');
protected async initCloudClient(integrationConfig: Config): Promise<any> {
const accountId = integrationConfig.getString('accountId');
const assumedRoleName = integrationConfig.getOptionalString('assumedRoleName');
const accessKeyId = integrationConfig.getOptionalString('accessKeyId');
const accessKeySecret = integrationConfig.getOptionalString('accessKeySecret');
const region = 'us-east-1';

if (!accessKeyId && !accessKeySecret && !assumedRoleName) {
Expand Down Expand Up @@ -143,7 +143,7 @@ export class AwsClient extends InfraWalletClient {
}

protected async fetchTagKeys(
_subAccountConfig: Config,
_integrationConfig: Config,
client: any,
query: TagsQuery,
): Promise<{ tagKeys: string[]; provider: CLOUD_PROVIDER }> {
Expand All @@ -152,7 +152,7 @@ export class AwsClient extends InfraWalletClient {
}

protected async fetchTagValues(
_subAccountConfig: Config,
_integrationConfig: Config,
client: any,
query: TagsQuery,
tagKey: string,
Expand All @@ -161,7 +161,7 @@ export class AwsClient extends InfraWalletClient {
return { tagValues: tagValues, provider: this.provider };
}

protected async fetchCosts(_subAccountConfig: Config, client: any, query: CostQuery): Promise<any> {
protected async fetchCosts(_integrationConfig: Config, client: any, query: CostQuery): Promise<any> {
// query this aws account's cost and usage using @aws-sdk/client-cost-explorer
let costAndUsageResults: any[] = [];
let nextPageToken = undefined;
Expand Down Expand Up @@ -220,9 +220,13 @@ export class AwsClient extends InfraWalletClient {
return costAndUsageResults;
}

protected async transformCostsData(subAccountConfig: Config, query: CostQuery, costResponse: any): Promise<Report[]> {
protected async transformCostsData(
integrationConfig: Config,
query: CostQuery,
costResponse: any,
): Promise<Report[]> {
const categoryMappingService = CategoryMappingService.getInstance();
const tags = subAccountConfig.getOptionalStringArray('tags');
const tags = integrationConfig.getOptionalStringArray('tags');
const tagKeyValues: { [key: string]: string } = {};
tags?.forEach(tag => {
const [k, v] = tag.split(':');
Expand All @@ -245,6 +249,11 @@ export class AwsClient extends InfraWalletClient {
row.Groups.forEach((group: any) => {
const accountId = group.Keys ? group.Keys[0] : '';
const accountName = this.accounts.get(accountId) || accountId;

if (!this.evaluateIntegrationFilters(accountName, integrationConfig)) {
return;
}

const serviceName = group.Keys ? group.Keys[1] : '';
const keyName = `${accountId}_${serviceName}`;

Expand Down
24 changes: 17 additions & 7 deletions plugins/infrawallet-backend/src/cost-clients/DatadogClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ export class DatadogClient extends InfraWalletClient {
return `${this.provider}/${convertedName}`;
}

protected async initCloudClient(config: any): Promise<any> {
const apiKey = config.getString('apiKey');
const applicationKey = config.getString('applicationKey');
const ddSite = config.getString('ddSite');
protected async initCloudClient(integrationConfig: any): Promise<any> {
const apiKey = integrationConfig.getString('apiKey');
const applicationKey = integrationConfig.getString('applicationKey');
const ddSite = integrationConfig.getString('ddSite');
const configuration = datadogClient.createConfiguration({
baseServer: new datadogClient.BaseServerConfiguration(ddSite, {}),
authMethods: {
Expand All @@ -88,7 +88,7 @@ export class DatadogClient extends InfraWalletClient {
return client;
}

protected async fetchCosts(_config: Config, client: any, query: CostQuery): Promise<any> {
protected async fetchCosts(integrationConfig: Config, client: any, query: CostQuery): Promise<any> {
const costData: datadogApiV2.CostByOrg[] = [];
const startTime = moment(parseInt(query.startTime, 10));
const endTime = moment(parseInt(query.endTime, 10));
Expand Down Expand Up @@ -131,8 +131,13 @@ export class DatadogClient extends InfraWalletClient {

if (query.granularity === GRANULARITY.MONTHLY) {
costData.forEach(costByOrg => {
const orgName = costByOrg.attributes?.orgName as string;
if (!this.evaluateIntegrationFilters(orgName, integrationConfig)) {
return;
}

costs.push({
orgName: costByOrg.attributes?.orgName,
orgName: orgName,
date: costByOrg.attributes?.date,
// only keep cost breakdown
charges: costByOrg.attributes?.charges?.filter(charge => charge.chargeType !== 'total'),
Expand All @@ -141,6 +146,11 @@ export class DatadogClient extends InfraWalletClient {
} else {
// Datadog doesn't provide daily costs based on usage, so we allocate monthly costs evenly by day
costData.forEach(costByOrg => {
const orgName = costByOrg.attributes?.orgName as string;
if (!this.evaluateIntegrationFilters(orgName, integrationConfig)) {
return;
}

const daysInMonth = moment(costByOrg.attributes?.date).daysInMonth();
costByOrg.attributes?.charges?.forEach(charge => {
if (charge.chargeType === 'total') {
Expand All @@ -150,7 +160,7 @@ export class DatadogClient extends InfraWalletClient {

for (let i = 0; i < daysInMonth; i++) {
const dailyCost = {
orgName: costByOrg.attributes?.orgName,
orgName: orgName,
date: moment(costByOrg.attributes?.date).add(i, 'd'),
charges: [
{
Expand Down
Loading