UNPKG

sleeveforarm

Version:
298 lines (270 loc) 12.2 kB
import * as fs from "fs-extra"; import * as Path from "path"; import * as ReplaceInFile from "replace-in-file"; import * as Winston from "winston"; import ApplicationInsights from "./applicationInsights"; // tslint:disable-next-line:max-line-length import * as ApplicationInsightsInfrastructure from "./applicationInsightsInfrastructure"; import * as CommonUtilities from "./common-utilities"; import * as Data from "./data"; import * as IInfrastructure from "./IInfrastructure"; import KeyVault from "./keyvault"; import * as KeyVaultInfrastructure from "./keyvaultInfrastructure"; import MySqlAzure from "./mysql-azure"; import * as MySqlAzureInfrastructure from "./mysql-azureInfrastructure"; import * as Resource from "./resource"; import ResourceGroup from "./resourcegroup"; import * as ResourceGroupInfrastructure from "./resourcegroupInfrastructure"; import WebappNodeAzure from "./webapp-node-azure"; // tslint:disable-next-line:max-line-length import * as WebappNodeAzureInfrastructure from "./webapp-node-azureInfrastructure"; export type InfraResourceType = IInfrastructure.IInfrastructure<any> & Resource.Resource; function createInfraResource(resource: Resource.Resource, targetDirectoryPath: string) : InfraResourceType { let infraResource: InfraResourceType | null = null; if (CommonUtilities.isClass(resource, ResourceGroup)) { infraResource = new ResourceGroupInfrastructure.ResourceGroupInfrastructure(); } if (CommonUtilities.isClass(resource, KeyVault)) { infraResource = new KeyVaultInfrastructure.KeyVaultInfrastructure(); } if (CommonUtilities.isClass(resource, WebappNodeAzure)) { infraResource = new WebappNodeAzureInfrastructure.WebappNodeAzureInfrastructure(); } if (CommonUtilities.isClass(resource, MySqlAzure)) { infraResource = new MySqlAzureInfrastructure.MySqlAzureInfrastructure(); } if (CommonUtilities.isClass(resource, ApplicationInsights)) { infraResource = new ApplicationInsightsInfrastructure.ApplicationInsightsInfrastructure(); } if (infraResource === null) { throw new Error("Unrecognized resource type!"); } infraResource.initialize(resource, targetDirectoryPath); return infraResource; } export async function init(currentWorkingDirectory: string) { const assetPath = Path.join(__dirname, "..", "assets", "cliInit"); if (!(await CommonUtilities.validateResource(Path.basename(process.cwd()), Data.data.ProjectNameLength))) { throw new Error(`Project name should be less than \ ${Data.data.ProjectNameLength} characters, contains only \ alphanumeric characters and start with a letter\n`); } await fs.copy(assetPath, currentWorkingDirectory); const aiInfrastructure = new ApplicationInsightsInfrastructure.ApplicationInsightsInfrastructure(); const aiSleevePath = Path.join(currentWorkingDirectory, "AppInsights"); await aiInfrastructure .initialize(null, aiSleevePath) .setup(); const aiReplaceOptions = { files: Path.join(aiSleevePath, "sleeve.js"), from: /new applicationInsights\(\);/, to: "new applicationInsights().setGlobalDefault(true);" }; await ReplaceInFile(aiReplaceOptions); // NPM publish turns all .gitignore into .npmignore. For awhile it // seemed that you could put in both a .gitignore and a .npmignore // and the .gitignore would be ignored but no longer. So now we // ship a .npmignore and then rename it after install. await fs.move(Path.join(currentWorkingDirectory, ".npmignore"), Path.join(currentWorkingDirectory, ".gitignore")); const locations = await CommonUtilities.azAppServiceListLocations(); const dataCenterEnum: string = locations[0].name.replace(/ /g, ""); const replaceInFileOptions = { files: Path.join(currentWorkingDirectory, "sleeve.js"), from: /XXXX/, to: `DataCenterNames.${dataCenterEnum}` }; await ReplaceInFile(replaceInFileOptions); await CommonUtilities.npmSetup(currentWorkingDirectory); await CommonUtilities.executeOnSleeveResources(currentWorkingDirectory, (path) => { return CommonUtilities.npmSetup(path); }); if (await fs.pathExists(Path.join(currentWorkingDirectory, ".git")) === false) { CommonUtilities.exec("git init", currentWorkingDirectory); } } export async function setup(currentWorkingDirectory: string, serviceName: string, serviceType: string): Promise<string> { const rootPath: string = await CommonUtilities.findGitRootDir(currentWorkingDirectory); const targetPath = Path.join(rootPath, serviceName); let infraResource: IInfrastructure.IInfrastructure<any>; switch (serviceType) { case Resource.ResourcesWeSupportSettingUp.MySqlAzure: { infraResource = new MySqlAzureInfrastructure.MySqlAzureInfrastructure(); break; } case Resource.ResourcesWeSupportSettingUp.WebAppNode: { infraResource = new WebappNodeAzureInfrastructure.WebappNodeAzureInfrastructure(); break; } default: { throw new Error(`Unsupported resource type ${serviceType}`); } } infraResource.initialize(null, targetPath); await infraResource.setup(); return targetPath; } async function hydrateEnvironment(currentWorkingDirectory: string, deploymentType: Resource.DeployType): Promise<InfraResourceType[]> { const resourcesInEnvironment: InfraResourceType[] = []; const rootOfDeploymentPath: string = await CommonUtilities.findGitRootDir(currentWorkingDirectory); const rootSleevePath = Path.join(rootOfDeploymentPath, "sleeve.js"); if (!(fs.existsSync(rootSleevePath))) { // tslint:disable-next-line:no-console console.log("There is no sleeve.js in the root, \ this is not a properly configured project"); process.exit(-1); } await CommonUtilities.npmSetup(rootOfDeploymentPath); const rootResourceGroup: ResourceGroup = require(rootSleevePath); // tslint:disable-next-line:max-line-length const rootResourceGroupInfra = createInfraResource(rootResourceGroup, rootOfDeploymentPath) as ResourceGroupInfrastructure.ResourceGroupInfrastructure; await rootResourceGroupInfra.hydrate(resourcesInEnvironment, deploymentType); resourcesInEnvironment.push(rootResourceGroupInfra); await CommonUtilities.executeOnSleeveResources(rootOfDeploymentPath, async (candidatePath) => { await CommonUtilities.npmSetup(candidatePath); const sleevePath = Path.join(candidatePath, "sleeve.js"); const resource = require(sleevePath); const infraResource = createInfraResource(resource, candidatePath); resourcesInEnvironment.push( await infraResource.hydrate(resourcesInEnvironment, deploymentType)); }); return resourcesInEnvironment; } // TODO: developmentDeploy is really just intended for Node WebAPPs so that // we deploy code in Azure that will properly set up the environment // to take a version of Sleeve from the local machine and not look to // NPM. But we really need to shove this into some kind of property bag // as the name and usage is confusing. export async function deployResources( currentWorkingDirectory: string, deploymentType: Resource.DeployType, developmentDeploy = false) : Promise<InfraResourceType[]> { const resourcesInEnvironment: InfraResourceType[] = await hydrateEnvironment(currentWorkingDirectory, deploymentType); if (resourcesInEnvironment.length === 0) { // tslint:disable-next-line:no-console console.log("There are no resources to deploy"); process.exit(-1); } const promisesToWaitFor = []; for (const resource of resourcesInEnvironment) { promisesToWaitFor.push(resource.deployResource(developmentDeploy)); } await Promise.all(promisesToWaitFor); if (deploymentType === Resource.DeployType.Production) { for (const resource of resourcesInEnvironment) { if (resource instanceof WebappNodeAzureInfrastructure.WebappNodeAzureInfrastructure) { const baseDeployWebApp = // tslint:disable-next-line:max-line-length await (resource as WebappNodeAzureInfrastructure.WebappNodeAzureInfrastructure) .getBaseDeployClassInstance(); console.log( `Web app ${resource.baseName} is available at \ ${await baseDeployWebApp.getDeployedURL()}`); } } } return resourcesInEnvironment; } function getAppInsightsURL(aiName: string, resourceGroupName: string): Promise<string> { return CommonUtilities.runAzCommand(`az resource show --name ${aiName} \ --resource-group ${resourceGroupName} \ --resource-type Microsoft.Insights/components`) .then((result) => { if (result && result.properties) { return `https://ms.portal.azure.com/#resource/subscriptions/\ ${result.properties.TenantId}/resourceGroups/${resourceGroupName}\ /providers/Microsoft.Insights/components/${aiName}/overview`; } return ""; }); } async function createEnvironmentToGetAiLocation( currentWorkingDirectory: string, deploymentType: Resource.DeployType): Promise<string> { const resourcesInEnvironment: InfraResourceType[] = await hydrateEnvironment(currentWorkingDirectory, deploymentType); const rootResourceGroupInfra = CommonUtilities .findGlobalDefaultResourceByType(resourcesInEnvironment, // tslint:disable-next-line:max-line-length ResourceGroupInfrastructure.ResourceGroupInfrastructure) as ResourceGroupInfrastructure.ResourceGroupInfrastructure; const rootAiInfra = CommonUtilities .findGlobalDefaultResourceByType(resourcesInEnvironment, // tslint:disable-next-line:max-line-length ApplicationInsightsInfrastructure.ApplicationInsightsInfrastructure) as ApplicationInsightsInfrastructure.ApplicationInsightsInfrastructure; return getAppInsightsURL(rootAiInfra.appInsightsFullName, rootResourceGroupInfra.resourceGroupName); } export async function getManageResourceData(currentWorkingDirectory: string): Promise<{[deploymentType: string]: string}> { const results: {[deploymentType: string]: string} = {}; results[Resource.DeployType.LocalDevelopment] = await createEnvironmentToGetAiLocation(currentWorkingDirectory, Resource.DeployType.LocalDevelopment); results[Resource.DeployType.Production] = await createEnvironmentToGetAiLocation(currentWorkingDirectory, Resource.DeployType.Production); return results; } function printManageResults(deployType: Resource.DeployType, deployResult: string) { const printPrefix = `${deployType} deployment:`; if (deployResult === "") { console.log(`${printPrefix} NO DEPLOYMENT`); return; } console.log(`${printPrefix} ${deployResult}`); } export async function manageResource(currentWorkingDirectory: string) { const manageResults = await getManageResourceData(currentWorkingDirectory); const devDeployResults = manageResults[Resource.DeployType.LocalDevelopment]; const productionDeployResults = manageResults[Resource.DeployType.Production]; printManageResults(Resource.DeployType.LocalDevelopment, devDeployResults); printManageResults(Resource.DeployType.Production, productionDeployResults); } export function setLoggingIfNeeded(argv: any) { if (argv.verbose) { Winston.add(Winston.transports.File, { filename: "log.txt", level: "silly" }); Winston.remove(Winston.transports.Console); Winston.add(Winston.transports.Console, { level: "silly", stderrLevels: [] }); } }