UNPKG

generator-fastboot

Version:

Spring Boot + Angular/React/Vue in one handy generator

613 lines (563 loc) 22.9 kB
/** * Copyright 2013-2021 the original author or authors from the JHipster project. * * This file is part of the JHipster project, see https://www.jhipster.tech/ * for more information. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-disable consistent-return */ const _ = require('lodash'); const fs = require('fs'); const exec = require('child_process').exec; const chalk = require('chalk'); const BaseBlueprintGenerator = require('../generator-base-blueprint'); const statistics = require('../statistics'); const { defaultConfig } = require('../generator-defaults'); // Global constants const constants = require('../generator-constants'); const { OptionNames } = require('../../jdl/jhipster/application-options'); const { MAVEN } = require('../../jdl/jhipster/build-tool-types'); const { GENERATOR_AZURE_APP_SERVICE } = require('../generator-list'); // Local constants const AZURE_WEBAPP_MAVEN_PLUGIN_VERSION = '1.8.0'; const AZURE_WEBAPP_RUNTIME = 'JAVA|11-java11'; const AZURE_APP_INSIGHTS_STARTER_VERSION = '2.5.1'; let useBlueprints; module.exports = class extends BaseBlueprintGenerator { constructor(args, opts) { super(args, opts); this.option('skip-build', { desc: 'Skips building the application', type: Boolean, defaults: false, }); this.option('skip-deploy', { desc: 'Skips deployment to Azure App Service', type: Boolean, defaults: false, }); this.option('skip-insights', { desc: 'Skips configuration of Azure Application Insights', type: Boolean, defaults: false, }); this.azureSpringCloudSkipBuild = this.options.skipBuild; this.azureSpringCloudSkipDeploy = this.options.skipDeploy || this.options.skipBuild; this.azureSpringCloudSkipInsights = this.options.skipInsights; useBlueprints = !this.fromBlueprint && this.instantiateBlueprints(GENERATOR_AZURE_APP_SERVICE); } _initializing() { return { getConfig() { if (!this.options.fromCli) { this.warning( `Deprecated: JHipster seems to be invoked using Yeoman command. Please use the JHipster CLI. Run ${chalk.red( 'jhipster <command>' )} instead of ${chalk.red('yo jhipster:<command>')}` ); } this.log(chalk.bold('Azure App Service configuration is starting')); this.baseName = this.config.get(OptionNames.BASE_NAME); this.buildTool = this.config.get(OptionNames.BUILD_TOOL); this.azureAppServiceResourceGroupName = ''; // This is not saved, as it is better to get the Azure default variable this.azureAppServicePlan = this.config.get('azureAppServicePlan'); this.azureAppServiceName = this.config.get('azureAppServiceName'); this.azureApplicationInsightsName = this.config.get('azureApplicationInsightsName'); this.azureAppServiceDeploymentType = this.config.get('azureAppServiceDeploymentType'); this.azureAppInsightsInstrumentationKey = ''; this.azureGroupId = ''; }, }; } get initializing() { if (useBlueprints) return; return this._initializing(); } get prompting() { return { checkBuildTool() { if (this.abort) return; const done = this.async(); if (this.buildTool !== MAVEN) { this.error('Sorry, this sub-generator only works with Maven projects for the moment.'); this.abort = true; } done(); }, checkInstallation() { if (this.abort) return; const done = this.async(); exec('az --version', err => { if (err) { this.error( `You don't have the Azure CLI installed. Download it from: ${chalk.red('https://docs.microsoft.com/en-us/cli/azure/install-azure-cli/?WT.mc_id=generator-jhipster-judubois')}` ); this.abort = true; } done(); }); }, getAzureAppServiceDefaults() { if (this.abort) return; const done = this.async(); exec('az configure --list-defaults true', (err, stdout) => { if (err) { this.config.set({ azureAppServiceResourceGroupName: null, }); this.abort = true; this.error('Could not retrieve your Azure default configuration.'); } else { const json = JSON.parse(stdout); Object.keys(json).forEach(key => { if (json[key].name === 'group') { this.azureAppServiceResourceGroupName = json[key].value; } }); if (this.azureAppServiceResourceGroupName === '') { this.log( `Your default Azure resource group is not set up. We recommend doing it using the command '${chalk.yellow('az configure --defaults group=<resource group name>')}` ); this.azureAppServiceResourceGroupName = ''; } } done(); }); }, askForazureAppServiceVariables() { if (this.abort) return; const done = this.async(); const prompts = [ { type: 'input', name: 'azureAppServiceResourceGroupName', message: 'Azure resource group name:', default: this.azureAppServiceResourceGroupName, }, { type: 'input', name: 'azureAppServicePlan', message: 'Azure App Service plan name:', default: this.azureAppServicePlan || `${this.baseName}-plan`, }, { type: 'input', name: 'azureApplicationInsightsName', message: 'Azure Application Insights instance name:', default: this.azureApplicationInsightsName || `${this.baseName}-insights`, }, { type: 'input', name: 'azureAppServiceName', message: 'Azure App Service application name:', default: this.azureAppServiceName || this.baseName, }, ]; this.prompt(prompts).then(props => { this.azureAppServiceResourceGroupName = props.azureAppServiceResourceGroupName; this.azureAppServicePlan = props.azureAppServicePlan; this.azureApplicationInsightsName = props.azureApplicationInsightsName; this.azureAppServiceName = props.azureAppServiceName; done(); }); }, askForAzureDeployType() { if (this.abort) return; const done = this.async(); const prompts = [ { type: 'list', name: 'azureAppServiceDeploymentType', message: 'Which type of deployment do you want ?', choices: [ { value: 'local', name: 'Build and deploy locally', }, { value: 'github-action', name: 'Build and deploy using GitHub Actions', }, ], default: 0, }, ]; this.prompt(prompts).then(props => { this.azureAppServiceDeploymentType = props.azureAppServiceDeploymentType; done(); }); }, }; } get configuring() { return { saveConfig() { if (this.abort) return; this.config.set({ azureAppServicePlan: this.azureAppServicePlan, azureApplicationInsightsName: this.azureApplicationInsightsName, azureAppServiceName: this.azureAppServiceName, azureAppServiceDeploymentType: this.azureAppServiceDeploymentType, }); }, }; } get default() { return { insight() { statistics.sendSubGenEvent('generator', 'azure-app-service'); }, checkAzureGroupId() { if (this.abort) return; const done = this.async(); this.log(chalk.bold(`\nChecking Azure resource group '${this.azureAppServiceResourceGroupName}'...`)); exec(`az group show --name ${this.azureAppServiceResourceGroupName}`, (err, stdout) => { if (err) { this.abort = true; this.error('Could not retrieve your Azure resource group information, it is probably not configured.'); } else { const json = JSON.parse(stdout); this.azureGroupId = json.id; } done(); }); }, azureAzureAppServicePlanCreate() { if (this.abort) return; const done = this.async(); this.log(chalk.bold(`\nChecking Azure App Service plan '${this.azureAppServicePlan}'...`)); let servicePlanAlreadyExists = false; exec(`az appservice plan list --resource-group ${this.azureAppServiceResourceGroupName}`, (err, stdout, stderr) => { if (err) { this.abort = true; this.error('Could not list your Azure App Service plans'); } else { const json = JSON.parse(stdout); if (json.filter(currentPlan => currentPlan.name === this.azureAppServicePlan).length > 0) { this.log(`Service plan '${this.azureAppServicePlan}' already exists, using it`); servicePlanAlreadyExists = true; } try { if (!servicePlanAlreadyExists) { this.log(`Service plan '${this.azureAppServicePlan}' doesn't exist, creating it...`); exec( `az appservice plan create --name ${this.azureAppServicePlan} --is-linux --sku B1 --resource-group ${this.azureAppServiceResourceGroupName}`, err => { if (err) { this.abort = true; this.error('Could not create the Azure App Service plan'); this.log(err); done(); } else { this.log(chalk.green(`Service plan '${this.azureAppServicePlan}' created!`)); this.log(`Service plan '${this.azureAppServicePlan}' uses the 'B1' (basic small) pricing tier, \ which is free for the first 30 days`); done(); } } ); } else { done(); } } catch (e) { this.log(e); this.abort = true; this.error('Could not manage the Azure App Service plan'); } } }); }, azureAzureAppServiceCreate() { if (this.abort) return; const done = this.async(); this.log(chalk.bold(`\nChecking Azure App Service '${this.azureAppServiceName}'...`)); exec(`az webapp list --query "[]" --resource-group ${this.azureAppServiceResourceGroupName}`, (err, stdout, stderr) => { if (err) { this.abort = true; this.error('Could not list your Azure App Service instances'); } else { const json = JSON.parse(stdout); let applicationAlreadyExists = false; if (json.filter(currentApp => currentApp.name === this.azureAppServiceName).length > 0) { this.log(`Application '${this.azureAppServiceName}' already exists, using it`); applicationAlreadyExists = true; } try { if (!applicationAlreadyExists) { this.log(`Application '${this.azureAppServiceName}' doesn't exist, creating it...`); exec( `az webapp create --name ${this.azureAppServiceName} --runtime "${AZURE_WEBAPP_RUNTIME}" --plan ${this.azureAppServicePlan} \ --resource-group ${this.azureAppServiceResourceGroupName}`, err => { if (err) { this.abort = true; this.error('Could not create the Web application'); this.log(err); done(); } else { this.log(chalk.green(`Web application '${this.azureAppServiceName}' created!`)); done(); } } ); } else { done(); } } catch (e) { this.log(e); this.abort = true; this.error('Could not manage the Azure App Service Web application'); } } }); }, azureAzureAppServiceConfig() { if (this.abort) return; const done = this.async(); this.log(`Configuring Azure App Service '${this.azureAppServiceName}'...`); this.log("Enabling 'prod' and 'azure' Spring Boot profiles"); exec( `az webapp config appsettings set --resource-group ${this.azureAppServiceResourceGroupName} --name ${this.azureAppServiceName} --settings SPRING_PROFILES_ACTIVE=prod,azure`, (err, stdout) => { if (err) { this.abort = true; this.error('Could not configure Azure App Service instance'); } done(); } ); }, addAzureAppServiceMavenPlugin() { if (this.abort) return; const done = this.async(); this.log(chalk.bold('\nAdding Azure Web App Maven plugin')); if (this.buildTool === MAVEN) { this.render('pom-plugin.xml.ejs', rendered => { this.addMavenPlugin('com.microsoft.azure', 'azure-webapp-maven-plugin', AZURE_WEBAPP_MAVEN_PLUGIN_VERSION, rendered); }); } done(); }, checkAzureApplicationInsightsExtension() { if (this.abort) return; if (this.azureSpringCloudSkipInsights) return; const done = this.async(); this.log(chalk.bold('\nAzure Application Insights configuration')); this.log('Checking Azure Application Insights CLI extension...'); exec('az extension show --name application-insights', err => { if (err) { this.log('The Azure Application Insights CLI extension is NOT installed, installing it...'); exec('az extension add --name application-insights', err => { if (!err) { this.log(chalk.green('The Azure Application Insights CLI extension is installed!')); } else { this.log(err); this.abort = true; this.error('Could not install the Azure Application Insights extension'); } done(); }); } else { this.log('The Azure Application Insights CLI extension is already installed'); done(); } }); }, configureAzureApplicationInsights() { if (this.abort) return; if (this.azureSpringCloudSkipInsights) return; const done = this.async(); this.log('Checking Azure Application Insights instance...'); exec( `az monitor app-insights component show --app ${this.azureApplicationInsightsName} --resource-group ${this.azureAppServiceResourceGroupName}`, (err, stdout) => { if (err) { this.log('Azure Application Insights instance does not exist, creating it...'); exec( `az monitor app-insights component create --app ${this.azureApplicationInsightsName} --resource-group ${this.azureAppServiceResourceGroupName}`, (err, stdout) => { if (err) { this.log(err); this.abort = true; this.error('Could not create the Azure Application Insights instance'); } else { this.log(chalk.green('The Azure Application Insights instance is created!')); const json = JSON.parse(stdout); this.azureAppInsightsInstrumentationKey = json.instrumentationKey; } done(); } ); } else { this.log('The Azure Application Insights instance already exists, using it'); const json = JSON.parse(stdout); this.azureAppInsightsInstrumentationKey = json.instrumentationKey; done(); } } ); }, addAzureApplicationInsightsDependency() { if (this.abort) return; if (this.azureSpringCloudSkipInsights) return; const done = this.async(); this.log('Adding Azure Application Insights support in the Web Application'); this.addMavenDependency('com.microsoft.azure', 'applicationinsights-spring-boot-starter', AZURE_APP_INSIGHTS_STARTER_VERSION); this.log(`The Application Insights instrumentation key used is: '${chalk.bold(this.azureAppInsightsInstrumentationKey)}'`); done(); }, }; } _loadPlatformConfig(config = _.defaults({}, this.jhipsterConfig, defaultConfig), dest = this) { super.loadPlatformConfig(config, dest); dest.azureAppInsightsInstrumentationKeyEmpty = config.azureAppInsightsInstrumentationKey === ''; } // Public API method used by the getter and also by Blueprints _loading() { return { loadSharedConfig() { this._loadPlatformConfig(); }, }; } get loading() { if (useBlueprints) return; return this._loading(); } _writing() { return { writeFiles() { if (this.abort) return; this.log(chalk.bold('\nCreating Azure App Service deployment files')); this.template('application-azure.yml.ejs', `${constants.SERVER_MAIN_RES_DIR}/config/application-azure.yml`); if (this.azureAppServiceDeploymentType === 'github-action') { this.template('github/workflows/azure-app-service.yml.ejs', '.github/workflows/azure-app-service.yml'); } }, }; } get writing() { if (useBlueprints) return; return this._writing(); } get end() { return { gitHubAction() { if (this.abort) return; if (this.azureAppServiceDeploymentType === 'local') return; const done = this.async(); try { this.log('Test if Git is configured on your project...'); fs.lstatSync('.git'); this.log(chalk.bold('\nUsing existing Git repository')); } catch (e) { // An exception is thrown if the folder doesn't exist this.error( `${chalk.red('Git is not set up on your project!')} You need a GitHub project correctly configured in order to use GitHub Actions.` ); this.abort = true; return; } const gitAddCmd = 'git add .'; this.log(chalk.bold('\nAdding Azure App Service files to the Git repository')); this.log(chalk.cyan(gitAddCmd)); exec(gitAddCmd, (err, stdout, stderr) => { if (err) { this.abort = true; this.error(err); } else { const line = stderr.toString().trimRight(); if (line.trim().length !== 0) this.log(line); this.log(chalk.bold('\nCommitting Azure App Service files')); const gitCommitCmd = 'git commit -m "Add Azure App Service files with automated GitHub Action deployment" --allow-empty'; this.log(chalk.cyan(gitCommitCmd)); exec(gitCommitCmd, (err, stdout, stderr) => { if (err) { this.abort = true; this.error(err); } else { const line = stderr.toString().trimRight(); if (line.trim().length !== 0) this.log(line); this.log(chalk.bold('\nPushing Azure App Service files')); const gitPushCmd = 'git push'; this.log(chalk.cyan(gitPushCmd)); exec(gitPushCmd, (err, stdout, stderr) => { if (err) { this.abort = true; this.error(err); } else { const line = stderr.toString().trimRight(); if (line.trim().length !== 0) this.log(line); this.log(chalk.bold(chalk.green('Congratulations, automated deployment with GitHub Action is set up!'))); this.log( `For the deployment to succeed, you will need to configure a ${chalk.bold( 'AZURE_CREDENTIALS' )} secret in GitHub. Type the following command to generate one for the current Azure Web Application:` ); this.log( chalk.bold( `'az ad sp create-for-rbac --name http://${this.azureAppServiceName} --role contributor --scopes ${this.azureGroupId} --sdk-auth'` ) ); done(); } }); } }); } }); }, productionBuild() { if (this.abort) return; if (this.azureAppServiceDeploymentType === 'github-action') return; if (this.azureSpringCloudSkipBuild) return; const done = this.async(); this.log(chalk.bold('\nBuilding application')); const child = this.buildApplication(this.buildTool, 'prod', false, err => { if (err) { this.abort = true; this.error(err); } done(); }); this.buildCmd = child.buildCmd; child.stdout.on('data', data => { process.stdout.write(data.toString()); }); }, productionDeploy() { if (this.abort) return; if (this.azureAppServiceDeploymentType === 'github-action') return; if (this.azureSpringCloudSkipDeploy) return; const done = this.async(); this.log(chalk.bold('\nDeploying application...')); const child = this.runJavaBuildCommand(this.buildTool, 'prod', 'azure-webapp:deploy', err => { if (err) { this.abort = true; this.log.error(err); } done(); }); this.buildCmd = child.buildCmd; child.stdout.on('data', data => { process.stdout.write(data.toString()); }); }, }; } };