@cloud-copilot/iam-collect
Version:
Collect IAM information from AWS Accounts
173 lines • 7.93 kB
JavaScript
;
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