UNPKG

@cloud-copilot/iam-collect

Version:

Collect IAM information from AWS Accounts

173 lines 7.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.paginateResource = paginateResource; exports.createResourceSyncType = createResourceSyncType; exports.paginateResourceConfig = paginateResourceConfig; exports.createTypedSyncOperation = createTypedSyncOperation; const client_tools_js_1 = require("../utils/client-tools.js"); const log_1 = require("@cloud-copilot/log"); const tags_js_1 = require("../utils/tags.js"); const sync_js_1 = require("./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 */ 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. */ 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. */ async function paginateResourceConfig(resourceTypeSync, credentials, region, endpoint, workerPool, awsService, resourceType, clientPool) { const accountId = credentials.accountId; const partition = credentials.partition; const client = clientPool.client(resourceTypeSync.client, credentials, region, endpoint); let resources = await (0, client_tools_js_1.withDnsRetry)(() => { return 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 resourceArn = resourceTypeSync.arn(resource, region, accountId, partition); const fields = resourceTypeSync.extraFields || {}; const extraFields = Object.entries(fields); //Get the extra field values const extraFieldPromises = workerPool.enqueueAll(extraFields.map(([key, callback]) => ({ properties: { field: key, arn: resourceArn }, execute: async (context) => { const value = await (0, client_tools_js_1.withDnsRetry)(() => { return (0, client_tools_js_1.runAndCatchAccessDeniedWithLog)(resourceArn, awsService, resourceType, key, async () => { return callback(client, resource, credentials.accountId, region, partition); }); }); return [key, value]; } }))); const extraFieldValues = await Promise.all(extraFieldPromises); resource.extraFields = {}; let anyFailure = false; for (const result of extraFieldValues) { if (result.status === 'rejected') { log_1.log.error(result.reason, { field: result.properties.field, arn: result.properties.arn, awsService, resourceType }, 'Failed to get extra field value'); anyFailure = true; } else { const value = result.value; resource.extraFields[value[0]] = value[1]; } } if (anyFailure) { throw new Error('Failed to get some extra field values'); } })); } 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 */ function createTypedSyncOperation(awsService, name, resourceTypeSync) { return { awsService, name, global: resourceTypeSync.globalResourceType ?? false, execute: async (accountId, region, credentials, storage, endpoint, syncOptions) => { const awsId = credentials.accountId; const clientPool = syncOptions.clientPool; log_1.log.trace('getting resources', { region: region, accountId, service: awsService, name }); const resources = await paginateResourceConfig(resourceTypeSync, credentials, region, endpoint, syncOptions.workerPool, awsService, name, clientPool); log_1.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 = (0, tags_js_1.convertTagsToRecord)(resourceTypeSync.tags(resource)); return result; }); await (0, sync_js_1.syncData)(records, storage, accountId, resourceTypeSync.resourceTypeParts(awsId, region), syncOptions.writeOnly); } }; } //# sourceMappingURL=typedSync.js.map