in packages/@aws-cdk/toolkit-lib/lib/api/resource-import/importer.ts [324:414]
private async askForResourceIdentifier(
resourceIdentifiers: ResourceIdentifiers,
chg: ImportableResource,
): Promise<ResourceIdentifierProperties | undefined> {
const resourceName = this.describeResource(chg.logicalId);
// Skip resources that do not support importing
const resourceType = chg.resourceDiff.newResourceType;
if (resourceType === undefined || !(resourceType in resourceIdentifiers)) {
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_WARN.msg(`${resourceName}: unsupported resource type ${resourceType}, skipping import.`));
return undefined;
}
const idPropSets = resourceIdentifiers[resourceType];
// Retain only literal strings: strip potential CFN intrinsics
const resourceProps = Object.fromEntries(Object.entries(chg.resourceDefinition.Properties ?? {})
.filter(([_, v]) => typeof v === 'string')) as Record<string, string>;
// Find property sets that are fully satisfied in the template, ask the user to confirm them
const satisfiedPropSets = idPropSets.filter(ps => ps.every(p => resourceProps[p]));
for (const satisfiedPropSet of satisfiedPropSets) {
const candidateProps = Object.fromEntries(satisfiedPropSet.map(p => [p, resourceProps[p]]));
const displayCandidateProps = fmtdict(candidateProps);
if (await promptly.confirm(
`${chalk.blue(resourceName)} (${resourceType}): import with ${chalk.yellow(displayCandidateProps)} (yes/no) [default: yes]? `,
{ default: 'yes' },
)) {
return candidateProps;
}
}
// If we got here and the user rejected any available identifiers, then apparently they don't want the resource at all
if (satisfiedPropSets.length > 0) {
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(chalk.grey(`Skipping import of ${resourceName}`)));
return undefined;
}
// We cannot auto-import this, ask the user for one of the props
// The only difference between these cases is what we print: for multiple properties, we print a preamble
const prefix = `${chalk.blue(resourceName)} (${resourceType})`;
let preamble;
let promptPattern;
if (idPropSets.length > 1) {
preamble = `${prefix}: enter one of ${idPropSets.map(x => chalk.blue(x.join('+'))).join(', ')} to import (all empty to skip)`;
promptPattern = `${prefix}: enter %`;
} else {
promptPattern = `${prefix}: enter %`;
}
// Do the input loop here
if (preamble) {
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(preamble));
}
for (const idProps of idPropSets) {
const input: Record<string, string> = {};
for (const idProp of idProps) {
// If we have a value from the template, use it as default. This will only be a partial
// identifier if present, otherwise we would have done the import already above.
const defaultValue = resourceProps[idProp] ?? '';
const prompt = [
promptPattern.replace(/%/g, chalk.blue(idProp)),
defaultValue
? `[${defaultValue}]`
: '(empty to skip)',
].join(' ') + ':';
const response = await promptly.prompt(prompt,
{ default: defaultValue, trim: true },
);
if (!response) {
break;
}
input[idProp] = response;
// Also stick this property into 'resourceProps', so that it may be reused by a subsequent question
// (for a different compound identifier that involves the same property). Just a small UX enhancement.
resourceProps[idProp] = response;
}
// If the user gave inputs for all values, we are complete
if (Object.keys(input).length === idProps.length) {
return input;
}
}
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(chalk.grey(`Skipping import of ${resourceName}`)));
return undefined;
}