UNPKG

aws-cdk

Version:

AWS CDK CLI, the command line tool for CDK apps

801 lines 115 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FromScan = exports.CfnTemplateGeneratorProvider = exports.FilterType = exports.ScanStatus = exports.TemplateSourceOptions = void 0; exports.generateCdkApp = generateCdkApp; exports.generateStack = generateStack; exports.readFromPath = readFromPath; exports.readFromStack = readFromStack; exports.generateTemplate = generateTemplate; exports.chunks = chunks; exports.setEnvironment = setEnvironment; exports.parseSourceOptions = parseSourceOptions; exports.printBar = printBar; exports.printDots = printDots; exports.rewriteLine = rewriteLine; exports.writeMigrateJsonFile = writeMigrateJsonFile; exports.getMigrateScanType = getMigrateScanType; exports.isThereAWarning = isThereAWarning; exports.buildGeneratedTemplateOutput = buildGeneratedTemplateOutput; exports.buildCfnClient = buildCfnClient; exports.appendWarningsToReadme = appendWarningsToReadme; /* eslint-disable @typescript-eslint/no-require-imports */ /* eslint-disable @typescript-eslint/no-var-requires */ const fs = require("fs"); const path = require("path"); const cx_api_1 = require("@aws-cdk/cx-api"); const toolkit_lib_1 = require("@aws-cdk/toolkit-lib"); const cdk_from_cfn = require("cdk-from-cfn"); const chalk = require("chalk"); const init_1 = require("./init"); const cloudformation_1 = require("../api/cloudformation"); const plugin_1 = require("../api/plugin"); const util_1 = require("../util"); const camelCase = require('camelcase'); const decamelize = require('decamelize'); /** The list of languages supported by the built-in noctilucent binary. */ const MIGRATE_SUPPORTED_LANGUAGES = cdk_from_cfn.supported_languages(); /** * Generates a CDK app from a yaml or json template. * * @param stackName - The name to assign to the stack in the generated app * @param stack - The yaml or json template for the stack * @param language - The language to generate the CDK app in * @param outputPath - The path at which to generate the CDK app */ async function generateCdkApp(ioHelper, stackName, stack, language, outputPath, compress) { const resolvedOutputPath = path.join(outputPath ?? process.cwd(), stackName); const formattedStackName = decamelize(stackName); try { fs.rmSync(resolvedOutputPath, { recursive: true, force: true }); fs.mkdirSync(resolvedOutputPath, { recursive: true }); const generateOnly = compress; await (0, init_1.cliInit)({ ioHelper, type: 'app', language, canUseNetwork: true, generateOnly, workDir: resolvedOutputPath, stackName, migrate: true, }); let stackFileName; switch (language) { case 'typescript': stackFileName = `${resolvedOutputPath}/lib/${formattedStackName}-stack.ts`; break; case 'java': stackFileName = `${resolvedOutputPath}/src/main/java/com/myorg/${camelCase(formattedStackName, { pascalCase: true })}Stack.java`; break; case 'python': stackFileName = `${resolvedOutputPath}/${formattedStackName.replace(/-/g, '_')}/${formattedStackName.replace(/-/g, '_')}_stack.py`; break; case 'csharp': stackFileName = `${resolvedOutputPath}/src/${camelCase(formattedStackName, { pascalCase: true })}/${camelCase(formattedStackName, { pascalCase: true })}Stack.cs`; break; case 'go': stackFileName = `${resolvedOutputPath}/${formattedStackName}.go`; break; default: throw new toolkit_lib_1.ToolkitError(`${language} is not supported by CDK Migrate. Please choose from: ${MIGRATE_SUPPORTED_LANGUAGES.join(', ')}`); } fs.writeFileSync(stackFileName, stack); if (compress) { await (0, util_1.zipDirectory)(resolvedOutputPath, `${resolvedOutputPath}.zip`); fs.rmSync(resolvedOutputPath, { recursive: true, force: true }); } } catch (error) { fs.rmSync(resolvedOutputPath, { recursive: true, force: true }); throw error; } } /** * Generates a CDK stack file. * @param template - The template to translate into a CDK stack * @param stackName - The name to assign to the stack * @param language - The language to generate the stack in * @returns A string representation of a CDK stack file */ function generateStack(template, stackName, language) { const formattedStackName = `${camelCase(decamelize(stackName), { pascalCase: true })}Stack`; try { return cdk_from_cfn.transmute(template, language, formattedStackName); } catch (e) { throw new toolkit_lib_1.ToolkitError(`${formattedStackName} could not be generated because ${e.message}`); } } /** * Reads and returns a stack template from a local path. * * @param inputPath - The location of the template * @returns A string representation of the template if present, otherwise undefined */ function readFromPath(inputPath) { let readFile; try { readFile = fs.readFileSync(inputPath, 'utf8'); } catch (e) { throw new toolkit_lib_1.ToolkitError(`'${inputPath}' is not a valid path.`); } if (readFile == '') { throw new toolkit_lib_1.ToolkitError(`Cloudformation template filepath: '${inputPath}' is an empty file.`); } return readFile; } /** * Reads and returns a stack template from a deployed CloudFormation stack. * * @param stackName - The name of the stack * @param sdkProvider - The sdk provider for making CloudFormation calls * @param environment - The account and region where the stack is deployed * @returns A string representation of the template if present, otherwise undefined */ async function readFromStack(stackName, sdkProvider, environment) { const cloudFormation = (await sdkProvider.forEnvironment(environment, plugin_1.Mode.ForReading)).sdk.cloudFormation(); const stack = await cloudformation_1.CloudFormationStack.lookup(cloudFormation, stackName, true); if (stack.stackStatus.isDeploySuccess || stack.stackStatus.isRollbackSuccess) { return JSON.stringify(await stack.template()); } else { throw new toolkit_lib_1.ToolkitError(`Stack '${stackName}' in account ${environment.account} and region ${environment.region} has a status of '${stack.stackStatus.name}' due to '${stack.stackStatus.reason}'. The stack cannot be migrated until it is in a healthy state.`); } } /** * Takes in a stack name and account and region and returns a generated cloudformation template using the cloudformation * template generator. * * @param GenerateTemplateOptions - An object containing the stack name, filters, sdkProvider, environment, and newScan flag * @returns a generated cloudformation template */ async function generateTemplate(options) { const cfn = new CfnTemplateGeneratorProvider(await buildCfnClient(options.sdkProvider, options.environment), options.ioHelper); const ioHelper = options.ioHelper; const scanId = await findLastSuccessfulScan(cfn, options); // if a customer accidentally ctrl-c's out of the command and runs it again, this will continue the progress bar where it left off const curScan = await cfn.describeResourceScan(scanId); if (curScan.Status == ScanStatus.IN_PROGRESS) { await ioHelper.defaults.info('Resource scan in progress. Please wait, this can take 10 minutes or longer.'); await scanProgressBar(ioHelper, scanId, cfn); } await displayTimeDiff(ioHelper, new Date(), new Date(curScan.StartTime)); let resources = await cfn.listResourceScanResources(scanId, options.filters); await ioHelper.defaults.info('finding related resources.'); let relatedResources = await cfn.getResourceScanRelatedResources(scanId, resources); await ioHelper.defaults.info(`Found ${relatedResources.length} resources.`); await ioHelper.defaults.info('Generating CFN template from scanned resources.'); const templateArn = (await cfn.createGeneratedTemplate(options.stackName, relatedResources)).GeneratedTemplateId; let generatedTemplate = await cfn.describeGeneratedTemplate(templateArn); await ioHelper.defaults.info('Please wait, template creation in progress. This may take a couple minutes.'); while (generatedTemplate.Status !== ScanStatus.COMPLETE && generatedTemplate.Status !== ScanStatus.FAILED) { await printDots(`[${generatedTemplate.Status}] Template Creation in Progress`, 400); generatedTemplate = await cfn.describeGeneratedTemplate(templateArn); } await ioHelper.defaults.info('\nTemplate successfully generated!'); return buildGeneratedTemplateOutput(generatedTemplate, (await cfn.getGeneratedTemplate(templateArn)).TemplateBody, templateArn); } async function findLastSuccessfulScan(cfn, options) { const ioHelper = options.ioHelper; let resourceScanSummaries = []; const clientRequestToken = `cdk-migrate-${options.environment.account}-${options.environment.region}`; if (options.fromScan === FromScan.NEW) { await ioHelper.defaults.info(`Starting new scan for account ${options.environment.account} in region ${options.environment.region}`); try { await cfn.startResourceScan(clientRequestToken); resourceScanSummaries = (await cfn.listResourceScans()).ResourceScanSummaries; } catch (e) { // continuing here because if the scan fails on a new-scan it is very likely because there is either already a scan in progress // or the customer hit a rate limit. In either case we want to continue with the most recent scan. // If this happens to fail for a credential error then that will be caught immediately after anyway. await ioHelper.defaults.info(`Scan failed to start due to error '${e.message}', defaulting to latest scan.`); } } else { resourceScanSummaries = (await cfn.listResourceScans()).ResourceScanSummaries; await cfn.checkForResourceScan(resourceScanSummaries, options, clientRequestToken); } // get the latest scan, which we know will exist resourceScanSummaries = (await cfn.listResourceScans()).ResourceScanSummaries; let scanId = resourceScanSummaries[0].ResourceScanId; // find the most recent scan that isn't in a failed state in case we didn't start a new one for (const summary of resourceScanSummaries) { if (summary.Status !== ScanStatus.FAILED) { scanId = summary.ResourceScanId; break; } } return scanId; } /** * Takes a string of filters in the format of key1=value1,key2=value2 and returns a map of the filters. * * @param filters - a string of filters in the format of key1=value1,key2=value2 * @returns a map of the filters */ function parseFilters(filters) { if (!filters) { return { 'resource-identifier': undefined, 'resource-type-prefix': undefined, 'tag-key': undefined, 'tag-value': undefined, }; } const filterShorthands = { 'identifier': FilterType.RESOURCE_IDENTIFIER, 'id': FilterType.RESOURCE_IDENTIFIER, 'type': FilterType.RESOURCE_TYPE_PREFIX, 'type-prefix': FilterType.RESOURCE_TYPE_PREFIX, }; const filterList = filters.split(','); let filterMap = { [FilterType.RESOURCE_IDENTIFIER]: undefined, [FilterType.RESOURCE_TYPE_PREFIX]: undefined, [FilterType.TAG_KEY]: undefined, [FilterType.TAG_VALUE]: undefined, }; for (const fil of filterList) { const filter = fil.split('='); let filterKey = filter[0]; const filterValue = filter[1]; // if the key is a shorthand, replace it with the full name if (filterKey in filterShorthands) { filterKey = filterShorthands[filterKey]; } if (Object.values(FilterType).includes(filterKey)) { filterMap[filterKey] = filterValue; } else { throw new toolkit_lib_1.ToolkitError(`Invalid filter: ${filterKey}`); } } return filterMap; } /** * Takes a list of any type and breaks it up into chunks of a specified size. * * @param list - The list to break up * @param chunkSize - The size of each chunk * @returns A list of lists of the specified size */ function chunks(list, chunkSize) { const chunkedList = []; for (let i = 0; i < list.length; i += chunkSize) { chunkedList.push(list.slice(i, i + chunkSize)); } return chunkedList; } /** * Sets the account and region for making CloudFormation calls. * @param account - The account to use * @param region - The region to use * @returns The environment object */ function setEnvironment(account, region) { return { account: account ?? cx_api_1.UNKNOWN_ACCOUNT, region: region ?? cx_api_1.UNKNOWN_REGION, name: 'cdk-migrate-env', }; } /** * Enum for the source options for the template */ var TemplateSourceOptions; (function (TemplateSourceOptions) { TemplateSourceOptions["PATH"] = "path"; TemplateSourceOptions["STACK"] = "stack"; TemplateSourceOptions["SCAN"] = "scan"; })(TemplateSourceOptions || (exports.TemplateSourceOptions = TemplateSourceOptions = {})); /** * Enum for the status of a resource scan */ var ScanStatus; (function (ScanStatus) { ScanStatus["IN_PROGRESS"] = "IN_PROGRESS"; ScanStatus["COMPLETE"] = "COMPLETE"; ScanStatus["FAILED"] = "FAILED"; })(ScanStatus || (exports.ScanStatus = ScanStatus = {})); var FilterType; (function (FilterType) { FilterType["RESOURCE_IDENTIFIER"] = "resource-identifier"; FilterType["RESOURCE_TYPE_PREFIX"] = "resource-type-prefix"; FilterType["TAG_KEY"] = "tag-key"; FilterType["TAG_VALUE"] = "tag-value"; })(FilterType || (exports.FilterType = FilterType = {})); /** * Validates that exactly one source option has been provided. * @param fromPath - The content of the flag `--from-path` * @param fromStack - the content of the flag `--from-stack` */ function parseSourceOptions(fromPath, fromStack, stackName) { if (fromPath && fromStack) { throw new toolkit_lib_1.ToolkitError('Only one of `--from-path` or `--from-stack` may be provided.'); } if (!stackName) { throw new toolkit_lib_1.ToolkitError('`--stack-name` is a required field.'); } if (!fromPath && !fromStack) { return { source: TemplateSourceOptions.SCAN }; } if (fromPath) { return { source: TemplateSourceOptions.PATH, templatePath: fromPath }; } return { source: TemplateSourceOptions.STACK, stackName: stackName }; } /** * Takes a set of resources and removes any with the managedbystack flag set to true. * * @param resourceList - the list of resources provided by the list scanned resources calls * @returns a list of resources not managed by cfn stacks */ function excludeManaged(resourceList) { return resourceList .filter((r) => !r.ManagedByStack) .map((r) => ({ ResourceType: r.ResourceType, ResourceIdentifier: r.ResourceIdentifier, })); } /** * Transforms a list of resources into a list of resource identifiers by removing the ManagedByStack flag. * Setting the value of the field to undefined effectively removes it from the object. * * @param resourceList - the list of resources provided by the list scanned resources calls * @returns a list of ScannedResourceIdentifier[] */ function resourceIdentifiers(resourceList) { const identifiers = []; resourceList.forEach((r) => { const identifier = { ResourceType: r.ResourceType, ResourceIdentifier: r.ResourceIdentifier, }; identifiers.push(identifier); }); return identifiers; } /** * Takes a scan id and maintains a progress bar to display the progress of a scan to the user. * * @param scanId - A string representing the scan id * @param cloudFormation - The CloudFormation sdk client to use */ async function scanProgressBar(ioHelper, scanId, cfn) { let curProgress = 0.5; // we know it's in progress initially since we wouldn't have gotten here if it wasn't let curScan = { Status: ScanStatus.IN_PROGRESS, $metadata: {}, }; while (curScan.Status == ScanStatus.IN_PROGRESS) { curScan = await cfn.describeResourceScan(scanId); curProgress = curScan.PercentageCompleted ?? curProgress; printBar(30, curProgress); await new Promise((resolve) => setTimeout(resolve, 2000)); } await ioHelper.defaults.info('\n✅ Scan Complete!'); } /** * Prints a progress bar to the console. To be used in a while loop to show progress of a long running task. * The progress bar deletes the current line on the console and rewrites it with the progress amount. * * @param width - The width of the progress bar * @param progress - The current progress to display as a percentage of 100 */ function printBar(width, progress) { if (!process.env.MIGRATE_INTEG_TEST) { const FULL_BLOCK = '█'; const PARTIAL_BLOCK = ['', '▏', '▎', '▍', '▌', '▋', '▊', '▉']; const fraction = Math.min(progress / 100, 1); const innerWidth = Math.max(1, width - 2); const chars = innerWidth * fraction; const remainder = chars - Math.floor(chars); const fullChars = FULL_BLOCK.repeat(Math.floor(chars)); const partialChar = PARTIAL_BLOCK[Math.floor(remainder * PARTIAL_BLOCK.length)]; const filler = '·'.repeat(innerWidth - Math.floor(chars) - (partialChar ? 1 : 0)); const color = chalk.green; rewriteLine('[' + color(fullChars + partialChar) + filler + `] (${progress}%)`); } } /** * Prints a message to the console with a series periods appended to it. To be used in a while loop to show progress of a long running task. * The message deletes the current line and rewrites it several times to display 1-3 periods to show the user that the task is still running. * * @param message - The message to display * @param timeoutx4 - The amount of time to wait before printing the next period */ async function printDots(message, timeoutx4) { if (!process.env.MIGRATE_INTEG_TEST) { rewriteLine(message + ' .'); await new Promise((resolve) => setTimeout(resolve, timeoutx4)); rewriteLine(message + ' ..'); await new Promise((resolve) => setTimeout(resolve, timeoutx4)); rewriteLine(message + ' ...'); await new Promise((resolve) => setTimeout(resolve, timeoutx4)); rewriteLine(message); await new Promise((resolve) => setTimeout(resolve, timeoutx4)); } } /** * Rewrites the current line on the console and writes a new message to it. * This is a helper funciton for printDots and printBar. * * @param message - The message to display */ function rewriteLine(message) { process.stdout.clearLine(0); process.stdout.cursorTo(0); process.stdout.write(message); } /** * Prints the time difference between two dates in days, hours, and minutes. * * @param time1 - The first date to compare * @param time2 - The second date to compare */ async function displayTimeDiff(ioHelper, time1, time2) { const diff = Math.abs(time1.getTime() - time2.getTime()); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); await ioHelper.defaults.info(`Using the latest successful scan which is ${days} days, ${hours} hours, and ${minutes} minutes old.`); } /** * Writes a migrate.json file to the output directory. * * @param outputPath - The path to write the migrate.json file to * @param stackName - The name of the stack * @param generatedOutput - The output of the template generator */ function writeMigrateJsonFile(outputPath, stackName, migrateJson) { const outputToJson = { '//': 'This file is generated by cdk migrate. It will be automatically deleted after the first successful deployment of this app to the environment of the original resources.', 'Source': migrateJson.source, 'Resources': migrateJson.resources, }; fs.writeFileSync(`${path.join(outputPath ?? process.cwd(), stackName)}/migrate.json`, JSON.stringify(outputToJson, null, 2)); } /** * Takes a string representing the from-scan flag and returns a FromScan enum value. * * @param scanType - A string representing the from-scan flag * @returns A FromScan enum value */ function getMigrateScanType(scanType) { switch (scanType) { case 'new': return FromScan.NEW; case 'most-recent': return FromScan.MOST_RECENT; case '': return FromScan.DEFAULT; case undefined: return FromScan.DEFAULT; default: throw new toolkit_lib_1.ToolkitError(`Unknown scan type: ${scanType}`); } } /** * Takes a generatedTemplateOutput objct and returns a boolean representing whether there are any warnings on any rescources. * * @param generatedTemplateOutput - A GenerateTemplateOutput object * @returns A boolean representing whether there are any warnings on any rescources */ function isThereAWarning(generatedTemplateOutput) { if (generatedTemplateOutput.resources) { for (const resource of generatedTemplateOutput.resources) { if (resource.Warnings && resource.Warnings.length > 0) { return true; } } } return false; } /** * Builds the GenerateTemplateOutput object from the DescribeGeneratedTemplateOutput and the template body. * * @param generatedTemplateSummary - The output of the describe generated template call * @param templateBody - The body of the generated template * @returns A GenerateTemplateOutput object */ function buildGeneratedTemplateOutput(generatedTemplateSummary, templateBody, source) { const resources = generatedTemplateSummary.Resources; const migrateJson = { templateBody: templateBody, source: source, resources: generatedTemplateSummary.Resources.map((r) => ({ ResourceType: r.ResourceType, LogicalResourceId: r.LogicalResourceId, ResourceIdentifier: r.ResourceIdentifier, })), }; const templateId = generatedTemplateSummary.GeneratedTemplateId; return { migrateJson: migrateJson, resources: resources, templateId: templateId, }; } /** * Builds a CloudFormation sdk client for making requests with the CFN template generator. * * @param sdkProvider - The sdk provider for making CloudFormation calls * @param environment - The account and region where the stack is deployed * @returns A CloudFormation sdk client */ async function buildCfnClient(sdkProvider, environment) { const sdk = (await sdkProvider.forEnvironment(environment, plugin_1.Mode.ForReading)).sdk; sdk.appendCustomUserAgent('cdk-migrate'); return sdk.cloudFormation(); } /** * Appends a list of warnings to a readme file. * * @param filepath - The path to the readme file * @param resources - A list of resources to append warnings for */ function appendWarningsToReadme(filepath, resources) { const readme = fs.readFileSync(filepath, 'utf8'); const lines = readme.split('\n'); const index = lines.findIndex((line) => line.trim() === 'Enjoy!'); let linesToAdd = ['\n## Warnings']; linesToAdd.push('### Write-only properties'); linesToAdd.push("Write-only properties are resource property values that can be written to but can't be read by AWS CloudFormation or CDK Migrate. For more information, see [IaC generator and write-only properties](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/generate-IaC-write-only-properties.html)."); linesToAdd.push('\n'); linesToAdd.push('Write-only properties discovered during migration are organized here by resource ID and categorized by write-only property type. Resolve write-only properties by providing property values in your CDK app. For guidance, see [Resolve write-only properties](https://docs.aws.amazon.com/cdk/v2/guide/migrate.html#migrate-resources-writeonly).'); for (const resource of resources) { if (resource.Warnings && resource.Warnings.length > 0) { linesToAdd.push(`### ${resource.LogicalResourceId}`); for (const warning of resource.Warnings) { linesToAdd.push(`- **${warning.Type}**: `); for (const property of warning.Properties) { linesToAdd.push(` - ${property.PropertyPath}: ${property.Description}`); } } } } lines.splice(index, 0, ...linesToAdd); fs.writeFileSync(filepath, lines.join('\n')); } /** * takes a list of resources and returns a list of unique resources based on the resource type and logical resource id. * * @param resources - A list of resources to deduplicate * @returns A list of unique resources */ function deduplicateResources(resources) { let uniqueResources = {}; for (const resource of resources) { const key = Object.keys(resource.ResourceIdentifier)[0]; // Creating our unique identifier using the resource type, the key, and the value of the resource identifier // The resource identifier is a combination of a key value pair defined by a resource's schema, and the resource type of the resource. const uniqueIdentifer = `${resource.ResourceType}:${key}:${resource.ResourceIdentifier[key]}`; uniqueResources[uniqueIdentifer] = resource; } return Object.values(uniqueResources); } /** * Class for making CloudFormation template generator calls */ class CfnTemplateGeneratorProvider { constructor(cfn, ioHelper) { this.cfn = cfn; this.ioHelper = ioHelper; } async checkForResourceScan(resourceScanSummaries, options, clientRequestToken) { if (!resourceScanSummaries || resourceScanSummaries.length === 0) { if (options.fromScan === FromScan.MOST_RECENT) { throw new toolkit_lib_1.ToolkitError('No scans found. Please either start a new scan with the `--from-scan` new or do not specify a `--from-scan` option.'); } else { await this.ioHelper.defaults.info('No scans found. Initiating a new resource scan.'); await this.startResourceScan(clientRequestToken); } } } /** * Retrieves a tokenized list of resources and their associated scan. If a token is present the function * will loop through all pages and combine them into a single list of ScannedRelatedResources * * @param scanId - scan id for the to list resources for * @param resources - A list of resources to find related resources for */ async getResourceScanRelatedResources(scanId, resources) { let relatedResourceList = resources; // break the list of resources into chunks of 100 to avoid hitting the 100 resource limit for (const chunk of chunks(resources, 100)) { // get the first page of related resources const res = await this.cfn.listResourceScanRelatedResources({ ResourceScanId: scanId, Resources: chunk, }); // add the first page to the list relatedResourceList.push(...(res.RelatedResources ?? [])); let nextToken = res.NextToken; // if there are more pages, cycle through them and add them to the list before moving on to the next chunk while (nextToken) { const nextRelatedResources = await this.cfn.listResourceScanRelatedResources({ ResourceScanId: scanId, Resources: resourceIdentifiers(resources), NextToken: nextToken, }); nextToken = nextRelatedResources.NextToken; relatedResourceList.push(...(nextRelatedResources.RelatedResources ?? [])); } } relatedResourceList = deduplicateResources(relatedResourceList); // prune the managedbystack flag off of them again. return process.env.MIGRATE_INTEG_TEST ? resourceIdentifiers(relatedResourceList) : resourceIdentifiers(excludeManaged(relatedResourceList)); } /** * Kicks off a scan of a customers account, returning the scan id. A scan can take * 10 minutes or longer to complete. However this will return a scan id as soon as * the scan has begun. * * @returns A string representing the scan id */ async startResourceScan(requestToken) { return (await this.cfn.startResourceScan({ ClientRequestToken: requestToken, })).ResourceScanId; } /** * Gets the most recent scans a customer has completed * * @returns a list of resource scan summaries */ async listResourceScans() { return this.cfn.listResourceScans(); } /** * Retrieves a tokenized list of resources from a resource scan. If a token is present, this function * will loop through all pages and combine them into a single list of ScannedResource[]. * Additionally will apply any filters provided by the customer. * * @param scanId - scan id for the to list resources for * @param filters - a string of filters in the format of key1=value1,key2=value2 * @returns a combined list of all resources from the scan */ async listResourceScanResources(scanId, filters = []) { let resourceList = []; let resourceScanInputs; if (filters.length > 0) { await this.ioHelper.defaults.info('Applying filters to resource scan.'); for (const filter of filters) { const filterList = parseFilters(filter); resourceScanInputs = { ResourceScanId: scanId, ResourceIdentifier: filterList[FilterType.RESOURCE_IDENTIFIER], ResourceTypePrefix: filterList[FilterType.RESOURCE_TYPE_PREFIX], TagKey: filterList[FilterType.TAG_KEY], TagValue: filterList[FilterType.TAG_VALUE], }; const resources = await this.cfn.listResourceScanResources(resourceScanInputs); resourceList = resourceList.concat(resources.Resources ?? []); let nextToken = resources.NextToken; // cycle through the pages adding all resources to the list until we run out of pages while (nextToken) { resourceScanInputs.NextToken = nextToken; const nextResources = await this.cfn.listResourceScanResources(resourceScanInputs); nextToken = nextResources.NextToken; resourceList = resourceList.concat(nextResources.Resources ?? []); } } } else { await this.ioHelper.defaults.info('No filters provided. Retrieving all resources from scan.'); resourceScanInputs = { ResourceScanId: scanId, }; const resources = await this.cfn.listResourceScanResources(resourceScanInputs); resourceList = resourceList.concat(resources.Resources ?? []); let nextToken = resources.NextToken; // cycle through the pages adding all resources to the list until we run out of pages while (nextToken) { resourceScanInputs.NextToken = nextToken; const nextResources = await this.cfn.listResourceScanResources(resourceScanInputs); nextToken = nextResources.NextToken; resourceList = resourceList.concat(nextResources.Resources ?? []); } } if (resourceList.length === 0) { throw new toolkit_lib_1.ToolkitError(`No resources found with filters ${filters.join(' ')}. Please try again with different filters.`); } resourceList = deduplicateResources(resourceList); return process.env.MIGRATE_INTEG_TEST ? resourceIdentifiers(resourceList) : resourceIdentifiers(excludeManaged(resourceList)); } /** * Retrieves information about a resource scan. * * @param scanId - scan id for the to list resources for * @returns information about the scan */ async describeResourceScan(scanId) { return this.cfn.describeResourceScan({ ResourceScanId: scanId, }); } /** * Describes the current status of the template being generated. * * @param templateId - A string representing the template id * @returns DescribeGeneratedTemplateOutput an object containing the template status and results */ async describeGeneratedTemplate(templateId) { const generatedTemplate = await this.cfn.describeGeneratedTemplate({ GeneratedTemplateName: templateId, }); if (generatedTemplate.Status == ScanStatus.FAILED) { throw new toolkit_lib_1.ToolkitError(generatedTemplate.StatusReason); } return generatedTemplate; } /** * Retrieves a completed generated cloudformation template from the template generator. * * @param templateId - A string representing the template id * @param cloudFormation - The CloudFormation sdk client to use * @returns DescribeGeneratedTemplateOutput an object containing the template status and body */ async getGeneratedTemplate(templateId) { return this.cfn.getGeneratedTemplate({ GeneratedTemplateName: templateId, }); } /** * Kicks off a template generation for a set of resources. * * @param stackName - The name of the stack * @param resources - A list of resources to generate the template from * @returns CreateGeneratedTemplateOutput an object containing the template arn to query on later */ async createGeneratedTemplate(stackName, resources) { const createTemplateOutput = await this.cfn.createGeneratedTemplate({ Resources: resources, GeneratedTemplateName: stackName, }); if (createTemplateOutput.GeneratedTemplateId === undefined) { throw new toolkit_lib_1.ToolkitError('CreateGeneratedTemplate failed to return an Arn.'); } return createTemplateOutput; } /** * Deletes a generated template from the template generator. * * @param templateArn - The arn of the template to delete * @returns A promise that resolves when the template has been deleted */ async deleteGeneratedTemplate(templateArn) { await this.cfn.deleteGeneratedTemplate({ GeneratedTemplateName: templateArn, }); } } exports.CfnTemplateGeneratorProvider = CfnTemplateGeneratorProvider; /** * The possible ways to choose a scan to generate a CDK application from */ var FromScan; (function (FromScan) { /** * Initiate a new resource scan to build the CDK application from. */ FromScan[FromScan["NEW"] = 0] = "NEW"; /** * Use the last successful scan to build the CDK application from. Will fail if no scan is found. */ FromScan[FromScan["MOST_RECENT"] = 1] = "MOST_RECENT"; /** * Starts a scan if none exists, otherwise uses the most recent successful scan to build the CDK application from. */ FromScan[FromScan["DEFAULT"] = 2] = "DEFAULT"; })(FromScan || (exports.FromScan = FromScan = {})); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlncmF0ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm1pZ3JhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBd0NBLHdDQXlEQztBQVNELHNDQU9DO0FBUUQsb0NBV0M7QUFVRCxzQ0FlQztBQVNELDRDQXNDQztBQWlHRCx3QkFNQztBQVFELHdDQU1DO0FBd0NELGdEQWNDO0FBaUVELDRCQWlCQztBQVNELDhCQWNDO0FBUUQsa0NBSUM7QUF5QkQsb0RBY0M7QUFRRCxnREFhQztBQVFELDBDQVNDO0FBU0Qsb0VBcUJDO0FBU0Qsd0NBSUM7QUFRRCx3REEwQkM7QUF0b0JELDBEQUEwRDtBQUMxRCx1REFBdUQ7QUFDdkQseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUU3Qiw0Q0FBa0U7QUFDbEUsc0RBQW9EO0FBYXBELDZDQUE2QztBQUM3QywrQkFBK0I7QUFDL0IsaUNBQWlDO0FBRWpDLDBEQUE0RDtBQUM1RCwwQ0FBcUM7QUFFckMsa0NBQXVDO0FBQ3ZDLE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUN2QyxNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDekMsMEVBQTBFO0FBQzFFLE1BQU0sMkJBQTJCLEdBQXNCLFlBQVksQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO0FBRTFGOzs7Ozs7O0dBT0c7QUFDSSxLQUFLLFVBQVUsY0FBYyxDQUNsQyxRQUFrQixFQUNsQixTQUFpQixFQUNqQixLQUFhLEVBQ2IsUUFBZ0IsRUFDaEIsVUFBbUIsRUFDbkIsUUFBa0I7SUFFbEIsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsU0FBUyxDQUFDLENBQUM7SUFDN0UsTUFBTSxrQkFBa0IsR0FBRyxVQUFVLENBQUMsU0FBUyxDQUFDLENBQUM7SUFFakQsSUFBSSxDQUFDO1FBQ0gsRUFBRSxDQUFDLE1BQU0sQ0FBQyxr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