UNPKG

sleeveforarm

Version:
247 lines 12.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const Crypto = require("crypto"); const fs = require("fs-extra"); const GeneratePassword = require("generate-password"); const Path = require("path"); const Winston = require("winston"); const CommonUtilities = require("./common-utilities"); const data = require("./data"); const KeyVaultInfra = require("./keyvaultInfrastructure"); const mysql_azure_1 = require("./mysql-azure"); const promiseGate_1 = require("./promiseGate"); const Resource = require("./resource"); const ServiceEnvironmentUtilities = require("./serviceEnvironmentUtilities"); class BaseDeployMySqlAzureInfrastructure { constructor(baseMySqlAzureInfrastructure, createResult) { this.baseMySqlAzureInfrastructure = baseMySqlAzureInfrastructure; this.environmentVariablesValues = []; const baseName = baseMySqlAzureInfrastructure.getBaseName(); const hostVariableName = `${baseName}${ServiceEnvironmentUtilities.resourceHostSuffix}`; const userVariableName = `${baseName}${ServiceEnvironmentUtilities.resourceUserSuffix}`; const passwordVariableName = `${baseName}${ServiceEnvironmentUtilities.resourcePasswordSuffix}`; this.environmentVariablesValues.push([hostVariableName, createResult.fullyQualifiedDomainName]); this.environmentVariablesValues.push([userVariableName, `${baseMySqlAzureInfrastructure.securityName}@${createResult.name}`]); this.environmentVariablesValues.push([passwordVariableName, baseMySqlAzureInfrastructure.password]); } /** * Returns a list name/value pairs for environment * variables to describe how to connect to this resource. */ getEnvironmentVariables() { return this.environmentVariablesValues; } /** * Creates a firewall on the storage resource with the given * name for the given ipAddress. */ setFirewallRule(nameOfResourceSettingRule, ipAddress) { return __awaiter(this, void 0, void 0, function* () { yield this.baseMySqlAzureInfrastructure .setFirewallRule(nameOfResourceSettingRule, ipAddress); return this; }); } } exports.BaseDeployMySqlAzureInfrastructure = BaseDeployMySqlAzureInfrastructure; class MySqlAzureInfrastructure extends mysql_azure_1.default { constructor() { super(...arguments); this.isStorageResource = true; this.promiseGate = new promiseGate_1.default(); } getBaseName() { return this.baseName; } initialize(resource, targetDirectoryPath) { super.initialize(resource, targetDirectoryPath); if (resource !== null) { Object.assign(this, resource); } return this; } setup() { return __awaiter(this, void 0, void 0, function* () { return yield MySqlAzureInfrastructure.internalSetup(__filename, this.targetDirectoryPath, data.data.MySQLNameLength); }); } hydrate(resourcesInEnvironment, deploymentType) { const _super = name => super[name]; return __awaiter(this, void 0, void 0, function* () { yield _super("hydrate").call(this, resourcesInEnvironment, deploymentType); if (this.mySqlAzureFullName === undefined) { this.mySqlAzureFullName = (this.resourceGroup.resourceGroupName + this.baseName).toLowerCase(); } this.securityName = this.baseName; this.password = GeneratePassword.generate({ // tslint:disable-next-line:max-line-length // This is a combination of https://docs.microsoft.com/en-us/sql/relational-databases/security/strong-passwords // and https://technet.microsoft.com/en-us/library/cc956689.aspx. // The later came up when setting a password! // The & character got added because it's a reserved command, // even in strings. If we want to use it we need to wrap it // as '"&"'. // I also took out double quotes and ^ because they seem to // disappear when I set them on Keyvault. I submitted a bug on that. exclude: "^\"&\/:|<>+=.'[]{}(),;?*!@", length: 32, numbers: true, strict: true, symbols: false, uppercase: true }); // BUGBUG: To meet the symbol requirement for now. this.password += "$"; return this; }); } deployResource() { return __awaiter(this, void 0, void 0, function* () { try { yield this.resourceGroup.getBaseDeployClassInstance(); const promisesToWaitFor = []; const resourceGroupName = this.resourceGroup.resourceGroupName; const createResult = yield CommonUtilities.runAzCommand(`az mysql server create \ --resource-group ${resourceGroupName} --name ${this.mySqlAzureFullName} \ --admin-user ${this.securityName} --admin-password ${this.password} \ --ssl-enforcement Enabled`, CommonUtilities.azCommandOutputs.json); this.promiseGate.openGateSuccess(new BaseDeployMySqlAzureInfrastructure(this, createResult)); const keyVault = CommonUtilities .findGlobalDefaultResourceByType(this.resourcesInEnvironment, KeyVaultInfra.KeyVaultInfrastructure); promisesToWaitFor.push(keyVault .getBaseDeployClassInstance() .then((keyVaultBaseClass) => { return keyVaultBaseClass .setSecret(this.securityName, this.password); })); if (this.deploymentType === Resource.DeployType.LocalDevelopment) { promisesToWaitFor.push(this.setFirewallAllowAll()); } const scriptPaths = []; for (const checkScriptPath of this.pathToMySqlInitializationScripts) { const scriptPath = Path.isAbsolute(checkScriptPath) ? checkScriptPath : Path.join(this.targetDirectoryPath, checkScriptPath); if ((yield fs.pathExists(scriptPath)) === false) { throw new Error(`Submitted mySql initialization script, \ located at ${scriptPath} for ${this.baseName} does not exist.`); } scriptPaths.push(scriptPath); } let firewallRuleName; if (scriptPaths.length !== 0) { firewallRuleName = yield this.setUpFirewallForSqlScript(); for (const scriptPath of scriptPaths) { promisesToWaitFor.push(this.runMySqlScript(scriptPath)); } } try { yield Promise.all(promisesToWaitFor); } finally { if (this.deploymentType === Resource.DeployType.Production && firewallRuleName) { yield this.removeFirewallRule(firewallRuleName); } } return this; } catch (err) { if (!this.promiseGate.isGateOpen) { this.promiseGate.openGateError(err); } throw err; } }); } getBaseDeployClassInstance() { return this.promiseGate.promise.then(function (baseClass) { return baseClass; }); } setUpFirewallForSqlScript() { return __awaiter(this, void 0, void 0, function* () { if (this.deploymentType === Resource.DeployType.LocalDevelopment) { return Promise.resolve(); } // We add in the '-e "SHOW DATABASES"' command just to give the // command something to do in the case that we already have // permission. Otherwise we won't ever get the failure we are // expecting. const initSqlCommand = `mysql -h ${this.mySqlAzureFullName}.mysql.database.azure.com \ -u ${this.securityName}@${this.mySqlAzureFullName} \ -p${this.password} -v -e "SHOW DATABASES"`; let devIp = ""; const baseFirewallRuleName = Crypto.randomBytes(10).toString("hex"); const re = /Client with IP address '(.*)' is not allowed to/; try { yield CommonUtilities.exec(initSqlCommand, this.targetDirectoryPath); Winston.debug("THE SYSTEM IS IN A BAD STATE. THERE IS A DANGLING \ FIREWALL RULE THAT WAS MOST LIKELY LEFT OVER FROM A PREVIOUS FAILED DEPLOY. \ WE DON'T AUTOMATICALLY FIX THIS YET. PLEASE SEE \ https://github.com/yaronyg/SleeveForARM/issues/38 FOR MORE DETAILS"); return ""; } catch (err) { const result = err.message.match(re); if (result.length !== 2) { throw new Error(`Search for dev IP failed with ${err}`); } devIp = result[1]; } if (devIp === "") { throw new Error("Call to get our IP failed!"); } return yield this.setFirewallRule(baseFirewallRuleName, devIp); }); } runMySqlScript(pathToScript) { return __awaiter(this, void 0, void 0, function* () { const initSqlCommand = `mysql -h ${this.mySqlAzureFullName}.mysql.database.azure.com \ -u ${this.securityName}@${this.mySqlAzureFullName} \ -p${this.password} -v < "${pathToScript}"`; // tslint:disable-next-line:max-line-length return yield CommonUtilities.retryAfterFailure(() => __awaiter(this, void 0, void 0, function* () { return yield CommonUtilities.exec(initSqlCommand, this.targetDirectoryPath); }), 60); }); } setFirewallRule(nameOfResourceSettingRule, ipAddress) { return __awaiter(this, void 0, void 0, function* () { const ipNoDots = ipAddress.replace(/\./g, ""); const ruleName = `${nameOfResourceSettingRule}${ipNoDots}`; yield CommonUtilities.runAzCommand(`az mysql server firewall-rule create \ --resource-group ${this.resourceGroup.resourceGroupName} \ --server ${this.mySqlAzureFullName} \ --name ${ruleName} --start-ip-address ${ipAddress} \ --end-ip-address ${ipAddress}`); return ruleName; }); } setFirewallAllowAll() { return CommonUtilities.runAzCommand(`az mysql server firewall-rule create \ --resource-group ${this.resourceGroup.resourceGroupName} \ --server ${this.mySqlAzureFullName} \ --name ${this.baseName}AllAccess --start-ip-address 0.0.0.0 \ --end-ip-address 255.255.255.255`); } removeFirewallRule(name) { return CommonUtilities.runAzCommand(`az mysql server firewall-rule delete \ --resource-group ${this.resourceGroup.resourceGroupName} \ --server-name ${this.mySqlAzureFullName} \ --name ${name} --yes`, CommonUtilities.azCommandOutputs.string); } } exports.MySqlAzureInfrastructure = MySqlAzureInfrastructure; //# sourceMappingURL=mysql-azureInfrastructure.js.map