aws-cdk
Version:
AWS CDK CLI, the command line tool for CDK apps
801 lines • 113 kB
JavaScript
"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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlncmF0ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm1pZ3JhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBd0NBLHdDQXVEQztBQVNELHNDQU9DO0FBUUQsb0NBV0M7QUFVRCxzQ0FlQztBQVNELDRDQXNDQztBQWdHRCx3QkFNQztBQVFELHdDQU1DO0FBd0NELGdEQWNDO0FBMENELDBDQWVDO0FBU0QsNEJBaUJDO0FBU0QsOEJBY0M7QUFRRCxrQ0FJQztBQVFELDBDQVFDO0FBU0Qsb0RBY0M7QUFRRCxnREFhQztBQVFELDBDQVNDO0FBU0Qsa0VBcUJDO0FBU0Qsd0NBSUM7QUFRRCx3REEwQkM7QUFwb0JELDBEQUEwRDtBQUMxRCx1REFBdUQ7QUFDdkQseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUc3Qiw0Q0FBa0U7QUFhbEUsNkNBQTZDO0FBQzdDLCtCQUErQjtBQUMvQixpQ0FBaUM7QUFDakMsdUVBQTZFO0FBQzdFLCtDQUF5QztBQUV6QywwREFBNEQ7QUFDNUQsa0NBQXVDO0FBQ3ZDLE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUN2QyxNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDekMsMEVBQTBFO0FBQzFFLE1BQU0sMkJBQTJCLEdBQXNCLFlBQVksQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO0FBRTFGOzs7Ozs7O0dBT0c7QUFDSSxLQUFLLFVBQVUsY0FBYyxDQUNsQyxTQUFpQixFQUNqQixLQUFhLEVBQ2IsUUFBZ0IsRUFDaEIsVUFBbUIsRUFDbkIsUUFBa0I7SUFFbEIsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsU0FBUyxDQUFDLENBQUM7SUFDN0UsTUFBTSxrQkFBa0IsR0FBRyxVQUFVLENBQUMsU0FBUyxDQUFDLENBQUM7SUFFakQsSUFBSSxDQUFDO1FBQ0gsRUFBRSxDQUFDLE1B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