"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AzureResourceConfigurator = exports.RegExValidator = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const _ = require("lodash");
const cli_tools_1 = require("@codiac.io/cli-tools");
const codiac_common_1 = require("@codiac.io/codiac-common");
const contracts_1 = require("../../apis/codiac-api/contracts");
const codiac_relay_1 = require("../../apis/codiac-relay");
const contracts_2 = require("../../relay/contracts");
const codiac_ops_common_1 = require("@codiac.io/codiac-ops-common");
const cli_common_1 = require("../../cli-common");
const image_registry_1 = require("@codiac.io/codiac-domain/image-registry");
const cluster_def_1 = require("@codiac.io/codiac-domain/cluster-def");
const security_account_1 = require("@codiac.io/codiac-domain/security-account");
const codiac_constants_1 = require("../../codiac-constants");
const ops_1 = require("../../ops");
const ingress_exposure_enum_1 = require("../../codiac-entities/ingress-exposure-enum");
const type_safety_utils_1 = require("../../uilogic/type-safety-utils");
const non_empty_validator_1 = require("../../uilogic/validators/non-empty-validator");
const DEFAULT_STARTING_NODE_COUNT = 2;
const ORIGINAL_NODE_POOL_NAME = "original";
/** Parameters by which to validate a string with a regular expression. */
class RegExValidator {
    constructor(rx, whyInvalid, allowUndefined = false) {
        this.rx = rx;
        this.whyInvalid = whyInvalid;
        this.allowUndefined = allowUndefined;
    }
    isValid(candidate) {
        let result = { isValid: false };
        if (candidate === undefined || candidate === "") {
            result.isValid = this.allowUndefined;
        }
        else {
            result.isValid = this.rx.test(candidate);
        }
        if (!result.isValid) {
            result.reason = this.whyInvalid;
        }
        return result;
    }
}
exports.RegExValidator = RegExValidator;
RegExValidator.COMMON_REGEXES = {
    /** From regexr.com...
     *
     * Will find valid ip-addresses between 0.0.0.0 and 255.255.255.255.All numbers must be in range 0-255.
     *
     * Improved with non-capturing groups. So the capturing groups existing will only be the 4 different numbers of the ip-address.
     *
     * by Artheus
     */
    // IPAddress: /(1?[1-9]?[0-9]|2?(?:[0-4]?[0-9]|5[0-5]))\.(1?[1-9]?[0-9]|2?(?:[0-4]?[0-9]|5[0-5]))\.(1?[1-9]?[0-9]|2?(?:[0-4]?[0-9]|5[0-5]))\.(1?[1-9]?[0-9]|2?(?:[0-4]?[0-9]|5[0-5]))\b/
    IPAddress: /\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/ig // from: https://regexr.com/38odc
};
;
/** Walks the user through the selection of an existing
 * or the declaration of a new image registry.
 */
