Skip to content

Commit

Permalink
Add possibility to define entity relationships by using AWS Tags when…
Browse files Browse the repository at this point in the history
… providing Resource entities directly from AWS. Expose `system`, `domain`, `dependencyOf` and `dependsOn` tag keys to be used.
  • Loading branch information
Xantier committed Jun 11, 2024
1 parent c62ec52 commit 3b7cf6c
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/slimy-tools-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@roadiehq/catalog-backend-module-aws': minor
---

Add possibility to define entity relationships by using AWS Tags when providing Resource entities directly from AWS. Expose `system`, `domain`, `dependencyOf` and `dependsOn` tag keys to be used.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import { AWSEntityProvider } from './AWSEntityProvider';
import { ResourceEntity } from '@backstage/catalog-model';
import { ANNOTATION_AWS_DDB_TABLE_ARN } from '../annotations';
import { arnToName } from '../utils/arnToName';
import { labelsFromTags, ownerFromTags } from '../utils/tags';
import {
labelsFromTags,
ownerFromTags,
relationShipsFromTags,
} from '../utils/tags';
import { CatalogApi } from '@backstage/catalog-client';

/**
Expand Down Expand Up @@ -105,6 +109,7 @@ export class AWSDynamoDbTableProvider extends AWSEntityProvider {
},
spec: {
owner: ownerFromTags(tags, this.getOwnerTag(), groups),
...relationShipsFromTags(tags),
type: 'dynamo-db-table',
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import { Config } from '@backstage/config';
import { AWSEntityProvider } from './AWSEntityProvider';
import { ANNOTATION_AWS_EC2_INSTANCE_ID } from '../annotations';
import { ARN } from 'link2aws';
import { labelsFromTags, ownerFromTags } from '../utils/tags';
import {
labelsFromTags,
ownerFromTags,
relationShipsFromTags,
} from '../utils/tags';
import { CatalogApi } from '@backstage/catalog-client';

/**
Expand Down Expand Up @@ -104,6 +108,7 @@ export class AWSEC2Provider extends AWSEntityProvider {
},
spec: {
owner: ownerFromTags(instance.Tags, this.getOwnerTag(), groups),
...relationShipsFromTags(instance.Tags),
type: 'ec2-instance',
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import {
ANNOTATION_AWS_IAM_ROLE_ARN,
} from '../annotations';
import { arnToName } from '../utils/arnToName';
import { labelsFromTags, ownerFromTags } from '../utils/tags';
import {
labelsFromTags,
ownerFromTags,
relationShipsFromTags,
} from '../utils/tags';
import { CatalogApi } from '@backstage/catalog-client';

/**
Expand Down Expand Up @@ -111,6 +115,7 @@ export class AWSEKSClusterProvider extends AWSEntityProvider {
this.getOwnerTag(),
groups,
),
...relationShipsFromTags(cluster.cluster?.tags),
type: 'eks-cluster',
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,14 @@ export abstract class AWSEntityProvider implements EntityProvider {

protected getCredentials() {
const region = parseArn(this.roleArn).region;
return fromTemporaryCredentials({
params: { RoleArn: this.roleArn, ExternalId: this.externalId },
clientConfig: { region: region },
});
return region
? fromTemporaryCredentials({
params: { RoleArn: this.roleArn, ExternalId: this.externalId },
clientConfig: { region: region },
})
: fromTemporaryCredentials({
params: { RoleArn: this.roleArn, ExternalId: this.externalId },
});
}

protected async getGroups() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import { AWSEntityProvider } from './AWSEntityProvider';
import { ANNOTATION_AWS_IAM_ROLE_ARN } from '../annotations';
import { arnToName } from '../utils/arnToName';
import { ARN } from 'link2aws';
import { labelsFromTags, ownerFromTags } from '../utils/tags';
import {
labelsFromTags,
ownerFromTags,
relationShipsFromTags,
} from '../utils/tags';
import { CatalogApi } from '@backstage/catalog-client';

/**
Expand Down Expand Up @@ -97,6 +101,7 @@ export class AWSIAMRoleProvider extends AWSEntityProvider {
spec: {
type: 'aws-role',
owner: ownerFromTags(role.Tags, this.getOwnerTag(), groups),
...relationShipsFromTags(role.Tags),
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import {
} from '../annotations';
import { arnToName } from '../utils/arnToName';
import { ARN } from 'link2aws';
import { labelsFromTags, ownerFromTags } from '../utils/tags';
import {
labelsFromTags,
ownerFromTags,
relationShipsFromTags,
} from '../utils/tags';
import { CatalogApi } from '@backstage/catalog-client';

/**
Expand Down Expand Up @@ -119,8 +123,8 @@ export class AWSLambdaFunctionProvider extends AWSEntityProvider {
},
spec: {
owner: ownerFromTags(tags, this.getOwnerTag(), groups),
...relationShipsFromTags(tags),
type: 'lambda-function',
dependsOn: [],
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ import {
ANNOTATION_AWS_ACCOUNT_ARN,
} from '../annotations';
import { arnToName } from '../utils/arnToName';
import { labelsFromTags, ownerFromTags } from '../utils/tags';
import {
labelsFromTags,
ownerFromTags,
relationShipsFromTags,
} from '../utils/tags';
import { Tag } from '@aws-sdk/client-organizations/dist-types/models/models_0';
import { CatalogApi } from '@backstage/catalog-client';

Expand Down Expand Up @@ -119,6 +123,7 @@ export class AWSOrganizationAccountsProvider extends AWSEntityProvider {
},
spec: {
owner: ownerFromTags(tags, this.getOwnerTag(), groups),
...relationShipsFromTags(tags),
type: 'aws-account',
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import { Config } from '@backstage/config';
import { AWSEntityProvider } from './AWSEntityProvider';
import { ANNOTATION_AWS_RDS_INSTANCE_ARN } from '../annotations';
import { ARN } from 'link2aws';
import { labelsFromTags, ownerFromTags } from '../utils/tags';
import {
labelsFromTags,
ownerFromTags,
relationShipsFromTags,
} from '../utils/tags';
import { CatalogApi } from '@backstage/catalog-client';

/**
Expand Down Expand Up @@ -110,6 +114,7 @@ export class AWSRDSProvider extends AWSEntityProvider {
this.getOwnerTag(),
groups,
),
...relationShipsFromTags(dbInstance.TagList),
type: 'rds-instance',
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import { AWSEntityProvider } from './AWSEntityProvider';
import { ANNOTATION_AWS_S3_BUCKET_ARN } from '../annotations';
import { arnToName } from '../utils/arnToName';
import { ARN } from 'link2aws';
import { labelsFromTags, ownerFromTags } from '../utils/tags';
import {
labelsFromTags,
ownerFromTags,
relationShipsFromTags,
} from '../utils/tags';
import { CatalogApi } from '@backstage/catalog-client';

/**
Expand Down Expand Up @@ -92,6 +96,7 @@ export class AWSS3BucketProvider extends AWSEntityProvider {
},
spec: {
owner: ownerFromTags(tags, this.getOwnerTag(), groups),
...relationShipsFromTags(tags),
type: 's3-bucket',
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { ownerFromTags, labelsFromTags } from './tags';
import { ownerFromTags, labelsFromTags, relationShipsFromTags } from './tags';
import { Entity } from '@backstage/catalog-model';

describe('labelsFromTags and ownerFromTags', () => {
Expand Down Expand Up @@ -150,3 +150,32 @@ describe('labelsFromTags and ownerFromTags', () => {
});
});
});

describe('relationShipsFromTags', () => {
it('should return an empty object if tags is undefined', () => {
const output = relationShipsFromTags();
expect(output).toEqual({});
});

it('should return an empty object if tags is an empty array', () => {
const output = relationShipsFromTags([]);
expect(output).toEqual({});
});

it('should return relationships from an array of tags', () => {
const tags = [{ Key: 'dependsOn', Value: 'Value1' }];
const output = relationShipsFromTags(tags);
expect(output).toEqual({ dependsOn: ['Value1'] });
});

it('should be case-insensitive when matching tag keys', () => {
const tags = [{ Key: 'dePeNdsOn', Value: 'Value1' }];
const output = relationShipsFromTags(tags);
expect(output).toEqual({ dependsOn: ['Value1'] });
});
it('should work with dependency of tag', () => {
const tags = [{ Key: 'dependencyOf', Value: 'Value1' }];
const output = relationShipsFromTags(tags);
expect(output).toEqual({ dependencyOf: ['Value1'] });
});
});
75 changes: 63 additions & 12 deletions plugins/backend/catalog-backend-module-aws/src/utils/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,49 @@ type Tag = {
};
const UNKNOWN_OWNER = 'unknown';

const TAG_DEPENDS_ON = 'dependsOn';
const TAG_DEPENDENCY_OF = 'dependencyOf';
const TAG_SYSTEM = 'system';
const TAG_DOMAIN = 'domain';

const dependencyTags = [TAG_DEPENDENCY_OF, TAG_DEPENDS_ON];
const relationshipTags = [TAG_SYSTEM, TAG_DOMAIN];

export const labelsFromTags = (tags?: Tag[] | Record<string, string>) => {
if (!tags) {
return {};
}
if (Array.isArray(tags)) {
return tags?.reduce((acc: Record<string, string>, tag) => {
if (tag.Key && tag.Value) {
const key = tag.Key.replaceAll(':', '_').replaceAll('/', '-');
acc[key] = tag.Value.replaceAll('/', '-').substring(0, 63);
}
return acc;
}, {});
return tags
?.filter(
tag =>
!tag.Key ||
![...dependencyTags, ...relationshipTags]
.map(it => it.toLowerCase())
.includes(tag.Key.toLowerCase()),
)
.reduce((acc: Record<string, string>, tag) => {
if (tag.Key && tag.Value) {
const key = tag.Key.replaceAll(':', '_').replaceAll('/', '-');
acc[key] = tag.Value.replaceAll('/', '-').substring(0, 63);
}
return acc;
}, {});
}
return Object.entries(tags as Record<string, string>).reduce(
(acc: Record<string, string>, [key, value]) => {
return Object.entries(tags as Record<string, string>)
?.filter(
([tagKey]) =>
![...dependencyTags, ...relationshipTags]
.map(it => it.toLowerCase())
.includes(tagKey.toLowerCase()),
)
.reduce((acc: Record<string, string>, [key, value]) => {
if (key && value) {
const k = key.replaceAll(':', '_').replaceAll('/', '-');
acc[k] = value.replaceAll('/', '-').substring(0, 63);
}
return acc;
},
{},
);
}, {});
};

export const ownerFromTags = (
Expand Down Expand Up @@ -79,3 +99,34 @@ export const ownerFromTags = (

return ownerString ? ownerString : UNKNOWN_OWNER;
};

export const relationShipsFromTags = (
tags?: Tag[] | Record<string, string>,
): Record<string, string | string[]> => {
if (!tags) {
return {};
}

const specPartial: Record<string, string | string[]> = {};
if (Array.isArray(tags)) {
dependencyTags.forEach(tagKey => {
const tagValue = tags?.find(
tag => tag.Key?.toLowerCase() === tagKey?.toLowerCase(),
);
if (tagValue && tagValue.Value) {
specPartial[tagKey] = [tagValue.Value.split(',')].flat();
}
});

relationshipTags.forEach(tagKey => {
const tagValue = tags?.find(
tag => tag.Key?.toLowerCase() === tagKey?.toLowerCase(),
);
if (tagValue && tagValue.Value) {
specPartial[tagKey] = tagValue.Value;
}
});
}

return specPartial;
};

0 comments on commit 3b7cf6c

Please sign in to comment.