Skip to content

Commit

Permalink
fix(cli): cannot cdk import resources that allow multiple identifiers
Browse files Browse the repository at this point in the history
Some resources that can be imported by CloudFormation allow multiple
identifiers. For example, `AWS::DynamoDB::GlobalTable` allows
`TableName`, `TableArn`, and `TableStreamArn`.

The CLI used to interpret multiple identifiers as "all must be present",
but the contract is actually "exactly one must be present":

* CloudFormation would fail the import if you supply all the fields;
* but CDK would skip the import if you left one out.

The effect is that it is impossible to import resources that allow
more than one identifier.

Fix this by being satisfied as soon as we have one identifier from
the set, instead of expecting all of them to be filled.

Fixes #20895.
  • Loading branch information
rix0rrr committed Mar 3, 2023
1 parent 2a81f0f commit 64446d7
Showing 1 changed file with 43 additions and 22 deletions.
65 changes: 43 additions & 22 deletions packages/aws-cdk/lib/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,15 @@ export class ResourceImporter {
return ret;
}

/**
* Ask for the importable identifier for the given resource
*
* There may be more than one identifier under which a resource can be imported. The `import`
* operation needs exactly one of them.
*
* - If we can get one from the template, we will use one.
* - Otherwise, we will ask the user for one of them.
*/
private async askForResourceIdentifier(
resourceIdentifiers: ResourceIdentifiers,
chg: ImportableResource,
Expand All @@ -247,42 +256,54 @@ export class ResourceImporter {
const idProps = resourceIdentifiers[resourceType];
const resourceProps = chg.resourceDefinition.Properties ?? {};

const fixedIdProps = idProps.filter(p => resourceProps[p]);
const fixedIdInput: ResourceIdentifierProperties = Object.fromEntries(fixedIdProps.map(p => [p, resourceProps[p]]));
// Find a property that's in the template already
const templateIdProp = idProps.find(p => resourceProps[p]);

const missingIdProps = idProps.filter(p => !resourceProps[p]);

if (missingIdProps.length === 0) {
// We can auto-import this, but ask the user to confirm
const props = fmtdict(fixedIdInput);
if (templateIdProp !== undefined) {
// We can auto-import this, but ask the user to confirm. Pick any property we can use, let's do the first one.
const candidateProps = { [templateIdProp]: resourceProps[templateIdProp] };
const displayCandidateProps = fmtdict(candidateProps);

if (!await promptly.confirm(
`${chalk.blue(resourceName)} (${resourceType}): import with ${chalk.yellow(props)} (yes/no) [default: yes]? `,
`${chalk.blue(resourceName)} (${resourceType}): import with ${chalk.yellow(displayCandidateProps)} (yes/no) [default: yes]? `,
{ default: 'yes' },
)) {
print(chalk.grey(`Skipping import of ${resourceName}`));
return undefined;
}

return candidateProps;
}

// 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 (idProps.length > 1) {
preamble = `${prefix}: enter one of ${idProps.map(chalk.blue).join(', ')} to import (all empty to skip)`;
promptPattern = `${prefix}: enter %:`;
} else {
promptPattern = `${prefix}: enter % to import (empty to skip):`;
}

// Ask the user to provide missing props
const userInput: ResourceIdentifierProperties = {};
for (const missingIdProp of missingIdProps) {
const response = (await promptly.prompt(
`${chalk.blue(resourceName)} (${resourceType}): enter ${chalk.blue(missingIdProp)} to import (empty to skip):`,
// Do the input loop here
if (preamble) {
print(preamble);
}
for (const idProp of idProps) {
const prompt = promptPattern.replace(/%/, chalk.blue(idProp));
const response = (await promptly.prompt(prompt,
{ default: '', trim: true },
));
if (!response) {
print(chalk.grey(`Skipping import of ${resourceName}`));
return undefined;

if (response) {
return { [idProp]: response };
}
userInput[missingIdProp] = response;
}

return {
...fixedIdInput,
...userInput,
};
print(chalk.grey(`Skipping import of ${resourceName}`));
return undefined;
}

/**
Expand Down Expand Up @@ -364,4 +385,4 @@ function addDefaultDeletionPolicy(resource: any): any {
export interface DiscoverImportableResourcesResult {
readonly additions: ImportableResource[];
readonly hasNonAdditions: boolean;
}
}

0 comments on commit 64446d7

Please sign in to comment.