async function configWizard()

in source/packages/services/installer/src/utils/wizard.ts [18:193]


async function configWizard(
    environment: string,
    region: string,
    dryRun = false
): Promise<Answers> {
    const modules = loadModules(environment);

    const accountId = await getCurrentAwsAccountId(region);
    const accountConfirmation = await inquirer.prompt([
        {
            message: `Detected account ${chalk.blue(
                accountId
            )} based on credentials in use. Is this correct?`,
            type: 'confirm',
            name: 'confirm',
            default: true,
        },
    ]);

    if (accountConfirmation.confirm === false) {
        console.log(
            chalk.red(
                '\nAborting deployment. Please ensure you are running the installer with the correct credentials.\n'
            )
        );
        throw new Error('Aborted');
    }

    const answersStorage = new AnswersStorage(accountId, region, environment);

    let answers: Answers = answersStorage.loadFromDefaultPath();
    answersStorage.save(answers);

    // reset answers that are specific to each deployment run, such as confirmations for deployments to continue
    delete answers.modules?.confirm;
    delete answers.modules?.expandedMandatory;
    delete answers.modules?.expandedIncludingOptional;

    const previouslyChosenModulesSet = new Set([
        ...(answers.modules?.list ?? []),
        ...(answers.modules?.expandedMandatory ?? []),
        ...(answers.modules?.expandedIncludingOptional ?? []),
    ]);

    // obtain list of service modules to choose from to deploy
    const servicesList = buildServicesList(modules, answers.modules?.list);
    answers = await inquirer.prompt(
        [chooseServicesPrompt(servicesList), confirmServicesPrompt(modules)],
        answers
    );

    answersStorage.save(answers);

    if ((answers.modules?.confirm ?? false) === false) {
        console.log(chalk.bgRed('Aborted!'));
        throw new Error('Aborted');
    }

    // based on selected service modules, obtain full list of service and infrastructure modules to configure and install based on configured dependencies
    answers.modules.expandedMandatory = expandModuleList(modules, answers.modules.list, false);

    answers.modules.expandedIncludingOptional = expandModuleList(
        modules,
        answers.modules.list,
        true
    );

    const newlyChosenModulesSet = new Set([
        ...(answers.modules?.list ?? []),
        ...(answers.modules?.expandedMandatory ?? []),
        ...(answers.modules?.expandedIncludingOptional ?? []),
    ]);

    for (const module of previouslyChosenModulesSet) {
        if (!newlyChosenModulesSet.has(module as ModuleName)) {
            // answer for modules that are unchecked will be deleted
            delete answers[`${module}`];
        }
    }

    const expandedFriendlyNames = modules
        .filter((m) => answers.modules.expandedIncludingOptional.includes(m.name))
        .map((m) => m.friendlyName);
    console.log(
        chalk.green(
            `\nIncluding dependencies, the full list of modules to be evaluated is:\n${expandedFriendlyNames
                .map((m) => `   - ${m}`)
                .join('\n')}\n`
        )
    );

    // TODO: verify that bundles exist for all the selected modules
    answers = await inquirer.prompt(
        [
            chooseS3BucketPrompt(
                'Provide the name of an existing S3 bucket to store artifacts for this CDF environment:',
                's3.bucket',
                answers.s3?.bucket
            ),
            {
                message: `Enter custom tags to be applied to all resources created for this CDF environment. You can enter up to 48 tags in the format key1;val1;key2;val2;...`,
                type: 'input',
                name: 'customTags',
                default: answers.customTags ?? '',
                askAnswered: true,
                validate(answer: string) {
                    let tagsList: TagsList;
                    try {
                        tagsList = new TagsList(answer);
                    } catch {
                        return `Please enter tags in the format key1;val1;key2;val2;... There must be an even number of keys/values.`;
                    }
                    if (tagsList.length > 48 * 2) {
                        return `You entered ${tagsList.length} tags but the maximum is 48. The CloudFormation service limit is 50, CDF always includes cdf_environment and cdf_service.`;
                    }
                    for (let idx = 0; idx < tagsList.length; idx++) {
                        const tag = tagsList.tags[idx];
                        if (tag.key === 'cdf_environment' || tag.key == 'cdf_service') {
                            return 'The tags cdf_environment and cdf_service are always included and cannot be specified as custom tags.';
                        }
                        if (!isValidTagKey(tag.key)) {
                            return `"${tag.key}" is not a valid tag key. Tag keys can contain up to 128 characters, numbers, and any of +\\-=._:@. Keys must not start with "aws:" or equal ".", "..", "_index", or the empty string. See also https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#tag-restrictions`;
                        }
                        if (!isValidTagValue(tag.value)) {
                            return `"${tag.value}" is not a valid tag value. Tag keys can contain up to 256 characters, numbers, spaces, and any of +\\-=._:/@. Values should not start or end with a space. See also: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#tag-restrictions`;
                        }
                    }

                    return true;
                },
            },
        ],
        answers
    );

    answers.dryRun = dryRun;
    answersStorage.save(answers);

    let optionalModuleAdded = true;

    const answeredModule: { [key: string]: boolean } = {};

    while (optionalModuleAdded) {
        const originalExpandedMandatory = clone(answers.modules.expandedMandatory);

        const mandatoryGrouped = topologicallySortModules(
            modules,
            answers.modules.expandedMandatory
        );

        for (const layer of mandatoryGrouped) {
            for (const name of layer) {
                const m = modules.find((m) => m.name === name);
                if (m.prompts && !answeredModule[name]) {
                    console.log(chalk.yellow(`\n${m.friendlyName}...`));
                    answers = await m.prompts(answers);
                    answersStorage.save(answers);
                    answeredModule[name] = true;
                }
            }
        }
        optionalModuleAdded =
            originalExpandedMandatory.length !== answers.modules.expandedMandatory.length;
    }

    delete answers.dryRun;
    answersStorage.save(answers);

    console.log(
        chalk.green(
            `Configuration has been saved to '${answersStorage.getConfigurationFilePath()}'.\nIt is highly recommended that you store this configuration under source control to automate future deployments.`
        )
    );

    return answers;
}