let AzureResourceConfigurator = class AzureResourceConfigurator {
    constructor(genRelayClient, codiacUser, _ui, _styler, logger) {
        this.genRelayClient = genRelayClient;
        this.codiacUser = codiacUser;
        this._ui = _ui;
        this._styler = _styler;
        this.logger = logger;
        this.cloudProvider = contracts_1.CloudProviderEnum.azure;
        /** Currently supporting only a single reg type per cloud provider. */
        this.regType = contracts_1.ImageRegistryTypes.azureCr;
        /** Validates the given name for use as the name of an entity in Azure.
         *
         * 1-50
         * alphanumerics and hyphens,
         * starting with a letter and ending with an alphanumeric.
         *
         * @see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules
         */
        this.azServiceNameValidator = new RegExValidator(/^[a-zA-Z]([\w-]{0,48}\w)?$/, "must be 1-50 alphanumerics or hyphens, starting with a letter, ending with alphanumeric", false);
        /** Validates the given name for use as the name of an entity in Azure.
         *
         *  1-90
         *  Underscores, hyphens, periods, and letters or digits as defined by the Char.IsLetterOrDigit function.
         *
         *  Valid characters are members of the following categories in UnicodeCategory:
         *  UppercaseLetter,
         *  LowercaseLetter,
         *  TitlecaseLetter,
         *  ModifierLetter,
         *  OtherLetter,
         *  DecimalDigitNumber.
         *
         *  Can't end with period.
         *
         * NOTE: the azure portal says Parentheses `()` are also valid characters in a resource group name.
         *
         * @see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftresources
         */
        this.azRgNameValidator = new RegExValidator(/^[a-zA-Z\d_\-\(\)\.]{0,89}[a-zA-Z\d_\-\(\)]$/, "must be 1-90 alphanumerics, hyphens, periods, or parentheses, not ending with a period", false);
        /** Validates the given name for use as the name of a userAssignedIdentity in Azure.
         *
         * 3-128 Alphanumerics, hyphens, and underscores,
         * starting with a letter or number.
         *
         * @see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftmanagedidentity
         */
        this.azIdentityNameValidator = new RegExValidator(/^[a-zA-Z\d][a-zA-Z\d_\-]{0,127}$/, "must be 3-128 alphanumerics, hyphens, or underscores, starting with a letter or number", true);
        this.cliExec = new cli_tools_1.CliExec(this.logger);
    }
    /* ***********************************************************************************
    *   _                 _
    *  | |               (_)
    *  | |     ___   __ _ _ _ __
    *  | |    / _ \ / _` | | '_ \
    *  | |___| (_) | (_| | | | | |
    *  \_____/\___/ \__, |_|_| |_|
    *                __/ |
    *               |___/
    *
    * ************************************************************************************/
    promptToLogin(providerSubscriptionId, suppressConfirmations, providedUserName, providedSecret) {
        var _a, _b;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let isAuthed = false;
                let userName = undefined;
                let secret = undefined;
                if (!suppressConfirmations) {
                    this._ui.userMessage(`Enter your credentials for ${this._styler.hilite(this.cloudProvider)}`);
                    userName = yield this._ui.prompt('regUsername', "Username (Hit enter if using a token): ", false);
                    if (userName === undefined || userName === "")
                        userName = "<token>"; // TODO: make username nullable on the authPvd, so this becomes the job of the auth provider
                    secret = yield this._ui.promptPassword('regSecret', `Password (or token): `);
                }
                else {
                    if (!providedUserName || !providedSecret)
                        throw new Error("Must provide cloud provider username/pwd to login silent mode");
                    userName = providedUserName;
                    secret = providedSecret;
                }
                try {
                    this._ui.startSpinner(`Authenticating with ${this.cloudProvider}...`);
                    // let authResult = await this.relayClient.logIntoCloudProvider(this.cloudProvider, { Username: userName, Secret: secret });
                    let authResult = yield this.genRelayClient.Call.csp_login({ providerCode: this.cloudProvider, username: userName, secret: secret });
                    isAuthed = authResult !== undefined; // <-- because authenticate throws on failures.  // authResult.success;
                    this._ui.stopSpinner("Success!");
                }
                catch (err) {
                    this.logger.debug(`Error authenticating with ${this.cloudProvider}: ${(_a = err.name) !== null && _a !== void 0 ? _a : ""} ${(_b = err.message) !== null && _b !== void 0 ? _b : ""}`);
                    this._ui.stopSpinner("Failed.");
                }
                if (isAuthed)
                    this._ui.userMessage(`Logged into ${this._styler.hilite(this.cloudProvider)} as [${this._styler.hilite(userName !== null && userName !== void 0 ? userName : "token")}]`);
                // let subxId = await this.promptForSubscriptionId(userName, providerSubscriptionId);
                return [isAuthed, userName];
            }
            catch (err) {
                throw err;
            }
        });
    }
    /** Suffixes the username with its applicable csp subscriptionId
     *
     * as:
     * ```
     * "<username> (<subscriptionId>)"
     * ``` */
    userAndSubx(user) {
        return (user.subscriptionId) ? user.username.concat(" (", user.subscriptionId, ")") : user.username;
    }
    selectCachedUser(providerSubscriptionId, cspTenantId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                const loginResult = yield this.genRelayClient.Call.get_current_users_for({ providerCode: this.cloudProvider, providerSubscriptionId: providerSubscriptionId, providerTenantId: cspTenantId });
                if (loginResult.error)
                    throw loginResult.error;
                if ((loginResult.output == undefined) || loginResult.output.length == 0)
                    throw new Error(`No ${this.cloudProvider} account has been authenticated.`);
                let azUser;
                if (loginResult.output.length == 1) {
                    azUser = loginResult.output[0];
                }
                else {
                    let index = 0;
                    let choices = loginResult.output.map(x => { return { name: this.userAndSubx(x), value: index++ }; });
                    let selected = (yield this._ui.promptSelect("cspSubxUser", `Select ${this.cloudProvider} credentials`, choices));
                    azUser = loginResult.output[selected];
                }
                return azUser;
            }
            catch (err) {
                throw err;
            }
        });
    }
    /* ***********************************************************************************
    * ______           _     _
    * | ___ \         (_)   | |
    * | |_/ /___  __ _ _ ___| |_ _ __ _   _
    * |    // _ \/ _` | / __| __| '__| | | |
    * | |\ \  __/ (_| | \__ \ |_| |  | |_| |
    * \_| \_\___|\__, |_|___/\__|_|   \__, |
    *             __/ |                __/ |
    *            |___/                |___/
    *
    * ***********************************************************************************/
    captureImageRegistry() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c;
                try {
                    let registry;
                    const azUser = yield this.selectCachedUser();
                    // Select subscription from the ones the user has access to
                    let subxId = yield this.promptForSubscriptionId(azUser);
                    // Select a container registry
                    this._ui.startSpinner(`Fetching Azure container registries `);
                    let regResult = yield this.genRelayClient.Call.getAzureAcrs({ subscriptionId: subxId });
                    let regs = (_a = regResult.output) !== null && _a !== void 0 ? _a : [];
                    if (regResult.error) {
                        this._ui.stopSpinner("Error!");
                        // this.logger.warn(`Error retrieving container registries: ${regResult.error.message ?? regResult.message}`);
                        throw regResult.error; // Gotta throw - we can't continue if they have no registries to choose from.
                    }
                    else {
                        this._ui.stopSpinner(`${(_c = (_b = regResult.output) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0} found.`);
                        if (regs.length === 0) {
                            throw new Error(`No registries found.`);
                        }
                    }
                    let regChoices = regs.map(r => { return { name: `${r.name} (resource group: ${r.resourceGroup})`, value: r.id }; });
                    let regId = (yield this._ui.promptSelect("reg", "Container Registry: ", regChoices));
                    let reg = (regs.find(r => r.id === regId)); // The "!" operator is ok - We just selected from the list.
                    // az acr list --query [].name --subscription 37dbb924-5ee5-470b-b2a3-187e9dea7008
                    // select container registry (login, select subscription), then from that get the scope/registryUrl
                    // let acrName = await this._ui.prompt<string>('scope', 'Name of the ACR: ', true, /* registry.scope || defaults.scope */);
                    // // regCode = registry.scope;
                    // let serverUrl = `${acrName}.azurecr.io`; // <---  HACK!!!!!  Replace this with an ACR selector!!!
                    registry = new image_registry_1.AzureContainerRegistry(reg.name, reg.id, reg.resourceGroup, subxId, reg.location);
                    registry.pullRequiresAuth = true;
                    // registry = new ImageRegistry(reg.name, reg.loginServer, this.cloudProvider, this.regType);
                    resolve(registry);
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    promptForSubscriptionId(azUser, defaultValue) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                let output;
                try {
                    let subxResult = yield this.genRelayClient.Call.getAzureSubscriptions({ subscriptionId: azUser.subscriptionId, tenantId: azUser.cspTenantId });
                    if (subxResult.error) {
                        this.logger.warn(`Error retrieving subscriptions: ${(_a = subxResult.error.message) !== null && _a !== void 0 ? _a : subxResult.message}`);
                    }
                    let subs = (_b = subxResult.output) !== null && _b !== void 0 ? _b : [];
                    let found = subs.find(s => s.id === defaultValue);
                    let keep = false;
                    if (defaultValue && found) {
                        keep = yield this._ui.promptConfirm("keepSubx", `Azure Subscription: ${this._styler.hilite(found.name)} (${defaultValue})`);
                    }
                    if (keep === true && defaultValue !== undefined) {
                        output = defaultValue;
                    }
                    else {
                        let choices = [];
                        if (subs.length > 0) {
                            choices = subs.filter(s => s.user.name === azUser.username)
                                .map(s => { return { name: `${s.name} (...${s.id.substring(s.id.length - 4)})`, value: s.id }; });
                            // choices = _.orderBy(choices, c => c.name);
                            if (choices.length > 0) {
                                output = (yield this._ui.promptSelect("subx", "Azure Subscription: ", choices));
                            }
                            else {
                                this._ui.userMessage(`No Azure subscriptions found for the user [${azUser}].`);
                                output = (yield this._ui.prompt("subx", "Azure Subscription Id: ", true, defaultValue));
                            }
                        }
                        else {
                            output = (yield this._ui.prompt("subx", "Azure Subscription Id: ", true, defaultValue));
                        }
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    /* ***********************************************************************************
    *   _____ _           _
    *  /  __ \ |         | |
    *  | /  \/ |_   _ ___| |_ ___ _ __
    *  | |   | | | | / __| __/ _ \ '__|
    *  | \__/\ | |_| \__ \ ||  __/ |
    *   \____/_|\__,_|___/\__\___|_|
    *
    *  ***********************************************************************************/
    // For info on accessing the Azure Resource Manager REST api,
    // see: https://docs.microsoft.com/en-us/rest/api/azure/#create-the-request
    defineCluster(initial, defaults, cspTenantId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c, _d, _e, _f, _g, _h;
                let output;
                try {
                    let workingModel = {};
                    workingModel.provider = this.cloudProvider;
                    workingModel.privateOwner = this.codiacUser.tenantCode;
                    let isValid = true;
                    // APPLY DEFAULTS FOR THIS CSP
                    let switchedCsp = Boolean(defaults !== undefined && (defaults.provider !== this.cloudProvider));
                    if (defaults === undefined || (defaults.provider !== this.cloudProvider)) {
                        let defaultResult = yield this.genRelayClient.Call.getClusterDefaults({ provider: this.cloudProvider });
                        if (defaultResult.error) {
                            this.logger.warn(`Error retrieving defaults for ${this.cloudProvider} cluster: ${(_a = defaultResult.error.name) !== null && _a !== void 0 ? _a : ""} ${(_b = defaultResult.error.message) !== null && _b !== void 0 ? _b : ""}`);
                        }
                        defaults = (_c = defaultResult.output) !== null && _c !== void 0 ? _c : {}; // fall back to an empty object if we failed, so that we dont keep looking this up on subsequent calls.
                        let assignment = (objVal, srcVal) => Boolean(srcVal === undefined) ? objVal : srcVal;
                        if (switchedCsp) {
                            initial = _.assignWith({}, defaults, workingModel, { name: initial.name }, assignment); // Throw out any prior cluster properties bc they were set for a diff provider.
                        }
                        else {
                            initial = _.assignWith({}, defaults, initial, assignment);
                        }
                    }
                    workingModel.name = (yield this._ui.prompt("name", "Cluster Name: ", true, (_d = workingModel.name) !== null && _d !== void 0 ? _d : initial === null || initial === void 0 ? void 0 : initial.name, this.azServiceNameValidator));
                    const azUser = yield this.selectCachedUser(initial.providerSubscription, cspTenantId);
                    // Select subscription from the ones the user has access to
                    // TODO: ask to change before prompting the list
                    workingModel.providerSubscription = yield this.promptForSubscriptionId(azUser, initial === null || initial === void 0 ? void 0 : initial.providerSubscription);
                    // cluster.locationName = (await this._ui.prompt("locationName", "Location: ", true, defaults?.locationName))!;
                    workingModel.locationName = (yield this.promptForLocation(workingModel.providerSubscription, initial === null || initial === void 0 ? void 0 : initial.locationName));
                    workingModel.providerGroup = yield this.promptForResourceGroup(workingModel.providerSubscription, workingModel.locationName, ((_e = initial === null || initial === void 0 ? void 0 : initial.providerGroup) !== null && _e !== void 0 ? _e : ((_f = workingModel.name) !== null && _f !== void 0 ? _f : initial === null || initial === void 0 ? void 0 : initial.name)) ? `${(_g = workingModel.name) !== null && _g !== void 0 ? _g : initial === null || initial === void 0 ? void 0 : initial.name}-rg` : undefined);
                    // ---------------------------------------
                    // NOTE: Up to here, we could simply call:
                    //       this.defineClusterCaptureParams()
                    // ---------------------------------------
                    // Generic cluster properties...
                    let initialSubnet;
                    if ((initial === null || initial === void 0 ? void 0 : initial.subnet) != undefined && !_.isEmpty(initial.subnet)) {
                        initialSubnet = (typeof (initial.subnet) == 'string') ? initial.subnet : initial.subnet[0]; // Azure supports only one subnet here: ignore any additionals.
                    }
                    // TODO: Add UI for pod subnet
                    // let initialPodSubnet: string | undefined;
                    // if (initial?.podSubnet != undefined && !_.isEmpty(initial.podSubnet)) {
                    //   initialPodSubnet = (typeof (initial.podSubnet) == 'string') ? initial.podSubnet : initial.podSubnet[0];  // Azure supports only one pod subnet here: ignore any additionals.
                    // }
                    workingModel.subnet = yield this.promptForVnetSubnet(workingModel.providerSubscription, workingModel.locationName, initialSubnet /* , initialPodSubnet */);
                    const mainNodeGroup = yield this.promptForNodeGroup(workingModel, initial, ORIGINAL_NODE_POOL_NAME, DEFAULT_STARTING_NODE_COUNT);
                    // TODO: Refactor to  -->  cluster.nodePools = [mainNodeGroup];
                    workingModel.nodeSpec = mainNodeGroup.nodeSpec;
                    workingModel.startingNodeCount = mainNodeGroup.startingNodeCount;
                    workingModel.nodesMin = mainNodeGroup.nodesMin;
                    workingModel.nodesMax = mainNodeGroup.nodesMax;
                    // TODO: Set default from AKS (mark it in list, too)
                    // TODO: ask to change before prompting the list
                    workingModel.k8sVersion = yield this.promptForK8sVersion(workingModel.locationName, workingModel.providerSubscription, initial === null || initial === void 0 ? void 0 : initial.k8sVersion);
                    workingModel.ingressDef = yield this.promptForIngressSpec(initial === null || initial === void 0 ? void 0 : initial.ingressDef);
                    // --------------------------------------------------------------------------------------------------------------------------------------
                    // Azure-specific properties...
                    // Cluster identity (Service Principal / Managed Identity)
                    let identityPak = yield this.promptForClusterIdentity(workingModel.name, initial === null || initial === void 0 ? void 0 : initial.identityInstance);
                    if (identityPak) {
                        workingModel.identityInstance = identityPak.instance;
                        workingModel.identitySpec = identityPak.spec;
                    }
                    else {
                        // User chose auto-create (we'd be better off setting this default in the cluster create in the relay, not here)
                        workingModel.identitySpec = {
                            type: cluster_def_1.ClusterDefConstants.DEFAULT_IDENTITY_TYPE,
                            name: cluster_def_1.ClusterDefConstants.buildDefaultIdentityName(workingModel.name)
                        };
                    }
                    let spec = Object.assign({ name: workingModel.name, provider: workingModel.provider, providerSubscription: workingModel.providerSubscription, locationName: workingModel.locationName, providerGroup: workingModel.providerGroup, nodeCount: workingModel.nodeCount, ingressDef: workingModel.ingressDef, privateOwner: workingModel.privateOwner, nodeSpec: workingModel.nodeSpec, startingNodeCount: workingModel.startingNodeCount, identitySpec: (_h = workingModel.identitySpec) !== null && _h !== void 0 ? _h : { type: "", name: "" } }, this.createAuditable());
                    spec.k8sVersion = workingModel.k8sVersion;
                    if (workingModel.nodePools)
                        spec.nodePools = workingModel.nodePools;
                    if (workingModel.defaultFootprint)
                        spec.defaultFootprint = workingModel.defaultFootprint;
                    if (workingModel.subnet)
                        spec.subnet = workingModel.subnet;
                    if (workingModel.windowsAdmin)
                        spec.windowsAdmin = workingModel.windowsAdmin;
                    if (workingModel.identityInstance)
                        spec.identityInstance = workingModel.identityInstance;
                    resolve({ spec: spec, defaults: defaults });
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    promptForNodeGroup(cluster, nodeGroupSpec, nodePoolName, defaultMaxNodes = 3) {
        var _a, _b, _c, _d;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                // const isWindows = await this._ui.promptConfirm("winNodePool", "Is Windows?: ", false);
                const nodeSpec = yield this.promptForNodeSpec(cluster.providerSubscription, cluster.locationName, nodeGroupSpec.nodeSpec);
                const nodesMin = (yield this._ui.prompt("startingNodeCount", "Min nodes: ", true, (_a = nodeGroupSpec.nodesMin) !== null && _a !== void 0 ? _a : 1));
                const nodesMax = (yield this._ui.prompt("startingNodeCount", "Max nodes: ", true, (_b = nodeGroupSpec.nodesMax) !== null && _b !== void 0 ? _b : defaultMaxNodes));
                const defaultStarting = Math.ceil(nodesMax + nodesMin / 2); // nearest avg integer
                const startingNodeCount = (yield this._ui.prompt("startingNodeCount", "Starting node count: ", true, (_c = nodeGroupSpec.startingNodeCount) !== null && _c !== void 0 ? _c : defaultStarting));
                let ndef = Object.assign({ name: nodePoolName, clusterName: cluster.name, nodeSpec: nodeSpec, nodesMin: nodesMin, nodesMax: nodesMax, startingNodeCount: startingNodeCount }, this.createAuditable());
                // if (isWindows == true) ndef.isWindows = isWindows;
                return ndef;
            }
            catch (err) {
                err.message = "Error defining cluster node pool: ".concat((_d = err.message) !== null && _d !== void 0 ? _d : "(no message available)");
                this.logger.error(err.message, err);
                throw err;
            }
        });
    }
    promptForIngressSpec(current) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let partial = Object.assign({}, current);
            // INTERNAL?
            let choices = []; //Object.keys(IngressExposure).map(k => <PromptChoice>{ name: k, value: k });
            choices.push({ value: ingress_exposure_enum_1.IngressExposure.public, name: ingress_exposure_enum_1.IngressExposure.public.concat(this._styler.dim(" (Accessible from the internet)")) });
            choices.push({ value: ingress_exposure_enum_1.IngressExposure.private, name: ingress_exposure_enum_1.IngressExposure.private.concat(this._styler.dim(" (private, inside a network)")) });
            // choices.push({ value: IngressExposure.none, name: IngressExposure.none.concat(this._styler.dim(" (do not install)")) });
            // default to external
            let access = type_safety_utils_1.TypeSafetyUtils.toBoolean(partial.internal) ? ingress_exposure_enum_1.IngressExposure.private : ingress_exposure_enum_1.IngressExposure.public;
            let keep = false;
            keep = yield this._ui.promptConfirm("internalConfirm", "Ingress: ".concat(this._styler.hilite(access)), true);
            if (!keep) {
                let selected = (yield this._ui.promptSelect("internal", "Ingress: ", choices));
                access = type_safety_utils_1.TypeSafetyUtils.toEnum(ingress_exposure_enum_1.IngressExposure, selected); // assertion ok because the list is limited to valid values.
            }
            partial.internal = Boolean(access == ingress_exposure_enum_1.IngressExposure.private);
            // if (access == IngressExposure.internal) {
            //   partial.internal = true;
            // } else {
            //   delete partial.internal;
            // }
            // Ingress Ip
            let keepIp = false;
            if (partial.ingressIp != undefined) {
                keepIp = yield this._ui.promptConfirm("keepIp", `Ingress IP: ${this._styler.hilite(partial.ingressIp)} `);
            }
            if (keepIp && partial.ingressIp != undefined) {
                // keeping existing value, nothing to do.
            }
            else {
                let ipChoices = [
                    { name: "Auto-generate a static public IP", value: "auto-generate" },
                    // { name: "Let me select from existing static public IP addresses", value: "select-existing-static" },  // not supported yet
                    // { name: "Auto-select from cluster's subnet", value: "auto-select" },  // not supported yet
                    // { name: "Let me select one from cluster's subnet", value: "select-from-cidr" },  // not supported yet
                    { name: "** Specify your own **", value: "prompt-me" }
                ];
                let selectedIpMode = (yield this._ui.promptSelect("selectIngressIp", "Ingress IP: ", ipChoices));
                if (selectedIpMode == "auto-generate") {
                    if (partial.ingressIp) {
                        delete partial.ingressIp;
                        partial.ipAutoGen = true;
                        // We want another flag for the upstream to know any prior value for this needs to be removed from the saved version
                        // see ClusterInitializer.ts first workunit: `clusterDef.ingressDef = _.assignIn()`
                    }
                }
                else {
                    partial.ingressIp = (yield this._ui.prompt("ingressIp", "IP Address: ", true, partial.ingressIp, new RegExValidator(RegExValidator.COMMON_REGEXES.IPAddress, "Invalid IP Address", false)));
                }
            }
            // Convert to domain
            let output = partial;
            // COMMENTED OUT BC we need to be explicit to provide for relay overlaying an existing ingressDef with this output.
            // // HACK: gotta cast (below) bc we're not fully hydrating this thing.
            // // TODO: reconsider putting a partial into the clusterDef
            // if (partial.internal == true) output = <IngressDef>{ ...output, internal: partial.internal };
            // // if (partial.disableExternal) output = <IngressDef>{ ...output, disableExternal: partial.disableExternal };
            // // if (partial.ip_name) output = <IngressDef>{ ...output, ip_name: partial.ip_name };
            return output;
        });
    }
    defineClusterCaptureParams(initial, rootMap, defaults, cspTenantId, cspUser) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c, _d, _e;
                let output;
                try {
                    let workingModel = {};
                    workingModel.provider = this.cloudProvider;
                    workingModel.privateOwner = this.codiacUser.tenantCode;
                    let isValid = true;
                    // APPLY DEFAULTS FOR THIS CSP
                    let switchedCsp = Boolean(defaults !== undefined && (defaults.provider !== this.cloudProvider));
                    if (defaults === undefined || (defaults.provider !== this.cloudProvider)) {
                        let defaultResult = yield this.genRelayClient.Call.getClusterDefaults({ provider: this.cloudProvider });
                        if (defaultResult.error) {
                            this.logger.warn(`Error retrieving defaults for ${this.cloudProvider} cluster: ${(_a = defaultResult.error.name) !== null && _a !== void 0 ? _a : ""} ${(_b = defaultResult.error.message) !== null && _b !== void 0 ? _b : ""}`);
                        }
                        defaults = (_c = defaultResult.output) !== null && _c !== void 0 ? _c : {}; // fall back to an empty object if we failed, so that we dont keep looking this up on subsequent calls.
                        let assignment = (objVal, srcVal) => Boolean(srcVal === undefined) ? objVal : srcVal;
                        if (switchedCsp) {
                            initial = _.assignWith({}, defaults, workingModel, { name: initial.name }, assignment); // Throw out any prior cluster properties bc they were set for a diff provider.
                        }
                        else {
                            initial = _.assignWith({}, defaults, initial, assignment);
                        }
                    }
                    const azUser = cspUser !== null && cspUser !== void 0 ? cspUser : yield this.selectCachedUser(initial.providerSubscription, cspTenantId);
                    // Select subscription from the ones the user has access to
                    // TODO: ask to change before prompting the list
                    workingModel.providerSubscription = yield this.promptForSubscriptionId(azUser, initial === null || initial === void 0 ? void 0 : initial.providerSubscription);
                    // workingModel.locationName = (await this.promptForLocation(workingModel.providerSubscription, initial?.locationName))!;
                    // workingModel.providerGroup = await this.promptForResourceGroup(workingModel.providerSubscription, workingModel.locationName, initial?.providerGroup ?? (workingModel.name ?? initial?.name) ? `${workingModel.name ?? initial?.name}-rg` : undefined);
                    let cluster = yield this.promptForExistingCluster(workingModel.providerSubscription, rootMap.clusters, initial.name);
                    workingModel.name = cluster.name;
                    workingModel.providerGroup = cluster.resourceGroupName;
                    workingModel.locationName = cluster.location;
                    // Commented out bc:  For captured clusters, this should be done AFTER the capture, at cluster init time.
                    // workingModel.ingressDef = await this.promptForIngressSpec(initial?.ingressDef);
                    let spec = {
                        name: workingModel.name,
                        provider: workingModel.provider,
                        providerSubscription: workingModel.providerSubscription,
                        locationName: workingModel.locationName,
                        providerGroup: workingModel.providerGroup
                    };
                    if ((_d = workingModel.ingressDef) === null || _d === void 0 ? void 0 : _d.internal)
                        spec.ingressDef = { internal: workingModel.ingressDef.internal };
                    if ((_e = workingModel.ingressDef) === null || _e === void 0 ? void 0 : _e.disableExternal)
                        spec.ingressDef = { disableExternal: workingModel.ingressDef.disableExternal };
                    resolve({ spec: spec, defaults: defaults });
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    /** Throws on exceptions or none found. */
    promptForExistingCluster(providerSubscriptionId, clustersInCodiac, defaultValue) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let fetchResult = yield this.genRelayClient.Call.GetExistingCspClusters({
                    provider: contracts_1.CloudProviderEnum.azure,
                    providerSubscriptionId: providerSubscriptionId
                });
                if (fetchResult.error)
                    throw fetchResult.error;
                else if (fetchResult.output == undefined || fetchResult.output.length == 0)
                    throw new Error("No clusters found in that subscription.");
                let clusters = fetchResult.output;
                let choices = clusters.map(c => { return { name: c.name, value: c.name }; });
                for (let choice of choices) {
                    if (clustersInCodiac.find(c => c.provider == contracts_1.CloudProviderEnum.azure && c.name == choice.name)) {
                        choice.name = this._styler.dim(choice.name.concat(" (already captured)"));
                        choice.value = ""; // invalid selection
                    }
                }
                let name;
                do {
                    name = (yield this._ui.promptSelect("azCluster", "Clusters: ", choices, false, false));
                } while (name == "");
                const selected = clusters.find(c => c.name == name); // assertion operator ok here bc selections werer limited to that list.
                return selected;
            }
            catch (err) {
                throw err;
            }
        });
    }
    promptForClusterIdentity(clusterName, defaultValue) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                let output;
                try {
                    // TODO: promptConfirm() if they want to skip, set or change the identity
                    // TODO: ask to autocreate or not, ask all or none
                    let adAccount;
                    let keep;
                    let existingDisplay = defaultValue ? `${defaultValue.name} (id: ${defaultValue.id})` : codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE;
                    keep = yield this._ui.promptConfirm("setIdentity", `Cluster Identity [${this._styler.hilite(existingDisplay)}]`, true);
                    if (keep === true) {
                        adAccount = defaultValue;
                    }
                    else {
                        // adAccount = defaultValue ?? <SecurityAccount>{};
                        let name = (yield this._ui.prompt("name", "Identity Name (blank to auto-create):", false, defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.name, this.azIdentityNameValidator));
                        if (name === undefined || name === "") {
                            // User requested auto-create
                        }
                        else {
                            // User chose to manually set the identity
                            let defaultId = (name === (defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.name)) ? defaultValue.id : undefined; // only default the id if the entered name matches the default value
                            let id = (yield this._ui.prompt("id", "Identity Id:", false, defaultId, new non_empty_validator_1.NonEmptyValidator()));
                            let systemName = "http://".concat(name);
                            adAccount = new security_account_1.SecurityAccount(id, name, systemName);
                            // TODO: Ask first if they want to change the password.
                            //       Allow them to attempt a password reset on the svc principal
                            let defaultPassword = (name === (defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.name)) ? defaultValue.password : undefined;
                            adAccount.password = yield this._ui.promptPassword("password", "Identity Password: ", defaultPassword);
                        }
                    }
                    // undefined means auto-create
                    if (adAccount !== undefined) {
                        let identitySpec = {
                            type: cluster_def_1.ClusterDefConstants.DEFAULT_IDENTITY_TYPE,
                            name: (adAccount.name) ? adAccount.name : cluster_def_1.ClusterDefConstants.buildDefaultIdentityName(clusterName)
                        };
                        output = { spec: identitySpec, instance: adAccount };
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    promptForVnetSubnet(subxId, location, defaultValue) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                let output;
                try {
                    let keep = false;
                    // if (defaultValue) {
                    keep = (yield this._ui.promptConfirm("keepSubnet", `Subnet: [${this._styler.hilite(defaultValue !== null && defaultValue !== void 0 ? defaultValue : codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE)}]`));
                    // }
                    if (keep === true) {
                        output = defaultValue;
                    }
                    else {
                        let vnets = yield this.getVnets(subxId, location);
                        let choices = vnets.map(v => { return { name: `${v.name}`, value: v.name }; });
                        choices.push({ name: codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE, value: codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE });
                        let vnetName = (yield this._ui.promptSelect("vnet", `VNet: `, choices, false));
                        if (vnetName === codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE) {
                            output = undefined;
                        }
                        else {
                            let vnet = (vnets.find(v => v.name === vnetName));
                            let subnetChoices = vnet.subnets.map(s => {
                                return {
                                    name: `${s.name} [${s.addressPrefix}]`,
                                    value: this.buildSubnetResourceId(subxId, vnet, s)
                                };
                            });
                            subnetChoices.push({ name: codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE, value: codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE });
                            let selectedSubnet = (yield this._ui.promptSelect("subnet", `Subnet: `, subnetChoices, false));
                            output = (selectedSubnet === codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE) ? undefined : selectedSubnet;
                        }
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    buildSubnetResourceId(subxId, vnet, s) {
        return `/subscriptions/${subxId}/resourceGroups/${vnet.resourceGroup}/providers/Microsoft.Network/virtualNetworks/${vnet.name}/subnets/${s.name}`;
    }
    getVnets(subxId, location) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                let output;
                try {
                    this._ui.startSpinner(`Loading virtual networks in ${location}...`);
                    // let cmd = `az network vnet list --subscription ${subxId}`;
                    // cmd = cmd.concat(" --query ", "\"[].{ resourceGuid: resourceGuid, name: name, location: location, subnets: subnets[].{ name: name, addressPrefix: addressPrefix }, resourceGroup: resourceGroup }\"")
                    // let output = await this.cliExec.execAsyncJson<AzureVNet[]>(cmd, { cwd: process.cwd() }, LogLevel.debug);
                    let result = yield this.genRelayClient.Call.getAzureVnets({ subscriptionId: subxId, location: location });
                    output = (_a = result.output) !== null && _a !== void 0 ? _a : [];
                    if (result.error) {
                        this._ui.stopSpinner(`Error!`);
                        this.logger.warn(`Error retrieving virtual netwotks: ${(_b = result.message) !== null && _b !== void 0 ? _b : result.error.message}`);
                    }
                    else {
                        this._ui.stopSpinner(`${output.length} found`);
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    promptForResourceGroup(subscriptionId, clusterLocation, defaultValue, selectionOnly = false) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                let output;
                try {
                    // TODO:  Render "(create new)" if the default value is not in the list of existings
                    let groups = yield this.getResourceGroups(clusterLocation, subscriptionId);
                    if (groups.length == 0 && selectionOnly) {
                        throw new Error("No resource groups found in [".concat(clusterLocation, "]"));
                    }
                    let keep = false;
                    if (defaultValue) {
                        let displayName = defaultValue;
                        // if not found
                        let exists = Boolean(groups.indexOf(defaultValue) < 0);
                        if (!exists && selectionOnly) {
                            keep = false; // not found, and we're limiting selections to existing records
                        }
                        else {
                            if (!exists)
                                displayName = displayName.concat(" (auto-create)"); // Allow them to add new record
                            keep = (yield this._ui.promptConfirm("keepGroup", `Resource Group: [${this._styler.hilite(displayName)}]`));
                        }
                    }
                    if (keep === true && defaultValue !== undefined) {
                        output = defaultValue;
                    }
                    else {
                        let choices = (selectionOnly ? [] : [...groups, codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE]).map(x => ({ name: x, value: x }));
                        let selector = new cli_common_1.PromptSelectFuzzy(this.logger, this._ui, this._styler);
                        let selected = (yield selector.go('providerGroup', `Resource Group (in ${clusterLocation}): `, false, defaultValue, false, () => tslib_1.__awaiter(this, void 0, void 0, function* () { return choices; })));
                        // let selected = (await this._ui.promptSelect("providerGroup", `Resource Group (in ${clusterLocation}): `, choices, false))!;
                        if (selected === codiac_constants_1.CliConstants.CHOICE_VALUE_FOR_AUTOCREATE) {
                            output = (yield this._ui.prompt("newProviderGroup", "Name: ", true, defaultValue, this.azRgNameValidator));
                        }
                        else {
                            output = selected;
                        }
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    getResourceGroups(location, subscriptionId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                let output;
                try {
                    this._ui.startSpinner(`Loading resource groups in ${location}...`);
                    let result = yield this.genRelayClient.Call.getAzureResourceGroups({ location: location, subscriptionId: subscriptionId });
                    output = (_a = result.output) !== null && _a !== void 0 ? _a : [];
                    if (result.error) {
                        this._ui.stopSpinner(`Error!`);
                        this.logger.warn(`Error retrieving resource groups: ${(_b = result.message) !== null && _b !== void 0 ? _b : result.error.message}`);
                    }
                    else {
                        this._ui.stopSpinner(`${output.length} found`);
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    /** Asks the user whether to keep the default version, and if not presents a select list. */
    promptForLocation(subscriptionId, defaultValue) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                let output;
                try {
                    let availableValues = yield this.getAvailableLocations(subscriptionId);
                    let keepDefault = false;
                    if (defaultValue && availableValues.find(x => x.name === defaultValue)) {
                        keepDefault = yield this._ui.promptConfirm("keepLocation", `Location: ${this._styler.hilite(defaultValue)} `);
                    }
                    if (keepDefault === true && defaultValue !== undefined) {
                        output = defaultValue;
                    }
                    else {
                        let byRegion = _.groupBy(availableValues, loc => loc.metadata.geographyGroup);
                        let geoChoices = Object.keys(byRegion).sort();
                        let geo = (yield this._ui.promptSelect("geo", "Region", geoChoices));
                        let locationsInGeo = byRegion[geo];
                        availableValues = _.orderBy(locationsInGeo, ['longitude', 'latitude', 'name']);
                        let choices = availableValues.map(x => {
                            return { name: `${x.displayName}${(x.metadata.physicalLocation) ? ` (${x.metadata.physicalLocation})` : ""}`, value: x.name };
                        });
                        choices = _.orderBy(choices, c => c.name);
                        // output = (await this._ui.promptSelect("locationName", "Location: ", choices))!;
                        let selector = new cli_common_1.PromptSelectFuzzy(this.logger, this._ui, this._styler);
                        output = (yield selector.go('locationName', 'Location:', false, undefined, false, () => new Promise(resolve => resolve(choices))));
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    /** Queries data centers under the current user's subscription.
     *
     * @see:  https://docs.microsoft.com/en-us/rest/api/resources/subscriptions/listlocations
     * DEVNOTE:  This uses the subx set in the current user state (az account show), so is it possible that this is NOT the subscription chosen a few steps earlier? */
    getAvailableLocations(subscriptionId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                let output;
                try {
                    this._ui.startSpinner("Loading locations...");
                    let result = yield this.genRelayClient.Call.getAzureLocations({ subscriptionId: subscriptionId });
                    output = (_a = result.output) !== null && _a !== void 0 ? _a : [];
                    if (result.error) {
                        this._ui.stopSpinner(`Error!`);
                        this.logger.warn(`Error retrieving available locations: ${(_b = result.message) !== null && _b !== void 0 ? _b : result.error.message}`);
                    }
                    else {
                        this._ui.stopSpinner(`${output.length} found`);
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    /** Asks the user whether to keep the default version, and if not presents a select list. */
    promptForK8sVersion(location, providerSubscription, defaultK8sVersion) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                let output;
                try {
                    this._ui.startSpinner(`Loading K8s versions available in ${location} ...`);
                    let versResult = yield this.genRelayClient.Call.getAvailableK8sVersions({
                        provider: this.cloudProvider,
                        location: location,
                        subscriptionId: providerSubscription
                    });
                    if (versResult.error)
                        throw versResult.error;
                    let k8sVersions = (_b = (_a = versResult.output) === null || _a === void 0 ? void 0 : _a.map(x => x.k8sVersion)) !== null && _b !== void 0 ? _b : [];
                    this._ui.stopSpinner(`${k8sVersions.length} found`);
                    let keepVersion = false;
                    if (defaultK8sVersion && k8sVersions.indexOf(defaultK8sVersion) >= 0) {
                        keepVersion = yield this._ui.promptConfirm("keepVersion", `Kubernetes version: ${this._styler.hilite(defaultK8sVersion)} `);
                    }
                    if (keepVersion === true && defaultK8sVersion !== undefined) {
                        output = defaultK8sVersion;
                    }
                    else {
                        if (k8sVersions.length > 0) {
                            output = (yield this._ui.promptSelect("k8sVersion", "Kubernetes version: ", k8sVersions)); // defaults?.k8sVersion;
                        }
                        else {
                            output = (yield this._ui.prompt("k8sVersion", "Kubernetes version: ", true, defaultK8sVersion));
                        }
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    // see:  https://docs.microsoft.com/en-us/rest/api/compute/resourceskus/list#lists-all-available-resource-skus
    promptForNodeSpec(subscriptionId, location, defaultValue) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                let output;
                try {
                    let vmSizes = yield this.getAvailableNodeSizes(subscriptionId, location);
                    let keepVersion = false;
                    if (defaultValue && vmSizes.find(s => s.name === defaultValue)) {
                        keepVersion = yield this._ui.promptConfirm("keepNodeSpec", `Node size: ${this._styler.hilite(defaultValue)} `);
                    }
                    if (keepVersion === true && defaultValue !== undefined) {
                        output = defaultValue;
                    }
                    else {
                        if (vmSizes.length > 0) {
                            let choices = vmSizes.map(s => { return { name: `${s.name.padEnd(25)} (${(s.memoryInMb + " Mb").padStart(12)}, ${(s.numberOfCores + " cores").padStart(10)})`, value: s.name }; });
                            choices = _.orderBy(choices, c => c.name);
                            // output = (await this._ui.promptSelect("nodeSpec", "Node type: ", choices))!;
                            let selector = new cli_common_1.PromptSelectFuzzy(this.logger, this._ui, this._styler);
                            output = (yield selector.go('nodeSpec', 'Node size:', false, undefined, false, () => new Promise(resolve => resolve(choices))));
                        }
                        else {
                            output = (yield this._ui.prompt("nodeSpec", "Node size: ", true, defaultValue));
                        }
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    getAvailableNodeSizes(subscriptionId, location) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c;
                let output;
                try {
                    this._ui.startSpinner(`Loading available node sizes in ${location}...`);
                    let result = yield this.genRelayClient.Call.getAzureNodeTypes({ location: location, subscriptionId: subscriptionId });
                    output = (_a = result.output) !== null && _a !== void 0 ? _a : [];
                    if (result.error) {
                        this._ui.stopSpinner(`Error!`);
                        this.logger.warn(`Error retrieving available node sizes: ${(_b = result.message) !== null && _b !== void 0 ? _b : (_c = result.error) === null || _c === void 0 ? void 0 : _c.message}`);
                    }
                    else {
                        this._ui.stopSpinner(`${output.length} found`);
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    /* ***********************************************************************************
     *       _   __             _   _             _ _
     *      | | / /            | | | |           | | |
     *      | |/ /  ___ _   _  | | | | __ _ _   _| | |_
     *      |    \ / _ \ | | | | | | |/ _` | | | | | __|
     *      | |\  \  __/ |_| | \ \_/ / (_| | |_| | | |_
     *      \_| \_/\___|\__, |  \___/ \__,_|\__,_|_|\__|
     *                   __/ |
     *                  |___/
     * ************************************************************************************/
    captureSecretStore(/* storeProv: ISecretStoreProvisioner, */ defaults) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let output;
                const azUser = yield this.selectCachedUser(defaults === null || defaults === void 0 ? void 0 : defaults.subscriptionId);
                if (!azUser)
                    throw new Error(`Cannot capture any ${this.cloudProvider} secret store: No ${this.cloudProvider} account has been authenticated.`);
                let subxId = yield this.promptForSubscriptionId(azUser, defaults === null || defaults === void 0 ? void 0 : defaults.subscriptionId);
                let crit = { subscriptionId: subxId };
                // let fetchResult = await storeProv.list(crit);
                // let otherResult = await this.relayClient.getStorageAccounts(this.cloudProvider, defaults?.subscriptionId ?? "");
                // let mylist = otherResult.output.map((x,index) => { })
                // let fetchResult = await this.relayClient.listSecretStores(this.cloudProvider, crit);
                /** TODO: use this instead: */ let fetchResult = yield this.genRelayClient.Call.GetSecretStores({ provider: this.cloudProvider, spec: crit });
                if (fetchResult.error) {
                    throw fetchResult.error;
                }
                else if (fetchResult.output === undefined || fetchResult.output.length === 0) {
                    // this._ui.userMessage(`No secret stores currently exist in ${partial.provider}.  You will need to create one.`);
                    throw new Error(`No secret stores currently exist in ${this.cloudProvider}.  You will need to create one.`);
                }
                else {
                    let choices = (fetchResult.output).map((s, index) => { return { name: s.name, value: index }; });
                    let selectedIndex = (yield this._ui.promptSelect("secretStore", "Secret store: ", choices));
                    let kv = fetchResult.output[selectedIndex]; // CAUTION: Later on this may not be a valid cast/assumption
                    const sku = "premium"; // TODO:  Get this onto the AzureKeyVault entity.
                    output = new contracts_2.AzureKeyVaultDef(kv.name, kv.resourceGroup, kv.tenantId, kv.subscriptionId, kv.location, sku, kv);
                }
                return output;
            }
            catch (err) {
                throw err;
            }
        });
    }
    createAuditable() {
        let stamp = new Date(Date.now());
        return {
            created: stamp,
            createdBy: this.codiacUser.username,
            modified: stamp,
            modifiedBy: this.codiacUser.username
        };
    }
    /* ***********************************************************************************
     *   ______ _ _      _____ _
     *   |  ___(_) |    /  ___| |
     *   | |_   _| | ___\ `--.| |_ ___  _ __ ___  ___
     *   |  _| | | |/ _ \`--. \ __/ _ \| '__/ _ \/ __|
     *   | |   | | |  __/\__/ / || (_) | | |  __/\__ \
     *   \_|   |_|_|\___\____/ \__\___/|_|  \___||___/
     *
     * ************************************************************************************/
    captureFileStore(providerSubscriptionId, partial, allowUserInteraction) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!allowUserInteraction)
                return yield this.captureFileStore_NonTTY(providerSubscriptionId, partial);
            try {
                let candidate;
                let saResult = yield this.genRelayClient.Call.GetStorageAccounts({
                    provider: this.cloudProvider,
                    providerSubscriptionId: (_a = partial.providerSubscriptionId) !== null && _a !== void 0 ? _a : providerSubscriptionId
                });
                if (saResult.success && saResult.output && saResult.output.length > 0) {
                    let choices = (saResult.output).map(x => x.name);
                    let storageName = (yield this._ui.promptSelect("storageAccount", "select the storage account", choices));
                    let storage = (saResult.output).find(x => x.name == storageName); // these casts may be due to muiltiple copies of the entity)
                    partial.resourceGroup = this.extractResourceGroup(storage.id);
                    // Storage Account Key
                    let keyResults = yield this.genRelayClient.Call.GetStorageAccountKeys({
                        provider: this.cloudProvider,
                        subscriptionId: (_b = partial.providerSubscriptionId) !== null && _b !== void 0 ? _b : providerSubscriptionId,
                        resourceGroup: partial.resourceGroup,
                        storageAccountName: storage.name
                    });
                    if (keyResults.success && keyResults.output && keyResults.output.length > 0) {
                        let accountKey = keyResults.output[0].value;
                        // File Store
                        let fileStores;
                        try {
                            let fileStoreResult = yield this.genRelayClient.Call.GetFileStores({
                                provider: contracts_1.CloudProviderEnum.azure,
                                providerSubscriptionId: (_c = partial.providerSubscriptionId) !== null && _c !== void 0 ? _c : providerSubscriptionId,
                                storageAccount: storage,
                                resourceGroup: partial.resourceGroup,
                                accountKey: accountKey
                            });
                            if (fileStoreResult.error)
                                throw fileStoreResult.error;
                            fileStores = (_d = fileStoreResult.output) === null || _d === void 0 ? void 0 : _d.map(x => x);
                        }
                        catch (err) {
                            this.logger.debug(`Error retrieving filestores (you or your machine may not have access to list them: ${err.message || "err.stack"}`, err);
                            this._ui.userMessage(`We are having trouble listing the fileshares in storage account [${storage.name}].`);
                            let fileshareName = (yield this._ui.prompt("fileshareName", `Please manually enter the fileshare name`, true, storage.name));
                            candidate = this.buildFileStoreDef((_e = partial.name) !== null && _e !== void 0 ? _e : fileshareName, (_f = partial.providerSubscriptionId) !== null && _f !== void 0 ? _f : providerSubscriptionId, storage, partial.resourceGroup, accountKey, (_g = partial.shareName) !== null && _g !== void 0 ? _g : fileshareName);
                        }
                        if (fileStores !== undefined) {
                            if (fileStores.length > 0) {
                                // TODO: get the one they called out for in the args
                                let fsName = yield this._ui.promptSelect("resourceId", "Select a file store", fileStores.map(f => f.name));
                                candidate = fileStores.find(f => f.name == fsName); // we can force this - we selected from this list.
                            }
                            else {
                                this._ui.userMessage(`No filestores found in that storage account.`);
                            }
                        }
                        if (candidate && candidate.instance) {
                            candidate.name = (yield this._ui.prompt("name", "Refer to as:", true, (_h = partial.name) !== null && _h !== void 0 ? _h : candidate.shareName));
                            // candidate.instance.accessKey = (await this._ui.prompt("accessKey", "Access Key for this filestore"))!;
                        }
                    }
                }
                else if (saResult.error !== undefined) {
                    throw saResult.error;
                }
                else {
                    this._ui.userMessage(`No filestores found in that subscription`);
                    partial.providerSubscriptionId = undefined;
                }
                return candidate;
            }
            catch (err) {
                throw err;
            }
        });
    }
    captureFileStore_NonTTY(providerSubscriptionId, partial) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                if (partial.storageAccount == undefined || partial.storageAccount.trim() == "")
                    throw new Error("Must provide storageAccount.");
                if (partial.shareName == undefined || partial.shareName.trim() == "")
                    throw new Error("Must provide shareName.");
                // ---------------------------------------------------------------
                // HARD-CODED OPTIONS!!!
                // TODO: Expose these as command args
                // ---------------------------------------------------------------
                partial.provider = contracts_1.CloudProviderEnum.azure;
                partial.type = contracts_2.FileStoreTypeEnum.azureFileStorage;
                // ---------------------------------------------------------------
                // Retrieve the specified storage account
                let storageAccount;
                let saResult = yield this.genRelayClient.Call.GetStorageAccounts({
                    provider: partial.provider,
                    providerSubscriptionId: (_a = partial.providerSubscriptionId) !== null && _a !== void 0 ? _a : providerSubscriptionId
                });
                if (saResult.error) {
                    throw saResult.error;
                }
                else if (saResult.success == false || saResult.output == undefined) {
                    throw new Error("No storage accounts found.");
                }
                else {
                    let found = saResult.output.find(x => x.name == partial.storageAccount);
                    if (!found)
                        throw new Error("Storage account not found.");
                    storageAccount = found;
                }
                partial.resourceGroup = this.extractResourceGroup(storageAccount.id); // Assertion ok because we know its an existing resource.
                // Retrieve the storage account key
                let accountKey;
                let keyResults = yield this.genRelayClient.Call.GetStorageAccountKeys({
                    provider: partial.provider,
                    subscriptionId: (_b = partial.providerSubscriptionId) !== null && _b !== void 0 ? _b : providerSubscriptionId,
                    resourceGroup: partial.resourceGroup,
                    storageAccountName: (_c = partial.storageAccount) !== null && _c !== void 0 ? _c : storageAccount.name
                });
                if (keyResults.success && keyResults.output && keyResults.output.length > 0) {
                    accountKey = keyResults.output[0].value;
                }
                else {
                    throw new Error("Failed to retrieve the storage account key: ".concat((_f = (_e = (_d = keyResults.error) === null || _d === void 0 ? void 0 : _d.message) !== null && _e !== void 0 ? _e : keyResults.message) !== null && _f !== void 0 ? _f : "no details given", "."));
                }
                let candidate = this.buildFileStoreDef((_g = partial.name) !== null && _g !== void 0 ? _g : partial.shareName, (_h = partial.providerSubscriptionId) !== null && _h !== void 0 ? _h : providerSubscriptionId, storageAccount, partial.resourceGroup, accountKey, partial.shareName);
                return candidate;
            }
            catch (err) {
                throw err;
            }
        });
    }
    /** Throws any encounted exceptions.
     *
     * We have to parse the resourceGroup out of the id bc M$ doesnt return it anywhere else.
     * Yeah, dont you just love those guys :|
     */
    extractResourceGroup(storageAccountResourceId) {
        let rg = new RegExp(/\/resourceGroups\/([^\/]+)\//).exec(storageAccountResourceId)[1];
        return rg;
    }
    /** Facory method for building a FileStoreDef from a fileshare */
    buildFileStoreDef(name, providerSubscriptionId, storageAccount, resourceGroup, accountKey, fileShareName) {
        let def = {
            id: undefined,
            name: name,
            provider: this.cloudProvider,
            providerSubscriptionId: providerSubscriptionId,
            resourceGroup: resourceGroup,
            storageAccount: storageAccount.name,
            shareName: fileShareName,
            type: contracts_2.FileStoreTypeEnum.azureFileStorage,
            instance: {
                accessKey: accountKey,
                providerSubscriptionId: providerSubscriptionId,
                resourceGroup: resourceGroup,
                resourceId: storageAccount.id
            },
            created: new Date(Date.now()),
            createdBy: this.codiacUser.username,
            modified: new Date(Date.now()),
            modifiedBy: this.codiacUser.username
        };
        return def;
    }
};
AzureResourceConfigurator = tslib_1.__decorate([
    (0, inversify_1.injectable)(),
    tslib_1.__param(0, (0, inversify_1.inject)(codiac_relay_1.RelayClient)),
    tslib_1.__param(1, (0, inversify_1.inject)(codiac_ops_common_1.UserNameTenantCode)),
    tslib_1.__param(2, (0, inversify_1.inject)(ops_1.INTERFACES_CLI.IUserInputProvider)),
    tslib_1.__param(3, (0, inversify_1.inject)(ops_1.INTERFACES_CLI.IOutputStyler)),
    tslib_1.__param(4, (0, inversify_1.inject)(codiac_common_1.INTERFACES.IBetterLogger)),
    tslib_1.__metadata("design:paramtypes", [codiac_relay_1.RelayClient,
        codiac_ops_common_1.UserNameTenantCode, Object, Object, Object])
], AzureResourceConfigurator);
exports.AzureResourceConfigurator = AzureResourceConfigurator;
//# sourceMappingURL=azure-resource-configurator.js.map