@cloud-copilot/iam-collect
Version:
Collect IAM information from AWS Accounts
138 lines • 6.21 kB
JavaScript
import { AwsClientPool } from '../aws/ClientPool.js';
import { log } from '../utils/log.js';
import { convertTagsToRecord } from '../utils/tags.js';
import { syncData } from './sync.js';
/**
* Paginates through all available resources for a given AWS API
*
* @param clientClass The AWS Client class to use
* @param commandClass The command class to invoke for the resource
* @param key The key of the resource in the command output to pull
* @param paginationConfig The pagination configuration for the command, defines the keys to use for pagination, use `::no-pagination::` to indicate no pagination
* @param params Optional parameters to pass to the command
* @returns Returns an array of resources returned from the commandClass Response Type key
*/
export async function paginateResource(clientClassOrInstance, commandClass, key, paginationConfig, params) {
const client = clientClassOrInstance;
let nextToken = undefined;
const inputPaginationKey = paginationInputKey(paginationConfig);
const outputPaginationKey = paginationOutputKey(paginationConfig);
const resources = [];
params = params || {};
do {
const args = { ...params };
if (inputPaginationKey) {
args[inputPaginationKey] = nextToken;
}
const command = new commandClass(args);
const results = await client.send(command);
if (results[key]) {
resources.push(...results[key]);
}
if (outputPaginationKey) {
nextToken = results[outputPaginationKey];
}
} while (nextToken);
return resources;
}
function paginationInputKey(paginationConfig) {
if (paginationConfig === '::no-pagination::') {
return undefined;
}
else if (typeof paginationConfig === 'object') {
return paginationConfig.inputKey;
}
else {
return undefined;
}
}
function paginationOutputKey(paginationConfig) {
if (paginationConfig === '::no-pagination::') {
return undefined;
}
else if (typeof paginationConfig === 'object') {
return paginationConfig.outputKey;
}
else {
return undefined;
}
}
/**
* Creates a resource sync type and returns it. This provides cleaner syntax than
* making one directly and having to define the types twice.
*
* @param config Configuration for the resource sync type
* @returns The ResourceSyncType instance passed in.
*/
export function createResourceSyncType(config) {
return config;
}
/**
* Paginates a ResourceSyncType and returns the resources.
*
* @param resourceTypeSync The configuration to use.
* @param credentials The credentials to use for AWS API calls. Pass undefined to use the environment credentials.
* @param region The region to get the resources for.
* @returns Returns all the resources for the given type in the given region with extraFields populated.
*/
export async function paginateResourceConfig(resourceTypeSync, credentials, region, endpoint) {
const accountId = credentials.accountId;
const partition = credentials.partition;
const client = AwsClientPool.defaultInstance.client(resourceTypeSync.client, credentials, region, endpoint);
let resources = await paginateResource(client, resourceTypeSync.command, resourceTypeSync.key, resourceTypeSync.paginationConfig, resourceTypeSync.arguments ? resourceTypeSync.arguments(accountId, region) : undefined);
//If the resource is a string, convert it to an object with the name field set
if (resources.length > 0 && typeof resources[0] === 'string') {
resources = resources.map((resource) => ({ name: resource }));
}
if (resourceTypeSync.extraFields) {
await Promise.all(resources.map(async (resource) => {
const fields = resourceTypeSync.extraFields || {};
const extraFields = Object.entries(fields);
//Get the extra field values
const extraFieldValues = await Promise.all(extraFields.map(async ([key, callback]) => {
const value = await callback(client, resource, credentials.accountId, region, partition);
return [key, value];
}));
//Map the extra field values to an object
const extraFieldsObject = extraFieldValues.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
resource.extraFields = extraFieldsObject;
}));
}
return resources;
}
/**
* Create a typed sync operation for a given AWS service and resource type.
*
* Because of obscure typescript issues, the order of the keys in `resourceTypeSync` is important.
* This order is known to work:
* client, command, key, paginationConfig, arn, extraFields, tags, resourceTypeParts, results
*
* @param awsService the AWS service to sync
* @param name the name of the sync operation
* @param resourceTypeSync the resource type sync configuration
* @returns The sync operation
*/
export function createTypedSyncOperation(awsService, name, resourceTypeSync) {
return {
awsService,
name,
global: resourceTypeSync.globalResourceType ?? false,
execute: async (accountId, region, credentials, storage, endpoint, syncOptions) => {
const awsId = credentials.accountId;
log.trace('getting resources', { region: region, accountId, service: awsService, name });
const resources = await paginateResourceConfig(resourceTypeSync, credentials, region, endpoint);
log.trace('received resources', { region: region, accountId, service: awsService, name });
const records = resources.map((resource) => {
const result = resourceTypeSync.results(resource);
result.arn = resourceTypeSync.arn(resource, region, awsId, credentials.partition);
if (result.metadata) {
result.metadata.arn = result.arn;
}
result.tags = convertTagsToRecord(resourceTypeSync.tags(resource));
return result;
});
await syncData(records, storage, accountId, resourceTypeSync.resourceTypeParts(awsId, region));
}
};
}
//# sourceMappingURL=typedSync.js.map