UNPKG

aws-cdk

Version:

AWS CDK CLI, the command line tool for CDK apps

801 lines 113 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.scanProgressBar = scanProgressBar; exports.printBar = printBar; exports.printDots = printDots; exports.rewriteLine = rewriteLine; exports.displayTimeDiff = displayTimeDiff; exports.writeMigrateJsonFile = writeMigrateJsonFile; exports.getMigrateScanType = getMigrateScanType; exports.isThereAWarning = isThereAWarning; exports.buildGenertedTemplateOutput = buildGenertedTemplateOutput; 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 cdk_from_cfn = require("cdk-from-cfn"); const chalk = require("chalk"); const init_1 = require("./init"); const api_1 = require("../../../@aws-cdk/tmp-toolkit-helpers/src/api"); const logging_1 = require("../../lib/logging"); const cloudformation_1 = require("../api/cloudformation"); 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(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)({ 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 api_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 api_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 api_1.ToolkitError(`'${inputPath}' is not a valid path.`); } if (readFile == '') { throw new api_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, 0)).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 api_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)); 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) { (0, logging_1.info)('Resource scan in progress. Please wait, this can take 10 minutes or longer.'); await scanProgressBar(scanId, cfn); } displayTimeDiff(new Date(), new Date(curScan.StartTime)); let resources = await cfn.listResourceScanResources(scanId, options.filters); (0, logging_1.info)('finding related resources.'); let relatedResources = await cfn.getResourceScanRelatedResources(scanId, resources); (0, logging_1.info)(`Found ${relatedResources.length} resources.`); (0, logging_1.info)('Generating CFN template from scanned resources.'); const templateArn = (await cfn.createGeneratedTemplate(options.stackName, relatedResources)).GeneratedTemplateId; let generatedTemplate = await cfn.describeGeneratedTemplate(templateArn); (0, logging_1.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); } (0, logging_1.info)(''); (0, logging_1.info)('Template successfully generated!'); return buildGenertedTemplateOutput(generatedTemplate, (await cfn.getGeneratedTemplate(templateArn)).TemplateBody, templateArn); } async function findLastSuccessfulScan(cfn, options) { let resourceScanSummaries = []; const clientRequestToken = `cdk-migrate-${options.environment.account}-${options.environment.region}`; if (options.fromScan === FromScan.NEW) { (0, logging_1.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. (0, logging_1.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 api_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 api_1.ToolkitError('Only one of `--from-path` or `--from-stack` may be provided.'); } if (!stackName) { throw new api_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(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)); } (0, logging_1.info)(''); (0, logging_1.info)('✅ 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 */ function displayTimeDiff(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)); (0, logging_1.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 api_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 buildGenertedTemplateOutput(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, 0)).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) { this.cfn = cfn; } async checkForResourceScan(resourceScanSummaries, options, clientRequestToken) { if (!resourceScanSummaries || resourceScanSummaries.length === 0) { if (options.fromScan === FromScan.MOST_RECENT) { throw new api_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 { (0, logging_1.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) { (0, logging_1.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 { (0, logging_1.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 api_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 api_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 api_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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlncmF0ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm1pZ3JhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBd0NBLHdDQXVEQztBQVNELHNDQU9DO0FBUUQsb0NBV0M7QUFVRCxzQ0FlQztBQVNELDRDQXNDQztBQWdHRCx3QkFNQztBQVFELHdDQU1DO0FBd0NELGdEQWNDO0FBMENELDBDQWVDO0FBU0QsNEJBaUJDO0FBU0QsOEJBY0M7QUFRRCxrQ0FJQztBQVFELDBDQVFDO0FBU0Qsb0RBY0M7QUFRRCxnREFhQztBQVFELDBDQVNDO0FBU0Qsa0VBcUJDO0FBU0Qsd0NBSUM7QUFRRCx3REEwQkM7QUFwb0JELDBEQUEwRDtBQUMxRCx1REFBdUQ7QUFDdkQseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUc3Qiw0Q0FBa0U7QUFhbEUsNkNBQTZDO0FBQzdDLCtCQUErQjtBQUMvQixpQ0FBaUM7QUFDakMsdUVBQTZFO0FBQzdFLCtDQUF5QztBQUV6QywwREFBNEQ7QUFDNUQsa0NBQXVDO0FBQ3ZDLE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUN2QyxNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDekMsMEVBQTBFO0FBQzFFLE1BQU0sMkJBQTJCLEdBQXNCLFlBQVksQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO0FBRTFGOzs7Ozs7O0dBT0c7QUFDSSxLQUFLLFVBQVUsY0FBYyxDQUNsQyxTQUFpQixFQUNqQixLQUFhLEVBQ2IsUUFBZ0IsRUFDaEIsVUFBbUIsRUFDbkIsUUFBa0I7SUFFbEIsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsU0FBUyxDQUFDLENBQUM7SUFDN0UsTUFBTSxrQkFBa0IsR0FBRyxVQUFVLENBQUMsU0FBUyxDQUFDLENBQUM7SUFFakQsSUFBSSxDQUFDO1FBQ0gsRUFBRSxDQUFDLE1BQU0sQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDaEUsRUFBRSxDQUFDLFNBQVMsQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ3RELE1BQU0sWUFBWSxHQUFHLFFBQVEsQ0FBQztRQUM5QixNQUFNLElBQUEsY0FBTyxFQUFDO1lBQ1osSUFBSSxFQUFFLEtBQUs7WUFDWCxRQUFRO1lBQ1IsYUFBYSxFQUFFLElBQUk7WUFDbkIsWUFBWTtZQUNaLE9BQU8sRUFBRSxrQkFBa0I7WUFDM0IsU0FBUztZQUNULE9BQU8sRUFBRSxJQUFJO1NBQ2QsQ0FBQyxDQUFDO1FBRUgsSUFBSSxhQUFxQixDQUFDO1FBQzFCLFFBQVEsUUFBUSxFQUFFLENBQUM7WUFDakIsS0FBSyxZQUFZO2dCQUNmLGFBQWEsR0FBRyxHQUFHLGtCQUFrQixRQUFRLGtCQUFrQixXQUFXLENBQUM7Z0JBQzNFLE1BQU07WUFDUixLQUFLLE1BQU07Z0JBQ1QsYUFBYSxHQUFHLEdBQUcsa0JBQWtCLDRCQUE0QixTQUFTLENBQUMsa0JBQWtCLEVBQUUsRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLENBQUMsWUFBWSxDQUFDO2dCQUNqSSxNQUFNO1lBQ1IsS0FBSyxRQUFRO2dCQUNYLGFBQWEsR0FBRyxHQUFHLGtCQUFrQixJQUFJLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLElBQUksa0JBQWtCLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsV0FBVyxDQUFDO2dCQUNuSSxNQUFNO1lBQ1IsS0FBSyxRQUFRO2dCQUNYLGFBQWEsR0FBRyxHQUFHLGtCQUFrQixRQUFRLFNBQVMsQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxJQUFJLFNBQVMsQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUM7Z0JBQ2xLLE1BQU07WUFDUixLQUFLLElBQUk7Z0JBQ1AsYUFBYSxHQUFHLEdBQUcsa0JBQWtCLElBQUksa0JBQWtCLEtBQUssQ0FBQztnQkFDakUsTUFBTTtZQUNSO2dCQUNFLE1BQU0sSUFBSSxrQkFBWSxDQUNwQixHQUFHLFFBQVEseURBQXlELDJCQUEyQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUM3RyxDQUFDO1FBQ04sQ0FBQztRQUNELEVBQUUsQ0FBQyxhQUFhLENBQUMsYUFBYSxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ3ZDLElBQUksUUFBUSxFQUFFLENBQUM7WUFDYixNQUFNLElBQUEsbUJBQVksRUFBQyxrQkFBa0IsRUFBRSxHQUFHLGtCQUFrQixNQUFNLENBQUMsQ0FBQztZQUNwRSxFQUFFLENBQUMsTUFBTSxDQUFDLGtCQUFrQixFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUNsRSxDQUFDO0lBQ0gsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixFQUFFLENBQUMsTUFBTSxDQUFDLGtCQUFrQixFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUNoRSxNQUFNLEtBQUssQ0FBQztJQUNkLENBQUM7QUFDSCxDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsU0FBZ0IsYUFBYSxDQUFDLFFBQWdCLEVBQUUsU0FBaUIsRUFBRSxRQUFnQjtJQUNqRixNQUFNLGtCQUFrQixHQUFHLEdBQUcsU0FBUyxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxPQUFPLENBQUM7SUFDNUYsSUFBSSxDQUFDO1FBQ0gsT0FBTyxZQUFZLENBQUMsU0FBUyxDQUFDLFFBQVEsRUFBRSxRQUFRLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztJQUN4RSxDQUFDO0lBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNYLE1BQU0sSUFBSSxrQkFBWSxDQUFDLEdBQUcsa0JBQWtCLG1DQUFvQyxDQUFXLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztJQUN6RyxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsU0FBZ0IsWUFBWSxDQUFDLFNBQWlCO0lBQzVDLElBQUksUUFBZ0IsQ0FBQztJQUNyQixJQUFJLENBQUM7UUFDSCxRQUFRLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDaEQsQ0FBQztJQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDWCxNQUFNLElBQUksa0JBQVksQ0FBQyxJQUFJLFNBQVMsd0JBQXdCLENBQUMsQ0FBQztJQUNoRSxDQUFDO0lBQ0QsSUFBSSxRQUFRLElBQUksRUFBRSxFQUFFLENBQUM7UUFDbkIsTUFBTSxJQUFJLGtCQUFZLENBQUMsc0NBQXNDLFNBQVMscUJBQXFCLENBQUMsQ0FBQztJQUMvRixDQUFDO0lBQ0QsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQztBQUVEOzs7Ozs7O0dBT0c7QUFDSSxLQUFLLFVBQVUsYUFBYSxDQUNqQyxTQUFpQixFQUNqQixXQUF3QixFQUN4QixXQUF3QjtJQUV4QixNQUFNLGNBQWMsR0FBRyxDQUFDLE1BQU0sV0FBVyxDQUFDLGNBQWMsQ0FBQyxXQUFXLEVBQUUsQ0FBc0IsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBRXBILE1BQU0sS0FBSyxHQUFHLE1BQU0sb0NBQW1CLENBQUMsTUFBTSxDQUFDLGNBQWMsRUFBRSxTQUFTLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDaEYsSUFBSSxLQUFLLENBQUMsV0FBVyxDQUFDLGVBQWUsSUFBSSxLQUFLLENBQUMsV0FBVyxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFDN0UsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sS0FBSyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7SUFDaEQsQ0FBQztTQUFNLENBQUM7UUFDTixNQUFNLElBQUksa0JBQVksQ0FDcEIsVUFBVSxTQUFTLGdCQUFnQixXQUFXLENBQUMsT0FBTyxlQUFlLFdBQVcsQ0FBQyxNQUFNLHFCQUFxQixLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksYUFBYSxLQUFLLENBQUMsV0FBVyxDQUFDLE1BQU0saUVBQWlFLENBQ3pPLENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNJLEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxPQUFnQztJQUNyRSxNQUFNLEdBQUcsR0FBRyxJQUFJLDRCQUE0QixDQUFDLE1BQU0sY0FBYyxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUM7SUFFN0csTUFBTSxNQUFNLEdBQUcsTUFBTSxzQkFBc0IsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFFMUQsa0lBQWtJO0lBQ2xJLE1BQU0sT0FBTyxHQUFHLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3ZELElBQUksT0FBTyxDQUFDLE1BQU0sSUFBSSxVQUFVLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDN0MsSUFBQSxjQUFJLEVBQUMsNkVBQTZFLENBQUMsQ0FBQztRQUNwRixNQUFNLGVBQWUsQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDckMsQ0FBQztJQUVELGVBQWUsQ0FBQyxJQUFJLElBQUksRUFBRSxFQUFFLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFVLENBQUMsQ0FBQyxDQUFDO0lBRTFELElBQUksU0FBUyxHQUFzQixNQUFNLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQyxNQUFPLEVBQUUsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBRWpHLElBQUEsY0FBSSxFQUFDLDRCQUE0QixDQUFDLENBQUM7SUFDbkMsSUFBSSxnQkFBZ0IsR0FBRyxNQUFNLEdBQUcsQ0FBQywrQkFBK0IsQ0FBQyxNQUFPLEVBQUUsU0FBUyxDQUFDLENBQUM7SUFFckYsSUFBQSxjQUFJLEVBQUMsU0FBUyxnQkFBZ0IsQ0FBQyxNQUFNLGFBQWEsQ0FBQyxDQUFDO0lBRXBELElBQUEsY0FBSSxFQUFDLGlEQUFpRCxDQUFDLENBQUM7SUFDeEQsTUFBTSxXQUFXLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyx1QkFBdUIsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxtQkFBb0IsQ0FBQztJQUVsSCxJQUFJLGlCQUFpQixHQUFHLE1BQU0sR0FBRyxDQUFDLHlCQUF5QixDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBRXpFLElBQUEsY0FBSSxFQUFDLDZFQUE2RSxDQUFDLENBQUM7SUFDcEYsT0FBTyxpQkFBaUIsQ0FBQyxNQUFNLEtBQUssVUFBVSxDQUFDLFFBQVEsSUFBSSxpQkFBaUIsQ0FBQyxNQUFNLEtBQUssVUFBVSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQzFHLE1BQU0sU0FBUyxDQUFDLElBQUksaUJBQWlCLENBQUMsTUFBTSxpQ0FBaUMsRUFBRSxHQUFHLENBQUMsQ0FBQztRQUNwRixpQkFBaUIsR0FBRyxNQUFNLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUN2RSxDQUFDO0lBQ0QsSUFBQSxjQUFJLEVBQUMsRUFBRSxDQUFDLENBQUM7SUFDVCxJQUFBLGNBQUksRUFBQyxrQ0FBa0MsQ0FBQyxDQUFDO0lBQ3pDLE9BQU8sMkJBQTJCLENBQ2hDLGlCQUFpQixFQUNqQixDQUFDLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsWUFBYSxFQUMzRCxXQUFXLENBQ1osQ0FBQztBQUNKLENBQUM7QUFFRCxLQUFLLFVBQVUsc0JBQXNCLENBQ25DLEdBQWlDLEVBQ2pDLE9BQWdDO0lBRWhDLElBQUkscUJBQXFCLEdBQXNDLEVBQUUsQ0FBQztJQUNsRSxNQUFNLGtCQUFrQixHQUFHLGVBQWUsT0FBTyxDQUFDLFdBQVcsQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLFdBQVcsQ0FBQyxNQUFNLEVBQUUsQ0FBQztJQUN0RyxJQUFJLE9BQU8sQ0FBQyxRQUFRLEtBQUssUUFBUSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3RDLElBQUEsY0FBSSxFQUFDLGlDQUFpQyxPQUFPLENBQUMsV0FBVyxDQUFDLE9BQU8sY0FBYyxPQUFPLENBQUMsV0FBVyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDN0csSUFBSSxDQUFDO1lBQ0gsTUFBTSxHQUFHLENBQUMsaUJBQWlCLENBQUMsa0JBQWtCLENBQUMsQ0FBQztZQUNoRCxxQkFBcUIsR0FBRyxDQUFDLE1BQU0sR0FBRyxDQUFDLGlCQUFpQixFQUFFLENBQUMsQ0FBQyxxQkFBcUIsQ0FBQztRQUNoRixDQUFDO1FBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztZQUNYLCtIQUErSDtZQUMvSCxrR0FBa0c7WUFDbEcsb0dBQW9HO1lBQ3BHLElBQUEsY0FBSSxFQUFDLHNDQUF1QyxDQUFXLENBQUMsT0FBTywrQkFBK0IsQ0FBQyxDQUFDO1FBQ2xHLENBQUM7SUFDSCxDQUFDO1NBQU0sQ0FBQztRQUNOLHFCQUFxQixHQUFHLENBQUMsTUFBTSxHQUFHLENBQUMsaUJBQWlCLEVBQUUsQ0FBQyxDQUFDLHFCQUFxQixDQUFDO1FBQzlFLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLHFCQUFxQixFQUFFLE9BQU8sRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO0lBQ3JGLENBQUM7SUFDRCxnREFBZ0Q7SUFDaEQscUJBQXFCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDLENBQUMscUJBQXFCLENBQUM7SUFDOUUsSUFBSSxNQUFNLEdBQXVCLHFCQUFzQixDQUFDLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQztJQUUxRSwyRkFBMkY7SUFDM0YsS0FBSyxNQUFNLE9BQU8sSUFBSSxxQkFBc0IsRUFBRSxDQUFDO1FBQzdDLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxVQUFVLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDekMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxjQUFlLENBQUM7WUFDakMsTUFBTTtRQUNSLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxNQUFPLENBQUM7QUFDakIsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsU0FBUyxZQUFZLENBQUMsT0FBZTtJQUduQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDYixPQUFPO1lBQ0wscUJBQXFCLEVBQUUsU0FBUztZQUNoQyxzQkFBc0IsRUFBRSxTQUFTO1lBQ2pDLFNBQVMsRUFBRSxTQUFTO1lBQ3BCLFdBQVcsRUFBRSxTQUFTO1NBQ3ZCLENBQUM7SUFDSixDQUFDO0lBRUQsTUFBTSxnQkFBZ0IsR0FBa0M7UUFDdEQsWUFBWSxFQUFFLFVBQVUsQ0FBQyxtQkFBbUI7UUFDNUMsSUFBSSxFQUFFLFVBQVUsQ0FBQyxtQkFBbUI7UUFDcEMsTUFBTSxFQUFFLFVBQVUsQ0FBQyxvQkFBb0I7UUFDdkMsYUFBYSxFQUFFLFVBQVUsQ0FBQyxvQkFBb0I7S0FDL0MsQ0FBQztJQUVGLE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFdEMsSUFBSSxTQUFTLEdBQWdEO1FBQzNELENBQUMsVUFBVSxDQUFDLG1CQUFtQixDQUFDLEVBQUUsU0FBUztRQUMzQyxDQUFDLFVBQVUsQ0FBQyxvQkFBb0IsQ0FBQyxFQUFFLFNBQVM7UUFDNUMsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEVBQUUsU0FBUztRQUMvQixDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsRUFBRSxTQUFTO0tBQ2xDLENBQUM7SUFFRixLQUFLLE1BQU0sR0FBRyxJQUFJLFVBQVUsRUFBRSxDQUFDO1FBQzdCLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDOUIsSUFBSSxTQUFTLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzFCLE1BQU0sV0FBVyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM5QiwyREFBMkQ7UUFDM0QsSUFBSSxTQUFTLElBQUksZ0JBQWdCLEVBQUUsQ0FBQztZQUNsQyxTQUFTLEdBQUcsZ0JBQWdCLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDMUMsQ0FBQztRQUNELElBQUksTUFBTSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQyxRQUFRLENBQUMsU0FBZ0IsQ0FBQyxFQUFFLENBQUM7WUFDekQsU0FBUyxDQUFDLFNBQW1DLENBQUMsR0FBRyxXQUFXLENBQUM7UUFDL0QsQ0FBQzthQUFNLENBQUM7WUFDTixNQUFNLElBQUksa0JBQVksQ0FBQyxtQkFBbUIsU0FBUyxFQUFFLENBQUMsQ0FBQztRQUN6RCxDQUFDO0lBQ0gsQ0FBQztJQUNELE9BQU8sU0FBUyxDQUFDO0FBQ25CLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxTQUFnQixNQUFNLENBQUMsSUFBVyxFQUFFLFNBQWlCO0lBQ25ELE1BQU0sV0FBVyxHQUFZLEVBQUUsQ0FBQztJQUNoQyxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQUksU0FBUyxFQUFFLENBQUM7UUFDaEQsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLEdBQUcsU0FBUyxDQUFDLENBQUMsQ0FBQztJQUNqRCxDQUFDO0lBQ0QsT0FBTyxXQUFXLENBQUM7QUFDckIsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsU0FBZ0IsY0FBYyxDQUFDLE9BQWdCLEVBQUUsTUFBZTtJQUM5RCxPQUFPO1FBQ0wsT0FBTyxFQUFFLE9BQU8sSUFBSSx3QkFBZTtRQUNuQyxNQUFNLEVBQUUsTUFBTSxJQUFJLHVCQUFjO1FBQ2hDLElBQUksRUFBRSxpQkFBaUI7S0FDeEIsQ0FBQztBQUNKLENBQUM7QUFFRDs7R0FFRztBQUNILElBQVkscUJBSVg7QUFKRCxXQUFZLHFCQUFxQjtJQUMvQixzQ0FBYSxDQUFBO0lBQ2Isd0NBQWUsQ0FBQTtJQUNmLHNDQUFhLENBQUE7QUFDZixDQUFDLEVBSlcscUJBQXFCLHFDQUFyQixxQkFBcUIsUUFJaEM7QUFVRDs7R0FFRztBQUNILElBQVksVUFJWDtBQUpELFdBQVksVUFBVTtJQUNwQix5Q0FBMkIsQ0FBQTtJQUMzQixtQ0FBcUIsQ0FBQTtJQUNyQiwrQkFBaUIsQ0FBQTtBQUNuQixDQUFDLEVBSlcsVUFBVSwwQkFBVixVQUFVLFFBSXJCO0FBRUQsSUFBWSxVQUtYO0FBTEQsV0FBWSxVQUFVO0lBQ3BCLHlEQUEyQyxDQUFBO0lBQzNDLDJEQUE2QyxDQUFBO0lBQzdDLGlDQUFtQixDQUFBO0lBQ25CLHFDQUF1QixDQUFBO0FBQ3pCLENBQUMsRUFMVyxVQUFVLDBCQUFWLFVBQVUsUUFLckI7QUFFRDs7OztHQUlHO0FBQ0gsU0FBZ0Isa0JBQWtCLENBQUMsUUFBaUIsRUFBRSxTQUFtQixFQUFFLFNBQWtCO0lBQzNGLElBQUksUUFBUSxJQUFJLFNBQVMsRUFBRSxDQUFDO1FBQzFCLE1BQU0sSUFBSSxrQkFBWSxDQUFDLDhEQUE4RCxDQUFDLENBQUM7SUFDekYsQ0FBQztJQUNELElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUNmLE1BQU0sSUFBSSxrQkFBWSxDQUFDLHFDQUFxQyxDQUFDLENBQUM7SUFDaEUsQ0FBQztJQUNELElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUM1QixPQUFPLEVBQUUsTUFBTSxFQUFFLHFCQUFxQixDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2hELENBQUM7SUFDRCxJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQ2IsT0FBTyxFQUFFLE1BQU0sRUFBRSxxQkFBcUIsQ0FBQyxJQUFJLEVBQUUsWUFBWSxFQUFFLFFBQVEsRUFBRSxDQUFDO0lBQ3hFLENBQUM7SUFDRCxPQUFPLEVBQUUsTUFBTSxFQUFFLHFCQUFxQixDQUFDLEtBQUssRUFBRSxTQUFTLEVBQUUsU0FBVSxFQUFFLENBQUM7QUFDeEUsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsU0FBUyxjQUFjLENBQUMsWUFBK0I7SUFDckQsT0FBTyxZQUFZO1NBQ2hCLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDO1NBQ2hDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNYLFlBQVksRUFBRSxDQUFDLENBQUMsWUFBYTtRQUM3QixrQkFBa0IsRUFBRSxDQUFDLENBQUMsa0JBQW1CO0tBQzFDLENBQUMsQ0FBQyxDQUFDO0FBQ1IsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILFNBQVMsbUJBQW1CLENBQUMsWUFBK0I7SUFDMUQsTUFBTSxXQUFXLEdBQWdDLEVBQUUsQ0FBQztJQUNwRCxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUU7UUFDekIsTUFBTSxVQUFVLEdBQThCO1lBQzVDLFlBQVksRUFBRSxDQUFDLENBQUMsWUFBYTtZQUM3QixrQkFBa0IsRUFBRSxDQUFDLENBQUMsa0JBQW1CO1NBQzFDLENBQUM7UUFDRixXQUFXLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQy9CLENBQUMsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxXQUFXLENBQUM7QUFDckIsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0ksS0FBSyxVQUFVLGVBQWUsQ0FBQyxNQUFjLEVBQUUsR0FBaUM7SUFDckYsSUFBSSxXQUFXLEdBQUcsR0FBRyxDQUFDO0lBQ3RCLHFGQUFxRjtJQUNyRixJQUFJLE9BQU8sR0FBc0M7UUFDL0MsTUFBTSxFQUFFLFVBQVUsQ0FBQyxXQUFXO1FBQzlCLFNBQVMsRUFBRSxFQUFFO0tBQ2QsQ0FBQztJQUNGLE9BQU8sT0FBTyxDQUFDLE1BQU0sSUFBSSxVQUFVLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDaEQsT0FBTyxHQUFHLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2pELFdBQVcsR0FBRyxPQUFPLENBQUMsbUJBQW1CLElBQUksV0FBVyxDQUFDO1FBQ3pELFFBQVEsQ0FBQyxFQUFFLEVBQUUsV0FBVyxDQUFDLENBQUM7UUFDMUIsTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQzVELENBQUM7SUFDRCxJQUFBLGNBQUksRUFBQyxFQUFFLENBQUMsQ0FBQztJQUNULElBQUEsY0FBSSxFQUFDLGtCQUFrQixDQUFDLENBQUM7QUFDM0IsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILFNBQWdCLFFBQVEsQ0FBQyxLQUFhLEVBQUUsUUFBZ0I7SUFDdEQsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUNwQyxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUM7UUFDdkIsTUFBTSxhQUFhLEdBQUcsQ0FBQyxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDOUQsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxRQUFRLEdBQUcsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQzdDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQztRQUMxQyxNQUFNLEtBQUssR0FBRyxVQUFVLEdBQUcsUUFBUSxDQUFDO1FBQ3BDLE1BQU0sU0FBUyxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBRTVDLE1BQU0sU0FBUyxHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQ3ZELE1BQU0sV0FBVyxHQUFHLGFBQWEsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLFNBQVMsR0FBRyxhQUFhLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUNoRixNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLEN