UNPKG

@cloud-copilot/iam-collect

Version:

Collect IAM information from AWS Accounts

138 lines 6.21 kB
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