private async askForResourceIdentifier()

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;
  }